diff --git a/build/.htaccess b/build/.htaccess index e01983226..8d2f25636 100644 --- a/build/.htaccess +++ b/build/.htaccess @@ -1 +1 @@ -deny from all +deny from all diff --git a/build/build.bat b/build/build.bat index a1ae41f2b..e659199de 100644 --- a/build/build.bat +++ b/build/build.bat @@ -1,23 +1,23 @@ -@echo off - -rem ------------------------------------------------------------- -rem build script for Windows. -rem -rem This is the bootstrap script for running build on Windows. -rem -rem @author Qiang Xue -rem @link http://www.yiiframework.com/ -rem @copyright 2008 Yii Software LLC -rem @license http://www.yiiframework.com/license/ -rem @version $Id$ -rem ------------------------------------------------------------- - -@setlocal - -set BUILD_PATH=%~dp0 - -if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe - -%PHP_COMMAND% "%BUILD_PATH%build" %* - +@echo off + +rem ------------------------------------------------------------- +rem build script for Windows. +rem +rem This is the bootstrap script for running build on Windows. +rem +rem @author Qiang Xue +rem @link http://www.yiiframework.com/ +rem @copyright 2008 Yii Software LLC +rem @license http://www.yiiframework.com/license/ +rem @version $Id$ +rem ------------------------------------------------------------- + +@setlocal + +set BUILD_PATH=%~dp0 + +if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe + +%PHP_COMMAND% "%BUILD_PATH%build" %* + @endlocal \ No newline at end of file diff --git a/build/commands/api/assets/css/style.css b/build/commands/api/assets/css/style.css index 709c8a4aa..1ead51c76 100644 --- a/build/commands/api/assets/css/style.css +++ b/build/commands/api/assets/css/style.css @@ -1,32 +1,32 @@ -body -{ -} - -body, div, span, p, input -{ - font-family: Verdana, sans-serif, Arial; - font-size: 10pt; - color: #333333; -} - -#apiPage { -} - -#apiHeader { - padding: 3px; - color: white; - background: #6078BF; - margin-bottom: 5px; - font-weight: bold; -} - -#apiHeader a { - color: white; -} - -#apiFooter { - margin-top: 5px; - padding: 3px; - border-top: 1px solid #BFCFFF; - text-align: center; -} +body +{ +} + +body, div, span, p, input +{ + font-family: Verdana, sans-serif, Arial; + font-size: 10pt; + color: #333333; +} + +#apiPage { +} + +#apiHeader { + padding: 3px; + color: white; + background: #6078BF; + margin-bottom: 5px; + font-weight: bold; +} + +#apiHeader a { + color: white; +} + +#apiFooter { + margin-top: 5px; + padding: 3px; + border-top: 1px solid #BFCFFF; + text-align: center; +} diff --git a/build/commands/lite/css/form.css b/build/commands/lite/css/form.css index 808c2e906..da356d958 100644 --- a/build/commands/lite/css/form.css +++ b/build/commands/lite/css/form.css @@ -1,108 +1,108 @@ -/** - * CSS styles for form and input fields. - * - * These styles are used with form and input fields generated via yiic script. - * - * @author Qiang Xue - * @link http://www.yiiframework.com/ - * @copyright 2008-2010 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -div.yiiForm -{ - border: 2px solid #B7DDF2; - background: #EBF4FB; - margin: 0; - padding: 5px; - width: 550px; -} - -div.errorSummary -{ - border: 2px solid #C00; - padding: 7px 7px 12px 7px; - margin: 0 0 20px 0; - background: #FEE; - font-size: 0.9em; -} - -div.errorSummary p -{ - margin: 0; - padding: 5px; -} - -div.errorSummary ul -{ - margin: 0; - padding: 0 0 0 20px; -} - -div.errorSummary ul li -{ - list-style: square; -} - -div.yiiForm p.hint -{ - color: gray; - font-size: 90%; - margin: 0 0 0 110px; -} - -div.yiiForm fieldset -{ - border: #DDD 1px solid; - margin: 10px 0; - padding: 10px; -} - -div.yiiForm legend -{ - font-weight: bold; -} - -div.yiiForm div.action -{ - clear: left; - margin-left: 110px; - padding: 0.25em 0; -} - -div.yiiForm div.simple, -div.yiiForm div.complex -{ - clear: left; - padding: 0.25em 0; -} - -div.yiiForm div.simple label, -div.yiiForm div.complex span -{ - display: block; - float: left; - margin-right: 10px; - position: relative; - text-align: right; - width: 100px; -} - -div.yiiForm label.error, -div.yiiForm span.error -{ - color: #C00; -} - -div.yiiForm input.error, -div.yiiForm textarea.error -{ - background: #FEE; - border-color: #C00; -} - -div.yiiForm div.simple div, -div.yiiForm div.complex div -{ - margin-left: 110px; -} +/** + * CSS styles for form and input fields. + * + * These styles are used with form and input fields generated via yiic script. + * + * @author Qiang Xue + * @link http://www.yiiframework.com/ + * @copyright 2008-2010 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +div.yiiForm +{ + border: 2px solid #B7DDF2; + background: #EBF4FB; + margin: 0; + padding: 5px; + width: 550px; +} + +div.errorSummary +{ + border: 2px solid #C00; + padding: 7px 7px 12px 7px; + margin: 0 0 20px 0; + background: #FEE; + font-size: 0.9em; +} + +div.errorSummary p +{ + margin: 0; + padding: 5px; +} + +div.errorSummary ul +{ + margin: 0; + padding: 0 0 0 20px; +} + +div.errorSummary ul li +{ + list-style: square; +} + +div.yiiForm p.hint +{ + color: gray; + font-size: 90%; + margin: 0 0 0 110px; +} + +div.yiiForm fieldset +{ + border: #DDD 1px solid; + margin: 10px 0; + padding: 10px; +} + +div.yiiForm legend +{ + font-weight: bold; +} + +div.yiiForm div.action +{ + clear: left; + margin-left: 110px; + padding: 0.25em 0; +} + +div.yiiForm div.simple, +div.yiiForm div.complex +{ + clear: left; + padding: 0.25em 0; +} + +div.yiiForm div.simple label, +div.yiiForm div.complex span +{ + display: block; + float: left; + margin-right: 10px; + position: relative; + text-align: right; + width: 100px; +} + +div.yiiForm label.error, +div.yiiForm span.error +{ + color: #C00; +} + +div.yiiForm input.error, +div.yiiForm textarea.error +{ + background: #FEE; + border-color: #C00; +} + +div.yiiForm div.simple div, +div.yiiForm div.complex div +{ + margin-left: 110px; +} diff --git a/build/commands/lite/css/main.css b/build/commands/lite/css/main.css index 53a0457f9..eb27754e9 100644 --- a/build/commands/lite/css/main.css +++ b/build/commands/lite/css/main.css @@ -1,111 +1,111 @@ -/* begin overall style */ -body -{ - margin: 0; - padding: 0; - background: white; - color: #444; - font: normal 10pt Arial,Helvetica,sans-serif; - background:white url(bg.gif) repeat-x left top; -} - -#page -{ - width: 750px; - margin: 0 auto; - padding: 0; -} - -#header -{ - margin: 0; - padding: 0; -} - -#logo -{ - padding: 20px 0 20px 10px; - font-size: 200%; -} - -#mainmenu -{ - position: absolute; - top: 59px; -} - -#mainmenu ul -{ - padding: 0; - margin: 0; - padding: 0.4em 0 0.3em 0; -} - -#mainmenu ul li -{ - display: inline; -} - -#mainmenu ul li a -{ - padding: 0.4em 0.4em; - color: white; - text-decoration: none; - font-weight: bold; -} - -#mainmenu ul li a:hover, #mainmenu ul li a.active -{ - background: #E6F2FF; - color: #6399cd; -} - -#content -{ - position: relative; - width: 95%; - margin: 0 auto; - padding: 0px; -} - -#footer -{ - padding: 15px; - margin: 20px 0 0 0; - font-size: 0.8em; - text-align: center; - border-top: 1px solid #EEE; -} -/* end of overall style */ - -/* begin data grid style */ -table.dataGrid -{ - background: #FFF9F2; - border-collapse: collapse; - width: 100%; -} - -table.dataGrid th, table.dataGrid td -{ - font-size: 0.9em; - border: 1px #DDE8ED solid; - padding: 0.3em; -} - -table.dataGrid th -{ - background: #D9CEC3; - text-align: left; -} - -table.dataGrid th.label -{ - width: 150px; -} - -table.dataGrid tr.odd -{ - background: #E6F2FF; -} -/* end of data grid style */ +/* begin overall style */ +body +{ + margin: 0; + padding: 0; + background: white; + color: #444; + font: normal 10pt Arial,Helvetica,sans-serif; + background:white url(bg.gif) repeat-x left top; +} + +#page +{ + width: 750px; + margin: 0 auto; + padding: 0; +} + +#header +{ + margin: 0; + padding: 0; +} + +#logo +{ + padding: 20px 0 20px 10px; + font-size: 200%; +} + +#mainmenu +{ + position: absolute; + top: 59px; +} + +#mainmenu ul +{ + padding: 0; + margin: 0; + padding: 0.4em 0 0.3em 0; +} + +#mainmenu ul li +{ + display: inline; +} + +#mainmenu ul li a +{ + padding: 0.4em 0.4em; + color: white; + text-decoration: none; + font-weight: bold; +} + +#mainmenu ul li a:hover, #mainmenu ul li a.active +{ + background: #E6F2FF; + color: #6399cd; +} + +#content +{ + position: relative; + width: 95%; + margin: 0 auto; + padding: 0px; +} + +#footer +{ + padding: 15px; + margin: 20px 0 0 0; + font-size: 0.8em; + text-align: center; + border-top: 1px solid #EEE; +} +/* end of overall style */ + +/* begin data grid style */ +table.dataGrid +{ + background: #FFF9F2; + border-collapse: collapse; + width: 100%; +} + +table.dataGrid th, table.dataGrid td +{ + font-size: 0.9em; + border: 1px #DDE8ED solid; + padding: 0.3em; +} + +table.dataGrid th +{ + background: #D9CEC3; + text-align: left; +} + +table.dataGrid th.label +{ + width: 150px; +} + +table.dataGrid tr.odd +{ + background: #E6F2FF; +} +/* end of data grid style */ diff --git a/build/commands/lite/protected/.htaccess b/build/commands/lite/protected/.htaccess index e01983226..8d2f25636 100644 --- a/build/commands/lite/protected/.htaccess +++ b/build/commands/lite/protected/.htaccess @@ -1 +1 @@ -deny from all +deny from all diff --git a/demos/blog/protected/.htaccess b/demos/blog/protected/.htaccess index e01983226..8d2f25636 100644 --- a/demos/blog/protected/.htaccess +++ b/demos/blog/protected/.htaccess @@ -1 +1 @@ -deny from all +deny from all diff --git a/demos/blog/themes/classic/views/.htaccess b/demos/blog/themes/classic/views/.htaccess index e01983226..8d2f25636 100644 --- a/demos/blog/themes/classic/views/.htaccess +++ b/demos/blog/themes/classic/views/.htaccess @@ -1 +1 @@ -deny from all +deny from all diff --git a/demos/hangman/protected/.htaccess b/demos/hangman/protected/.htaccess index e01983226..8d2f25636 100644 --- a/demos/hangman/protected/.htaccess +++ b/demos/hangman/protected/.htaccess @@ -1 +1 @@ -deny from all +deny from all diff --git a/demos/helloworld/protected/.htaccess b/demos/helloworld/protected/.htaccess index e01983226..8d2f25636 100644 --- a/demos/helloworld/protected/.htaccess +++ b/demos/helloworld/protected/.htaccess @@ -1 +1 @@ -deny from all +deny from all diff --git a/demos/phonebook/flex/.actionScriptProperties b/demos/phonebook/flex/.actionScriptProperties index 4df3b85ee..d651edbce 100644 --- a/demos/phonebook/flex/.actionScriptProperties +++ b/demos/phonebook/flex/.actionScriptProperties @@ -1,24 +1,24 @@ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demos/phonebook/flex/.flexProperties b/demos/phonebook/flex/.flexProperties index ac1c9308c..3e11770b3 100644 --- a/demos/phonebook/flex/.flexProperties +++ b/demos/phonebook/flex/.flexProperties @@ -1,2 +1,2 @@ - - + + diff --git a/demos/phonebook/flex/.project b/demos/phonebook/flex/.project index f5ef55455..2181f7295 100644 --- a/demos/phonebook/flex/.project +++ b/demos/phonebook/flex/.project @@ -1,18 +1,18 @@ - - - phonebook - - - - - - com.adobe.flexbuilder.project.flexbuilder - - - - - - com.adobe.flexbuilder.project.flexnature - com.adobe.flexbuilder.project.actionscriptnature - - + + + phonebook + + + + + + com.adobe.flexbuilder.project.flexbuilder + + + + + + com.adobe.flexbuilder.project.flexnature + com.adobe.flexbuilder.project.actionscriptnature + + diff --git a/demos/phonebook/flex/.settings/com.adobe.flexbuilder.project.prefs b/demos/phonebook/flex/.settings/com.adobe.flexbuilder.project.prefs index 0246193a7..4348f48f9 100644 --- a/demos/phonebook/flex/.settings/com.adobe.flexbuilder.project.prefs +++ b/demos/phonebook/flex/.settings/com.adobe.flexbuilder.project.prefs @@ -1,3 +1,3 @@ -#Wed Oct 08 08:18:30 EDT 2008 -eclipse.preferences.version=1 -upgradeSDK/fb3= +#Wed Oct 08 08:18:30 EDT 2008 +eclipse.preferences.version=1 +upgradeSDK/fb3= diff --git a/demos/phonebook/flex/.settings/org.eclipse.core.resources.prefs b/demos/phonebook/flex/.settings/org.eclipse.core.resources.prefs index 662bb271c..415feac2f 100644 --- a/demos/phonebook/flex/.settings/org.eclipse.core.resources.prefs +++ b/demos/phonebook/flex/.settings/org.eclipse.core.resources.prefs @@ -1,3 +1,3 @@ -#Wed Oct 08 03:19:04 GMT 2008 -eclipse.preferences.version=1 -encoding/=utf-8 +#Wed Oct 08 03:19:04 GMT 2008 +eclipse.preferences.version=1 +encoding/=utf-8 diff --git a/demos/phonebook/flex/bin/AC_OETags.js b/demos/phonebook/flex/bin/AC_OETags.js index d1f066bdf..e77e6fd8a 100644 --- a/demos/phonebook/flex/bin/AC_OETags.js +++ b/demos/phonebook/flex/bin/AC_OETags.js @@ -1,276 +1,276 @@ -// Flash Player Version Detection - Rev 1.6 -// Detect Client Browser type -// Copyright(c) 2005-2006 Adobe Macromedia Software, LLC. All rights reserved. -var isIE = (navigator.appVersion.indexOf("MSIE") != -1) ? true : false; -var isWin = (navigator.appVersion.toLowerCase().indexOf("win") != -1) ? true : false; -var isOpera = (navigator.userAgent.indexOf("Opera") != -1) ? true : false; - -function ControlVersion() -{ - var version; - var axo; - var e; - - // NOTE : new ActiveXObject(strFoo) throws an exception if strFoo isn't in the registry - - try { - // version will be set for 7.X or greater players - axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7"); - version = axo.GetVariable("$version"); - } catch (e) { - } - - if (!version) - { - try { - // version will be set for 6.X players only - axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6"); - - // installed player is some revision of 6.0 - // GetVariable("$version") crashes for versions 6.0.22 through 6.0.29, - // so we have to be careful. - - // default to the first public version - version = "WIN 6,0,21,0"; - - // throws if AllowScripAccess does not exist (introduced in 6.0r47) - axo.AllowScriptAccess = "always"; - - // safe to call for 6.0r47 or greater - version = axo.GetVariable("$version"); - - } catch (e) { - } - } - - if (!version) - { - try { - // version will be set for 4.X or 5.X player - axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.3"); - version = axo.GetVariable("$version"); - } catch (e) { - } - } - - if (!version) - { - try { - // version will be set for 3.X player - axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.3"); - version = "WIN 3,0,18,0"; - } catch (e) { - } - } - - if (!version) - { - try { - // version will be set for 2.X player - axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"); - version = "WIN 2,0,0,11"; - } catch (e) { - version = -1; - } - } - - return version; -} - -// JavaScript helper required to detect Flash Player PlugIn version information -function GetSwfVer(){ - // NS/Opera version >= 3 check for Flash plugin in plugin array - var flashVer = -1; - - if (navigator.plugins != null && navigator.plugins.length > 0) { - if (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]) { - var swVer2 = navigator.plugins["Shockwave Flash 2.0"] ? " 2.0" : ""; - var flashDescription = navigator.plugins["Shockwave Flash" + swVer2].description; - var descArray = flashDescription.split(" "); - var tempArrayMajor = descArray[2].split("."); - var versionMajor = tempArrayMajor[0]; - var versionMinor = tempArrayMajor[1]; - var versionRevision = descArray[3]; - if (versionRevision == "") { - versionRevision = descArray[4]; - } - if (versionRevision[0] == "d") { - versionRevision = versionRevision.substring(1); - } else if (versionRevision[0] == "r") { - versionRevision = versionRevision.substring(1); - if (versionRevision.indexOf("d") > 0) { - versionRevision = versionRevision.substring(0, versionRevision.indexOf("d")); - } - } - var flashVer = versionMajor + "." + versionMinor + "." + versionRevision; - } - } - // MSN/WebTV 2.6 supports Flash 4 - else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.6") != -1) flashVer = 4; - // WebTV 2.5 supports Flash 3 - else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.5") != -1) flashVer = 3; - // older WebTV supports Flash 2 - else if (navigator.userAgent.toLowerCase().indexOf("webtv") != -1) flashVer = 2; - else if ( isIE && isWin && !isOpera ) { - flashVer = ControlVersion(); - } - return flashVer; -} - -// When called with reqMajorVer, reqMinorVer, reqRevision returns true if that version or greater is available -function DetectFlashVer(reqMajorVer, reqMinorVer, reqRevision) -{ - versionStr = GetSwfVer(); - if (versionStr == -1 ) { - return false; - } else if (versionStr != 0) { - if(isIE && isWin && !isOpera) { - // Given "WIN 2,0,0,11" - tempArray = versionStr.split(" "); // ["WIN", "2,0,0,11"] - tempString = tempArray[1]; // "2,0,0,11" - versionArray = tempString.split(","); // ['2', '0', '0', '11'] - } else { - versionArray = versionStr.split("."); - } - var versionMajor = versionArray[0]; - var versionMinor = versionArray[1]; - var versionRevision = versionArray[2]; - - // is the major.revision >= requested major.revision AND the minor version >= requested minor - if (versionMajor > parseFloat(reqMajorVer)) { - return true; - } else if (versionMajor == parseFloat(reqMajorVer)) { - if (versionMinor > parseFloat(reqMinorVer)) - return true; - else if (versionMinor == parseFloat(reqMinorVer)) { - if (versionRevision >= parseFloat(reqRevision)) - return true; - } - } - return false; - } -} - -function AC_AddExtension(src, ext) -{ - if (src.indexOf('?') != -1) - return src.replace(/\?/, ext+'?'); - else - return src + ext; -} - -function AC_Generateobj(objAttrs, params, embedAttrs) -{ - var str = ''; - if (isIE && isWin && !isOpera) - { - str += ' '; - str += ''; - } else { - str += '= 3 check for Flash plugin in plugin array + var flashVer = -1; + + if (navigator.plugins != null && navigator.plugins.length > 0) { + if (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]) { + var swVer2 = navigator.plugins["Shockwave Flash 2.0"] ? " 2.0" : ""; + var flashDescription = navigator.plugins["Shockwave Flash" + swVer2].description; + var descArray = flashDescription.split(" "); + var tempArrayMajor = descArray[2].split("."); + var versionMajor = tempArrayMajor[0]; + var versionMinor = tempArrayMajor[1]; + var versionRevision = descArray[3]; + if (versionRevision == "") { + versionRevision = descArray[4]; + } + if (versionRevision[0] == "d") { + versionRevision = versionRevision.substring(1); + } else if (versionRevision[0] == "r") { + versionRevision = versionRevision.substring(1); + if (versionRevision.indexOf("d") > 0) { + versionRevision = versionRevision.substring(0, versionRevision.indexOf("d")); + } + } + var flashVer = versionMajor + "." + versionMinor + "." + versionRevision; + } + } + // MSN/WebTV 2.6 supports Flash 4 + else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.6") != -1) flashVer = 4; + // WebTV 2.5 supports Flash 3 + else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.5") != -1) flashVer = 3; + // older WebTV supports Flash 2 + else if (navigator.userAgent.toLowerCase().indexOf("webtv") != -1) flashVer = 2; + else if ( isIE && isWin && !isOpera ) { + flashVer = ControlVersion(); + } + return flashVer; +} + +// When called with reqMajorVer, reqMinorVer, reqRevision returns true if that version or greater is available +function DetectFlashVer(reqMajorVer, reqMinorVer, reqRevision) +{ + versionStr = GetSwfVer(); + if (versionStr == -1 ) { + return false; + } else if (versionStr != 0) { + if(isIE && isWin && !isOpera) { + // Given "WIN 2,0,0,11" + tempArray = versionStr.split(" "); // ["WIN", "2,0,0,11"] + tempString = tempArray[1]; // "2,0,0,11" + versionArray = tempString.split(","); // ['2', '0', '0', '11'] + } else { + versionArray = versionStr.split("."); + } + var versionMajor = versionArray[0]; + var versionMinor = versionArray[1]; + var versionRevision = versionArray[2]; + + // is the major.revision >= requested major.revision AND the minor version >= requested minor + if (versionMajor > parseFloat(reqMajorVer)) { + return true; + } else if (versionMajor == parseFloat(reqMajorVer)) { + if (versionMinor > parseFloat(reqMinorVer)) + return true; + else if (versionMinor == parseFloat(reqMinorVer)) { + if (versionRevision >= parseFloat(reqRevision)) + return true; + } + } + return false; + } +} + +function AC_AddExtension(src, ext) +{ + if (src.indexOf('?') != -1) + return src.replace(/\?/, ext+'?'); + else + return src + ext; +} + +function AC_Generateobj(objAttrs, params, embedAttrs) +{ + var str = ''; + if (isIE && isWin && !isOpera) + { + str += ' '; + str += ''; + } else { + str += '= 2 && hash.charAt(0) == "?") { - hash = hash.substring(1); - } - return hash; - } - - /* Get the current location hash excluding the '#' symbol. */ - function getHash() { - // It would be nice if we could use document.location.hash here, - // but it's faulty sometimes. - var idx = document.location.href.indexOf('#'); - return (idx >= 0) ? document.location.href.substr(idx+1) : ''; - } - - /* Get the current location hash excluding the '#' symbol. */ - function setHash(hash) { - // It would be nice if we could use document.location.hash here, - // but it's faulty sometimes. - if (hash == '') hash = '#' - document.location.hash = hash; - } - - function createState(baseUrl, newUrl, flexAppUrl) { - return { 'baseUrl': baseUrl, 'newUrl': newUrl, 'flexAppUrl': flexAppUrl, 'title': null }; - } - - /* Add a history entry to the browser. - * baseUrl: the portion of the location prior to the '#' - * newUrl: the entire new URL, including '#' and following fragment - * flexAppUrl: the portion of the location following the '#' only - */ - function addHistoryEntry(baseUrl, newUrl, flexAppUrl) { - - //delete all the history entries - forwardStack = []; - - if (browser.ie) { - //Check to see if we are being asked to do a navigate for the first - //history entry, and if so ignore, because it's coming from the creation - //of the history iframe - if (flexAppUrl == defaultHash && document.location.href == initialHref && window['_ie_firstload']) { - currentHref = initialHref; - return; - } - if ((!flexAppUrl || flexAppUrl == defaultHash) && window['_ie_firstload']) { - newUrl = baseUrl + '#' + defaultHash; - flexAppUrl = defaultHash; - } else { - // for IE, tell the history frame to go somewhere without a '#' - // in order to get this entry into the browser history. - getHistoryFrame().src = historyFrameSourcePrefix + flexAppUrl; - } - setHash(flexAppUrl); - } else { - - //ADR - if (backStack.length == 0 && initialState.flexAppUrl == flexAppUrl) { - initialState = createState(baseUrl, newUrl, flexAppUrl); - } else if(backStack.length > 0 && backStack[backStack.length - 1].flexAppUrl == flexAppUrl) { - backStack[backStack.length - 1] = createState(baseUrl, newUrl, flexAppUrl); - } - - if (browser.safari) { - // for Safari, submit a form whose action points to the desired URL - if (browser.version <= 419.3) { - var file = window.location.pathname.toString(); - file = file.substring(file.lastIndexOf("/")+1); - getFormElement().innerHTML = '
'; - //get the current elements and add them to the form - var qs = window.location.search.substring(1); - var qs_arr = qs.split("&"); - for (var i = 0; i < qs_arr.length; i++) { - var tmp = qs_arr[i].split("="); - var elem = document.createElement("input"); - elem.type = "hidden"; - elem.name = tmp[0]; - elem.value = tmp[1]; - document.forms.historyForm.appendChild(elem); - } - document.forms.historyForm.submit(); - } else { - top.location.hash = flexAppUrl; - } - // We also have to maintain the history by hand for Safari - historyHash[history.length] = flexAppUrl; - _storeStates(); - } else { - // Otherwise, write an anchor into the page and tell the browser to go there - addAnchor(flexAppUrl); - setHash(flexAppUrl); - } - } - backStack.push(createState(baseUrl, newUrl, flexAppUrl)); - } - - function _storeStates() { - if (browser.safari) { - getRememberElement().value = historyHash.join(","); - } - } - - function handleBackButton() { - //The "current" page is always at the top of the history stack. - var current = backStack.pop(); - if (!current) { return; } - var last = backStack[backStack.length - 1]; - if (!last && backStack.length == 0){ - last = initialState; - } - forwardStack.push(current); - } - - function handleForwardButton() { - //summary: private method. Do not call this directly. - - var last = forwardStack.pop(); - if (!last) { return; } - backStack.push(last); - } - - function handleArbitraryUrl() { - //delete all the history entries - forwardStack = []; - } - - /* Called periodically to poll to see if we need to detect navigation that has occurred */ - function checkForUrlChange() { - - if (browser.ie) { - if (currentHref != document.location.href && currentHref + '#' != document.location.href) { - //This occurs when the user has navigated to a specific URL - //within the app, and didn't use browser back/forward - //IE seems to have a bug where it stops updating the URL it - //shows the end-user at this point, but programatically it - //appears to be correct. Do a full app reload to get around - //this issue. - if (browser.version < 7) { - currentHref = document.location.href; - document.location.reload(); - } else { - if (getHash() != getIframeHash()) { - // this.iframe.src = this.blankURL + hash; - var sourceToSet = historyFrameSourcePrefix + getHash(); - getHistoryFrame().src = sourceToSet; - } - } - } - } - - if (browser.safari) { - // For Safari, we have to check to see if history.length changed. - if (currentHistoryLength >= 0 && history.length != currentHistoryLength) { - //alert("did change: " + history.length + ", " + historyHash.length + "|" + historyHash[history.length] + "|>" + historyHash.join("|")); - // If it did change, then we have to look the old state up - // in our hand-maintained array since document.location.hash - // won't have changed, then call back into BrowserManager. - currentHistoryLength = history.length; - var flexAppUrl = historyHash[currentHistoryLength]; - if (flexAppUrl == '') { - //flexAppUrl = defaultHash; - } - //ADR: to fix multiple - if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) { - var pl = getPlayers(); - for (var i = 0; i < pl.length; i++) { - pl[i].browserURLChange(flexAppUrl); - } - } else { - getPlayer().browserURLChange(flexAppUrl); - } - _storeStates(); - } - } - if (browser.firefox) { - if (currentHref != document.location.href) { - var bsl = backStack.length; - - var urlActions = { - back: false, - forward: false, - set: false - } - - if ((window.location.hash == initialHash || window.location.href == initialHref) && (bsl == 1)) { - urlActions.back = true; - // FIXME: could this ever be a forward button? - // we can't clear it because we still need to check for forwards. Ugg. - // clearInterval(this.locationTimer); - handleBackButton(); - } - - // first check to see if we could have gone forward. We always halt on - // a no-hash item. - if (forwardStack.length > 0) { - if (forwardStack[forwardStack.length-1].flexAppUrl == getHash()) { - urlActions.forward = true; - handleForwardButton(); - } - } - - // ok, that didn't work, try someplace back in the history stack - if ((bsl >= 2) && (backStack[bsl - 2])) { - if (backStack[bsl - 2].flexAppUrl == getHash()) { - urlActions.back = true; - handleBackButton(); - } - } - - if (!urlActions.back && !urlActions.forward) { - var foundInStacks = { - back: -1, - forward: -1 - } - - for (var i = 0; i < backStack.length; i++) { - if (backStack[i].flexAppUrl == getHash() && i != (bsl - 2)) { - arbitraryUrl = true; - foundInStacks.back = i; - } - } - for (var i = 0; i < forwardStack.length; i++) { - if (forwardStack[i].flexAppUrl == getHash() && i != (bsl - 2)) { - arbitraryUrl = true; - foundInStacks.forward = i; - } - } - handleArbitraryUrl(); - } - - // Firefox changed; do a callback into BrowserManager to tell it. - currentHref = document.location.href; - var flexAppUrl = getHash(); - if (flexAppUrl == '') { - //flexAppUrl = defaultHash; - } - //ADR: to fix multiple - if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) { - var pl = getPlayers(); - for (var i = 0; i < pl.length; i++) { - pl[i].browserURLChange(flexAppUrl); - } - } else { - getPlayer().browserURLChange(flexAppUrl); - } - } - } - //setTimeout(checkForUrlChange, 50); - } - - /* Write an anchor into the page to legitimize it as a URL for Firefox et al. */ - function addAnchor(flexAppUrl) - { - if (document.getElementsByName(flexAppUrl).length == 0) { - getAnchorElement().innerHTML += "" + flexAppUrl + ""; - } - } - - var _initialize = function () { - if (browser.ie) - { - var scripts = document.getElementsByTagName('script'); - for (var i = 0, s; s = scripts[i]; i++) { - if (s.src.indexOf("history.js") > -1) { - var iframe_location = (new String(s.src)).replace("history.js", "historyFrame.html"); - } - } - historyFrameSourcePrefix = iframe_location + "?"; - var src = historyFrameSourcePrefix; - - var iframe = document.createElement("iframe"); - iframe.id = 'ie_historyFrame'; - iframe.name = 'ie_historyFrame'; - //iframe.src = historyFrameSourcePrefix; - try { - document.body.appendChild(iframe); - } catch(e) { - setTimeout(function() { - document.body.appendChild(iframe); - }, 0); - } - } - - if (browser.safari) - { - var rememberDiv = document.createElement("div"); - rememberDiv.id = 'safari_rememberDiv'; - document.body.appendChild(rememberDiv); - rememberDiv.innerHTML = ''; - - var formDiv = document.createElement("div"); - formDiv.id = 'safari_formDiv'; - document.body.appendChild(formDiv); - - var reloader_content = document.createElement('div'); - reloader_content.id = 'safarireloader'; - var scripts = document.getElementsByTagName('script'); - for (var i = 0, s; s = scripts[i]; i++) { - if (s.src.indexOf("history.js") > -1) { - html = (new String(s.src)).replace(".js", ".html"); - } - } - reloader_content.innerHTML = ''; - document.body.appendChild(reloader_content); - reloader_content.style.position = 'absolute'; - reloader_content.style.left = reloader_content.style.top = '-9999px'; - iframe = reloader_content.getElementsByTagName('iframe')[0]; - - if (document.getElementById("safari_remember_field").value != "" ) { - historyHash = document.getElementById("safari_remember_field").value.split(","); - } - - } - - if (browser.firefox) - { - var anchorDiv = document.createElement("div"); - anchorDiv.id = 'firefox_anchorDiv'; - document.body.appendChild(anchorDiv); - } - - //setTimeout(checkForUrlChange, 50); - } - - return { - historyHash: historyHash, - backStack: function() { return backStack; }, - forwardStack: function() { return forwardStack }, - getPlayer: getPlayer, - initialize: function(src) { - _initialize(src); - }, - setURL: function(url) { - document.location.href = url; - }, - getURL: function() { - return document.location.href; - }, - getTitle: function() { - return document.title; - }, - setTitle: function(title) { - try { - backStack[backStack.length - 1].title = title; - } catch(e) { } - //if on safari, set the title to be the empty string. - if (browser.safari) { - if (title == "") { - try { - var tmp = window.location.href.toString(); - title = tmp.substring((tmp.lastIndexOf("/")+1), tmp.lastIndexOf("#")); - } catch(e) { - title = ""; - } - } - } - document.title = title; - }, - setDefaultURL: function(def) - { - defaultHash = def; - def = getHash(); - //trailing ? is important else an extra frame gets added to the history - //when navigating back to the first page. Alternatively could check - //in history frame navigation to compare # and ?. - if (browser.ie) - { - window['_ie_firstload'] = true; - var sourceToSet = historyFrameSourcePrefix + def; - var func = function() { - getHistoryFrame().src = sourceToSet; - window.location.replace("#" + def); - setInterval(checkForUrlChange, 50); - } - try { - func(); - } catch(e) { - window.setTimeout(function() { func(); }, 0); - } - } - - if (browser.safari) - { - currentHistoryLength = history.length; - if (historyHash.length == 0) { - historyHash[currentHistoryLength] = def; - var newloc = "#" + def; - window.location.replace(newloc); - } else { - //alert(historyHash[historyHash.length-1]); - } - //setHash(def); - setInterval(checkForUrlChange, 50); - } - - - if (browser.firefox || browser.opera) - { - var reg = new RegExp("#" + def + "$"); - if (window.location.toString().match(reg)) { - } else { - var newloc ="#" + def; - window.location.replace(newloc); - } - setInterval(checkForUrlChange, 50); - //setHash(def); - } - - }, - - /* Set the current browser URL; called from inside BrowserManager to propagate - * the application state out to the container. - */ - setBrowserURL: function(flexAppUrl, objectId) { - if (browser.ie && typeof objectId != "undefined") { - currentObjectId = objectId; - } - //fromIframe = fromIframe || false; - //fromFlex = fromFlex || false; - //alert("setBrowserURL: " + flexAppUrl); - //flexAppUrl = (flexAppUrl == "") ? defaultHash : flexAppUrl ; - - var pos = document.location.href.indexOf('#'); - var baseUrl = pos != -1 ? document.location.href.substr(0, pos) : document.location.href; - var newUrl = baseUrl + '#' + flexAppUrl; - - if (document.location.href != newUrl && document.location.href + '#' != newUrl) { - currentHref = newUrl; - addHistoryEntry(baseUrl, newUrl, flexAppUrl); - currentHistoryLength = history.length; - } - - return false; - }, - - browserURLChange: function(flexAppUrl) { - var objectId = null; - if (browser.ie && currentObjectId != null) { - objectId = currentObjectId; - } - pendingURL = ''; - - if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) { - var pl = getPlayers(); - for (var i = 0; i < pl.length; i++) { - try { - pl[i].browserURLChange(flexAppUrl); - } catch(e) { } - } - } else { - try { - getPlayer(objectId).browserURLChange(flexAppUrl); - } catch(e) { } - } - - currentObjectId = null; - } - - } - -})(); - -// Initialization - -// Automated unit testing and other diagnostics - -function setURL(url) -{ - document.location.href = url; -} - -function backButton() -{ - history.back(); -} - -function forwardButton() -{ - history.forward(); -} - -function goForwardOrBackInHistory(step) -{ - history.go(step); -} - -//BrowserHistoryUtils.addEvent(window, "load", function() { BrowserHistory.initialize(); }); -(function(i) { - var u =navigator.userAgent;var e=/*@cc_on!@*/false; - var st = setTimeout; - if(/webkit/i.test(u)){ - st(function(){ - var dr=document.readyState; - if(dr=="loaded"||dr=="complete"){i()} - else{st(arguments.callee,10);}},10); - } else if((/mozilla/i.test(u)&&!/(compati)/.test(u)) || (/opera/i.test(u))){ - document.addEventListener("DOMContentLoaded",i,false); - } else if(e){ - (function(){ - var t=document.createElement('doc:rdy'); - try{t.doScroll('left'); - i();t=null; - }catch(e){st(arguments.callee,0);}})(); - } else{ - window.onload=i; - } -})( function() {BrowserHistory.initialize();} ); +BrowserHistoryUtils = { + addEvent: function(elm, evType, fn, useCapture) { + useCapture = useCapture || false; + if (elm.addEventListener) { + elm.addEventListener(evType, fn, useCapture); + return true; + } + else if (elm.attachEvent) { + var r = elm.attachEvent('on' + evType, fn); + return r; + } + else { + elm['on' + evType] = fn; + } + } +} + +BrowserHistory = (function() { + // type of browser + var browser = { + ie: false, + firefox: false, + safari: false, + opera: false, + version: -1 + }; + + // if setDefaultURL has been called, our first clue + // that the SWF is ready and listening + //var swfReady = false; + + // the URL we'll send to the SWF once it is ready + //var pendingURL = ''; + + // Default app state URL to use when no fragment ID present + var defaultHash = ''; + + // Last-known app state URL + var currentHref = document.location.href; + + // Initial URL (used only by IE) + var initialHref = document.location.href; + + // Initial URL (used only by IE) + var initialHash = document.location.hash; + + // History frame source URL prefix (used only by IE) + var historyFrameSourcePrefix = 'history/historyFrame.html?'; + + // History maintenance (used only by Safari) + var currentHistoryLength = -1; + + var historyHash = []; + + var initialState = createState(initialHref, initialHref + '#' + initialHash, initialHash); + + var backStack = []; + var forwardStack = []; + + var currentObjectId = null; + + //UserAgent detection + var useragent = navigator.userAgent.toLowerCase(); + + if (useragent.indexOf("opera") != -1) { + browser.opera = true; + } else if (useragent.indexOf("msie") != -1) { + browser.ie = true; + browser.version = parseFloat(useragent.substring(useragent.indexOf('msie') + 4)); + } else if (useragent.indexOf("safari") != -1) { + browser.safari = true; + browser.version = parseFloat(useragent.substring(useragent.indexOf('safari') + 7)); + } else if (useragent.indexOf("gecko") != -1) { + browser.firefox = true; + } + + if (browser.ie == true && browser.version == 7) { + window["_ie_firstload"] = false; + } + + // Accessor functions for obtaining specific elements of the page. + function getHistoryFrame() + { + return document.getElementById('ie_historyFrame'); + } + + function getAnchorElement() + { + return document.getElementById('firefox_anchorDiv'); + } + + function getFormElement() + { + return document.getElementById('safari_formDiv'); + } + + function getRememberElement() + { + return document.getElementById("safari_remember_field"); + } + + /* Get the Flash player object for performing ExternalInterface callbacks. */ + function getPlayer(objectId) { + var objectId = objectId || null; + var player = null; /* AJH, needed? = document.getElementById(getPlayerId()); */ + if (browser.ie && objectId != null) { + player = document.getElementById(objectId); + } + if (player == null) { + player = document.getElementsByTagName('object')[0]; + } + + if (player == null || player.object == null) { + player = document.getElementsByTagName('embed')[0]; + } + + return player; + } + + function getPlayers() { + var players = []; + if (players.length == 0) { + var tmp = document.getElementsByTagName('object'); + players = tmp; + } + + if (players.length == 0 || players[0].object == null) { + var tmp = document.getElementsByTagName('embed'); + players = tmp; + } + return players; + } + + function getIframeHash() { + var doc = getHistoryFrame().contentWindow.document; + var hash = String(doc.location.search); + if (hash.length == 1 && hash.charAt(0) == "?") { + hash = ""; + } + else if (hash.length >= 2 && hash.charAt(0) == "?") { + hash = hash.substring(1); + } + return hash; + } + + /* Get the current location hash excluding the '#' symbol. */ + function getHash() { + // It would be nice if we could use document.location.hash here, + // but it's faulty sometimes. + var idx = document.location.href.indexOf('#'); + return (idx >= 0) ? document.location.href.substr(idx+1) : ''; + } + + /* Get the current location hash excluding the '#' symbol. */ + function setHash(hash) { + // It would be nice if we could use document.location.hash here, + // but it's faulty sometimes. + if (hash == '') hash = '#' + document.location.hash = hash; + } + + function createState(baseUrl, newUrl, flexAppUrl) { + return { 'baseUrl': baseUrl, 'newUrl': newUrl, 'flexAppUrl': flexAppUrl, 'title': null }; + } + + /* Add a history entry to the browser. + * baseUrl: the portion of the location prior to the '#' + * newUrl: the entire new URL, including '#' and following fragment + * flexAppUrl: the portion of the location following the '#' only + */ + function addHistoryEntry(baseUrl, newUrl, flexAppUrl) { + + //delete all the history entries + forwardStack = []; + + if (browser.ie) { + //Check to see if we are being asked to do a navigate for the first + //history entry, and if so ignore, because it's coming from the creation + //of the history iframe + if (flexAppUrl == defaultHash && document.location.href == initialHref && window['_ie_firstload']) { + currentHref = initialHref; + return; + } + if ((!flexAppUrl || flexAppUrl == defaultHash) && window['_ie_firstload']) { + newUrl = baseUrl + '#' + defaultHash; + flexAppUrl = defaultHash; + } else { + // for IE, tell the history frame to go somewhere without a '#' + // in order to get this entry into the browser history. + getHistoryFrame().src = historyFrameSourcePrefix + flexAppUrl; + } + setHash(flexAppUrl); + } else { + + //ADR + if (backStack.length == 0 && initialState.flexAppUrl == flexAppUrl) { + initialState = createState(baseUrl, newUrl, flexAppUrl); + } else if(backStack.length > 0 && backStack[backStack.length - 1].flexAppUrl == flexAppUrl) { + backStack[backStack.length - 1] = createState(baseUrl, newUrl, flexAppUrl); + } + + if (browser.safari) { + // for Safari, submit a form whose action points to the desired URL + if (browser.version <= 419.3) { + var file = window.location.pathname.toString(); + file = file.substring(file.lastIndexOf("/")+1); + getFormElement().innerHTML = '
'; + //get the current elements and add them to the form + var qs = window.location.search.substring(1); + var qs_arr = qs.split("&"); + for (var i = 0; i < qs_arr.length; i++) { + var tmp = qs_arr[i].split("="); + var elem = document.createElement("input"); + elem.type = "hidden"; + elem.name = tmp[0]; + elem.value = tmp[1]; + document.forms.historyForm.appendChild(elem); + } + document.forms.historyForm.submit(); + } else { + top.location.hash = flexAppUrl; + } + // We also have to maintain the history by hand for Safari + historyHash[history.length] = flexAppUrl; + _storeStates(); + } else { + // Otherwise, write an anchor into the page and tell the browser to go there + addAnchor(flexAppUrl); + setHash(flexAppUrl); + } + } + backStack.push(createState(baseUrl, newUrl, flexAppUrl)); + } + + function _storeStates() { + if (browser.safari) { + getRememberElement().value = historyHash.join(","); + } + } + + function handleBackButton() { + //The "current" page is always at the top of the history stack. + var current = backStack.pop(); + if (!current) { return; } + var last = backStack[backStack.length - 1]; + if (!last && backStack.length == 0){ + last = initialState; + } + forwardStack.push(current); + } + + function handleForwardButton() { + //summary: private method. Do not call this directly. + + var last = forwardStack.pop(); + if (!last) { return; } + backStack.push(last); + } + + function handleArbitraryUrl() { + //delete all the history entries + forwardStack = []; + } + + /* Called periodically to poll to see if we need to detect navigation that has occurred */ + function checkForUrlChange() { + + if (browser.ie) { + if (currentHref != document.location.href && currentHref + '#' != document.location.href) { + //This occurs when the user has navigated to a specific URL + //within the app, and didn't use browser back/forward + //IE seems to have a bug where it stops updating the URL it + //shows the end-user at this point, but programatically it + //appears to be correct. Do a full app reload to get around + //this issue. + if (browser.version < 7) { + currentHref = document.location.href; + document.location.reload(); + } else { + if (getHash() != getIframeHash()) { + // this.iframe.src = this.blankURL + hash; + var sourceToSet = historyFrameSourcePrefix + getHash(); + getHistoryFrame().src = sourceToSet; + } + } + } + } + + if (browser.safari) { + // For Safari, we have to check to see if history.length changed. + if (currentHistoryLength >= 0 && history.length != currentHistoryLength) { + //alert("did change: " + history.length + ", " + historyHash.length + "|" + historyHash[history.length] + "|>" + historyHash.join("|")); + // If it did change, then we have to look the old state up + // in our hand-maintained array since document.location.hash + // won't have changed, then call back into BrowserManager. + currentHistoryLength = history.length; + var flexAppUrl = historyHash[currentHistoryLength]; + if (flexAppUrl == '') { + //flexAppUrl = defaultHash; + } + //ADR: to fix multiple + if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) { + var pl = getPlayers(); + for (var i = 0; i < pl.length; i++) { + pl[i].browserURLChange(flexAppUrl); + } + } else { + getPlayer().browserURLChange(flexAppUrl); + } + _storeStates(); + } + } + if (browser.firefox) { + if (currentHref != document.location.href) { + var bsl = backStack.length; + + var urlActions = { + back: false, + forward: false, + set: false + } + + if ((window.location.hash == initialHash || window.location.href == initialHref) && (bsl == 1)) { + urlActions.back = true; + // FIXME: could this ever be a forward button? + // we can't clear it because we still need to check for forwards. Ugg. + // clearInterval(this.locationTimer); + handleBackButton(); + } + + // first check to see if we could have gone forward. We always halt on + // a no-hash item. + if (forwardStack.length > 0) { + if (forwardStack[forwardStack.length-1].flexAppUrl == getHash()) { + urlActions.forward = true; + handleForwardButton(); + } + } + + // ok, that didn't work, try someplace back in the history stack + if ((bsl >= 2) && (backStack[bsl - 2])) { + if (backStack[bsl - 2].flexAppUrl == getHash()) { + urlActions.back = true; + handleBackButton(); + } + } + + if (!urlActions.back && !urlActions.forward) { + var foundInStacks = { + back: -1, + forward: -1 + } + + for (var i = 0; i < backStack.length; i++) { + if (backStack[i].flexAppUrl == getHash() && i != (bsl - 2)) { + arbitraryUrl = true; + foundInStacks.back = i; + } + } + for (var i = 0; i < forwardStack.length; i++) { + if (forwardStack[i].flexAppUrl == getHash() && i != (bsl - 2)) { + arbitraryUrl = true; + foundInStacks.forward = i; + } + } + handleArbitraryUrl(); + } + + // Firefox changed; do a callback into BrowserManager to tell it. + currentHref = document.location.href; + var flexAppUrl = getHash(); + if (flexAppUrl == '') { + //flexAppUrl = defaultHash; + } + //ADR: to fix multiple + if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) { + var pl = getPlayers(); + for (var i = 0; i < pl.length; i++) { + pl[i].browserURLChange(flexAppUrl); + } + } else { + getPlayer().browserURLChange(flexAppUrl); + } + } + } + //setTimeout(checkForUrlChange, 50); + } + + /* Write an anchor into the page to legitimize it as a URL for Firefox et al. */ + function addAnchor(flexAppUrl) + { + if (document.getElementsByName(flexAppUrl).length == 0) { + getAnchorElement().innerHTML += "" + flexAppUrl + ""; + } + } + + var _initialize = function () { + if (browser.ie) + { + var scripts = document.getElementsByTagName('script'); + for (var i = 0, s; s = scripts[i]; i++) { + if (s.src.indexOf("history.js") > -1) { + var iframe_location = (new String(s.src)).replace("history.js", "historyFrame.html"); + } + } + historyFrameSourcePrefix = iframe_location + "?"; + var src = historyFrameSourcePrefix; + + var iframe = document.createElement("iframe"); + iframe.id = 'ie_historyFrame'; + iframe.name = 'ie_historyFrame'; + //iframe.src = historyFrameSourcePrefix; + try { + document.body.appendChild(iframe); + } catch(e) { + setTimeout(function() { + document.body.appendChild(iframe); + }, 0); + } + } + + if (browser.safari) + { + var rememberDiv = document.createElement("div"); + rememberDiv.id = 'safari_rememberDiv'; + document.body.appendChild(rememberDiv); + rememberDiv.innerHTML = ''; + + var formDiv = document.createElement("div"); + formDiv.id = 'safari_formDiv'; + document.body.appendChild(formDiv); + + var reloader_content = document.createElement('div'); + reloader_content.id = 'safarireloader'; + var scripts = document.getElementsByTagName('script'); + for (var i = 0, s; s = scripts[i]; i++) { + if (s.src.indexOf("history.js") > -1) { + html = (new String(s.src)).replace(".js", ".html"); + } + } + reloader_content.innerHTML = ''; + document.body.appendChild(reloader_content); + reloader_content.style.position = 'absolute'; + reloader_content.style.left = reloader_content.style.top = '-9999px'; + iframe = reloader_content.getElementsByTagName('iframe')[0]; + + if (document.getElementById("safari_remember_field").value != "" ) { + historyHash = document.getElementById("safari_remember_field").value.split(","); + } + + } + + if (browser.firefox) + { + var anchorDiv = document.createElement("div"); + anchorDiv.id = 'firefox_anchorDiv'; + document.body.appendChild(anchorDiv); + } + + //setTimeout(checkForUrlChange, 50); + } + + return { + historyHash: historyHash, + backStack: function() { return backStack; }, + forwardStack: function() { return forwardStack }, + getPlayer: getPlayer, + initialize: function(src) { + _initialize(src); + }, + setURL: function(url) { + document.location.href = url; + }, + getURL: function() { + return document.location.href; + }, + getTitle: function() { + return document.title; + }, + setTitle: function(title) { + try { + backStack[backStack.length - 1].title = title; + } catch(e) { } + //if on safari, set the title to be the empty string. + if (browser.safari) { + if (title == "") { + try { + var tmp = window.location.href.toString(); + title = tmp.substring((tmp.lastIndexOf("/")+1), tmp.lastIndexOf("#")); + } catch(e) { + title = ""; + } + } + } + document.title = title; + }, + setDefaultURL: function(def) + { + defaultHash = def; + def = getHash(); + //trailing ? is important else an extra frame gets added to the history + //when navigating back to the first page. Alternatively could check + //in history frame navigation to compare # and ?. + if (browser.ie) + { + window['_ie_firstload'] = true; + var sourceToSet = historyFrameSourcePrefix + def; + var func = function() { + getHistoryFrame().src = sourceToSet; + window.location.replace("#" + def); + setInterval(checkForUrlChange, 50); + } + try { + func(); + } catch(e) { + window.setTimeout(function() { func(); }, 0); + } + } + + if (browser.safari) + { + currentHistoryLength = history.length; + if (historyHash.length == 0) { + historyHash[currentHistoryLength] = def; + var newloc = "#" + def; + window.location.replace(newloc); + } else { + //alert(historyHash[historyHash.length-1]); + } + //setHash(def); + setInterval(checkForUrlChange, 50); + } + + + if (browser.firefox || browser.opera) + { + var reg = new RegExp("#" + def + "$"); + if (window.location.toString().match(reg)) { + } else { + var newloc ="#" + def; + window.location.replace(newloc); + } + setInterval(checkForUrlChange, 50); + //setHash(def); + } + + }, + + /* Set the current browser URL; called from inside BrowserManager to propagate + * the application state out to the container. + */ + setBrowserURL: function(flexAppUrl, objectId) { + if (browser.ie && typeof objectId != "undefined") { + currentObjectId = objectId; + } + //fromIframe = fromIframe || false; + //fromFlex = fromFlex || false; + //alert("setBrowserURL: " + flexAppUrl); + //flexAppUrl = (flexAppUrl == "") ? defaultHash : flexAppUrl ; + + var pos = document.location.href.indexOf('#'); + var baseUrl = pos != -1 ? document.location.href.substr(0, pos) : document.location.href; + var newUrl = baseUrl + '#' + flexAppUrl; + + if (document.location.href != newUrl && document.location.href + '#' != newUrl) { + currentHref = newUrl; + addHistoryEntry(baseUrl, newUrl, flexAppUrl); + currentHistoryLength = history.length; + } + + return false; + }, + + browserURLChange: function(flexAppUrl) { + var objectId = null; + if (browser.ie && currentObjectId != null) { + objectId = currentObjectId; + } + pendingURL = ''; + + if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) { + var pl = getPlayers(); + for (var i = 0; i < pl.length; i++) { + try { + pl[i].browserURLChange(flexAppUrl); + } catch(e) { } + } + } else { + try { + getPlayer(objectId).browserURLChange(flexAppUrl); + } catch(e) { } + } + + currentObjectId = null; + } + + } + +})(); + +// Initialization + +// Automated unit testing and other diagnostics + +function setURL(url) +{ + document.location.href = url; +} + +function backButton() +{ + history.back(); +} + +function forwardButton() +{ + history.forward(); +} + +function goForwardOrBackInHistory(step) +{ + history.go(step); +} + +//BrowserHistoryUtils.addEvent(window, "load", function() { BrowserHistory.initialize(); }); +(function(i) { + var u =navigator.userAgent;var e=/*@cc_on!@*/false; + var st = setTimeout; + if(/webkit/i.test(u)){ + st(function(){ + var dr=document.readyState; + if(dr=="loaded"||dr=="complete"){i()} + else{st(arguments.callee,10);}},10); + } else if((/mozilla/i.test(u)&&!/(compati)/.test(u)) || (/opera/i.test(u))){ + document.addEventListener("DOMContentLoaded",i,false); + } else if(e){ + (function(){ + var t=document.createElement('doc:rdy'); + try{t.doScroll('left'); + i();t=null; + }catch(e){st(arguments.callee,0);}})(); + } else{ + window.onload=i; + } +})( function() {BrowserHistory.initialize();} ); diff --git a/demos/phonebook/flex/bin/history/historyFrame.html b/demos/phonebook/flex/bin/history/historyFrame.html index 9cb74ce3f..e83255f18 100644 --- a/demos/phonebook/flex/bin/history/historyFrame.html +++ b/demos/phonebook/flex/bin/history/historyFrame.html @@ -1,29 +1,29 @@ - - - - - - - - Hidden frame for Browser History support. - - + + + + + + + + Hidden frame for Browser History support. + + diff --git a/demos/phonebook/flex/bin/phonebook.html b/demos/phonebook/flex/bin/phonebook.html index 745bd8051..82bc03d0f 100644 --- a/demos/phonebook/flex/bin/phonebook.html +++ b/demos/phonebook/flex/bin/phonebook.html @@ -1,121 +1,121 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demos/phonebook/flex/html-template/AC_OETags.js b/demos/phonebook/flex/html-template/AC_OETags.js index d1f066bdf..e77e6fd8a 100644 --- a/demos/phonebook/flex/html-template/AC_OETags.js +++ b/demos/phonebook/flex/html-template/AC_OETags.js @@ -1,276 +1,276 @@ -// Flash Player Version Detection - Rev 1.6 -// Detect Client Browser type -// Copyright(c) 2005-2006 Adobe Macromedia Software, LLC. All rights reserved. -var isIE = (navigator.appVersion.indexOf("MSIE") != -1) ? true : false; -var isWin = (navigator.appVersion.toLowerCase().indexOf("win") != -1) ? true : false; -var isOpera = (navigator.userAgent.indexOf("Opera") != -1) ? true : false; - -function ControlVersion() -{ - var version; - var axo; - var e; - - // NOTE : new ActiveXObject(strFoo) throws an exception if strFoo isn't in the registry - - try { - // version will be set for 7.X or greater players - axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7"); - version = axo.GetVariable("$version"); - } catch (e) { - } - - if (!version) - { - try { - // version will be set for 6.X players only - axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6"); - - // installed player is some revision of 6.0 - // GetVariable("$version") crashes for versions 6.0.22 through 6.0.29, - // so we have to be careful. - - // default to the first public version - version = "WIN 6,0,21,0"; - - // throws if AllowScripAccess does not exist (introduced in 6.0r47) - axo.AllowScriptAccess = "always"; - - // safe to call for 6.0r47 or greater - version = axo.GetVariable("$version"); - - } catch (e) { - } - } - - if (!version) - { - try { - // version will be set for 4.X or 5.X player - axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.3"); - version = axo.GetVariable("$version"); - } catch (e) { - } - } - - if (!version) - { - try { - // version will be set for 3.X player - axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.3"); - version = "WIN 3,0,18,0"; - } catch (e) { - } - } - - if (!version) - { - try { - // version will be set for 2.X player - axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"); - version = "WIN 2,0,0,11"; - } catch (e) { - version = -1; - } - } - - return version; -} - -// JavaScript helper required to detect Flash Player PlugIn version information -function GetSwfVer(){ - // NS/Opera version >= 3 check for Flash plugin in plugin array - var flashVer = -1; - - if (navigator.plugins != null && navigator.plugins.length > 0) { - if (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]) { - var swVer2 = navigator.plugins["Shockwave Flash 2.0"] ? " 2.0" : ""; - var flashDescription = navigator.plugins["Shockwave Flash" + swVer2].description; - var descArray = flashDescription.split(" "); - var tempArrayMajor = descArray[2].split("."); - var versionMajor = tempArrayMajor[0]; - var versionMinor = tempArrayMajor[1]; - var versionRevision = descArray[3]; - if (versionRevision == "") { - versionRevision = descArray[4]; - } - if (versionRevision[0] == "d") { - versionRevision = versionRevision.substring(1); - } else if (versionRevision[0] == "r") { - versionRevision = versionRevision.substring(1); - if (versionRevision.indexOf("d") > 0) { - versionRevision = versionRevision.substring(0, versionRevision.indexOf("d")); - } - } - var flashVer = versionMajor + "." + versionMinor + "." + versionRevision; - } - } - // MSN/WebTV 2.6 supports Flash 4 - else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.6") != -1) flashVer = 4; - // WebTV 2.5 supports Flash 3 - else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.5") != -1) flashVer = 3; - // older WebTV supports Flash 2 - else if (navigator.userAgent.toLowerCase().indexOf("webtv") != -1) flashVer = 2; - else if ( isIE && isWin && !isOpera ) { - flashVer = ControlVersion(); - } - return flashVer; -} - -// When called with reqMajorVer, reqMinorVer, reqRevision returns true if that version or greater is available -function DetectFlashVer(reqMajorVer, reqMinorVer, reqRevision) -{ - versionStr = GetSwfVer(); - if (versionStr == -1 ) { - return false; - } else if (versionStr != 0) { - if(isIE && isWin && !isOpera) { - // Given "WIN 2,0,0,11" - tempArray = versionStr.split(" "); // ["WIN", "2,0,0,11"] - tempString = tempArray[1]; // "2,0,0,11" - versionArray = tempString.split(","); // ['2', '0', '0', '11'] - } else { - versionArray = versionStr.split("."); - } - var versionMajor = versionArray[0]; - var versionMinor = versionArray[1]; - var versionRevision = versionArray[2]; - - // is the major.revision >= requested major.revision AND the minor version >= requested minor - if (versionMajor > parseFloat(reqMajorVer)) { - return true; - } else if (versionMajor == parseFloat(reqMajorVer)) { - if (versionMinor > parseFloat(reqMinorVer)) - return true; - else if (versionMinor == parseFloat(reqMinorVer)) { - if (versionRevision >= parseFloat(reqRevision)) - return true; - } - } - return false; - } -} - -function AC_AddExtension(src, ext) -{ - if (src.indexOf('?') != -1) - return src.replace(/\?/, ext+'?'); - else - return src + ext; -} - -function AC_Generateobj(objAttrs, params, embedAttrs) -{ - var str = ''; - if (isIE && isWin && !isOpera) - { - str += ' '; - str += ''; - } else { - str += '= 3 check for Flash plugin in plugin array + var flashVer = -1; + + if (navigator.plugins != null && navigator.plugins.length > 0) { + if (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]) { + var swVer2 = navigator.plugins["Shockwave Flash 2.0"] ? " 2.0" : ""; + var flashDescription = navigator.plugins["Shockwave Flash" + swVer2].description; + var descArray = flashDescription.split(" "); + var tempArrayMajor = descArray[2].split("."); + var versionMajor = tempArrayMajor[0]; + var versionMinor = tempArrayMajor[1]; + var versionRevision = descArray[3]; + if (versionRevision == "") { + versionRevision = descArray[4]; + } + if (versionRevision[0] == "d") { + versionRevision = versionRevision.substring(1); + } else if (versionRevision[0] == "r") { + versionRevision = versionRevision.substring(1); + if (versionRevision.indexOf("d") > 0) { + versionRevision = versionRevision.substring(0, versionRevision.indexOf("d")); + } + } + var flashVer = versionMajor + "." + versionMinor + "." + versionRevision; + } + } + // MSN/WebTV 2.6 supports Flash 4 + else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.6") != -1) flashVer = 4; + // WebTV 2.5 supports Flash 3 + else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.5") != -1) flashVer = 3; + // older WebTV supports Flash 2 + else if (navigator.userAgent.toLowerCase().indexOf("webtv") != -1) flashVer = 2; + else if ( isIE && isWin && !isOpera ) { + flashVer = ControlVersion(); + } + return flashVer; +} + +// When called with reqMajorVer, reqMinorVer, reqRevision returns true if that version or greater is available +function DetectFlashVer(reqMajorVer, reqMinorVer, reqRevision) +{ + versionStr = GetSwfVer(); + if (versionStr == -1 ) { + return false; + } else if (versionStr != 0) { + if(isIE && isWin && !isOpera) { + // Given "WIN 2,0,0,11" + tempArray = versionStr.split(" "); // ["WIN", "2,0,0,11"] + tempString = tempArray[1]; // "2,0,0,11" + versionArray = tempString.split(","); // ['2', '0', '0', '11'] + } else { + versionArray = versionStr.split("."); + } + var versionMajor = versionArray[0]; + var versionMinor = versionArray[1]; + var versionRevision = versionArray[2]; + + // is the major.revision >= requested major.revision AND the minor version >= requested minor + if (versionMajor > parseFloat(reqMajorVer)) { + return true; + } else if (versionMajor == parseFloat(reqMajorVer)) { + if (versionMinor > parseFloat(reqMinorVer)) + return true; + else if (versionMinor == parseFloat(reqMinorVer)) { + if (versionRevision >= parseFloat(reqRevision)) + return true; + } + } + return false; + } +} + +function AC_AddExtension(src, ext) +{ + if (src.indexOf('?') != -1) + return src.replace(/\?/, ext+'?'); + else + return src + ext; +} + +function AC_Generateobj(objAttrs, params, embedAttrs) +{ + var str = ''; + if (isIE && isWin && !isOpera) + { + str += ' '; + str += ''; + } else { + str += '= 2 && hash.charAt(0) == "?") { - hash = hash.substring(1); - } - return hash; - } - - /* Get the current location hash excluding the '#' symbol. */ - function getHash() { - // It would be nice if we could use document.location.hash here, - // but it's faulty sometimes. - var idx = document.location.href.indexOf('#'); - return (idx >= 0) ? document.location.href.substr(idx+1) : ''; - } - - /* Get the current location hash excluding the '#' symbol. */ - function setHash(hash) { - // It would be nice if we could use document.location.hash here, - // but it's faulty sometimes. - if (hash == '') hash = '#' - document.location.hash = hash; - } - - function createState(baseUrl, newUrl, flexAppUrl) { - return { 'baseUrl': baseUrl, 'newUrl': newUrl, 'flexAppUrl': flexAppUrl, 'title': null }; - } - - /* Add a history entry to the browser. - * baseUrl: the portion of the location prior to the '#' - * newUrl: the entire new URL, including '#' and following fragment - * flexAppUrl: the portion of the location following the '#' only - */ - function addHistoryEntry(baseUrl, newUrl, flexAppUrl) { - - //delete all the history entries - forwardStack = []; - - if (browser.ie) { - //Check to see if we are being asked to do a navigate for the first - //history entry, and if so ignore, because it's coming from the creation - //of the history iframe - if (flexAppUrl == defaultHash && document.location.href == initialHref && window['_ie_firstload']) { - currentHref = initialHref; - return; - } - if ((!flexAppUrl || flexAppUrl == defaultHash) && window['_ie_firstload']) { - newUrl = baseUrl + '#' + defaultHash; - flexAppUrl = defaultHash; - } else { - // for IE, tell the history frame to go somewhere without a '#' - // in order to get this entry into the browser history. - getHistoryFrame().src = historyFrameSourcePrefix + flexAppUrl; - } - setHash(flexAppUrl); - } else { - - //ADR - if (backStack.length == 0 && initialState.flexAppUrl == flexAppUrl) { - initialState = createState(baseUrl, newUrl, flexAppUrl); - } else if(backStack.length > 0 && backStack[backStack.length - 1].flexAppUrl == flexAppUrl) { - backStack[backStack.length - 1] = createState(baseUrl, newUrl, flexAppUrl); - } - - if (browser.safari) { - // for Safari, submit a form whose action points to the desired URL - if (browser.version <= 419.3) { - var file = window.location.pathname.toString(); - file = file.substring(file.lastIndexOf("/")+1); - getFormElement().innerHTML = '
'; - //get the current elements and add them to the form - var qs = window.location.search.substring(1); - var qs_arr = qs.split("&"); - for (var i = 0; i < qs_arr.length; i++) { - var tmp = qs_arr[i].split("="); - var elem = document.createElement("input"); - elem.type = "hidden"; - elem.name = tmp[0]; - elem.value = tmp[1]; - document.forms.historyForm.appendChild(elem); - } - document.forms.historyForm.submit(); - } else { - top.location.hash = flexAppUrl; - } - // We also have to maintain the history by hand for Safari - historyHash[history.length] = flexAppUrl; - _storeStates(); - } else { - // Otherwise, write an anchor into the page and tell the browser to go there - addAnchor(flexAppUrl); - setHash(flexAppUrl); - } - } - backStack.push(createState(baseUrl, newUrl, flexAppUrl)); - } - - function _storeStates() { - if (browser.safari) { - getRememberElement().value = historyHash.join(","); - } - } - - function handleBackButton() { - //The "current" page is always at the top of the history stack. - var current = backStack.pop(); - if (!current) { return; } - var last = backStack[backStack.length - 1]; - if (!last && backStack.length == 0){ - last = initialState; - } - forwardStack.push(current); - } - - function handleForwardButton() { - //summary: private method. Do not call this directly. - - var last = forwardStack.pop(); - if (!last) { return; } - backStack.push(last); - } - - function handleArbitraryUrl() { - //delete all the history entries - forwardStack = []; - } - - /* Called periodically to poll to see if we need to detect navigation that has occurred */ - function checkForUrlChange() { - - if (browser.ie) { - if (currentHref != document.location.href && currentHref + '#' != document.location.href) { - //This occurs when the user has navigated to a specific URL - //within the app, and didn't use browser back/forward - //IE seems to have a bug where it stops updating the URL it - //shows the end-user at this point, but programatically it - //appears to be correct. Do a full app reload to get around - //this issue. - if (browser.version < 7) { - currentHref = document.location.href; - document.location.reload(); - } else { - if (getHash() != getIframeHash()) { - // this.iframe.src = this.blankURL + hash; - var sourceToSet = historyFrameSourcePrefix + getHash(); - getHistoryFrame().src = sourceToSet; - } - } - } - } - - if (browser.safari) { - // For Safari, we have to check to see if history.length changed. - if (currentHistoryLength >= 0 && history.length != currentHistoryLength) { - //alert("did change: " + history.length + ", " + historyHash.length + "|" + historyHash[history.length] + "|>" + historyHash.join("|")); - // If it did change, then we have to look the old state up - // in our hand-maintained array since document.location.hash - // won't have changed, then call back into BrowserManager. - currentHistoryLength = history.length; - var flexAppUrl = historyHash[currentHistoryLength]; - if (flexAppUrl == '') { - //flexAppUrl = defaultHash; - } - //ADR: to fix multiple - if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) { - var pl = getPlayers(); - for (var i = 0; i < pl.length; i++) { - pl[i].browserURLChange(flexAppUrl); - } - } else { - getPlayer().browserURLChange(flexAppUrl); - } - _storeStates(); - } - } - if (browser.firefox) { - if (currentHref != document.location.href) { - var bsl = backStack.length; - - var urlActions = { - back: false, - forward: false, - set: false - } - - if ((window.location.hash == initialHash || window.location.href == initialHref) && (bsl == 1)) { - urlActions.back = true; - // FIXME: could this ever be a forward button? - // we can't clear it because we still need to check for forwards. Ugg. - // clearInterval(this.locationTimer); - handleBackButton(); - } - - // first check to see if we could have gone forward. We always halt on - // a no-hash item. - if (forwardStack.length > 0) { - if (forwardStack[forwardStack.length-1].flexAppUrl == getHash()) { - urlActions.forward = true; - handleForwardButton(); - } - } - - // ok, that didn't work, try someplace back in the history stack - if ((bsl >= 2) && (backStack[bsl - 2])) { - if (backStack[bsl - 2].flexAppUrl == getHash()) { - urlActions.back = true; - handleBackButton(); - } - } - - if (!urlActions.back && !urlActions.forward) { - var foundInStacks = { - back: -1, - forward: -1 - } - - for (var i = 0; i < backStack.length; i++) { - if (backStack[i].flexAppUrl == getHash() && i != (bsl - 2)) { - arbitraryUrl = true; - foundInStacks.back = i; - } - } - for (var i = 0; i < forwardStack.length; i++) { - if (forwardStack[i].flexAppUrl == getHash() && i != (bsl - 2)) { - arbitraryUrl = true; - foundInStacks.forward = i; - } - } - handleArbitraryUrl(); - } - - // Firefox changed; do a callback into BrowserManager to tell it. - currentHref = document.location.href; - var flexAppUrl = getHash(); - if (flexAppUrl == '') { - //flexAppUrl = defaultHash; - } - //ADR: to fix multiple - if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) { - var pl = getPlayers(); - for (var i = 0; i < pl.length; i++) { - pl[i].browserURLChange(flexAppUrl); - } - } else { - getPlayer().browserURLChange(flexAppUrl); - } - } - } - //setTimeout(checkForUrlChange, 50); - } - - /* Write an anchor into the page to legitimize it as a URL for Firefox et al. */ - function addAnchor(flexAppUrl) - { - if (document.getElementsByName(flexAppUrl).length == 0) { - getAnchorElement().innerHTML += "" + flexAppUrl + ""; - } - } - - var _initialize = function () { - if (browser.ie) - { - var scripts = document.getElementsByTagName('script'); - for (var i = 0, s; s = scripts[i]; i++) { - if (s.src.indexOf("history.js") > -1) { - var iframe_location = (new String(s.src)).replace("history.js", "historyFrame.html"); - } - } - historyFrameSourcePrefix = iframe_location + "?"; - var src = historyFrameSourcePrefix; - - var iframe = document.createElement("iframe"); - iframe.id = 'ie_historyFrame'; - iframe.name = 'ie_historyFrame'; - //iframe.src = historyFrameSourcePrefix; - try { - document.body.appendChild(iframe); - } catch(e) { - setTimeout(function() { - document.body.appendChild(iframe); - }, 0); - } - } - - if (browser.safari) - { - var rememberDiv = document.createElement("div"); - rememberDiv.id = 'safari_rememberDiv'; - document.body.appendChild(rememberDiv); - rememberDiv.innerHTML = ''; - - var formDiv = document.createElement("div"); - formDiv.id = 'safari_formDiv'; - document.body.appendChild(formDiv); - - var reloader_content = document.createElement('div'); - reloader_content.id = 'safarireloader'; - var scripts = document.getElementsByTagName('script'); - for (var i = 0, s; s = scripts[i]; i++) { - if (s.src.indexOf("history.js") > -1) { - html = (new String(s.src)).replace(".js", ".html"); - } - } - reloader_content.innerHTML = ''; - document.body.appendChild(reloader_content); - reloader_content.style.position = 'absolute'; - reloader_content.style.left = reloader_content.style.top = '-9999px'; - iframe = reloader_content.getElementsByTagName('iframe')[0]; - - if (document.getElementById("safari_remember_field").value != "" ) { - historyHash = document.getElementById("safari_remember_field").value.split(","); - } - - } - - if (browser.firefox) - { - var anchorDiv = document.createElement("div"); - anchorDiv.id = 'firefox_anchorDiv'; - document.body.appendChild(anchorDiv); - } - - //setTimeout(checkForUrlChange, 50); - } - - return { - historyHash: historyHash, - backStack: function() { return backStack; }, - forwardStack: function() { return forwardStack }, - getPlayer: getPlayer, - initialize: function(src) { - _initialize(src); - }, - setURL: function(url) { - document.location.href = url; - }, - getURL: function() { - return document.location.href; - }, - getTitle: function() { - return document.title; - }, - setTitle: function(title) { - try { - backStack[backStack.length - 1].title = title; - } catch(e) { } - //if on safari, set the title to be the empty string. - if (browser.safari) { - if (title == "") { - try { - var tmp = window.location.href.toString(); - title = tmp.substring((tmp.lastIndexOf("/")+1), tmp.lastIndexOf("#")); - } catch(e) { - title = ""; - } - } - } - document.title = title; - }, - setDefaultURL: function(def) - { - defaultHash = def; - def = getHash(); - //trailing ? is important else an extra frame gets added to the history - //when navigating back to the first page. Alternatively could check - //in history frame navigation to compare # and ?. - if (browser.ie) - { - window['_ie_firstload'] = true; - var sourceToSet = historyFrameSourcePrefix + def; - var func = function() { - getHistoryFrame().src = sourceToSet; - window.location.replace("#" + def); - setInterval(checkForUrlChange, 50); - } - try { - func(); - } catch(e) { - window.setTimeout(function() { func(); }, 0); - } - } - - if (browser.safari) - { - currentHistoryLength = history.length; - if (historyHash.length == 0) { - historyHash[currentHistoryLength] = def; - var newloc = "#" + def; - window.location.replace(newloc); - } else { - //alert(historyHash[historyHash.length-1]); - } - //setHash(def); - setInterval(checkForUrlChange, 50); - } - - - if (browser.firefox || browser.opera) - { - var reg = new RegExp("#" + def + "$"); - if (window.location.toString().match(reg)) { - } else { - var newloc ="#" + def; - window.location.replace(newloc); - } - setInterval(checkForUrlChange, 50); - //setHash(def); - } - - }, - - /* Set the current browser URL; called from inside BrowserManager to propagate - * the application state out to the container. - */ - setBrowserURL: function(flexAppUrl, objectId) { - if (browser.ie && typeof objectId != "undefined") { - currentObjectId = objectId; - } - //fromIframe = fromIframe || false; - //fromFlex = fromFlex || false; - //alert("setBrowserURL: " + flexAppUrl); - //flexAppUrl = (flexAppUrl == "") ? defaultHash : flexAppUrl ; - - var pos = document.location.href.indexOf('#'); - var baseUrl = pos != -1 ? document.location.href.substr(0, pos) : document.location.href; - var newUrl = baseUrl + '#' + flexAppUrl; - - if (document.location.href != newUrl && document.location.href + '#' != newUrl) { - currentHref = newUrl; - addHistoryEntry(baseUrl, newUrl, flexAppUrl); - currentHistoryLength = history.length; - } - - return false; - }, - - browserURLChange: function(flexAppUrl) { - var objectId = null; - if (browser.ie && currentObjectId != null) { - objectId = currentObjectId; - } - pendingURL = ''; - - if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) { - var pl = getPlayers(); - for (var i = 0; i < pl.length; i++) { - try { - pl[i].browserURLChange(flexAppUrl); - } catch(e) { } - } - } else { - try { - getPlayer(objectId).browserURLChange(flexAppUrl); - } catch(e) { } - } - - currentObjectId = null; - } - - } - -})(); - -// Initialization - -// Automated unit testing and other diagnostics - -function setURL(url) -{ - document.location.href = url; -} - -function backButton() -{ - history.back(); -} - -function forwardButton() -{ - history.forward(); -} - -function goForwardOrBackInHistory(step) -{ - history.go(step); -} - -//BrowserHistoryUtils.addEvent(window, "load", function() { BrowserHistory.initialize(); }); -(function(i) { - var u =navigator.userAgent;var e=/*@cc_on!@*/false; - var st = setTimeout; - if(/webkit/i.test(u)){ - st(function(){ - var dr=document.readyState; - if(dr=="loaded"||dr=="complete"){i()} - else{st(arguments.callee,10);}},10); - } else if((/mozilla/i.test(u)&&!/(compati)/.test(u)) || (/opera/i.test(u))){ - document.addEventListener("DOMContentLoaded",i,false); - } else if(e){ - (function(){ - var t=document.createElement('doc:rdy'); - try{t.doScroll('left'); - i();t=null; - }catch(e){st(arguments.callee,0);}})(); - } else{ - window.onload=i; - } -})( function() {BrowserHistory.initialize();} ); +BrowserHistoryUtils = { + addEvent: function(elm, evType, fn, useCapture) { + useCapture = useCapture || false; + if (elm.addEventListener) { + elm.addEventListener(evType, fn, useCapture); + return true; + } + else if (elm.attachEvent) { + var r = elm.attachEvent('on' + evType, fn); + return r; + } + else { + elm['on' + evType] = fn; + } + } +} + +BrowserHistory = (function() { + // type of browser + var browser = { + ie: false, + firefox: false, + safari: false, + opera: false, + version: -1 + }; + + // if setDefaultURL has been called, our first clue + // that the SWF is ready and listening + //var swfReady = false; + + // the URL we'll send to the SWF once it is ready + //var pendingURL = ''; + + // Default app state URL to use when no fragment ID present + var defaultHash = ''; + + // Last-known app state URL + var currentHref = document.location.href; + + // Initial URL (used only by IE) + var initialHref = document.location.href; + + // Initial URL (used only by IE) + var initialHash = document.location.hash; + + // History frame source URL prefix (used only by IE) + var historyFrameSourcePrefix = 'history/historyFrame.html?'; + + // History maintenance (used only by Safari) + var currentHistoryLength = -1; + + var historyHash = []; + + var initialState = createState(initialHref, initialHref + '#' + initialHash, initialHash); + + var backStack = []; + var forwardStack = []; + + var currentObjectId = null; + + //UserAgent detection + var useragent = navigator.userAgent.toLowerCase(); + + if (useragent.indexOf("opera") != -1) { + browser.opera = true; + } else if (useragent.indexOf("msie") != -1) { + browser.ie = true; + browser.version = parseFloat(useragent.substring(useragent.indexOf('msie') + 4)); + } else if (useragent.indexOf("safari") != -1) { + browser.safari = true; + browser.version = parseFloat(useragent.substring(useragent.indexOf('safari') + 7)); + } else if (useragent.indexOf("gecko") != -1) { + browser.firefox = true; + } + + if (browser.ie == true && browser.version == 7) { + window["_ie_firstload"] = false; + } + + // Accessor functions for obtaining specific elements of the page. + function getHistoryFrame() + { + return document.getElementById('ie_historyFrame'); + } + + function getAnchorElement() + { + return document.getElementById('firefox_anchorDiv'); + } + + function getFormElement() + { + return document.getElementById('safari_formDiv'); + } + + function getRememberElement() + { + return document.getElementById("safari_remember_field"); + } + + /* Get the Flash player object for performing ExternalInterface callbacks. */ + function getPlayer(objectId) { + var objectId = objectId || null; + var player = null; /* AJH, needed? = document.getElementById(getPlayerId()); */ + if (browser.ie && objectId != null) { + player = document.getElementById(objectId); + } + if (player == null) { + player = document.getElementsByTagName('object')[0]; + } + + if (player == null || player.object == null) { + player = document.getElementsByTagName('embed')[0]; + } + + return player; + } + + function getPlayers() { + var players = []; + if (players.length == 0) { + var tmp = document.getElementsByTagName('object'); + players = tmp; + } + + if (players.length == 0 || players[0].object == null) { + var tmp = document.getElementsByTagName('embed'); + players = tmp; + } + return players; + } + + function getIframeHash() { + var doc = getHistoryFrame().contentWindow.document; + var hash = String(doc.location.search); + if (hash.length == 1 && hash.charAt(0) == "?") { + hash = ""; + } + else if (hash.length >= 2 && hash.charAt(0) == "?") { + hash = hash.substring(1); + } + return hash; + } + + /* Get the current location hash excluding the '#' symbol. */ + function getHash() { + // It would be nice if we could use document.location.hash here, + // but it's faulty sometimes. + var idx = document.location.href.indexOf('#'); + return (idx >= 0) ? document.location.href.substr(idx+1) : ''; + } + + /* Get the current location hash excluding the '#' symbol. */ + function setHash(hash) { + // It would be nice if we could use document.location.hash here, + // but it's faulty sometimes. + if (hash == '') hash = '#' + document.location.hash = hash; + } + + function createState(baseUrl, newUrl, flexAppUrl) { + return { 'baseUrl': baseUrl, 'newUrl': newUrl, 'flexAppUrl': flexAppUrl, 'title': null }; + } + + /* Add a history entry to the browser. + * baseUrl: the portion of the location prior to the '#' + * newUrl: the entire new URL, including '#' and following fragment + * flexAppUrl: the portion of the location following the '#' only + */ + function addHistoryEntry(baseUrl, newUrl, flexAppUrl) { + + //delete all the history entries + forwardStack = []; + + if (browser.ie) { + //Check to see if we are being asked to do a navigate for the first + //history entry, and if so ignore, because it's coming from the creation + //of the history iframe + if (flexAppUrl == defaultHash && document.location.href == initialHref && window['_ie_firstload']) { + currentHref = initialHref; + return; + } + if ((!flexAppUrl || flexAppUrl == defaultHash) && window['_ie_firstload']) { + newUrl = baseUrl + '#' + defaultHash; + flexAppUrl = defaultHash; + } else { + // for IE, tell the history frame to go somewhere without a '#' + // in order to get this entry into the browser history. + getHistoryFrame().src = historyFrameSourcePrefix + flexAppUrl; + } + setHash(flexAppUrl); + } else { + + //ADR + if (backStack.length == 0 && initialState.flexAppUrl == flexAppUrl) { + initialState = createState(baseUrl, newUrl, flexAppUrl); + } else if(backStack.length > 0 && backStack[backStack.length - 1].flexAppUrl == flexAppUrl) { + backStack[backStack.length - 1] = createState(baseUrl, newUrl, flexAppUrl); + } + + if (browser.safari) { + // for Safari, submit a form whose action points to the desired URL + if (browser.version <= 419.3) { + var file = window.location.pathname.toString(); + file = file.substring(file.lastIndexOf("/")+1); + getFormElement().innerHTML = '
'; + //get the current elements and add them to the form + var qs = window.location.search.substring(1); + var qs_arr = qs.split("&"); + for (var i = 0; i < qs_arr.length; i++) { + var tmp = qs_arr[i].split("="); + var elem = document.createElement("input"); + elem.type = "hidden"; + elem.name = tmp[0]; + elem.value = tmp[1]; + document.forms.historyForm.appendChild(elem); + } + document.forms.historyForm.submit(); + } else { + top.location.hash = flexAppUrl; + } + // We also have to maintain the history by hand for Safari + historyHash[history.length] = flexAppUrl; + _storeStates(); + } else { + // Otherwise, write an anchor into the page and tell the browser to go there + addAnchor(flexAppUrl); + setHash(flexAppUrl); + } + } + backStack.push(createState(baseUrl, newUrl, flexAppUrl)); + } + + function _storeStates() { + if (browser.safari) { + getRememberElement().value = historyHash.join(","); + } + } + + function handleBackButton() { + //The "current" page is always at the top of the history stack. + var current = backStack.pop(); + if (!current) { return; } + var last = backStack[backStack.length - 1]; + if (!last && backStack.length == 0){ + last = initialState; + } + forwardStack.push(current); + } + + function handleForwardButton() { + //summary: private method. Do not call this directly. + + var last = forwardStack.pop(); + if (!last) { return; } + backStack.push(last); + } + + function handleArbitraryUrl() { + //delete all the history entries + forwardStack = []; + } + + /* Called periodically to poll to see if we need to detect navigation that has occurred */ + function checkForUrlChange() { + + if (browser.ie) { + if (currentHref != document.location.href && currentHref + '#' != document.location.href) { + //This occurs when the user has navigated to a specific URL + //within the app, and didn't use browser back/forward + //IE seems to have a bug where it stops updating the URL it + //shows the end-user at this point, but programatically it + //appears to be correct. Do a full app reload to get around + //this issue. + if (browser.version < 7) { + currentHref = document.location.href; + document.location.reload(); + } else { + if (getHash() != getIframeHash()) { + // this.iframe.src = this.blankURL + hash; + var sourceToSet = historyFrameSourcePrefix + getHash(); + getHistoryFrame().src = sourceToSet; + } + } + } + } + + if (browser.safari) { + // For Safari, we have to check to see if history.length changed. + if (currentHistoryLength >= 0 && history.length != currentHistoryLength) { + //alert("did change: " + history.length + ", " + historyHash.length + "|" + historyHash[history.length] + "|>" + historyHash.join("|")); + // If it did change, then we have to look the old state up + // in our hand-maintained array since document.location.hash + // won't have changed, then call back into BrowserManager. + currentHistoryLength = history.length; + var flexAppUrl = historyHash[currentHistoryLength]; + if (flexAppUrl == '') { + //flexAppUrl = defaultHash; + } + //ADR: to fix multiple + if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) { + var pl = getPlayers(); + for (var i = 0; i < pl.length; i++) { + pl[i].browserURLChange(flexAppUrl); + } + } else { + getPlayer().browserURLChange(flexAppUrl); + } + _storeStates(); + } + } + if (browser.firefox) { + if (currentHref != document.location.href) { + var bsl = backStack.length; + + var urlActions = { + back: false, + forward: false, + set: false + } + + if ((window.location.hash == initialHash || window.location.href == initialHref) && (bsl == 1)) { + urlActions.back = true; + // FIXME: could this ever be a forward button? + // we can't clear it because we still need to check for forwards. Ugg. + // clearInterval(this.locationTimer); + handleBackButton(); + } + + // first check to see if we could have gone forward. We always halt on + // a no-hash item. + if (forwardStack.length > 0) { + if (forwardStack[forwardStack.length-1].flexAppUrl == getHash()) { + urlActions.forward = true; + handleForwardButton(); + } + } + + // ok, that didn't work, try someplace back in the history stack + if ((bsl >= 2) && (backStack[bsl - 2])) { + if (backStack[bsl - 2].flexAppUrl == getHash()) { + urlActions.back = true; + handleBackButton(); + } + } + + if (!urlActions.back && !urlActions.forward) { + var foundInStacks = { + back: -1, + forward: -1 + } + + for (var i = 0; i < backStack.length; i++) { + if (backStack[i].flexAppUrl == getHash() && i != (bsl - 2)) { + arbitraryUrl = true; + foundInStacks.back = i; + } + } + for (var i = 0; i < forwardStack.length; i++) { + if (forwardStack[i].flexAppUrl == getHash() && i != (bsl - 2)) { + arbitraryUrl = true; + foundInStacks.forward = i; + } + } + handleArbitraryUrl(); + } + + // Firefox changed; do a callback into BrowserManager to tell it. + currentHref = document.location.href; + var flexAppUrl = getHash(); + if (flexAppUrl == '') { + //flexAppUrl = defaultHash; + } + //ADR: to fix multiple + if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) { + var pl = getPlayers(); + for (var i = 0; i < pl.length; i++) { + pl[i].browserURLChange(flexAppUrl); + } + } else { + getPlayer().browserURLChange(flexAppUrl); + } + } + } + //setTimeout(checkForUrlChange, 50); + } + + /* Write an anchor into the page to legitimize it as a URL for Firefox et al. */ + function addAnchor(flexAppUrl) + { + if (document.getElementsByName(flexAppUrl).length == 0) { + getAnchorElement().innerHTML += "" + flexAppUrl + ""; + } + } + + var _initialize = function () { + if (browser.ie) + { + var scripts = document.getElementsByTagName('script'); + for (var i = 0, s; s = scripts[i]; i++) { + if (s.src.indexOf("history.js") > -1) { + var iframe_location = (new String(s.src)).replace("history.js", "historyFrame.html"); + } + } + historyFrameSourcePrefix = iframe_location + "?"; + var src = historyFrameSourcePrefix; + + var iframe = document.createElement("iframe"); + iframe.id = 'ie_historyFrame'; + iframe.name = 'ie_historyFrame'; + //iframe.src = historyFrameSourcePrefix; + try { + document.body.appendChild(iframe); + } catch(e) { + setTimeout(function() { + document.body.appendChild(iframe); + }, 0); + } + } + + if (browser.safari) + { + var rememberDiv = document.createElement("div"); + rememberDiv.id = 'safari_rememberDiv'; + document.body.appendChild(rememberDiv); + rememberDiv.innerHTML = ''; + + var formDiv = document.createElement("div"); + formDiv.id = 'safari_formDiv'; + document.body.appendChild(formDiv); + + var reloader_content = document.createElement('div'); + reloader_content.id = 'safarireloader'; + var scripts = document.getElementsByTagName('script'); + for (var i = 0, s; s = scripts[i]; i++) { + if (s.src.indexOf("history.js") > -1) { + html = (new String(s.src)).replace(".js", ".html"); + } + } + reloader_content.innerHTML = ''; + document.body.appendChild(reloader_content); + reloader_content.style.position = 'absolute'; + reloader_content.style.left = reloader_content.style.top = '-9999px'; + iframe = reloader_content.getElementsByTagName('iframe')[0]; + + if (document.getElementById("safari_remember_field").value != "" ) { + historyHash = document.getElementById("safari_remember_field").value.split(","); + } + + } + + if (browser.firefox) + { + var anchorDiv = document.createElement("div"); + anchorDiv.id = 'firefox_anchorDiv'; + document.body.appendChild(anchorDiv); + } + + //setTimeout(checkForUrlChange, 50); + } + + return { + historyHash: historyHash, + backStack: function() { return backStack; }, + forwardStack: function() { return forwardStack }, + getPlayer: getPlayer, + initialize: function(src) { + _initialize(src); + }, + setURL: function(url) { + document.location.href = url; + }, + getURL: function() { + return document.location.href; + }, + getTitle: function() { + return document.title; + }, + setTitle: function(title) { + try { + backStack[backStack.length - 1].title = title; + } catch(e) { } + //if on safari, set the title to be the empty string. + if (browser.safari) { + if (title == "") { + try { + var tmp = window.location.href.toString(); + title = tmp.substring((tmp.lastIndexOf("/")+1), tmp.lastIndexOf("#")); + } catch(e) { + title = ""; + } + } + } + document.title = title; + }, + setDefaultURL: function(def) + { + defaultHash = def; + def = getHash(); + //trailing ? is important else an extra frame gets added to the history + //when navigating back to the first page. Alternatively could check + //in history frame navigation to compare # and ?. + if (browser.ie) + { + window['_ie_firstload'] = true; + var sourceToSet = historyFrameSourcePrefix + def; + var func = function() { + getHistoryFrame().src = sourceToSet; + window.location.replace("#" + def); + setInterval(checkForUrlChange, 50); + } + try { + func(); + } catch(e) { + window.setTimeout(function() { func(); }, 0); + } + } + + if (browser.safari) + { + currentHistoryLength = history.length; + if (historyHash.length == 0) { + historyHash[currentHistoryLength] = def; + var newloc = "#" + def; + window.location.replace(newloc); + } else { + //alert(historyHash[historyHash.length-1]); + } + //setHash(def); + setInterval(checkForUrlChange, 50); + } + + + if (browser.firefox || browser.opera) + { + var reg = new RegExp("#" + def + "$"); + if (window.location.toString().match(reg)) { + } else { + var newloc ="#" + def; + window.location.replace(newloc); + } + setInterval(checkForUrlChange, 50); + //setHash(def); + } + + }, + + /* Set the current browser URL; called from inside BrowserManager to propagate + * the application state out to the container. + */ + setBrowserURL: function(flexAppUrl, objectId) { + if (browser.ie && typeof objectId != "undefined") { + currentObjectId = objectId; + } + //fromIframe = fromIframe || false; + //fromFlex = fromFlex || false; + //alert("setBrowserURL: " + flexAppUrl); + //flexAppUrl = (flexAppUrl == "") ? defaultHash : flexAppUrl ; + + var pos = document.location.href.indexOf('#'); + var baseUrl = pos != -1 ? document.location.href.substr(0, pos) : document.location.href; + var newUrl = baseUrl + '#' + flexAppUrl; + + if (document.location.href != newUrl && document.location.href + '#' != newUrl) { + currentHref = newUrl; + addHistoryEntry(baseUrl, newUrl, flexAppUrl); + currentHistoryLength = history.length; + } + + return false; + }, + + browserURLChange: function(flexAppUrl) { + var objectId = null; + if (browser.ie && currentObjectId != null) { + objectId = currentObjectId; + } + pendingURL = ''; + + if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) { + var pl = getPlayers(); + for (var i = 0; i < pl.length; i++) { + try { + pl[i].browserURLChange(flexAppUrl); + } catch(e) { } + } + } else { + try { + getPlayer(objectId).browserURLChange(flexAppUrl); + } catch(e) { } + } + + currentObjectId = null; + } + + } + +})(); + +// Initialization + +// Automated unit testing and other diagnostics + +function setURL(url) +{ + document.location.href = url; +} + +function backButton() +{ + history.back(); +} + +function forwardButton() +{ + history.forward(); +} + +function goForwardOrBackInHistory(step) +{ + history.go(step); +} + +//BrowserHistoryUtils.addEvent(window, "load", function() { BrowserHistory.initialize(); }); +(function(i) { + var u =navigator.userAgent;var e=/*@cc_on!@*/false; + var st = setTimeout; + if(/webkit/i.test(u)){ + st(function(){ + var dr=document.readyState; + if(dr=="loaded"||dr=="complete"){i()} + else{st(arguments.callee,10);}},10); + } else if((/mozilla/i.test(u)&&!/(compati)/.test(u)) || (/opera/i.test(u))){ + document.addEventListener("DOMContentLoaded",i,false); + } else if(e){ + (function(){ + var t=document.createElement('doc:rdy'); + try{t.doScroll('left'); + i();t=null; + }catch(e){st(arguments.callee,0);}})(); + } else{ + window.onload=i; + } +})( function() {BrowserHistory.initialize();} ); diff --git a/demos/phonebook/flex/html-template/history/historyFrame.html b/demos/phonebook/flex/html-template/history/historyFrame.html index 9cb74ce3f..e83255f18 100644 --- a/demos/phonebook/flex/html-template/history/historyFrame.html +++ b/demos/phonebook/flex/html-template/history/historyFrame.html @@ -1,29 +1,29 @@ - - - - - - - - Hidden frame for Browser History support. - - + + + + + + + + Hidden frame for Browser History support. + + diff --git a/demos/phonebook/flex/html-template/index.template.html b/demos/phonebook/flex/html-template/index.template.html index a8b3b64c6..20ee8091c 100644 --- a/demos/phonebook/flex/html-template/index.template.html +++ b/demos/phonebook/flex/html-template/index.template.html @@ -1,121 +1,121 @@ - - - - - - - - - - - - -${title} - - - - - - - - - - - - - - - + + + + + + + + + + + + +${title} + + + + + + + + + + + + + + + diff --git a/demos/phonebook/flex/phonebook.mxml b/demos/phonebook/flex/phonebook.mxml index fc9e2fe94..c8875f8f0 100644 --- a/demos/phonebook/flex/phonebook.mxml +++ b/demos/phonebook/flex/phonebook.mxml @@ -1,136 +1,136 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demos/phonebook/protected/.htaccess b/demos/phonebook/protected/.htaccess index e01983226..8d2f25636 100644 --- a/demos/phonebook/protected/.htaccess +++ b/demos/phonebook/protected/.htaccess @@ -1 +1 @@ -deny from all +deny from all diff --git a/demos/phonebook/protected/data/schema.sql b/demos/phonebook/protected/data/schema.sql index 58ad4a103..3202f9b0f 100644 --- a/demos/phonebook/protected/data/schema.sql +++ b/demos/phonebook/protected/data/schema.sql @@ -1,6 +1,6 @@ -CREATE TABLE Contact -( - id INTEGER NOT NULL PRIMARY KEY, - name VARCHAR(128) NOT NULL, - phone VARCHAR(64) NOT NULL +CREATE TABLE Contact +( + id INTEGER NOT NULL PRIMARY KEY, + name VARCHAR(128) NOT NULL, + phone VARCHAR(64) NOT NULL ); \ No newline at end of file diff --git a/docs/blog/ru/comment.model.txt b/docs/blog/ru/comment.model.txt index 628b7add0..002ada5b9 100644 --- a/docs/blog/ru/comment.model.txt +++ b/docs/blog/ru/comment.model.txt @@ -1,81 +1,81 @@ -Доработка модели Comment -======================== - -В модели `Comment` нам необходимо поправить методы `rules()` и `attributeLabels()`. -Метод `attributeLabels()` возвращает массив заголовков для указанных полей. -Метод `relations()` исправлять не будем так как код, сгенерированный `Gii` нам -подходит. - -Изменение метода `rules()` --------------------------- - -Начнём с уточнения правил валидации, сгенерированных при помощи `Gii`. -Для комментариев будем использовать следующие правила: - -~~~ -[php] -public function rules() -{ - return array( - array('content, author, email', 'required'), - array('author, email, url', 'length', 'max'=>128), - array('email','email'), - array('url','url'), - ); -} -~~~ - -Здесь мы указываем, что атрибуты `author`, `email` и `content` обязательны. -Длина `author`, `email` и `url` не может превышать 128 символов. Атрибут `email` -должен содержать корректный email-адрес. `url` должен содержать корректный URL. - - -Изменение метода `attributeLabels()` ------------------------------------- - -Изменим метод `attributeLabels()`. Зададим свои подписи атрибутам. Этот метод -возвращает массив пар имя атрибута-подпись. - -~~~ -[php] -public function attributeLabels() -{ - return array( - 'id' => 'Id', - 'content' => 'Comment', - 'status' => 'Status', - 'create_time' => 'Create Time', - 'author' => 'Name', - 'email' => 'Email', - 'url' => 'Website', - 'post_id' => 'Post', - ); -} -~~~ - -> Tip|Подсказка: Если подпись атрибута не задана в `attributeLabels()`, -для её генерации используется специальный алгоритм. К примеру, для -атрибутов `create_time` и `createTime` подпись будет выглядеть как -`Create Time`. - - -Изменение процесса сохранения ------------------------------ - -Для того, чтобы записывать время создания комментария, переопределим -метод `beforeSave()` класса `Comment` также, как это сделано для модели `Post`: - -~~~ -[php] -protected function beforeSave() -{ - if(parent::beforeSave()) - { - if($this->isNewRecord) - $this->create_time=time(); - return true; - } - else - return false; -} +Доработка модели Comment +======================== + +В модели `Comment` нам необходимо поправить методы `rules()` и `attributeLabels()`. +Метод `attributeLabels()` возвращает массив заголовков для указанных полей. +Метод `relations()` исправлять не будем так как код, сгенерированный `Gii` нам +подходит. + +Изменение метода `rules()` +-------------------------- + +Начнём с уточнения правил валидации, сгенерированных при помощи `Gii`. +Для комментариев будем использовать следующие правила: + +~~~ +[php] +public function rules() +{ + return array( + array('content, author, email', 'required'), + array('author, email, url', 'length', 'max'=>128), + array('email','email'), + array('url','url'), + ); +} +~~~ + +Здесь мы указываем, что атрибуты `author`, `email` и `content` обязательны. +Длина `author`, `email` и `url` не может превышать 128 символов. Атрибут `email` +должен содержать корректный email-адрес. `url` должен содержать корректный URL. + + +Изменение метода `attributeLabels()` +------------------------------------ + +Изменим метод `attributeLabels()`. Зададим свои подписи атрибутам. Этот метод +возвращает массив пар имя атрибута-подпись. + +~~~ +[php] +public function attributeLabels() +{ + return array( + 'id' => 'Id', + 'content' => 'Comment', + 'status' => 'Status', + 'create_time' => 'Create Time', + 'author' => 'Name', + 'email' => 'Email', + 'url' => 'Website', + 'post_id' => 'Post', + ); +} +~~~ + +> Tip|Подсказка: Если подпись атрибута не задана в `attributeLabels()`, +для её генерации используется специальный алгоритм. К примеру, для +атрибутов `create_time` и `createTime` подпись будет выглядеть как +`Create Time`. + + +Изменение процесса сохранения +----------------------------- + +Для того, чтобы записывать время создания комментария, переопределим +метод `beforeSave()` класса `Comment` также, как это сделано для модели `Post`: + +~~~ +[php] +protected function beforeSave() +{ + if(parent::beforeSave()) + { + if($this->isNewRecord) + $this->create_time=time(); + return true; + } + else + return false; +} ~~~ \ No newline at end of file diff --git a/docs/blog/ru/post.admin.txt b/docs/blog/ru/post.admin.txt index 4ccfbac95..23ca41128 100644 --- a/docs/blog/ru/post.admin.txt +++ b/docs/blog/ru/post.admin.txt @@ -1,138 +1,138 @@ -Управление записями -=================== - -Под управлением записями подразумевается отображение их списка в административном -разделе с возможностью просматривать записи с любым статусом, редактировать и -удалять их. Эта функциональность реализуется в действиях `admin` и `delete` -соответственно. Код, сгенерированный при помощи `Gii` почти не нуждается в -изменениях. Ниже мы объясним, как реализованы эти действия. - - -Отображение записей в виде таблицы ----------------------------------- - -Действие `admin` выводит записи со всеми статусами в виде таблицы, -разбитой на несколько страниц и поддерживающей сортировку по нескольким колонкам. -Далее приведён метод `actionAdmin()` контроллера `PostController`: - -~~~ -[php] -public function actionAdmin() -{ - $model=new Post('search'); - if(isset($_GET['Post'])) - $model->attributes=$_GET['Post']; - $this->render('admin',array( - 'model'=>$model, - )); -} -~~~ - -Данный код полностью сгенерирован `Gii`. Сначала создаётся модель `Post` со [сценарием](/doc/guide/ru/form.model) -`search`, которую мы будем использовать для -сбора критериев поиска, указанных пользователем. Далее мы присваиваем -данные, введённые пользователем, модели. И, наконец, мы выводим отображение -`admin`, используя модель. - -Ниже приведён код отображения `admin`: - -~~~ -[php] -breadcrumbs=array( - 'Manage Posts', -); -?> -

Manage Posts

- -widget('zii.widgets.grid.CGridView', array( - 'dataProvider'=>$model->search(), - 'filter'=>$model, - 'columns'=>array( - array( - 'name'=>'title', - 'type'=>'raw', - 'value'=>'CHtml::link(CHtml::encode($data->title), $data->url)' - ), - array( - 'name'=>'status', - 'value'=>'Lookup::item("PostStatus",$data->status)', - 'filter'=>Lookup::items('PostStatus'), - ), - array( - 'name'=>'create_time', - 'type'=>'datetime', - 'filter'=>false, - ), - array( - 'class'=>'CButtonColumn', - ), - ), -)); ?> -~~~ - -Для вывода записей мы используем компонент [CGridView], который -разбивает данные на страницы и позволяет их сортировать по столбцам. -Наше изменение касается, главным образом, отображения каждого столбца. -К примеру, для столбца `title` мы указываем, что он должен содержать -ссылку на просмотр записи. Выражение `$data->url` возвращает значение -свойства `url`, которое мы определяем в классе `Post`. - -> Tip|Подсказка: При выводе текста мы используем [CHtml::encode()] для - кодирования сущностей HTML. Это позволяет избежать атак через [межсайтовый - скриптинг](/doc/guide/ru/topics.security). - - -Удаление записей ----------------- - -В data grid `admin` в каждой строке есть кнопка «Удалить», удаляющая соответствующую -запись. Действие `delete` выглядит следующим образом: - -~~~ -[php] -public function actionDelete() -{ - if(Yii::app()->request->isPostRequest) - { - // we only allow deletion via POST request - $this->loadModel()->delete(); - - if(!isset($_GET['ajax'])) - $this->redirect(array('index')); - } - else - throw new CHttpException(400,'Invalid request. Please do not repeat this request again.'); -} -~~~ - -Приведённый выше код полностью сгенерирован `Gii`. Остановимся подробнее на -проверке `$_GET['ajax']`. В виджете [CGridView] сортировка, постраничная -разбивка и удаление по умолчанию реализованы с использованием AJAX. То есть при -выполнении перечисленных действий страница перезагружаться не будет. -Виджет может работать и без AJAX (если свойство `ajaxUpdate` выставлено в -`false` или отключен JavaScript). В действии `delete` при AJAX запросе перенаправление -производиться не должно. - -Удаление записи должно вызывать удаление всех комментариев к ней. Вдобавок, -мы должны обновить теги в таблице `tbl_tag`. Обе задачи могут быть выполнены -в методе `afterDelete` модели `Post`: - -~~~ -[php] -protected function afterDelete() -{ - parent::afterDelete(); - Comment::model()->deleteAll('post_id='.$this->id); - Tag::model()->updateFrequency($this->tags, ''); -} -~~~ - -Код, приведённый выше не сложен: сначала удаляются все комментарии, чей -`post_id` равен ID удаляемой записи. Далее обновляется таблица `tbl_tag`. - -> Tip|Подсказка: Мы удаляем комментарии удаляемой записи в коде, так как SQLite -> не поддерживает ограничения по внешнему ключу. В СУБД, которые данное ограничение -> поддерживают (например, MySQL или PostgreSQL), можно использовать каскадное -> удаление комментариев в случае удаления записи. В этом случае нет необходимости +Управление записями +=================== + +Под управлением записями подразумевается отображение их списка в административном +разделе с возможностью просматривать записи с любым статусом, редактировать и +удалять их. Эта функциональность реализуется в действиях `admin` и `delete` +соответственно. Код, сгенерированный при помощи `Gii` почти не нуждается в +изменениях. Ниже мы объясним, как реализованы эти действия. + + +Отображение записей в виде таблицы +---------------------------------- + +Действие `admin` выводит записи со всеми статусами в виде таблицы, +разбитой на несколько страниц и поддерживающей сортировку по нескольким колонкам. +Далее приведён метод `actionAdmin()` контроллера `PostController`: + +~~~ +[php] +public function actionAdmin() +{ + $model=new Post('search'); + if(isset($_GET['Post'])) + $model->attributes=$_GET['Post']; + $this->render('admin',array( + 'model'=>$model, + )); +} +~~~ + +Данный код полностью сгенерирован `Gii`. Сначала создаётся модель `Post` со [сценарием](/doc/guide/ru/form.model) +`search`, которую мы будем использовать для +сбора критериев поиска, указанных пользователем. Далее мы присваиваем +данные, введённые пользователем, модели. И, наконец, мы выводим отображение +`admin`, используя модель. + +Ниже приведён код отображения `admin`: + +~~~ +[php] +breadcrumbs=array( + 'Manage Posts', +); +?> +

Manage Posts

+ +widget('zii.widgets.grid.CGridView', array( + 'dataProvider'=>$model->search(), + 'filter'=>$model, + 'columns'=>array( + array( + 'name'=>'title', + 'type'=>'raw', + 'value'=>'CHtml::link(CHtml::encode($data->title), $data->url)' + ), + array( + 'name'=>'status', + 'value'=>'Lookup::item("PostStatus",$data->status)', + 'filter'=>Lookup::items('PostStatus'), + ), + array( + 'name'=>'create_time', + 'type'=>'datetime', + 'filter'=>false, + ), + array( + 'class'=>'CButtonColumn', + ), + ), +)); ?> +~~~ + +Для вывода записей мы используем компонент [CGridView], который +разбивает данные на страницы и позволяет их сортировать по столбцам. +Наше изменение касается, главным образом, отображения каждого столбца. +К примеру, для столбца `title` мы указываем, что он должен содержать +ссылку на просмотр записи. Выражение `$data->url` возвращает значение +свойства `url`, которое мы определяем в классе `Post`. + +> Tip|Подсказка: При выводе текста мы используем [CHtml::encode()] для + кодирования сущностей HTML. Это позволяет избежать атак через [межсайтовый + скриптинг](/doc/guide/ru/topics.security). + + +Удаление записей +---------------- + +В data grid `admin` в каждой строке есть кнопка «Удалить», удаляющая соответствующую +запись. Действие `delete` выглядит следующим образом: + +~~~ +[php] +public function actionDelete() +{ + if(Yii::app()->request->isPostRequest) + { + // we only allow deletion via POST request + $this->loadModel()->delete(); + + if(!isset($_GET['ajax'])) + $this->redirect(array('index')); + } + else + throw new CHttpException(400,'Invalid request. Please do not repeat this request again.'); +} +~~~ + +Приведённый выше код полностью сгенерирован `Gii`. Остановимся подробнее на +проверке `$_GET['ajax']`. В виджете [CGridView] сортировка, постраничная +разбивка и удаление по умолчанию реализованы с использованием AJAX. То есть при +выполнении перечисленных действий страница перезагружаться не будет. +Виджет может работать и без AJAX (если свойство `ajaxUpdate` выставлено в +`false` или отключен JavaScript). В действии `delete` при AJAX запросе перенаправление +производиться не должно. + +Удаление записи должно вызывать удаление всех комментариев к ней. Вдобавок, +мы должны обновить теги в таблице `tbl_tag`. Обе задачи могут быть выполнены +в методе `afterDelete` модели `Post`: + +~~~ +[php] +protected function afterDelete() +{ + parent::afterDelete(); + Comment::model()->deleteAll('post_id='.$this->id); + Tag::model()->updateFrequency($this->tags, ''); +} +~~~ + +Код, приведённый выше не сложен: сначала удаляются все комментарии, чей +`post_id` равен ID удаляемой записи. Далее обновляется таблица `tbl_tag`. + +> Tip|Подсказка: Мы удаляем комментарии удаляемой записи в коде, так как SQLite +> не поддерживает ограничения по внешнему ключу. В СУБД, которые данное ограничение +> поддерживают (например, MySQL или PostgreSQL), можно использовать каскадное +> удаление комментариев в случае удаления записи. В этом случае нет необходимости > удалять комментарии в коде. \ No newline at end of file diff --git a/docs/blog/ru/post.create.txt b/docs/blog/ru/post.create.txt index d036eb5b7..3eb69f9e6 100644 --- a/docs/blog/ru/post.create.txt +++ b/docs/blog/ru/post.create.txt @@ -1,123 +1,123 @@ -Создание и редактирование записей -================================= - -После того, как мы закончили с моделью `Post`, займёмся контроллером -`PostController` и его отображениями. В данном разделе мы настроим правила -доступа операций CRUD. Затем изменим код, отвечающий за `создание`(`create`) и -`обновление`(`update`). - -Настройка правил доступа ------------------------- - -Первое, что мы запланировали — настройка -[прав доступа](/doc/guide/ru/topics.auth#access-control-filter). Код, -сгенерированный при помощи `gii` нам не подойдёт. - -Необходимо изменить метод `accessRules()` в файле -`/wwwroot/blog/protected/controllers/PostController.php` следующим образом: - -~~~ -[php] -public function accessRules() -{ - return array( - array('allow', // позволим всем пользователям выполнять действия 'list' и 'show' - 'actions'=>array('index', 'view'), - 'users'=>array('*'), - ), - array('allow', // позволим аутентифицированным пользователям выполнять любые действия - 'users'=>array('@'), - ), - array('deny', // остальным запретим всё - 'users'=>array('*'), - ), - ); -} -~~~ - -Описанные выше правила разрешают всем пользователям выполнять действия -`index` и `view`. Аутентифицированным — любые действия, включая `admin`. -Всем остальным пользователям запрещено всё. Стоит отметить, что правила -применяются в порядке их описания. Первое сработавшее правило определяет, -давать доступ или не давать. К примеру, если текущий пользователь является -владельцем системы и пытается зайти на страницу создания записи, будет применено -второе правило и доступ будет разрешён. - - -Правки в действиях `create` и `update` --------------------------------------- - -Операции `create` и `update` довольно похожи. В обоих случаях требуется -вывести HTML форму для сбора данных, вводимых пользователем. Также требуется -валидация и сохранение данных в БД. Главное отличие в том, что при `update` -форма будет заполняться данными о редактируемой записи. По этой причине `gii` -генерирует вложенное отображение `/wwwroot/blog/protected/views/post/_form.php`, -которое включается как в отображение `create`, так и отображение `update` для -вывода HTML формы. - -Для начала изменим файл `_form.php` таким образом, чтобы форма собирала только -нужные нам данные: `title`, `content`, `tags` и `status`. Для первых трёх атрибутов мы -используем текстовые поля. Для `status` — выпадающий список со всеми возможными -состояниями записи: - -~~~ -[php] -dropDownList($model,'status',Lookup::items('PostStatus')); ?> -~~~ - -В приведённом коде для получения списка статусов используется вызов `Lookup::items('PostStatus')`. - -Далее изменим класс `Post` таким образом, чтобы он автоматически выставлял -некоторые атрибуты (такие, как `create_time` и `author_id`) непосредственно перед -сохранением записи в БД. Перекроем метод `beforeSave()`: - -~~~ -[php] -protected function beforeSave() -{ - if(parent::beforeSave()) - { - if($this->isNewRecord) - { - $this->create_time=$this->update_time=time(); - $this->author_id=Yii::app()->user->id; - } - else - $this->update_time=time(); - return true; - } - else - return false; -} -~~~ - -При сохранении записи мы хотим также обновить информацию о частоте использования -тегов в таблице `tbl_tag`. Мы можем реализовать это в методе `afterSave()`, -который автоматически вызывается после успешного сохранения записи в БД. - -~~~ -[php] -protected function afterSave() -{ - parent::afterSave(); - Tag::model()->updateFrequency($this->_oldTags, $this->tags); -} - -private $_oldTags; - -protected function afterFind() -{ - parent::afterFind(); - $this->_oldTags=$this->tags; -} -~~~ - -Так как необходимо определить, менял ли пользователь теги при редактировании -записи, нам понадобятся старые теги. Для этого мы реализуем метод -`afterFind()`, который записывает старые теги в свойство `_oldTags`. Метод -`afterFind()` вызывается автоматически при заполнении модели AR данными, полученными -из БД. - -Здесь мы не будем детально рассматривать метод `Tag::updateFrequency()`. -Заинтересованные читатели могут ознакомиться с ним в файле +Создание и редактирование записей +================================= + +После того, как мы закончили с моделью `Post`, займёмся контроллером +`PostController` и его отображениями. В данном разделе мы настроим правила +доступа операций CRUD. Затем изменим код, отвечающий за `создание`(`create`) и +`обновление`(`update`). + +Настройка правил доступа +------------------------ + +Первое, что мы запланировали — настройка +[прав доступа](/doc/guide/ru/topics.auth#access-control-filter). Код, +сгенерированный при помощи `gii` нам не подойдёт. + +Необходимо изменить метод `accessRules()` в файле +`/wwwroot/blog/protected/controllers/PostController.php` следующим образом: + +~~~ +[php] +public function accessRules() +{ + return array( + array('allow', // позволим всем пользователям выполнять действия 'list' и 'show' + 'actions'=>array('index', 'view'), + 'users'=>array('*'), + ), + array('allow', // позволим аутентифицированным пользователям выполнять любые действия + 'users'=>array('@'), + ), + array('deny', // остальным запретим всё + 'users'=>array('*'), + ), + ); +} +~~~ + +Описанные выше правила разрешают всем пользователям выполнять действия +`index` и `view`. Аутентифицированным — любые действия, включая `admin`. +Всем остальным пользователям запрещено всё. Стоит отметить, что правила +применяются в порядке их описания. Первое сработавшее правило определяет, +давать доступ или не давать. К примеру, если текущий пользователь является +владельцем системы и пытается зайти на страницу создания записи, будет применено +второе правило и доступ будет разрешён. + + +Правки в действиях `create` и `update` +-------------------------------------- + +Операции `create` и `update` довольно похожи. В обоих случаях требуется +вывести HTML форму для сбора данных, вводимых пользователем. Также требуется +валидация и сохранение данных в БД. Главное отличие в том, что при `update` +форма будет заполняться данными о редактируемой записи. По этой причине `gii` +генерирует вложенное отображение `/wwwroot/blog/protected/views/post/_form.php`, +которое включается как в отображение `create`, так и отображение `update` для +вывода HTML формы. + +Для начала изменим файл `_form.php` таким образом, чтобы форма собирала только +нужные нам данные: `title`, `content`, `tags` и `status`. Для первых трёх атрибутов мы +используем текстовые поля. Для `status` — выпадающий список со всеми возможными +состояниями записи: + +~~~ +[php] +dropDownList($model,'status',Lookup::items('PostStatus')); ?> +~~~ + +В приведённом коде для получения списка статусов используется вызов `Lookup::items('PostStatus')`. + +Далее изменим класс `Post` таким образом, чтобы он автоматически выставлял +некоторые атрибуты (такие, как `create_time` и `author_id`) непосредственно перед +сохранением записи в БД. Перекроем метод `beforeSave()`: + +~~~ +[php] +protected function beforeSave() +{ + if(parent::beforeSave()) + { + if($this->isNewRecord) + { + $this->create_time=$this->update_time=time(); + $this->author_id=Yii::app()->user->id; + } + else + $this->update_time=time(); + return true; + } + else + return false; +} +~~~ + +При сохранении записи мы хотим также обновить информацию о частоте использования +тегов в таблице `tbl_tag`. Мы можем реализовать это в методе `afterSave()`, +который автоматически вызывается после успешного сохранения записи в БД. + +~~~ +[php] +protected function afterSave() +{ + parent::afterSave(); + Tag::model()->updateFrequency($this->_oldTags, $this->tags); +} + +private $_oldTags; + +protected function afterFind() +{ + parent::afterFind(); + $this->_oldTags=$this->tags; +} +~~~ + +Так как необходимо определить, менял ли пользователь теги при редактировании +записи, нам понадобятся старые теги. Для этого мы реализуем метод +`afterFind()`, который записывает старые теги в свойство `_oldTags`. Метод +`afterFind()` вызывается автоматически при заполнении модели AR данными, полученными +из БД. + +Здесь мы не будем детально рассматривать метод `Tag::updateFrequency()`. +Заинтересованные читатели могут ознакомиться с ним в файле `/wwwroot/yii/demos/blog/protected/models/Tag.php`. \ No newline at end of file diff --git a/docs/blog/ru/post.display.txt b/docs/blog/ru/post.display.txt index ac47347da..f8e94791f 100644 --- a/docs/blog/ru/post.display.txt +++ b/docs/blog/ru/post.display.txt @@ -1,139 +1,139 @@ -Отображение записей -=================== - -В нашем приложении запись может показываться как отдельно, так и среди других -записей. Первое реализуется действием `view`, второе — `index`. В данном разделе -мы изменим оба действия для достижения первоначальных требований. - - -Изменение действия `view` -------------------------- - -Действие `view` реализовано в методе `actionView()` контроллера `PostController`. -Отдаваемый пользователю HTML генерируется из отображения `view`, находящегося -в файле `/wwwroot/blog/protected/views/post/view.php`. - -Ниже приведён код действия `view` контроллера `PostController`: - -~~~ -[php] -public function actionView() -{ - $post=$this->loadModel(); - $this->render('view',array( - 'model'=>$post, - )); -} - -private $_model; - -public function loadModel() -{ - if($this->_model===null) - { - if(isset($_GET['id'])) - { - if(Yii::app()->user->isGuest) - $condition='status='.Post::STATUS_PUBLISHED - .' OR status='.Post::STATUS_ARCHIVED; - else - $condition=''; - $this->_model=Post::model()->findByPk($_GET['id'], $condition); - } - if($this->_model===null) - throw new CHttpException(404,'Запрашиваемая страница не существует.'); - } - return $this->_model; -} -~~~ - -Наши изменения в основном коснулись метода `loadModel()`. В нём мы получаем запись -из таблицы `Post`, используя параметр `id` из GET. Если запись не найдена, -не опубликована или находится в архиве, и при этом пользователь является -гостем — показываем ошибку 404. Иначе возвращаем объект записи методу `actionView()`, -который передаёт объект отображению. - -> Tip|Подсказка: Yii перехватывает исключения HTTP (экземпляры класса - [CHttpException]) и отображает их, используя либо предопределённые, - либо свои шаблоны. Каркас, сгенерированный `yiic` уже содержит - свой шаблон для ошибок в файле `/wwwroot/blog/protected/views/site/error.php`. - При необходимости мы можем изменить этот файл. - -Изменения в отображении `view` в основном затрагивают форматирование и стили -отображения записи, поэтому на нём мы останавливаться не будем. -Заинтересованные читатели могут обратиться к файлу -`/wwwroot/blog/protected/views/post/view.php`. - - -Изменение действия `index` -------------------------- - -Как и в действии `view`, мы будем изменять действие `index` в двух местах: -метод `actionIndex()` контроллера `PostController` и отображение -`/wwwroot/blog/protected/views/post/index.php`. Требуется добавить поддержку -отображения записей с определённым тегом. - -Ниже приведён изменённый метод `actionIndex()` контроллера `PostController`: - -~~~ -[php] -public function actionIndex() -{ - $criteria=new CDbCriteria(array( - 'condition'=>'status='.Post::STATUS_PUBLISHED, - 'order'=>'update_time DESC', - 'with'=>'commentCount', - )); - if(isset($_GET['tag'])) - $criteria->addSearchCondition('tags',$_GET['tag']); - - $dataProvider=new CActiveDataProvider('Post', array( - 'pagination'=>array( - 'pageSize'=>5, - ), - 'criteria'=>$criteria, - )); - - $this->render('index',array( - 'dataProvider'=>$dataProvider, - )); -} -~~~ - -Сначала мы создаём критерий запроса для получения списка записей. Критерий -включает ограничения на получение только опубликованных записей и сортировку -по времени их обновления в обратном порядке. Так как при отображении записи -в списке мы также хотим показывать количество комментариев, в критерии указывается -необходимость получения связи `commentCount`, описанного в `Post::relations()`. - -В том случае, когда пользователь хочет получить записи с определённым тегом, -мы добавляем в критерий условие поиска тега. - -Используя критерий мы создаём провайдер данных, нужный для трёх целей. Во-первых, -он занимается постраничной разбивкой данных. Мы задаём количество результатов на -страницу равным 5. Во-вторых, данные сортируются в соответствии -с запросом пользователя. И, наконец, провайдер отдаёт разбитые на страницы -отсортированные данные виджетам или отображению. - -После того, как мы закончили с `actionIndex()`, мы изменяем отображение `index` -как показано ниже. Будем отображать заголовок `h1` в том случае, когда пользователь -запрашивает записи с определённым тегом. - -~~~ -[php] - -

Записи с тегом

- - -widget('zii.widgets.CListView', array( - 'dataProvider'=>$dataProvider, - 'itemView'=>'_view', - 'template'=>"{items}\n{pager}", -)); ?> -~~~ - -Стоит отметить, что для построения списка записей мы используем [CListView]. -Этот виджет использует отображение для построения каждой отдельной записи. -Мы указываем отображение `_view`, то есть файл -`/wwwroot/blog/protected/views/post/_view.php`, в котором мы можем +Отображение записей +=================== + +В нашем приложении запись может показываться как отдельно, так и среди других +записей. Первое реализуется действием `view`, второе — `index`. В данном разделе +мы изменим оба действия для достижения первоначальных требований. + + +Изменение действия `view` +------------------------- + +Действие `view` реализовано в методе `actionView()` контроллера `PostController`. +Отдаваемый пользователю HTML генерируется из отображения `view`, находящегося +в файле `/wwwroot/blog/protected/views/post/view.php`. + +Ниже приведён код действия `view` контроллера `PostController`: + +~~~ +[php] +public function actionView() +{ + $post=$this->loadModel(); + $this->render('view',array( + 'model'=>$post, + )); +} + +private $_model; + +public function loadModel() +{ + if($this->_model===null) + { + if(isset($_GET['id'])) + { + if(Yii::app()->user->isGuest) + $condition='status='.Post::STATUS_PUBLISHED + .' OR status='.Post::STATUS_ARCHIVED; + else + $condition=''; + $this->_model=Post::model()->findByPk($_GET['id'], $condition); + } + if($this->_model===null) + throw new CHttpException(404,'Запрашиваемая страница не существует.'); + } + return $this->_model; +} +~~~ + +Наши изменения в основном коснулись метода `loadModel()`. В нём мы получаем запись +из таблицы `Post`, используя параметр `id` из GET. Если запись не найдена, +не опубликована или находится в архиве, и при этом пользователь является +гостем — показываем ошибку 404. Иначе возвращаем объект записи методу `actionView()`, +который передаёт объект отображению. + +> Tip|Подсказка: Yii перехватывает исключения HTTP (экземпляры класса + [CHttpException]) и отображает их, используя либо предопределённые, + либо свои шаблоны. Каркас, сгенерированный `yiic` уже содержит + свой шаблон для ошибок в файле `/wwwroot/blog/protected/views/site/error.php`. + При необходимости мы можем изменить этот файл. + +Изменения в отображении `view` в основном затрагивают форматирование и стили +отображения записи, поэтому на нём мы останавливаться не будем. +Заинтересованные читатели могут обратиться к файлу +`/wwwroot/blog/protected/views/post/view.php`. + + +Изменение действия `index` +------------------------- + +Как и в действии `view`, мы будем изменять действие `index` в двух местах: +метод `actionIndex()` контроллера `PostController` и отображение +`/wwwroot/blog/protected/views/post/index.php`. Требуется добавить поддержку +отображения записей с определённым тегом. + +Ниже приведён изменённый метод `actionIndex()` контроллера `PostController`: + +~~~ +[php] +public function actionIndex() +{ + $criteria=new CDbCriteria(array( + 'condition'=>'status='.Post::STATUS_PUBLISHED, + 'order'=>'update_time DESC', + 'with'=>'commentCount', + )); + if(isset($_GET['tag'])) + $criteria->addSearchCondition('tags',$_GET['tag']); + + $dataProvider=new CActiveDataProvider('Post', array( + 'pagination'=>array( + 'pageSize'=>5, + ), + 'criteria'=>$criteria, + )); + + $this->render('index',array( + 'dataProvider'=>$dataProvider, + )); +} +~~~ + +Сначала мы создаём критерий запроса для получения списка записей. Критерий +включает ограничения на получение только опубликованных записей и сортировку +по времени их обновления в обратном порядке. Так как при отображении записи +в списке мы также хотим показывать количество комментариев, в критерии указывается +необходимость получения связи `commentCount`, описанного в `Post::relations()`. + +В том случае, когда пользователь хочет получить записи с определённым тегом, +мы добавляем в критерий условие поиска тега. + +Используя критерий мы создаём провайдер данных, нужный для трёх целей. Во-первых, +он занимается постраничной разбивкой данных. Мы задаём количество результатов на +страницу равным 5. Во-вторых, данные сортируются в соответствии +с запросом пользователя. И, наконец, провайдер отдаёт разбитые на страницы +отсортированные данные виджетам или отображению. + +После того, как мы закончили с `actionIndex()`, мы изменяем отображение `index` +как показано ниже. Будем отображать заголовок `h1` в том случае, когда пользователь +запрашивает записи с определённым тегом. + +~~~ +[php] + +

Записи с тегом

+ + +widget('zii.widgets.CListView', array( + 'dataProvider'=>$dataProvider, + 'itemView'=>'_view', + 'template'=>"{items}\n{pager}", +)); ?> +~~~ + +Стоит отметить, что для построения списка записей мы используем [CListView]. +Этот виджет использует отображение для построения каждой отдельной записи. +Мы указываем отображение `_view`, то есть файл +`/wwwroot/blog/protected/views/post/_view.php`, в котором мы можем обращаться к записи через переменную `$data`. \ No newline at end of file diff --git a/docs/blog/ru/post.model.txt b/docs/blog/ru/post.model.txt index b5042df93..86a49f9d5 100644 --- a/docs/blog/ru/post.model.txt +++ b/docs/blog/ru/post.model.txt @@ -1,262 +1,262 @@ -Доработка модели Post -===================== - -Модель `Post`, сгенерированная при помощи `Gii`, нуждается в следующих изменениях: - - - метод `rules()`: задаёт правила валидации атрибутов модели; - - метод `relations()`: задаёт связи с другими объектами. - -> Info|Информация: [Модель](/doc/guide/ru/basics.model) состоит из набора атрибутов, - каждый из которых ассоциируется с соответствующим полем в таблице БД. - Атрибуты могут быть описаны явно как переменные класса, либо использоваться без - какого-либо описания. - -Изменение метода `rules()` --------------------------- - -В первую очередь необходимо определить правила валидации, которые позволят -убедиться в том, что данные, введённые пользователем, корректны до их сохранения в БД. -К примеру, атрибут `status` модели `Post` должен быть целым числом, равным 1, 2 или 3. -Консоль `Gii` генерирует правила валидации для каждой модели. При этом -используется структура БД, поэтому некоторые правила могут оказаться неточными. - -Основываясь на анализе требований, изменим метод `rules()` следующим образом: - -~~~ -[php] -public function rules() -{ - return array( - array('title, content, status', 'required'), - array('title', 'length', 'max'=>128), - array('status', 'in', 'range'=>array(1,2,3)), - array('tags', 'match', 'pattern'=>'/^[\w\s,]+$/', - 'message'=>'В тегах можно использовать только буквы.'), - array('tags', 'normalizeTags'), - - array('title, status', 'safe', 'on'=>'search'), - ); -} -~~~ - -В коде выше мы определили, что атрибуты `title`, `content` и `status` являются -обязательными для заполнения. Длина `title` не должна превышать 128 символов. -Значение `status` может быть 1 (черновик), 2 (опубликовано) или 3 (в архиве). -В `tags` могут содержаться только буквы, запятые и пробелы. Вводимые пользователем -теги дополнительно нормализуются при помощи `normalizeTags`. Это делается для того, -чтобы теги были уникальными и правильно разделялись запятыми. Последнее правило -используется поиском и будет описано позже. - -Валидаторы, такие как `required`, `length`, `in` и `match` являются стандартными -валидаторами Yii. Валидатор `normalizeTags` использует определённый в классе `Post` -метод. За дополнительной информацией о том, как описывать правила валидации вы -можете обратиться к [полному руководству](/doc/guide/ru/form.model#declaring-validation-rules). - -~~~ -[php] -public function normalizeTags($attribute,$params) -{ - $this->tags=Tag::array2string(array_unique(Tag::string2array($this->tags))); -} -~~~ - -где `array2string` и `string2array` - новые методы, которые мы должны -определить в классе модели `Tag`: - -~~~ -[php] -public static function string2array($tags) -{ - return preg_split('/\s*,\s*/',trim($tags),-1,PREG_SPLIT_NO_EMPTY); -} - -public static function array2string($tags) -{ - return implode(', ',$tags); -} -~~~ - -Правила, описанные в методе `rules()`, вызываются по очереди при вызове методов модели -[validate()|CModel::validate] или [save()|CActiveRecord::save]. - -> Note|Примечание: Важно помнить, что атрибуты, описываемые в `rules()` -> должны вводиться пользователем. Другие атрибуты модели `Post`, такие как `id` -или `create_time`, заполняемые в коде или напрямую в БД не должны присутствовать -в `rules()`. Подробнее это описано в разделе -[Безопасное присваивание значений атрибутам](/doc/guide/ru/form.model#securing-attribute-assignments). - -После того, как мы сделали описанные изменения, мы можем зайти на страницу -создания записи и проверить, что новые правила валидации работают. - - -Изменение метода `relations()` ------------------------------- - -Далее укажем в методе `relations()` связанные с записью объекты. После этого мы -сможем использовать [реляционную ActiveRecord (RAR)](/doc/guide/ru/database.arr) -для получения связанных с записью данных, таких как информацию об авторе и -комментарии. Сложные SQL запросы с JOIN в этом случае не потребуются. - -Определим метод `relations()`: - -~~~ -[php] -public function relations() -{ - return array( - 'author' => array(self::BELONGS_TO, 'User', 'author_id'), - 'comments' => array(self::HAS_MANY, 'Comment', 'post_id', - 'condition'=>'comments.status='.Comment::STATUS_APPROVED, - 'order'=>'comments.create_time DESC'), - 'commentCount' => array(self::STAT, 'Comment', 'post_id', - 'condition'=>'status='.Comment::STATUS_APPROVED), - ); -} -~~~ - -Также, в классе модели `Comment` мы описываем две константы, которые используются -в приведённом выше методе: - -~~~ -[php] -class Comment extends CActiveRecord -{ - const STATUS_PENDING=1; - const STATUS_APPROVED=2; - ...... -} -~~~ - -Связи, описанные в методе `relations()`, означают следующее: - - * Запись принадлежит автору(`User`), связь с которым устанавливается на основе - поля записи `author_id`; - * Запись может содержать много комментариев(`Comment`), связь с которыми - устанавливается на основе поля комментария `post_id`. Комментарии сортируются по - времени их создания. - * Связь `commentCount` является особенной так как возвращает результат - агрегации, то есть число комментариев записи. - -Задав описанные выше связи, мы можем получить информацию об авторе и -комментариях к записи следующим образом: - -~~~ -[php] -$author=$post->author; -echo $author->username; - -$comments=$post->comments; -foreach($comments as $comment) - echo $comment->content; -~~~ - -Более подробно использование и определение связей описано в -[полном руководстве](/doc/guide/ru/database.arr). - -Добавляем свойство `url` ------------------------- - -Каждой записи соответствует уникальный URL. Вместо повсеместного -вызова [CWebApplication::createUrl] для формирования этого URL, мы можем -добавить свойство `url` модели `Post` и повторно использовать код для генерации URL. -Позже мы опишем, как получить красивые URL. Использование свойства модели позволит -реализовать это максимально удобно. - -Для того, чтобы добавить свойство `url`, мы добавляем геттер в класс `Post`: - -~~~ -[php] -class Post extends CActiveRecord -{ - public function getUrl() - { - return Yii::app()->createUrl('post/view', array( - 'id'=>$this->id, - 'title'=>$this->title, - )); - } -} -~~~ - -В дополнение к ID записи, в URL через GET-параметр мы выводим заголовок. -Делается это главным образом для оптимизации под поисковые алгоритмы (SEO). -Подробнее это будет описано в разделе «[человекопонятные URL](/doc/blog/final.url)». - -Так как [CComponent] является предком класса `Post`, геттер `getUrl()` позволяет -нам писать код вроде `$post->url`. При обращении к `$post->url` будет вызван геттер -и мы получим результат его выполнения. Более подробно это описано -[в полном руководстве](/doc/guide/ru/basics.component). - -Текстовое представление для статуса ------------------------------------ - -Так как статус записи хранится в БД в виде числа, нам необходимо получить его -текстовое представление для отображения пользователям. Для больших систем такое -требование является довольно типичным. - -Для хранения связей между целыми числами и их текстовым представлением, необходимым -другим объектам данных, мы используем таблицу `tbl_lookup`. Для более удобного -получения текстовых данных изменим модель `Lookup` следующим образом: - -~~~ -[php] -class Lookup extends CActiveRecord -{ - … - - private static $_items=array(); - - public static function items($type) - { - if(!isset(self::$_items[$type])) - self::loadItems($type); - return self::$_items[$type]; - } - - public static function item($type,$code) - { - if(!isset(self::$_items[$type])) - self::loadItems($type); - return isset(self::$_items[$type][$code]) ? self::$_items[$type][$code] : false; - } - - private static function loadItems($type) - { - self::$_items[$type]=array(); - $models=self::model()->findAll(array( - 'condition'=>'type=:type', - 'params'=>array(':type'=>$type), - 'order'=>'position', - )); - foreach($models as $model) - self::$_items[$type][$model->code]=$model->name; - } -} -~~~ - -Мы добавили два статичных метода: `Lookup::items()` и `Lookup::item()`. -Первый возвращает список строк для заданного типа данных, второй — конкретную -строку для заданного типа данных и значения. - -В базе данных блога есть два типа данных: `PostStatus` и `CommentStatus`. -Первый содержит возможные статусы записи, второй — статусы комментария. - -Для того, чтобы сделать код более читаемым мы описываем константы, соответствующие -целочисленным значениям статуса. Эти константы необходимо использовать в коде -вместо соответствующих им целых значений. - -~~~ -[php] -class Post extends CActiveRecord -{ - const STATUS_DRAFT=1; - const STATUS_PUBLISHED=2; - const STATUS_ARCHIVED=3; - ...... -} -~~~ - -Следовательно, для получения списка всех возможных статусов записи (массива строк -с ключами, равными соответствующим им значениям), мы можем воспользоваться кодом -`Lookup::items('PostStatus')`. А для получения конкретной строки — кодом +Доработка модели Post +===================== + +Модель `Post`, сгенерированная при помощи `Gii`, нуждается в следующих изменениях: + + - метод `rules()`: задаёт правила валидации атрибутов модели; + - метод `relations()`: задаёт связи с другими объектами. + +> Info|Информация: [Модель](/doc/guide/ru/basics.model) состоит из набора атрибутов, + каждый из которых ассоциируется с соответствующим полем в таблице БД. + Атрибуты могут быть описаны явно как переменные класса, либо использоваться без + какого-либо описания. + +Изменение метода `rules()` +-------------------------- + +В первую очередь необходимо определить правила валидации, которые позволят +убедиться в том, что данные, введённые пользователем, корректны до их сохранения в БД. +К примеру, атрибут `status` модели `Post` должен быть целым числом, равным 1, 2 или 3. +Консоль `Gii` генерирует правила валидации для каждой модели. При этом +используется структура БД, поэтому некоторые правила могут оказаться неточными. + +Основываясь на анализе требований, изменим метод `rules()` следующим образом: + +~~~ +[php] +public function rules() +{ + return array( + array('title, content, status', 'required'), + array('title', 'length', 'max'=>128), + array('status', 'in', 'range'=>array(1,2,3)), + array('tags', 'match', 'pattern'=>'/^[\w\s,]+$/', + 'message'=>'В тегах можно использовать только буквы.'), + array('tags', 'normalizeTags'), + + array('title, status', 'safe', 'on'=>'search'), + ); +} +~~~ + +В коде выше мы определили, что атрибуты `title`, `content` и `status` являются +обязательными для заполнения. Длина `title` не должна превышать 128 символов. +Значение `status` может быть 1 (черновик), 2 (опубликовано) или 3 (в архиве). +В `tags` могут содержаться только буквы, запятые и пробелы. Вводимые пользователем +теги дополнительно нормализуются при помощи `normalizeTags`. Это делается для того, +чтобы теги были уникальными и правильно разделялись запятыми. Последнее правило +используется поиском и будет описано позже. + +Валидаторы, такие как `required`, `length`, `in` и `match` являются стандартными +валидаторами Yii. Валидатор `normalizeTags` использует определённый в классе `Post` +метод. За дополнительной информацией о том, как описывать правила валидации вы +можете обратиться к [полному руководству](/doc/guide/ru/form.model#declaring-validation-rules). + +~~~ +[php] +public function normalizeTags($attribute,$params) +{ + $this->tags=Tag::array2string(array_unique(Tag::string2array($this->tags))); +} +~~~ + +где `array2string` и `string2array` - новые методы, которые мы должны +определить в классе модели `Tag`: + +~~~ +[php] +public static function string2array($tags) +{ + return preg_split('/\s*,\s*/',trim($tags),-1,PREG_SPLIT_NO_EMPTY); +} + +public static function array2string($tags) +{ + return implode(', ',$tags); +} +~~~ + +Правила, описанные в методе `rules()`, вызываются по очереди при вызове методов модели +[validate()|CModel::validate] или [save()|CActiveRecord::save]. + +> Note|Примечание: Важно помнить, что атрибуты, описываемые в `rules()` +> должны вводиться пользователем. Другие атрибуты модели `Post`, такие как `id` +или `create_time`, заполняемые в коде или напрямую в БД не должны присутствовать +в `rules()`. Подробнее это описано в разделе +[Безопасное присваивание значений атрибутам](/doc/guide/ru/form.model#securing-attribute-assignments). + +После того, как мы сделали описанные изменения, мы можем зайти на страницу +создания записи и проверить, что новые правила валидации работают. + + +Изменение метода `relations()` +------------------------------ + +Далее укажем в методе `relations()` связанные с записью объекты. После этого мы +сможем использовать [реляционную ActiveRecord (RAR)](/doc/guide/ru/database.arr) +для получения связанных с записью данных, таких как информацию об авторе и +комментарии. Сложные SQL запросы с JOIN в этом случае не потребуются. + +Определим метод `relations()`: + +~~~ +[php] +public function relations() +{ + return array( + 'author' => array(self::BELONGS_TO, 'User', 'author_id'), + 'comments' => array(self::HAS_MANY, 'Comment', 'post_id', + 'condition'=>'comments.status='.Comment::STATUS_APPROVED, + 'order'=>'comments.create_time DESC'), + 'commentCount' => array(self::STAT, 'Comment', 'post_id', + 'condition'=>'status='.Comment::STATUS_APPROVED), + ); +} +~~~ + +Также, в классе модели `Comment` мы описываем две константы, которые используются +в приведённом выше методе: + +~~~ +[php] +class Comment extends CActiveRecord +{ + const STATUS_PENDING=1; + const STATUS_APPROVED=2; + ...... +} +~~~ + +Связи, описанные в методе `relations()`, означают следующее: + + * Запись принадлежит автору(`User`), связь с которым устанавливается на основе + поля записи `author_id`; + * Запись может содержать много комментариев(`Comment`), связь с которыми + устанавливается на основе поля комментария `post_id`. Комментарии сортируются по + времени их создания. + * Связь `commentCount` является особенной так как возвращает результат + агрегации, то есть число комментариев записи. + +Задав описанные выше связи, мы можем получить информацию об авторе и +комментариях к записи следующим образом: + +~~~ +[php] +$author=$post->author; +echo $author->username; + +$comments=$post->comments; +foreach($comments as $comment) + echo $comment->content; +~~~ + +Более подробно использование и определение связей описано в +[полном руководстве](/doc/guide/ru/database.arr). + +Добавляем свойство `url` +------------------------ + +Каждой записи соответствует уникальный URL. Вместо повсеместного +вызова [CWebApplication::createUrl] для формирования этого URL, мы можем +добавить свойство `url` модели `Post` и повторно использовать код для генерации URL. +Позже мы опишем, как получить красивые URL. Использование свойства модели позволит +реализовать это максимально удобно. + +Для того, чтобы добавить свойство `url`, мы добавляем геттер в класс `Post`: + +~~~ +[php] +class Post extends CActiveRecord +{ + public function getUrl() + { + return Yii::app()->createUrl('post/view', array( + 'id'=>$this->id, + 'title'=>$this->title, + )); + } +} +~~~ + +В дополнение к ID записи, в URL через GET-параметр мы выводим заголовок. +Делается это главным образом для оптимизации под поисковые алгоритмы (SEO). +Подробнее это будет описано в разделе «[человекопонятные URL](/doc/blog/final.url)». + +Так как [CComponent] является предком класса `Post`, геттер `getUrl()` позволяет +нам писать код вроде `$post->url`. При обращении к `$post->url` будет вызван геттер +и мы получим результат его выполнения. Более подробно это описано +[в полном руководстве](/doc/guide/ru/basics.component). + +Текстовое представление для статуса +----------------------------------- + +Так как статус записи хранится в БД в виде числа, нам необходимо получить его +текстовое представление для отображения пользователям. Для больших систем такое +требование является довольно типичным. + +Для хранения связей между целыми числами и их текстовым представлением, необходимым +другим объектам данных, мы используем таблицу `tbl_lookup`. Для более удобного +получения текстовых данных изменим модель `Lookup` следующим образом: + +~~~ +[php] +class Lookup extends CActiveRecord +{ + … + + private static $_items=array(); + + public static function items($type) + { + if(!isset(self::$_items[$type])) + self::loadItems($type); + return self::$_items[$type]; + } + + public static function item($type,$code) + { + if(!isset(self::$_items[$type])) + self::loadItems($type); + return isset(self::$_items[$type][$code]) ? self::$_items[$type][$code] : false; + } + + private static function loadItems($type) + { + self::$_items[$type]=array(); + $models=self::model()->findAll(array( + 'condition'=>'type=:type', + 'params'=>array(':type'=>$type), + 'order'=>'position', + )); + foreach($models as $model) + self::$_items[$type][$model->code]=$model->name; + } +} +~~~ + +Мы добавили два статичных метода: `Lookup::items()` и `Lookup::item()`. +Первый возвращает список строк для заданного типа данных, второй — конкретную +строку для заданного типа данных и значения. + +В базе данных блога есть два типа данных: `PostStatus` и `CommentStatus`. +Первый содержит возможные статусы записи, второй — статусы комментария. + +Для того, чтобы сделать код более читаемым мы описываем константы, соответствующие +целочисленным значениям статуса. Эти константы необходимо использовать в коде +вместо соответствующих им целых значений. + +~~~ +[php] +class Post extends CActiveRecord +{ + const STATUS_DRAFT=1; + const STATUS_PUBLISHED=2; + const STATUS_ARCHIVED=3; + ...... +} +~~~ + +Следовательно, для получения списка всех возможных статусов записи (массива строк +с ключами, равными соответствующим им значениям), мы можем воспользоваться кодом +`Lookup::items('PostStatus')`. А для получения конкретной строки — кодом `Lookup::item('PostStatus', Post::STATUS_PUBLISHED)`. \ No newline at end of file diff --git a/docs/blog/ru/start.overview.txt b/docs/blog/ru/start.overview.txt index fed7f7fcf..fa17efd46 100644 --- a/docs/blog/ru/start.overview.txt +++ b/docs/blog/ru/start.overview.txt @@ -1,25 +1,25 @@ -Создание блога с использованием Yii -=================================== - -Данное учебное пособие описывает процесс создания блога, показанного в -[демонстрационном приложении](http://www.yiiframework.com/demos/blog/), которое -можно найти в архиве с фреймворком. -Каждый шаг разработки описан подробно и может быть применён при создании других -приложений. В дополнение к -[полному руководству](/doc/guide/ru/index) и -[API](http://www.yiiframework.com/doc/api/), данное пособие показывает, -вместо полного и подробного описания, пример практического применения Yii. - -Для того, чтобы читать данное пособие, не обязательно знать Yii. -Тем не менее, начальное знание объектно-ориентированного программирования -и баз данных помогут легче понять материал. - -> Note|Примечание: Данный документ не является пошаговым руководством. По ходу выполнения будьте готовы исправлять - возникающие ошибки, проверять API и читать полное руководство. - -Данное учебное пособие выпущено в соответствии с [положениями о документации Yii](http://www.yiiframework.com/doc/terms/). - -Переводчики ------------ -- Александр Макаров, Sam Dark ([rmcreative.ru](http://rmcreative.ru/)) +Создание блога с использованием Yii +=================================== + +Данное учебное пособие описывает процесс создания блога, показанного в +[демонстрационном приложении](http://www.yiiframework.com/demos/blog/), которое +можно найти в архиве с фреймворком. +Каждый шаг разработки описан подробно и может быть применён при создании других +приложений. В дополнение к +[полному руководству](/doc/guide/ru/index) и +[API](http://www.yiiframework.com/doc/api/), данное пособие показывает, +вместо полного и подробного описания, пример практического применения Yii. + +Для того, чтобы читать данное пособие, не обязательно знать Yii. +Тем не менее, начальное знание объектно-ориентированного программирования +и баз данных помогут легче понять материал. + +> Note|Примечание: Данный документ не является пошаговым руководством. По ходу выполнения будьте готовы исправлять + возникающие ошибки, проверять API и читать полное руководство. + +Данное учебное пособие выпущено в соответствии с [положениями о документации Yii](http://www.yiiframework.com/doc/terms/). + +Переводчики +----------- +- Александр Макаров, Sam Dark ([rmcreative.ru](http://rmcreative.ru/)) - Алексей Лукьяненко, Caveman ([caveman.ru](http://caveman.ru/)) \ No newline at end of file diff --git a/docs/blog/ru/toc.txt b/docs/blog/ru/toc.txt index a35e7c86d..bf0207d2e 100644 --- a/docs/blog/ru/toc.txt +++ b/docs/blog/ru/toc.txt @@ -1,33 +1,33 @@ -* Начало - - [Обзор](start.overview) - - [Знакомимся с Yii](start.testdrive) - - [Анализ требований](start.requirements) - - [Общая структура](start.design) - -* Начальное прототипирование - - [Настройка БД](prototype.database) - - [Генерация каркаса](prototype.scaffold) - - [Аутентификация](prototype.auth) - - [Итог](prototype.summary) - -* Управление записями - - [Доработка модели Post](post.model) - - [Создание и редактирование](post.create) - - [Отображение](post.display) - - [Управление записями](post.admin) - -* Управление комментариями - - [Доработка модели Comment](comment.model) - - [Создание и отображение](comment.create) - - [Управление комментариями](comment.admin) - -* Портлеты - - [Меню пользователя](portlet.menu) - - [Облако тэгов](portlet.tags) - - [Последние комментарии](portlet.comments) - -* Последние штрихи - - [Человекопонятные URL](final.url) - - [Журналирование ошибок](final.logging) - - [Тонкая настройка и развёртывание](final.deployment) - - [Дальнейшие улучшения](final.future) +* Начало + - [Обзор](start.overview) + - [Знакомимся с Yii](start.testdrive) + - [Анализ требований](start.requirements) + - [Общая структура](start.design) + +* Начальное прототипирование + - [Настройка БД](prototype.database) + - [Генерация каркаса](prototype.scaffold) + - [Аутентификация](prototype.auth) + - [Итог](prototype.summary) + +* Управление записями + - [Доработка модели Post](post.model) + - [Создание и редактирование](post.create) + - [Отображение](post.display) + - [Управление записями](post.admin) + +* Управление комментариями + - [Доработка модели Comment](comment.model) + - [Создание и отображение](comment.create) + - [Управление комментариями](comment.admin) + +* Портлеты + - [Меню пользователя](portlet.menu) + - [Облако тэгов](portlet.tags) + - [Последние комментарии](portlet.comments) + +* Последние штрихи + - [Человекопонятные URL](final.url) + - [Журналирование ошибок](final.logging) + - [Тонкая настройка и развёртывание](final.deployment) + - [Дальнейшие улучшения](final.future) diff --git a/docs/blog/zh_cn/comment.admin.txt b/docs/blog/zh_cn/comment.admin.txt index 8e3cd8e12..412ce76ad 100644 --- a/docs/blog/zh_cn/comment.admin.txt +++ b/docs/blog/zh_cn/comment.admin.txt @@ -1,60 +1,60 @@ -评论管理 -================= - -评论管理包括更新,删除和审核。这些操作是以 `CommentController` 类的动作实现的。 - - -评论的更新与删除 ------------------------------- - -由 `yiic` 生成的更新及删除评论的代码大部分都不需要修改。 - - -评论审核 ------------------- - -当评论刚创建时,它们处于待审核状态,需要等审核通过后才会显示给访客。审核评论主要就是修改评论的状态(status)列。 - -我们创建一个 `CommentController` 中的 `actionApprove()` 方法如下: - -~~~ -[php] -public function actionApprove() -{ - if(Yii::app()->request->isPostRequest) - { - $comment=$this->loadModel(); - $comment->approve(); - $this->redirect(array('index')); - } - else - throw new CHttpException(400,'Invalid request...'); -} -~~~ - -如上所示,当 `approve` 动作通过一个 POST 请求被调用时,我们执行了 `Comment` 模型中定义的 `approve()` 方法改变评论状态。然后我们重定向用户浏览器到显示此评论所属日志的页面。 - -我们还修改了 `Comment` 的 `actionIndex()` 方法以显示所有评论。我们希望看到等待审核的评论显示在前面。 - -~~~ -[php] -public function actionIndex() -{ - $dataProvider=new CActiveDataProvider('Comment', array( - 'criteria'=>array( - 'with'=>'post', - 'order'=>'t.status, t.create_time DESC', - ), - )); - - $this->render('index',array( - 'dataProvider'=>$dataProvider, - )); -} -~~~ - -注意,在上面的代码中,由于 `tbl_post` 和 `tbl_comment` 表都含有 `status` 和 `create_time` 列,我们需要通过使用表的别名前缀消除列名的歧义。 正如 [指南](http://www.yiiframework.com/doc/guide/database.arr#disambiguating-column-names) 中所描述的,在一个关系查询中,主表的别名总是使用 `t`。因此,我们在上面的代码中对 `status` 和 `create_time` 使用了 `t` 前缀。 - -和日志的索引视图(index view)类似, `CommentController` 的 `index` 视图使用 [CListView] 显示评论列表, [CListView] 又使用了局部视图 `/wwwroot/blog/protected/views/comment/_view.php` 显示每一条评论。此处我们不打算深入讲解。读者可参考博客演示中相应的文件 `/wwwroot/yii/demos/blog/protected/views/comment/_view.php`. - +评论管理 +================= + +评论管理包括更新,删除和审核。这些操作是以 `CommentController` 类的动作实现的。 + + +评论的更新与删除 +------------------------------ + +由 `yiic` 生成的更新及删除评论的代码大部分都不需要修改。 + + +评论审核 +------------------ + +当评论刚创建时,它们处于待审核状态,需要等审核通过后才会显示给访客。审核评论主要就是修改评论的状态(status)列。 + +我们创建一个 `CommentController` 中的 `actionApprove()` 方法如下: + +~~~ +[php] +public function actionApprove() +{ + if(Yii::app()->request->isPostRequest) + { + $comment=$this->loadModel(); + $comment->approve(); + $this->redirect(array('index')); + } + else + throw new CHttpException(400,'Invalid request...'); +} +~~~ + +如上所示,当 `approve` 动作通过一个 POST 请求被调用时,我们执行了 `Comment` 模型中定义的 `approve()` 方法改变评论状态。然后我们重定向用户浏览器到显示此评论所属日志的页面。 + +我们还修改了 `Comment` 的 `actionIndex()` 方法以显示所有评论。我们希望看到等待审核的评论显示在前面。 + +~~~ +[php] +public function actionIndex() +{ + $dataProvider=new CActiveDataProvider('Comment', array( + 'criteria'=>array( + 'with'=>'post', + 'order'=>'t.status, t.create_time DESC', + ), + )); + + $this->render('index',array( + 'dataProvider'=>$dataProvider, + )); +} +~~~ + +注意,在上面的代码中,由于 `tbl_post` 和 `tbl_comment` 表都含有 `status` 和 `create_time` 列,我们需要通过使用表的别名前缀消除列名的歧义。 正如 [指南](http://www.yiiframework.com/doc/guide/database.arr#disambiguating-column-names) 中所描述的,在一个关系查询中,主表的别名总是使用 `t`。因此,我们在上面的代码中对 `status` 和 `create_time` 使用了 `t` 前缀。 + +和日志的索引视图(index view)类似, `CommentController` 的 `index` 视图使用 [CListView] 显示评论列表, [CListView] 又使用了局部视图 `/wwwroot/blog/protected/views/comment/_view.php` 显示每一条评论。此处我们不打算深入讲解。读者可参考博客演示中相应的文件 `/wwwroot/yii/demos/blog/protected/views/comment/_view.php`. +
$Id: comment.admin.txt 1810 2010-02-18 00:24:54Z qiang.xue $
\ No newline at end of file diff --git a/docs/blog/zh_cn/comment.create.txt b/docs/blog/zh_cn/comment.create.txt index 6f9ca9fe4..82ba306a8 100644 --- a/docs/blog/zh_cn/comment.create.txt +++ b/docs/blog/zh_cn/comment.create.txt @@ -1,150 +1,150 @@ -评论的创建与显示 -================================ - -此节中,我们实现评论的创建与显示功能。 - -为增强用户交互体验,我们打算在用户输入完每个表单域时就提示用户可能的出错信息。也就是客户端输入验证。我们将看到,在Yii中实现这个是多么简单多么爽。注意,这需要 Yii 1.1.1 版或更高版本的支持。 - - -评论的显示 -------------------- - -我们使用日志详情页(由 `PostController` 的 `view` 动作生成)来显示和创建评论,而不是使用单独的页面。在日志内容下面,我们首先显示此日志的评论列表,然后显示一个用于创建评论的表单。 - -为了在日志详情页中显示评论,我们把视图脚本 `/wwwroot/blog/protected/views/post/view.php` 修改如下: - -~~~ -[php] -...这儿是日志的视图... - -
- commentCount>=1): ?> -

- commentCount . 'comment(s)'; ?> -

- - renderPartial('_comments',array( - 'post'=>$model, - 'comments'=>$model->comments, - )); ?> - -
-~~~ - -如上所示,我们调用了 `renderPartial()` 方法渲染一个名为 `_comments` 的局部视图以显示属于当前日志的评论列表。注意,在这个视图中我们使用了表达式 `$model->comments` 获取日志的评论。这是有效的,因为我们在 `Post` 类中声明了一个 `comments` 关系。执行此表达式会触发一个隐式的 JOIN 数据库查询以获取相应的评论数据。此特性被称为 [懒惰的关系查询(lazy relational query)](http://www.yiiframework.com/doc/guide/database.arr)。 - -局部视图 `_comments` 没有太多有意思的。它主要用于遍历每条评论并显示其详情。感兴趣的读者可以参考 `/wwwroot/yii/demos/blog/protected/post/_comments.php`. - - -评论的创建 ------------------ - -要处理评论创建,我们首先修改 `PostController` 中的 `actionView()` 方法如下: - -~~~ -[php] -public function actionView() -{ - $post=$this->loadModel(); - $comment=$this->newComment($post); - - $this->render('view',array( - 'model'=>$post, - 'comment'=>$comment, - )); -} - -protected function newComment($post) -{ - $comment=new Comment; - if(isset($_POST['Comment'])) - { - $comment->attributes=$_POST['Comment']; - if($post->addComment($comment)) - { - if($comment->status==Comment::STATUS_PENDING) - Yii::app()->user->setFlash('commentSubmitted','Thank you...'); - $this->refresh(); - } - } - return $comment; -} -~~~ - -如上所示,我们在渲染 `view` 前调用了 `newComment()` 方法。在 `newComment()` 方法中,我们创建了一个 `Comment` 实例并检查评论表单是否已提交。如果已提交,我们尝试通过调用 `$post->addComment($comment)` 添加日志评论。如果一切顺利,我们刷新详情页面。由于评论需要审核,我们将显示一条闪过信息(flash message)以作出提示。闪过信息通常是一条显示给最终用户的确认信息。如果用户点击了浏览器的刷新按钮,此信息将会消失。 - -此外,我们还需要修改 `/wwwroot/blog/protected/views/post/view.php` , - -~~~ -[php] -...... -
- ...... -

Leave a Comment

- - user->hasFlash('commentSubmitted')): ?> -
- user->getFlash('commentSubmitted'); ?> -
- - renderPartial('/comment/_form',array( - 'model'=>$comment, - )); ?> - - -
-~~~ - -以上代码中,如果有可用的闪过信息,我们就会显示它。如果没有,我们就通过渲染局部视图 `/wwwroot/blog/protected/views/comment/_form.php` 显示评论输入表单。 - - -客户端验证 ----------------------- - -为支持评论表单的客户端验证,我们需要对评论表单视图 `/wwwroot/blog/protected/views/comment/_form.php` 和 `newComment()` 方法做一些小的修改。 - -在 `_form.php` 文件中,我们主要需要在创建 [CActiveForm] 小物件时设置 [CActiveForm::enableAjaxValidation] 为 true: - -~~~ -[php] -
- -beginWidget('CActiveForm', array( - 'id'=>'comment-form', - 'enableAjaxValidation'=>true, -)); ?> -...... -endWidget(); ?> - -
-~~~ - -在 `newComment()` 方法中,我们插入了一段代码以响应 AJAX 验证请求。这段代码检查是否存在一个名为 `ajax` 的 `POST` 变量,如果存在,它将通过调用 [CActiveForm::validate] 显示验证结果。 - -~~~ -[php] -protected function newComment($post) -{ - $comment=new Comment; - - if(isset($_POST['ajax']) && $_POST['ajax']==='comment-form') - { - echo CActiveForm::validate($comment); - Yii::app()->end(); - } - - if(isset($_POST['Comment'])) - { - $comment->attributes=$_POST['Comment']; - if($post->addComment($comment)) - { - if($comment->status==Comment::STATUS_PENDING) - Yii::app()->user->setFlash('commentSubmitted','Thank you...'); - $this->refresh(); - } - } - return $comment; -} -~~~ - +评论的创建与显示 +================================ + +此节中,我们实现评论的创建与显示功能。 + +为增强用户交互体验,我们打算在用户输入完每个表单域时就提示用户可能的出错信息。也就是客户端输入验证。我们将看到,在Yii中实现这个是多么简单多么爽。注意,这需要 Yii 1.1.1 版或更高版本的支持。 + + +评论的显示 +------------------- + +我们使用日志详情页(由 `PostController` 的 `view` 动作生成)来显示和创建评论,而不是使用单独的页面。在日志内容下面,我们首先显示此日志的评论列表,然后显示一个用于创建评论的表单。 + +为了在日志详情页中显示评论,我们把视图脚本 `/wwwroot/blog/protected/views/post/view.php` 修改如下: + +~~~ +[php] +...这儿是日志的视图... + +
+ commentCount>=1): ?> +

+ commentCount . 'comment(s)'; ?> +

+ + renderPartial('_comments',array( + 'post'=>$model, + 'comments'=>$model->comments, + )); ?> + +
+~~~ + +如上所示,我们调用了 `renderPartial()` 方法渲染一个名为 `_comments` 的局部视图以显示属于当前日志的评论列表。注意,在这个视图中我们使用了表达式 `$model->comments` 获取日志的评论。这是有效的,因为我们在 `Post` 类中声明了一个 `comments` 关系。执行此表达式会触发一个隐式的 JOIN 数据库查询以获取相应的评论数据。此特性被称为 [懒惰的关系查询(lazy relational query)](http://www.yiiframework.com/doc/guide/database.arr)。 + +局部视图 `_comments` 没有太多有意思的。它主要用于遍历每条评论并显示其详情。感兴趣的读者可以参考 `/wwwroot/yii/demos/blog/protected/post/_comments.php`. + + +评论的创建 +----------------- + +要处理评论创建,我们首先修改 `PostController` 中的 `actionView()` 方法如下: + +~~~ +[php] +public function actionView() +{ + $post=$this->loadModel(); + $comment=$this->newComment($post); + + $this->render('view',array( + 'model'=>$post, + 'comment'=>$comment, + )); +} + +protected function newComment($post) +{ + $comment=new Comment; + if(isset($_POST['Comment'])) + { + $comment->attributes=$_POST['Comment']; + if($post->addComment($comment)) + { + if($comment->status==Comment::STATUS_PENDING) + Yii::app()->user->setFlash('commentSubmitted','Thank you...'); + $this->refresh(); + } + } + return $comment; +} +~~~ + +如上所示,我们在渲染 `view` 前调用了 `newComment()` 方法。在 `newComment()` 方法中,我们创建了一个 `Comment` 实例并检查评论表单是否已提交。如果已提交,我们尝试通过调用 `$post->addComment($comment)` 添加日志评论。如果一切顺利,我们刷新详情页面。由于评论需要审核,我们将显示一条闪过信息(flash message)以作出提示。闪过信息通常是一条显示给最终用户的确认信息。如果用户点击了浏览器的刷新按钮,此信息将会消失。 + +此外,我们还需要修改 `/wwwroot/blog/protected/views/post/view.php` , + +~~~ +[php] +...... +
+ ...... +

Leave a Comment

+ + user->hasFlash('commentSubmitted')): ?> +
+ user->getFlash('commentSubmitted'); ?> +
+ + renderPartial('/comment/_form',array( + 'model'=>$comment, + )); ?> + + +
+~~~ + +以上代码中,如果有可用的闪过信息,我们就会显示它。如果没有,我们就通过渲染局部视图 `/wwwroot/blog/protected/views/comment/_form.php` 显示评论输入表单。 + + +客户端验证 +---------------------- + +为支持评论表单的客户端验证,我们需要对评论表单视图 `/wwwroot/blog/protected/views/comment/_form.php` 和 `newComment()` 方法做一些小的修改。 + +在 `_form.php` 文件中,我们主要需要在创建 [CActiveForm] 小物件时设置 [CActiveForm::enableAjaxValidation] 为 true: + +~~~ +[php] +
+ +beginWidget('CActiveForm', array( + 'id'=>'comment-form', + 'enableAjaxValidation'=>true, +)); ?> +...... +endWidget(); ?> + +
+~~~ + +在 `newComment()` 方法中,我们插入了一段代码以响应 AJAX 验证请求。这段代码检查是否存在一个名为 `ajax` 的 `POST` 变量,如果存在,它将通过调用 [CActiveForm::validate] 显示验证结果。 + +~~~ +[php] +protected function newComment($post) +{ + $comment=new Comment; + + if(isset($_POST['ajax']) && $_POST['ajax']==='comment-form') + { + echo CActiveForm::validate($comment); + Yii::app()->end(); + } + + if(isset($_POST['Comment'])) + { + $comment->attributes=$_POST['Comment']; + if($post->addComment($comment)) + { + if($comment->status==Comment::STATUS_PENDING) + Yii::app()->user->setFlash('commentSubmitted','Thank you...'); + $this->refresh(); + } + } + return $comment; +} +~~~ +
$Id: comment.create.txt 1753 2010-01-25 18:25:03Z qiang.xue $
\ No newline at end of file diff --git a/docs/blog/zh_cn/comment.model.txt b/docs/blog/zh_cn/comment.model.txt index 02d43f015..7bd759c3c 100644 --- a/docs/blog/zh_cn/comment.model.txt +++ b/docs/blog/zh_cn/comment.model.txt @@ -1,74 +1,74 @@ -自定义评论模型 -========================= - -对 `Comment` 模型,我们主要需要自定义 `rules()` 和 `attributeLabels()` 方法。 `attributeLabels()` 方法返回一个属性名字和属性标签的映射。由于 `yiic` 生成的 `relations()` 代码已经很不错了,我们现在就不需要改动这个玩意了。 - - -自定义 `rules()` 方法 ----------------------------- - -我们首先自定义 `yiic` 工具生成的验证规则。用于评论的验证规则如下: - -~~~ -[php] -public function rules() -{ - return array( - array('content, author, email', 'required'), - array('author, email, url', 'length', 'max'=>128), - array('email','email'), - array('url','url'), - ); -} -~~~ - -如上所示,我们制定了 `author`, `email` 和 `content` 属性是必填项; `author`, `email` 和 `url` 的长度不能超过 128; `email` 属性必须是一个有效的 Email 地址; `url` 属性必须是一个有效的 URL; - - -自定义 `attributeLabels()` 方法 --------------------------------------- - -然后我们自定义 `attributeLabels()` 方法以声明每个模型属性显示时的标签(label)文本。此方法在当我们调用 [CHtml::activeLabel()] 显示一个属性标签时返回一个包含了名字-标签对的数组。 - -~~~ -[php] -public function attributeLabels() -{ - return array( - 'id' => 'Id', - 'content' => 'Comment', - 'status' => 'Status', - 'create_time' => 'Create Time', - 'author' => 'Name', - 'email' => 'Email', - 'url' => 'Website', - 'post_id' => 'Post', - ); -} -~~~ - -> Tip|提示: 如果属性的标签没有在 `attributeLabels()` 中定义,则会使用一种算法自动生成一个标签名。例如,将会为属性 `create_time` 或 `createTime` 生成标签 `Create Time`。 - - -自定义存储的流程 --------------------------- - -由于我们想要记录评论创建的时间,和我们在 `Post` 模型中的做法一样,我们覆盖 `Comment` 的 `beforeSave()`方法如下: - -~~~ -[php] -protected function beforeSave() -{ - if(parent::beforeSave()) - { - if($this->isNewRecord) - $this->create_time=time(); - return true; - } - else - return false; -} -~~~ - - +自定义评论模型 +========================= + +对 `Comment` 模型,我们主要需要自定义 `rules()` 和 `attributeLabels()` 方法。 `attributeLabels()` 方法返回一个属性名字和属性标签的映射。由于 `yiic` 生成的 `relations()` 代码已经很不错了,我们现在就不需要改动这个玩意了。 + + +自定义 `rules()` 方法 +---------------------------- + +我们首先自定义 `yiic` 工具生成的验证规则。用于评论的验证规则如下: + +~~~ +[php] +public function rules() +{ + return array( + array('content, author, email', 'required'), + array('author, email, url', 'length', 'max'=>128), + array('email','email'), + array('url','url'), + ); +} +~~~ + +如上所示,我们制定了 `author`, `email` 和 `content` 属性是必填项; `author`, `email` 和 `url` 的长度不能超过 128; `email` 属性必须是一个有效的 Email 地址; `url` 属性必须是一个有效的 URL; + + +自定义 `attributeLabels()` 方法 +-------------------------------------- + +然后我们自定义 `attributeLabels()` 方法以声明每个模型属性显示时的标签(label)文本。此方法在当我们调用 [CHtml::activeLabel()] 显示一个属性标签时返回一个包含了名字-标签对的数组。 + +~~~ +[php] +public function attributeLabels() +{ + return array( + 'id' => 'Id', + 'content' => 'Comment', + 'status' => 'Status', + 'create_time' => 'Create Time', + 'author' => 'Name', + 'email' => 'Email', + 'url' => 'Website', + 'post_id' => 'Post', + ); +} +~~~ + +> Tip|提示: 如果属性的标签没有在 `attributeLabels()` 中定义,则会使用一种算法自动生成一个标签名。例如,将会为属性 `create_time` 或 `createTime` 生成标签 `Create Time`。 + + +自定义存储的流程 +-------------------------- + +由于我们想要记录评论创建的时间,和我们在 `Post` 模型中的做法一样,我们覆盖 `Comment` 的 `beforeSave()`方法如下: + +~~~ +[php] +protected function beforeSave() +{ + if(parent::beforeSave()) + { + if($this->isNewRecord) + $this->create_time=time(); + return true; + } + else + return false; +} +~~~ + +
$Id: comment.model.txt 1733 2010-01-21 16:54:29Z qiang.xue $
\ No newline at end of file diff --git a/docs/blog/zh_cn/final.deployment.txt b/docs/blog/zh_cn/final.deployment.txt index 4e3b84f24..8e7acb151 100644 --- a/docs/blog/zh_cn/final.deployment.txt +++ b/docs/blog/zh_cn/final.deployment.txt @@ -1,68 +1,68 @@ -最终调整与部署 -============================ - -我们的博客应用快要完成了。在部署之前,我们还想做一些调整。 - - -修改主页 ------------------- - -我们要把日志列表页修改为主页。我们将 [应用配置](http://www.yiiframework.com/doc/guide/basics.application#application-configuration) 修改如下, - -~~~ -[php] -return array( - ...... - 'defaultController'=>'post', - ...... -); -~~~ - -> Tip|提示: 由于 `PostController` 已经声明了 `index` 作为它的默认动作,当我们访问此应用的首页时,我们将看到由 post 控制器的 `index` 动作生成的结果页面。 - - -启用表结构缓存 ------------------------ - -由于 ActiveRecord 按数据表的元数据(metadata)测定列的信息。读取元数据并对其分析需要消耗时间。这在开发环境中应该问题不大,但对于一个在生产环境中运行的应用来说,数据表结构如果不发生变化那这就是在浪费时间。因此,我们应通过修改应用配置启用数据表结构缓存, - -~~~ -[php] -return array( - ...... - 'components'=>array( - ...... - 'cache'=>array( - 'class'=>'CDbCache', - ), - 'db'=>array( - 'class'=>'system.db.CDbConnection', - 'connectionString'=>'sqlite:/wwwroot/blog/protected/data/blog.db', - 'schemaCachingDuration'=>3600, - ), - ), -); -~~~ - -如上所示,我们首先添加了一个 `cache` 组件,它使用一个默认的 SQLite 数据库作为缓存平台。如果我们的服务器配备了其他的缓存扩展,例如 APC, 我们同样可以使用它们。我们还修改了 `db` 组件,设置它的 [schemaCachingDuration|CDbConnection::schemaCachingDuration] 属性为 3600,这样解析的数据表结构将可以在 3600 秒的缓存期内有效。 - - -禁用除错(Debug)模式 ------------------------- - -我们修改入口文件 `/wwwroot/blog/index.php` ,移除定义了 `YII_DEBUG` 常量的那一行。此常量在开发环境中非常有用,它使 Yii 在错误发生时显示更多的除错信息。然而,当应用运行于生产环境时,显示除错信息并不是一个好主意。因为它可能含有一些敏感信息,例如文件所在的位置,文件的内容等。 - - -部署应用 -------------------------- - -最终的部署主要是将 `/wwwroot/blog` 目录复制到目标目录。下面的检查列表列出了每一个所需的步骤: - - 1. 如果目标位置没有可用的 Yii,先将其安装好。 - 2. 复制整个 `/wwwroot/blog` 目录到目标位置; - 3. 修改入口文件 `index.php` ,把 `$yii` 变量指向新的Yii引导文件。 - 4. 修改文件 `protected/yiic.php` ,设置 `$yiic` 变量的值为新的 `yiic.php` 文件位置; - 5. 修改目录 `assets` 和 `protected/runtime` 的权限,确保Web服务器进程对它们有可写权。 - - +最终调整与部署 +============================ + +我们的博客应用快要完成了。在部署之前,我们还想做一些调整。 + + +修改主页 +------------------ + +我们要把日志列表页修改为主页。我们将 [应用配置](http://www.yiiframework.com/doc/guide/basics.application#application-configuration) 修改如下, + +~~~ +[php] +return array( + ...... + 'defaultController'=>'post', + ...... +); +~~~ + +> Tip|提示: 由于 `PostController` 已经声明了 `index` 作为它的默认动作,当我们访问此应用的首页时,我们将看到由 post 控制器的 `index` 动作生成的结果页面。 + + +启用表结构缓存 +----------------------- + +由于 ActiveRecord 按数据表的元数据(metadata)测定列的信息。读取元数据并对其分析需要消耗时间。这在开发环境中应该问题不大,但对于一个在生产环境中运行的应用来说,数据表结构如果不发生变化那这就是在浪费时间。因此,我们应通过修改应用配置启用数据表结构缓存, + +~~~ +[php] +return array( + ...... + 'components'=>array( + ...... + 'cache'=>array( + 'class'=>'CDbCache', + ), + 'db'=>array( + 'class'=>'system.db.CDbConnection', + 'connectionString'=>'sqlite:/wwwroot/blog/protected/data/blog.db', + 'schemaCachingDuration'=>3600, + ), + ), +); +~~~ + +如上所示,我们首先添加了一个 `cache` 组件,它使用一个默认的 SQLite 数据库作为缓存平台。如果我们的服务器配备了其他的缓存扩展,例如 APC, 我们同样可以使用它们。我们还修改了 `db` 组件,设置它的 [schemaCachingDuration|CDbConnection::schemaCachingDuration] 属性为 3600,这样解析的数据表结构将可以在 3600 秒的缓存期内有效。 + + +禁用除错(Debug)模式 +------------------------ + +我们修改入口文件 `/wwwroot/blog/index.php` ,移除定义了 `YII_DEBUG` 常量的那一行。此常量在开发环境中非常有用,它使 Yii 在错误发生时显示更多的除错信息。然而,当应用运行于生产环境时,显示除错信息并不是一个好主意。因为它可能含有一些敏感信息,例如文件所在的位置,文件的内容等。 + + +部署应用 +------------------------- + +最终的部署主要是将 `/wwwroot/blog` 目录复制到目标目录。下面的检查列表列出了每一个所需的步骤: + + 1. 如果目标位置没有可用的 Yii,先将其安装好。 + 2. 复制整个 `/wwwroot/blog` 目录到目标位置; + 3. 修改入口文件 `index.php` ,把 `$yii` 变量指向新的Yii引导文件。 + 4. 修改文件 `protected/yiic.php` ,设置 `$yiic` 变量的值为新的 `yiic.php` 文件位置; + 5. 修改目录 `assets` 和 `protected/runtime` 的权限,确保Web服务器进程对它们有可写权。 + +
$Id: final.deployment.txt 2017 2010-04-05 17:12:13Z alexander.makarow $
\ No newline at end of file diff --git a/docs/blog/zh_cn/final.future.txt b/docs/blog/zh_cn/final.future.txt index 95c548d5c..f7339e2b0 100644 --- a/docs/blog/zh_cn/final.future.txt +++ b/docs/blog/zh_cn/final.future.txt @@ -1,57 +1,57 @@ -今后的增强 -=================== - -使用主题 -------------- - -不需要写任何代码,我们的博客应用已经是 [可更换主题(themeable)](http://www.yiiframework.com/doc/guide/topics.theming) 的了。要使用主题,我们主要是需要通过编写个性化的视图文件开发主题。例如,要使用一个名为 `classic` 的使用不同布局的主题,我们需要创建一个布局视图文件 `/wwwroot/blog/themes/classic/views/layouts/main.php`。我们还需要修改应用配置以显示我们选择的 `classic` 主题。 - -~~~ -[php] -return array( - ...... - 'theme'=>'classic', - ...... -); -~~~ - - -国际化 --------------------- - -我们也可以把我们的博客应用国际化,这样它就可以通过多种语言显示。这主要包括两方面的工作。 - -第一,我们创建不同语言的视图文件。例如,针对 `PostController` 的 `index` 页面,我们创建了视图文件 `/wwwroot/blog/protected/views/post/zh_cn/index.php`。当应用的语言被配置为简体中文(语言代码是 `zh_cn`)时,Yii 将自动使用此视图文件。 - -第二,我们可以为代码生成的信息创建信息翻译。信息翻译应保存在目录 `/wwwroot/blog/protected/messages` 中,我们也需要在使用文本字符串的地方调用 `Yii::t()` 方法把这些字符串括起来。 - -关于国际化的更多详情,请参考 [指南](http://www.yiiframework.com/doc/guide/topics.i18n)。 - - -通过缓存提高性能 --------------------------------- - -虽然 Yii 框架 [非常高效](http://www.yiiframework.com/performance/), 但 Yii 写的某个应用未必高效。在我们的博客应用中有基础可以提高性能的地方。例如,标签云 portlet 可能是性能瓶颈之一,因为它使用了较复杂的数据库查询和PHP逻辑。 - -我们可以使用 Yii 提供的成熟的 [缓存功能](http://www.yiiframework.com/doc/guide/caching.overview) 提高性能。Yii 中最有用的组件之一就是 [COutputCache], 它会缓存页面显示中的片段,这样生成此片段的代码就不需要在每次收到请求时执行。例如,在布局文件 `/wwwroot/blog/protected/views/layouts/column2.php` 中,我们可以将标签云 portlet 嵌入到 [COutputCache] 中: - -~~~ -[php] -beginCache('tagCloud', array('duration'=>3600))) { ?> - - widget('TagCloud', array( - 'maxTags'=>Yii::app()->params['tagCloudCount'], - )); ?> - -endCache(); } ?> -~~~ - -通过以上代码,标签云的显示将由缓存实现,而不需要在每次收到请求时实时生成。缓存内容将在 3600 秒的缓存期内有效。 - - -添加新功能 -------------------- - -我们的博客应用现在只有非常基本的功能。要成为一个完整的博客系统,还需要添加更多的功能。例如,日历 portlet,邮件提醒,日志分类,存档日志 portlet 等等。我们把这些功能的实现留给感兴趣的读者。 - +今后的增强 +=================== + +使用主题 +------------- + +不需要写任何代码,我们的博客应用已经是 [可更换主题(themeable)](http://www.yiiframework.com/doc/guide/topics.theming) 的了。要使用主题,我们主要是需要通过编写个性化的视图文件开发主题。例如,要使用一个名为 `classic` 的使用不同布局的主题,我们需要创建一个布局视图文件 `/wwwroot/blog/themes/classic/views/layouts/main.php`。我们还需要修改应用配置以显示我们选择的 `classic` 主题。 + +~~~ +[php] +return array( + ...... + 'theme'=>'classic', + ...... +); +~~~ + + +国际化 +-------------------- + +我们也可以把我们的博客应用国际化,这样它就可以通过多种语言显示。这主要包括两方面的工作。 + +第一,我们创建不同语言的视图文件。例如,针对 `PostController` 的 `index` 页面,我们创建了视图文件 `/wwwroot/blog/protected/views/post/zh_cn/index.php`。当应用的语言被配置为简体中文(语言代码是 `zh_cn`)时,Yii 将自动使用此视图文件。 + +第二,我们可以为代码生成的信息创建信息翻译。信息翻译应保存在目录 `/wwwroot/blog/protected/messages` 中,我们也需要在使用文本字符串的地方调用 `Yii::t()` 方法把这些字符串括起来。 + +关于国际化的更多详情,请参考 [指南](http://www.yiiframework.com/doc/guide/topics.i18n)。 + + +通过缓存提高性能 +-------------------------------- + +虽然 Yii 框架 [非常高效](http://www.yiiframework.com/performance/), 但 Yii 写的某个应用未必高效。在我们的博客应用中有基础可以提高性能的地方。例如,标签云 portlet 可能是性能瓶颈之一,因为它使用了较复杂的数据库查询和PHP逻辑。 + +我们可以使用 Yii 提供的成熟的 [缓存功能](http://www.yiiframework.com/doc/guide/caching.overview) 提高性能。Yii 中最有用的组件之一就是 [COutputCache], 它会缓存页面显示中的片段,这样生成此片段的代码就不需要在每次收到请求时执行。例如,在布局文件 `/wwwroot/blog/protected/views/layouts/column2.php` 中,我们可以将标签云 portlet 嵌入到 [COutputCache] 中: + +~~~ +[php] +beginCache('tagCloud', array('duration'=>3600))) { ?> + + widget('TagCloud', array( + 'maxTags'=>Yii::app()->params['tagCloudCount'], + )); ?> + +endCache(); } ?> +~~~ + +通过以上代码,标签云的显示将由缓存实现,而不需要在每次收到请求时实时生成。缓存内容将在 3600 秒的缓存期内有效。 + + +添加新功能 +------------------- + +我们的博客应用现在只有非常基本的功能。要成为一个完整的博客系统,还需要添加更多的功能。例如,日历 portlet,邮件提醒,日志分类,存档日志 portlet 等等。我们把这些功能的实现留给感兴趣的读者。 +
$Id: final.future.txt 2017 2010-04-05 17:12:13Z alexander.makarow $
\ No newline at end of file diff --git a/docs/blog/zh_cn/final.logging.txt b/docs/blog/zh_cn/final.logging.txt index ffb492bc0..7158fd454 100644 --- a/docs/blog/zh_cn/final.logging.txt +++ b/docs/blog/zh_cn/final.logging.txt @@ -1,35 +1,35 @@ -错误日志 -============== - -生产环境中的 Web 应用常需要具有完善的事件日志功能。在我们的博客应用中,我们想记录它在使用时发生的错误。这些错误可能是程序错误或者是用户对系统的不当使用导致的错误。记录这些错误可以帮助我们完善此博客应用。 - -为启用错误日志功能,我们修改 [应用配置](http://www.yiiframework.com/doc/guide/basics.application#application-configuration) 如下, - -~~~ -[php] -return array( - 'preload'=>array('log'), - - ...... - - 'components'=>array( - 'log'=>array( - 'class'=>'CLogRouter', - 'routes'=>array( - array( - 'class'=>'CFileLogRoute', - 'levels'=>'error, warning', - ), - ), - ), - ...... - ), -); -~~~ - -通过上述配置,如果有错误(error)或警告(warning)发生,其详细信息将被记录并保存到位于 `/wwwroot/blog/protected/runtime` 目录的文件中。 - -`log` 组件还提供了更多的高级功能,例如将日志信息发送到一个 Email 列表,在 JavaScript 控制台窗口中显示日志信息等。更多详情,请参考[指南](http://www.yiiframework.com/doc/guide/topics.logging)。 - - +错误日志 +============== + +生产环境中的 Web 应用常需要具有完善的事件日志功能。在我们的博客应用中,我们想记录它在使用时发生的错误。这些错误可能是程序错误或者是用户对系统的不当使用导致的错误。记录这些错误可以帮助我们完善此博客应用。 + +为启用错误日志功能,我们修改 [应用配置](http://www.yiiframework.com/doc/guide/basics.application#application-configuration) 如下, + +~~~ +[php] +return array( + 'preload'=>array('log'), + + ...... + + 'components'=>array( + 'log'=>array( + 'class'=>'CLogRouter', + 'routes'=>array( + array( + 'class'=>'CFileLogRoute', + 'levels'=>'error, warning', + ), + ), + ), + ...... + ), +); +~~~ + +通过上述配置,如果有错误(error)或警告(warning)发生,其详细信息将被记录并保存到位于 `/wwwroot/blog/protected/runtime` 目录的文件中。 + +`log` 组件还提供了更多的高级功能,例如将日志信息发送到一个 Email 列表,在 JavaScript 控制台窗口中显示日志信息等。更多详情,请参考[指南](http://www.yiiframework.com/doc/guide/topics.logging)。 + +
$Id: final.logging.txt 878 2009-03-23 15:31:21Z qiang.xue $
\ No newline at end of file diff --git a/docs/blog/zh_cn/final.url.txt b/docs/blog/zh_cn/final.url.txt index 330521c07..e0475dfe9 100644 --- a/docs/blog/zh_cn/final.url.txt +++ b/docs/blog/zh_cn/final.url.txt @@ -1,45 +1,45 @@ -美化 URL -================ - -链接着我们博客应用不同页面的 URL 看起来很丑。例如展示日志内容的页面,其 URL 如下: - -~~~ -/index.php?r=post/show&id=1&title=A+Test+Post -~~~ - -此节中,我们将讲解如何美化这些 URL 并使它们对 SEO 友好。我们的目标是在应用中可以使用如下样式的 URL: - - 1. `/index.php/posts/yii`: 指向属于标签 `yii` 的日志列表页; - 2. `/index.php/post/2/A+Test+Post`: 指向 ID 为 2,标题为 `A Test Post` 的日志的日志详情页; - 3. `/index.php/post/update?id=1`: 指向 ID 为 1 的日志更新页。 - -注意在第二个URL格式中,我们在URL中还包含了日志标题。这主要是为了使其对 SEO 友好。据说搜索引擎会在索引URL时重视其中的单词。 - -要实现我们的目标,我们修改 [应用配置](http://www.yiiframework.com/doc/guide/basics.application#application-configuration) 如下, - -~~~ -[php] -return array( - ...... - 'components'=>array( - ...... - 'urlManager'=>array( - 'urlFormat'=>'path', - 'rules'=>array( - 'post//'=>'post/view', - 'posts/'=>'post/index', - '/'=>'/', - ), - ), - ), -); -~~~ - -如上所示,我们配置了 [urlManager](http://www.yiiframework.com/doc/guide/topics.url) 组件,设置其 `urlFormat` 属性为 `path` 并添加了一系列 `rules` (规则)。 - -`urlManager` 通过这些规则解析并创建目标格式的URL。例如,第二条规则指明:如果一个 URL `/index.php/posts/yii` 被请求, `urlManager` 组件就应负责调度此请求到 [路由(route)](http://www.yiiframework.com/doc/guide/basics.controller#route) `post/index` 并创建一个值为 `yii` 的 GET 参数 `tag` 。从另一个角度来说,当使用路由 `post/index` 和 `tag` 参数生成URL时,`urlManager` 组件将同样使用此规则生成目标 URL `/index.php/posts/yii`。鉴于此,我们说 `urlManager` 是一个双向的 URL 管理器。 - -`urlManager` 组件还可以继续美化我们的URL,例如从URL中隐藏 `index.php` ,在URL的结尾添加 `.html` 等。我们可以通过在应用配置中设置 `urlManager` 的各种属性实现这些功能。更多详情,请参考 [指南](http://www.yiiframework.com/doc/guide/topics.url). - - +美化 URL +================ + +链接着我们博客应用不同页面的 URL 看起来很丑。例如展示日志内容的页面,其 URL 如下: + +~~~ +/index.php?r=post/show&id=1&title=A+Test+Post +~~~ + +此节中,我们将讲解如何美化这些 URL 并使它们对 SEO 友好。我们的目标是在应用中可以使用如下样式的 URL: + + 1. `/index.php/posts/yii`: 指向属于标签 `yii` 的日志列表页; + 2. `/index.php/post/2/A+Test+Post`: 指向 ID 为 2,标题为 `A Test Post` 的日志的日志详情页; + 3. `/index.php/post/update?id=1`: 指向 ID 为 1 的日志更新页。 + +注意在第二个URL格式中,我们在URL中还包含了日志标题。这主要是为了使其对 SEO 友好。据说搜索引擎会在索引URL时重视其中的单词。 + +要实现我们的目标,我们修改 [应用配置](http://www.yiiframework.com/doc/guide/basics.application#application-configuration) 如下, + +~~~ +[php] +return array( + ...... + 'components'=>array( + ...... + 'urlManager'=>array( + 'urlFormat'=>'path', + 'rules'=>array( + 'post//'=>'post/view', + 'posts/'=>'post/index', + '/'=>'/', + ), + ), + ), +); +~~~ + +如上所示,我们配置了 [urlManager](http://www.yiiframework.com/doc/guide/topics.url) 组件,设置其 `urlFormat` 属性为 `path` 并添加了一系列 `rules` (规则)。 + +`urlManager` 通过这些规则解析并创建目标格式的URL。例如,第二条规则指明:如果一个 URL `/index.php/posts/yii` 被请求, `urlManager` 组件就应负责调度此请求到 [路由(route)](http://www.yiiframework.com/doc/guide/basics.controller#route) `post/index` 并创建一个值为 `yii` 的 GET 参数 `tag` 。从另一个角度来说,当使用路由 `post/index` 和 `tag` 参数生成URL时,`urlManager` 组件将同样使用此规则生成目标 URL `/index.php/posts/yii`。鉴于此,我们说 `urlManager` 是一个双向的 URL 管理器。 + +`urlManager` 组件还可以继续美化我们的URL,例如从URL中隐藏 `index.php` ,在URL的结尾添加 `.html` 等。我们可以通过在应用配置中设置 `urlManager` 的各种属性实现这些功能。更多详情,请参考 [指南](http://www.yiiframework.com/doc/guide/topics.url). + +
$Id: final.url.txt 2240 2010-07-03 18:06:11Z alexander.makarow $
\ No newline at end of file diff --git a/docs/blog/zh_cn/portlet.comments.txt b/docs/blog/zh_cn/portlet.comments.txt index 72ff95e23..81b64174b 100644 --- a/docs/blog/zh_cn/portlet.comments.txt +++ b/docs/blog/zh_cn/portlet.comments.txt @@ -1,82 +1,82 @@ -创建最新评论 Portlet -================================ - -此节中,我们创建最后一个 portlet ,它将显示最新发布的评论列表。 - - -创建 `RecentComments` 类 -------------------------------- - -我们将 `RecentComments`类创建在文件 `/wwwroot/blog/protected/components/RecentComments.php` 中。此文件内容如下: - -~~~ -[php] -Yii::import('zii.widgets.CPortlet'); - -class RecentComments extends CPortlet -{ - public $title='Recent Comments'; - public $maxComments=10; - - public function getRecentComments() - { - return Comment::model()->findRecentComments($this->maxComments); - } - - protected function renderContent() - { - $this->render('recentComments'); - } -} -~~~ - -如上所示,我们调用了 `Comment` 类中定义的 `findRecentComments` 方法。此方法代码如下: - -~~~ -[php] -class Comment extends CActiveRecord -{ - ...... - public function findRecentComments($limit=10) - { - return $this->with('post')->findAll(array( - 'condition'=>'t.status='.self::STATUS_APPROVED, - 'order'=>'t.create_time DESC', - 'limit'=>$limit, - )); - } -} -~~~ - - -创建 `recentComments` 视图 -------------------------- - -`recentComments` 视图存储在文件 `/wwwroot/blog/protected/components/views/recentComments.php` 中。它只是简单的显示由 `RecentComments::getRecentComments()` 方法返回的每一条评论。 - - -使用 `RecentComments` Portlet ------------------------------- - -我们修改布局文件 `/wwwroot/blog/protected/views/layouts/column2.php` 插入最后这个 portlet, - -~~~ -[php] -...... - -...... -~~~ - +创建最新评论 Portlet +================================ + +此节中,我们创建最后一个 portlet ,它将显示最新发布的评论列表。 + + +创建 `RecentComments` 类 +------------------------------- + +我们将 `RecentComments`类创建在文件 `/wwwroot/blog/protected/components/RecentComments.php` 中。此文件内容如下: + +~~~ +[php] +Yii::import('zii.widgets.CPortlet'); + +class RecentComments extends CPortlet +{ + public $title='Recent Comments'; + public $maxComments=10; + + public function getRecentComments() + { + return Comment::model()->findRecentComments($this->maxComments); + } + + protected function renderContent() + { + $this->render('recentComments'); + } +} +~~~ + +如上所示,我们调用了 `Comment` 类中定义的 `findRecentComments` 方法。此方法代码如下: + +~~~ +[php] +class Comment extends CActiveRecord +{ + ...... + public function findRecentComments($limit=10) + { + return $this->with('post')->findAll(array( + 'condition'=>'t.status='.self::STATUS_APPROVED, + 'order'=>'t.create_time DESC', + 'limit'=>$limit, + )); + } +} +~~~ + + +创建 `recentComments` 视图 +------------------------- + +`recentComments` 视图存储在文件 `/wwwroot/blog/protected/components/views/recentComments.php` 中。它只是简单的显示由 `RecentComments::getRecentComments()` 方法返回的每一条评论。 + + +使用 `RecentComments` Portlet +------------------------------ + +我们修改布局文件 `/wwwroot/blog/protected/views/layouts/column2.php` 插入最后这个 portlet, + +~~~ +[php] +...... + +...... +~~~ +
$Id: portlet.comments.txt 1773 2010-02-01 18:39:49Z qiang.xue $
\ No newline at end of file diff --git a/docs/blog/zh_cn/portlet.menu.txt b/docs/blog/zh_cn/portlet.menu.txt index e6db2952e..a827a50c6 100644 --- a/docs/blog/zh_cn/portlet.menu.txt +++ b/docs/blog/zh_cn/portlet.menu.txt @@ -1,94 +1,94 @@ -创建用户菜单 Portlet -========================== - -基于需求分析,我们需要三个不同的 portlet (译者注:如果一开始不理解什么是 portlet 没关系,继续往下看就知道了。):“用户菜单” portlet,“标签云” portlet 和“最新评论” portlet 。我们将通过继承Yii提供的 [CPortlet] 小物件开发这三个 portlet。 - -在这一节中,我们将开发第一个具体的 portlet ——用户菜单 portlet,它显示一个只对已通过身份验证的用户可见的菜单。此菜单包含四个项目: - - * 评论审核: 一个指向待审核评论列表的超级链接; - * 创建新日志: 一个指向日志创建页的超级链接; - * 管理日志: 一个指向日志管理页的超级链接; - * 注销: 一个可用于注销当前用户的链接按钮。 - - -创建 `UserMenu` 类 -------------------------- - -我们创建一个用于表现用户菜单 portlet 逻辑的 `UserMenu` 类。此类保存在文件 `/wwwroot/blog/protected/components/UserMenu.php` 中,其代码如下: - -~~~ -[php] -Yii::import('zii.widgets.CPortlet'); - -class UserMenu extends CPortlet -{ - public function init() - { - $this->title=CHtml::encode(Yii::app()->user->name); - parent::init(); - } - - protected function renderContent() - { - $this->render('userMenu'); - } -} -~~~ - -`UserMenu` 类继承自 `zii` 库中的 `CPortlet` 类。它覆盖了 `CPortlet` 类的 `init()` 和 `renderContent()` 方法。前者设置 portlet 的标题为当前用户的名字;后者通过渲染一个名为 `userMenu` 的视图生成 portlet 的主体内容。 - -> Tip|提示: 注意,我们必须在首次使用之前通过调用 `Yii::import()` 显式包含 `CPortlet` 类。这是因为 `CPortlet` 是 `zii` 工程的一部分。`zii` 工程是 Yii 的官方扩展库。出于性能的考虑,此工程中的类并未列入核心类。因此,我们必须在首次使用之前将其导入(import)。 - - -创建 `userMenu` 视图 ------------------------- - -然后,我们创建 `userMenu` 视图,它保存在 `/wwwroot/blog/protected/components/views/userMenu.php`: - -~~~ -[php] -
    -
  • -
  • -
  • pendingCommentCount . ')'; ?>
  • -
  • -
-~~~ - -> Info|信息: 默认情况下,小物件的视图文件应保存在包含小物件类文件的目录的 `views` 子目录中。文件名必须和视图名称相同。 - - -使用 `UserMenu` Portlet ------------------------- - -是可以把我们新完成的 `UserMenu` portlet 投入使用的时候了。我们把布局文件 `/wwwroot/blog/protected/views/layouts/column2.php` 修改如下: - -~~~ -[php] -...... - -...... -~~~ - -如上所示,我们调用了 `widget()` 方法创建并执行了 `UserMenu` 类的实例。由于此 portlet 只应显示给已通过身份验证的用户,我们只在当前用户的 `isGuest` 属性为 false 时(即用户未登录时)调用 `widget()` 方法。 - - -测试 `UserMenu` Portlet --------------------------- - -让我们来测试一下所作的工作: - - 1. 打开浏览器输入 URL `http://www.example.com/blog/index.php` 。 核实页面中的侧边栏中没有任何东西显示。 - 2. 点击 `Login` 超链接,填写登录表单登录,如果登录成功,核实 `UserMenu` portlet 显示在了侧边栏中,且其标题为当前用户名。 - 3. 点击 `UserMenu` portlet 中的 'Logout' ,核实注销成功且 `UserMenu` portlet 已消失。 - - -总结 -------- - -我们创建的是一个 portlet, 它是高度可复用的。我们可以稍加修改或不作修改就能很容易地把它用在另一个不同的工程中。此外,此 portlet 的设计完美重现了表现与逻辑分离的思想。虽然我们在前面的部分中没有提到这一点,但此实践在一个典型的 Yii 应用中几乎随处可见。 - +创建用户菜单 Portlet +========================== + +基于需求分析,我们需要三个不同的 portlet (译者注:如果一开始不理解什么是 portlet 没关系,继续往下看就知道了。):“用户菜单” portlet,“标签云” portlet 和“最新评论” portlet 。我们将通过继承Yii提供的 [CPortlet] 小物件开发这三个 portlet。 + +在这一节中,我们将开发第一个具体的 portlet ——用户菜单 portlet,它显示一个只对已通过身份验证的用户可见的菜单。此菜单包含四个项目: + + * 评论审核: 一个指向待审核评论列表的超级链接; + * 创建新日志: 一个指向日志创建页的超级链接; + * 管理日志: 一个指向日志管理页的超级链接; + * 注销: 一个可用于注销当前用户的链接按钮。 + + +创建 `UserMenu` 类 +------------------------- + +我们创建一个用于表现用户菜单 portlet 逻辑的 `UserMenu` 类。此类保存在文件 `/wwwroot/blog/protected/components/UserMenu.php` 中,其代码如下: + +~~~ +[php] +Yii::import('zii.widgets.CPortlet'); + +class UserMenu extends CPortlet +{ + public function init() + { + $this->title=CHtml::encode(Yii::app()->user->name); + parent::init(); + } + + protected function renderContent() + { + $this->render('userMenu'); + } +} +~~~ + +`UserMenu` 类继承自 `zii` 库中的 `CPortlet` 类。它覆盖了 `CPortlet` 类的 `init()` 和 `renderContent()` 方法。前者设置 portlet 的标题为当前用户的名字;后者通过渲染一个名为 `userMenu` 的视图生成 portlet 的主体内容。 + +> Tip|提示: 注意,我们必须在首次使用之前通过调用 `Yii::import()` 显式包含 `CPortlet` 类。这是因为 `CPortlet` 是 `zii` 工程的一部分。`zii` 工程是 Yii 的官方扩展库。出于性能的考虑,此工程中的类并未列入核心类。因此,我们必须在首次使用之前将其导入(import)。 + + +创建 `userMenu` 视图 +------------------------ + +然后,我们创建 `userMenu` 视图,它保存在 `/wwwroot/blog/protected/components/views/userMenu.php`: + +~~~ +[php] +
    +
  • +
  • +
  • pendingCommentCount . ')'; ?>
  • +
  • +
+~~~ + +> Info|信息: 默认情况下,小物件的视图文件应保存在包含小物件类文件的目录的 `views` 子目录中。文件名必须和视图名称相同。 + + +使用 `UserMenu` Portlet +------------------------ + +是可以把我们新完成的 `UserMenu` portlet 投入使用的时候了。我们把布局文件 `/wwwroot/blog/protected/views/layouts/column2.php` 修改如下: + +~~~ +[php] +...... + +...... +~~~ + +如上所示,我们调用了 `widget()` 方法创建并执行了 `UserMenu` 类的实例。由于此 portlet 只应显示给已通过身份验证的用户,我们只在当前用户的 `isGuest` 属性为 false 时(即用户未登录时)调用 `widget()` 方法。 + + +测试 `UserMenu` Portlet +-------------------------- + +让我们来测试一下所作的工作: + + 1. 打开浏览器输入 URL `http://www.example.com/blog/index.php` 。 核实页面中的侧边栏中没有任何东西显示。 + 2. 点击 `Login` 超链接,填写登录表单登录,如果登录成功,核实 `UserMenu` portlet 显示在了侧边栏中,且其标题为当前用户名。 + 3. 点击 `UserMenu` portlet 中的 'Logout' ,核实注销成功且 `UserMenu` portlet 已消失。 + + +总结 +------- + +我们创建的是一个 portlet, 它是高度可复用的。我们可以稍加修改或不作修改就能很容易地把它用在另一个不同的工程中。此外,此 portlet 的设计完美重现了表现与逻辑分离的思想。虽然我们在前面的部分中没有提到这一点,但此实践在一个典型的 Yii 应用中几乎随处可见。 +
$Id: portlet.menu.txt 1739 2010-01-22 15:20:03Z qiang.xue $
\ No newline at end of file diff --git a/docs/blog/zh_cn/portlet.tags.txt b/docs/blog/zh_cn/portlet.tags.txt index 8711c90f7..6bf15b2e8 100644 --- a/docs/blog/zh_cn/portlet.tags.txt +++ b/docs/blog/zh_cn/portlet.tags.txt @@ -1,62 +1,62 @@ -创建标签云 Portlet -========================== - -[标签云](http://zh.wikipedia.org/zh-cn/%E6%A0%87%E7%AD%BE%E4%BA%91) 显示一个日志标签列表,每个标签都可以通过可视化的方式反映其使用频度。 - - -创建 `TagCloud` 类 -------------------------- - -我们在 `/wwwroot/blog/protected/components/TagCloud.php` 文件中创建 `TagCloud` 类。此文件内容如下: - -~~~ -[php] -Yii::import('zii.widgets.CPortlet'); - -class TagCloud extends CPortlet -{ - public $title='Tags'; - public $maxTags=20; - - protected function renderContent() - { - $tags=Tag::model()->findTagWeights($this->maxTags); - - foreach($tags as $tag=>$weight) - { - $link=CHtml::link(CHtml::encode($tag), array('post/index','tag'=>$tag)); - echo CHtml::tag('span', array( - 'class'=>'tag', - 'style'=>"font-size:{$weight}pt", - ), $link)."\n"; - } - } -} -~~~ - -与 `UserMenu` portlet 不同, `TagCloud` portlet 不使用视图。它的前端表现是在 `renderContent()` 方法中完成的。这是因为其前端表现并不含有很多HTML标签。 - -我们把每个标签显示为指向带有此标签参数的日志索引页的链接。每个标签链接的文字大小是通过他们与其他标签的相对比重确定的。如果一个标签比其他标签有更高的使用频度,则它会以更大的字体显示。 - - -使用 `TagCloud` Portlet -------------------------- - -`TagCloud` portlet 的使用非常简单。我们把布局文件 `/wwwroot/blog/protected/views/layouts/column2.php` 修改如下: - -~~~ -[php] -...... - -...... -~~~ - +创建标签云 Portlet +========================== + +[标签云](http://zh.wikipedia.org/zh-cn/%E6%A0%87%E7%AD%BE%E4%BA%91) 显示一个日志标签列表,每个标签都可以通过可视化的方式反映其使用频度。 + + +创建 `TagCloud` 类 +------------------------- + +我们在 `/wwwroot/blog/protected/components/TagCloud.php` 文件中创建 `TagCloud` 类。此文件内容如下: + +~~~ +[php] +Yii::import('zii.widgets.CPortlet'); + +class TagCloud extends CPortlet +{ + public $title='Tags'; + public $maxTags=20; + + protected function renderContent() + { + $tags=Tag::model()->findTagWeights($this->maxTags); + + foreach($tags as $tag=>$weight) + { + $link=CHtml::link(CHtml::encode($tag), array('post/index','tag'=>$tag)); + echo CHtml::tag('span', array( + 'class'=>'tag', + 'style'=>"font-size:{$weight}pt", + ), $link)."\n"; + } + } +} +~~~ + +与 `UserMenu` portlet 不同, `TagCloud` portlet 不使用视图。它的前端表现是在 `renderContent()` 方法中完成的。这是因为其前端表现并不含有很多HTML标签。 + +我们把每个标签显示为指向带有此标签参数的日志索引页的链接。每个标签链接的文字大小是通过他们与其他标签的相对比重确定的。如果一个标签比其他标签有更高的使用频度,则它会以更大的字体显示。 + + +使用 `TagCloud` Portlet +------------------------- + +`TagCloud` portlet 的使用非常简单。我们把布局文件 `/wwwroot/blog/protected/views/layouts/column2.php` 修改如下: + +~~~ +[php] +...... + +...... +~~~ +
$Id: portlet.tags.txt 1772 2010-02-01 18:18:09Z qiang.xue $
\ No newline at end of file diff --git a/docs/blog/zh_cn/post.admin.txt b/docs/blog/zh_cn/post.admin.txt index 1face70d6..1d683f0cf 100644 --- a/docs/blog/zh_cn/post.admin.txt +++ b/docs/blog/zh_cn/post.admin.txt @@ -1,109 +1,109 @@ -日志管理 -============== - -日志管理主要是在一个管理视图中列出日志,我们可以查看所有状态的日志,更新或删除它们。它们分别通过 `admin` 操作和 `delete` 操作实现。`yiic` 生成的代码并不需要太多修改。下面我们主要解释这两个操作是怎样实现的。 - - -在表格视图中列出日志 ------------------------------ - -`admin` 操作在一个表格视图中列出了所有状态的日志。此视图支持排序和分页。下面就是 `PostController` 中的 `actionAdmin()` 方法: - -~~~ -[php] -public function actionAdmin() -{ - $model=new Post('search'); - if(isset($_GET['Post'])) - $model->attributes=$_GET['Post']; - $this->render('admin',array( - 'model'=>$model, - )); -} -~~~ - -上面的代码由 `yiic` 工具生成,且未作任何修改。它首先创建了一个 `search` [场景(scenario)](/doc/guide/form.model) 下的 `Post` 模型。我们将使用此模型收集用户指定的搜索条件。然后我们把用户可能会提供的数据赋值给模型。 最后,我们以此模型显示 `admin` 视图。 - -下面就是 `admin` 视图的代码: - -~~~ -[php] -breadcrumbs=array( - 'Manage Posts', -); -?> -

Manage Posts

- -widget('zii.widgets.grid.CGridView', array( - 'dataProvider'=>$model->search(), - 'filter'=>$model, - 'columns'=>array( - array( - 'name'=>'title', - 'type'=>'raw', - 'value'=>'CHtml::link(CHtml::encode($data->title), $data->url)' - ), - array( - 'name'=>'status', - 'value'=>'Lookup::item("PostStatus",$data->status)', - 'filter'=>Lookup::items('PostStatus'), - ), - array( - 'name'=>'create_time', - 'type'=>'datetime', - 'filter'=>false, - ), - array( - 'class'=>'CButtonColumn', - ), - ), -)); ?> -~~~ - -我们使用 [CGridView] 来显示这些日志。它允许我们在单页显示过多时可以分页并可以按某一列排序。我们的修改主要针对每一列的显示。例如,针对 `title` 列,我们指定它应该显示为一个超级链接,指向日志的详情页面。表达式`$data->url` 返回我们之前在 `Post` 类中定义的 `url` 属性值。 - -> Tip|提示: 当显示文本时,我们要调用 [CHtml::encode()] 对其中的HTML编码。这可以防止 [跨站脚本攻击(cross-site scripting attack)](http://www.yiiframework.com/doc/guide/topics.security). - - -日志删除 --------------- - -在 `admin` 数据表格中,每行有一个删除按钮。点击此按钮将会删除相应的日志。在程序内部,这会触发如下实现的 `delete` 动作。 - -~~~ -[php] -public function actionDelete() -{ - if(Yii::app()->request->isPostRequest) - { - // we only allow deletion via POST request - $this->loadModel()->delete(); - - if(!isset($_POST['ajax'])) - $this->redirect(array('index')); - } - else - throw new CHttpException(400,'Invalid request. Please do not repeat this request again.'); -} -~~~ - -上面的代码就是 `yiic` 生成的代码,未经任何修改。我们想在此对判断 `$_POST['ajax']` 稍作解释。[CGridView] 小物件有一个非常好的特性:它的排序、分页和删除操作默认是通过AJAX实现的。这就意味着在执行上述操作时,整个页面不会重新加载。然而,它也可以在非AJAX模式下运行(通过设置它的 `ajaxUpdate` 属性为 false 或在客户端禁用JavaScript)。`delete` 动作区分两个场景是必要的:如果删除请求通过AJAX提交,我们就不应该重定向用户的浏览器,反之则应该重定向。 - -删除日志应该同时导致日志的所有评论被删除。额外的,我们应更新相关的删除日志后的 `tbl_tag` 表。 这两个任务都可以通过在 `Post` 模型类中写一个如下的 `afterDelete` 方法实现。 - -~~~ -[php] -protected function afterDelete() -{ - parent::afterDelete(); - Comment::model()->deleteAll('post_id='.$this->id); - Tag::model()->updateFrequency($this->tags, ''); -} -~~~ - -上面的代码很直观:它首先删除了所有 `post_id` 和所删除的日志ID相同的那些评论。然后它针对所删日志中的 `tags` 更新了 `tbl_tag` 表。 - -> Tip|提示: 由于 SQLite 并不真正支持外键约束,我们需要显式地删除属于所删日志的所有评论。在一个支持此约束的DBMS (例如 MySQL, PostgreSQL)中,可以设置好外键约束,这样如果删除了一篇日志,DBMS就可以自动删除其评论。这样的话,我们就不需要在我们的代码中显式执行删除了。 - +日志管理 +============== + +日志管理主要是在一个管理视图中列出日志,我们可以查看所有状态的日志,更新或删除它们。它们分别通过 `admin` 操作和 `delete` 操作实现。`yiic` 生成的代码并不需要太多修改。下面我们主要解释这两个操作是怎样实现的。 + + +在表格视图中列出日志 +----------------------------- + +`admin` 操作在一个表格视图中列出了所有状态的日志。此视图支持排序和分页。下面就是 `PostController` 中的 `actionAdmin()` 方法: + +~~~ +[php] +public function actionAdmin() +{ + $model=new Post('search'); + if(isset($_GET['Post'])) + $model->attributes=$_GET['Post']; + $this->render('admin',array( + 'model'=>$model, + )); +} +~~~ + +上面的代码由 `yiic` 工具生成,且未作任何修改。它首先创建了一个 `search` [场景(scenario)](/doc/guide/form.model) 下的 `Post` 模型。我们将使用此模型收集用户指定的搜索条件。然后我们把用户可能会提供的数据赋值给模型。 最后,我们以此模型显示 `admin` 视图。 + +下面就是 `admin` 视图的代码: + +~~~ +[php] +breadcrumbs=array( + 'Manage Posts', +); +?> +

Manage Posts

+ +widget('zii.widgets.grid.CGridView', array( + 'dataProvider'=>$model->search(), + 'filter'=>$model, + 'columns'=>array( + array( + 'name'=>'title', + 'type'=>'raw', + 'value'=>'CHtml::link(CHtml::encode($data->title), $data->url)' + ), + array( + 'name'=>'status', + 'value'=>'Lookup::item("PostStatus",$data->status)', + 'filter'=>Lookup::items('PostStatus'), + ), + array( + 'name'=>'create_time', + 'type'=>'datetime', + 'filter'=>false, + ), + array( + 'class'=>'CButtonColumn', + ), + ), +)); ?> +~~~ + +我们使用 [CGridView] 来显示这些日志。它允许我们在单页显示过多时可以分页并可以按某一列排序。我们的修改主要针对每一列的显示。例如,针对 `title` 列,我们指定它应该显示为一个超级链接,指向日志的详情页面。表达式`$data->url` 返回我们之前在 `Post` 类中定义的 `url` 属性值。 + +> Tip|提示: 当显示文本时,我们要调用 [CHtml::encode()] 对其中的HTML编码。这可以防止 [跨站脚本攻击(cross-site scripting attack)](http://www.yiiframework.com/doc/guide/topics.security). + + +日志删除 +-------------- + +在 `admin` 数据表格中,每行有一个删除按钮。点击此按钮将会删除相应的日志。在程序内部,这会触发如下实现的 `delete` 动作。 + +~~~ +[php] +public function actionDelete() +{ + if(Yii::app()->request->isPostRequest) + { + // we only allow deletion via POST request + $this->loadModel()->delete(); + + if(!isset($_POST['ajax'])) + $this->redirect(array('index')); + } + else + throw new CHttpException(400,'Invalid request. Please do not repeat this request again.'); +} +~~~ + +上面的代码就是 `yiic` 生成的代码,未经任何修改。我们想在此对判断 `$_POST['ajax']` 稍作解释。[CGridView] 小物件有一个非常好的特性:它的排序、分页和删除操作默认是通过AJAX实现的。这就意味着在执行上述操作时,整个页面不会重新加载。然而,它也可以在非AJAX模式下运行(通过设置它的 `ajaxUpdate` 属性为 false 或在客户端禁用JavaScript)。`delete` 动作区分两个场景是必要的:如果删除请求通过AJAX提交,我们就不应该重定向用户的浏览器,反之则应该重定向。 + +删除日志应该同时导致日志的所有评论被删除。额外的,我们应更新相关的删除日志后的 `tbl_tag` 表。 这两个任务都可以通过在 `Post` 模型类中写一个如下的 `afterDelete` 方法实现。 + +~~~ +[php] +protected function afterDelete() +{ + parent::afterDelete(); + Comment::model()->deleteAll('post_id='.$this->id); + Tag::model()->updateFrequency($this->tags, ''); +} +~~~ + +上面的代码很直观:它首先删除了所有 `post_id` 和所删除的日志ID相同的那些评论。然后它针对所删日志中的 `tags` 更新了 `tbl_tag` 表。 + +> Tip|提示: 由于 SQLite 并不真正支持外键约束,我们需要显式地删除属于所删日志的所有评论。在一个支持此约束的DBMS (例如 MySQL, PostgreSQL)中,可以设置好外键约束,这样如果删除了一篇日志,DBMS就可以自动删除其评论。这样的话,我们就不需要在我们的代码中显式执行删除了。 +
$Id: post.admin.txt 1810 2010-02-18 00:24:54Z qiang.xue $
\ No newline at end of file diff --git a/docs/blog/zh_cn/post.create.txt b/docs/blog/zh_cn/post.create.txt index 1ee9122a5..1cfda541e 100644 --- a/docs/blog/zh_cn/post.create.txt +++ b/docs/blog/zh_cn/post.create.txt @@ -1,96 +1,96 @@ -日志的创建与更新 -=========================== - -准备好了 `Post` 模型,我们现在需要调整控制器 `PostController` 的动作和视图了。在这一节里,我们首先自定义CRUD操作的访问权限控制;然后我们修改代码实现 `创建` 与 `更新` 操作。 - - -自定义访问控制 --------------------------- - -我们想做的第一件事是自定义 [访问控制(Access control)](http://www.yiiframework.com/doc/guide/topics.auth#access-control-filter) ,因为 `yiic` 工具生成的代码并不符合我们的需求。 - -我们将 `/wwwroot/blog/protected/controllers/PostController.php` 文件中的 `accessRules()` 方法修改如下: - -~~~ -[php] -public function accessRules() -{ - return array( - array('allow', // allow all users to perform 'list' and 'show' actions - 'actions'=>array('index', 'view'), - 'users'=>array('*'), - ), - array('allow', // allow authenticated users to perform any action - 'users'=>array('@'), - ), - array('deny', // deny all users - 'users'=>array('*'), - ), - ); -} -~~~ - -上面的规则说明:所有用户均可访问 `index` 和 `view` 动作,已通过身份验证的用户可以访问任意动作,包括 `admin` 动作。在其他场景中,应禁止用户访问。注意这些规则将会按它们在此列出的顺序计算。第一条匹配当前场景的规则将决定访问权。例如,如果当前用户是系统所有者,他想尝试访问日志创建页,第二条规则将匹配成功并授予此用户权限。 - - -自定义 `创建` 和 `更新` 操作 --------------------------------------------- - -`创建` 和 `更新` 操作非常相似。他们都需要显示一个HTML表单用于收集用户的输入的信息,然后对其进行验证,然后将其存入数据库。主要的不同是 `更新` 操作需要把从数据库找到的已存在的日志数据重现在表单中。鉴于此,`yiic` 工具创建了一个局部视图 `/wwwroot/blog/protected/views/post/_form.php` ,它会插入 `创建` 和 `更新` 视图来渲染所需的HTML表单。 - -我们先修改 `_form.php` 这个文件,使这个HTML表单只收集我们想要的输入:`title`, `content`, `tags` 和 `status`。我们使用文本域收集前三个属性的输入,还有一个下拉列表用来收集 `status` 的输入。此下拉列表的选项值就是可用的日志状态文本。 - -~~~ -[php] -dropDownList($model,'status',Lookup::items('PostStatus')); ?> -~~~ - -在上面的代码中,我们调用了 `Lookup::items('PostStatus')` 以带回日志状态列表。 - -然后我们修改 `Post` 类,使它可以在日志被存入数据库前自动设置几个属性 (例如 `create_time`, `author_id`)。我们覆盖 `beforeSave()` 方法如下: - -~~~ -[php] -protected function beforeSave() -{ - if(parent::beforeSave()) - { - if($this->isNewRecord) - { - $this->create_time=$this->update_time=time(); - $this->author_id=Yii::app()->user->id; - } - else - $this->update_time=time(); - return true; - } - else - return false; -} -~~~ - -当我们保存日志时,我们想更新 `tbl_tag` 表以反映 Tag 的使用频率。我们可以在 `afterSave()` 方法中完成此工作,它会在日志被成功存入数据库后自动被Yii调用。 - -~~~ -[php] -protected function afterSave() -{ - parent::afterSave(); - Tag::model()->updateFrequency($this->_oldTags, $this->tags); -} - -private $_oldTags; - -protected function afterFind() -{ - parent::afterFind(); - $this->_oldTags=$this->tags; -} -~~~ - -在这个实现中,因为我们想检测出用户在更新现有日志的时候是否修改了 Tag ,我们需要知道原来的 Tag 是什么, 鉴于此,我们还写了一个 `afterFind()` 方法把原有的 Tag 信息保存到变量 `_oldTags` 中。方法 `afterFind()` 会在一个 AR 记录被数据库中的数据填充时自动被 Yii 调用。 - -这里我们不再列出 `Tag::updateFrequency()` 方法的细节,读者可以参考 `/wwwroot/yii/demos/blog/protected/models/Tag.php` 文件。 - - +日志的创建与更新 +=========================== + +准备好了 `Post` 模型,我们现在需要调整控制器 `PostController` 的动作和视图了。在这一节里,我们首先自定义CRUD操作的访问权限控制;然后我们修改代码实现 `创建` 与 `更新` 操作。 + + +自定义访问控制 +-------------------------- + +我们想做的第一件事是自定义 [访问控制(Access control)](http://www.yiiframework.com/doc/guide/topics.auth#access-control-filter) ,因为 `yiic` 工具生成的代码并不符合我们的需求。 + +我们将 `/wwwroot/blog/protected/controllers/PostController.php` 文件中的 `accessRules()` 方法修改如下: + +~~~ +[php] +public function accessRules() +{ + return array( + array('allow', // allow all users to perform 'list' and 'show' actions + 'actions'=>array('index', 'view'), + 'users'=>array('*'), + ), + array('allow', // allow authenticated users to perform any action + 'users'=>array('@'), + ), + array('deny', // deny all users + 'users'=>array('*'), + ), + ); +} +~~~ + +上面的规则说明:所有用户均可访问 `index` 和 `view` 动作,已通过身份验证的用户可以访问任意动作,包括 `admin` 动作。在其他场景中,应禁止用户访问。注意这些规则将会按它们在此列出的顺序计算。第一条匹配当前场景的规则将决定访问权。例如,如果当前用户是系统所有者,他想尝试访问日志创建页,第二条规则将匹配成功并授予此用户权限。 + + +自定义 `创建` 和 `更新` 操作 +-------------------------------------------- + +`创建` 和 `更新` 操作非常相似。他们都需要显示一个HTML表单用于收集用户的输入的信息,然后对其进行验证,然后将其存入数据库。主要的不同是 `更新` 操作需要把从数据库找到的已存在的日志数据重现在表单中。鉴于此,`yiic` 工具创建了一个局部视图 `/wwwroot/blog/protected/views/post/_form.php` ,它会插入 `创建` 和 `更新` 视图来渲染所需的HTML表单。 + +我们先修改 `_form.php` 这个文件,使这个HTML表单只收集我们想要的输入:`title`, `content`, `tags` 和 `status`。我们使用文本域收集前三个属性的输入,还有一个下拉列表用来收集 `status` 的输入。此下拉列表的选项值就是可用的日志状态文本。 + +~~~ +[php] +dropDownList($model,'status',Lookup::items('PostStatus')); ?> +~~~ + +在上面的代码中,我们调用了 `Lookup::items('PostStatus')` 以带回日志状态列表。 + +然后我们修改 `Post` 类,使它可以在日志被存入数据库前自动设置几个属性 (例如 `create_time`, `author_id`)。我们覆盖 `beforeSave()` 方法如下: + +~~~ +[php] +protected function beforeSave() +{ + if(parent::beforeSave()) + { + if($this->isNewRecord) + { + $this->create_time=$this->update_time=time(); + $this->author_id=Yii::app()->user->id; + } + else + $this->update_time=time(); + return true; + } + else + return false; +} +~~~ + +当我们保存日志时,我们想更新 `tbl_tag` 表以反映 Tag 的使用频率。我们可以在 `afterSave()` 方法中完成此工作,它会在日志被成功存入数据库后自动被Yii调用。 + +~~~ +[php] +protected function afterSave() +{ + parent::afterSave(); + Tag::model()->updateFrequency($this->_oldTags, $this->tags); +} + +private $_oldTags; + +protected function afterFind() +{ + parent::afterFind(); + $this->_oldTags=$this->tags; +} +~~~ + +在这个实现中,因为我们想检测出用户在更新现有日志的时候是否修改了 Tag ,我们需要知道原来的 Tag 是什么, 鉴于此,我们还写了一个 `afterFind()` 方法把原有的 Tag 信息保存到变量 `_oldTags` 中。方法 `afterFind()` 会在一个 AR 记录被数据库中的数据填充时自动被 Yii 调用。 + +这里我们不再列出 `Tag::updateFrequency()` 方法的细节,读者可以参考 `/wwwroot/yii/demos/blog/protected/models/Tag.php` 文件。 + +
$Id: post.create.txt 2120 2010-05-10 01:29:41Z qiang.xue $
\ No newline at end of file diff --git a/docs/blog/zh_cn/post.display.txt b/docs/blog/zh_cn/post.display.txt index 403028ece..ec48802b1 100644 --- a/docs/blog/zh_cn/post.display.txt +++ b/docs/blog/zh_cn/post.display.txt @@ -1,108 +1,108 @@ -日志显示 -================ - -在我们的博客应用中,一篇日志可以显示在一个列表中,也可以单独显示。前者的实现通过 `index` 操作,而后者是通过 `view` 操作。 在这一节中,我们将自定义这两个操作来适合我们一开始的需求。 - - -自定义 `view` 操作 ----------------------------- - -`view` 操作是通过 `PostController` 中的 `actionView()` 方法实现的。它的显示是通过 `view` 视图文件 `/wwwroot/blog/protected/views/post/view.php` 生成的。 - -下面是在 `PostController` 中实现 `view` 操作的具体代码: - -~~~ -[php] -public function actionView() -{ - $post=$this->loadModel(); - $this->render('view',array( - 'model'=>$post, - )); -} - -private $_model; - -public function loadModel() -{ - if($this->_model===null) - { - if(isset($_GET['id'])) - { - if(Yii::app()->user->isGuest) - $condition='status='.Post::STATUS_PUBLISHED - .' OR status='.Post::STATUS_ARCHIVED; - else - $condition=''; - $this->_model=Post::model()->findByPk($_GET['id'], $condition); - } - if($this->_model===null) - throw new CHttpException(404,'The requested page does not exist.'); - } - return $this->_model; -} -~~~ - -我们的修改主要是在 `loadModel()` 方法上进行的。在这个方法中,我们通过 `id` GET参数查询了 `Post` 表。如果日志未找到或者没有发布,也未存档(当用户为游客(guest)时),我们将抛出一个 404 HTTP 错误。否则,日志对象将返回给 `actionView()` ,`actionView()` 又会把日志对象传递给视图脚本用于显示。 - -> Tip|提示: Yii 会捕获 HTTP 异常 ([CHttpException] 的实例) 并通过预置的模板或自定义的错误视图显示出来。由 `yiic` 生成的程序骨架已经包含了一个自定义的错误视图 `/wwwroot/blog/protected/views/site/error.php`。如果想进一步自定义此错误显示,我们可以自己修改此文件。 - -`view` 脚本中的修改主要是关于调整日志显示格式和样式的。此处我们不再细讲,读者可以参考 `/wwwroot/blog/protected/views/post/view.php`. - - -自定义 `index` 操作 ----------------------------- - -和 `view` 操作类似,我们在两处自定义 `index` 操作:`PostController` 中的 `actionIndex()` 方法和视图文件 `/wwwroot/blog/protected/views/post/index.php`。我们主要需要添加对显示一个特定Tag下的日志列表的支持; - -下面就是在 `PostController` 中对 `actionIndex() 方法作出的修改: - -~~~ -[php] -public function actionIndex() -{ - $criteria=new CDbCriteria(array( - 'condition'=>'status='.Post::STATUS_PUBLISHED, - 'order'=>'update_time DESC', - 'with'=>'commentCount', - )); - if(isset($_GET['tag'])) - $criteria->addSearchCondition('tags',$_GET['tag']); - - $dataProvider=new CActiveDataProvider('Post', array( - 'pagination'=>array( - 'pageSize'=>5, - ), - 'criteria'=>$criteria, - )); - - $this->render('index',array( - 'dataProvider'=>$dataProvider, - )); -} -~~~ - -在上面的代码中,我们首先为检索日志列表创建了一个查询标准(criteria),此标准规定只返回已发布的日志,且应该按其更新时间倒序排列。因为我们打算在显示日志列表的同时显示日志收到的评论数量,因此在这个标准中我们还指定了要带回 `commentCount`, 如果你还记得,它就是在 `Post::relations()` 中定义的一个关系。 - -考虑到当用户想查看某个Tag下的日志列表时的情况,我们还要为指定的Tag添加一个搜索条件到上述标准中。 - -使用这个查询标准,我们创建了一个数据提供者(data provider)。这主要出于三个目的。第一,它会在查询结果过多时实现数据分页。这里我们定义分页的页面大小为5。 第二,它会按用户的请求对数据排序。最后,它会填充排序并分页后的数据到小部件(widgets)或视图代码用于显示。 - -完成 `actionIndex()` 后,我们将 `index` 视图修改为如下代码。 此修改主要是关于在用户指定显示Tag下的日志时添加一个 `h1` 标题。 - -~~~ -[php] - -

Posts Tagged with

- - -widget('zii.widgets.CListView', array( - 'dataProvider'=>$dataProvider, - 'itemView'=>'_view', - 'template'=>"{items}\n{pager}", -)); ?> -~~~ - -注意上面的代码,我们使用了 [CListView] 来显示日志列表。这个小物件需要一个局部视图以显示每一篇日志的详情。这里我们制定了局部视图为 `_view`,也就是文件 `/wwwroot/blog/protected/views/post/_view.php`. 在这个视图脚本中,我们可以通过一个名为 `$data` 的本地变量访问显示的日志实例。 - +日志显示 +================ + +在我们的博客应用中,一篇日志可以显示在一个列表中,也可以单独显示。前者的实现通过 `index` 操作,而后者是通过 `view` 操作。 在这一节中,我们将自定义这两个操作来适合我们一开始的需求。 + + +自定义 `view` 操作 +---------------------------- + +`view` 操作是通过 `PostController` 中的 `actionView()` 方法实现的。它的显示是通过 `view` 视图文件 `/wwwroot/blog/protected/views/post/view.php` 生成的。 + +下面是在 `PostController` 中实现 `view` 操作的具体代码: + +~~~ +[php] +public function actionView() +{ + $post=$this->loadModel(); + $this->render('view',array( + 'model'=>$post, + )); +} + +private $_model; + +public function loadModel() +{ + if($this->_model===null) + { + if(isset($_GET['id'])) + { + if(Yii::app()->user->isGuest) + $condition='status='.Post::STATUS_PUBLISHED + .' OR status='.Post::STATUS_ARCHIVED; + else + $condition=''; + $this->_model=Post::model()->findByPk($_GET['id'], $condition); + } + if($this->_model===null) + throw new CHttpException(404,'The requested page does not exist.'); + } + return $this->_model; +} +~~~ + +我们的修改主要是在 `loadModel()` 方法上进行的。在这个方法中,我们通过 `id` GET参数查询了 `Post` 表。如果日志未找到或者没有发布,也未存档(当用户为游客(guest)时),我们将抛出一个 404 HTTP 错误。否则,日志对象将返回给 `actionView()` ,`actionView()` 又会把日志对象传递给视图脚本用于显示。 + +> Tip|提示: Yii 会捕获 HTTP 异常 ([CHttpException] 的实例) 并通过预置的模板或自定义的错误视图显示出来。由 `yiic` 生成的程序骨架已经包含了一个自定义的错误视图 `/wwwroot/blog/protected/views/site/error.php`。如果想进一步自定义此错误显示,我们可以自己修改此文件。 + +`view` 脚本中的修改主要是关于调整日志显示格式和样式的。此处我们不再细讲,读者可以参考 `/wwwroot/blog/protected/views/post/view.php`. + + +自定义 `index` 操作 +---------------------------- + +和 `view` 操作类似,我们在两处自定义 `index` 操作:`PostController` 中的 `actionIndex()` 方法和视图文件 `/wwwroot/blog/protected/views/post/index.php`。我们主要需要添加对显示一个特定Tag下的日志列表的支持; + +下面就是在 `PostController` 中对 `actionIndex() 方法作出的修改: + +~~~ +[php] +public function actionIndex() +{ + $criteria=new CDbCriteria(array( + 'condition'=>'status='.Post::STATUS_PUBLISHED, + 'order'=>'update_time DESC', + 'with'=>'commentCount', + )); + if(isset($_GET['tag'])) + $criteria->addSearchCondition('tags',$_GET['tag']); + + $dataProvider=new CActiveDataProvider('Post', array( + 'pagination'=>array( + 'pageSize'=>5, + ), + 'criteria'=>$criteria, + )); + + $this->render('index',array( + 'dataProvider'=>$dataProvider, + )); +} +~~~ + +在上面的代码中,我们首先为检索日志列表创建了一个查询标准(criteria),此标准规定只返回已发布的日志,且应该按其更新时间倒序排列。因为我们打算在显示日志列表的同时显示日志收到的评论数量,因此在这个标准中我们还指定了要带回 `commentCount`, 如果你还记得,它就是在 `Post::relations()` 中定义的一个关系。 + +考虑到当用户想查看某个Tag下的日志列表时的情况,我们还要为指定的Tag添加一个搜索条件到上述标准中。 + +使用这个查询标准,我们创建了一个数据提供者(data provider)。这主要出于三个目的。第一,它会在查询结果过多时实现数据分页。这里我们定义分页的页面大小为5。 第二,它会按用户的请求对数据排序。最后,它会填充排序并分页后的数据到小部件(widgets)或视图代码用于显示。 + +完成 `actionIndex()` 后,我们将 `index` 视图修改为如下代码。 此修改主要是关于在用户指定显示Tag下的日志时添加一个 `h1` 标题。 + +~~~ +[php] + +

Posts Tagged with

+ + +widget('zii.widgets.CListView', array( + 'dataProvider'=>$dataProvider, + 'itemView'=>'_view', + 'template'=>"{items}\n{pager}", +)); ?> +~~~ + +注意上面的代码,我们使用了 [CListView] 来显示日志列表。这个小物件需要一个局部视图以显示每一篇日志的详情。这里我们制定了局部视图为 `_view`,也就是文件 `/wwwroot/blog/protected/views/post/_view.php`. 在这个视图脚本中,我们可以通过一个名为 `$data` 的本地变量访问显示的日志实例。 +
$Id: post.display.txt 2121 2010-05-10 01:31:30Z qiang.xue $
\ No newline at end of file diff --git a/docs/blog/zh_cn/post.model.txt b/docs/blog/zh_cn/post.model.txt index 250feafd4..9afdef96f 100644 --- a/docs/blog/zh_cn/post.model.txt +++ b/docs/blog/zh_cn/post.model.txt @@ -1,200 +1,200 @@ -自定义日志模型 -====================== - -由 `yiic` 工具生成的 `Post` 日志模型类主要需要做如下两处修改: - - - `rules()` 方法:指定对模型属性的验证规则; - - `relations()` 方法:指定关联的对象; - -> Info|信息: [模型](http://www.yiiframework.com/doc/guide/basics.model) 包含了一系列属性,每个属性关联到数据表中相应的列。属性可以在类成员变量中显式定义,也可以隐式定义,不需要事先声明。 - - -自定义 `rules()` 方法 ----------------------------- - -我们先来指定验证规则,它可以确保用户输入的信息在保存到数据库之前是正确的。例如, `Post` 的 `status` 属性应该是 1, 2 或 3 中的任一数字。 `yiic` 工具其实也为每个模型生成了验证规则,但是这些规则是基于数据表的列信息的,可能并不是非常恰当。 - -基于需求分析,我们把 `rules()` 做如下修改: - -~~~ -[php] -public function rules() -{ - return array( - array('title, content, status', 'required'), - array('title', 'length', 'max'=>128), - array('status', 'in', 'range'=>array(1,2,3)), - array('tags', 'match', 'pattern'=>'/^[\w\s,]+$/', - 'message'=>'Tags can only contain word characters.'), - array('tags', 'normalizeTags'), - - array('title, status', 'safe', 'on'=>'search'), - ); -} -~~~ - -在上面的代码中,我们指定了 `title`, `content` 和 `status` 属性是必填项;`title` 的长度不能超过 128;`status` 属性值应该是 1 (草稿), 2 (已发布) 或 3 (已存档);`tags` 属性应只允许使用单词字母和逗号。另外,我们使用 `normalizeTags` 来规范化用户输入的Tag,使Tag是唯一的且整齐地通过逗号分隔。最后的规则会被搜索功能用到,这个我们后面再讲。 - -像 `required`, `length`, `in` 和 `match` 这几个验证器(validator)是Yii提供的内置验证器。`normalizeTags` 验证器是一个基于方法的验证器,我们需要在 `Post` 类中定义它。关于如何设置验证规则的更多信息,请参考 [指南](http://www.yiiframework.com/doc/guide/form.model#declaring-validation-rules)。 - -~~~ -[php] -public function normalizeTags($attribute,$params) -{ - $this->tags=Tag::array2string(array_unique(Tag::string2array($this->tags))); -} -~~~ - -其中的 `array2string` 和 `string2array` 是在 `Tag` 模型类中定义的新方法。详情请参考 `/wwwroot/yii/demos/blog/protected/models/Tag.php` 文件。 - -`rules()` 方法中定义的规则会在模型实例调用其 [validate()|CModel::validate] 或 [save()|CActiveRecord::save] 方法时逐一执行。 - -> Note|注意: 请务必记住 `rules()` 中出现的属性必须是那些通过用户输入的属性。其他的属性,如 `Post` 模型中的 `id` 和 `create_time` ,是通过我们的代码或数据库设定的,不应该出现在 `rules()` 中。详情请参考 [属性的安全赋值(Securing Attribute Assignments)](http://www.yiiframework.com/doc/guide/form.model#securing-attribute-assignments). - -作出这些修改之后,我们可以再次访问日志创建页检查新的验证规则是否已生效。 - - -自定义 `relations()` 方法 --------------------------------- - -最后我们来自定义 `relations()` 方法,以指定与日志相关的对象。通过在 `relations()` 中声明这些相关对象,我们就可以利用强大的 [Relational ActiveRecord (RAR)](http://www.yiiframework.com/doc/guide/database.arr) 功能来访问日志的相关对象,例如它的作者和评论。不需要自己写复杂的 SQL JOIN 语句。 - -我们自定义 `relations()` 方法如下: - -~~~ -[php] -public function relations() -{ - return array( - 'author' => array(self::BELONGS_TO, 'User', 'author_id'), - 'comments' => array(self::HAS_MANY, 'Comment', 'post_id', - 'condition'=>'comments.status='.Comment::STATUS_APPROVED, - 'order'=>'comments.create_time DESC'), - 'commentCount' => array(self::STAT, 'Comment', 'post_id', - 'condition'=>'status='.Comment::STATUS_APPROVED), - ); -} -~~~ - -我们还在 `Comment` 模型类中定义了两个在上面的方法中用到的常量。 - -~~~ -[php] -class Comment extends CActiveRecord -{ - const STATUS_PENDING=1; - const STATUS_APPROVED=2; - ...... -} -~~~ - -`relations()` 中声明的关系表明: - - * 一篇日志属于一个作者,它的类是 `User` ,它们的关系建立在日志的 `author_id` 属性值之上; - * 一篇日志有多个评论,它们的类是 `Comment` ,它们的关系建立在评论的 `post_id` 属性值之上。这些评论应该按它们的创建时间排列,且评论必须已通过审核; - * `commentCount` 关系有一点特别,它返回一个关于日志有多少条评论的一个聚合结果。 - - -通过以上的关系声明,我们现在可以按下面的方式很容易的访问日志的作者和评论信息。 - -~~~ -[php] -$author=$post->author; -echo $author->username; - -$comments=$post->comments; -foreach($comments as $comment) - echo $comment->content; -~~~ - -关于如何声明和使用关系的更多详情,请参考 [指南](http://www.yiiframework.com/doc/guide/database.arr). - - -添加 `url` 属性 ---------------------- - -日志是一份可以通过一个唯一的URL访问的内容。我们可以在 `Post` 模型中添加一个 `url` 属性,这样同样的创建URL的代码就可以被复用,而不是在代码中到处调用 [CWebApplication::createUrl] 。 稍后讲解怎样美化 URL 的时候,我们将看到添加这个属性给我们带来了超拽的便利。 - -要添加 `url` 属性,我们可以按如下方式给 `Post` 类添加一个 getter 方法: - -~~~ -[php] -class Post extends CActiveRecord -{ - public function getUrl() - { - return Yii::app()->createUrl('post/view', array( - 'id'=>$this->id, - 'title'=>$this->title, - )); - } -} -~~~ - -注意我们除了使用日志的ID之外,还添加了日志的标题作为URL中的一个 GET 参数。这主要是为了搜索引擎优化 (SEO) 的目的,在 [美化 URL](/doc/blog/final.url) 中将会讲述。 - -由于 [CComponent] 是 `Post` 的最顶级父类,添加 `getUrl()` 这个 getter 方法使我们可以使用类似 `$post->url` 这样的表达式。当我们访问 `$post->url` 时,getter 方法将会被执行,它的返回结果会成为此表达式的值。关于这种组件的更多详情,请参考 [指南](/doc/guide/basics.component)。 - - -以文本方式显示状态 ---------------------------- - -由于日志的状态在数据库中是以一个整型数字存储的,我们需要提供一个文本话的表现形式,这样在它显示给最终用户时会更加直观。在一个大的系统中,类似的需求是很常见的。 - -作为一个总体的解决方案,我们使用 `tbl_lookup` 表存储数字值和被用于其他数据对象的文本值的映射。为了更简单的访问表中的文本数据,我们按如下方式修改 `Lookup` 模型类: - -~~~ -[php] -class Lookup extends CActiveRecord -{ - private static $_items=array(); - - public static function items($type) - { - if(!isset(self::$_items[$type])) - self::loadItems($type); - return self::$_items[$type]; - } - - public static function item($type,$code) - { - if(!isset(self::$_items[$type])) - self::loadItems($type); - return isset(self::$_items[$type][$code]) ? self::$_items[$type][$code] : false; - } - - private static function loadItems($type) - { - self::$_items[$type]=array(); - $models=self::model()->findAll(array( - 'condition'=>'type=:type', - 'params'=>array(':type'=>$type), - 'order'=>'position', - )); - foreach($models as $model) - self::$_items[$type][$model->code]=$model->name; - } -} -~~~ - -我们的新代码主要提供了两个静态方法: `Lookup::items()` 和 `Lookup::item()`。前者返回一个属于指定的数据类型的字符串列表,后者按指定的数据类型和数据值返回一个具体的字符串。 - -我们的博客数据库已经预置了两个查询类别: `PostStatus` 和 `CommentStatus`。前者代表可用的日志状态,后者代表评论状态。 - -为了使我们的代码更加易读,我们还定义了一系列常量,用于表示整数型状态值。我们应该在涉及到相应的状态值时在代码中使用这些常量。 - -~~~ -[php] -class Post extends CActiveRecord -{ - const STATUS_DRAFT=1; - const STATUS_PUBLISHED=2; - const STATUS_ARCHIVED=3; - ...... -} -~~~ - -这样,我们可以通过调用 `Lookup::items('PostStatus')` 来获取可用的日志状态列表(按相应的整数值索引的文本字符串),通过调用 `Lookup::item('PostStatus', Post::STATUS_PUBLISHED)` 来获取发布状态的文本表现形式。 - - +自定义日志模型 +====================== + +由 `yiic` 工具生成的 `Post` 日志模型类主要需要做如下两处修改: + + - `rules()` 方法:指定对模型属性的验证规则; + - `relations()` 方法:指定关联的对象; + +> Info|信息: [模型](http://www.yiiframework.com/doc/guide/basics.model) 包含了一系列属性,每个属性关联到数据表中相应的列。属性可以在类成员变量中显式定义,也可以隐式定义,不需要事先声明。 + + +自定义 `rules()` 方法 +---------------------------- + +我们先来指定验证规则,它可以确保用户输入的信息在保存到数据库之前是正确的。例如, `Post` 的 `status` 属性应该是 1, 2 或 3 中的任一数字。 `yiic` 工具其实也为每个模型生成了验证规则,但是这些规则是基于数据表的列信息的,可能并不是非常恰当。 + +基于需求分析,我们把 `rules()` 做如下修改: + +~~~ +[php] +public function rules() +{ + return array( + array('title, content, status', 'required'), + array('title', 'length', 'max'=>128), + array('status', 'in', 'range'=>array(1,2,3)), + array('tags', 'match', 'pattern'=>'/^[\w\s,]+$/', + 'message'=>'Tags can only contain word characters.'), + array('tags', 'normalizeTags'), + + array('title, status', 'safe', 'on'=>'search'), + ); +} +~~~ + +在上面的代码中,我们指定了 `title`, `content` 和 `status` 属性是必填项;`title` 的长度不能超过 128;`status` 属性值应该是 1 (草稿), 2 (已发布) 或 3 (已存档);`tags` 属性应只允许使用单词字母和逗号。另外,我们使用 `normalizeTags` 来规范化用户输入的Tag,使Tag是唯一的且整齐地通过逗号分隔。最后的规则会被搜索功能用到,这个我们后面再讲。 + +像 `required`, `length`, `in` 和 `match` 这几个验证器(validator)是Yii提供的内置验证器。`normalizeTags` 验证器是一个基于方法的验证器,我们需要在 `Post` 类中定义它。关于如何设置验证规则的更多信息,请参考 [指南](http://www.yiiframework.com/doc/guide/form.model#declaring-validation-rules)。 + +~~~ +[php] +public function normalizeTags($attribute,$params) +{ + $this->tags=Tag::array2string(array_unique(Tag::string2array($this->tags))); +} +~~~ + +其中的 `array2string` 和 `string2array` 是在 `Tag` 模型类中定义的新方法。详情请参考 `/wwwroot/yii/demos/blog/protected/models/Tag.php` 文件。 + +`rules()` 方法中定义的规则会在模型实例调用其 [validate()|CModel::validate] 或 [save()|CActiveRecord::save] 方法时逐一执行。 + +> Note|注意: 请务必记住 `rules()` 中出现的属性必须是那些通过用户输入的属性。其他的属性,如 `Post` 模型中的 `id` 和 `create_time` ,是通过我们的代码或数据库设定的,不应该出现在 `rules()` 中。详情请参考 [属性的安全赋值(Securing Attribute Assignments)](http://www.yiiframework.com/doc/guide/form.model#securing-attribute-assignments). + +作出这些修改之后,我们可以再次访问日志创建页检查新的验证规则是否已生效。 + + +自定义 `relations()` 方法 +-------------------------------- + +最后我们来自定义 `relations()` 方法,以指定与日志相关的对象。通过在 `relations()` 中声明这些相关对象,我们就可以利用强大的 [Relational ActiveRecord (RAR)](http://www.yiiframework.com/doc/guide/database.arr) 功能来访问日志的相关对象,例如它的作者和评论。不需要自己写复杂的 SQL JOIN 语句。 + +我们自定义 `relations()` 方法如下: + +~~~ +[php] +public function relations() +{ + return array( + 'author' => array(self::BELONGS_TO, 'User', 'author_id'), + 'comments' => array(self::HAS_MANY, 'Comment', 'post_id', + 'condition'=>'comments.status='.Comment::STATUS_APPROVED, + 'order'=>'comments.create_time DESC'), + 'commentCount' => array(self::STAT, 'Comment', 'post_id', + 'condition'=>'status='.Comment::STATUS_APPROVED), + ); +} +~~~ + +我们还在 `Comment` 模型类中定义了两个在上面的方法中用到的常量。 + +~~~ +[php] +class Comment extends CActiveRecord +{ + const STATUS_PENDING=1; + const STATUS_APPROVED=2; + ...... +} +~~~ + +`relations()` 中声明的关系表明: + + * 一篇日志属于一个作者,它的类是 `User` ,它们的关系建立在日志的 `author_id` 属性值之上; + * 一篇日志有多个评论,它们的类是 `Comment` ,它们的关系建立在评论的 `post_id` 属性值之上。这些评论应该按它们的创建时间排列,且评论必须已通过审核; + * `commentCount` 关系有一点特别,它返回一个关于日志有多少条评论的一个聚合结果。 + + +通过以上的关系声明,我们现在可以按下面的方式很容易的访问日志的作者和评论信息。 + +~~~ +[php] +$author=$post->author; +echo $author->username; + +$comments=$post->comments; +foreach($comments as $comment) + echo $comment->content; +~~~ + +关于如何声明和使用关系的更多详情,请参考 [指南](http://www.yiiframework.com/doc/guide/database.arr). + + +添加 `url` 属性 +--------------------- + +日志是一份可以通过一个唯一的URL访问的内容。我们可以在 `Post` 模型中添加一个 `url` 属性,这样同样的创建URL的代码就可以被复用,而不是在代码中到处调用 [CWebApplication::createUrl] 。 稍后讲解怎样美化 URL 的时候,我们将看到添加这个属性给我们带来了超拽的便利。 + +要添加 `url` 属性,我们可以按如下方式给 `Post` 类添加一个 getter 方法: + +~~~ +[php] +class Post extends CActiveRecord +{ + public function getUrl() + { + return Yii::app()->createUrl('post/view', array( + 'id'=>$this->id, + 'title'=>$this->title, + )); + } +} +~~~ + +注意我们除了使用日志的ID之外,还添加了日志的标题作为URL中的一个 GET 参数。这主要是为了搜索引擎优化 (SEO) 的目的,在 [美化 URL](/doc/blog/final.url) 中将会讲述。 + +由于 [CComponent] 是 `Post` 的最顶级父类,添加 `getUrl()` 这个 getter 方法使我们可以使用类似 `$post->url` 这样的表达式。当我们访问 `$post->url` 时,getter 方法将会被执行,它的返回结果会成为此表达式的值。关于这种组件的更多详情,请参考 [指南](/doc/guide/basics.component)。 + + +以文本方式显示状态 +--------------------------- + +由于日志的状态在数据库中是以一个整型数字存储的,我们需要提供一个文本话的表现形式,这样在它显示给最终用户时会更加直观。在一个大的系统中,类似的需求是很常见的。 + +作为一个总体的解决方案,我们使用 `tbl_lookup` 表存储数字值和被用于其他数据对象的文本值的映射。为了更简单的访问表中的文本数据,我们按如下方式修改 `Lookup` 模型类: + +~~~ +[php] +class Lookup extends CActiveRecord +{ + private static $_items=array(); + + public static function items($type) + { + if(!isset(self::$_items[$type])) + self::loadItems($type); + return self::$_items[$type]; + } + + public static function item($type,$code) + { + if(!isset(self::$_items[$type])) + self::loadItems($type); + return isset(self::$_items[$type][$code]) ? self::$_items[$type][$code] : false; + } + + private static function loadItems($type) + { + self::$_items[$type]=array(); + $models=self::model()->findAll(array( + 'condition'=>'type=:type', + 'params'=>array(':type'=>$type), + 'order'=>'position', + )); + foreach($models as $model) + self::$_items[$type][$model->code]=$model->name; + } +} +~~~ + +我们的新代码主要提供了两个静态方法: `Lookup::items()` 和 `Lookup::item()`。前者返回一个属于指定的数据类型的字符串列表,后者按指定的数据类型和数据值返回一个具体的字符串。 + +我们的博客数据库已经预置了两个查询类别: `PostStatus` 和 `CommentStatus`。前者代表可用的日志状态,后者代表评论状态。 + +为了使我们的代码更加易读,我们还定义了一系列常量,用于表示整数型状态值。我们应该在涉及到相应的状态值时在代码中使用这些常量。 + +~~~ +[php] +class Post extends CActiveRecord +{ + const STATUS_DRAFT=1; + const STATUS_PUBLISHED=2; + const STATUS_ARCHIVED=3; + ...... +} +~~~ + +这样,我们可以通过调用 `Lookup::items('PostStatus')` 来获取可用的日志状态列表(按相应的整数值索引的文本字符串),通过调用 `Lookup::item('PostStatus', Post::STATUS_PUBLISHED)` 来获取发布状态的文本表现形式。 + +
$Id: post.model.txt 2119 2010-05-10 01:27:29Z qiang.xue $
\ No newline at end of file diff --git a/docs/blog/zh_cn/prototype.auth.txt b/docs/blog/zh_cn/prototype.auth.txt index 9da97dcfc..eb65954e4 100644 --- a/docs/blog/zh_cn/prototype.auth.txt +++ b/docs/blog/zh_cn/prototype.auth.txt @@ -1,105 +1,105 @@ -用户验证 -=================== - -我们的博客应用需要区分系统所有者和来宾用户。因此,我们需要实现 [用户验证](http://www.yiiframework.com/doc/guide/topics.auth) 功能。 - -或许你已经发现了,我们的程序骨架已经提供了用户验证功能,它会判断用户名和密码是不是都为 `demo` 或 `admin`。在这一节里,我们将修改这些代码,以使身份验证通过 `User` 数据表实现。 - -用户验证在一个实现了 [IUserIdentity] 接口的类中进行。此程序骨架通过 `UserIdentity` 类实现此目的。此类存储在 `/wwwroot/blog/protected/components/UserIdentity.php` 文件中。 - -> Tip|提示: 按照约定,类文件的名字必须是相应的类名加上 `.php` 后缀。遵循此约定,就可以通过一个[路径别名(path alias)](http://www.yiiframework.com/doc/guide/basics.namespace) 指向此类。例如,我们可以通过别名 `application.components.UserIdentity` 指向 `UserIdentity` 类。Yii 的许多API都可以识别路径别名 (例如 [Yii::createComponent()|YiiBase::createComponent]),使用路径别名可以避免在代码中插入文件的绝对路径。绝对路径的存在往往会导致在部署应用时遇到麻烦。 - -我们将 `UserIdentity` 类做如下修改, - -~~~ -[php] -username); - $user=User::model()->find('LOWER(username)=?',array($username)); - if($user===null) - $this->errorCode=self::ERROR_USERNAME_INVALID; - else if(!$user->validatePassword($this->password)) - $this->errorCode=self::ERROR_PASSWORD_INVALID; - else - { - $this->_id=$user->id; - $this->username=$user->username; - $this->errorCode=self::ERROR_NONE; - } - return $this->errorCode==self::ERROR_NONE; - } - - public function getId() - { - return $this->_id; - } -} -~~~ - -在 `authenticate()` 方法中,我们使用 `User` 类来查询 `tbl_user` 表中 `username` 列值(不区分大小写)和提供的用户名一致的一行,请记住 `User` 类是在前面的章节中通过 `gii` 工具创建的。由于 `User` 类继承自 [CActiveRecord] ,我们可以利用 [ActiveRecord 功能](http://www.yiiframework.com/doc/guide/database.ar) 以 OOP 的风格访问 `tbl_user` 表。 - -为了检查用户是否输入了一个有效的密码,我们调用了 `User` 类的 `validatePassword` 方法。我们需要按下面的代码修改 `/wwwroot/blog/protected/models/User.php` 文件。注意,我们在数据库中存储了密码的加密串和随机生成的SALT密钥,而不是存储明文密码。 所以当要验证用户输入的密码时,我们应该和加密结果做对比。 - -~~~ -[php] -class User extends CActiveRecord -{ - ...... - public function validatePassword($password) - { - return $this->hashPassword($password,$this->salt)===$this->password; - } - - public function hashPassword($password,$salt) - { - return md5($salt.$password); - } -} -~~~ - -在 `UserIdentity` 类中,我们还覆盖(Override,又称为重写)了 `getId()` 方法,它会返回在 `User` 表中找到的用户的 `id`。父类 ([CUserIdentity]) 则会返回用户名。`username` 和 `id` 属性都将存储在用户 SESSION 中,可在代码的任何部分通过 `Yii::app()->user` 访问。 - -> Tip|提示: 在 `UserIdentity` 类中,我们没有显式包含(include)相应的类文件就访问了 [CUserIdentity] 类,这是因为 [CUserIdentity] 是一个由Yii框架提供的核心类。Yii 将会在任何核心类被首次使用时自动包含类文件。 -> -> 我们也对 `User` 类做了同样的事情。这是因为 `User` 类文件被放在了 `/wwwroot/blog/protected/models` 目录,此目录已经通过应用配置中的如下几行代码被添加到了 PHP 的 `include_path` 中: -> -> ~~~ -> [php] -> return array( -> ...... -> 'import'=>array( -> 'application.models.*', -> 'application.components.*', -> ), -> ...... -> ); -> ~~~ -> -> 上面的配置说明,位于 `/wwwroot/blog/protected/models` 或 `/wwwroot/blog/protected/components` 目录中的任何类将在第一次使用时被自动包含。 - -`UserIdentity` 类主要用于 `LoginForm` 类中,它基于用户名和从登录页中收到的密码来实现用户验证。下面的代码展示了 `UserIdentity` 的使用: - -~~~ -[php] -$identity=new UserIdentity($username,$password); -$identity->authenticate(); -switch($identity->errorCode) -{ - case UserIdentity::ERROR_NONE: - Yii::app()->user->login($identity); - break; - ...... -} -~~~ - -> Info|信息: 人们经常对 identity 和 `user` 应用组件感到困惑,前者代表的是一种验证方法,后者代表当前用户相关的信息。一个应用只能有一个 `user` 组件,但它可以有一个或多个 identity 类,这取决于它支持什么样的验证方法。一旦验证通过,identity 实例会把它自己的状态信息传递给 `user` 组件,这样它们就可以通过 `user` 实现全局可访问。 - -要测试修改过的 `UserIdentity` 类,我们可以浏览 URL `http://www.example.com/blog/index.php` ,然后尝试使用存储在 `User` 表中的用户名和密码登录。如果我们使用了 [博客演示](http://www.yiiframework.com/demos/blog/) 中的数据,我们应该可以通过用户名 `demo` 和密码 `demo` 登录。注意,此博客系统没有提供用户管理功能。因此,用户无法修改自己的信息或通过Web界面创建一个新的帐号。用户管理功能可以考虑作为以后对此博客应用的增强。 - +用户验证 +=================== + +我们的博客应用需要区分系统所有者和来宾用户。因此,我们需要实现 [用户验证](http://www.yiiframework.com/doc/guide/topics.auth) 功能。 + +或许你已经发现了,我们的程序骨架已经提供了用户验证功能,它会判断用户名和密码是不是都为 `demo` 或 `admin`。在这一节里,我们将修改这些代码,以使身份验证通过 `User` 数据表实现。 + +用户验证在一个实现了 [IUserIdentity] 接口的类中进行。此程序骨架通过 `UserIdentity` 类实现此目的。此类存储在 `/wwwroot/blog/protected/components/UserIdentity.php` 文件中。 + +> Tip|提示: 按照约定,类文件的名字必须是相应的类名加上 `.php` 后缀。遵循此约定,就可以通过一个[路径别名(path alias)](http://www.yiiframework.com/doc/guide/basics.namespace) 指向此类。例如,我们可以通过别名 `application.components.UserIdentity` 指向 `UserIdentity` 类。Yii 的许多API都可以识别路径别名 (例如 [Yii::createComponent()|YiiBase::createComponent]),使用路径别名可以避免在代码中插入文件的绝对路径。绝对路径的存在往往会导致在部署应用时遇到麻烦。 + +我们将 `UserIdentity` 类做如下修改, + +~~~ +[php] +username); + $user=User::model()->find('LOWER(username)=?',array($username)); + if($user===null) + $this->errorCode=self::ERROR_USERNAME_INVALID; + else if(!$user->validatePassword($this->password)) + $this->errorCode=self::ERROR_PASSWORD_INVALID; + else + { + $this->_id=$user->id; + $this->username=$user->username; + $this->errorCode=self::ERROR_NONE; + } + return $this->errorCode==self::ERROR_NONE; + } + + public function getId() + { + return $this->_id; + } +} +~~~ + +在 `authenticate()` 方法中,我们使用 `User` 类来查询 `tbl_user` 表中 `username` 列值(不区分大小写)和提供的用户名一致的一行,请记住 `User` 类是在前面的章节中通过 `gii` 工具创建的。由于 `User` 类继承自 [CActiveRecord] ,我们可以利用 [ActiveRecord 功能](http://www.yiiframework.com/doc/guide/database.ar) 以 OOP 的风格访问 `tbl_user` 表。 + +为了检查用户是否输入了一个有效的密码,我们调用了 `User` 类的 `validatePassword` 方法。我们需要按下面的代码修改 `/wwwroot/blog/protected/models/User.php` 文件。注意,我们在数据库中存储了密码的加密串和随机生成的SALT密钥,而不是存储明文密码。 所以当要验证用户输入的密码时,我们应该和加密结果做对比。 + +~~~ +[php] +class User extends CActiveRecord +{ + ...... + public function validatePassword($password) + { + return $this->hashPassword($password,$this->salt)===$this->password; + } + + public function hashPassword($password,$salt) + { + return md5($salt.$password); + } +} +~~~ + +在 `UserIdentity` 类中,我们还覆盖(Override,又称为重写)了 `getId()` 方法,它会返回在 `User` 表中找到的用户的 `id`。父类 ([CUserIdentity]) 则会返回用户名。`username` 和 `id` 属性都将存储在用户 SESSION 中,可在代码的任何部分通过 `Yii::app()->user` 访问。 + +> Tip|提示: 在 `UserIdentity` 类中,我们没有显式包含(include)相应的类文件就访问了 [CUserIdentity] 类,这是因为 [CUserIdentity] 是一个由Yii框架提供的核心类。Yii 将会在任何核心类被首次使用时自动包含类文件。 +> +> 我们也对 `User` 类做了同样的事情。这是因为 `User` 类文件被放在了 `/wwwroot/blog/protected/models` 目录,此目录已经通过应用配置中的如下几行代码被添加到了 PHP 的 `include_path` 中: +> +> ~~~ +> [php] +> return array( +> ...... +> 'import'=>array( +> 'application.models.*', +> 'application.components.*', +> ), +> ...... +> ); +> ~~~ +> +> 上面的配置说明,位于 `/wwwroot/blog/protected/models` 或 `/wwwroot/blog/protected/components` 目录中的任何类将在第一次使用时被自动包含。 + +`UserIdentity` 类主要用于 `LoginForm` 类中,它基于用户名和从登录页中收到的密码来实现用户验证。下面的代码展示了 `UserIdentity` 的使用: + +~~~ +[php] +$identity=new UserIdentity($username,$password); +$identity->authenticate(); +switch($identity->errorCode) +{ + case UserIdentity::ERROR_NONE: + Yii::app()->user->login($identity); + break; + ...... +} +~~~ + +> Info|信息: 人们经常对 identity 和 `user` 应用组件感到困惑,前者代表的是一种验证方法,后者代表当前用户相关的信息。一个应用只能有一个 `user` 组件,但它可以有一个或多个 identity 类,这取决于它支持什么样的验证方法。一旦验证通过,identity 实例会把它自己的状态信息传递给 `user` 组件,这样它们就可以通过 `user` 实现全局可访问。 + +要测试修改过的 `UserIdentity` 类,我们可以浏览 URL `http://www.example.com/blog/index.php` ,然后尝试使用存储在 `User` 表中的用户名和密码登录。如果我们使用了 [博客演示](http://www.yiiframework.com/demos/blog/) 中的数据,我们应该可以通过用户名 `demo` 和密码 `demo` 登录。注意,此博客系统没有提供用户管理功能。因此,用户无法修改自己的信息或通过Web界面创建一个新的帐号。用户管理功能可以考虑作为以后对此博客应用的增强。 +
$Id: prototype.auth.txt 2333 2010-08-24 21:11:55Z mdomba $
\ No newline at end of file diff --git a/docs/blog/zh_cn/prototype.database.txt b/docs/blog/zh_cn/prototype.database.txt index b7c19be76..7f1dcba7a 100644 --- a/docs/blog/zh_cn/prototype.database.txt +++ b/docs/blog/zh_cn/prototype.database.txt @@ -1,70 +1,70 @@ -建立数据库 -=================== - -完成了程序骨架和数据库设计,在这一节里我们将创建博客的数据库并将其连接到程序骨架中。 - - -创建数据库 ------------------ - -我们选择创建一个SQLite数据库。由于Yii中的数据库支持是建立在 [PDO](http://www.php.net/manual/en/book.pdo.php) 之上的,我们可以很容易地切换到一个不同的 DBMS (例如 MySQL, PostgreSQL) 而不需要修改我们的应用代码。 - -我们把数据库文件 `blog.db` 建立在 `/wwwroot/blog/protected/data` 中。注意,数据库文件和其所在的目录都必须对Web服务器进程可写,这是SQLite的要求。我们可以简单的从博客演示中复制这个数据库文件,它位于 `/wwwroot/yii/demos/blog/protected/data/blog.db`。我们也可以通过执行 `/wwwroot/yii/demos/blog/protected/data/schema.sqlite.sql` 文件中的SQL语句自己创建这个数据库。 - -> Tip|提示: 要执行SQL语句,我们可以使用 `sqlite3` 命令行工具。它可以在 [SQLite 官方网站](http://www.sqlite.org/download.html) 中找到。 - - -建立数据库连接 --------------------------------- - -要在我们创建的程序骨架中使用这个数据库,我们需要修改它的[应用配置](http://www.yiiframework.com/doc/guide/basics.application#application-configuration) ,它保存在PHP脚本 `/wwwroot/blog/protected/config/main.php` 中。此脚本返回一个包含键值对的关联数组,它们中的每一项被用来初始化[应用实例](http://www.yiiframework.com/doc/guide/basics.application) 中的可写属性。 - -我们按如下方式配置 `db` 组件, - -~~~ -[php] -return array( - ...... - 'components'=>array( - ...... - 'db'=>array( - 'connectionString'=>'sqlite:/wwwroot/blog/protected/data/blog.db', - 'tablePrefix'=>'tbl_', - ), - ), - ...... -); -~~~ - -上述配置的意思是说我们有一个 `db` [应用组件](http://www.yiiframework.com/doc/guide/basics.application#application-component) ,它的 `connectionString` 属性应当以 `sqlite:/wwwroot/blog/protected/data/blog.db` 这个值初始化,它的 `tablePrefix` 属性应该是 `tbl_`。 - -通过这个配置,我们就可以在代码的任意位置使用 `Yii::app()->db` 来访问数据库连接对象了。注意, `Yii::app()` 会返回我们在入口脚本中创建的应用实例。如果你对数据库连接的其他可用的方法和属性感兴趣,可以阅读 [类参考|CDbConnection]。然而,在多数情况下,我们并不会直接使用这个数据库连接。而是使用被称为 [ActiveRecord](http://www.yiiframework.com/doc/guide/database.ar) 的东西来访问数据库。 - -我们想对配置中的 `tablePrefix` 属性再解释一点。此属性告诉 `db` 连接它应该关注我们使用了 `tbl_` 作为数据库表前缀。具体来说,如果一条SQL语句中含有一个被双大括号括起来的标记 (例如 `{{post}}`),那么 `db` 连接应该在把它提交给DBMS执行前,先将其翻译成带有表前缀的名字 (例如 `tbl_post`) 。这个特性非常有用,如果将来我们需要修改表前缀,就不需要再动代码了。例如,如果我们正在开发一个通用内容管理系统 (CMS),我们就可以利用此特性,这样当它被安装在一个不同的环境中时,我们就能允许用户选择一个他们喜欢的表前缀。 - -> Tip|提示: 如果你想使用MySQL而不是SQLite来存储数据,你可以使用位于 -> `/wwwroot/yii/demos/blog/protected/data/schema.mysql.sql` 文件 -> 中的SQL语句创建一个名为 `blog` 的 MySQL 数据库。然后,按如下方式 -> 修改应用配置, -> -> ~~~ -> [php] -> return array( -> ...... -> 'components'=>array( -> ...... -> 'db'=>array( -> 'connectionString' => 'mysql:host=localhost;dbname=blog', -> 'emulatePrepare' => true, -> 'username' => 'root', -> 'password' => '', -> 'charset' => 'utf8', -> 'tablePrefix' => 'tbl_', -> ), -> ), -> ...... -> ); -> ~~~ - - +建立数据库 +=================== + +完成了程序骨架和数据库设计,在这一节里我们将创建博客的数据库并将其连接到程序骨架中。 + + +创建数据库 +----------------- + +我们选择创建一个SQLite数据库。由于Yii中的数据库支持是建立在 [PDO](http://www.php.net/manual/en/book.pdo.php) 之上的,我们可以很容易地切换到一个不同的 DBMS (例如 MySQL, PostgreSQL) 而不需要修改我们的应用代码。 + +我们把数据库文件 `blog.db` 建立在 `/wwwroot/blog/protected/data` 中。注意,数据库文件和其所在的目录都必须对Web服务器进程可写,这是SQLite的要求。我们可以简单的从博客演示中复制这个数据库文件,它位于 `/wwwroot/yii/demos/blog/protected/data/blog.db`。我们也可以通过执行 `/wwwroot/yii/demos/blog/protected/data/schema.sqlite.sql` 文件中的SQL语句自己创建这个数据库。 + +> Tip|提示: 要执行SQL语句,我们可以使用 `sqlite3` 命令行工具。它可以在 [SQLite 官方网站](http://www.sqlite.org/download.html) 中找到。 + + +建立数据库连接 +-------------------------------- + +要在我们创建的程序骨架中使用这个数据库,我们需要修改它的[应用配置](http://www.yiiframework.com/doc/guide/basics.application#application-configuration) ,它保存在PHP脚本 `/wwwroot/blog/protected/config/main.php` 中。此脚本返回一个包含键值对的关联数组,它们中的每一项被用来初始化[应用实例](http://www.yiiframework.com/doc/guide/basics.application) 中的可写属性。 + +我们按如下方式配置 `db` 组件, + +~~~ +[php] +return array( + ...... + 'components'=>array( + ...... + 'db'=>array( + 'connectionString'=>'sqlite:/wwwroot/blog/protected/data/blog.db', + 'tablePrefix'=>'tbl_', + ), + ), + ...... +); +~~~ + +上述配置的意思是说我们有一个 `db` [应用组件](http://www.yiiframework.com/doc/guide/basics.application#application-component) ,它的 `connectionString` 属性应当以 `sqlite:/wwwroot/blog/protected/data/blog.db` 这个值初始化,它的 `tablePrefix` 属性应该是 `tbl_`。 + +通过这个配置,我们就可以在代码的任意位置使用 `Yii::app()->db` 来访问数据库连接对象了。注意, `Yii::app()` 会返回我们在入口脚本中创建的应用实例。如果你对数据库连接的其他可用的方法和属性感兴趣,可以阅读 [类参考|CDbConnection]。然而,在多数情况下,我们并不会直接使用这个数据库连接。而是使用被称为 [ActiveRecord](http://www.yiiframework.com/doc/guide/database.ar) 的东西来访问数据库。 + +我们想对配置中的 `tablePrefix` 属性再解释一点。此属性告诉 `db` 连接它应该关注我们使用了 `tbl_` 作为数据库表前缀。具体来说,如果一条SQL语句中含有一个被双大括号括起来的标记 (例如 `{{post}}`),那么 `db` 连接应该在把它提交给DBMS执行前,先将其翻译成带有表前缀的名字 (例如 `tbl_post`) 。这个特性非常有用,如果将来我们需要修改表前缀,就不需要再动代码了。例如,如果我们正在开发一个通用内容管理系统 (CMS),我们就可以利用此特性,这样当它被安装在一个不同的环境中时,我们就能允许用户选择一个他们喜欢的表前缀。 + +> Tip|提示: 如果你想使用MySQL而不是SQLite来存储数据,你可以使用位于 +> `/wwwroot/yii/demos/blog/protected/data/schema.mysql.sql` 文件 +> 中的SQL语句创建一个名为 `blog` 的 MySQL 数据库。然后,按如下方式 +> 修改应用配置, +> +> ~~~ +> [php] +> return array( +> ...... +> 'components'=>array( +> ...... +> 'db'=>array( +> 'connectionString' => 'mysql:host=localhost;dbname=blog', +> 'emulatePrepare' => true, +> 'username' => 'root', +> 'password' => '', +> 'charset' => 'utf8', +> 'tablePrefix' => 'tbl_', +> ), +> ), +> ...... +> ); +> ~~~ + +
$Id: prototype.database.txt 2332 2010-08-24 20:55:36Z mdomba $
\ No newline at end of file diff --git a/docs/blog/zh_cn/prototype.scaffold.txt b/docs/blog/zh_cn/prototype.scaffold.txt index a0f3ac0a6..18b6db1d8 100644 --- a/docs/blog/zh_cn/prototype.scaffold.txt +++ b/docs/blog/zh_cn/prototype.scaffold.txt @@ -1,118 +1,118 @@ -脚手架 -=========== - -创建,读取,更新,删除 (CRUD) 是应用的数据对象中的四个基本操作。由于在Web应用的开发中实现CURD的任务非常常见,Yii 为我们提供了一些可以使这些过程自动化的代码生成工具,名为 *Gii* (也被称为 *脚手架*) 。 - -> Note|注意: Gii 从 Yii 1.1.2 版开始提供。在这之前,你可能需要使用 [yiic shell tool](http://www.yiiframework.com/doc/guide/quickstart.first-app-yiic) 来实现相同的任务。 - -下面,我们将阐述如何使用这个工具来实现博客应用中的CRUD操作。 - -安装 Gii --------------- - -首先我们需要安装 Gii. 打开文件 `/wwwroot/blog/protected/config/main.php` ,添加如下代码: - -~~~ -[php] -return array( - ...... - 'import'=>array( - 'application.models.*', - 'application.components.*', - ), - - 'modules'=>array( - 'gii'=>array( - 'class'=>'system.gii.GiiModule', - 'password'=>'这儿设置一个密码', - ), - ), -); -~~~ - -上面的代码安装了一个名为 `gii` 的模块,这样我们就可以通过在浏览器中浏览如下URL来访问 Gii 模块: - -~~~ -http://www.example.com/blog/index.php?r=gii -~~~ - -我们将被提示要求输入一个密码。输入我们前面在 `/wwwroot/blog/protected/config/main.php` 中设置的密码,我们将看到一个页面,它列出了所有可用的代码生成工具。 - -> Note|注意: 上述代码在生产环境中应当移除。代码生成工具只应当用于开发环境。 - - -创建模型 ---------------- - -首先我们需要为每个数据表创建一个[模型(Model)](http://www.yiiframework.com/doc/guide/basics.model) 类。模型类会使我们可以通过一种直观的、面向对象的风格访问数据库。稍后我们将会看到这一点。 - -点击 `Model Generator` 链接开始使用模型创建工具。 - -在 `Model Generator` 页中,在`Table Name`一栏输入 `tbl_user` (用户表的名字),然后按下 `Preview` 按钮。一个预览表将显示在我们面前。我们可以点击表格中的链接来预览要生成的代码。如果一切OK,我们可以按下 `Generate` 按钮来生成代码并将其保存在一个文件中。 - -> Info|信息: 由于代码生成器需要保存生成的代码到文件,它要求Web服务器进程必须拥有对相应文件的创建和修改权限。为简单起见,我们可以赋予Web服务器进程对整个 `/wwwroot/blog` 目录的写权限。注意这只在开发机器上使用 `Gii` 时会用到。 - -对剩余的其他表重复同样的步骤,包括 `tbl_post`, `tbl_comment`, `tbl_tag` 和 `tbl_lookup`。 - -> Tip|提示: 我们还可以在 `Table Name` 栏中输入一个星号 '\*' 。这样就可以通过一次点击就对 *所有的* 数据表生成相应的模型类。 - -通过这一步,我们就有了如下新创建的文件: - - * `models/User.php` 包含了继承自 [CActiveRecord] 的 `User` 类,可用于访问 `tbl_user` 数据表; - * `models/Post.php` 包含了继承自 [CActiveRecord] 的 `Post` 类,可用于访问 `tbl_post` 数据表; - * `models/Tag.php` 包含了继承自 [CActiveRecord] 的 `Tag` 类,可用于访问 `tbl_tag` 数据表; - * `models/Comment.php` 包含了继承自 [CActiveRecord] 的 `Comment` 类,可用于访问 `tbl_comment` 数据表; - * `models/Lookup.php` 包含了继承自 [CActiveRecord] 的 `Lookup` 类,可用于访问 `tbl_lookup` 数据表; - - -实现 CRUD 操作 ----------------------------- - -模型类建好之后,我们就可以使用 `Crud Generator` 来创建为这些模型实现CRUD操作的代码了。我们将对 `Post` 和 `Comment` 模型执行此操作。 - -在 `Crud Generator` 页面中,`Model Class` 一栏输入 `Post` (就是我们刚创建的 Post 模型的名字) ,然后按下 `Preview` 按钮。我们会看到有很多文件将被创建。按下 `Generate` 按钮来创建它们。 - -对 `Comment` 模型重复同样的步骤。 - -让我们看一下通过CRUD生成器生成的这些文件。所有的文件都创建在了 `/wwwroot/blog/protected` 目录中。为方便起见,我们把它们分组为 [控制器(Controller)](http://www.yiiframework.com/doc/guide/basics.controller) 文件和 [视图(View)](http://www.yiiframework.com/doc/guide/basics.view) 文件: - - - 控制器文件: - * `controllers/PostController.php` 包含负责所有CRUD操作的 `PostController` 控制器类; - * `controllers/CommentController.php` 包含负责所有CRUD操作的 `CommentController` 控制器类; - - - 视图文件: - * `views/post/create.php` 一个视图文件,用于显示创建新日志的 HTML 表单; - * `views/post/update.php` 一个视图文件,用于显示更新日志的 HTML 表单; - * `views/post/view.php` 一个视图文件,用于显示一篇日志的详情; - * `views/post/index.php` 一个视图文件,用于显示日志列表; - * `views/post/admin.php` 一个视图文件,用于在一个带有管理员命令的表格中显示日志; - * `views/post/_form.php` 一个插入 `views/post/create.php` 和 `views/post/update.php` 的局部视图文件。它显示用于收集日志信息的HTML表单; - * `views/post/_view.php` 一个在 `views/post/index.php` 中使用的局部视图文件。它显示单篇日志的摘要信息; - * `views/post/_search.php` 一个在 `views/post/admin.php` 中使用的局部视图文件。它显示一个搜索表单; - * 还有为评论创建的一系列相似的文件。 - - -测试 -------- - -我们可以通过访问如下网址测试我们刚生成的代码所实现的功能: - -~~~ -http://www.example.com/blog/index.php?r=post -http://www.example.com/blog/index.php?r=comment -~~~ - -注意,由代码生成器实现的日志和评论功能是完全相互独立的。并且,当创建一个新的日志或评论时,我们必须输入如 `author_id` 和 `create_time` 这些信息,而在现实应用中这些应当由程序自动设置。别担心。我们将在下一个阶段中修正这些问题。现在呢,这个模型已经包含了大多数我们需要在博客应用中实现的功能,我们应该对此感到满意了 ^_^。 - - -为了更好地理解这些文件是如何使用的,我们在下面列出了当显示一个日志列表时发生的工作流程。 - - 0. 用户请求访问这个 URL `http://www.example.com/blog/index.php?r=post`; - 1. [入口脚本](http://www.yiiframework.com/doc/guide/basics.entry) 被Web服务器执行,它创建并实例化了一个 [应用](http://www.yiiframework.com/doc/guide/basics.application) 实例来处理此请求; - 2. 应用创建并执行了 `PostController` 实例; - 3. `PostController` 实例通过调用它的 `actionIndex()` 方法执行了 `index` 动作。注意,如果用户没有在URL中指定执行一个动作,则 `index` 就是默认的动作; - 4. `actionIndex()` 方法查询数据库,带回最新的日志列表; - 5. `actionIndex()` 方法使用日志数据渲染 `index` 视图。 - - +脚手架 +=========== + +创建,读取,更新,删除 (CRUD) 是应用的数据对象中的四个基本操作。由于在Web应用的开发中实现CURD的任务非常常见,Yii 为我们提供了一些可以使这些过程自动化的代码生成工具,名为 *Gii* (也被称为 *脚手架*) 。 + +> Note|注意: Gii 从 Yii 1.1.2 版开始提供。在这之前,你可能需要使用 [yiic shell tool](http://www.yiiframework.com/doc/guide/quickstart.first-app-yiic) 来实现相同的任务。 + +下面,我们将阐述如何使用这个工具来实现博客应用中的CRUD操作。 + +安装 Gii +-------------- + +首先我们需要安装 Gii. 打开文件 `/wwwroot/blog/protected/config/main.php` ,添加如下代码: + +~~~ +[php] +return array( + ...... + 'import'=>array( + 'application.models.*', + 'application.components.*', + ), + + 'modules'=>array( + 'gii'=>array( + 'class'=>'system.gii.GiiModule', + 'password'=>'这儿设置一个密码', + ), + ), +); +~~~ + +上面的代码安装了一个名为 `gii` 的模块,这样我们就可以通过在浏览器中浏览如下URL来访问 Gii 模块: + +~~~ +http://www.example.com/blog/index.php?r=gii +~~~ + +我们将被提示要求输入一个密码。输入我们前面在 `/wwwroot/blog/protected/config/main.php` 中设置的密码,我们将看到一个页面,它列出了所有可用的代码生成工具。 + +> Note|注意: 上述代码在生产环境中应当移除。代码生成工具只应当用于开发环境。 + + +创建模型 +--------------- + +首先我们需要为每个数据表创建一个[模型(Model)](http://www.yiiframework.com/doc/guide/basics.model) 类。模型类会使我们可以通过一种直观的、面向对象的风格访问数据库。稍后我们将会看到这一点。 + +点击 `Model Generator` 链接开始使用模型创建工具。 + +在 `Model Generator` 页中,在`Table Name`一栏输入 `tbl_user` (用户表的名字),然后按下 `Preview` 按钮。一个预览表将显示在我们面前。我们可以点击表格中的链接来预览要生成的代码。如果一切OK,我们可以按下 `Generate` 按钮来生成代码并将其保存在一个文件中。 + +> Info|信息: 由于代码生成器需要保存生成的代码到文件,它要求Web服务器进程必须拥有对相应文件的创建和修改权限。为简单起见,我们可以赋予Web服务器进程对整个 `/wwwroot/blog` 目录的写权限。注意这只在开发机器上使用 `Gii` 时会用到。 + +对剩余的其他表重复同样的步骤,包括 `tbl_post`, `tbl_comment`, `tbl_tag` 和 `tbl_lookup`。 + +> Tip|提示: 我们还可以在 `Table Name` 栏中输入一个星号 '\*' 。这样就可以通过一次点击就对 *所有的* 数据表生成相应的模型类。 + +通过这一步,我们就有了如下新创建的文件: + + * `models/User.php` 包含了继承自 [CActiveRecord] 的 `User` 类,可用于访问 `tbl_user` 数据表; + * `models/Post.php` 包含了继承自 [CActiveRecord] 的 `Post` 类,可用于访问 `tbl_post` 数据表; + * `models/Tag.php` 包含了继承自 [CActiveRecord] 的 `Tag` 类,可用于访问 `tbl_tag` 数据表; + * `models/Comment.php` 包含了继承自 [CActiveRecord] 的 `Comment` 类,可用于访问 `tbl_comment` 数据表; + * `models/Lookup.php` 包含了继承自 [CActiveRecord] 的 `Lookup` 类,可用于访问 `tbl_lookup` 数据表; + + +实现 CRUD 操作 +---------------------------- + +模型类建好之后,我们就可以使用 `Crud Generator` 来创建为这些模型实现CRUD操作的代码了。我们将对 `Post` 和 `Comment` 模型执行此操作。 + +在 `Crud Generator` 页面中,`Model Class` 一栏输入 `Post` (就是我们刚创建的 Post 模型的名字) ,然后按下 `Preview` 按钮。我们会看到有很多文件将被创建。按下 `Generate` 按钮来创建它们。 + +对 `Comment` 模型重复同样的步骤。 + +让我们看一下通过CRUD生成器生成的这些文件。所有的文件都创建在了 `/wwwroot/blog/protected` 目录中。为方便起见,我们把它们分组为 [控制器(Controller)](http://www.yiiframework.com/doc/guide/basics.controller) 文件和 [视图(View)](http://www.yiiframework.com/doc/guide/basics.view) 文件: + + - 控制器文件: + * `controllers/PostController.php` 包含负责所有CRUD操作的 `PostController` 控制器类; + * `controllers/CommentController.php` 包含负责所有CRUD操作的 `CommentController` 控制器类; + + - 视图文件: + * `views/post/create.php` 一个视图文件,用于显示创建新日志的 HTML 表单; + * `views/post/update.php` 一个视图文件,用于显示更新日志的 HTML 表单; + * `views/post/view.php` 一个视图文件,用于显示一篇日志的详情; + * `views/post/index.php` 一个视图文件,用于显示日志列表; + * `views/post/admin.php` 一个视图文件,用于在一个带有管理员命令的表格中显示日志; + * `views/post/_form.php` 一个插入 `views/post/create.php` 和 `views/post/update.php` 的局部视图文件。它显示用于收集日志信息的HTML表单; + * `views/post/_view.php` 一个在 `views/post/index.php` 中使用的局部视图文件。它显示单篇日志的摘要信息; + * `views/post/_search.php` 一个在 `views/post/admin.php` 中使用的局部视图文件。它显示一个搜索表单; + * 还有为评论创建的一系列相似的文件。 + + +测试 +------- + +我们可以通过访问如下网址测试我们刚生成的代码所实现的功能: + +~~~ +http://www.example.com/blog/index.php?r=post +http://www.example.com/blog/index.php?r=comment +~~~ + +注意,由代码生成器实现的日志和评论功能是完全相互独立的。并且,当创建一个新的日志或评论时,我们必须输入如 `author_id` 和 `create_time` 这些信息,而在现实应用中这些应当由程序自动设置。别担心。我们将在下一个阶段中修正这些问题。现在呢,这个模型已经包含了大多数我们需要在博客应用中实现的功能,我们应该对此感到满意了 ^_^。 + + +为了更好地理解这些文件是如何使用的,我们在下面列出了当显示一个日志列表时发生的工作流程。 + + 0. 用户请求访问这个 URL `http://www.example.com/blog/index.php?r=post`; + 1. [入口脚本](http://www.yiiframework.com/doc/guide/basics.entry) 被Web服务器执行,它创建并实例化了一个 [应用](http://www.yiiframework.com/doc/guide/basics.application) 实例来处理此请求; + 2. 应用创建并执行了 `PostController` 实例; + 3. `PostController` 实例通过调用它的 `actionIndex()` 方法执行了 `index` 动作。注意,如果用户没有在URL中指定执行一个动作,则 `index` 就是默认的动作; + 4. `actionIndex()` 方法查询数据库,带回最新的日志列表; + 5. `actionIndex()` 方法使用日志数据渲染 `index` 视图。 + +
$Id: prototype.scaffold.txt 2258 2010-07-12 14:13:50Z alexander.makarow $
\ No newline at end of file diff --git a/docs/blog/zh_cn/prototype.summary.txt b/docs/blog/zh_cn/prototype.summary.txt index fa1e5f5a7..2135a0bd0 100644 --- a/docs/blog/zh_cn/prototype.summary.txt +++ b/docs/blog/zh_cn/prototype.summary.txt @@ -1,21 +1,21 @@ -总结 -======= - -我们已经完成了阶段1。来总结一下目前为止我们所完成的工作: - - 1. 我们确定了完整的需求; - 2. 我们安装了Yii框架; - 3. 我们创建了一个程序骨架; - 4. 我们设计并创建了博客数据库; - 5. 我们修改了应用配置,添加了数据库连接; - 6. 我们为日志和评论生成了实现CRUD操作的代码; - 7. 我们修改了验证方法以实现通过 `tbl_user` 表验证身份; - -对一个新项目来说,大部分时间将花在对程序骨架的第1至4步操作上。 - -虽然 `gii` 工具生成的代码可以对数据库实现完整的 CRUD 操作,但它在实际应用中常需要做一些修改。鉴于此,在下面的两个阶段中,我们的工作就是自定义生成的日志及评论的 CRUD 代码,使他们达到我们一开始的需求。 - -总体来说,我们首先要修改 [模型](http://www.yiiframework.com/doc/guide/basics.model) 类,添加适当的 [验证](http://www.yiiframework.com/doc/guide/form.model#declaring-validation-rules) 规则并声明 [相关的对象](http://www.yiiframework.com/doc/guide/database.arr#declaring-relationship)。然后我们要为每个CRUD操作修改其 [控制器动作](http://www.yiiframework.com/doc/guide/basics.controller) 和 [视图](http://www.yiiframework.com/doc/guide/basics.view) 代码。 - - +总结 +======= + +我们已经完成了阶段1。来总结一下目前为止我们所完成的工作: + + 1. 我们确定了完整的需求; + 2. 我们安装了Yii框架; + 3. 我们创建了一个程序骨架; + 4. 我们设计并创建了博客数据库; + 5. 我们修改了应用配置,添加了数据库连接; + 6. 我们为日志和评论生成了实现CRUD操作的代码; + 7. 我们修改了验证方法以实现通过 `tbl_user` 表验证身份; + +对一个新项目来说,大部分时间将花在对程序骨架的第1至4步操作上。 + +虽然 `gii` 工具生成的代码可以对数据库实现完整的 CRUD 操作,但它在实际应用中常需要做一些修改。鉴于此,在下面的两个阶段中,我们的工作就是自定义生成的日志及评论的 CRUD 代码,使他们达到我们一开始的需求。 + +总体来说,我们首先要修改 [模型](http://www.yiiframework.com/doc/guide/basics.model) 类,添加适当的 [验证](http://www.yiiframework.com/doc/guide/form.model#declaring-validation-rules) 规则并声明 [相关的对象](http://www.yiiframework.com/doc/guide/database.arr#declaring-relationship)。然后我们要为每个CRUD操作修改其 [控制器动作](http://www.yiiframework.com/doc/guide/basics.controller) 和 [视图](http://www.yiiframework.com/doc/guide/basics.view) 代码。 + +
$Id: prototype.summary.txt 2333 2010-08-24 21:11:55Z mdomba $
\ No newline at end of file diff --git a/docs/blog/zh_cn/start.design.txt b/docs/blog/zh_cn/start.design.txt index 07444db55..dbc622f1d 100644 --- a/docs/blog/zh_cn/start.design.txt +++ b/docs/blog/zh_cn/start.design.txt @@ -1,53 +1,53 @@ -整体设计 -============== - -基于需求分析,我们决定为我们的博客应用使用如下数据表存储持久数据: - - * `tbl_user` 存储用户信息,包括用户名和密码。 - * `tbl_post` 存储博客日志信息。它由如下几列组成: - - `title`: 必填项,日志的标题; - - `content`: 必填项,日志的内容,使用 [Markdown 格式](http://daringfireball.net/projects/markdown/syntax); - - `status`: 必填项,日志的状态,可以是以下值之一: - * 1, 表示日志在草稿箱里,对外不可见; - * 2, 表示日志已经对外发布; - * 3, 表示日志已经过期,在日志列表中不可见(但仍然可以单独访问)。 - - `tags`: 可选项,用于对日志归类的一个以逗号分隔的词语列表。 - * `tbl_comment` 存储日志评论信息,每条评论关联于一篇日志,主要包含如下几列: - - `name`: 必填项, 作者名字; - - `email`: 必填项, 作者 Email; - - `website`: 可选项, 作者网站的 URL; - - `content`: 必填项, 纯文本格式的评论内容; - - `status`: 必填项, 评论状态,用于表示日志是(2)否(1)已通过审核。 - * `tbl_tag` 存储日志Tag使用频率信息,用于实现标签云效果。此表主要包含以下几列: - - `name`: 必填项, 唯一的Tag名字; - - `frequency`: 必填项,Tag出现在日志中的次数。 - * `tbl_lookup` 存储通用查找信息。它本质上是一个整型数字和文本字符的映射。前者是我们的代码中的数据表现,后者是相应的对最终用户的表现。例如,我们使用整数1表示草稿日志,而使用字符串“草稿”把此状态显示给最终用户。此表主要包含以下几列: - - `name`: 数据项的文本表现形式,用于显示给最终用户; - - `code`: 数据项的整数表现形式; - - `type`: 数据项的类型; - - `position`: 同类数据项中,数据项相对于其他数据项的显示顺序。 - - -如下的实体-关系(ER)图展示了上述表的表结构和他们之间的关系。 - -![博客数据库实体-关系图](schema.png) - - -上述ER图相应的完整SQL语句可以在 [博客演示](http://www.yiiframework.com/demos/blog/) 中找到。在我们的安装包中,它们位于 `/wwwroot/yii/demos/blog/protected/data/schema.sqlite.sql`。 - - - -> Info|信息: 我们对所有表和列的命名使用了小写字母。这是因为不同的 DBMS 通常有不同的大小写敏感处理方式,我们通过这种方式来避免这种问题。 -> -> 我们同时对所有的表使用了 `tbl_` 前缀。这出于两个目的。第一,此前缀对这些表提供了一个命名空间以使他们和同一数据库中的其他表共存,此情况常出现在在共享的主机环境中,一个数据库常被多个应用使用。第二,使用表前缀减少了表名中出现DBMS保留字的可能。 - - -我们把博客应用的开发划分为如下几个阶段: - - * 阶段 1: 创建一个博客系统的原型。它应该包括大多数所需的功能。 - * 阶段 2: 完善日志管理功能。包括日志的创建,日志列表,日志显示,日志更新和删除。 - * 阶段 3: 完善评论管理功能。包括评论创建,评论列表,审核,更新以及日志评论的删除。 - * 阶段 4: 实现 Portlets。它包括用户菜单,登录,标签云和最新评论Portlets。 - * 阶段 5: 最终调试和部署。 - +整体设计 +============== + +基于需求分析,我们决定为我们的博客应用使用如下数据表存储持久数据: + + * `tbl_user` 存储用户信息,包括用户名和密码。 + * `tbl_post` 存储博客日志信息。它由如下几列组成: + - `title`: 必填项,日志的标题; + - `content`: 必填项,日志的内容,使用 [Markdown 格式](http://daringfireball.net/projects/markdown/syntax); + - `status`: 必填项,日志的状态,可以是以下值之一: + * 1, 表示日志在草稿箱里,对外不可见; + * 2, 表示日志已经对外发布; + * 3, 表示日志已经过期,在日志列表中不可见(但仍然可以单独访问)。 + - `tags`: 可选项,用于对日志归类的一个以逗号分隔的词语列表。 + * `tbl_comment` 存储日志评论信息,每条评论关联于一篇日志,主要包含如下几列: + - `name`: 必填项, 作者名字; + - `email`: 必填项, 作者 Email; + - `website`: 可选项, 作者网站的 URL; + - `content`: 必填项, 纯文本格式的评论内容; + - `status`: 必填项, 评论状态,用于表示日志是(2)否(1)已通过审核。 + * `tbl_tag` 存储日志Tag使用频率信息,用于实现标签云效果。此表主要包含以下几列: + - `name`: 必填项, 唯一的Tag名字; + - `frequency`: 必填项,Tag出现在日志中的次数。 + * `tbl_lookup` 存储通用查找信息。它本质上是一个整型数字和文本字符的映射。前者是我们的代码中的数据表现,后者是相应的对最终用户的表现。例如,我们使用整数1表示草稿日志,而使用字符串“草稿”把此状态显示给最终用户。此表主要包含以下几列: + - `name`: 数据项的文本表现形式,用于显示给最终用户; + - `code`: 数据项的整数表现形式; + - `type`: 数据项的类型; + - `position`: 同类数据项中,数据项相对于其他数据项的显示顺序。 + + +如下的实体-关系(ER)图展示了上述表的表结构和他们之间的关系。 + +![博客数据库实体-关系图](schema.png) + + +上述ER图相应的完整SQL语句可以在 [博客演示](http://www.yiiframework.com/demos/blog/) 中找到。在我们的安装包中,它们位于 `/wwwroot/yii/demos/blog/protected/data/schema.sqlite.sql`。 + + + +> Info|信息: 我们对所有表和列的命名使用了小写字母。这是因为不同的 DBMS 通常有不同的大小写敏感处理方式,我们通过这种方式来避免这种问题。 +> +> 我们同时对所有的表使用了 `tbl_` 前缀。这出于两个目的。第一,此前缀对这些表提供了一个命名空间以使他们和同一数据库中的其他表共存,此情况常出现在在共享的主机环境中,一个数据库常被多个应用使用。第二,使用表前缀减少了表名中出现DBMS保留字的可能。 + + +我们把博客应用的开发划分为如下几个阶段: + + * 阶段 1: 创建一个博客系统的原型。它应该包括大多数所需的功能。 + * 阶段 2: 完善日志管理功能。包括日志的创建,日志列表,日志显示,日志更新和删除。 + * 阶段 3: 完善评论管理功能。包括评论创建,评论列表,审核,更新以及日志评论的删除。 + * 阶段 4: 实现 Portlets。它包括用户菜单,登录,标签云和最新评论Portlets。 + * 阶段 5: 最终调试和部署。 +
$Id: start.design.txt 1677 2010-01-07 20:29:26Z qiang.xue $
\ No newline at end of file diff --git a/docs/blog/zh_cn/start.requirements.txt b/docs/blog/zh_cn/start.requirements.txt index c54b52482..b2873751c 100644 --- a/docs/blog/zh_cn/start.requirements.txt +++ b/docs/blog/zh_cn/start.requirements.txt @@ -1,27 +1,27 @@ -需求分析 -===================== - -我们要开发的博客系统是一个单用户系统。系统的所有者可以执行以下操作: - - * 登录和退出 - * 创建,更新,删除日志 - * 发布,撤销发布,存档日志 - * 审核和删除评论 - -其他的访客则可以执行以下操作: - - * 阅读日志 - * 创建评论 - -此系统的额外需求包括: - - * 系统的首页应显示最新的帖子列表。 - * 如果页面中有超过10篇日志,应该以分页的方式显示。 - * 系统应该在显示日志的同时显示此日志的评论。 - * 系统应可以按照指定的Tag列出相应的日志。 - * 系统应展示一个可以表明标签使用频率的标签云。 - * 系统应展示一个最新评论列表。 - * 系统应可以更换主题。 - * 系统应使用 SEO 友好的 URL 。 - +需求分析 +===================== + +我们要开发的博客系统是一个单用户系统。系统的所有者可以执行以下操作: + + * 登录和退出 + * 创建,更新,删除日志 + * 发布,撤销发布,存档日志 + * 审核和删除评论 + +其他的访客则可以执行以下操作: + + * 阅读日志 + * 创建评论 + +此系统的额外需求包括: + + * 系统的首页应显示最新的帖子列表。 + * 如果页面中有超过10篇日志,应该以分页的方式显示。 + * 系统应该在显示日志的同时显示此日志的评论。 + * 系统应可以按照指定的Tag列出相应的日志。 + * 系统应展示一个可以表明标签使用频率的标签云。 + * 系统应展示一个最新评论列表。 + * 系统应可以更换主题。 + * 系统应使用 SEO 友好的 URL 。 +
$Id: start.requirements.txt 683 2009-02-16 05:20:17Z qiang.xue $
\ No newline at end of file diff --git a/docs/guide/de/quickstart.apache-nginx-config.txt b/docs/guide/de/quickstart.apache-nginx-config.txt index f3e98ffc4..79ecf5f7b 100644 --- a/docs/guide/de/quickstart.apache-nginx-config.txt +++ b/docs/guide/de/quickstart.apache-nginx-config.txt @@ -1,94 +1,94 @@ -Apache- und Nginx-Konfigurationen -================================= - -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 -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: - -~~~ -RewriteEngine on - -# Zugriff auf sog. dotfiles verhindern (.htaccess, .svn, .git, etc.) -RedirectMatch 403 /\..*$ - -# Existierende Verzeichnisse oder Dateien direkt ausliefern -RewriteCond %{REQUEST_FILENAME} !-f -RewriteCond %{REQUEST_FILENAME} !-d - -# ansonsten zu index.php weiterleiten -RewriteRule . index.php -~~~ - - -Nginx ------ - -Man kann Yii auch mit [Nginx](http://wiki.nginx.org/) und PHP mit [FPM -SAPI](http://php.net/install.fpm) verwenden. Hier eine beispielhafte -Host-Konfiguration. Sie legt das Startscript fest und leitet alle Anfragen an -nicht existente Dateien an Yii um. Damit erhält man lesbare URLs. - -~~~ -server { - set $host_path "/www/mysite"; - access_log /www/mysite/log/access.log main; - - server_name mysite; - root $host_path/htdocs; - set $yii_bootstrap "index.php"; - - charset utf-8; - - location / { - index index.html $yii_bootstrap; - try_files $uri $uri/ /$yii_bootstrap?$args; - } - - location ~ ^/(protected|framework|themes/\w+/views) { - deny all; - } - - # Vermeidet die verarbeitung von statischen, nicht existenten Dateien - location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { - try_files $uri =404; - } - - # Leitet PHP Scripts an den FastCGI server unter 127.0.0.1:9000 weiter - # - location ~ \.php { - fastcgi_split_path_info ^(.+\.php)(.*)$; - - # Yii soll Aufrufe von nicht existierenden PHP-Dateien abfangen - set $fsn /$yii_bootstrap; - if (-f $document_root$fastcgi_script_name){ - set $fsn $fastcgi_script_name; - } - - fastcgi_pass 127.0.0.1:9000; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fsn; - - # PATH_INFO und PATH_TRANSLATED müssen nicht angegeben werden, - # sind aber in RFC 3875 für CGI definiert: - fastcgi_param PATH_INFO $fastcgi_path_info; - fastcgi_param PATH_TRANSLATED $document_root$fsn; - } - - # Zugriff auf sog. dotfiles verhindern (.htaccess, .svn, .git, etc.) - location ~ /\. { - deny all; - access_log off; - log_not_found off; - } -} -~~~ - -Mit dieser Konfiguration kann man dann in der `php.ini` `cgi.fix_pathinfo=0` -setzen, um unnötige `stat()`-Aufrufe des Systems zu vermeiden. - +Apache- und Nginx-Konfigurationen +================================= + +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 +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: + +~~~ +RewriteEngine on + +# Zugriff auf sog. dotfiles verhindern (.htaccess, .svn, .git, etc.) +RedirectMatch 403 /\..*$ + +# Existierende Verzeichnisse oder Dateien direkt ausliefern +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d + +# ansonsten zu index.php weiterleiten +RewriteRule . index.php +~~~ + + +Nginx +----- + +Man kann Yii auch mit [Nginx](http://wiki.nginx.org/) und PHP mit [FPM +SAPI](http://php.net/install.fpm) verwenden. Hier eine beispielhafte +Host-Konfiguration. Sie legt das Startscript fest und leitet alle Anfragen an +nicht existente Dateien an Yii um. Damit erhält man lesbare URLs. + +~~~ +server { + set $host_path "/www/mysite"; + access_log /www/mysite/log/access.log main; + + server_name mysite; + root $host_path/htdocs; + set $yii_bootstrap "index.php"; + + charset utf-8; + + location / { + index index.html $yii_bootstrap; + try_files $uri $uri/ /$yii_bootstrap?$args; + } + + location ~ ^/(protected|framework|themes/\w+/views) { + deny all; + } + + # Vermeidet die verarbeitung von statischen, nicht existenten Dateien + location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { + try_files $uri =404; + } + + # Leitet PHP Scripts an den FastCGI server unter 127.0.0.1:9000 weiter + # + location ~ \.php { + fastcgi_split_path_info ^(.+\.php)(.*)$; + + # Yii soll Aufrufe von nicht existierenden PHP-Dateien abfangen + set $fsn /$yii_bootstrap; + if (-f $document_root$fastcgi_script_name){ + set $fsn $fastcgi_script_name; + } + + fastcgi_pass 127.0.0.1:9000; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fsn; + + # PATH_INFO und PATH_TRANSLATED müssen nicht angegeben werden, + # sind aber in RFC 3875 für CGI definiert: + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param PATH_TRANSLATED $document_root$fsn; + } + + # Zugriff auf sog. dotfiles verhindern (.htaccess, .svn, .git, etc.) + location ~ /\. { + deny all; + access_log off; + log_not_found off; + } +} +~~~ + +Mit dieser Konfiguration kann man dann in der `php.ini` `cgi.fix_pathinfo=0` +setzen, um unnötige `stat()`-Aufrufe des Systems zu vermeiden. + diff --git a/docs/guide/fr/quickstart.installation.txt b/docs/guide/fr/quickstart.installation.txt index c61e25412..f3ca40e8d 100644 --- a/docs/guide/fr/quickstart.installation.txt +++ b/docs/guide/fr/quickstart.installation.txt @@ -1,29 +1,29 @@ -Installation -============ - -L'installation du framework Yii se fait en deux temps : - - 1. Télécharger le framework Yii sur [yiiframework.com](http://www.yiiframework.com/). - 2. Décompresser l'archive dans un répertoire du serveur Web. - -> Tip|Astuce : Il n'est pas obligatoire d'installer Yii dans un répertoire accessible du Web. -Une application Yii a un seul point d'entrée, qui est généralement le seul fichier qu'il soit -nécessaire d'exposer aux utilisateurs. Les autres scripts PHP, y compris ceux de Yii, devraient -être protégés, car ils pourraient être utilisés à mauvais escient. - -Configuration nécessaire ------------------------- - -Après avoir installé Yii, vous pouvez vérifier que le serveur supporte tous les pré-requis -nécessaires au fonctionnement de Yii. Vous pouvez le faire en accédant au script de test -de la configuration du serveur à l'URL : - -~~~ -http://hostname/path/to/yii/requirements/index.php -~~~ - -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. - -
$Id: quickstart.installation.txt 1907 $
+Installation +============ + +L'installation du framework Yii se fait en deux temps : + + 1. Télécharger le framework Yii sur [yiiframework.com](http://www.yiiframework.com/). + 2. Décompresser l'archive dans un répertoire du serveur Web. + +> Tip|Astuce : Il n'est pas obligatoire d'installer Yii dans un répertoire accessible du Web. +Une application Yii a un seul point d'entrée, qui est généralement le seul fichier qu'il soit +nécessaire d'exposer aux utilisateurs. Les autres scripts PHP, y compris ceux de Yii, devraient +être protégés, car ils pourraient être utilisés à mauvais escient. + +Configuration nécessaire +------------------------ + +Après avoir installé Yii, vous pouvez vérifier que le serveur supporte tous les pré-requis +nécessaires au fonctionnement de Yii. Vous pouvez le faire en accédant au script de test +de la configuration du serveur à l'URL : + +~~~ +http://hostname/path/to/yii/requirements/index.php +~~~ + +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. + +
$Id: quickstart.installation.txt 1907 $
diff --git a/docs/guide/he/basics.application.txt b/docs/guide/he/basics.application.txt index 260c47965..ec3fde2e7 100644 --- a/docs/guide/he/basics.application.txt +++ b/docs/guide/he/basics.application.txt @@ -1,160 +1,160 @@ -אפליקציה -=========== - -האפליקציה מהווה עיבוד ראשוני של הבקשות אשר נכנסות למערכת. המטרה העיקרית שלה היא לקבל את בקשת המשתמש ולהפנות אותה לקונטרולר המתאים להמשך עיבוד הנתונים. בנוסף היא משמשת לחלק המרכזי של כל ההגדרות באפליקציה. מסיבה זו,האפליקציה נקראת גם בשם 'קונטרולר-ראשי'. - -האפליקציה נוצרת על ידי [סקריפט הכניסה הראשי](/doc/guide/basics.entry). -ניתן לגשת לכל אובייקט וערך באפליקציה בעזרת [Yii::app()|YiiBase::app]. - - -הגדרות האפליקציה -------------------------- - -כברירת מחדל, אפליקציה היא אובייקט של [CWebApplication]. להתאמה אישית, אנחנו בדרך כלל מספקים קובץ הגדרות (או מערך) כדי לאתחל את המאפיינים של קובץ/מערך ההגדרות בעת יצירת האפליקציה. דרך נוספת להתאמה אישית של האפליקציה היא על ידי הרחבת [CWebApplication]. - -ההגדרות הם מערך של אלמנטים בפורמט של מפתח=>ערך. כל מפתח מייצג את שמו של המאפיין באובייקט של האפליקציה, וכל ערך את ערך ברירת המחדל של אותו מאפיין. לדוגמא, ההגדרות הבאות מגדירות את [שם האפליקציה|CApplication::name] ו [קונטרולר ברירת המחדל|CWebApplication::defaultController] של האפליקציה. - -~~~ -[php] -array( - 'name'=>'Yii Framework', - 'defaultController'=>'site', -) -~~~ - -בדרך כלל אנחנו שומרים את קובץ ההגדרות של האפליקציה בקובץ PHP נפרד (לדוגמא `protected/config/main.php`). בתוך הקובץ אנחנו מחזירים את מערך ההגדרות בצורה הבאה, - -~~~ -[php] -return array(...); -~~~ - -כדי ליישם את ההגדרות, אנחנו מעבירים את שם קובץ ההגדרות כפרמטר לקונסטרקטור (constructor) של האפליקציה, או ל [Yii::createWebApplication()] כמו הדוגמא הבאה, שבדרך כלל נעשית ב [סקריפט הכניסה הראשי](/doc/guide/basics.entry). - -~~~ -[php] -$app=Yii::createWebApplication($configFile); -~~~ - -> Tip|טיפ: אם ההגדרות של האפליקציה הם מורכבות, ניתן לפצל אותם לכמה קבצים, כל אחד מחזיר חלק אחר ממערך ההגדרות הכולל. לאחר מכן, בקובץ ההגדרות הראשי של האפליקציה ניתן להשתמש בפונקצית ה `include` של PHP כדי להוסיף את שאר קבצי ההגדרות ולאחד אותם למערך הגדרות אחד כולל. - -ספרית הבסיס של האפליקציה --------------------------- - -ספרית הבסיס של האפליקציה מתיחחסת לתיקיה הראשית אשר מכילה את כל קבצי ה PHP והמידע של המערכת, הרגישים מבחינת אבטחה. כברירת מחדל, זוהי תת תיקיה בשם `protected` אשר נמצאת תחת התיקיה אשר מכילה את סקריפט הכניסה הראשי. ניתן לשנותה על ידי הגדרת המאפיין [basePath|CWebApplication::basePath] [בהגדרות האפליקציה](#application-configuration). - -התוכן תחת התיקיה הראשית של האפליקציה צריך להיות מוגן מגישה וצפייה ישירה ועקיפה על ידי משתמשים. בעזרת [Apache HTTP](http://httpd.apache.org/), ניתן לבצע זאת בצורה פשוטה על ידי הוספת קובץ `htaccess.` תחת התיקיה הראשית. הקובץ `htaccess.` אמור להכיל את הקוד הבא, - -~~~ -deny from all -~~~ - -רכיב האפליקציה ---------------------- - -הפונקציונליות של האפליקציה ניתנת להתאמה אישית בצורה קלה בעזרת הארכיטקטורה הגמישה של רכיב זה. האפליקציה מנהלת סט של רכיבי-אפליקציה, כל אחד מיישם אפשרויות מסויימות. לדוגמא, האפליקציה נעזרת ברכיבים [CUrlManager] ו [CHttpRequest] כדי לקבל את בקשת המשתמש ולנתב אותה למקום המתאים. - -על ידי הגדרת ערך [הרכיבים|CApplication::components] באפליקציה, ניתן להגדיר בצורה אישית את הערכים והמחלקה של כל רכיב אשר משתמשים בו באפליקציה. לדוגמא, ניתן להגדיר את הרכיב [CMemCache] בצורה כזו שיעבוד מול כמה שרתי memcache כדי לשמור תוכן במטמון, - -~~~ -[php] -array( - ...... - 'components'=>array( - ...... - 'cache'=>array( - 'class'=>'CMemCache', - 'servers'=>array( - array('host'=>'server1', 'port'=>11211, 'weight'=>60), - array('host'=>'server2', 'port'=>11211, 'weight'=>40), - ), - ), - ), -) -~~~ - -בקוד המוצג למעלה, אנחנו מוסיפים את האלמנט `cache` למערך של ה `components`. האלמנט `cache` הצהיר שהמחלקה של הרכיב הזה הינה `CMemCache` והערך של `servers` (שרתים) צריך להיות מוגדר כפי שהוא מוצג בקוד למעלה. - -כדי לגשת לרכיב באפליקציה, יש להשתמש ב `Yii::app()->ComponentID`, כש `ComponentID` מתייחס לשם היחודי של הרכיב (לדוגמא `Yii::app()->cache`). - -ניתן לכבות כל רכיב באפליקציה על ידי הגדרת הערך `enabled` ל false בהגדרות של אותו רכיב. ברגע שננסה לגשת לרכיב שהוא לא פעיל נקבל את הערך השווה ל Null. - -> Tip|טיפ: כברירת מחדל, רכיבים באפליקציה נוצרים בעת בקשתם. זה אומר שרכיב באפליקציה יכול לא להווצר כלל אם לא קראו לו במהלך הבקשה של המשתמש. כתוצאה מכך, הביצועים הכללים של האפליקציה לא מושפעים גם אם האפליקציה מוגדרת עם הרבה רכיבים. חלק מהרכיבים באפליקציה (לדוגמא [CLogRouter]) צריכים להווצר בכל מקרה בין אם קראו לאותו רכיב או לא. כדי לבצע זאת, רשום את המזהה היחודי שלהם במאפיין [preload|CApplication::preload] (טעינה מראש) של האפליקציה. - - -רכיבים מרכזיים באפליקציה ---------------------------- - -Yii מגדירה מראש סט של רכיבים מרכזיים באפליקציה כדי לספק אפשרויות משותפות בקרב אפליקציות ווב. לדוגמא, הרכיב [request|CWebApplication::request] מעבד את בקשת המשתמש ומאפשר שימוש במידע כגון קישורים, עוגיות. על ידי הגדרת המאפיינים של רכיבים בסיסיים אלו, ניתן לשנות את אופי ההתנהלות של Yii בכל היבט כמעט. - -למטה מופיעים הרכיבים הבסיסיים שמוגדרים מראש על ידי [CWebApplication]. - - -- [assetManager|CWebApplication::assetManager]: [CAssetManager] - -מנהל פרסום של קבצי הנכסים הפרטיים (בדרך כלל קבצי JS, CSS ותמונות). - -- [authManager|CWebApplication::authManager]: [CAuthManager] - ניהול בקרת גישות - -- [cache|CApplication::cache]: [CCache] - מספק פונקציונליות לניהול מטמון. הערה, הינך חייב לציין את שם המחלקה (לדוגמא -[CMemCache], [CDbCache]). אחרת, יוחזר ערך השווה ל Null ברגע שתנסה לגשת לרכיב זה. - -- [clientScript|CWebApplication::clientScript]: [CClientScript] - -ניהול סקריפטים בצד הלקוח (קבצי CSS ו JS). - -- [coreMessages|CApplication::coreMessages]: [CPhpMessageSource] - -מספק תרגום של משפטי המקור אשר מוגדרים במערכת הפנימית של Yii. - -- [db|CApplication::db]: [CDbConnection] - מספק את החיבור של מסד הנתונים. הערה, הינך חייב להגדיר את המאפיין -[connectionString|CDbConnection::connectionString] כדי להשתמש ברכיב זה. - -- [errorHandler|CApplication::errorHandler]: [CErrorHandler] - טיפול בשגיאות PHP ושגיאות חריגות אחרות שלא נתפסו. - -- [format|CApplication::format]: [CFormatter] - מעבד מידע למטרת תצוגה בלבד. אפשרות זאת קיימת מגרסא 1.1.0. - -- [messages|CApplication::messages]: [CPhpMessageSource] - מספק ניהול תרגומים של האפליקציה עצמה. - -- [request|CWebApplication::request]: [CHttpRequest] - מספק מידע הקשור לבקשת המשתמש. - -- [securityManager|CApplication::securityManager]: [CSecurityManager] - -מספק שירותים הקשורים לאבטחת מידע, כגון יצירת HASH והצפנה. - -- [session|CWebApplication::session]: [CHttpSession] - מאפשר שימוש ב SESSIONS. - -- [statePersister|CApplication::statePersister]: [CStatePersister] - -מאפשר ניהול של מידע אשר שמור באופן קבוע. - -- [urlManager|CWebApplication::urlManager]: [CUrlManager] - מאפשר עיבוד של קישורים ויצירתם. - -- [user|CWebApplication::user]: [CWebUser] - מייצג את זהות המשתמש הנוכחי. - -- [themeManager|CWebApplication::themeManager]: [CThemeManager] - ניהול תבניות. - - -מחזור החיים של האפליקציה ----------------------- - -בעת טיפול בבקשת המשתמש, אפליקציה תעבור במחזור החיים הבא: - -0. אתחול האפליקציה בעזרת [CApplication::preinit]; - -1. הגדרת מחלקת הטעינה האוטומטית ומחלקת טיפול בשגיאות; - -2. רישום רכיבים בסיסים של האפליקציה; - -3. טעינת הגדרות האפליקציה; - -4. אתחול האפליקציה בעזרת [CApplication::init] -- רישום התנהלות האפליקציה; -- טעינת רכיבים סטטיים של האפליקציה; - -5. העלאת אירוע [onBeginRequest|CApplication::onBeginRequest]; - -6. תהליך עיבוד בקשת המשתמש: -- עיבוד הבקשה; -- יצירת קונטרולר; -- הרצת קונטרולר; - -7. העלאת אירוע [onEndRequest|CApplication::onEndRequest]; - - +אפליקציה +=========== + +האפליקציה מהווה עיבוד ראשוני של הבקשות אשר נכנסות למערכת. המטרה העיקרית שלה היא לקבל את בקשת המשתמש ולהפנות אותה לקונטרולר המתאים להמשך עיבוד הנתונים. בנוסף היא משמשת לחלק המרכזי של כל ההגדרות באפליקציה. מסיבה זו,האפליקציה נקראת גם בשם 'קונטרולר-ראשי'. + +האפליקציה נוצרת על ידי [סקריפט הכניסה הראשי](/doc/guide/basics.entry). +ניתן לגשת לכל אובייקט וערך באפליקציה בעזרת [Yii::app()|YiiBase::app]. + + +הגדרות האפליקציה +------------------------- + +כברירת מחדל, אפליקציה היא אובייקט של [CWebApplication]. להתאמה אישית, אנחנו בדרך כלל מספקים קובץ הגדרות (או מערך) כדי לאתחל את המאפיינים של קובץ/מערך ההגדרות בעת יצירת האפליקציה. דרך נוספת להתאמה אישית של האפליקציה היא על ידי הרחבת [CWebApplication]. + +ההגדרות הם מערך של אלמנטים בפורמט של מפתח=>ערך. כל מפתח מייצג את שמו של המאפיין באובייקט של האפליקציה, וכל ערך את ערך ברירת המחדל של אותו מאפיין. לדוגמא, ההגדרות הבאות מגדירות את [שם האפליקציה|CApplication::name] ו [קונטרולר ברירת המחדל|CWebApplication::defaultController] של האפליקציה. + +~~~ +[php] +array( + 'name'=>'Yii Framework', + 'defaultController'=>'site', +) +~~~ + +בדרך כלל אנחנו שומרים את קובץ ההגדרות של האפליקציה בקובץ PHP נפרד (לדוגמא `protected/config/main.php`). בתוך הקובץ אנחנו מחזירים את מערך ההגדרות בצורה הבאה, + +~~~ +[php] +return array(...); +~~~ + +כדי ליישם את ההגדרות, אנחנו מעבירים את שם קובץ ההגדרות כפרמטר לקונסטרקטור (constructor) של האפליקציה, או ל [Yii::createWebApplication()] כמו הדוגמא הבאה, שבדרך כלל נעשית ב [סקריפט הכניסה הראשי](/doc/guide/basics.entry). + +~~~ +[php] +$app=Yii::createWebApplication($configFile); +~~~ + +> Tip|טיפ: אם ההגדרות של האפליקציה הם מורכבות, ניתן לפצל אותם לכמה קבצים, כל אחד מחזיר חלק אחר ממערך ההגדרות הכולל. לאחר מכן, בקובץ ההגדרות הראשי של האפליקציה ניתן להשתמש בפונקצית ה `include` של PHP כדי להוסיף את שאר קבצי ההגדרות ולאחד אותם למערך הגדרות אחד כולל. + +ספרית הבסיס של האפליקציה +-------------------------- + +ספרית הבסיס של האפליקציה מתיחחסת לתיקיה הראשית אשר מכילה את כל קבצי ה PHP והמידע של המערכת, הרגישים מבחינת אבטחה. כברירת מחדל, זוהי תת תיקיה בשם `protected` אשר נמצאת תחת התיקיה אשר מכילה את סקריפט הכניסה הראשי. ניתן לשנותה על ידי הגדרת המאפיין [basePath|CWebApplication::basePath] [בהגדרות האפליקציה](#application-configuration). + +התוכן תחת התיקיה הראשית של האפליקציה צריך להיות מוגן מגישה וצפייה ישירה ועקיפה על ידי משתמשים. בעזרת [Apache HTTP](http://httpd.apache.org/), ניתן לבצע זאת בצורה פשוטה על ידי הוספת קובץ `htaccess.` תחת התיקיה הראשית. הקובץ `htaccess.` אמור להכיל את הקוד הבא, + +~~~ +deny from all +~~~ + +רכיב האפליקציה +--------------------- + +הפונקציונליות של האפליקציה ניתנת להתאמה אישית בצורה קלה בעזרת הארכיטקטורה הגמישה של רכיב זה. האפליקציה מנהלת סט של רכיבי-אפליקציה, כל אחד מיישם אפשרויות מסויימות. לדוגמא, האפליקציה נעזרת ברכיבים [CUrlManager] ו [CHttpRequest] כדי לקבל את בקשת המשתמש ולנתב אותה למקום המתאים. + +על ידי הגדרת ערך [הרכיבים|CApplication::components] באפליקציה, ניתן להגדיר בצורה אישית את הערכים והמחלקה של כל רכיב אשר משתמשים בו באפליקציה. לדוגמא, ניתן להגדיר את הרכיב [CMemCache] בצורה כזו שיעבוד מול כמה שרתי memcache כדי לשמור תוכן במטמון, + +~~~ +[php] +array( + ...... + 'components'=>array( + ...... + 'cache'=>array( + 'class'=>'CMemCache', + 'servers'=>array( + array('host'=>'server1', 'port'=>11211, 'weight'=>60), + array('host'=>'server2', 'port'=>11211, 'weight'=>40), + ), + ), + ), +) +~~~ + +בקוד המוצג למעלה, אנחנו מוסיפים את האלמנט `cache` למערך של ה `components`. האלמנט `cache` הצהיר שהמחלקה של הרכיב הזה הינה `CMemCache` והערך של `servers` (שרתים) צריך להיות מוגדר כפי שהוא מוצג בקוד למעלה. + +כדי לגשת לרכיב באפליקציה, יש להשתמש ב `Yii::app()->ComponentID`, כש `ComponentID` מתייחס לשם היחודי של הרכיב (לדוגמא `Yii::app()->cache`). + +ניתן לכבות כל רכיב באפליקציה על ידי הגדרת הערך `enabled` ל false בהגדרות של אותו רכיב. ברגע שננסה לגשת לרכיב שהוא לא פעיל נקבל את הערך השווה ל Null. + +> Tip|טיפ: כברירת מחדל, רכיבים באפליקציה נוצרים בעת בקשתם. זה אומר שרכיב באפליקציה יכול לא להווצר כלל אם לא קראו לו במהלך הבקשה של המשתמש. כתוצאה מכך, הביצועים הכללים של האפליקציה לא מושפעים גם אם האפליקציה מוגדרת עם הרבה רכיבים. חלק מהרכיבים באפליקציה (לדוגמא [CLogRouter]) צריכים להווצר בכל מקרה בין אם קראו לאותו רכיב או לא. כדי לבצע זאת, רשום את המזהה היחודי שלהם במאפיין [preload|CApplication::preload] (טעינה מראש) של האפליקציה. + + +רכיבים מרכזיים באפליקציה +--------------------------- + +Yii מגדירה מראש סט של רכיבים מרכזיים באפליקציה כדי לספק אפשרויות משותפות בקרב אפליקציות ווב. לדוגמא, הרכיב [request|CWebApplication::request] מעבד את בקשת המשתמש ומאפשר שימוש במידע כגון קישורים, עוגיות. על ידי הגדרת המאפיינים של רכיבים בסיסיים אלו, ניתן לשנות את אופי ההתנהלות של Yii בכל היבט כמעט. + +למטה מופיעים הרכיבים הבסיסיים שמוגדרים מראש על ידי [CWebApplication]. + + +- [assetManager|CWebApplication::assetManager]: [CAssetManager] - +מנהל פרסום של קבצי הנכסים הפרטיים (בדרך כלל קבצי JS, CSS ותמונות). + +- [authManager|CWebApplication::authManager]: [CAuthManager] - ניהול בקרת גישות + +- [cache|CApplication::cache]: [CCache] - מספק פונקציונליות לניהול מטמון. הערה, הינך חייב לציין את שם המחלקה (לדוגמא +[CMemCache], [CDbCache]). אחרת, יוחזר ערך השווה ל Null ברגע שתנסה לגשת לרכיב זה. + +- [clientScript|CWebApplication::clientScript]: [CClientScript] - +ניהול סקריפטים בצד הלקוח (קבצי CSS ו JS). + +- [coreMessages|CApplication::coreMessages]: [CPhpMessageSource] - +מספק תרגום של משפטי המקור אשר מוגדרים במערכת הפנימית של Yii. + +- [db|CApplication::db]: [CDbConnection] - מספק את החיבור של מסד הנתונים. הערה, הינך חייב להגדיר את המאפיין +[connectionString|CDbConnection::connectionString] כדי להשתמש ברכיב זה. + +- [errorHandler|CApplication::errorHandler]: [CErrorHandler] - טיפול בשגיאות PHP ושגיאות חריגות אחרות שלא נתפסו. + +- [format|CApplication::format]: [CFormatter] - מעבד מידע למטרת תצוגה בלבד. אפשרות זאת קיימת מגרסא 1.1.0. + +- [messages|CApplication::messages]: [CPhpMessageSource] - מספק ניהול תרגומים של האפליקציה עצמה. + +- [request|CWebApplication::request]: [CHttpRequest] - מספק מידע הקשור לבקשת המשתמש. + +- [securityManager|CApplication::securityManager]: [CSecurityManager] - +מספק שירותים הקשורים לאבטחת מידע, כגון יצירת HASH והצפנה. + +- [session|CWebApplication::session]: [CHttpSession] - מאפשר שימוש ב SESSIONS. + +- [statePersister|CApplication::statePersister]: [CStatePersister] - +מאפשר ניהול של מידע אשר שמור באופן קבוע. + +- [urlManager|CWebApplication::urlManager]: [CUrlManager] - מאפשר עיבוד של קישורים ויצירתם. + +- [user|CWebApplication::user]: [CWebUser] - מייצג את זהות המשתמש הנוכחי. + +- [themeManager|CWebApplication::themeManager]: [CThemeManager] - ניהול תבניות. + + +מחזור החיים של האפליקציה +---------------------- + +בעת טיפול בבקשת המשתמש, אפליקציה תעבור במחזור החיים הבא: + +0. אתחול האפליקציה בעזרת [CApplication::preinit]; + +1. הגדרת מחלקת הטעינה האוטומטית ומחלקת טיפול בשגיאות; + +2. רישום רכיבים בסיסים של האפליקציה; + +3. טעינת הגדרות האפליקציה; + +4. אתחול האפליקציה בעזרת [CApplication::init] +- רישום התנהלות האפליקציה; +- טעינת רכיבים סטטיים של האפליקציה; + +5. העלאת אירוע [onBeginRequest|CApplication::onBeginRequest]; + +6. תהליך עיבוד בקשת המשתמש: +- עיבוד הבקשה; +- יצירת קונטרולר; +- הרצת קונטרולר; + +7. העלאת אירוע [onEndRequest|CApplication::onEndRequest]; + +
$Id: basics.application.txt 1601 2009-12-18 04:31:19Z qiang.xue $
\ No newline at end of file diff --git a/docs/guide/he/basics.component.txt b/docs/guide/he/basics.component.txt index df03d03eb..14e783c9b 100644 --- a/docs/guide/he/basics.component.txt +++ b/docs/guide/he/basics.component.txt @@ -1,137 +1,137 @@ -רכיב -========= - -האפליקציות ב Yii נבנות תחת רכיבים שהינם אובייקטים אשר נכתבים על פי מפרט מסויים. רכיב הינו אובייקט של [CComponent] או מחלקות אשר נמצאות תחתיו. שימוש ברכיב בדרך כלל משמש כדי לגשת למאפיינים של אותו רכיב והעלאה וניהול של אירועים המוגדרים בו. המחלקה הבסיסית (הראשית) [CComponent] מציינת כיצד להגדיר את המאפיינים והאירועים. - -מאפייני רכיב ------------------- - -מאפיין רכיב הוא כמו משתנה במחלקה. ניתן לקרוא את הערך שבו או להגדיר לו ערך חדש. לדוגמא, - -~~~ -[php] -$width=$component-»textWidth; // get the textWidth property -$component-»enableCaching=true; // set the enableCaching property -~~~ - -כדי להגדיר מאפיין רכיב, כל מה שצריך לעשות זה פשוט להגדיר משתנה במחלקה של אותו רכיב. למרות זאת, אפשרות יותר גמישה תיהיה על ידי הגדרת פונקציות `קריאה` ו `כתיבה` בצורה הבאה: - -~~~ -[php] -public function getTextWidth() -{ - return $this-»_textWidth; -} - -public function setTextWidth($value) -{ - $this-»_textWidth=$value; -} -~~~ - -הקוד המוצג למעלה מגדיר מאפיין הניתן לכתיבה בשם `textWidth` (השם אינו רגיש לאותיות גדולות-קטנות). בעת קריאה של מאפיין זה הפונקציה `getTextWidth` נכנסת לפעולה והערך שמוחזר ממנה הוא הערך של המשתנה במחלקה; באותו אופן, בעת שמירת ערך למאפיין זה הפונקציה `setTextWidth` נכנסת לפעולה. אם לא הוגדרה פונקציה לכתיבה של אותו מאפיין, המאפיין יהיה ניתן לקריאה-בלבד ובעת ניסיון לשנות את הערך שלו תזרק שגיאה. שימוש בפונקציות קריאה וכתיבה של משתנים/מאפיינים כדי לקרוא אותם ולהגדירם מאפשרת הרצת בדיקות ולוגיקות נוספות (לדוגמא ביצוע אימות נתונים, העלאת אירועים). - - -»Note|הערה: ישנו הבדל קטן בין מאפיין אשר מוגדר בעזרת פונקציות קריאה/כתיבה ומאפיין אשר מוגדר בעזרת משתנה במחלקה. מאפיין המוגדר בעזרת פונקציות שמו לא רגיש לאותיות גדולות-קטנות, לעומת משתנה במחלקה שהינו רגיש לאותיות גדולות-קטנות. - -אירועים ברכיב ---------------- - -אירועי רכיב הם תכונות מיוחדות הלוקחות פונקציות (הנקראות `event handlers`) בתור הערכים שלהם. צירוף (הקצאת) פונקציה לאירוע מסויים יגרום לפונקציה לרוץ אוטומטית במקומות בהם האירוע מתרחש. לכן, ההתנהלות של הרכיב יכולה להשתנות בצורה כזאת שלא ניתן לצפות בה מראש במהלך פיתוח הרכיב. - -אירוע ברכיב מוגדר על ידי הגדרת פונקציה ששמה מתחיל ב 'on'. כמו שמות מאפיינים המוגדרים בעזרת פונקציות קריאה/כתיבה, שמות האירועים אינם רגישים לאותיות גדולות-קטנות. הקוד הבא מגדיר אירוע `onClicked`: - -~~~ -[php] -public function onClicked($event) -{ - $this-»raiseEvent('onClicked', $event); -} -~~~ - -איפה ש `event$` הוא אובייקט של [CEvent] או מחלקות אשר נמצאות תחתיו המייצג את פרמטר האירוע. - -ניתן לצרף פונקציה לאירוע זה בצורה הבאה: - -~~~ -[php] -$component-»onClicked=$callback; -~~~ - -כש `callback$` מכוון לפונקצית PHP תקינה. פונקציה זו יכולה להיות פונקציה גלובלית או מתודה במחלקה. אם הוגדר שהיא הינה מתודה במחלקה, יש להגדיר את הערך כמערך כשהאלמנט הראשון הוא האובייקט של אותה מחלקה והשני הוא שם המתודה: - -~~~ -[php] -array($object,'methodName') -~~~ - -פונקציה המטפלת באירוע כלשהו צריכה להראות בדומה לקוד הבא: - -~~~ -[php] -function methodName($event) -{ - ...... -} -~~~ - -כש `event$` הוא משתנה המכיל מידע אודות האירוע שהועלה (הוא נוצר מהקריאה של `raiseEvent`). המשתנה `event$` הוא אובייקט של [CEvent] או מחלקות הנמצאות תחתיו. המינימום הוא שיכיל מידע של מי העלאה את האירוע. - -החל מגרסא 1.0.10, ניתן לנהל אירוע על ידי פונקציה אנונימית אשר נתמכת בגרסאת PHP 5.3 ומעלה, לדוגמא, - -~~~ -[php] -$component-»onClicked=function($event) { - ...... -} -~~~ - -אם נקרא ל `onClicked` עכשיו, האירוע `onClicked` יבוצע (בתוך הפונקציה `onClicked`), והפונקציה אשר מטפלת באירוע זה תקרא אוטומטית. - -ניתן לצרף כמה פונקציות לטיפול באותו אירוע. כשהאירוע יבוצע והפונקציות יקראו, הם יבוצעו בסדר שהם צורפו לאותו אירוע. אם אחד מהפונקציות המטפלות באירוע רוצה למנוע קריאה לשאר הפונקציות המטפלות באותו האירוע, ניתן להגדיר [$event-»handled|CEvent::handled] לערך השווה ל `true` ולא יקראו פונקציות נוספות לטיפול באירוע זה. - -ניהול הרכיב ------------------- - -החל מגרסא 1.0.2, לרכיב נוספה תמיכה ב [mixin](http://en.wikipedia.org/wiki/Mixin) וניתנת לצירוף בעזרת ניהול אחד או יותר. *ניהול* הוא אובייקט שניתן `להוריש` את המתודות שלו על ידי הרכיבים המצורפים אליו כדי לאסוף פונקציונליות במקום התמחות מסויימת (לדוגמא, הורשה רגילה של מחלקות). לרכיב ניתן לצרף כמה מחלקות ניהול ובכך להשיג `הורשת מחלקות מרובה`. - -מחלקות ניהוליות חייבות ליישם את הממשק [IBehavior]. רוב המחלקות הניהוליות מרחיבות תחת המחלקה הבסיסית [CBehavior]. אם מחלקה ניהולית צריכה להיות מצורפת [למודל](/doc/guide/basics.model), ניתן להרחיב אותה גם מ [CModelBehavior] או [CActiveRecordBehavior] אשר מיישמות אפשרויות נוספות ספציפיות למודלים. - -בכדי להשתמש במחלקה ניהולית, חובה לצרף אותה לרכיב קודם על ידי קריאה למתודה בשם [attach|IBehavior::attach]. לאחר מכן אנו קוראים למתודה של מחלקה ניהולית דרך הרכיב: - -~~~ -[php] -// $name uniquely identifies the behavior in the component -$component-»attachBehavior($name,$behavior); -// test() is a method of $behavior -$component-»test(); -~~~ - -ניתן לגשת למחלקה ניהולית מצורפת כמו למאפיין רגיל של הרכיב. לדוגמא, אם צרפנו מחלקה ניהולית בשם `tree` לרכיב, ניתן לקבל יחוס של אותה מחלקה על ידי שימוש ב: - -~~~ -[php] -$behavior=$component-»tree; -// equivalent to the following: -// $behavior=$component-»asa('tree'); -~~~ - -ניתן לכבות זמנית מחלקה מצורפת כדי שלא יהיה ניתן לגשת למתודות שלה דרך הרכיב. לדוגמא: - -~~~ -[php] -$component-»disableBehavior($name); -// the following statement will throw an exception -$component-»test(); -$component-»enableBehavior($name); -// it works now -$component-»test(); -~~~ - -ישנה אפשרות ששני מחלקות ניהוליות אשר מצורפות לאותו רכיב יכילו מתודות זהות בשמם. במקרה הזה, המתודה של המחלקה שצורפה קודם לכן תקבל זכות על פני השנייה. - -כשמשתמשים ביחד עם [אירועים](#component-event), מחלקות ניהוליות הרבה יותר שימושיות. מחלקה ניהולית, בעת צירופה לרכיב, יכולה לצרף כמה מתודות שלה לכמה אירועים ברכיב. על ידי ביצוע פעולה זו, המחלקה הניהולית מקבלת הזדמנות להתבונן או לשנות את רצף הביצועים הרציפים של הרכיב. - -החל מגרסא 1.1.0, ניתן לקרוא למאפיינים של מחלקה ניהולית דרך הרכיב אליו הם מצורפים. המאפיינים מכילים גם את המשתנים של המחלקה והמאפיינים אשר מוגדרים באמצעות פונקציות קריאה/כתיבה במחלקה הניהולית. לדוגמא, אם מחלקה ניהולית מכילה מאפיין בשם `xyz` והיא מצורפת לרכיב `a$`, אז נוכל להשתמש בביטוי `a-»xyz$` כדי לגשת למאפיין של המחלקה הניהולית המצורפת לרכיב. - +רכיב +========= + +האפליקציות ב Yii נבנות תחת רכיבים שהינם אובייקטים אשר נכתבים על פי מפרט מסויים. רכיב הינו אובייקט של [CComponent] או מחלקות אשר נמצאות תחתיו. שימוש ברכיב בדרך כלל משמש כדי לגשת למאפיינים של אותו רכיב והעלאה וניהול של אירועים המוגדרים בו. המחלקה הבסיסית (הראשית) [CComponent] מציינת כיצד להגדיר את המאפיינים והאירועים. + +מאפייני רכיב +------------------ + +מאפיין רכיב הוא כמו משתנה במחלקה. ניתן לקרוא את הערך שבו או להגדיר לו ערך חדש. לדוגמא, + +~~~ +[php] +$width=$component-»textWidth; // get the textWidth property +$component-»enableCaching=true; // set the enableCaching property +~~~ + +כדי להגדיר מאפיין רכיב, כל מה שצריך לעשות זה פשוט להגדיר משתנה במחלקה של אותו רכיב. למרות זאת, אפשרות יותר גמישה תיהיה על ידי הגדרת פונקציות `קריאה` ו `כתיבה` בצורה הבאה: + +~~~ +[php] +public function getTextWidth() +{ + return $this-»_textWidth; +} + +public function setTextWidth($value) +{ + $this-»_textWidth=$value; +} +~~~ + +הקוד המוצג למעלה מגדיר מאפיין הניתן לכתיבה בשם `textWidth` (השם אינו רגיש לאותיות גדולות-קטנות). בעת קריאה של מאפיין זה הפונקציה `getTextWidth` נכנסת לפעולה והערך שמוחזר ממנה הוא הערך של המשתנה במחלקה; באותו אופן, בעת שמירת ערך למאפיין זה הפונקציה `setTextWidth` נכנסת לפעולה. אם לא הוגדרה פונקציה לכתיבה של אותו מאפיין, המאפיין יהיה ניתן לקריאה-בלבד ובעת ניסיון לשנות את הערך שלו תזרק שגיאה. שימוש בפונקציות קריאה וכתיבה של משתנים/מאפיינים כדי לקרוא אותם ולהגדירם מאפשרת הרצת בדיקות ולוגיקות נוספות (לדוגמא ביצוע אימות נתונים, העלאת אירועים). + + +»Note|הערה: ישנו הבדל קטן בין מאפיין אשר מוגדר בעזרת פונקציות קריאה/כתיבה ומאפיין אשר מוגדר בעזרת משתנה במחלקה. מאפיין המוגדר בעזרת פונקציות שמו לא רגיש לאותיות גדולות-קטנות, לעומת משתנה במחלקה שהינו רגיש לאותיות גדולות-קטנות. + +אירועים ברכיב +--------------- + +אירועי רכיב הם תכונות מיוחדות הלוקחות פונקציות (הנקראות `event handlers`) בתור הערכים שלהם. צירוף (הקצאת) פונקציה לאירוע מסויים יגרום לפונקציה לרוץ אוטומטית במקומות בהם האירוע מתרחש. לכן, ההתנהלות של הרכיב יכולה להשתנות בצורה כזאת שלא ניתן לצפות בה מראש במהלך פיתוח הרכיב. + +אירוע ברכיב מוגדר על ידי הגדרת פונקציה ששמה מתחיל ב 'on'. כמו שמות מאפיינים המוגדרים בעזרת פונקציות קריאה/כתיבה, שמות האירועים אינם רגישים לאותיות גדולות-קטנות. הקוד הבא מגדיר אירוע `onClicked`: + +~~~ +[php] +public function onClicked($event) +{ + $this-»raiseEvent('onClicked', $event); +} +~~~ + +איפה ש `event$` הוא אובייקט של [CEvent] או מחלקות אשר נמצאות תחתיו המייצג את פרמטר האירוע. + +ניתן לצרף פונקציה לאירוע זה בצורה הבאה: + +~~~ +[php] +$component-»onClicked=$callback; +~~~ + +כש `callback$` מכוון לפונקצית PHP תקינה. פונקציה זו יכולה להיות פונקציה גלובלית או מתודה במחלקה. אם הוגדר שהיא הינה מתודה במחלקה, יש להגדיר את הערך כמערך כשהאלמנט הראשון הוא האובייקט של אותה מחלקה והשני הוא שם המתודה: + +~~~ +[php] +array($object,'methodName') +~~~ + +פונקציה המטפלת באירוע כלשהו צריכה להראות בדומה לקוד הבא: + +~~~ +[php] +function methodName($event) +{ + ...... +} +~~~ + +כש `event$` הוא משתנה המכיל מידע אודות האירוע שהועלה (הוא נוצר מהקריאה של `raiseEvent`). המשתנה `event$` הוא אובייקט של [CEvent] או מחלקות הנמצאות תחתיו. המינימום הוא שיכיל מידע של מי העלאה את האירוע. + +החל מגרסא 1.0.10, ניתן לנהל אירוע על ידי פונקציה אנונימית אשר נתמכת בגרסאת PHP 5.3 ומעלה, לדוגמא, + +~~~ +[php] +$component-»onClicked=function($event) { + ...... +} +~~~ + +אם נקרא ל `onClicked` עכשיו, האירוע `onClicked` יבוצע (בתוך הפונקציה `onClicked`), והפונקציה אשר מטפלת באירוע זה תקרא אוטומטית. + +ניתן לצרף כמה פונקציות לטיפול באותו אירוע. כשהאירוע יבוצע והפונקציות יקראו, הם יבוצעו בסדר שהם צורפו לאותו אירוע. אם אחד מהפונקציות המטפלות באירוע רוצה למנוע קריאה לשאר הפונקציות המטפלות באותו האירוע, ניתן להגדיר [$event-»handled|CEvent::handled] לערך השווה ל `true` ולא יקראו פונקציות נוספות לטיפול באירוע זה. + +ניהול הרכיב +------------------ + +החל מגרסא 1.0.2, לרכיב נוספה תמיכה ב [mixin](http://en.wikipedia.org/wiki/Mixin) וניתנת לצירוף בעזרת ניהול אחד או יותר. *ניהול* הוא אובייקט שניתן `להוריש` את המתודות שלו על ידי הרכיבים המצורפים אליו כדי לאסוף פונקציונליות במקום התמחות מסויימת (לדוגמא, הורשה רגילה של מחלקות). לרכיב ניתן לצרף כמה מחלקות ניהול ובכך להשיג `הורשת מחלקות מרובה`. + +מחלקות ניהוליות חייבות ליישם את הממשק [IBehavior]. רוב המחלקות הניהוליות מרחיבות תחת המחלקה הבסיסית [CBehavior]. אם מחלקה ניהולית צריכה להיות מצורפת [למודל](/doc/guide/basics.model), ניתן להרחיב אותה גם מ [CModelBehavior] או [CActiveRecordBehavior] אשר מיישמות אפשרויות נוספות ספציפיות למודלים. + +בכדי להשתמש במחלקה ניהולית, חובה לצרף אותה לרכיב קודם על ידי קריאה למתודה בשם [attach|IBehavior::attach]. לאחר מכן אנו קוראים למתודה של מחלקה ניהולית דרך הרכיב: + +~~~ +[php] +// $name uniquely identifies the behavior in the component +$component-»attachBehavior($name,$behavior); +// test() is a method of $behavior +$component-»test(); +~~~ + +ניתן לגשת למחלקה ניהולית מצורפת כמו למאפיין רגיל של הרכיב. לדוגמא, אם צרפנו מחלקה ניהולית בשם `tree` לרכיב, ניתן לקבל יחוס של אותה מחלקה על ידי שימוש ב: + +~~~ +[php] +$behavior=$component-»tree; +// equivalent to the following: +// $behavior=$component-»asa('tree'); +~~~ + +ניתן לכבות זמנית מחלקה מצורפת כדי שלא יהיה ניתן לגשת למתודות שלה דרך הרכיב. לדוגמא: + +~~~ +[php] +$component-»disableBehavior($name); +// the following statement will throw an exception +$component-»test(); +$component-»enableBehavior($name); +// it works now +$component-»test(); +~~~ + +ישנה אפשרות ששני מחלקות ניהוליות אשר מצורפות לאותו רכיב יכילו מתודות זהות בשמם. במקרה הזה, המתודה של המחלקה שצורפה קודם לכן תקבל זכות על פני השנייה. + +כשמשתמשים ביחד עם [אירועים](#component-event), מחלקות ניהוליות הרבה יותר שימושיות. מחלקה ניהולית, בעת צירופה לרכיב, יכולה לצרף כמה מתודות שלה לכמה אירועים ברכיב. על ידי ביצוע פעולה זו, המחלקה הניהולית מקבלת הזדמנות להתבונן או לשנות את רצף הביצועים הרציפים של הרכיב. + +החל מגרסא 1.1.0, ניתן לקרוא למאפיינים של מחלקה ניהולית דרך הרכיב אליו הם מצורפים. המאפיינים מכילים גם את המשתנים של המחלקה והמאפיינים אשר מוגדרים באמצעות פונקציות קריאה/כתיבה במחלקה הניהולית. לדוגמא, אם מחלקה ניהולית מכילה מאפיין בשם `xyz` והיא מצורפת לרכיב `a$`, אז נוכל להשתמש בביטוי `a-»xyz$` כדי לגשת למאפיין של המחלקה הניהולית המצורפת לרכיב. + «div class="revision"»$Id: basics.component.txt 1474 2009-10-18 21:13:52Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/basics.controller.txt b/docs/guide/he/basics.controller.txt index 225b8dfa6..74f35402b 100644 --- a/docs/guide/he/basics.controller.txt +++ b/docs/guide/he/basics.controller.txt @@ -1,168 +1,168 @@ -קונטרולר -========== - -`קונטרולר` הינו אובייקט של [CController] או מחלקות היורשות ממנו. הקונטרולר נוצר על ידי האפליקציה כהמשתמש מבקש אותו. - כשהקונטרולר רץ, הוא מבצע את הפעולה הנחוצה שבדרך כלל נעזרת במודלים ומציגה את התוכן בעזרת קבצי התצוגה. - `פעולה` (Action), בצורה הכי פשוטה שלו, הינו מתודה במחלקה של הקונטרולר אשר מתחילה ב `action`. - -לקונטרולר יש פעולה ברירת מחדל. - כשמשתמש אשר מבקש את הקונטרולר לא מגדיר איזה פעולה לבצע, פעולה ברירת המחדל היא זו שתרוץ. - כברירת מחדל, פעולת ברירת המחדל הינה `index`. ניתן לשנות זאת על ידי הגדרת המאפיין [CController::defaultAction]. - -למטה מוצג הקוד המינימלי כדי ליצור מחלקה לקונטרולר. מאחר ולא קיימים שום פעולות במחלקה במידה והקונטרולר יבוקש על ידי המשתמש תזרק שגיאה. - -~~~ -[php] -class SiteController extends CController -{ -} -~~~ - -ניתוב ------ - -קונטרולרים ופעולות מזוהים על ידי זיהוי יחודי. מזהה יחודי של קונטרולר הוא בפורמט של `path/to/xyz` אשר מכוונת לקובץ המחלקה של הקונטרולר `protected/controllers/path/to/xyzController.php`, כשהמזהה `xyz` יוחלף בשמות אמיתיים של קונטרולרים (לדוגמא `post` מכוון לקובץ `protected/controllers/PostController.php`). -מזהה יחודי של פעולה הינו שמה של המתודה במחלקה ללא הקידומת של `action`. לדוגמא, אם מחלקת הקונטרולר מכילה מתודה בשם `actionEdit`, המזהה היחודי של הפעולה יהיה `edit`. - -» Note|הערה: בגרסאות לפני 1.0.3, הפורמט של המזהה היחודי של הקונטרולר היה `path.to.xyz` במקום `path/to/xyz`. - -בקשת משתמש עבור קונטרולר ופעולה כלשהי מיוצגת כניתוב. - נתב נוצר על ידי שרשור של מזהה היחודי של הקונטרולר והמזהה היחודי של הפעולה מופרדים בסלאש (/). - לדוגמא, הנתב `post/edit` מכוון אל הקונטרולר `PostController` ולפעולה `edit`. - וכברירת מחדל הקישור `http://hostname/index.php?r=post/edit` יטען את הקונטרולר והפעולה הללו. - -» Note|הערה: כברירת מחדל, נתבים רגישים לאותיות גדולות-קטנות. - מגרסא 1.0.1 ומעלה, ניתן לבטל את הרגישות של הנתבים לאותיות גדולות-קטנות על ידי הגדרת [CUrlManager::caseSensitive] לערך השווה ל `false` בהגדרות של האפליקציה. - במצב שהנתבים לא רגישים לאותיות גדולות-קטנות, וודא שהינך עוקב אחר המוסכמות ששמות התיקיות המכילות קונטרולרים כתובים באותיות קטנות באנגלית, [מפת קונטרולרים|CWebApplication::controllerMap] ו [מפת פעולות|CController::actions] משתמשים בשמות מפתח באותיות קטנות ובאנגלית. - -מגרסא 1.0.3 ומעלה, אפליקציה יכולה להכיל [מודולים](/doc/guide/basics.module). הניתוב לפעולה בקונטרולר שונה בפורמט שלה ועכשיו היא בפורמט של `moduleID/controllerID/actionID`. למידע נוסף קרא אודות [מודולים](/doc/guide/basics.module). - -יצירת אובייקט הקונטרולר ------------------------- - -אובייקט הקונטרולר נוצר כש [CWebApplication] מטפל בבקשה הנכנסת. עם קבלת המזהה היחודי של הקונטרולר, האפליקציה תשתמש בחוקים הבאים כדי להחליט איזה קונטרולר לטעון ואיפה קובץ המחלקה שלו נמצא. - -- אם [CWebApplication::catchAllRequest] מוגדר, יווצר קונטרולר בהתבסס על המאפיין הזה, והאפליקציה תתעלם מהקונטרולר שהמשתמש ביקש. מאפיין זה נועד כדי לתפוס את כל בקשות המשתמש ולהכווין אותם אל מקום אחד, לדוגמא במצב של תחזוקה באתר יהיה צורך להציג עמוד אחד שמציג הודעת תחזוקה כלשהי. - -- אם המזהה היחודי של הקונטרולר נמצא ב [CWebApplication::controllerMap], ההגדרות של אותו קונטרולר שנמצא ישומשו והאובייקט יווצר בהתבסס על אותם ההגדרות. - -- אם המזהה היחודי הוא בפורמט של `path/to/xyz`, שם המחלקה של הקונטרולר יהיה `XyzController` והקובץ ימצא תחת `protected/controllers/path/to/XyzController.php`. לדוגמא, מזהה יחודי של קונטרולר בשם `admin/user` ינותב למחלקה `UserController` וקובץ המחלקה יהיה `protected/controllers/admin/UserController.php` אם קובץ המחלקה לא קיים, תזרק שגיאה 404 [CHttpException]. - -במקרה של שימוש במודולים (בגרסאות 1.0.3), התהליך המוצג למעלה שונה במקצת. ספציפית, האפליקציה תבדוק אם המזהה היחודי מכוון לקונטרולר בתוך מודול, במידה וכן, אובייקט של המודל יווצר קודם ולאחר מכן יווצר האובייקט של הקונטרולר. - -פעולה ------- - -כפי שצויין, ניתן להגדיר פעולה על ידי יצירת מתודה במחלקה של הקונטרולר כששמה מתחיל ב `action`. דרך יותר מתקדמת תיהיה על ידי יצירת מחלקה לפעולה ולבקש מהקונטרולר ליצור אובייקט שלה כשצריך. דרך זו מאפשרת שימוש חוזר בפעולות ויותר שימושית. - -כדי להגדיר פעולה בתור מחלקה יש לבצע את הפעולות הבאות: - -~~~ -[php] -class UpdateAction extends CAction -{ - public function run() - { - // place the action logic here - } -} -~~~ - -כדי שהקונטרולר יהיה מודע לפעולה זו, אנחנו דורסים את המתודה [actions|CController::actions] של מחלקת הקונטרולר שלנו: - -~~~ -[php] -class PostController extends CController -{ - public function actions() - { - return array( - 'edit'=»'application.controllers.post.UpdateAction', - ); - } -} -~~~ - -בקוד למעלה, אנחנו משתמשים בשם מקוצר `application.controllers.post.UpdateAction` כדי להגדיר שקובץ מחלקת הפעולה נמצא ב `protected/controllers/post/UpdateAction.php`. - -באמצעות כתיבת פעולות בתור מחלקות, ניתן לארגן את האפליקציה בצורה מודולרית. לדוגמא, ניתן להשתמש במבנה האפליקציה הבא כדי לארגן את כל הקונטרולרים: - -~~~ -protected/ - controllers/ - PostController.php - UserController.php - post/ - CreateAction.php - ReadAction.php - UpdateAction.php - user/ - CreateAction.php - ListAction.php - ProfileAction.php - UpdateAction.php -~~~ - -פילטרים ------- - -פילטר הינו חתיכת קוד אשר מוגדרת לרוץ לפני ו/או אחרי הרצה של פעולה בקונטרולר. לדוגמא, ניתן לבדוק בעזרת פילטר אם משתמש יכול לבצע את הפעולה אותה הוא מנסה להריץ; ניתן להריץ פילטר אשר בודק את משך זמן הרצת הפעולה לצורך קבלת משך זמן הרצת הפעולה. - -כל פעולה יכולה להכיל כמה פילטרים. הפילטרים רצים בסדר בהם הם רשומים ברשימת הפילטרים. פילטר יכול למנוע את ההרצה של פעולה כלשהי והרצה של פילטרים נוספים הבאים אחריה. - -ניתן להגדיר פילטר בתור מתודה במחלקת הקונטרולר. שם המתודה חייב להתחיל ב `method`. לדוגמא, הקיום של מתודת הפילטר בשם `filterAccessControl` מגדירה פילטר בשם `accessControl`. מתודת הפילטר צריכה להיות חתומה בפורמט של: - -~~~ -[php] -public function filterAccessControl($filterChain) -{ - // call $filterChain-»run() to continue filtering and action execution -} -~~~ - -כש `filterChain$` הוא אובייקט של [CFilterChain] אשר מייצג את רשימת הפילטרים המקושרת לפעולה שהתבקשה. בתוך המתודה של הפילטר ניתן לקרוא ל `filterChain-»run$` להמשך הרצת הפילטרים הבאים והרצת הפעולה עצמה. - -פילטר יכול להיות גם אובייקט של [CFilter] או מחלקות היורשות ממנו. הקוד הבא מגדיר מחלקת פילטר חדשה: - -~~~ -[php] -class PerformanceFilter extends CFilter -{ - protected function preFilter($filterChain) - { - // logic being applied before the action is executed - return true; // false if the action should not be executed - } - - protected function postFilter($filterChain) - { - // logic being applied after the action is executed - } -} -~~~ - -כדי לצרף פילטרים לפעולות, יש צורך בלדרוס את המתודה `CController::filters`. המתודה צריכה להחזיר מערך עם רשימת הפילטרים וההגדרות שלהם. לדוגמא, - -~~~ -[php] -class PostController extends CController -{ - ...... - public function filters() - { - return array( - 'postOnly + edit, create', - array( - 'application.filters.PerformanceFilter - edit, create', - 'unit'=»'second', - ), - ); - } -} -~~~ - -הקוד למעלה מגדיר שני פילטרים: `postOnly` ו `PerformanceFilter`. הפילטר `postOnly` הוא פילטר המבוסס על מתודה במחלקה הנוכחית (המתודה של הפילטר כבר מוגדרת ב [CController]); בזמן שהפילטר `PerformanceFilter` הוא מבוסס אובייקט. הנתיב המקוצר `application.filters.PerformanceFilter` מציין שהמחלקה לפילטר נמצאת תחת `protected/filters/PerformanceFilter`. אנחנו משתמשים במערך כדי להגדיר את הפילטר `PerformanceFilter` שיהיה ניתן ליצור אותו בעזרת אותם ערכים כמאפיינים של המחלקה של הפילטר. בדוגמא הזאת המאפיין `unit` יווצר ויוגדר לערך השווה ל `second` במחלקה `PerformanceFilter`. - -שימוש באופרטורים של פלוס ומינוס, ניתן להגדיר אילו פעולות הפילטר יצורף אליהם ואילו לא. בדוגמא למעלה, הפילטר `postOnly` יצורף אל הפעולות `edit` ו `create` , בזמן שהפילטר `PerformanceFilter` יצורף אל כל הפעולות **מלבד** `edit` ו `create`. אם בהגדרות הפילטר לא יוגדרו פלוס או מינוס, הפילטר יצורף אל כל הפעולות. - +קונטרולר +========== + +`קונטרולר` הינו אובייקט של [CController] או מחלקות היורשות ממנו. הקונטרולר נוצר על ידי האפליקציה כהמשתמש מבקש אותו. + כשהקונטרולר רץ, הוא מבצע את הפעולה הנחוצה שבדרך כלל נעזרת במודלים ומציגה את התוכן בעזרת קבצי התצוגה. + `פעולה` (Action), בצורה הכי פשוטה שלו, הינו מתודה במחלקה של הקונטרולר אשר מתחילה ב `action`. + +לקונטרולר יש פעולה ברירת מחדל. + כשמשתמש אשר מבקש את הקונטרולר לא מגדיר איזה פעולה לבצע, פעולה ברירת המחדל היא זו שתרוץ. + כברירת מחדל, פעולת ברירת המחדל הינה `index`. ניתן לשנות זאת על ידי הגדרת המאפיין [CController::defaultAction]. + +למטה מוצג הקוד המינימלי כדי ליצור מחלקה לקונטרולר. מאחר ולא קיימים שום פעולות במחלקה במידה והקונטרולר יבוקש על ידי המשתמש תזרק שגיאה. + +~~~ +[php] +class SiteController extends CController +{ +} +~~~ + +ניתוב +----- + +קונטרולרים ופעולות מזוהים על ידי זיהוי יחודי. מזהה יחודי של קונטרולר הוא בפורמט של `path/to/xyz` אשר מכוונת לקובץ המחלקה של הקונטרולר `protected/controllers/path/to/xyzController.php`, כשהמזהה `xyz` יוחלף בשמות אמיתיים של קונטרולרים (לדוגמא `post` מכוון לקובץ `protected/controllers/PostController.php`). +מזהה יחודי של פעולה הינו שמה של המתודה במחלקה ללא הקידומת של `action`. לדוגמא, אם מחלקת הקונטרולר מכילה מתודה בשם `actionEdit`, המזהה היחודי של הפעולה יהיה `edit`. + +» Note|הערה: בגרסאות לפני 1.0.3, הפורמט של המזהה היחודי של הקונטרולר היה `path.to.xyz` במקום `path/to/xyz`. + +בקשת משתמש עבור קונטרולר ופעולה כלשהי מיוצגת כניתוב. + נתב נוצר על ידי שרשור של מזהה היחודי של הקונטרולר והמזהה היחודי של הפעולה מופרדים בסלאש (/). + לדוגמא, הנתב `post/edit` מכוון אל הקונטרולר `PostController` ולפעולה `edit`. + וכברירת מחדל הקישור `http://hostname/index.php?r=post/edit` יטען את הקונטרולר והפעולה הללו. + +» Note|הערה: כברירת מחדל, נתבים רגישים לאותיות גדולות-קטנות. + מגרסא 1.0.1 ומעלה, ניתן לבטל את הרגישות של הנתבים לאותיות גדולות-קטנות על ידי הגדרת [CUrlManager::caseSensitive] לערך השווה ל `false` בהגדרות של האפליקציה. + במצב שהנתבים לא רגישים לאותיות גדולות-קטנות, וודא שהינך עוקב אחר המוסכמות ששמות התיקיות המכילות קונטרולרים כתובים באותיות קטנות באנגלית, [מפת קונטרולרים|CWebApplication::controllerMap] ו [מפת פעולות|CController::actions] משתמשים בשמות מפתח באותיות קטנות ובאנגלית. + +מגרסא 1.0.3 ומעלה, אפליקציה יכולה להכיל [מודולים](/doc/guide/basics.module). הניתוב לפעולה בקונטרולר שונה בפורמט שלה ועכשיו היא בפורמט של `moduleID/controllerID/actionID`. למידע נוסף קרא אודות [מודולים](/doc/guide/basics.module). + +יצירת אובייקט הקונטרולר +------------------------ + +אובייקט הקונטרולר נוצר כש [CWebApplication] מטפל בבקשה הנכנסת. עם קבלת המזהה היחודי של הקונטרולר, האפליקציה תשתמש בחוקים הבאים כדי להחליט איזה קונטרולר לטעון ואיפה קובץ המחלקה שלו נמצא. + +- אם [CWebApplication::catchAllRequest] מוגדר, יווצר קונטרולר בהתבסס על המאפיין הזה, והאפליקציה תתעלם מהקונטרולר שהמשתמש ביקש. מאפיין זה נועד כדי לתפוס את כל בקשות המשתמש ולהכווין אותם אל מקום אחד, לדוגמא במצב של תחזוקה באתר יהיה צורך להציג עמוד אחד שמציג הודעת תחזוקה כלשהי. + +- אם המזהה היחודי של הקונטרולר נמצא ב [CWebApplication::controllerMap], ההגדרות של אותו קונטרולר שנמצא ישומשו והאובייקט יווצר בהתבסס על אותם ההגדרות. + +- אם המזהה היחודי הוא בפורמט של `path/to/xyz`, שם המחלקה של הקונטרולר יהיה `XyzController` והקובץ ימצא תחת `protected/controllers/path/to/XyzController.php`. לדוגמא, מזהה יחודי של קונטרולר בשם `admin/user` ינותב למחלקה `UserController` וקובץ המחלקה יהיה `protected/controllers/admin/UserController.php` אם קובץ המחלקה לא קיים, תזרק שגיאה 404 [CHttpException]. + +במקרה של שימוש במודולים (בגרסאות 1.0.3), התהליך המוצג למעלה שונה במקצת. ספציפית, האפליקציה תבדוק אם המזהה היחודי מכוון לקונטרולר בתוך מודול, במידה וכן, אובייקט של המודל יווצר קודם ולאחר מכן יווצר האובייקט של הקונטרולר. + +פעולה +------ + +כפי שצויין, ניתן להגדיר פעולה על ידי יצירת מתודה במחלקה של הקונטרולר כששמה מתחיל ב `action`. דרך יותר מתקדמת תיהיה על ידי יצירת מחלקה לפעולה ולבקש מהקונטרולר ליצור אובייקט שלה כשצריך. דרך זו מאפשרת שימוש חוזר בפעולות ויותר שימושית. + +כדי להגדיר פעולה בתור מחלקה יש לבצע את הפעולות הבאות: + +~~~ +[php] +class UpdateAction extends CAction +{ + public function run() + { + // place the action logic here + } +} +~~~ + +כדי שהקונטרולר יהיה מודע לפעולה זו, אנחנו דורסים את המתודה [actions|CController::actions] של מחלקת הקונטרולר שלנו: + +~~~ +[php] +class PostController extends CController +{ + public function actions() + { + return array( + 'edit'=»'application.controllers.post.UpdateAction', + ); + } +} +~~~ + +בקוד למעלה, אנחנו משתמשים בשם מקוצר `application.controllers.post.UpdateAction` כדי להגדיר שקובץ מחלקת הפעולה נמצא ב `protected/controllers/post/UpdateAction.php`. + +באמצעות כתיבת פעולות בתור מחלקות, ניתן לארגן את האפליקציה בצורה מודולרית. לדוגמא, ניתן להשתמש במבנה האפליקציה הבא כדי לארגן את כל הקונטרולרים: + +~~~ +protected/ + controllers/ + PostController.php + UserController.php + post/ + CreateAction.php + ReadAction.php + UpdateAction.php + user/ + CreateAction.php + ListAction.php + ProfileAction.php + UpdateAction.php +~~~ + +פילטרים +------ + +פילטר הינו חתיכת קוד אשר מוגדרת לרוץ לפני ו/או אחרי הרצה של פעולה בקונטרולר. לדוגמא, ניתן לבדוק בעזרת פילטר אם משתמש יכול לבצע את הפעולה אותה הוא מנסה להריץ; ניתן להריץ פילטר אשר בודק את משך זמן הרצת הפעולה לצורך קבלת משך זמן הרצת הפעולה. + +כל פעולה יכולה להכיל כמה פילטרים. הפילטרים רצים בסדר בהם הם רשומים ברשימת הפילטרים. פילטר יכול למנוע את ההרצה של פעולה כלשהי והרצה של פילטרים נוספים הבאים אחריה. + +ניתן להגדיר פילטר בתור מתודה במחלקת הקונטרולר. שם המתודה חייב להתחיל ב `method`. לדוגמא, הקיום של מתודת הפילטר בשם `filterAccessControl` מגדירה פילטר בשם `accessControl`. מתודת הפילטר צריכה להיות חתומה בפורמט של: + +~~~ +[php] +public function filterAccessControl($filterChain) +{ + // call $filterChain-»run() to continue filtering and action execution +} +~~~ + +כש `filterChain$` הוא אובייקט של [CFilterChain] אשר מייצג את רשימת הפילטרים המקושרת לפעולה שהתבקשה. בתוך המתודה של הפילטר ניתן לקרוא ל `filterChain-»run$` להמשך הרצת הפילטרים הבאים והרצת הפעולה עצמה. + +פילטר יכול להיות גם אובייקט של [CFilter] או מחלקות היורשות ממנו. הקוד הבא מגדיר מחלקת פילטר חדשה: + +~~~ +[php] +class PerformanceFilter extends CFilter +{ + protected function preFilter($filterChain) + { + // logic being applied before the action is executed + return true; // false if the action should not be executed + } + + protected function postFilter($filterChain) + { + // logic being applied after the action is executed + } +} +~~~ + +כדי לצרף פילטרים לפעולות, יש צורך בלדרוס את המתודה `CController::filters`. המתודה צריכה להחזיר מערך עם רשימת הפילטרים וההגדרות שלהם. לדוגמא, + +~~~ +[php] +class PostController extends CController +{ + ...... + public function filters() + { + return array( + 'postOnly + edit, create', + array( + 'application.filters.PerformanceFilter - edit, create', + 'unit'=»'second', + ), + ); + } +} +~~~ + +הקוד למעלה מגדיר שני פילטרים: `postOnly` ו `PerformanceFilter`. הפילטר `postOnly` הוא פילטר המבוסס על מתודה במחלקה הנוכחית (המתודה של הפילטר כבר מוגדרת ב [CController]); בזמן שהפילטר `PerformanceFilter` הוא מבוסס אובייקט. הנתיב המקוצר `application.filters.PerformanceFilter` מציין שהמחלקה לפילטר נמצאת תחת `protected/filters/PerformanceFilter`. אנחנו משתמשים במערך כדי להגדיר את הפילטר `PerformanceFilter` שיהיה ניתן ליצור אותו בעזרת אותם ערכים כמאפיינים של המחלקה של הפילטר. בדוגמא הזאת המאפיין `unit` יווצר ויוגדר לערך השווה ל `second` במחלקה `PerformanceFilter`. + +שימוש באופרטורים של פלוס ומינוס, ניתן להגדיר אילו פעולות הפילטר יצורף אליהם ואילו לא. בדוגמא למעלה, הפילטר `postOnly` יצורף אל הפעולות `edit` ו `create` , בזמן שהפילטר `PerformanceFilter` יצורף אל כל הפעולות **מלבד** `edit` ו `create`. אם בהגדרות הפילטר לא יוגדרו פלוס או מינוס, הפילטר יצורף אל כל הפעולות. + «div class="revision"»$Id: basics.controller.txt 1264 2009-07-21 19:34:55Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/basics.convention.txt b/docs/guide/he/basics.convention.txt index f7a6e3139..68fc9057d 100644 --- a/docs/guide/he/basics.convention.txt +++ b/docs/guide/he/basics.convention.txt @@ -1,90 +1,90 @@ -מוסכמות -=========== - -Yii מעדיפה מוסכמות על הגדרות. לעקוב אחר המוסכמות וניתן יהיה לבנות אפליקציות מתוחכמות ללא צורך בכתיבה וניהול של הגדרות מסובכות. כמובן, עדיין יהיה ניתן להגדיר את כל ההגדרות של Yii כשצריך. - -למטה מוצגים המוסכמות המומלצים כשמתכנתים בעזרת Yii. לנוחות, אנחנו מניחים ש `webRoot` מנותב לתיקיה הראשית בה מותקנת האפליקציה של Yii. - -קישורים ---- - -כברירת מחדל, Yii מזהה קישורים בפורמט הבא: - -~~~ -http://hostname/index.php?r=ControllerID/ActionID -~~~ - -הפרמטר `r` מכוון אל [נתב](/doc/guide/basics.controller#route) אשר ניתן לעבד אותו לקונטרולר ופעולה. במידה והמזהה היחודי של הפעולה `ActionID` חסר, הקונטרולר יריץ את פעולת ברירת המחדל המוגדרת באמצעות [CController::defaultAction]; במידה והמזהה היחודי של הקונטרולר `controllerID` חסר (או הפרמטר `r` חסר) האפליקציה תריץ את הקונטרולר המוגדר כברירת מחדל בעזרת [CWebApplication::defaultController]. - -בעזרת [CUrlManager], ניתן ליצור קישורים יותר ידידותיים גם ל SEO, כמו לדוגמא `http://hostname/ControllerID/ActionID.html`. ניתן לקרוא בהרחבה אודות אפשרות זו [בניהול קישורים](/doc/guide/topics.url). - - -קוד ----- - -Yii ממליצה לקרוא למשתנים, פונקציות ומחלקות בפורמט של camelCase (פורמט אשר רושמים ראשונה במילה כאות גדולה ולחבר אותם ללא רווחים). שמות משתנים ופונקציות צריכות שהמילה הראשונה בשם שלהם תיהיה כולה באותיות קטנות (מילה ראשונה באותיות קטנות כל שאר המילים האות הראשונה במילה כאות גדולה ולחברם ביחד ללא רווחים), כדי להבדיל אותם משמות מחלקה (לדוגמא `$basePath`, `runController()`, `LinkPager`). למשתנים פרטיים (המוגדרים כ `private`) במחלקה מומלץ להוסיף קו תחתון (_) לשמם לדוגמא (`$_actionList`). - -מאחר והתמיכה במרחבי שמות לא נתמכת בגרסאות PHP מתחת ל 5.3.0, מומלץ לקרוא למחלקות בשמות יחודיים כדי למנוע סתירות בין שמות מחלקות. מסיבה זו, כל המחלקות הבסיסיות של Yii מתחילות באות 'C'. - -חוק ברזל לשמות מחלקות הוא שהם צריכים להסתיים במילה `Controller`. המזהה היחודי של הקונטרולר מוגדר כשם המחלקה באותיות קטנות בלבד וללא המילה `Controller`. לדוגמא, מחלקת הקונטרולר `PageController` תקבל את המזהה היחודי `page`. חוק זה מוודא שהאפליקציה תיהיה מאובטחת יותר. בנוסף הוא מציג את הקישורים בצורה נקייה יותר (לדוגמא `/index.php?r=page/index` במקום `/index.php?r=PageController/index` ). - -הגדרות -------------- - -הגדרות הם מערך זוגות של מפתחות וערכים. כל מפתח מייצג את שמו של המאפיין של הרכיב שצריך להגדיר, וכל ערך מייצג את הערך הראשוני של אותו מאפיין. לדוגמא , `array('name'=»'My -application', 'basePath'=»'./protected') מאתחל את המאפיינים `name` ו `basePath` באפליקציה בהתאם לערכים המוגדרים במערך. - -כל המאפיינים (הציבוריים - Public) באובייקט ניתנים להגדרה. אם לא הוגדרו, המאפיינים יקבלו את ערך ברירת המחדל שלהם. כשמגדירים מאפיין כלשהו, רצוי לקרוא בדוקומנטציה אודות המאפיין כדי להגדיר את הערך המתאים. - -קובץ ----- - -מוסכמות להגדרת שמות ושימוש בקבצים תלויים בסוג שלהם. - -שמות קבצי מחלקות צריכות להקרא על פי המחלקה הציבורית הנמצאת בהם. לדוגמא, שם הקובץ של המחלקה [CController] הוא `CController.php`. מחלקה ציבורית הינה מחלקה שניתן להשתמש בה בכל מחלקה אחרת. רצוי שכל קובץ מחלקה יכיל מחלקה ציבורית אחת לכל היותר. מחלקות פרטיות (מחלקות אשר ניתן להשתמש בהם במחלקה ציבורית אחת בלבד) יכול להיות בתוך אותו קובץ של המחלקה הציבורית שבה היא משומשת. - -קבצי תצוגה צריכים להקרא על פי שמות התצוגה. לדוגמא, שם קובץ התצוגה `index` יהיה `index.php`. קובץ תצוגה הוא קובץ PHP המכיל בעיקר קוד PHP ו HTML למטרת תצוגה בעיקר. - -קבצי הגדרות יכולים להקרא באופן שרירותי. קובץ הגדרות הוא קובץ PHP המכיל ומחזיר מערך של הגדרות. - -תיקיה ---------- - -Yii מניח כברירת מחדל כמה תיקיות אשר הוא משתמש למטרות שונות. את כל אחד ניתן להגדיר בצורה שונה במידה וצריך. - -- `WebRoot/protected`: זוהי [התיקיה הראשית](/doc/guide/basics.application#application-base-directory) של האפליקציה המכילה קבצים וסקריפטים של PHP רגישים. Yii מגדירה שם מקוצר ברירת מחדל בשם `application` אשר מנותבת לתיקיה זו. תיקיה זו וכל מה שבתוכה צריכה להיות מוגנת מפני גישה של משתמשי קצה. ניתן להגדירה בעזרת [CWebApplication::basePath]. - -- `WebRoot/protected/runtime`: תיקיה זו מכילה קבצים זמניים פרטיים הנוצרים בזמן הרצת האפליקציה. תיקיה זו חייבת לקבל הרשאות כתיבה על ידי השרת. ניתן להגדירה בעזרת [CApplication::runtimePath]. - -- `WebRoot/protected/extensions`: תיקיה זו מכילה את כל התוספים למערכת. ניתן להגדירה בעזרת [CApplication::extensionPath]. - -- `WebRoot/protected/modules`: תיקיה זו מכילה את כל [המודולים](/doc/guide/basics.module) של האפליקציה, כל אחת מייצגת כתת-תיקיה. - -- `WebRoot/protected/controllers`: תיקיה זו מכילה את כל הקונטרולרים של האפליקציה. ניתן להגדירה בעזרת [CWebApplication::controllerPath]. - -- `WebRoot/protected/views`: תיקיה זו מכילה את כל קבצי התצוגה, הכוללים את קבצי התצוגה של הקונטרולרים, קבצי תצוגת התבניות, וקבצי תצוגה מערכתיים. ניתן להגדירה בעזרת [CWebApplication::viewPath]. - -- `WebRoot/protected/views/ControllerID`: תיקיה זו מכילה את קבצי התצוגה לקונטרולר יחיד. במקרה הזה `ControllerID` מייצג את המזהה היחודי של הקונטרולר. ניתן להגדיר זאת על ידי [CController::viewPath]. - -- `WebRoot/protected/views/layouts`: תיקיה זו מכילה את כל קבצי התבניות של האפליקציה. ניתן להגדירה בעזרת [CWebApplication::layoutPath]. - -- `WebRoot/protected/views/system`: תיקיה זו מכילה את כל קבצי התצוגה המערכתיים כמו התצוגה של שגיאות ולוגים של המערכת. ניתן להגדיר זאת על ידי [CWebApplication::systemViewPath]. - -- `WebRoot/assets` : תיקיה זו מכילה את קבצי הנכסים (JS, CSS) , קבצים אלו הם פרטיים אשר ניתן לפרסמם כדי שמשתמשי קצה יוכלו לגשת אליהם. תיקיה זו צריכה לקבל הרשאות כתיבה על ידי השרת כדי לתפקד כראוי. ניתן להגדירה בעזרת [CAssetManager::basePath]. - -- `WebRoot/themes` : תיקיה זו מכילה עיצובים שונים אשר ניתן להשתמש בהם באפליקציה. כל תת תיקיה מייצגת עיצוב בודד ששמו הוא שם התיקיה בה הוא נמצא. ניתן להגדיר את נתיב התיקיה בעזרת [CThemeManager::basePath]. - -מסד נתונים --------- - -מרבית אפליקציות הווב מגובות על ידי מסד נתונים. לצרכי תרגול טוב, אנחנו ממליצים על נתינת שמות בפורמט הבא לטבלאות ועמודות במסדי הנתונים. למרות ש Yii אינו מחייב זאת. - -- שמות הטבלאות והעמודות צריכות להיות כתובות באותיות קטנות בלבד. - -- המילים בשם צריכים להיות מופרדים בקו תחתון ( _ ) לדוגמא `product_order`. - -- שמות טבלאות יכולות להיות בפורמט של יחיד או רבים, אך לא שניהם. לפשטות, אנו ממליצים שימוש בפורמט של יחיד. - -- ניתן להגדיר קידומת לשמות טבלאות כמו `_tbl`. זה במיוחד רצוי כשהטבלאות של האפליקציה חולקות את אותו המסד עם אפליקציה נוספת. ניתן להפריד והבדיל בין הטבלאות בעזרת הקידומת שלהם. - +מוסכמות +=========== + +Yii מעדיפה מוסכמות על הגדרות. לעקוב אחר המוסכמות וניתן יהיה לבנות אפליקציות מתוחכמות ללא צורך בכתיבה וניהול של הגדרות מסובכות. כמובן, עדיין יהיה ניתן להגדיר את כל ההגדרות של Yii כשצריך. + +למטה מוצגים המוסכמות המומלצים כשמתכנתים בעזרת Yii. לנוחות, אנחנו מניחים ש `webRoot` מנותב לתיקיה הראשית בה מותקנת האפליקציה של Yii. + +קישורים +--- + +כברירת מחדל, Yii מזהה קישורים בפורמט הבא: + +~~~ +http://hostname/index.php?r=ControllerID/ActionID +~~~ + +הפרמטר `r` מכוון אל [נתב](/doc/guide/basics.controller#route) אשר ניתן לעבד אותו לקונטרולר ופעולה. במידה והמזהה היחודי של הפעולה `ActionID` חסר, הקונטרולר יריץ את פעולת ברירת המחדל המוגדרת באמצעות [CController::defaultAction]; במידה והמזהה היחודי של הקונטרולר `controllerID` חסר (או הפרמטר `r` חסר) האפליקציה תריץ את הקונטרולר המוגדר כברירת מחדל בעזרת [CWebApplication::defaultController]. + +בעזרת [CUrlManager], ניתן ליצור קישורים יותר ידידותיים גם ל SEO, כמו לדוגמא `http://hostname/ControllerID/ActionID.html`. ניתן לקרוא בהרחבה אודות אפשרות זו [בניהול קישורים](/doc/guide/topics.url). + + +קוד +---- + +Yii ממליצה לקרוא למשתנים, פונקציות ומחלקות בפורמט של camelCase (פורמט אשר רושמים ראשונה במילה כאות גדולה ולחבר אותם ללא רווחים). שמות משתנים ופונקציות צריכות שהמילה הראשונה בשם שלהם תיהיה כולה באותיות קטנות (מילה ראשונה באותיות קטנות כל שאר המילים האות הראשונה במילה כאות גדולה ולחברם ביחד ללא רווחים), כדי להבדיל אותם משמות מחלקה (לדוגמא `$basePath`, `runController()`, `LinkPager`). למשתנים פרטיים (המוגדרים כ `private`) במחלקה מומלץ להוסיף קו תחתון (_) לשמם לדוגמא (`$_actionList`). + +מאחר והתמיכה במרחבי שמות לא נתמכת בגרסאות PHP מתחת ל 5.3.0, מומלץ לקרוא למחלקות בשמות יחודיים כדי למנוע סתירות בין שמות מחלקות. מסיבה זו, כל המחלקות הבסיסיות של Yii מתחילות באות 'C'. + +חוק ברזל לשמות מחלקות הוא שהם צריכים להסתיים במילה `Controller`. המזהה היחודי של הקונטרולר מוגדר כשם המחלקה באותיות קטנות בלבד וללא המילה `Controller`. לדוגמא, מחלקת הקונטרולר `PageController` תקבל את המזהה היחודי `page`. חוק זה מוודא שהאפליקציה תיהיה מאובטחת יותר. בנוסף הוא מציג את הקישורים בצורה נקייה יותר (לדוגמא `/index.php?r=page/index` במקום `/index.php?r=PageController/index` ). + +הגדרות +------------- + +הגדרות הם מערך זוגות של מפתחות וערכים. כל מפתח מייצג את שמו של המאפיין של הרכיב שצריך להגדיר, וכל ערך מייצג את הערך הראשוני של אותו מאפיין. לדוגמא , `array('name'=»'My +application', 'basePath'=»'./protected') מאתחל את המאפיינים `name` ו `basePath` באפליקציה בהתאם לערכים המוגדרים במערך. + +כל המאפיינים (הציבוריים - Public) באובייקט ניתנים להגדרה. אם לא הוגדרו, המאפיינים יקבלו את ערך ברירת המחדל שלהם. כשמגדירים מאפיין כלשהו, רצוי לקרוא בדוקומנטציה אודות המאפיין כדי להגדיר את הערך המתאים. + +קובץ +---- + +מוסכמות להגדרת שמות ושימוש בקבצים תלויים בסוג שלהם. + +שמות קבצי מחלקות צריכות להקרא על פי המחלקה הציבורית הנמצאת בהם. לדוגמא, שם הקובץ של המחלקה [CController] הוא `CController.php`. מחלקה ציבורית הינה מחלקה שניתן להשתמש בה בכל מחלקה אחרת. רצוי שכל קובץ מחלקה יכיל מחלקה ציבורית אחת לכל היותר. מחלקות פרטיות (מחלקות אשר ניתן להשתמש בהם במחלקה ציבורית אחת בלבד) יכול להיות בתוך אותו קובץ של המחלקה הציבורית שבה היא משומשת. + +קבצי תצוגה צריכים להקרא על פי שמות התצוגה. לדוגמא, שם קובץ התצוגה `index` יהיה `index.php`. קובץ תצוגה הוא קובץ PHP המכיל בעיקר קוד PHP ו HTML למטרת תצוגה בעיקר. + +קבצי הגדרות יכולים להקרא באופן שרירותי. קובץ הגדרות הוא קובץ PHP המכיל ומחזיר מערך של הגדרות. + +תיקיה +--------- + +Yii מניח כברירת מחדל כמה תיקיות אשר הוא משתמש למטרות שונות. את כל אחד ניתן להגדיר בצורה שונה במידה וצריך. + +- `WebRoot/protected`: זוהי [התיקיה הראשית](/doc/guide/basics.application#application-base-directory) של האפליקציה המכילה קבצים וסקריפטים של PHP רגישים. Yii מגדירה שם מקוצר ברירת מחדל בשם `application` אשר מנותבת לתיקיה זו. תיקיה זו וכל מה שבתוכה צריכה להיות מוגנת מפני גישה של משתמשי קצה. ניתן להגדירה בעזרת [CWebApplication::basePath]. + +- `WebRoot/protected/runtime`: תיקיה זו מכילה קבצים זמניים פרטיים הנוצרים בזמן הרצת האפליקציה. תיקיה זו חייבת לקבל הרשאות כתיבה על ידי השרת. ניתן להגדירה בעזרת [CApplication::runtimePath]. + +- `WebRoot/protected/extensions`: תיקיה זו מכילה את כל התוספים למערכת. ניתן להגדירה בעזרת [CApplication::extensionPath]. + +- `WebRoot/protected/modules`: תיקיה זו מכילה את כל [המודולים](/doc/guide/basics.module) של האפליקציה, כל אחת מייצגת כתת-תיקיה. + +- `WebRoot/protected/controllers`: תיקיה זו מכילה את כל הקונטרולרים של האפליקציה. ניתן להגדירה בעזרת [CWebApplication::controllerPath]. + +- `WebRoot/protected/views`: תיקיה זו מכילה את כל קבצי התצוגה, הכוללים את קבצי התצוגה של הקונטרולרים, קבצי תצוגת התבניות, וקבצי תצוגה מערכתיים. ניתן להגדירה בעזרת [CWebApplication::viewPath]. + +- `WebRoot/protected/views/ControllerID`: תיקיה זו מכילה את קבצי התצוגה לקונטרולר יחיד. במקרה הזה `ControllerID` מייצג את המזהה היחודי של הקונטרולר. ניתן להגדיר זאת על ידי [CController::viewPath]. + +- `WebRoot/protected/views/layouts`: תיקיה זו מכילה את כל קבצי התבניות של האפליקציה. ניתן להגדירה בעזרת [CWebApplication::layoutPath]. + +- `WebRoot/protected/views/system`: תיקיה זו מכילה את כל קבצי התצוגה המערכתיים כמו התצוגה של שגיאות ולוגים של המערכת. ניתן להגדיר זאת על ידי [CWebApplication::systemViewPath]. + +- `WebRoot/assets` : תיקיה זו מכילה את קבצי הנכסים (JS, CSS) , קבצים אלו הם פרטיים אשר ניתן לפרסמם כדי שמשתמשי קצה יוכלו לגשת אליהם. תיקיה זו צריכה לקבל הרשאות כתיבה על ידי השרת כדי לתפקד כראוי. ניתן להגדירה בעזרת [CAssetManager::basePath]. + +- `WebRoot/themes` : תיקיה זו מכילה עיצובים שונים אשר ניתן להשתמש בהם באפליקציה. כל תת תיקיה מייצגת עיצוב בודד ששמו הוא שם התיקיה בה הוא נמצא. ניתן להגדיר את נתיב התיקיה בעזרת [CThemeManager::basePath]. + +מסד נתונים +-------- + +מרבית אפליקציות הווב מגובות על ידי מסד נתונים. לצרכי תרגול טוב, אנחנו ממליצים על נתינת שמות בפורמט הבא לטבלאות ועמודות במסדי הנתונים. למרות ש Yii אינו מחייב זאת. + +- שמות הטבלאות והעמודות צריכות להיות כתובות באותיות קטנות בלבד. + +- המילים בשם צריכים להיות מופרדים בקו תחתון ( _ ) לדוגמא `product_order`. + +- שמות טבלאות יכולות להיות בפורמט של יחיד או רבים, אך לא שניהם. לפשטות, אנו ממליצים שימוש בפורמט של יחיד. + +- ניתן להגדיר קידומת לשמות טבלאות כמו `_tbl`. זה במיוחד רצוי כשהטבלאות של האפליקציה חולקות את אותו המסד עם אפליקציה נוספת. ניתן להפריד והבדיל בין הטבלאות בעזרת הקידומת שלהם. + «div class="revision"»$Id: basics.convention.txt 1768 2010-02-01 01:34:15Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/basics.entry.txt b/docs/guide/he/basics.entry.txt index fe9326cce..3dd4d5973 100644 --- a/docs/guide/he/basics.entry.txt +++ b/docs/guide/he/basics.entry.txt @@ -1,26 +1,26 @@ -קובץ כניסה ראשי -============ - -קובץ הכניסה הראשי הינו סקריפט PHP אשר מקבל את בקשת המשתמש לראשונה וקובע את טעינת האפליקציה. זהו קובץ ה PHP היחידי שמשתמש הקצה יכול לבקש להריץ. - -ברוב המקרים, קובץ הכניסה הראשי באפליקציה של Yii מכיל את הקוד הפשוט הבא, - -~~~ -[php] -// remove the following line when in production mode -defined('YII_DEBUG') or define('YII_DEBUG',true); -// include Yii bootstrap file -require_once('path/to/yii/framework/yii.php'); -// create application instance and run -$configFile='path/to/config/file.php'; -Yii::createWebApplication($configFile)-»run(); -~~~ - -הסקריפט קודם כל טוען את קובץ הכניסה הראשי של Yii בשם `yii.php` מהתיקיה הראשית של הפריימוורק. לאחר מכן היא יוצרת אובייקט של האפליקציה עם ההגדרות הדרושות ומריצה אותו. - -מצב ניפוי שגיאות ----------- - -אפליקצית Yii יכולה לרוץ במצב של ניפוי שגיאות או במצב רגיל (תפוקתי - Production) בהתאם לערך שהוגדר במשתנה הקבוע `YII_DEBUG`. כברירת מחדל, משתנה זה מוגדר לערך השווה ל `false`, משמע מצב רגיל. כדי להריץ את האפליקציה במצב של ניפוי שגיאות, יש לקבוע את המשתנה הזה לערך השווה ל `true` לפני הוספת הקובץ `yii.php`. הרצת אפליקציה במצב של ניפוי שגיאות היא לא יעילה מאחר והיא שומרת הרבה לוגים ומידע אודות הרצת האפליקציה. מצד שני, הרצת אפליקציה במצב של ניפוי שגיאות מקלה על פיתוח האפליקציה מאחר והיא מציגה מידע ולוגים אודות השגיאות אשר קוראות במהלך הרצת האפליקציה. - +קובץ כניסה ראשי +============ + +קובץ הכניסה הראשי הינו סקריפט PHP אשר מקבל את בקשת המשתמש לראשונה וקובע את טעינת האפליקציה. זהו קובץ ה PHP היחידי שמשתמש הקצה יכול לבקש להריץ. + +ברוב המקרים, קובץ הכניסה הראשי באפליקציה של Yii מכיל את הקוד הפשוט הבא, + +~~~ +[php] +// remove the following line when in production mode +defined('YII_DEBUG') or define('YII_DEBUG',true); +// include Yii bootstrap file +require_once('path/to/yii/framework/yii.php'); +// create application instance and run +$configFile='path/to/config/file.php'; +Yii::createWebApplication($configFile)-»run(); +~~~ + +הסקריפט קודם כל טוען את קובץ הכניסה הראשי של Yii בשם `yii.php` מהתיקיה הראשית של הפריימוורק. לאחר מכן היא יוצרת אובייקט של האפליקציה עם ההגדרות הדרושות ומריצה אותו. + +מצב ניפוי שגיאות +---------- + +אפליקצית Yii יכולה לרוץ במצב של ניפוי שגיאות או במצב רגיל (תפוקתי - Production) בהתאם לערך שהוגדר במשתנה הקבוע `YII_DEBUG`. כברירת מחדל, משתנה זה מוגדר לערך השווה ל `false`, משמע מצב רגיל. כדי להריץ את האפליקציה במצב של ניפוי שגיאות, יש לקבוע את המשתנה הזה לערך השווה ל `true` לפני הוספת הקובץ `yii.php`. הרצת אפליקציה במצב של ניפוי שגיאות היא לא יעילה מאחר והיא שומרת הרבה לוגים ומידע אודות הרצת האפליקציה. מצד שני, הרצת אפליקציה במצב של ניפוי שגיאות מקלה על פיתוח האפליקציה מאחר והיא מציגה מידע ולוגים אודות השגיאות אשר קוראות במהלך הרצת האפליקציה. + «div class="revision"»$Id: basics.entry.txt 1622 2009-12-26 20:56:05Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/basics.model.txt b/docs/guide/he/basics.model.txt index acd41380b..cc3fdd53e 100644 --- a/docs/guide/he/basics.model.txt +++ b/docs/guide/he/basics.model.txt @@ -1,14 +1,14 @@ -מודל -===== - -מודל הוא אובייקט של [CModel] או מחלקות היורשות ממנו. שימוש במודלים נועד כדי לשמור את המידע והלוגיקה מאחוריהם. - -מודל מייצג אובייקט מידע יחיד. אובייקט זה יכול לייצג רשומה אחת במסד הנתונים טבלה או טופס של נתונים שהמשתמש הזין. כל שדה של אובייקט המידע מיוצג כמאפיין של המודל. למאפיין ישנה תוית וניתן לאמת אותו מול רשימה של חוקים. - -Yii מיישמת שני סוגים של מודלים: מודל טופס ומודל AR. שניהם יורשים מאותה מחלקה בסיסית בשם [CModel]. - -מודל טופס הוא אובייקט של [CFormModel]. מודל טופס נועד לאחסון מידע שהוזן על ידי המשתמש. בדרך כלל מידע זה נאסף, משומש, ונזרק. לדוגמא, בעמוד התחברות, ניתן להשתמש במודל טופס כדי לייצג מידע של שם המשתמש והסיסמא שהמשתמש הזין. למידע נוסף, יש לקרוא [כיצד לעבוד עם טפסים](/doc/guide/form.model). - -AR (Active Record) הינו דפוס אשר ניתן להשתמש בגישה למסד הנתונים, טבלאות ורשומות בצורה של תכנות מונחה-עצמים. כל אובייקט AR הינו אובייקט של [CActiveRecord] או מחלקות היורשות ממנו, המייצג שורה אחת בטבלה במסד הנתונים. העמודות בשורה מיוצגות כמאפיינים של אובייקט ה AR. למידע נוסף יש לקרוא אודות [Active Record](/doc/guide/database.ar). - +מודל +===== + +מודל הוא אובייקט של [CModel] או מחלקות היורשות ממנו. שימוש במודלים נועד כדי לשמור את המידע והלוגיקה מאחוריהם. + +מודל מייצג אובייקט מידע יחיד. אובייקט זה יכול לייצג רשומה אחת במסד הנתונים טבלה או טופס של נתונים שהמשתמש הזין. כל שדה של אובייקט המידע מיוצג כמאפיין של המודל. למאפיין ישנה תוית וניתן לאמת אותו מול רשימה של חוקים. + +Yii מיישמת שני סוגים של מודלים: מודל טופס ומודל AR. שניהם יורשים מאותה מחלקה בסיסית בשם [CModel]. + +מודל טופס הוא אובייקט של [CFormModel]. מודל טופס נועד לאחסון מידע שהוזן על ידי המשתמש. בדרך כלל מידע זה נאסף, משומש, ונזרק. לדוגמא, בעמוד התחברות, ניתן להשתמש במודל טופס כדי לייצג מידע של שם המשתמש והסיסמא שהמשתמש הזין. למידע נוסף, יש לקרוא [כיצד לעבוד עם טפסים](/doc/guide/form.model). + +AR (Active Record) הינו דפוס אשר ניתן להשתמש בגישה למסד הנתונים, טבלאות ורשומות בצורה של תכנות מונחה-עצמים. כל אובייקט AR הינו אובייקט של [CActiveRecord] או מחלקות היורשות ממנו, המייצג שורה אחת בטבלה במסד הנתונים. העמודות בשורה מיוצגות כמאפיינים של אובייקט ה AR. למידע נוסף יש לקרוא אודות [Active Record](/doc/guide/database.ar). + «div class="revision"»$Id: basics.model.txt 162 2008-11-05 12:44:08Z weizhuo $«/div» \ No newline at end of file diff --git a/docs/guide/he/basics.module.txt b/docs/guide/he/basics.module.txt index 103414bc1..38dcb5beb 100644 --- a/docs/guide/he/basics.module.txt +++ b/docs/guide/he/basics.module.txt @@ -1,82 +1,82 @@ -מודול -====== - -» Note|הערה: תמיכה במודולים קיימת מגרסא 1.0.3 ומעלה. - -מודול היא יחידה בפני עצמה המורכבת מ - [מודלים](/doc/guide/basics.model), [קבצי תצוגה](/doc/guide/basics.view), [קונטרולרים](/doc/guide/basics.controller) ושאר רכיבים נתמכים. במובנים רבים, מודול דומה ל[אפליקציה](/doc/guide/basics.application). ההבדל היחידי הוא שלא ניתן לפרוס מודול כפי שהוא והוא חייב להיות ממוקם בתוך אפליקציה. ניתן לגשת לקונטרולרים בתוך מודול כפי שניגשים לקונטרולר רגיל באפליקציה. - -מודולים יעילים בכמה תסריטים. בעבור אפליקציה גדולה במיוחד, ניתן לחלק אותה למספר מודולים, כל אחד מפותח ומתוחזק בצורה נפרדת. כמה חלקים אשר משתמשים בהם לעיתים קרובות, כמו ניהול משתמשים, ניהול תגובות, יכולים להיות מפותחים בפורמט של מודול כדי שיהיה ניתן להשתמש בהם בקלות בפרוייקטים עתידיים. - -יצירת מודול ---------------- - -מודול מאורגן על פי תיקיה ששמה הוא [המזהה היחודי|CWebModule::id] של המודול. המבנה של התיקיות בתוך מודול הוא בדומה למבנה של [התיקיות באפליקציה](/doc/guide/basics.application#application-base-directory). הדוגמא הבאה מציגה מבנה של תיקיות פשוט של מודול בשם `forum`: - -~~~ -forum/ - ForumModule.php קובץ המחלקה של המודול - components/ מכיל רכיבים אשר ניתן להשתמש בכל מקום במודול - views/ מכיל קבצי וידג'טים - controllers/ מכיל קבצי מחלקות הקונטרולרים - DefaultController.php קונטרולר ברירת המחדל - extensions/ מכיל תוספות צד שלישי - models/ מכיל את קבצי המודלים - views/ מכיל את קבצי התצוגה - layouts/ מכיל את תבניות התצוגה - default/ DefaultController מכיל קבצי תצוגה השייכים ל - index.php קובץ תצוגה -~~~ - -מודול חייב להכיל מחלקה אשר יורשת מהמחלקה [CWebModule]. שם המחלקה נקבע על פי הביטוי `ucfirst($id).'Module'`, איפה ש `id$` מתייחס למזהה היחודי של המודול (או שמה של התיקיה של המודול). מחלקת המודול משרתת כמקום מרכזי לאחסון ושיתוף המידע לאורך כל קוד המודול. לדוגמא, ניתן להשתמש ב [CWebModule::params] כדי לשמור פרמטרים של המודול, ולהשתמש ב [CWebModule::components] כדי לשתף [רכיבי אפליקציה](/doc/guide/basics.application#application-component) ברמת המודול. - -» Tip|טיפ: אנו יכולים להשתמש בכלי בשם Gii כדי ליצור מודול עם המבנה הבסיסי של מודול חדש. - -שימוש במודול ------------- - -כדי להשתמש במודול, ראשית יש ליצור תיקיה בשם `modules` תחת [התיקיה הראשית](/doc/guide/basics.application#application-base-directory) של האפליקציה. לאחר מכן יש להגדיר את המזהה היחודי של המודול במאפיין [modules|CWebApplication::modules] של האפליקציה. לדוגמא, כדי להשתמש במודול המוצג למעלה `forum`, ניתן להשתמש [הגדרות אפליקציה](/doc/guide/basics.application#application-configuration) הבאות: - -~~~ -[php] -return array( - ...... - 'modules'=»array('forum',...), - ...... -); -~~~ - -ניתן להגדיר מודול עם המאפיינים הראשוניים שלו מוגדרים בצורה שונה. השימוש הוא דומה מאוד [להגדרות רכיב](/doc/guide/basics.application#application-component). לדוגמא, מודול ה `forum` יכול להכיל מאפיין בשם `postPerPage` בתוך מחלקת המודול שלו שניתן להגדירו [בהגדרות האפליקציה](/doc/guide/basics.application#application-configuration) בצורה הבאה: - -~~~ -[php] -return array( - ...... - 'modules'=»array( - 'forum'=»array( - 'postPerPage'=»20, - ), - ), - ...... -); -~~~ - -ניתן לגשת לאובייקט המודול בעזרת המאפיין [module|CController::module] של הקונטרולר הפעיל כרגע. דרך האובייקט של המודול, ניתן לגשת למידע אשר משותף ברמת המודול. לדוגמא, בכדי לגשת למידע של `postPerPage`, ניתן להשתמש בביטוי: - -~~~ -[php] -$postPerPage=Yii::app()-»controller-»module-»postPerPage; -// or the following if $this refers to the controller instance -// $postPerPage=$this-»module-»postPerPage; -~~~ - -ניתן לגשת לפעולה בקונטרולר בתוך מודול על ידי [הניתוב](/doc/guide/basics.controller#route) `moduleID/controllerID/actionID`. לדוגמא, נניח שלמודול `forum` המוצג למעלה ישנו קונטרולר בשם `PostController`, ניתן להשתמש [בניתוב](/doc/guide/basics.controller#route) `forum/post/create` כדי לנתב את הבקשה אל הפעולה `create` אשר נמצאת בקונטרולר. הקישור המתאים לניתוב זה יהיה `http://www.example.com/index.php?r=forum/post/create`. - -» Tip|טיפ: במידה והקונטרולר הוא בתת תיקיה תחת `controllers`, אנו עדיין יכולים להשתמש בפורמט של [הניתוב](/doc/guide/basics.controller#route) המוצג למעלה. לדוגמא, נניח ש `PostController` נמצא תחת `forum/controllers/admin`, ניתן לגשת לפעולה `create` על ידי שימוש ב `forum/admin/post/create`. - -שרשור מודולים -------------- - -ניתן לשרשר מודולים. זאת אומרת, שמודול יכול להכיל מודול נוסף. אנו קוראים לקודם *מודל אב* (*parent module*) ואת המודול תחתיו *תת מודול* (*child module*). תתי מודולים צריכים להיות ממוקמים בתוך תיקית `modules` של מודול האב. כדי לגשת לפעולה של קונטרולר בתת מודול, יש צורך להשתמש בניתוב `parentModuleID/childModuleID/controllerID/actionID`. - - +מודול +====== + +» Note|הערה: תמיכה במודולים קיימת מגרסא 1.0.3 ומעלה. + +מודול היא יחידה בפני עצמה המורכבת מ - [מודלים](/doc/guide/basics.model), [קבצי תצוגה](/doc/guide/basics.view), [קונטרולרים](/doc/guide/basics.controller) ושאר רכיבים נתמכים. במובנים רבים, מודול דומה ל[אפליקציה](/doc/guide/basics.application). ההבדל היחידי הוא שלא ניתן לפרוס מודול כפי שהוא והוא חייב להיות ממוקם בתוך אפליקציה. ניתן לגשת לקונטרולרים בתוך מודול כפי שניגשים לקונטרולר רגיל באפליקציה. + +מודולים יעילים בכמה תסריטים. בעבור אפליקציה גדולה במיוחד, ניתן לחלק אותה למספר מודולים, כל אחד מפותח ומתוחזק בצורה נפרדת. כמה חלקים אשר משתמשים בהם לעיתים קרובות, כמו ניהול משתמשים, ניהול תגובות, יכולים להיות מפותחים בפורמט של מודול כדי שיהיה ניתן להשתמש בהם בקלות בפרוייקטים עתידיים. + +יצירת מודול +--------------- + +מודול מאורגן על פי תיקיה ששמה הוא [המזהה היחודי|CWebModule::id] של המודול. המבנה של התיקיות בתוך מודול הוא בדומה למבנה של [התיקיות באפליקציה](/doc/guide/basics.application#application-base-directory). הדוגמא הבאה מציגה מבנה של תיקיות פשוט של מודול בשם `forum`: + +~~~ +forum/ + ForumModule.php קובץ המחלקה של המודול + components/ מכיל רכיבים אשר ניתן להשתמש בכל מקום במודול + views/ מכיל קבצי וידג'טים + controllers/ מכיל קבצי מחלקות הקונטרולרים + DefaultController.php קונטרולר ברירת המחדל + extensions/ מכיל תוספות צד שלישי + models/ מכיל את קבצי המודלים + views/ מכיל את קבצי התצוגה + layouts/ מכיל את תבניות התצוגה + default/ DefaultController מכיל קבצי תצוגה השייכים ל + index.php קובץ תצוגה +~~~ + +מודול חייב להכיל מחלקה אשר יורשת מהמחלקה [CWebModule]. שם המחלקה נקבע על פי הביטוי `ucfirst($id).'Module'`, איפה ש `id$` מתייחס למזהה היחודי של המודול (או שמה של התיקיה של המודול). מחלקת המודול משרתת כמקום מרכזי לאחסון ושיתוף המידע לאורך כל קוד המודול. לדוגמא, ניתן להשתמש ב [CWebModule::params] כדי לשמור פרמטרים של המודול, ולהשתמש ב [CWebModule::components] כדי לשתף [רכיבי אפליקציה](/doc/guide/basics.application#application-component) ברמת המודול. + +» Tip|טיפ: אנו יכולים להשתמש בכלי בשם Gii כדי ליצור מודול עם המבנה הבסיסי של מודול חדש. + +שימוש במודול +------------ + +כדי להשתמש במודול, ראשית יש ליצור תיקיה בשם `modules` תחת [התיקיה הראשית](/doc/guide/basics.application#application-base-directory) של האפליקציה. לאחר מכן יש להגדיר את המזהה היחודי של המודול במאפיין [modules|CWebApplication::modules] של האפליקציה. לדוגמא, כדי להשתמש במודול המוצג למעלה `forum`, ניתן להשתמש [הגדרות אפליקציה](/doc/guide/basics.application#application-configuration) הבאות: + +~~~ +[php] +return array( + ...... + 'modules'=»array('forum',...), + ...... +); +~~~ + +ניתן להגדיר מודול עם המאפיינים הראשוניים שלו מוגדרים בצורה שונה. השימוש הוא דומה מאוד [להגדרות רכיב](/doc/guide/basics.application#application-component). לדוגמא, מודול ה `forum` יכול להכיל מאפיין בשם `postPerPage` בתוך מחלקת המודול שלו שניתן להגדירו [בהגדרות האפליקציה](/doc/guide/basics.application#application-configuration) בצורה הבאה: + +~~~ +[php] +return array( + ...... + 'modules'=»array( + 'forum'=»array( + 'postPerPage'=»20, + ), + ), + ...... +); +~~~ + +ניתן לגשת לאובייקט המודול בעזרת המאפיין [module|CController::module] של הקונטרולר הפעיל כרגע. דרך האובייקט של המודול, ניתן לגשת למידע אשר משותף ברמת המודול. לדוגמא, בכדי לגשת למידע של `postPerPage`, ניתן להשתמש בביטוי: + +~~~ +[php] +$postPerPage=Yii::app()-»controller-»module-»postPerPage; +// or the following if $this refers to the controller instance +// $postPerPage=$this-»module-»postPerPage; +~~~ + +ניתן לגשת לפעולה בקונטרולר בתוך מודול על ידי [הניתוב](/doc/guide/basics.controller#route) `moduleID/controllerID/actionID`. לדוגמא, נניח שלמודול `forum` המוצג למעלה ישנו קונטרולר בשם `PostController`, ניתן להשתמש [בניתוב](/doc/guide/basics.controller#route) `forum/post/create` כדי לנתב את הבקשה אל הפעולה `create` אשר נמצאת בקונטרולר. הקישור המתאים לניתוב זה יהיה `http://www.example.com/index.php?r=forum/post/create`. + +» Tip|טיפ: במידה והקונטרולר הוא בתת תיקיה תחת `controllers`, אנו עדיין יכולים להשתמש בפורמט של [הניתוב](/doc/guide/basics.controller#route) המוצג למעלה. לדוגמא, נניח ש `PostController` נמצא תחת `forum/controllers/admin`, ניתן לגשת לפעולה `create` על ידי שימוש ב `forum/admin/post/create`. + +שרשור מודולים +------------- + +ניתן לשרשר מודולים. זאת אומרת, שמודול יכול להכיל מודול נוסף. אנו קוראים לקודם *מודל אב* (*parent module*) ואת המודול תחתיו *תת מודול* (*child module*). תתי מודולים צריכים להיות ממוקמים בתוך תיקית `modules` של מודול האב. כדי לגשת לפעולה של קונטרולר בתת מודול, יש צורך להשתמש בניתוב `parentModuleID/childModuleID/controllerID/actionID`. + + «div class="revision"»$Id: basics.module.txt 2042 2009-02-25 21:45:42Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/basics.mvc.txt b/docs/guide/he/basics.mvc.txt index 343f810e3..6fde5e69f 100644 --- a/docs/guide/he/basics.mvc.txt +++ b/docs/guide/he/basics.mvc.txt @@ -1,33 +1,33 @@ -מודל-תצוגה-קונטרולר (MVC) -=========================== - -Yii מאמצת את השימוש בדפוס של מודל-תצוגה-קונטרולר (MVC) אשר משתמשים בה לרוב בשפות פיתוח. MVC נועד להפרדת הקוד מהעיצוב כדי שלמפתחים יהיה יותר קל לשנות חלק מסויים בקוד מבלי להשפיע על חלק אחר. -ב MVC, המודל מייצג את המידע (התוכן) והלוגיקה; התצוגה מכיל אלמנטים של ממשקי המשתמש כמו טקסט, טפסים; והקונטרולר מנהל את התקשורת בין המודל לתצוגה. - -בנוסף ל MVC, Yii מציגה קונטרולר-ראשי, הנקרא אפליקציה, המייצג את הנתונים בהקשר של עיבוד בקשת המשתמש. אפליקציה מעבדת את בקשת המשתמש ומנתבת אותה לקונטרולר המתאים להמשך הניהול והעיבוד. - -הדיאגרמה הבאה מציגה את המבנה הסטטי של אפליקציה ב Yii: - -![מבנה סטטי של אפליקציה ב Yii](structure.png) - - -רצף עבודה אופייני ------------------- - -הדיאגרמה הבאה מציגה את רצף העבודה האופייני של אפליקצית Yii כשהיא מנהלת בקשת משתמש: - -![רצף עבודה אופייני לאפליקציה ב Yii](flow.png) - -1. משתמש מבצע בקשה לקישור `http://www.example.com/index.php?r=post/show&id=1` ושרת הווב מטפל בבקשה ומריץ את קובץ הכניסה הראשי `index.php`. -2. קובץ הכניסה הראשי יוצר אובייקט של [האפליקציה](/doc/guide/basics.application) ומריץ אותו. -3. האפליקציה מקבלת את המידע המפורט אודות בקשת המשתמש [מרכיב האפליקציה](/doc/guide/basics.application#application-component) בשם `request`. -4. האפליקציה מחליטה את [הקונטרולר](/doc/guide/basics.controller) [והפעולה](/doc/guide/basics.controller#action) שצריך להריץ בעזרת רכיב אפליקציה בשם `urlManager`. בדוגמא זו, הקונטרולר הוא `post` אשר מנותב אל המחלקה `PostController`; והפעולה היא `show` אשר פעולתה מוגדרת בקונטרולר. -5. האפליקציה יוצרת אובייקט של אותו קונטרולר להמשך הטיפול בבקשת המשתמש. הקונטרולר מחליט שהפעולה `show` מנותבת למתודה במחלקה בשם `actionShow`. לאחר מכן היא יוצרת ומריצה פילטרים (לדוגמא הרשאות גישה, בדיקות) המצורפים לפעולה זו. הפעולה רצה אם היא עוברת את תהליך הפילטרים. -6. הפעולה קוראת [מודל](/doc/guide/basics.model) של `Post` אשר מספר המזהה היחודי שלו הוא `1` מתוך מסד הנתונים. -7. הפעולה מגישה קובץ [תצוגה](/doc/guide/basics.view) בשם `show` עם המודל `Post`. -8. קובץ התצוגה קורא ומציג את המאפיינים של מודל ה `Post`. -9. קובץ התצוגה מריץ כמה [וידג'טים](/doc/guide/basics.view#widget). -10. התוכן הסופי של קבצי התצוגה נכנס אל תוך [תבנית](/doc/guide/basics.view#layout). -11. הפעולה מסיימת את הגשת קבצי התצוגה ומציגה את התוכן למשתמש. - +מודל-תצוגה-קונטרולר (MVC) +=========================== + +Yii מאמצת את השימוש בדפוס של מודל-תצוגה-קונטרולר (MVC) אשר משתמשים בה לרוב בשפות פיתוח. MVC נועד להפרדת הקוד מהעיצוב כדי שלמפתחים יהיה יותר קל לשנות חלק מסויים בקוד מבלי להשפיע על חלק אחר. +ב MVC, המודל מייצג את המידע (התוכן) והלוגיקה; התצוגה מכיל אלמנטים של ממשקי המשתמש כמו טקסט, טפסים; והקונטרולר מנהל את התקשורת בין המודל לתצוגה. + +בנוסף ל MVC, Yii מציגה קונטרולר-ראשי, הנקרא אפליקציה, המייצג את הנתונים בהקשר של עיבוד בקשת המשתמש. אפליקציה מעבדת את בקשת המשתמש ומנתבת אותה לקונטרולר המתאים להמשך הניהול והעיבוד. + +הדיאגרמה הבאה מציגה את המבנה הסטטי של אפליקציה ב Yii: + +![מבנה סטטי של אפליקציה ב Yii](structure.png) + + +רצף עבודה אופייני +------------------ + +הדיאגרמה הבאה מציגה את רצף העבודה האופייני של אפליקצית Yii כשהיא מנהלת בקשת משתמש: + +![רצף עבודה אופייני לאפליקציה ב Yii](flow.png) + +1. משתמש מבצע בקשה לקישור `http://www.example.com/index.php?r=post/show&id=1` ושרת הווב מטפל בבקשה ומריץ את קובץ הכניסה הראשי `index.php`. +2. קובץ הכניסה הראשי יוצר אובייקט של [האפליקציה](/doc/guide/basics.application) ומריץ אותו. +3. האפליקציה מקבלת את המידע המפורט אודות בקשת המשתמש [מרכיב האפליקציה](/doc/guide/basics.application#application-component) בשם `request`. +4. האפליקציה מחליטה את [הקונטרולר](/doc/guide/basics.controller) [והפעולה](/doc/guide/basics.controller#action) שצריך להריץ בעזרת רכיב אפליקציה בשם `urlManager`. בדוגמא זו, הקונטרולר הוא `post` אשר מנותב אל המחלקה `PostController`; והפעולה היא `show` אשר פעולתה מוגדרת בקונטרולר. +5. האפליקציה יוצרת אובייקט של אותו קונטרולר להמשך הטיפול בבקשת המשתמש. הקונטרולר מחליט שהפעולה `show` מנותבת למתודה במחלקה בשם `actionShow`. לאחר מכן היא יוצרת ומריצה פילטרים (לדוגמא הרשאות גישה, בדיקות) המצורפים לפעולה זו. הפעולה רצה אם היא עוברת את תהליך הפילטרים. +6. הפעולה קוראת [מודל](/doc/guide/basics.model) של `Post` אשר מספר המזהה היחודי שלו הוא `1` מתוך מסד הנתונים. +7. הפעולה מגישה קובץ [תצוגה](/doc/guide/basics.view) בשם `show` עם המודל `Post`. +8. קובץ התצוגה קורא ומציג את המאפיינים של מודל ה `Post`. +9. קובץ התצוגה מריץ כמה [וידג'טים](/doc/guide/basics.view#widget). +10. התוכן הסופי של קבצי התצוגה נכנס אל תוך [תבנית](/doc/guide/basics.view#layout). +11. הפעולה מסיימת את הגשת קבצי התצוגה ומציגה את התוכן למשתמש. + «div class="revision"»$Id: basics.mvc.txt 1622 2009-12-26 20:56:05Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/basics.namespace.txt b/docs/guide/he/basics.namespace.txt index af514674e..bda710d64 100644 --- a/docs/guide/he/basics.namespace.txt +++ b/docs/guide/he/basics.namespace.txt @@ -1,46 +1,46 @@ -מרחבי שמות וקיצורי שמות לנתיבים -======================== - -Yii משתמשת בשמות מקוצרים לנתיבים באופן נרחב. שם מקוצר משוייך לנתיב מלא לתיקיה או קובץ כלשהו בשרת. השמות המקוצרים כתובים בפורמט של נקודות ( Dot Syntax . ), בדומה לפורמט בו משתמשים למרחבי שמות: - -~~~ -RootAlias.path.to.target -~~~ - -איפה ש `RootAlias` הוא שם מקוצר לנתיב קיים כלשהו. על ידי קריאה ל [YiiBase::setPathOfAlias], ניתן להגדיר שמות מקוצרים לנתיבים. לנוחות השימוש, Yii מגדירה מראש את השמות המקוצרים הבאים כברירת מחדל: - -- `system`: מתייחס לתיקיה הראשית של הפריימוורק (היכן ש Yii נמצא) . -- `zii`: מתייחס לתיקיה של [ספריית Zii](/doc/guide/extension.use#zii-extensions) -- `application`: מתייחס לתיקיה [הראשית](/doc/guide/basics.application#application-base-directory) של האפליקציה. -- `webroot`: מתייחס לתיקיה בו נמצא [קובץ הגישה](/doc/guide/basics.entry) הראשי. שם מקוצר זה קיים מגרסא 1.0.3 ומעלה. -- `ext`: מתייחס לתיקיה בה נמצאים כל [התוספים](/doc/guide/extension.overview). שם מקוצר זה קיים מגרסא 1.0.8 ומעלה. - -בנוסף, במידה והאפליקציה משתמשת [מודלים](/doc/guide/basics.module), נוצר שם מקוצר לכל מודול על פי שמו היחודי אשר מכוון לתיקיה הראשית של אותו מודול. אפשרות זאת קיימת מגרסא 1.0.3 ומעלה. - -על ידי שימוש ב [YiiBase::getPathOfAlias], ניתן להמיר שם מקוצר לנתיב המדוייק אליו הוא מכוון. לדוגמא, `system.web.CController` יתורגם לנתיב `yii/framework/web/CController` לאחר הקריאה. - -באמצעות שימוש בשמות מקוצרים, יבוא מחלקות ניתן לעשות בצורה קלה יחסית. לדוגמא, אם ברצוננו לייבא את המחלקה [CController], ניתן לקרוא לפקודה הבאה: - -~~~ -[php] -Yii::import('system.web.CController'); -~~~ - -שימוש בפונקצית ה [import|YiiBase::import] שונה משימוש ב `include` ו `require` מאחר והיא יותר יעילה. מכיוון שאת המחלקה אותה מייבאים לא מוסיפים מייד אלה בפעם הראשונה בה קוראים למחלקה. ייבוא של אותו מרחב שם כמה פעמים הרבה יותר מהיר משימוש ב `include_once` ו `require_once`. - -» Tip|טיפ: כשמתייחסים למחלקה אשר נמצאת תחת הפריימוורק של Yii, אין צורך בלייבא את אותה מחלקה. כל מחלקות הבסיס של Yii כבר מיובאות. - -ניתן גם להשתמש בתחביר הבא כדי לייבא תיקיה שלמה עם כל המחלקות והקבצים שנמצאים בתוכה כדי שנוכל להשתמש בהם בעת הצורך ללא כל צורך בטעינה של כל אחד מהם בנפרד. - -~~~ -[php] -Yii::import('system.web.*'); -~~~ - -מלבד [import|YiiBase::import], ניתן להשתמש בשמות מקוצרים במקומות אחרים כדי להתייחס למחלקות. לדוגמא, ניתן להשתמש בשם מקוצר כדי ליצור אובייקט של מחלקה בעזרת [Yii::createComponent], גם אם המחלקה לא נוספה בעבר. - -יש לזכור לא להתבלבל בין שמות מקוצרים למרחבי שמות. מרחב שם מתייחס לקיבוץ לוגי של שמות מחלקה כדי שניתן יהיה להבדיל ביניהם לבין מחלקות נוספות בעלות אותו השם, בעוד שקיצור שם נועד להתייחסות לקובץ או תיקיה. שמות מקוצרים לא סותרים את מרחבי השמות. - -» Tip|טיפ: מאחר ומרחבי שמות לא נתמכים בגרסאות מתחת ל 5.3.0 ב PHP, לא ניתן ליצור אובייקט של שני מחלקות שונות בעלות אותו השם. מסיבה זו, כל שמותיהם של המחלקות הבסיסיות של Yii מתחילות באות 'C' (אשר מייצג 'class' מחלקה) כדי שיהיה ניתן להבדיל בניהם לבין המחלקות שנוצרות על ידי המשתמש וכדי למנוע סתירה בין שמות המחלקות. רצוי ששמות המחלקה הנוצרים על ידי המשתמשים לא יתחילו באות 'C' מאחר וזה שמור למחלקות הבסיסיות של Yii. ושמות מחלקות שנוצרות על ידי המשתמש יתחילו באות אחרת. - +מרחבי שמות וקיצורי שמות לנתיבים +======================== + +Yii משתמשת בשמות מקוצרים לנתיבים באופן נרחב. שם מקוצר משוייך לנתיב מלא לתיקיה או קובץ כלשהו בשרת. השמות המקוצרים כתובים בפורמט של נקודות ( Dot Syntax . ), בדומה לפורמט בו משתמשים למרחבי שמות: + +~~~ +RootAlias.path.to.target +~~~ + +איפה ש `RootAlias` הוא שם מקוצר לנתיב קיים כלשהו. על ידי קריאה ל [YiiBase::setPathOfAlias], ניתן להגדיר שמות מקוצרים לנתיבים. לנוחות השימוש, Yii מגדירה מראש את השמות המקוצרים הבאים כברירת מחדל: + +- `system`: מתייחס לתיקיה הראשית של הפריימוורק (היכן ש Yii נמצא) . +- `zii`: מתייחס לתיקיה של [ספריית Zii](/doc/guide/extension.use#zii-extensions) +- `application`: מתייחס לתיקיה [הראשית](/doc/guide/basics.application#application-base-directory) של האפליקציה. +- `webroot`: מתייחס לתיקיה בו נמצא [קובץ הגישה](/doc/guide/basics.entry) הראשי. שם מקוצר זה קיים מגרסא 1.0.3 ומעלה. +- `ext`: מתייחס לתיקיה בה נמצאים כל [התוספים](/doc/guide/extension.overview). שם מקוצר זה קיים מגרסא 1.0.8 ומעלה. + +בנוסף, במידה והאפליקציה משתמשת [מודלים](/doc/guide/basics.module), נוצר שם מקוצר לכל מודול על פי שמו היחודי אשר מכוון לתיקיה הראשית של אותו מודול. אפשרות זאת קיימת מגרסא 1.0.3 ומעלה. + +על ידי שימוש ב [YiiBase::getPathOfAlias], ניתן להמיר שם מקוצר לנתיב המדוייק אליו הוא מכוון. לדוגמא, `system.web.CController` יתורגם לנתיב `yii/framework/web/CController` לאחר הקריאה. + +באמצעות שימוש בשמות מקוצרים, יבוא מחלקות ניתן לעשות בצורה קלה יחסית. לדוגמא, אם ברצוננו לייבא את המחלקה [CController], ניתן לקרוא לפקודה הבאה: + +~~~ +[php] +Yii::import('system.web.CController'); +~~~ + +שימוש בפונקצית ה [import|YiiBase::import] שונה משימוש ב `include` ו `require` מאחר והיא יותר יעילה. מכיוון שאת המחלקה אותה מייבאים לא מוסיפים מייד אלה בפעם הראשונה בה קוראים למחלקה. ייבוא של אותו מרחב שם כמה פעמים הרבה יותר מהיר משימוש ב `include_once` ו `require_once`. + +» Tip|טיפ: כשמתייחסים למחלקה אשר נמצאת תחת הפריימוורק של Yii, אין צורך בלייבא את אותה מחלקה. כל מחלקות הבסיס של Yii כבר מיובאות. + +ניתן גם להשתמש בתחביר הבא כדי לייבא תיקיה שלמה עם כל המחלקות והקבצים שנמצאים בתוכה כדי שנוכל להשתמש בהם בעת הצורך ללא כל צורך בטעינה של כל אחד מהם בנפרד. + +~~~ +[php] +Yii::import('system.web.*'); +~~~ + +מלבד [import|YiiBase::import], ניתן להשתמש בשמות מקוצרים במקומות אחרים כדי להתייחס למחלקות. לדוגמא, ניתן להשתמש בשם מקוצר כדי ליצור אובייקט של מחלקה בעזרת [Yii::createComponent], גם אם המחלקה לא נוספה בעבר. + +יש לזכור לא להתבלבל בין שמות מקוצרים למרחבי שמות. מרחב שם מתייחס לקיבוץ לוגי של שמות מחלקה כדי שניתן יהיה להבדיל ביניהם לבין מחלקות נוספות בעלות אותו השם, בעוד שקיצור שם נועד להתייחסות לקובץ או תיקיה. שמות מקוצרים לא סותרים את מרחבי השמות. + +» Tip|טיפ: מאחר ומרחבי שמות לא נתמכים בגרסאות מתחת ל 5.3.0 ב PHP, לא ניתן ליצור אובייקט של שני מחלקות שונות בעלות אותו השם. מסיבה זו, כל שמותיהם של המחלקות הבסיסיות של Yii מתחילות באות 'C' (אשר מייצג 'class' מחלקה) כדי שיהיה ניתן להבדיל בניהם לבין המחלקות שנוצרות על ידי המשתמש וכדי למנוע סתירה בין שמות המחלקות. רצוי ששמות המחלקה הנוצרים על ידי המשתמשים לא יתחילו באות 'C' מאחר וזה שמור למחלקות הבסיסיות של Yii. ושמות מחלקות שנוצרות על ידי המשתמש יתחילו באות אחרת. + «div class="revision"»$Id: basics.namespace.txt 1602 2009-12-18 19:33:34Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/basics.view.txt b/docs/guide/he/basics.view.txt index ff7398098..aa20e5bab 100644 --- a/docs/guide/he/basics.view.txt +++ b/docs/guide/he/basics.view.txt @@ -1,105 +1,105 @@ -תצוגה -==== - -קובץ תצוגה הוא קובץ PHP המכיל בעיקר אלמנטים הקשורים לממשק המשתמש. קובץ זה יכול להכיל פקודות PHP, אבל רצוי שפקודות אלו לא ישנו מידע לגבי המודלים וישארו פשוטות יחסית. למען הלוגיקה של הפרדת הקוד מהעיצוב, חלקים גדולים של פקודות PHP צריכות להיות ממוקמות בקבצי הקונטרולר והמודלים ולא בקבצי התצוגה. - -לקובץ תצוגה יש שם אשר נועד למטרת זיהוי בעת הקריאה לאותו הקובץ. שמו של קובץ התצוגה זהה לשם הקובץ הפיזי בו הוא נשמר. לדוגמא, קובץ תצוגה בשם `edit` מתייחס לקובץ סקריפט PHP בשם `edit.php`. כדי לטעון קובץ תצוגה יש לקרוא לפונקציה [CController::render] כשהפרמטר הראשון בפונקציה הוא שמו של קובץ התצוגה אותו רוצים להציג. הפונקציה תחפש אחר קובץ התצוגה המתאים תחת הנתיב `protected/views/ControllerID`. - -בתוך קובץ התצוגה, ניתן לגשת לאובייקט הקונטרולר בעזרת האובייקט `this$`. לכן ניתן לגשת לכל מאפיין/משתנה של המחלקה בעזרת קריאה ל `this-»propertyName$` בתוך קובץ התצוגה. - -כמו כן ניתן להשתמש בשיטה הבאה כדי `לדחוף` נתונים אל קובץ התצוגה: - -~~~ -[php] -$this-»render('edit', array( - 'varone'=»$value1, - 'vartwo'=»$value2, -)); -~~~ - - -בקוד המוצג למעלה, הפונקציה [render|CController::render] תחלץ את המערך המוגדר בפרמטר השני כערכים. כתוצאה, בתוך קובץ התצוגה ניתן יהיה לגשת למשתנים `varone$` ו `vartwo$`. - - - -תבנית ------- - -תבנית מהווה כקובץ תצוגה מיוחד הנועד לעטר את קבצי התצוגה הרגילים (המוצגים למעלה). בדרך כלל הוא מכיל חלקים של ממשקי משתמש אשר מוצגים בכל מקום. לדוגמא, קובץ תבנית יכול להכיל את ראש הדף ואת תחתית הדף ולהציג את התוכן באמצע. - -~~~ -[php] -......header here...... -«?php echo $content; ?» -......footer here...... -~~~ - -כשהערך `content$` מכיל את התוצאה של אותם קבצי תצוגה שקראנו להם קודם לכן בעזרת [render|CController::render]. - -תבנית מיושמת ברגע שיש קריאה ל [render|CController::render]. כברירת מחדל, קובץ התצוגה `protected/views/layouts/main.php` מוגדר כקובץ התבנית. ניתן לשנות זאת על ידי שינוי הערך של [CWebApplication::layout] או [CController::layout]. כדי לקרוא לקובץ תצוגה ולהציג אותו ללא קובץ תבנית, יש לקרוא לפונקציה [renderPartial|CController::renderPartial] במקום [render|CController::render]. - -וידג'ט ------- - -וידג'ט הוא אובייקט של [CWidget] או במחלקות הנמצאות תחתיו. זהו רכיב המיועד בעיקר למטרות תצוגה. וידג'טים בדרך כלל מוטמעים בקבצי תצוגה כדי ליצור ממשקים מורכבים אך כלליים. לדוגמא, וידג'ט לוח-שנה יכול לשמש כדי להציג לוח-שנה מורכב בממשק המשתמש. וידג'טים מאפשרים שימוש חוזר טוב יותר לצרכי תצוגה. - -כדי להשתמש ב וידג'ט השתמש בצורה הבאה בקובץ תצוגה: - -~~~ -[php] -«?php $this-»beginWidget('path.to.WidgetClass'); ?» -...תוכן כלשהו שניתן לתפוס בעזרת ה וידג'ט... -«?php $this-»endWidget(); ?» -~~~ - -או - -~~~ -[php] -«?php $this-»widget('path.to.WidgetClass'); ?» -~~~ - -הדוגמא השנייה משמשת כשהוידג'ט לא צריך לתפוס שום תוכן. - -ניתן לשנות את אופן ההתנהלות של הוידג'טים בעת קריאתם. בכדי לעשות זאת יש להגדיר את הערכים הראשוניים של הוידג'ט בזמן הקריאה הראשונה שלהם על ידי [CBaseController::beginWidget] או [CBaseController::widget]. לדוגמא, כשמשתמשים בוידג'ט [CMaskedTextField], אנחנו נרצה להגדיר את המסכה לשימוש. אנחנו מבצעים זאת על ידי העברת מערך כמפרמטר שני להגדרת הערכים הראשוניים כדלקמן, כשמפתחות המערך הם שמות המאפיינים של הוידג'ט והערכים של המערך הם הערכים הראשוניים של הוידג'ט: - -~~~ -[php] -«?php -$this-»widget('CMaskedTextField',array( - 'mask'=»'99/99/9999' -)); -?» -~~~ - -כדי להגדיר וידג'ט חדש, יש ליצור מחלקה אשר יורשת מ [CWidget], וליישם את המתודות [init|CWidget::init] ו [run|CWidget::run] במחלקה: - -~~~ -[php] -class MyWidget extends CWidget -{ - public function init() - { - // this method is called by CController::beginWidget() - } - - public function run() - { - // this method is called by CController::endWidget() - } -} -~~~ - -כמו בקונטרולר, וידג'ט יכול להכיל קובץ תצוגה משלו. כברירת מחדל, קבצי התצוגה של הוידג'ט נמצאים תחת התיקיה `views` בתיקיה הראשית בה המחלקה של הוידג'ט נמצא. ניתן להציג את קבצי התצוגה הללו בעזרת [CWidget::render], בדומה לפעולה הנעשית בקונטרולר. ההבדל היחיד הוא שלא ניתן להגדיר תבנית לתצוגה של הוידג'ט. בנוסף, `this$` בתוך קובץ תצוגה של וידג'ט מכוון אל האובייקט של הוידג'ט במקום האובייקט של הקונטרולר. - -תצוגת מערכת ------------ - -תצוגת מערכת הם קבצי תצוגה אשר Yii משתמשת בהם לצרכי תצוגה של מידע אודות שגיאות ולוגים. לדוגמא, כשמשתמש מבקש קונטרולר לא קיים או פעולה שלא קיימת, Yii יזרוק שגיאה שמסבירה אודות השגיאה שהתרחשה. Yii מציג את השגיאה בתוך קובץ תצוגה מערכתי ספציפי. - -שמות התצוגה של קבצי תצוגת המערכת עוקבים אחר אותם חוקים. שמות כמו `errorXXX` משמשים לתצוגה של שגיאות אשר נזרקות על ידי [CHttpException] כשקוד השגיאה הוא `XXX`. לדוגמא, אם התרחשה שגיאה של [CHttpException] עם קוד שגיאה 404, קובץ התצוגה המערכתי שיוצג יהיה `error404`. - -Yii מספקת קבצי תצוגה מערכתיים כברירת מחדל אשר נמצאים תחת `framework/views`. ניתן להגדיר את קבצי התצוגה באופן עצמאי על ידי יצירת אותם קבצי תצוגה עם אותם השמות תחת התיקיה `protected/views/system`. - - +תצוגה +==== + +קובץ תצוגה הוא קובץ PHP המכיל בעיקר אלמנטים הקשורים לממשק המשתמש. קובץ זה יכול להכיל פקודות PHP, אבל רצוי שפקודות אלו לא ישנו מידע לגבי המודלים וישארו פשוטות יחסית. למען הלוגיקה של הפרדת הקוד מהעיצוב, חלקים גדולים של פקודות PHP צריכות להיות ממוקמות בקבצי הקונטרולר והמודלים ולא בקבצי התצוגה. + +לקובץ תצוגה יש שם אשר נועד למטרת זיהוי בעת הקריאה לאותו הקובץ. שמו של קובץ התצוגה זהה לשם הקובץ הפיזי בו הוא נשמר. לדוגמא, קובץ תצוגה בשם `edit` מתייחס לקובץ סקריפט PHP בשם `edit.php`. כדי לטעון קובץ תצוגה יש לקרוא לפונקציה [CController::render] כשהפרמטר הראשון בפונקציה הוא שמו של קובץ התצוגה אותו רוצים להציג. הפונקציה תחפש אחר קובץ התצוגה המתאים תחת הנתיב `protected/views/ControllerID`. + +בתוך קובץ התצוגה, ניתן לגשת לאובייקט הקונטרולר בעזרת האובייקט `this$`. לכן ניתן לגשת לכל מאפיין/משתנה של המחלקה בעזרת קריאה ל `this-»propertyName$` בתוך קובץ התצוגה. + +כמו כן ניתן להשתמש בשיטה הבאה כדי `לדחוף` נתונים אל קובץ התצוגה: + +~~~ +[php] +$this-»render('edit', array( + 'varone'=»$value1, + 'vartwo'=»$value2, +)); +~~~ + + +בקוד המוצג למעלה, הפונקציה [render|CController::render] תחלץ את המערך המוגדר בפרמטר השני כערכים. כתוצאה, בתוך קובץ התצוגה ניתן יהיה לגשת למשתנים `varone$` ו `vartwo$`. + + + +תבנית +------ + +תבנית מהווה כקובץ תצוגה מיוחד הנועד לעטר את קבצי התצוגה הרגילים (המוצגים למעלה). בדרך כלל הוא מכיל חלקים של ממשקי משתמש אשר מוצגים בכל מקום. לדוגמא, קובץ תבנית יכול להכיל את ראש הדף ואת תחתית הדף ולהציג את התוכן באמצע. + +~~~ +[php] +......header here...... +«?php echo $content; ?» +......footer here...... +~~~ + +כשהערך `content$` מכיל את התוצאה של אותם קבצי תצוגה שקראנו להם קודם לכן בעזרת [render|CController::render]. + +תבנית מיושמת ברגע שיש קריאה ל [render|CController::render]. כברירת מחדל, קובץ התצוגה `protected/views/layouts/main.php` מוגדר כקובץ התבנית. ניתן לשנות זאת על ידי שינוי הערך של [CWebApplication::layout] או [CController::layout]. כדי לקרוא לקובץ תצוגה ולהציג אותו ללא קובץ תבנית, יש לקרוא לפונקציה [renderPartial|CController::renderPartial] במקום [render|CController::render]. + +וידג'ט +------ + +וידג'ט הוא אובייקט של [CWidget] או במחלקות הנמצאות תחתיו. זהו רכיב המיועד בעיקר למטרות תצוגה. וידג'טים בדרך כלל מוטמעים בקבצי תצוגה כדי ליצור ממשקים מורכבים אך כלליים. לדוגמא, וידג'ט לוח-שנה יכול לשמש כדי להציג לוח-שנה מורכב בממשק המשתמש. וידג'טים מאפשרים שימוש חוזר טוב יותר לצרכי תצוגה. + +כדי להשתמש ב וידג'ט השתמש בצורה הבאה בקובץ תצוגה: + +~~~ +[php] +«?php $this-»beginWidget('path.to.WidgetClass'); ?» +...תוכן כלשהו שניתן לתפוס בעזרת ה וידג'ט... +«?php $this-»endWidget(); ?» +~~~ + +או + +~~~ +[php] +«?php $this-»widget('path.to.WidgetClass'); ?» +~~~ + +הדוגמא השנייה משמשת כשהוידג'ט לא צריך לתפוס שום תוכן. + +ניתן לשנות את אופן ההתנהלות של הוידג'טים בעת קריאתם. בכדי לעשות זאת יש להגדיר את הערכים הראשוניים של הוידג'ט בזמן הקריאה הראשונה שלהם על ידי [CBaseController::beginWidget] או [CBaseController::widget]. לדוגמא, כשמשתמשים בוידג'ט [CMaskedTextField], אנחנו נרצה להגדיר את המסכה לשימוש. אנחנו מבצעים זאת על ידי העברת מערך כמפרמטר שני להגדרת הערכים הראשוניים כדלקמן, כשמפתחות המערך הם שמות המאפיינים של הוידג'ט והערכים של המערך הם הערכים הראשוניים של הוידג'ט: + +~~~ +[php] +«?php +$this-»widget('CMaskedTextField',array( + 'mask'=»'99/99/9999' +)); +?» +~~~ + +כדי להגדיר וידג'ט חדש, יש ליצור מחלקה אשר יורשת מ [CWidget], וליישם את המתודות [init|CWidget::init] ו [run|CWidget::run] במחלקה: + +~~~ +[php] +class MyWidget extends CWidget +{ + public function init() + { + // this method is called by CController::beginWidget() + } + + public function run() + { + // this method is called by CController::endWidget() + } +} +~~~ + +כמו בקונטרולר, וידג'ט יכול להכיל קובץ תצוגה משלו. כברירת מחדל, קבצי התצוגה של הוידג'ט נמצאים תחת התיקיה `views` בתיקיה הראשית בה המחלקה של הוידג'ט נמצא. ניתן להציג את קבצי התצוגה הללו בעזרת [CWidget::render], בדומה לפעולה הנעשית בקונטרולר. ההבדל היחיד הוא שלא ניתן להגדיר תבנית לתצוגה של הוידג'ט. בנוסף, `this$` בתוך קובץ תצוגה של וידג'ט מכוון אל האובייקט של הוידג'ט במקום האובייקט של הקונטרולר. + +תצוגת מערכת +----------- + +תצוגת מערכת הם קבצי תצוגה אשר Yii משתמשת בהם לצרכי תצוגה של מידע אודות שגיאות ולוגים. לדוגמא, כשמשתמש מבקש קונטרולר לא קיים או פעולה שלא קיימת, Yii יזרוק שגיאה שמסבירה אודות השגיאה שהתרחשה. Yii מציג את השגיאה בתוך קובץ תצוגה מערכתי ספציפי. + +שמות התצוגה של קבצי תצוגת המערכת עוקבים אחר אותם חוקים. שמות כמו `errorXXX` משמשים לתצוגה של שגיאות אשר נזרקות על ידי [CHttpException] כשקוד השגיאה הוא `XXX`. לדוגמא, אם התרחשה שגיאה של [CHttpException] עם קוד שגיאה 404, קובץ התצוגה המערכתי שיוצג יהיה `error404`. + +Yii מספקת קבצי תצוגה מערכתיים כברירת מחדל אשר נמצאים תחת `framework/views`. ניתן להגדיר את קבצי התצוגה באופן עצמאי על ידי יצירת אותם קבצי תצוגה עם אותם השמות תחת התיקיה `protected/views/system`. + + «div class="revision"»$Id: basics.view.txt 1809 2010-02-17 22:08:34Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/basics.workflow.txt b/docs/guide/he/basics.workflow.txt index eab383fb9..01acb33a1 100644 --- a/docs/guide/he/basics.workflow.txt +++ b/docs/guide/he/basics.workflow.txt @@ -1,28 +1,28 @@ -רצף עבודת הפיתוח -==================== - -לאחר הסבר על יסודות Yii, אנחנו מציגים את רצף העבודה הפשוט ביותר לפיתוח אפליקצית ווב בעזרת Yii. הרצף מניח שכבר בצענו את ניתוח הדרישות בנוסף לעיצוב הדרוש לאפליקציה. - -1. יצירת מבנה התיקיות. כלי ה `yiic` אשר מתואר ב [יצירת אפליקצית Yii ראשונה](/doc/guide/quickstart.first-app) יכול לעזור כדי להאיץ את שלב זה. - -2. הגדרת [האפליקציה](/doc/guide/basics.application). שלב זה נעשה על ידי עריכת קובץ ההגדרות של האפליקציה. שלב זה יכול לדרוש גם הוספה של כמה רכיבי אפליקציה (לדוגמא רכיב המשתמשים). - -3. יצירת מחלקה [למודל](/doc/guide/basics.model) לכל סוג של מידע לניהול. שוב, ניתן להשתמש בכלי `yiic` כדי ליצור אוטומטית את מחלקות ה [AR](/doc/guide/database.ar) לכל טבלה שצריך במסד הנתונים. - -4. יצירת [קונטרולר](/doc/guide/basics.controller) לכל סוג של בקשת משתמש. מיון בקשות המשתמש תלויות בדרישות מסויימות. באופן כללי, במידה ומחלקה למודל צריכה להיות נגישה למשתמשים, יש צורך ביצירה של קונטרולר תואם למחלקה. כלי ה `yiic` יכול לבצע אוטומטית פעולה זו גם כן. - -5. יישום [פעולות](/doc/guide/basics.controller#action) וקבצי [התצוגה](/doc/guide/basics.view) התואמים שלהם. בשלב זה העבודה האמיתית מתבצעת. - -6. הגדרת [פילטרים](/doc/guide/basics.controller#filter) נחוצים לפעולות בקונטרולר. - -7. יצירת [תבניות](/doc/guide/topics.theming) במידה ואפשרות התבניות היא הכרחית. - -8. יצירת הודעות מתורגמות במידה ויש צורך [בתמיכה בכמה שפות](/doc/guide/topics.i18n). - -9. איתור מידע וקבצי תצוגה שניתן לשמור אותם במטמון ויישם טכניקות [מטמון](/doc/guide/caching.overview) מתאימות. - -10. [כיוונים אחרונים](/doc/guide/topics.performance) ופריסה. - -לכל אחד מהשלבים למעלה, יש צורך ביצירת בדיקות והרצתם. - +רצף עבודת הפיתוח +==================== + +לאחר הסבר על יסודות Yii, אנחנו מציגים את רצף העבודה הפשוט ביותר לפיתוח אפליקצית ווב בעזרת Yii. הרצף מניח שכבר בצענו את ניתוח הדרישות בנוסף לעיצוב הדרוש לאפליקציה. + +1. יצירת מבנה התיקיות. כלי ה `yiic` אשר מתואר ב [יצירת אפליקצית Yii ראשונה](/doc/guide/quickstart.first-app) יכול לעזור כדי להאיץ את שלב זה. + +2. הגדרת [האפליקציה](/doc/guide/basics.application). שלב זה נעשה על ידי עריכת קובץ ההגדרות של האפליקציה. שלב זה יכול לדרוש גם הוספה של כמה רכיבי אפליקציה (לדוגמא רכיב המשתמשים). + +3. יצירת מחלקה [למודל](/doc/guide/basics.model) לכל סוג של מידע לניהול. שוב, ניתן להשתמש בכלי `yiic` כדי ליצור אוטומטית את מחלקות ה [AR](/doc/guide/database.ar) לכל טבלה שצריך במסד הנתונים. + +4. יצירת [קונטרולר](/doc/guide/basics.controller) לכל סוג של בקשת משתמש. מיון בקשות המשתמש תלויות בדרישות מסויימות. באופן כללי, במידה ומחלקה למודל צריכה להיות נגישה למשתמשים, יש צורך ביצירה של קונטרולר תואם למחלקה. כלי ה `yiic` יכול לבצע אוטומטית פעולה זו גם כן. + +5. יישום [פעולות](/doc/guide/basics.controller#action) וקבצי [התצוגה](/doc/guide/basics.view) התואמים שלהם. בשלב זה העבודה האמיתית מתבצעת. + +6. הגדרת [פילטרים](/doc/guide/basics.controller#filter) נחוצים לפעולות בקונטרולר. + +7. יצירת [תבניות](/doc/guide/topics.theming) במידה ואפשרות התבניות היא הכרחית. + +8. יצירת הודעות מתורגמות במידה ויש צורך [בתמיכה בכמה שפות](/doc/guide/topics.i18n). + +9. איתור מידע וקבצי תצוגה שניתן לשמור אותם במטמון ויישם טכניקות [מטמון](/doc/guide/caching.overview) מתאימות. + +10. [כיוונים אחרונים](/doc/guide/topics.performance) ופריסה. + +לכל אחד מהשלבים למעלה, יש צורך ביצירת בדיקות והרצתם. + «div class="revision"»$Id: basics.workflow.txt 1034 2009-05-19 21:33:55Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/caching.data.txt b/docs/guide/he/caching.data.txt index 12689d3e9..8b618d30c 100644 --- a/docs/guide/he/caching.data.txt +++ b/docs/guide/he/caching.data.txt @@ -1,78 +1,78 @@ -שמירת נתונים -============ - -שמירת נתונים במטמון היא דרך לשמירת משתנה PHP במטמון וקבלתו בזמן מאוחר יותר. למטרה זו, המחלקה הבסיסית של המטמון [CCache] מספקת שני מתודות עם השימוש הנפוץ ביותר: [set|CCache::set] ו [get|CCache::get]. - -כדי לשמור את המשתנה `value$` במטמון, אנו בוחרים מזהה יחודי כלשהו וקוראים למתודה [set|CCache::set] כדי לשמור אותה: - -~~~ -[php] -Yii::app()-»cache-»set($id, $value); -~~~ - -התוכן הנשמר יאוחסן במטמון לעד אלה אם כן הוא ימחק מסיבות שאינן תלויות באפליקציה (לדוגמא, מקום האחסון של המטמון מלא והפריטים הישנים ביותר שנשמרו ימחקו). כדי לשנות התנהגות זו, אנו יכולים להוסיף פרמטר שלישי אשר יגדיר את תפוקת הנתונים אותם אנו שומרים בזמן קריאה ל [set|CCache::set] כדי שהתוכן שנשמר במטמון ימחק אחרי פרק זמן מסויים: - -~~~ -[php] -// keep the value in cache for at most 30 seconds -Yii::app()-»cache-»set($id, $value, 30); -~~~ - -בזמן מאוחר יותר כשאנו צריכים לגשת למשתנה זה (בבקשה הנוכחית או אחרת), אנו קוראים למתודה א עם אותו מזהה יחודי בו שמרנו את הנתונים כדי לקבל אותם מהמטמון. אם הערך שהתקבל מהקריאה שווה ל `false`, זה אומר שהנתון לא נמצא במטמון ועלינו ליצור אותו שוב פעם. - -~~~ -[php] -$value=Yii::app()-»cache-»get($id); -if($value===false) -{ - // regenerate $value because it is not found in cache - // and save it in cache for later use: - // Yii::app()-»cache-»set($id,$value); -} -~~~ - -‎כשבוחרים מזהה יחודי בתור השם של המטמון אותו שומרים, וודא שאותו שם הוא יחודי בקרב כל שאר המשתנים הניתנים לשמירה במטמון באפליקציה. אין צורך שהשם יהיה יחודי בין אפליקציות שונות מאחר ורכיב זה מספיק חכם כדי להבדיל בין האפליקציות בהם הוא רץ. - -חלק מאפשרויות האחסון, כמו MemCache, APC, תומכים באפשרות של קבלת כמה נתונים מהמטמון בקריאה אחת כקבוצה, פעולה זו חוסכת בזמן הלקוח לקבלת נתון מהמטמון. מגרסא 1.0.8, ישנה מתודה חדשה בשם [mget|CCache::mget] כדי ליישם אפשרות זו. במידה ורכיב האחסון בו משתמשים לא תומך באפשרות זו כברירת מחדל, האפליקציה תחקה את הפעולה שהמתודה [mget|CCache::mget] מבצעת. - -כדי למחוק נתון מהמטמון, יש לקרוא למתודה [delete|CCache::delete]; ובכדי להוריד את כל הנתונים מהמטמון יש לקרוא למתודה [flush|CCache::flush]. יש להזהר בעת השימוש במתודה [flush|CCache::flush] מאחר והיא מוחקת נתונים במטמון מאפליקציות אחרות. - -» Tip|טיפ: מאחר והמחלקה [CCache] מיישמת את הממשק `ArrayAccess`, ניתן להשתמש במטמון בתור מערך. להלן כמה דוגמאות: -» ~~~ -» [php] -» $cache=Yii::app()-»cache; -» $cache['var1']=$value1; // equivalent to: $cache-»set('var1',$value1); -» $value2=$cache['var2']; // equivalent to: $value2=$cache-»get('var2'); -» ~~~ - -הגדרת תלות למטמון ----------------- - -מלבד הגדרת זמן קיום המטמון, ניתן להגדיר תלות מסויימת לנתונים הנשמרים במטמון כדי `לבטל` את אותם נתונים שנשמרו. לדוגמא, אם אנו שומרים תוכן של קובץ מסויים במטמון והקובץ ההוא השתנה, אנו צריכים לבטל את הנתון השמור במטמון ולשמור את תוכן הקובץ במטמון שוב פעם. - -אנו מייצגים תלות באובייקט של [CCacheDependency] או מחלקות היורשות ממנו. אנו קוראים לתלות מסויימת בזמן קריאה למתודה [set|CCache::set] והגדרתו כפרמטר רבעי. - -~~~ -[php] -// the value will expire in 30 seconds -// it may also be invalidated earlier if the dependent file is changed -Yii::app()-»cache-»set($id, $value, 30, new CFileCacheDependency('FileName')); -~~~ - -כעת, אם נשלוף את המשתנה `value$` מתוך המטמון על ידי קריאה למתודה [get|CCache::get], התלות תכנס לפעולה ובמידה והקובץ השתנה, אנו נקבל ערך השווה ל `false`, האומר שצריך ליצור מחדש את התוכן השמור במטמון. - -למטה רשומים בקצרה התלויות הקיימות למטמון: - -- [CFileCacheDependency]: התלות משתנה אם הזמן האחרון בו הקובץ נערך השתנה. - -- [CDirectoryCacheDependency]: התלות משתנה אם ישנו שינוי בכל אחד מהקבצים הנמצאים בתיקיה ותתי-תיקיות. - -- [CDbCacheDependency]: התלות משתנה במידה והתוצאה של השאילתה השתנתה. - -- [CGlobalStateCacheDependency]: התלות משתנה במידה והערך הגלובלי שהוזן השתנה. משתנה גלובלי באפליקציה הינו משתנה אשר קיים לאורך כל האפליקציה. הוא מוגדר על ידי [CApplication::setGlobalState]. - -- [CChainedCacheDependency]: התלות משתנה במידה וכל אחד מהתלויות המשורשרות משתנה. - -- [CExpressionDependency]: התלות משתנה במידה והתוצאה של ביטוי ה PHP היא שונה. מחלקה זו קיימת מגרסאות 1.0.4 ומעלה. - +שמירת נתונים +============ + +שמירת נתונים במטמון היא דרך לשמירת משתנה PHP במטמון וקבלתו בזמן מאוחר יותר. למטרה זו, המחלקה הבסיסית של המטמון [CCache] מספקת שני מתודות עם השימוש הנפוץ ביותר: [set|CCache::set] ו [get|CCache::get]. + +כדי לשמור את המשתנה `value$` במטמון, אנו בוחרים מזהה יחודי כלשהו וקוראים למתודה [set|CCache::set] כדי לשמור אותה: + +~~~ +[php] +Yii::app()-»cache-»set($id, $value); +~~~ + +התוכן הנשמר יאוחסן במטמון לעד אלה אם כן הוא ימחק מסיבות שאינן תלויות באפליקציה (לדוגמא, מקום האחסון של המטמון מלא והפריטים הישנים ביותר שנשמרו ימחקו). כדי לשנות התנהגות זו, אנו יכולים להוסיף פרמטר שלישי אשר יגדיר את תפוקת הנתונים אותם אנו שומרים בזמן קריאה ל [set|CCache::set] כדי שהתוכן שנשמר במטמון ימחק אחרי פרק זמן מסויים: + +~~~ +[php] +// keep the value in cache for at most 30 seconds +Yii::app()-»cache-»set($id, $value, 30); +~~~ + +בזמן מאוחר יותר כשאנו צריכים לגשת למשתנה זה (בבקשה הנוכחית או אחרת), אנו קוראים למתודה א עם אותו מזהה יחודי בו שמרנו את הנתונים כדי לקבל אותם מהמטמון. אם הערך שהתקבל מהקריאה שווה ל `false`, זה אומר שהנתון לא נמצא במטמון ועלינו ליצור אותו שוב פעם. + +~~~ +[php] +$value=Yii::app()-»cache-»get($id); +if($value===false) +{ + // regenerate $value because it is not found in cache + // and save it in cache for later use: + // Yii::app()-»cache-»set($id,$value); +} +~~~ + +‎כשבוחרים מזהה יחודי בתור השם של המטמון אותו שומרים, וודא שאותו שם הוא יחודי בקרב כל שאר המשתנים הניתנים לשמירה במטמון באפליקציה. אין צורך שהשם יהיה יחודי בין אפליקציות שונות מאחר ורכיב זה מספיק חכם כדי להבדיל בין האפליקציות בהם הוא רץ. + +חלק מאפשרויות האחסון, כמו MemCache, APC, תומכים באפשרות של קבלת כמה נתונים מהמטמון בקריאה אחת כקבוצה, פעולה זו חוסכת בזמן הלקוח לקבלת נתון מהמטמון. מגרסא 1.0.8, ישנה מתודה חדשה בשם [mget|CCache::mget] כדי ליישם אפשרות זו. במידה ורכיב האחסון בו משתמשים לא תומך באפשרות זו כברירת מחדל, האפליקציה תחקה את הפעולה שהמתודה [mget|CCache::mget] מבצעת. + +כדי למחוק נתון מהמטמון, יש לקרוא למתודה [delete|CCache::delete]; ובכדי להוריד את כל הנתונים מהמטמון יש לקרוא למתודה [flush|CCache::flush]. יש להזהר בעת השימוש במתודה [flush|CCache::flush] מאחר והיא מוחקת נתונים במטמון מאפליקציות אחרות. + +» Tip|טיפ: מאחר והמחלקה [CCache] מיישמת את הממשק `ArrayAccess`, ניתן להשתמש במטמון בתור מערך. להלן כמה דוגמאות: +» ~~~ +» [php] +» $cache=Yii::app()-»cache; +» $cache['var1']=$value1; // equivalent to: $cache-»set('var1',$value1); +» $value2=$cache['var2']; // equivalent to: $value2=$cache-»get('var2'); +» ~~~ + +הגדרת תלות למטמון +---------------- + +מלבד הגדרת זמן קיום המטמון, ניתן להגדיר תלות מסויימת לנתונים הנשמרים במטמון כדי `לבטל` את אותם נתונים שנשמרו. לדוגמא, אם אנו שומרים תוכן של קובץ מסויים במטמון והקובץ ההוא השתנה, אנו צריכים לבטל את הנתון השמור במטמון ולשמור את תוכן הקובץ במטמון שוב פעם. + +אנו מייצגים תלות באובייקט של [CCacheDependency] או מחלקות היורשות ממנו. אנו קוראים לתלות מסויימת בזמן קריאה למתודה [set|CCache::set] והגדרתו כפרמטר רבעי. + +~~~ +[php] +// the value will expire in 30 seconds +// it may also be invalidated earlier if the dependent file is changed +Yii::app()-»cache-»set($id, $value, 30, new CFileCacheDependency('FileName')); +~~~ + +כעת, אם נשלוף את המשתנה `value$` מתוך המטמון על ידי קריאה למתודה [get|CCache::get], התלות תכנס לפעולה ובמידה והקובץ השתנה, אנו נקבל ערך השווה ל `false`, האומר שצריך ליצור מחדש את התוכן השמור במטמון. + +למטה רשומים בקצרה התלויות הקיימות למטמון: + +- [CFileCacheDependency]: התלות משתנה אם הזמן האחרון בו הקובץ נערך השתנה. + +- [CDirectoryCacheDependency]: התלות משתנה אם ישנו שינוי בכל אחד מהקבצים הנמצאים בתיקיה ותתי-תיקיות. + +- [CDbCacheDependency]: התלות משתנה במידה והתוצאה של השאילתה השתנתה. + +- [CGlobalStateCacheDependency]: התלות משתנה במידה והערך הגלובלי שהוזן השתנה. משתנה גלובלי באפליקציה הינו משתנה אשר קיים לאורך כל האפליקציה. הוא מוגדר על ידי [CApplication::setGlobalState]. + +- [CChainedCacheDependency]: התלות משתנה במידה וכל אחד מהתלויות המשורשרות משתנה. + +- [CExpressionDependency]: התלות משתנה במידה והתוצאה של ביטוי ה PHP היא שונה. מחלקה זו קיימת מגרסאות 1.0.4 ומעלה. + «div class="revision"»$Id: caching.data.txt 1855 2010-03-04 22:42:32Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/caching.dynamic.txt b/docs/guide/he/caching.dynamic.txt index eb65e99d1..a1da56c82 100644 --- a/docs/guide/he/caching.dynamic.txt +++ b/docs/guide/he/caching.dynamic.txt @@ -1,25 +1,25 @@ -תוכן דינאמי -=============== - -כשמשתמשים [שמירת חלקים](/doc/guide/caching.fragment) או [שמירת עמוד שלם](/doc/guide/caching.page), אנו בדרך כלל נתקלים במצבים איפה שכל העמוד הוא יחסית תוכן סטטי מלבד חלק אחד או יותר. לדוגמא, עמוד עזרה מציג מידע סטטי אודות עזרה באתר אבל עם שמו של המשתמש שגרגע מחובר לאתר מוצג בראש העמוד. - -כדי לפתור בעיה זו, ניתן לשמור את התוכן במטמון על פי שם המשתמש, אך זה יהיה בזבוז מקום האחסון היקר של המטמון בו אנו משתמשים מאחר והתוכן הוא זהה בכל צורה מלבד שם המשתמש. ניתן גם לחלק את העמוד לחתיכות ולשמור אותם בנפרד, אך זה מסבך את קובץ התצוגה שלנו והקוד שלנו נראה מאוד מורכב. שיטה טובה יותר היא להשתמש באפשרות השמירה של *התוכן הדינאמי* הקיים ב [CController]. - -תוכן דינאמי הוא חתיכת תוכן שלא צריך להשמר במטמון גם אם הוא מוקף בקוד השומר חתיכת תוכן במטמון. כדי שהתוכן יהיה תמיד דינאמי, הוא צריך להווצר כל הזמן גם אם התוכן בו הוא נמצא נשלף מתוך המטמון. מסיבה זו, אנו דורשים שתוכן דינאמי יווצר על ידי מתודה או פונקציה כלשהי. - -אנו קוראים ל [CController::renderDynamic] כדי להכניס תוכן דינאמי במקום הרצוי. - -~~~ -[php] -...other HTML content... -«?php if($this-»beginCache($id)) { ?» -...fragment content to be cached... - «?php $this-»renderDynamic($callback); ?» -...fragment content to be cached... -«?php $this-»endCache(); } ?» -...other HTML content... -~~~ - -בדוגמא למעלה, `callback$` מתייחס לפונקציה או מתודה תקנית של PHP. ההגדרה צריכה להיות סטרינג המתייחס למתודה במחלקה הנוכחית או פונקציה גלובלית. כמו כן היא יכולה להיות מערך אשר מתייחס למתודה במחלקה. כל הפרמטרים הנוספים שיוגדרו [renderDynamic|CController::renderDynamic] יועברו לפונקציה/מתודה אשר הוגדרה. הפונקציה/מתודה שהוגדרה צריכה להחזיר את התוכן ולא להציג אותו. - +תוכן דינאמי +=============== + +כשמשתמשים [שמירת חלקים](/doc/guide/caching.fragment) או [שמירת עמוד שלם](/doc/guide/caching.page), אנו בדרך כלל נתקלים במצבים איפה שכל העמוד הוא יחסית תוכן סטטי מלבד חלק אחד או יותר. לדוגמא, עמוד עזרה מציג מידע סטטי אודות עזרה באתר אבל עם שמו של המשתמש שגרגע מחובר לאתר מוצג בראש העמוד. + +כדי לפתור בעיה זו, ניתן לשמור את התוכן במטמון על פי שם המשתמש, אך זה יהיה בזבוז מקום האחסון היקר של המטמון בו אנו משתמשים מאחר והתוכן הוא זהה בכל צורה מלבד שם המשתמש. ניתן גם לחלק את העמוד לחתיכות ולשמור אותם בנפרד, אך זה מסבך את קובץ התצוגה שלנו והקוד שלנו נראה מאוד מורכב. שיטה טובה יותר היא להשתמש באפשרות השמירה של *התוכן הדינאמי* הקיים ב [CController]. + +תוכן דינאמי הוא חתיכת תוכן שלא צריך להשמר במטמון גם אם הוא מוקף בקוד השומר חתיכת תוכן במטמון. כדי שהתוכן יהיה תמיד דינאמי, הוא צריך להווצר כל הזמן גם אם התוכן בו הוא נמצא נשלף מתוך המטמון. מסיבה זו, אנו דורשים שתוכן דינאמי יווצר על ידי מתודה או פונקציה כלשהי. + +אנו קוראים ל [CController::renderDynamic] כדי להכניס תוכן דינאמי במקום הרצוי. + +~~~ +[php] +...other HTML content... +«?php if($this-»beginCache($id)) { ?» +...fragment content to be cached... + «?php $this-»renderDynamic($callback); ?» +...fragment content to be cached... +«?php $this-»endCache(); } ?» +...other HTML content... +~~~ + +בדוגמא למעלה, `callback$` מתייחס לפונקציה או מתודה תקנית של PHP. ההגדרה צריכה להיות סטרינג המתייחס למתודה במחלקה הנוכחית או פונקציה גלובלית. כמו כן היא יכולה להיות מערך אשר מתייחס למתודה במחלקה. כל הפרמטרים הנוספים שיוגדרו [renderDynamic|CController::renderDynamic] יועברו לפונקציה/מתודה אשר הוגדרה. הפונקציה/מתודה שהוגדרה צריכה להחזיר את התוכן ולא להציג אותו. + «div class="revision"»$Id: caching.dynamic.txt 163 2008-11-05 12:51:48Z weizhuo $«/div» \ No newline at end of file diff --git a/docs/guide/he/caching.fragment.txt b/docs/guide/he/caching.fragment.txt index ea1629937..5b8196c70 100644 --- a/docs/guide/he/caching.fragment.txt +++ b/docs/guide/he/caching.fragment.txt @@ -1,103 +1,103 @@ -שמירת חתיכות במטמון -================ - -שמירת חתיכות במטמון מתייחסת לשמירה של חתיכות תוכן בעמוד. לדוגמא, אם העמוד מציג טבלה של סך הכל מכירות לשנה הזו, אנו יכולים לשמור את הטבלה במטמון כדי לחסוך את הזמן שדרוש כדי ליצור אותה בכל בקשה. - -כדי לשמור חתיכה במטמון, אנו קוראים ל [CController::beginCache|CBaseController::beginCache] ו [CController::endCache|CBaseController::endCache] בתוך קובץ תצוגה של קונטרולר. שני המתודות מסמנות את תחילתו וסופו של אותה חתיכה שצריכה להשמר במטמון. בדומה ל [שמירת נתונים](/doc/guide/caching.data), יש צורך בשם כמזהה יחודי של ערך מטמון זה לצורך זיהוי המטמון. - -~~~ -[php] -...other HTML content... -«?php if($this-»beginCache($id)) { ?» -...content to be cached... -«?php $this-»endCache(); } ?» -...other HTML content... -~~~ - -בדוגמא למעלה, אם [beginCache|CBaseController::beginCache] מחזיר ערך השווה ל `false`, התוכן השמור במטמון יוכנס במקום בצורה אוטומטית; אחרת, התוכן בתוך פקודת ה `if` יורץ וישמר במטמון בעת הגעת הקוד למתודה [endCache|CBaseController::endCache]. - -אפשרויות מטמון ---------------- - -בעת הקריאה ל [beginCache|CBaseController::beginCache], אנו יכולים להעביר מערך כפרמטר שני למתודה המכיל אפשרויות לאופן השמירה של החתיכה במטמון. למעשה, המתודות [beginCache|CBaseController::beginCache] ו [endCache|CBaseController::endCache] הם מעטפת נוחה של הוידג'ט [COutputCache]. לכן, אפשרויות המטמון יכולות להיות ערכים המגדירים את כל המאפיינים הקיימים ב [COutputCache]. - -### תקופת קיום - -אולי האפשרות הנפוצה ביותר הינה [תקופת הזמן|COutputCache::duration] המגדירה את משך הזמן בו התוכן הנשמר במטמון תקף. הוא דומה לפרמטר התפוגה של המתודה [CCache::set]. הקוד הבא שומר את תוכן החתיכה במטמון לפרק זמן של עד שעה: - -~~~ -[php] -...other HTML content... -«?php if($this-»beginCache($id, array('duration'=»3600))) { ?» -...content to be cached... -«?php $this-»endCache(); } ?» -...other HTML content... -~~~ - -אם אנו לא מגדירים תקופת קיום, היא תוגדר לברירת מחדל של 60, האומר שהתוכן הנשמר במטמון ימחק לאחר 60 שניות. - -### תלות - -בדומה [מטמון נתונים](/doc/guide/caching.data), ניתן להגדיר לחתיכות הנשמרות במטמון תלות כלשהי. לדוגמא, התוכן של ההודעה המוצגת תלוי במידה וההודעה השתנתה. - -כדי להגדיר תלות, אנו מגדירים את המאפיין [dependency|COutputCache::dependency], אשר יכול להיות אובייקט של מחלקה המיישמת את [ICacheDependency] או מערך של הגדרות אשר יכול ליצור את אובייקט התלות. הקוד הבא מציין את החתיכה הנשמרת במטמון בהתבסס על השינוי של הערך של המאפיין `lastModified`: - -~~~ -[php] -...other HTML content... -«?php if($this-»beginCache($id, array('dependency'=»array( - 'class'=»'system.caching.dependencies.CDbCacheDependency', - 'sql'=»'SELECT MAX(lastModified) FROM Post')))) { ?» -...content to be cached... -«?php $this-»endCache(); } ?» -...other HTML content... -~~~ - -### גיוון (וריאציה) - -ניתן לשמור את התוכן בהתבסס על פרמטרים מסויים כדי לגוון זאת. לדוגמא, הפרופיל האישי יכול להראות שונה למשתמשים שונים. כדי לשמור את הפרופיל האישי במטמון, אנו נרצה שהתוכן הנשמר במטמון ישמר בהתבסס על מספר המשתמש הצופה בו כרגע. זה אומר שאנו צריכים להשתמש בשמות שונים בעת קראיה ל [beginCache()|CBaseController::beginCache()]. - -במקום לבקש מהמתכנתים לגוון במזהים היחודיים בהתבסס על תבנית מסויימת, [COutputCache] כבר מכילה אפשרות כזו מובנית. למטה תקציר בנוגע לשימוש באפשרות זו. - -- [varyByRoute|COutputCache::varyByRoute]: על ידי הגדרת אפשרות זו ל `true`, התוכן הנשמר במטמון יגוון על ידי [הקישור](/doc/guide/basics.controller#route). לכן, כל קומבינציה של פעולה וקונטרולר התוכן שישמר יהיה שונה. - -- [varyBySession|COutputCache::varyBySession]: על ידי הגדרת אפשרות זו ל `true`, התוכן הנשמר במטמון יגוון על ידי המזהה היחודי (session) של המשתמש. לכן, כל משתמש יראה תוכן שונה המגיע ישירות מהמטמון. - -- [varyByParam|COutputCache::varyByParam]: על ידי הגדרת אפשרות זו למערך של שמות, ניתן לגוון את המטמון שנשמר בהתבסס על הערכים המגיעים מהפרמטרים של GET. לדוגמא, אם עמוד מציג את התוכן של ההודעה על פי מספרה - `id` המגיע מהפרמטר בקישור (GET) , ניתן להגדיר את [varyByParam|COutputCache::varyByParam] בתור `array('id')` כדי שכל הודעה תשמר במטמון. ללא גיוון זה ושמירה על פי הפרמטר, יכולנו לשמור הודעה אחת בלבד במטמון. - -- [varyByExpression|COutputCache::varyByExpression]: על ידי הגדרת אפשרות זו לביטוי ב PHP, ניתן לגוון את התוכן הנשמר בהתבסס על התוצאה של הביטוי. אפשרות זו קיימת מגרסאות 1.0.4 ומעלה. - -### סוגי בקשות - -לפעמים אנו נרצה לשמור חתיכה במטמון רק לסוגי בקשות מסויימות (GET או POST). לדוגמא, עמוד המציג טופס, אנו רוצים לשמור אותו רק כשהוא מבוקש לראשונה (דרך GET). כל שאר הבקשות לאותו הטופס (דרך POST) לא צריכות להשמר במטמון מאחר והם יכולים להכיל מידע שהמשתמש הזין. כדי לבצע זאת, אנו יכולים להגדיר את הפרמטר [requestTypes|COutputCache::requestTypes]: - -~~~ -[php] -...other HTML content... -«?php if($this-»beginCache($id, array('requestTypes'=»array('GET')))) { ?» -...content to be cached... -«?php $this-»endCache(); } ?» -...other HTML content... -~~~ - -קינון נתונים --------------- - -ניתן לקנן בעת השימוש בשמירת נתונים במטמון. זאת אומרת, שחתיכה הנשמרת במטמון עטופה בחתיכה גדולה יותר אשר גם היא נשמרת במטמון. לדוגמא, התגובות של הודעה נשמרות במטמון פנימי, והם נשמרות במטמון ביחד עם התוכן של ההודעה במטמון חיצוני. - -~~~ -[php] -...other HTML content... -«?php if($this-»beginCache($id1)) { ?» -...outer content to be cached... - «?php if($this-»beginCache($id2)) { ?» - ...inner content to be cached... - «?php $this-»endCache(); } ?» -...outer content to be cached... -«?php $this-»endCache(); } ?» -...other HTML content... -~~~ - -ניתן להגדיר אפשרויות שונות לכל שמירה במטמון במצב של קינון. לדוגמא, בקוד למעלה השמירה של המטמון הפנימית והחיצונית יכולות להיות מוגדרות עם מאפיין תקופת קיום שונות אחד מהשני. כשהתוכן הנשמר במטמון החיצוני ימחק, התוכן הנשמר במטמון הפנימי עדיין יכול לספק תוכן תקני ועדכני של אותה חתיכה שנשמרה במטמון. למרות שזה לא תקף במקרה ההפוך. במידה והשמירה במטמון החיצוני תקפה וקיימת, היא תמיד תוצג, גם אם התוכן במטמון הפנימי שנשמר כבר לא תקף וצריך להחליפו. - +שמירת חתיכות במטמון +================ + +שמירת חתיכות במטמון מתייחסת לשמירה של חתיכות תוכן בעמוד. לדוגמא, אם העמוד מציג טבלה של סך הכל מכירות לשנה הזו, אנו יכולים לשמור את הטבלה במטמון כדי לחסוך את הזמן שדרוש כדי ליצור אותה בכל בקשה. + +כדי לשמור חתיכה במטמון, אנו קוראים ל [CController::beginCache|CBaseController::beginCache] ו [CController::endCache|CBaseController::endCache] בתוך קובץ תצוגה של קונטרולר. שני המתודות מסמנות את תחילתו וסופו של אותה חתיכה שצריכה להשמר במטמון. בדומה ל [שמירת נתונים](/doc/guide/caching.data), יש צורך בשם כמזהה יחודי של ערך מטמון זה לצורך זיהוי המטמון. + +~~~ +[php] +...other HTML content... +«?php if($this-»beginCache($id)) { ?» +...content to be cached... +«?php $this-»endCache(); } ?» +...other HTML content... +~~~ + +בדוגמא למעלה, אם [beginCache|CBaseController::beginCache] מחזיר ערך השווה ל `false`, התוכן השמור במטמון יוכנס במקום בצורה אוטומטית; אחרת, התוכן בתוך פקודת ה `if` יורץ וישמר במטמון בעת הגעת הקוד למתודה [endCache|CBaseController::endCache]. + +אפשרויות מטמון +--------------- + +בעת הקריאה ל [beginCache|CBaseController::beginCache], אנו יכולים להעביר מערך כפרמטר שני למתודה המכיל אפשרויות לאופן השמירה של החתיכה במטמון. למעשה, המתודות [beginCache|CBaseController::beginCache] ו [endCache|CBaseController::endCache] הם מעטפת נוחה של הוידג'ט [COutputCache]. לכן, אפשרויות המטמון יכולות להיות ערכים המגדירים את כל המאפיינים הקיימים ב [COutputCache]. + +### תקופת קיום + +אולי האפשרות הנפוצה ביותר הינה [תקופת הזמן|COutputCache::duration] המגדירה את משך הזמן בו התוכן הנשמר במטמון תקף. הוא דומה לפרמטר התפוגה של המתודה [CCache::set]. הקוד הבא שומר את תוכן החתיכה במטמון לפרק זמן של עד שעה: + +~~~ +[php] +...other HTML content... +«?php if($this-»beginCache($id, array('duration'=»3600))) { ?» +...content to be cached... +«?php $this-»endCache(); } ?» +...other HTML content... +~~~ + +אם אנו לא מגדירים תקופת קיום, היא תוגדר לברירת מחדל של 60, האומר שהתוכן הנשמר במטמון ימחק לאחר 60 שניות. + +### תלות + +בדומה [מטמון נתונים](/doc/guide/caching.data), ניתן להגדיר לחתיכות הנשמרות במטמון תלות כלשהי. לדוגמא, התוכן של ההודעה המוצגת תלוי במידה וההודעה השתנתה. + +כדי להגדיר תלות, אנו מגדירים את המאפיין [dependency|COutputCache::dependency], אשר יכול להיות אובייקט של מחלקה המיישמת את [ICacheDependency] או מערך של הגדרות אשר יכול ליצור את אובייקט התלות. הקוד הבא מציין את החתיכה הנשמרת במטמון בהתבסס על השינוי של הערך של המאפיין `lastModified`: + +~~~ +[php] +...other HTML content... +«?php if($this-»beginCache($id, array('dependency'=»array( + 'class'=»'system.caching.dependencies.CDbCacheDependency', + 'sql'=»'SELECT MAX(lastModified) FROM Post')))) { ?» +...content to be cached... +«?php $this-»endCache(); } ?» +...other HTML content... +~~~ + +### גיוון (וריאציה) + +ניתן לשמור את התוכן בהתבסס על פרמטרים מסויים כדי לגוון זאת. לדוגמא, הפרופיל האישי יכול להראות שונה למשתמשים שונים. כדי לשמור את הפרופיל האישי במטמון, אנו נרצה שהתוכן הנשמר במטמון ישמר בהתבסס על מספר המשתמש הצופה בו כרגע. זה אומר שאנו צריכים להשתמש בשמות שונים בעת קראיה ל [beginCache()|CBaseController::beginCache()]. + +במקום לבקש מהמתכנתים לגוון במזהים היחודיים בהתבסס על תבנית מסויימת, [COutputCache] כבר מכילה אפשרות כזו מובנית. למטה תקציר בנוגע לשימוש באפשרות זו. + +- [varyByRoute|COutputCache::varyByRoute]: על ידי הגדרת אפשרות זו ל `true`, התוכן הנשמר במטמון יגוון על ידי [הקישור](/doc/guide/basics.controller#route). לכן, כל קומבינציה של פעולה וקונטרולר התוכן שישמר יהיה שונה. + +- [varyBySession|COutputCache::varyBySession]: על ידי הגדרת אפשרות זו ל `true`, התוכן הנשמר במטמון יגוון על ידי המזהה היחודי (session) של המשתמש. לכן, כל משתמש יראה תוכן שונה המגיע ישירות מהמטמון. + +- [varyByParam|COutputCache::varyByParam]: על ידי הגדרת אפשרות זו למערך של שמות, ניתן לגוון את המטמון שנשמר בהתבסס על הערכים המגיעים מהפרמטרים של GET. לדוגמא, אם עמוד מציג את התוכן של ההודעה על פי מספרה - `id` המגיע מהפרמטר בקישור (GET) , ניתן להגדיר את [varyByParam|COutputCache::varyByParam] בתור `array('id')` כדי שכל הודעה תשמר במטמון. ללא גיוון זה ושמירה על פי הפרמטר, יכולנו לשמור הודעה אחת בלבד במטמון. + +- [varyByExpression|COutputCache::varyByExpression]: על ידי הגדרת אפשרות זו לביטוי ב PHP, ניתן לגוון את התוכן הנשמר בהתבסס על התוצאה של הביטוי. אפשרות זו קיימת מגרסאות 1.0.4 ומעלה. + +### סוגי בקשות + +לפעמים אנו נרצה לשמור חתיכה במטמון רק לסוגי בקשות מסויימות (GET או POST). לדוגמא, עמוד המציג טופס, אנו רוצים לשמור אותו רק כשהוא מבוקש לראשונה (דרך GET). כל שאר הבקשות לאותו הטופס (דרך POST) לא צריכות להשמר במטמון מאחר והם יכולים להכיל מידע שהמשתמש הזין. כדי לבצע זאת, אנו יכולים להגדיר את הפרמטר [requestTypes|COutputCache::requestTypes]: + +~~~ +[php] +...other HTML content... +«?php if($this-»beginCache($id, array('requestTypes'=»array('GET')))) { ?» +...content to be cached... +«?php $this-»endCache(); } ?» +...other HTML content... +~~~ + +קינון נתונים +-------------- + +ניתן לקנן בעת השימוש בשמירת נתונים במטמון. זאת אומרת, שחתיכה הנשמרת במטמון עטופה בחתיכה גדולה יותר אשר גם היא נשמרת במטמון. לדוגמא, התגובות של הודעה נשמרות במטמון פנימי, והם נשמרות במטמון ביחד עם התוכן של ההודעה במטמון חיצוני. + +~~~ +[php] +...other HTML content... +«?php if($this-»beginCache($id1)) { ?» +...outer content to be cached... + «?php if($this-»beginCache($id2)) { ?» + ...inner content to be cached... + «?php $this-»endCache(); } ?» +...outer content to be cached... +«?php $this-»endCache(); } ?» +...other HTML content... +~~~ + +ניתן להגדיר אפשרויות שונות לכל שמירה במטמון במצב של קינון. לדוגמא, בקוד למעלה השמירה של המטמון הפנימית והחיצונית יכולות להיות מוגדרות עם מאפיין תקופת קיום שונות אחד מהשני. כשהתוכן הנשמר במטמון החיצוני ימחק, התוכן הנשמר במטמון הפנימי עדיין יכול לספק תוכן תקני ועדכני של אותה חתיכה שנשמרה במטמון. למרות שזה לא תקף במקרה ההפוך. במידה והשמירה במטמון החיצוני תקפה וקיימת, היא תמיד תוצג, גם אם התוכן במטמון הפנימי שנשמר כבר לא תקף וצריך להחליפו. + «div class="revision"»$Id: caching.fragment.txt 956 2009-04-21 15:16:03Z qiang.xue@gmail.com $«/div» \ No newline at end of file diff --git a/docs/guide/he/caching.overview.txt b/docs/guide/he/caching.overview.txt index 9335a7b69..df6c9aa6e 100644 --- a/docs/guide/he/caching.overview.txt +++ b/docs/guide/he/caching.overview.txt @@ -1,59 +1,59 @@ -‎מטמון -======= - -‎מטמון הינה דרך קלה ויעילה לשיפור ביצועי אפליקצית ווב‫.‬ על ידי אחסון תוכן סטטי בתוך המטמון וקריאתו בעת הצורך‫,‬ אנו חוסכים את הזמן הדרוש כדי ליצור את אותו תוכן‫.‬ - -‎שימוש במטמון ב Yii בדרך כלל דורש הגדרת רכיב מטמון וגישה אליו‫.‬ הגדרות האפליקציה הבאות מגדירות רכיב מטמון המשתמש בשיטת מטמון של memcache עם שני שרתים לאחסון התוכן במטמון‫.‬ - -~~~ -[php] -array( - ...... - 'components'=»array( - ...... - 'cache'=»array( - 'class'=»'system.caching.CMemCache', - 'servers'=»array( - array('host'=»'server1', 'port'=»11211, 'weight'=»60), - array('host'=»'server2', 'port'=»11211, 'weight'=»40), - ), - ), - ), -); -~~~ - -‎בזמן הרצת האפליקציה‫,‬ ניתן לגשת לרכיב המטמון בעזרת `Yii::app()-»cache`‫.‬ - -Yii מספקת רכיבי מטמון שונים אשר מאפשרים שמירה של תוכן במטמון במדיות שונות‫.‬ -‎לדוגמא‫,‬ רכיב ה [CMemCache] משתמשת בתוסף של PHP בשם memcache כדי להשתמש בזכרון כמדית שמירת המטמון‫; ‬הרכיב [CApcCache] משתמש בתוסף ה PHP בשם APC‫;‬ -‎ורכיב [CDbCache] שומר את המטמון במסד הנתונים‫.‬ -‎להלן סיכום של רכיבי המטמון הניתנים לשימוש ב Yii‫:‬ - -- [CMemCache] משתמש בתוסף של PHP בשם [memcache](http://www.php.net/manual/en/book.memcache.php). - -- [CApcCache] משתמש בתוסף של PHP בשם [APC](http://www.php.net/manual/en/book.apc.php). - -- [CXCache] משתמש בתוסף של PHP בשם [XCache](http://xcache.lighttpd.net/). הערה, רכיב זה קיים מגרסאות 1.0.1 ומעלה. - -- [CEAcceleratorCache] משתמש בתוסף של PHP בשם [EAccelerator](http://eaccelerator.net/). - -- [CDbCache] משתמש בטבלה במסד הנתונים כדי לשמור את התוכן. כברירת מחדל זה יוצר ומשתמש במסד נתונים מסוג SQLite3 תחת תיקית הקבצים הזמניים באפליקציה. תוכל להגדיר את סוג מסד הנתונים לשימוש על ידי הגדרת המאפיין [connectionID|CDbCache::connectionID]. - -- [CZendDataCache] משתמש ברכיב של PHP בשם Zend Data Cache בתור המדיה לשמירה. הערה, רכיב זה קיים מגרסא 1.0.4 ומעלה. - -- [CFileCache] משתמש בקבצים כדי לאחסן את התוכן השמור במטמון. זה מתאים במיוחד כדי לשמור חלקים גדולים של תוכן (כמו לדוגמא עמודים שלמים). הערה, רכיב זה קיים מגרסא 1.0.6 ומעלה. - -- [CDummyCache] מייצג רכיב מטמון דמה אשר לא מאחסן ולא שומר כלום במטמון. המטרה של רכיב זה היא לפשט את הקוד שדורש לבדוק אם מטמון מסויים קיים. לדוגמא, במהלך פיתוח או במידה והשרת לא תומך באותו רכיב מטמון שרוצים להשתמש, אנו יכולים להשתמש ברכיב מטמון זה. כשרכיב המטמון יהיה קיים ופעיל, ניתן יהיה להחליף לאותו רכיב מטמון בקלות. בשני המקרים, אנו נשתמש באותו הקוד `Yii::app()-»cache-»get($key)` כדי לנסות ולקבל פיסת מידע מבלי לחשוש במידה ו `Yii::app()-»cache` הוא `null`. רכיב זה קיים מגרסא 1.0.5 ומעלה. - - -» Tip|טיפ: מאחר וכל אותם רכיבי מטמון יורשים מאותה מחלקה בסיסית [CCache] ‫,‬ ניתן להחליף‫ את סוג רכיב המטמון ללא שום שינוי בקוד היוצר וקורא את המטמון.‬ - -‎ניתן להשתמש במטמון בכמה רמות שונות‫.‬ ברמה הכי נמוכה‫,‬ אנו נעזרים במטמון כדי לאחסן חתיכת מידע אחת‫,‬ כמו משתנה‫,‬ ואנו קוראים לזו ‫*‬אחסון מידע‫*.‬ ברמה הבאה‫,‬ אנו שומרים במטמון חלק מעמוד הנוצר בעזרת קובץ תצוגה‫.‬ וברמה הגבוה ביותר‫,‬ אנו שומרים עמוד שלם במטמון וטוענים את אותו עמוד מהמטמון בעת הצורך‫.‬ - -‎בתתי נושאים הבאים‫,‬ אנו נרחיב על אופן השימוש ברכיבי המטמון השונים‫.‬ - -» Note|הערה: כברירת מחדל‫,‬ מטמון הוא מדיום שמירה לא אמין‫.‬ הוא לא מוודא את קיומו של התוכן במטמון‫ גם אם זמנו לא עבר.‬ -‎‫לכן, רצוי לא להשתמש במטמון כמדיום שמירה קבוע (לדוגמא, לא להשתמש במטמון לשמירת מידע אודות המשתמש המחובר).‬ - - +‎מטמון +======= + +‎מטמון הינה דרך קלה ויעילה לשיפור ביצועי אפליקצית ווב‫.‬ על ידי אחסון תוכן סטטי בתוך המטמון וקריאתו בעת הצורך‫,‬ אנו חוסכים את הזמן הדרוש כדי ליצור את אותו תוכן‫.‬ + +‎שימוש במטמון ב Yii בדרך כלל דורש הגדרת רכיב מטמון וגישה אליו‫.‬ הגדרות האפליקציה הבאות מגדירות רכיב מטמון המשתמש בשיטת מטמון של memcache עם שני שרתים לאחסון התוכן במטמון‫.‬ + +~~~ +[php] +array( + ...... + 'components'=»array( + ...... + 'cache'=»array( + 'class'=»'system.caching.CMemCache', + 'servers'=»array( + array('host'=»'server1', 'port'=»11211, 'weight'=»60), + array('host'=»'server2', 'port'=»11211, 'weight'=»40), + ), + ), + ), +); +~~~ + +‎בזמן הרצת האפליקציה‫,‬ ניתן לגשת לרכיב המטמון בעזרת `Yii::app()-»cache`‫.‬ + +Yii מספקת רכיבי מטמון שונים אשר מאפשרים שמירה של תוכן במטמון במדיות שונות‫.‬ +‎לדוגמא‫,‬ רכיב ה [CMemCache] משתמשת בתוסף של PHP בשם memcache כדי להשתמש בזכרון כמדית שמירת המטמון‫; ‬הרכיב [CApcCache] משתמש בתוסף ה PHP בשם APC‫;‬ +‎ורכיב [CDbCache] שומר את המטמון במסד הנתונים‫.‬ +‎להלן סיכום של רכיבי המטמון הניתנים לשימוש ב Yii‫:‬ + +- [CMemCache] משתמש בתוסף של PHP בשם [memcache](http://www.php.net/manual/en/book.memcache.php). + +- [CApcCache] משתמש בתוסף של PHP בשם [APC](http://www.php.net/manual/en/book.apc.php). + +- [CXCache] משתמש בתוסף של PHP בשם [XCache](http://xcache.lighttpd.net/). הערה, רכיב זה קיים מגרסאות 1.0.1 ומעלה. + +- [CEAcceleratorCache] משתמש בתוסף של PHP בשם [EAccelerator](http://eaccelerator.net/). + +- [CDbCache] משתמש בטבלה במסד הנתונים כדי לשמור את התוכן. כברירת מחדל זה יוצר ומשתמש במסד נתונים מסוג SQLite3 תחת תיקית הקבצים הזמניים באפליקציה. תוכל להגדיר את סוג מסד הנתונים לשימוש על ידי הגדרת המאפיין [connectionID|CDbCache::connectionID]. + +- [CZendDataCache] משתמש ברכיב של PHP בשם Zend Data Cache בתור המדיה לשמירה. הערה, רכיב זה קיים מגרסא 1.0.4 ומעלה. + +- [CFileCache] משתמש בקבצים כדי לאחסן את התוכן השמור במטמון. זה מתאים במיוחד כדי לשמור חלקים גדולים של תוכן (כמו לדוגמא עמודים שלמים). הערה, רכיב זה קיים מגרסא 1.0.6 ומעלה. + +- [CDummyCache] מייצג רכיב מטמון דמה אשר לא מאחסן ולא שומר כלום במטמון. המטרה של רכיב זה היא לפשט את הקוד שדורש לבדוק אם מטמון מסויים קיים. לדוגמא, במהלך פיתוח או במידה והשרת לא תומך באותו רכיב מטמון שרוצים להשתמש, אנו יכולים להשתמש ברכיב מטמון זה. כשרכיב המטמון יהיה קיים ופעיל, ניתן יהיה להחליף לאותו רכיב מטמון בקלות. בשני המקרים, אנו נשתמש באותו הקוד `Yii::app()-»cache-»get($key)` כדי לנסות ולקבל פיסת מידע מבלי לחשוש במידה ו `Yii::app()-»cache` הוא `null`. רכיב זה קיים מגרסא 1.0.5 ומעלה. + + +» Tip|טיפ: מאחר וכל אותם רכיבי מטמון יורשים מאותה מחלקה בסיסית [CCache] ‫,‬ ניתן להחליף‫ את סוג רכיב המטמון ללא שום שינוי בקוד היוצר וקורא את המטמון.‬ + +‎ניתן להשתמש במטמון בכמה רמות שונות‫.‬ ברמה הכי נמוכה‫,‬ אנו נעזרים במטמון כדי לאחסן חתיכת מידע אחת‫,‬ כמו משתנה‫,‬ ואנו קוראים לזו ‫*‬אחסון מידע‫*.‬ ברמה הבאה‫,‬ אנו שומרים במטמון חלק מעמוד הנוצר בעזרת קובץ תצוגה‫.‬ וברמה הגבוה ביותר‫,‬ אנו שומרים עמוד שלם במטמון וטוענים את אותו עמוד מהמטמון בעת הצורך‫.‬ + +‎בתתי נושאים הבאים‫,‬ אנו נרחיב על אופן השימוש ברכיבי המטמון השונים‫.‬ + +» Note|הערה: כברירת מחדל‫,‬ מטמון הוא מדיום שמירה לא אמין‫.‬ הוא לא מוודא את קיומו של התוכן במטמון‫ גם אם זמנו לא עבר.‬ +‎‫לכן, רצוי לא להשתמש במטמון כמדיום שמירה קבוע (לדוגמא, לא להשתמש במטמון לשמירת מידע אודות המשתמש המחובר).‬ + + «div class="revision"»$Id: caching.overview.txt 2005 2010-03-04 22:42:32Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/caching.page.txt b/docs/guide/he/caching.page.txt index adbe6fe3f..b4736b33c 100644 --- a/docs/guide/he/caching.page.txt +++ b/docs/guide/he/caching.page.txt @@ -1,31 +1,31 @@ -שמירת עמוד במטמון -============ - -שמירת עמוד במטמון מתייחס לשמירת כל התוכן של עמוד שלם במטמון. שמירת עמוד יכולה להתבצע במקומות שונים. לדוגמא, על ידי בחירת כותרת עמוד מתאימה, הדפדפן של הלקוח יכול לשמור את העמוד הנצפה כרגע לפרק זמן מוגבל. האפליקציה עצמה יכולה לשמור את תוכן העמוד במטמון גם כן. בתת נושא זה, אנו נתמקד על האפשרות השנייה שכרגע הצגנו. - -שמירת עמוד במטמון יכולה להחשב כמצב מיוחד של [שמירת חלק מהתצוגה במטמון](/doc/guide/caching.fragment)‫.‬ -מאחר ותוכן העמוד בדרך כלל נוצר על ידי צירוף תבנית לתצוגה, זה לא יעבוד פשוט על ידי קריאה ל [beginCache|CBaseController::beginCache] ו [endCache|CBaseController::endCache] בתוך קובץ התבנית. -הסיבה היא שהתבנית מצורפת בתוך מתודת [CController::render] **אחרי** שתוכן התצוגה כבר הוערך והורץ. - -כדי לשמור עמוד שלם במטמון, אנו צריכים לדלג על ההרצה של הפעולה היוצרת את התוכן של העמוד. אנו יכולים להשתמש ב [COutputCache] כ [פילטר](/doc/guide/basics.controller#filter) כדי להשיג מטרה זו. -הקוד הבא מציג כיצג ניתן להגדיר את הפילטר של המטמון: - -~~~ -[php] -public function filters() -{ - return array( - array( - 'COutputCache', - 'duration'=»100, - 'varyByParam'=»array('id'), - ), - ); -} -~~~ - -הגדרות הפילטר למעלה יצרפו את הפילטר לכל הפעולות בקונטרולר. ניתן להגביל אותו לשימוש על פעולות מסויימות על ידי שמוש באופרטור של פלוס ( + ). מידע נוסף ניתן לקרוא על [פילטר](/doc/guide/basics.controller#filter). - -» Tip|טיפ: אנו יכולים להשתמש ב [COutputCache] כפילטר מכיוון שהוא יורש מהמחלקה [CFilterWidget], וזה אומר שזהו גם פילטר וגם וידג'ט. למעשה, הדרך בה עובד וידג'ט דומה מאוד לפילטר: וידג'ט (פילטר) מתחיל לפני שתוכן של פעולה רץ, והוידג'ט (פילטר) מסתיים אחרי שתוכן הפעולה הורץ. - +שמירת עמוד במטמון +============ + +שמירת עמוד במטמון מתייחס לשמירת כל התוכן של עמוד שלם במטמון. שמירת עמוד יכולה להתבצע במקומות שונים. לדוגמא, על ידי בחירת כותרת עמוד מתאימה, הדפדפן של הלקוח יכול לשמור את העמוד הנצפה כרגע לפרק זמן מוגבל. האפליקציה עצמה יכולה לשמור את תוכן העמוד במטמון גם כן. בתת נושא זה, אנו נתמקד על האפשרות השנייה שכרגע הצגנו. + +שמירת עמוד במטמון יכולה להחשב כמצב מיוחד של [שמירת חלק מהתצוגה במטמון](/doc/guide/caching.fragment)‫.‬ +מאחר ותוכן העמוד בדרך כלל נוצר על ידי צירוף תבנית לתצוגה, זה לא יעבוד פשוט על ידי קריאה ל [beginCache|CBaseController::beginCache] ו [endCache|CBaseController::endCache] בתוך קובץ התבנית. +הסיבה היא שהתבנית מצורפת בתוך מתודת [CController::render] **אחרי** שתוכן התצוגה כבר הוערך והורץ. + +כדי לשמור עמוד שלם במטמון, אנו צריכים לדלג על ההרצה של הפעולה היוצרת את התוכן של העמוד. אנו יכולים להשתמש ב [COutputCache] כ [פילטר](/doc/guide/basics.controller#filter) כדי להשיג מטרה זו. +הקוד הבא מציג כיצג ניתן להגדיר את הפילטר של המטמון: + +~~~ +[php] +public function filters() +{ + return array( + array( + 'COutputCache', + 'duration'=»100, + 'varyByParam'=»array('id'), + ), + ); +} +~~~ + +הגדרות הפילטר למעלה יצרפו את הפילטר לכל הפעולות בקונטרולר. ניתן להגביל אותו לשימוש על פעולות מסויימות על ידי שמוש באופרטור של פלוס ( + ). מידע נוסף ניתן לקרוא על [פילטר](/doc/guide/basics.controller#filter). + +» Tip|טיפ: אנו יכולים להשתמש ב [COutputCache] כפילטר מכיוון שהוא יורש מהמחלקה [CFilterWidget], וזה אומר שזהו גם פילטר וגם וידג'ט. למעשה, הדרך בה עובד וידג'ט דומה מאוד לפילטר: וידג'ט (פילטר) מתחיל לפני שתוכן של פעולה רץ, והוידג'ט (פילטר) מסתיים אחרי שתוכן הפעולה הורץ. + «div class="revision"»$Id: caching.page.txt 1014 2009-05-10 12:25:55Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/changes.txt b/docs/guide/he/changes.txt index 6e4af9af0..ed017b9b7 100644 --- a/docs/guide/he/changes.txt +++ b/docs/guide/he/changes.txt @@ -1,120 +1,120 @@ -אפשרויות חדשות -=============== - -עמוד זה מסכם את האפשרויות החדשות שנוספו למערכת ה Yii בכל גרסא. - -גרסא 1.1.3 -------------- - -* [נוספה תמיכה בהגדרת אפשרויות ברירת המחדל לוידגטים דרך קובץ הגדרות האפליקציה](/doc/guide/topics.theming#customizing-widgets-globally) - -גרסא 1.1.2 -------------- - -* נוסף כלי ווב ליצירת קוד בשם [Gii](/doc/guide/topics.gii) - -גרסא 1.1.1 -------------- - -* נוספה מחלקה חדשה בשם CActiveForm אשר מפשטת כתיבה של קוד הקשור לטפסים ותומכת באימות הנתונים (ולידציה) בצורה רציפה ועקבית בצד הלקוח צד השרת כאחד. - -* שינויים משמעותיים בקוד אשר נוצר דרך כלי ה yiic אשר מאפשר יצירה אוטומטית של קוד דרך שורת הפקודות במערכת. בפרט, השלד של המערכת עכשיו נוצר עם כמה תבניות; -נוספה תמיכה בחיפוש וסינון לעמוד לוח הבקרה שנוצר על ידי פקודת CRUD (פקודה אשר יוצרת,מריצה,מעדכנת,מוחקת נתונים במסד); שימוש ב CActiveForm כדי להציג טופס. - - * [נוספה תמיכה להגדרת פקודות גלובאליות לכלי yiic](/doc/guide/topics.console) - -גרסא 1.1.0 -------------- - -* [נוספה תמיכה בכתיבה של בדיקות (Unit Test) יחידות ופונקציונליות](/doc/guide/test.overview) - -* [נוספה תמיכה בסקינים בעת שימוש בוידג'טים](/doc/guide/topics.theming#skin) - -* [נוסף מודל בר-הרחבה אשר מאפשר יצירה של טפסים](/doc/guide/form.builder) - -* הדרך בה מגדירים ערכים בטוחים במודלים שופרה. ראה - [הגדרת ערכים בטוחים](/doc/guide/form.model#securing-attribute-assignments). - -* שינוי האלגוריתם בשאילתת ה SQL כדי שכל הטבלאות הנוספות בשאילתה (Joins) יצורפו לשאילתה אחת. - -* שינוי שם ברירת המחדל של הטבלה ב AR לשמה של מחלקת ה AR. זאת אומרת שכעת כשמגדירים קישור לטבלה (Relation) ולא הוגדר שם ברירת מחדל לקישור השם שלו שבעזרתו -קוראים לטבלה המקושרת יהיה השם של מחלקת ה AR שלה. - -* [נוספה תמיכה לשימוש בקידומת לטבלאות במסד](/doc/guide/database.dao#using-table-prefix). - -* נוספו עשרות תוספים חדשים לסט חדש הנקרא [ספריית Zii](http://code.google.com/p/zii/). - -* שם המפתח בטבלה הראשית בשאילתת AR הוגדר להיות 't' (שאילתות AR בלבד). - - -גרסא 1.0.11 --------------- - -* נוספה תמיכה ליצירה ועיבוד של קישורים עם סאב-דומיינים כפרמטרים בניתוב הקישור. - - [פרמטרים בסאב-דומיינים](/doc/guide/topics.url#parameterizing-hostnames) - -גרסא 1.0.10 --------------- - -* שיפור לשימוש ב CPhpMessageSource אשר מאפשר עכשיו לנהל הודעות (תרגומים) על בסיס מודלים בנוסף לקונטרולרים. - - [ניהול תרגומים](/doc/guide/topics.i18n#message-translation) - -* נוספה תמיכה לצירוף פונקציות אנונימיות למנהלי האירועים (Events Manger Handlers) במערכת. - - [אירועים ברכיבי מערכת](/doc/guide/basics.component#component-event) - -גרסא 1.0.8 -------------- - -* נוספה תמיכה בקבלת מספר ערכים שמורים במטמון בבת אחת - - [ניהול מטמון](/doc/guide/caching.data) - -* נתיב לתיקיה ברירת מחדל במערכת נוספה בשם 'ext' אשר מכוונת לתיקיה בה שמורים כל תוספות (Extensions) הצד השלישי. - - [שימוש בתוספים](/doc/guide/extension.use) - - -גרסא 1.0.7 -------------- - - * נוספה תמיכה להצגת ערמות המידע אשר נקראו על ידי האפליקציה בהודעות המעקב - - [שמירת מידע אודות השרת ופרמטרים גלובאלים](/doc/guide/topics.logging#logging-context-information) - - * נוספה האפשרות של הוספת 'index' לרשומות הפעילות של המסד כדי שיהיה ניתן לאנדקס אובייקטים משוייכים על ידי שימוש בערכים של עמודה מסויימת - - [אפשרויות שאילתות מקושרות](/doc/guide/database.arr#relational-query-options) - -גרסא 1.0.6 -------------- - - * נוספה תמיכה לשימוש במרחבי שמות במתודות כמו עדכון ומחיקה: - - [מרחבי שמות](/doc/guide/database.ar#named-scopes) - - * נוספה תמיכה במרחבי שמות באפשרות של 'with' בחוקים מקושרים: - - [שאילתות מקושרות עם מרחבי שמות](/doc/guide/database.arr#relational-query-with-named-scopes) - - * נוספה תמיכה בביצוע פרופילינג של שאילתות SQL - - [פרופילינג לשאילתות SQL](/doc/guide/topics.logging#profiling-sql-executions) - - * נוספה תמיכה לשמירת מידע אודות ערכים גלובאלים - - [שמירת מידע אודות השרת ופרמטרים גלובאלים](/doc/guide/topics.logging#logging-context-information) - - * נוספה תמיכה לעריכה של חוק קישור בודד על ידי הגדרת אפשרויות ה 'urlFormat' ו 'caseSensitive': - - [קישורים ידידותיים](/doc/guide/topics.url#user-friendly-urls) - - * נוספה תמיכה להצגת שגיאות מערכת על ידי מתודה בקונטרולר: - - [טיפול בשגיאות על ידי מתודות](/doc/guide/topics.error#handling-errors-using-an-action) - -גרסא 1.0.5 -------------- - - * נוספה תמיכה במרחבי שמות לרשומות הפעילות. ראה: - - [מרחיב שמות](/doc/guide/database.ar#named-scopes) - - [מרחבי שמות ברירת מחדל](/doc/guide/database.ar#default-named-scope) - - [שאילתת קישור עם מרחבי שמות](/doc/guide/database.arr#relational-query-with-named-scopes) - - - * נוספה תמיכה בטעינה עצלה בשימוש עם אפשריות שאילתות דינאמיות ברשומות הפעילות. ראה: - - [אפשרויות דינאמיות לשאילתות מקושרות](/doc/guide/database.arr#dynamic-relational-query-options) - - * נוספה אפשרות ב [CUrlManager] כדי לתמוך בהוספת פרמטרים לחלקים בנתיב בחוקי הקישורים. ראה: - - [הוספת פרמטרים לחוקי קישורים](/doc/guide/topics.url#parameterizing-routes) - +אפשרויות חדשות +=============== + +עמוד זה מסכם את האפשרויות החדשות שנוספו למערכת ה Yii בכל גרסא. + +גרסא 1.1.3 +------------- + +* [נוספה תמיכה בהגדרת אפשרויות ברירת המחדל לוידגטים דרך קובץ הגדרות האפליקציה](/doc/guide/topics.theming#customizing-widgets-globally) + +גרסא 1.1.2 +------------- + +* נוסף כלי ווב ליצירת קוד בשם [Gii](/doc/guide/topics.gii) + +גרסא 1.1.1 +------------- + +* נוספה מחלקה חדשה בשם CActiveForm אשר מפשטת כתיבה של קוד הקשור לטפסים ותומכת באימות הנתונים (ולידציה) בצורה רציפה ועקבית בצד הלקוח צד השרת כאחד. + +* שינויים משמעותיים בקוד אשר נוצר דרך כלי ה yiic אשר מאפשר יצירה אוטומטית של קוד דרך שורת הפקודות במערכת. בפרט, השלד של המערכת עכשיו נוצר עם כמה תבניות; +נוספה תמיכה בחיפוש וסינון לעמוד לוח הבקרה שנוצר על ידי פקודת CRUD (פקודה אשר יוצרת,מריצה,מעדכנת,מוחקת נתונים במסד); שימוש ב CActiveForm כדי להציג טופס. + + * [נוספה תמיכה להגדרת פקודות גלובאליות לכלי yiic](/doc/guide/topics.console) + +גרסא 1.1.0 +------------- + +* [נוספה תמיכה בכתיבה של בדיקות (Unit Test) יחידות ופונקציונליות](/doc/guide/test.overview) + +* [נוספה תמיכה בסקינים בעת שימוש בוידג'טים](/doc/guide/topics.theming#skin) + +* [נוסף מודל בר-הרחבה אשר מאפשר יצירה של טפסים](/doc/guide/form.builder) + +* הדרך בה מגדירים ערכים בטוחים במודלים שופרה. ראה + [הגדרת ערכים בטוחים](/doc/guide/form.model#securing-attribute-assignments). + +* שינוי האלגוריתם בשאילתת ה SQL כדי שכל הטבלאות הנוספות בשאילתה (Joins) יצורפו לשאילתה אחת. + +* שינוי שם ברירת המחדל של הטבלה ב AR לשמה של מחלקת ה AR. זאת אומרת שכעת כשמגדירים קישור לטבלה (Relation) ולא הוגדר שם ברירת מחדל לקישור השם שלו שבעזרתו +קוראים לטבלה המקושרת יהיה השם של מחלקת ה AR שלה. + +* [נוספה תמיכה לשימוש בקידומת לטבלאות במסד](/doc/guide/database.dao#using-table-prefix). + +* נוספו עשרות תוספים חדשים לסט חדש הנקרא [ספריית Zii](http://code.google.com/p/zii/). + +* שם המפתח בטבלה הראשית בשאילתת AR הוגדר להיות 't' (שאילתות AR בלבד). + + +גרסא 1.0.11 +-------------- + +* נוספה תמיכה ליצירה ועיבוד של קישורים עם סאב-דומיינים כפרמטרים בניתוב הקישור. + - [פרמטרים בסאב-דומיינים](/doc/guide/topics.url#parameterizing-hostnames) + +גרסא 1.0.10 +-------------- + +* שיפור לשימוש ב CPhpMessageSource אשר מאפשר עכשיו לנהל הודעות (תרגומים) על בסיס מודלים בנוסף לקונטרולרים. + - [ניהול תרגומים](/doc/guide/topics.i18n#message-translation) + +* נוספה תמיכה לצירוף פונקציות אנונימיות למנהלי האירועים (Events Manger Handlers) במערכת. + - [אירועים ברכיבי מערכת](/doc/guide/basics.component#component-event) + +גרסא 1.0.8 +------------- + +* נוספה תמיכה בקבלת מספר ערכים שמורים במטמון בבת אחת + - [ניהול מטמון](/doc/guide/caching.data) + +* נתיב לתיקיה ברירת מחדל במערכת נוספה בשם 'ext' אשר מכוונת לתיקיה בה שמורים כל תוספות (Extensions) הצד השלישי. + - [שימוש בתוספים](/doc/guide/extension.use) + + +גרסא 1.0.7 +------------- + + * נוספה תמיכה להצגת ערמות המידע אשר נקראו על ידי האפליקציה בהודעות המעקב + - [שמירת מידע אודות השרת ופרמטרים גלובאלים](/doc/guide/topics.logging#logging-context-information) + + * נוספה האפשרות של הוספת 'index' לרשומות הפעילות של המסד כדי שיהיה ניתן לאנדקס אובייקטים משוייכים על ידי שימוש בערכים של עמודה מסויימת + - [אפשרויות שאילתות מקושרות](/doc/guide/database.arr#relational-query-options) + +גרסא 1.0.6 +------------- + + * נוספה תמיכה לשימוש במרחבי שמות במתודות כמו עדכון ומחיקה: + - [מרחבי שמות](/doc/guide/database.ar#named-scopes) + + * נוספה תמיכה במרחבי שמות באפשרות של 'with' בחוקים מקושרים: + - [שאילתות מקושרות עם מרחבי שמות](/doc/guide/database.arr#relational-query-with-named-scopes) + + * נוספה תמיכה בביצוע פרופילינג של שאילתות SQL + - [פרופילינג לשאילתות SQL](/doc/guide/topics.logging#profiling-sql-executions) + + * נוספה תמיכה לשמירת מידע אודות ערכים גלובאלים + - [שמירת מידע אודות השרת ופרמטרים גלובאלים](/doc/guide/topics.logging#logging-context-information) + + * נוספה תמיכה לעריכה של חוק קישור בודד על ידי הגדרת אפשרויות ה 'urlFormat' ו 'caseSensitive': + - [קישורים ידידותיים](/doc/guide/topics.url#user-friendly-urls) + + * נוספה תמיכה להצגת שגיאות מערכת על ידי מתודה בקונטרולר: + - [טיפול בשגיאות על ידי מתודות](/doc/guide/topics.error#handling-errors-using-an-action) + +גרסא 1.0.5 +------------- + + * נוספה תמיכה במרחבי שמות לרשומות הפעילות. ראה: + - [מרחיב שמות](/doc/guide/database.ar#named-scopes) + - [מרחבי שמות ברירת מחדל](/doc/guide/database.ar#default-named-scope) + - [שאילתת קישור עם מרחבי שמות](/doc/guide/database.arr#relational-query-with-named-scopes) + + + * נוספה תמיכה בטעינה עצלה בשימוש עם אפשריות שאילתות דינאמיות ברשומות הפעילות. ראה: + - [אפשרויות דינאמיות לשאילתות מקושרות](/doc/guide/database.arr#dynamic-relational-query-options) + + * נוספה אפשרות ב [CUrlManager] כדי לתמוך בהוספת פרמטרים לחלקים בנתיב בחוקי הקישורים. ראה: + - [הוספת פרמטרים לחוקי קישורים](/doc/guide/topics.url#parameterizing-routes) + «div class="revision"»$Id: changes.txt 2172 2010-03-09 22:23:19Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/database.ar.txt b/docs/guide/he/database.ar.txt index fe89a9587..ab05e4537 100644 --- a/docs/guide/he/database.ar.txt +++ b/docs/guide/he/database.ar.txt @@ -1,509 +1,509 @@ -Active Record -============= - -למרות שה DAO של Yii יכול לטפל בכמעט כל פעולה הקשורה למסד הנתונים, רוב הסיכויים שאנו נבזבז 90% מהזמן שלנו בכתיבת שאילתות SQL אשר מבצעות את הפעולות הנפוצות במסד הנתונים CRUD (יצירה, קריאה, עדכון ומחיקה). גם קשה לנהל את הקוד שאנו כותבים כשהוא מעורבב עם שאילתות SQL שונות. כדי לפתור בעיות אלו אנו יכולים להשתמש ב Active Record. - -Active Record או בקיצור AR הינה שיטת ORM (Object-Relational Mapping) נפוצה. כל מחלקת AR מייצגת טבלה במסד הנתונים אשר התכונות שלה מאופיינות כמשתנים במחלקת ה AR, ואובייקט של המחלקה מייצג שורה אחת בטבלה. פעולות CRUD נפוצות מיושמות בתור מתודות במחלקת ה AR. כתוצאה מכך, אנו יכולים לגשת למידע במסד הנתונים בצורה יותר מונחית-עצמים. לדוגמא, אנו יכולים להשתמש בקוד הבא כדי להכניס שורה חדשה בטבלה `tbl_post`: - -~~~ -[php] -$post=new Post; -$post-»title='כותרת לדוגמא'; -$post-»content='תוכן הודעה לדוגמא'; -$post-»save(); -~~~ - -במסמך זה אנו נתאר כיצד להגדיר מחלקת AR ולהשתמש בה כדי לבצע פעולות CRUD. בחלק הבא אנו נציג כיצד להשתמש ב AR כדי לטפל בקשרים בין טבלאות במסד הנתונים. לנוחות, אנו נשתמש בטבלה הבאה למטרת הצגת הדוגמאות בחלק זה. הערה: במידה והינך עובד עם מסד נתונים מסוג MySQL, יש להחליף את `AUTOINCREMENT` ב `AUTO_INCREMENT` בשאילתה הבאה. - -~~~ -[sql] -CREATE TABLE tbl_post ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - title VARCHAR(128) NOT NULL, - content TEXT NOT NULL, - create_time INTEGER NOT NULL -); -~~~ - -» Note|הערה: שימוש ב-AR לא נועד כדי לפתור את כל הבעיות הקשורות למסדי הנתונים. השימוש הטוב ביותר בו נועד על מנת לבצע שאילות SQL פשוטות (CRUD) ושימוש בתור מודל באפליקציה. מומלץ לא להשתמש בו בעת ביצוע שאילתות SQL מורכבות. למטרה זו מומלץ להשתמש ב DAO. - - -התחברות למסד הנתונים --------------------------- - -AR משתמך על חיבור למסד הנתונים כדי לבצע פעולות הקשורות אליו. כברירת מחדל, AR מניח שהרכיב `db` מספק אובייקט [CDbConnection] המאפשר התחברות למסד הנתונים. הגדרות האפליקציה הבאות מציגות דוגמא לשימוש: - -~~~ -[php] -return array( - 'components'=»array( - 'db'=»array( - 'class'=»'system.db.CDbConnection', - 'connectionString'=»'sqlite:path/to/dbfile', - // הפעלת מטמון להפחתת עומס - // 'schemaCachingDuration'=»3600, - ), - ), -); -~~~ - -» Tip|טיפ: מאחר וה AR מסתמך על ה metadata של הטבלאות כדי להחליט לגבי סוגי העמודות בטבלאות, לוקח זמן לקרוא מידע זה אודות הטבלאות ולנתח אותו. במידה ותרשים טבלאות מסדי הנתונים שלך לא משתנה, מומלץ להפעיל את המטמון לגבי הנתונים אודות הטבלאות על ידי הגדרת המאפיין [CDbConnection::schemaCachingDuration] לערך הגדול מ-0. - -תמיכה ב AR מוגבלת על פי DBMS. נכון לעכשיו, ה-DBMS הבאים נתמכים: - -- [MySQL 4.1 או יותר](http://www.mysql.com) - -- [PostgreSQL 7.3 או יותר](http://www.postgres.com) - -- [Microsoft SQL Server 2000 או יותר](http://www.microsoft.com/sqlserver/) - -- [Oracle](http://www.oracle.com) - - -» Note|הערה: התמיכה ב SQLServer קיימת מגרסאות 1.0.4 ומעלה. והתמיכה של Oracle קיימת מגרסאות 1.0.5 ומעלה. - -אם ברצונך להשתמש ברכיב אפליקציה שהוא לא `db`, או אם ברצונך לעבוד עם כמה מסדי נתונים בעזרת ה AR, רצוי שתדרוס את [CActiveRecord::getDbConnection]. המחלקה [CActiveRecord] הינה מחלקת הבסיס של כל מחלקות ה AR. - -» Tip|טיפ: ישנם שני דרכים לעבוד עם כמה מסדי נתונים תוך שימוש ב-AR . אם תרשים מסדי הנתונים הוא שונה, תוכל ליצור מחלקת AR בסיסית שונה אשר בתוכה יהיה צורך לדרוס את [getDbConnection|CActiveRecord::getDbConnection] בצורה שונה. דרך נוספת תיהיה לשנות את המשתנה הסטטי [CActiveRecord::db] בצורה דינאמית בעת הצורך. - -הגדרת מחלקת AR ------------------ - -כדי לגשת לטבלה במסד הנתונים, אנו קודם צריכים להגדיר מחלקת AR היורשת מ [CActiveRecord]. כל מחלקת AR מייצגת טבלה במסד הנתונים, ואובייקט של המחלקה מייצג רשומה באותה טבלה. הדוגמא הבאה מציגה את הקוד המינימלי הדרוש למחלקת ה AR המייצגת את הטבלה `tbl_post`. - -~~~ -[php] -class Post extends CActiveRecord -{ - public static function model($className=__CLASS__) - { - return parent::model($className); - } - - public function tableName() - { - return 'tbl_post'; - } -} -~~~ - -» Tip|טיפ: מאחר וניתן לקרוא למחלקות AR במקומות רבים, ניתן לייבא את כל התיקיה המכילה את מחלקת ה AR, במקום לייבא אותם אחד אחד. לדוגמא, אם כל מחלקות ה AR נמצאות תחת התיקיה `protected/models`, ניתן להגדיר את האפליקציה בצורה הבאה: -» ~~~ -» [php] -» return array( -» 'import'=»array( -» 'application.models.*', -» ), -» ); -» ~~~ - -כברירת מחדל, שם מחלקת ה AR הוא זהה לשם של הטבלה במסד הנתונים. יש לדרוס את המתודה [tableName|CActiveRecord::tableName] במידה והם שונים אחד מהשני. המתודה [model|CActiveRecord::model] מוגדרת בצורה הזאת לכל מחלקת AR (הסבר בהמשך). - -» Info|מידע: כדי להשתמש באפשרות [קידומת טבלאות](/doc/guide/database.dao#using-table-prefix) הקיימת מגרסאות 1.1.0, המתודה [tableName|CActiveRecord::tableName] של מחלקת AR צריכה להדרס בצורה הבאה, -» ~~~ -» [php] -» public function tableName() -» { -» return '{{post}}'; -» } -» ~~~ -זאת, במקום להחזיר את שמה המלא של הטבלה, אנו מחזירים את שם הטבלה ללא הקידומת ועוטפים אותה בסוגריים מסולסלות כפולות. - -ניתן לגשת לערכים בעמודות של טבלה כמאפיינים של מחלקת ה AR. לדוגמא, הקוד הבא מגדיר את העמודה (מאפיין) `title`: - -~~~ -[php] -$post=new Post; -$post-»title='הודעה לדוגמא'; -~~~ - -למרות שאנו לא מגדירים ספציפית את המאפיין `title` במחלקת ה `Post`, אנו עדיין יכולים לגשת אליו בקוד המוצג למעלה. הסיבה לכך היא מכיוון ש `title` הינו עמודה בטבלה `tbl_post` , ומחלקת ה AR מאפשרת גישה אליו כמאפיין במחלקה בעזרת פונקצית ה `__get` ב PHP. תזרק שגיאה במידה ויהיה ניסיון לגשת לעמודה אשר לא קיימת בטבלה. - -» Info|מידע: במדריך זה, אנו משתמשים באותיות קטנות לכל שמות הטבלאות והעמודות. זאת מאחר וכל DBMS רגיש באופן שונה לאותיות גדולות-קטנות. לדוגמא, PostgreSQL לא רגיש לאותיות גדולות-קטנות לשמות עמודות בטבלאות כברירת מחדל, ואנו חייבים לתחום את שמות העמודות בתנאים בתוך השאילתה במידה ושם העמודה מכיל שילוב של אותיות גדולות וקטנות. שימוש באותיות קטנות בלבד בשמות פותר בעיה זו. - -מחלקת AR מסתמכת על מפתחות ראשיים המוגדרים בטבלאות. במידה וטבלה לא מכילה מפתח ראשי, יש צורך בהגדרת המפתח הראשי במחלקה על ידי דריסה של המתודה `primaryKey` כפי שמוצג בדוגמא הבאה, - -~~~ -[php] -public function primaryKey() -{ - return 'id'; - // בעבור מפתחות מורכבים, יש להחזיר מערך בצורה הבאה - // return array('pk1', 'pk2'); -} -~~~ - - -יצירת רשומה ---------------- - -בכדי להכניס רשומה חדשה לטבלה במסד הנתונים, אנו יוצרים אובייקט חדש של מחלקת ה AR, מגדירים את המאפיינים שלה בהתאם לעמודות בטבלה, וקוראים למתודה [save|CActiveRecord::save] כדי להשלים את תהליך ההוספה. - -~~~ -[php] -$post=new Post; -$post-»title='כותרת לדוגמא'; -$post-»content='תוכן להודעה לדוגמא'; -$post-»create_time=time(); -$post-»save(); -~~~ - -במידה והמפתח הראשי של הטבלה הינו ערך מספרי אשר עולה אוטומטית (מוגדר כ auto_increment), לאחר ההוספה לטבלה מחלקת ה AR תכיל מפתח ראשי מעודכן. בדוגמא למעלה, המאפיין `id` משקף את המפתח הראשי של ההודעה החדשה שנוצרה כרגע, למרות שאנו לא משנים אותו בצורה ספציפית. - -במידה וישנו ערך ברירת מחדל לעמודה בטבלה (לדוגמא סטרינג או מספר), המאפיין במחלקת ה AR יכיל את אותו ערך ברירת מחדל לאחר יצירת הרשומה החדשה. דרך אחת לשנות ערך ברירת מחדל זה היא על ידי הגדרת המאפיין במחלקת ה AR עם ערך ברירת מחדל חדש: - -~~~ -[php] -class Post extends CActiveRecord -{ - public $title='אנא הזן כותרת'; - ...... -} - -$post=new Post; -echo $post-»title; // זה יציג: אנא הזן כותרת -~~~ - -החל מגרסא 1.0.2, ניתן להגדיר ערך של [CDbExpression] למאפיין כלשהו לפני שמירת הרשומה (בין אם זה יצירת רשומה חדשה או עדכון רשומה קיימת) במסד הנתונים. לדוגמא, בכדי לשמור את הזמן הנוכחי המוחזר באמצעות הפונקציה `()NOW` ב MySQL, אנו יכולים להשתמש בקוד הבא: - -~~~ -[php] -$post=new Post; -$post-»create_time=new CDbExpression('NOW()'); -// $post-»create_time='NOW()'; לא יעבוד מאחר -// 'NOW()' יהיה כסטרינג ולא כפונקציה -$post-»save(); -~~~ - -» Tip|טיפ: אף על פי ש AR מאפשר לנו לבצע פעולות הקשורות למסד הנתונים ללא צורך בכתיבה של שאילתות SQL מסובכות, אנו בדרך כלל נרצה לדעת אילו שאילתות SQL רצות מאחורי ה AR. ניתן לקבל מידע זה על ידי הפעלת אפשרות [התיעוד](/doc/guide/topics.logging) של Yii. לדוגמא, אנו יכולים להפעיל את [CWebLogRoute] בהגדרות האפליקציה, ואנו נראה את השאילתות שבוצעו בסוף כל עמוד. מגרסא 1.0.5, אנו יכולים להגדיר את המאפיין [CDbConnection::enableParamLogging] לערך השווה ל `true` בהגדרות האפליקציה כדי שהפרמטרים התחומים בשאילתה יוצגו גם הם בתיעוד. - -קריאת רשומה --------------- - -בכדי לקרוא מידע מטבלה במסד הנתונים, אנו קוראים לאחת ממתודות ה `find` הבאות. - -~~~ -[php] -// מצא את הרשומה הראשונה המשביע את התנאי שהועבר -$post=Post::model()-»find($condition,$params); -// מצא את הרשומה עם המפתח הראשי שהועבר -$post=Post::model()-»findByPk($postID,$condition,$params); -// מצא את הרשומה עם המאפיין שהועבר -$post=Post::model()-»findByAttributes($attributes,$condition,$params); -// מצא את הרשומה הראשונה עם השאילתה שהועברה -$post=Post::model()-»findBySql($sql,$params); -~~~ - -בדוגמאות למעלה, אנו קוראים למתודות ה `find` בעזרת `()Post::model`. זכור שהמתודה הסטטית `()model` הינה הכרחית לכל מחלקת AR. המתודה מחזירה אובייקט AR אשר משתמשים בו לגשת למתודות בעלי הרשאה לאותה מחלקה בלבד (משהו דומה למתודות סטטיות במחלקה) באופן מונחה עצמים. - -אם המתודה `find` מוצאת רשומה המשביע את התנאים שהועברו, היא תחזיר אובייקט של `Post` כשהמאפיינים שלה מכילים את הערכים של העמודות של אותה רשומה בטבלה. לאחר מכן אנו יכולים לקרוא את הערכים שנטענו בצורה הרגילה בה אנו ניגשים למאפיינים של המחלקה, לדוגמא, `;echo $post-»title`. - -מתודת ה `find` תחזיר null במידה ולא נמצא שום דבר במסד הנתונים התואם לתנאים שהועברו. - -בעת הקריאה ל `find`, אנו משתמשים ב `condition$` ו `params$` כדי להגדיר את התנאים של השאילתה. כאן `condition$` יכול להוות סטרינג המייצג את הסעיף `WHERE` בשאילתת SQL, ו `params$` הינו מערך של פרמטרים שערכיהם צריכים להתחם במפתחות שהוגדרו מראש ב `condition$`. לדוגמא, - -~~~ -[php] -// מצא את הרשומה איפה ש postID = 10 -$post=Post::model()-»find('postID=:postID', array(':postID'=»10)); -~~~ - -» Note|הערה: בדוגמא למעלה, אנו נצטרך לבצע חיטוי לייחוס של העמודה `postID` בעבור DBMS מסויימים. לדוגמא, במידה ואנחנו משתמשים ב PostgreSQL, אנו נצטרך לכתוב את התנאי בצורה הבאה `postID"=:postID"`, מאחר ו PostgreSQL כברירת מחדל יתייחס לשמות העמודות ללא רגישות לאותיות גדולות-קטנות. - -כמו כן אנו יכולים להשתמש ב `condition$` כדי להגדיר תנאים מורכבים יותר. במקום סטרינג, אנו נותנים ל `condition$` להיות אובייקט של [CDbCriteria], המאפשר לנו להגדיר תנאים נוספים מלבד סעיף ה `WHERE`. לדוגמא, - -~~~ -[php] -$criteria=new CDbCriteria; -$criteria-»select='title'; // בחר רק את העמודה 'title' -$criteria-»condition='postID=:postID'; -$criteria-»params=array(':postID'=»10); -$post=Post::model()-»find($criteria); // $params אינו נחוץ כאן -~~~ - -זכור, שבעת השימוש ב [CDbCriteria] בתור התנאי של השאילתה, הפרמטר `params$` אינו נחוץ מאחר וניתן להגדיר אותו בעזרת [CDbCriteria], כפי שמוצג למעלה. - -כמו כן, במקום שימוש ב [CDbCriteria] ניתן להעביר מערך למתודת ה-`find`. -שמות המפתחות והערכים מתייחסות למאפיינים של התנאים וערכיהם, בהתאם. ניתן לשכתב את הדוגמא למעלה בקוד הבא, - -~~~ -[php] -$post=Post::model()-»find(array( - 'select'=»'title', - 'condition'=»'postID=:postID', - 'params'=»array(':postID'=»10), -)); -~~~ - -» Info|מידע: כשהתנאי של השאילתה עוסק בהתאמת עמודות והערכים שהוגדרו, אנו יכולים להשתמש ב [findByAttributes()|CActiveRecord::findByAttributes]. -אנו נותנים לפרמטר `attributes$` להיות מערך של שמות העמודות בתור המפתחות וערך כל מפתח הינו הערך שאותו אנו רוצים להתאמים בשאילתה. בכמה פריימוורקים (Frameworks), פעולה זו ניתנת לביצוע על ידי קריאה למתודות כמו `findByNameAndTitle`. למרות שגישה זו נראית מושכת, היא גורמת ברוב המקרים לבלבול, קונפליקטים ובעיות של רגישות לאותיות גדולות-קטנות בשמות העמודות. - -כשישנם מספר רב של רשומות התואמות לתנאים שהוצבו בשאילתה, אנו יכולים להביא את כולם יחדיו על ידי שימוש במתודות `findAll` הבאות, לכל אחת מהם ישנה מתודת `find` תואמת, כפי שכבר הסברנו. - -~~~ -[php] -// מצא את כל הרשומות התואמות לתנאי שהועבר -$posts=Post::model()-»findAll($condition,$params); -// מצא את כל הרשומות בעלות המפתח הראשי שהועבר -$posts=Post::model()-»findAllByPk($postIDs,$condition,$params); -// מצא את כל הרשומות התואמות למאפיינים שהועברו -$posts=Post::model()-»findAllByAttributes($attributes,$condition,$params); -// מצא את כל הרשומות אשר תואמות לשאילתה שהועברה -$posts=Post::model()-»findAllBySql($sql,$params); -~~~ - -במידה ולא נמצאו התאמות לתנאים שהוצבו בשאילתה, `findAll` תחזיר מערך ריק. זה שונה מהערך שיוחזור ממתודות `find` המחזירות null במידה ולא נמצאו רשומות. - -מלבד המתודות `find` ו `findAll` המתוארות למעלה, לנוחות השימוש ניתן להשתמש במתודות הבאות גם כן: - -~~~ -[php] -// קבל את מספר הרשומות התואמות לתנאי שהועבר -$n=Post::model()-»count($condition,$params); -// קבל את מספר הרשומות התואמות לשאילתה שהועברה -$n=Post::model()-»countBySql($sql,$params); -// בדוק אם קיימת לפחות רשומה אחת התואמת לתנאי שהועבר -$exists=Post::model()-»exists($condition,$params); -~~~ - -עדכון רשומה ---------------- - -לאחר שאובייקט AR עם ערכים לעמודות, ניתן לשנותם ולשמור אותם בחזרה לטבלה במסד. - -~~~ -[php] -$post=Post::model()-»findByPk(10); -$post-»title='כותרת חדש'; -$post-»save(); // שמור את השינויים במסד -~~~ - -כפי שניתן לראות, אנו משתמשים באותה מתודה [save()|CActiveRecord::save] כדי לבצע פעולות הוספה ועדכון. במידה ואובייקט ה AR נוצר כאובייקט חדש בעזרת שימוש באופרטור `new`, קריאה ל [save()|CActiveRecord::save] תוסיף רשומה חדשה לטבלה במסד הנתונים; במידה ואובייקט ה AR נוצר כתוצאה משימוש במתודה כמו `find` או `findAll`, קריאה ל [save()|CActiveRecord::save] תעדכן את הרשומה הקיימת בטבלה. למעשה, אנו יכולים להשתמש ב [CActiveRecord::isNewRecord] כדי להבחין במידה אובייקט ה AR הינו חדש או לא. - -ניתן לעדכן רשומה אחת או יותר בטבלה במסד הנתונים ללא צורך בטעינתם מראש. AR מספקת את המתודות הבאות לנוחות השימוש: - -~~~ -[php] -// עדכן את הרשומות התואמות לתנאי שהועבר -Post::model()-»updateAll($attributes,$condition,$params); -// עדכן את הרשומות התואמות לתנאי שהועבר ולמפתחות הראשיים שהועברו -Post::model()-»updateByPk($pk,$attributes,$condition,$params); -// עדכן את עמודות הספירה ברשומות התואמות לתנאי שהועבר -Post::model()-»updateCounters($counters,$condition,$params); -~~~ - -בדוגמא למעלה, `attributes$` הינו מערך של שמות העמודות וערכיהם; `counter$` הינו מערך של שמות העמודות וערכיהם הינו מספר עולה; ו `condition$` ו `params$` כפי שהוסבר בחלק הקודם. - -מחיקת רשומה ---------------- - -אנו יכולים למחוק רשומה במידה ואובייקט ה AR נוצר על ידי קבלת המידע מהטבלה במסד הנתונים. - -~~~ -[php] -$post=Post::model()-»findByPk(10); // נניח וישנה הודעה עם המספר 10 -$post-»delete(); // מחק את הרשומה מהטבלה במסד הנתונים -~~~ - -הערה, לאחר המחיקה, אובייקט ה AR נשאר ללא שינוי, אבל אותה שורה בטבלה במסד הנתונים נמחקה. - -לנוחות ניתן להעזר במתודות הבאות כדי למחוק רשומות ללא צורך בטעינה מראש שלהם: - -~~~ -[php] -// מחק את הרשומות התואמות לתנאי שהועבר -Post::model()-»deleteAll($condition,$params); -// מחק את הרשומות התואמות לתנאי שהועבר ולמפתחות שהועברו -Post::model()-»deleteByPk($pk,$condition,$params); -~~~ - -אימות נתונים ---------------- - -כשמוסיפים או מעדכנים רשומה, אנו בדרך כלל צריכים לבדוק במידה והערכים שהוצבו עונים על חוקים מסויימים. זה בהחלט הכרחי במידה והערכים שמוצבים מוגדרים על ידי משתמשי קצה. בדרך כלל, אנו לא יכולים לסמוך על שום דבר המגיע מצד הלקוח. - -AR מבצע אימות נתונים אוטומטית בעת הקריאה ל [()save|CActiveRecord::save]. האימות מבוסס על החוקים שהוגדרו במתודת [()rules|CModel::rules] במחלקת ה AR. למידע נוסף אודות הגדרת חוקי אימות נתונים, יש לקרוא את החלק אודות [הגדרת חוקי אימות נתונים](/doc/guide/form.model#declaring-validation-rules). למטה מוצג קוד בסיסי הנחוץ לשמירה של רשומה: - -~~~ -[php] -if($post-»save()) -{ - // נתונים אומתו והרשומה נוספה/עודכנה -} -else -{ - // נתונים לא תקינים. יש לקרוא למתודה ה getErrors() לקבלת השגיאות שחזרו -} -~~~ - -כשהמידע לעדכון או הוספה של רשומה מתקבל על ידי משתמשי קצה בעזרת טופס HTML , אנו צריכים להציב אותם למאפיינים התואמים במחלקת ה AR. ניתן לבצע זאת בצורה הבאה: - -~~~ -[php] -$post-»title=$_POST['title']; -$post-»content=$_POST['content']; -$post-»save(); -~~~ - -במידה וישנם עמודות רבים, אנו נראה רשימה ארוכה של הצבות כאלו. בכדי להקל על תהליך ההצבה ניתן להעזר במאפיין [attributes|CActiveRecord::attributes] כפי שמוצג בדוגמא למטה. -מידע נוסף ניתן למצוא בחלק [אבטחת הצבת מאפיינים](/doc/guide/form.model#securing-attribute-assignments) ובחלק [יצירת פעולה](/doc/guide/form.action) . - -~~~ -[php] -// נניח ש $_POST['Post'] הינו מערך ששמות המפתחות הינם שמות העמודות בטבלה והערכים שלהם בהתאם -$post-»attributes=$_POST['Post']; -$post-»save(); -~~~ - - -השוואת רשומות ------------------ - -בדומה לשורות בטבלה, אובייקטים של AR מזוהים בצורה יחודית על ידי הערך במפתח הראשי שלהם. לכן, בכדי להשוות בין שני אובייקטים של AR, כל מה אנו צריכים לעשות זה להשוות בין את ערכי המפתחות הראשיים שלהם, בהנחה שהם שייכים לאותה מחלקת AR. למרות, שיהיה יותר קל לקרוא פשוט למתודה [()CActiveRecord::equals]. - -» Info|מידע: בניגוד ליישום ושימוש של AR בפריימוורקס (Frameworks) שונים אחרים, Yii תומכת במפתחות ראשיים מורכבים במחלקות ה AR שלה. מפתח ראשי מורכב מכיל שני עמודות או יותר. מפתח ראשי מורכב מיוצג על ידי מערך ב Yii, בהתאמה. המאפיין [primaryKey|CActiveRecord::primaryKey] מספק את ערך המפתח הראשי של אובייקט AR. - -התאמה אישית -------------- - -[CActiveRecord] מספקת כמה מתודות שניתנים לדריסה על ידי תתי המחלקות שלה כדי לשנות את רצף העבודה שלהם וההתנהלות. - -- [beforeValidate|CModel::beforeValidate] ו [afterValidate|CModel::afterValidate]: מתודות אלו נקראות לפני ואחרי ביצוע אימות הנתונים. - -- [beforeSave|CActiveRecord::beforeSave] ו [afterSave|CActiveRecord::afterSave]: מתודות אלו נקראות לפני ואחרי שמירת אובייקט AR. - -- [beforeDelete|CActiveRecord::beforeDelete] ו [afterDelete|CActiveRecord::afterDelete]: מתודות אלו נקראות לפני ואחרי מחיקת אובייקט AR. - -- [afterConstruct|CActiveRecord::afterConstruct]: מתודה זו רצה בכל פעם שאובייקט AR חדש נוצר בעזרת האופרטור `new`. - -- [beforeFind|CActiveRecord::beforeFind]: מתודה זו רצה לפני שימוש באחד ממתודות ה `find` (לדוגמא `find`, `findAll`). אפשרות זו קיימת מגרסאות 1.0.9 ומעלה. - -- [afterFind|CActiveRecord::afterFind]: מתודה זו רצה לאחר יצירת אובייקט AR כתוצאה מביצוע שאילתה. - - -שימוש בטרנזקציה בעזרת AR -------------------------- - -כל אובייקט AR מכיל מאפיין בשם [dbConnection|CActiveRecord::dbConnection] אשר מייצג אובייקט של [CDbConnection]. לכן אנו יכולים להשתמש באפשרות של [טרנזקציות](/doc/guide/database.dao#using-transactions) המסופקת על ידי ה-DAO של Yii בעת הצורך במהלך השימוש ב-AR: - -~~~ -[php] -$model=Post::model(); -$transaction=$model-»dbConnection-»beginTransaction(); -try -{ - // שימוש ב find ו save הינם שני שלבים אשר ניתן להתערב בהם על ידי בקשות נוספות - // לכן אנו משתמשים בטרנזקציה כדי לוודא המשכיות - $post=$model-»findByPk(10); - $post-»title='כותרת חדשה'; - $post-»save(); - $transaction-»commit(); -} -catch(Exception $e) -{ - $transaction-»rollBack(); -} -~~~ - - -מרחבים מוגדרים ------------- - -» Note|הערה: תמיכה במרחבים מוגדרים נוספה מגרסאות 1.0.5 ומעלה. הרעיון המקורי של מרחבים מוגדרים הגיע מ Ruby on Rails. - -*מרחב מוגדר* מייצג *שם* של תנאי בשאילתה שניתן לאחד אותה ביחד עם עוד מרחבים מוגדרים ולצרף לשאילתה של AR. - -מרחבים מוגדרים בדרך כלל מוגדרים במתודה [CActiveRecord::scopes] בזוגות בפורמט של שם-תנאי. הקוד הבא מגדיר שני מרחבים מוגדרים, `published` ו `recently`, במחלקה של המודל `Post`: - -~~~ -[php] -class Post extends CActiveRecord -{ - ...... - public function scopes() - { - return array( - 'published'=»array( - 'condition'=»'status=1', - ), - 'recently'=»array( - 'order'=»'create_time DESC', - 'limit'=»5, - ), - ); - } -} -~~~ - -כל מרחב מוגדר בתור מערך שניתן לאתחל בעזרתו אובייקט של [CDbCriteria]. לדוגמא, המרחב `recently` מגדיר את המאפיין `order` בערך `create_time DESC` ואת המאפיין `limit` לערך 5, שמתורגם לתנאי בשאילתה שאמור להחזיר את חמשת ההודעות האחרונות. - -שימושם העיקרי של המרחבים המוגדרים הינו *שינוי והגדרה* של קריאה למתודות `find` שונות. ניתן לשרשר כמה מרחבים מוגדרים וכתוצאה מכך לבצע שאילתה הרבה יותר מוגבלת הכוללת יותר תנאים. לדוגמא, בכדי למצוא את ההודעות שפורסמו לאחרונה, אנו יכולים להשתמש בקוד הבא: - -~~~ -[php] -$posts=Post::model()-»published()-»recently()-»findAll(); -~~~ - -בדרך כלל, מרחב מוגדר צריך להיות מוגדר מצד שמאל (לבוא לפני) של המתודה `find`. כל אחד מהם מספק תנאי לשאילתה, אשר בסופו של דבר מתאחד עם תנאים אחרים, כולל את התנאי שהועבר למתודת `find`, ויוצר תנאי אחד גדול. התוצאה הסופית דומה להוספת רשימה של פילטרים לשאילתה. - -החל מגרסא 1.0.6, ניתן להשתמש במרחבים מוגדרים במתודות `update` ו `delete`. לדוגמא, הקוד הבא ימחק את כל ההודעות שנוספו לאחרונה: - -~~~ -[php] -Post::model()-»published()-»recently()-»delete(); -~~~ - -» Note|הערה: ניתן להשתמש במרחבים מוגדרים על מתודות במחלקה הנוכחית. זאת אומרת, המתודה צריכה להקרא על ידי `()ClassName::model`. - -### מרחבים מוגדרים עם פרמטרים - -ניתן להוסיף פרמטרים (סממנים) למרחבים מוגדרים. לדוגמא, אנו נרצה לשנות את הערך של ההודעות במרחב המוגדר בשם `recently`. בכדי לעשות זאת, במקום להגדיר את שם המרחב במתודה [CActiveRecord::scopes], אנו צריכים להגדיר מתודה ששמה הוא זהה לשם המרחב בו אנו משתמשים: - -~~~ -[php] -public function recently($limit=5) -{ - $this-»getDbCriteria()-»mergeWith(array( - 'order'=»'create_time DESC', - 'limit'=»$limit, - )); - return $this; -} -~~~ - -לאחר מכן, אנו משתמשים בביטוי הבא בכדי לקבל את שלושת ההודעות שפורסמו לאחרונה: - -~~~ -[php] -$posts=Post::model()-»published()-»recently(3)-»findAll(); -~~~ - -במידה ולא נעביר את הספרה 3 כפרמטר במתודה `recently` בקוד למעלה, אנו נקבל את חמשת ההודעות שפורסמו לאחרונה כברירת מחדל. - -### מרחבים מוגדרים כברירת מחדל - -במחלקה של מודל ניתן להגדיר מרחב מוגדר אשר יתווסף לכל השאילתות שאנו מריצים בעזרת המחלקה (כולל קישורים לטבלאות אחרות). לדוגמא, אתר התומך בכמה שפות ירצה להציג תוכן באותה שפה שהמשתמש כרגע צופה בה. מאחר וישנם שאילתות רבות בנוגע לתוכן האתר, אנו יכולים להגדיר מרחב מוגדר ברירת מחדל בכדי לפתור בעיה זו. בכדי לבצע זאת, אנו נצטרך לדרוס את המתודה [CActiveRecord::defaultScope] בצורה הבאה, - -~~~ -[php] -class Content extends CActiveRecord -{ - public function defaultScope() - { - return array( - 'condition'=»"language='".Yii::app()-»language."'", - ); - } -} -~~~ - -כעת, הביטוי הבא אוטומטית משתמש בתנאי כפי שהוגדר למעלה: - -~~~ -[php] -$contents=Content::model()-»findAll(); -~~~ - -יש לזכור ששימוש במרחבים מוגדרים כברירת מחדל תקף רק לשאילתות מסוג `SELECT`. הוא אינו תקף לגבי `INSERT`, `UPDATE` ו `DELETE` ופשוט יתעלם מהם. - +Active Record +============= + +למרות שה DAO של Yii יכול לטפל בכמעט כל פעולה הקשורה למסד הנתונים, רוב הסיכויים שאנו נבזבז 90% מהזמן שלנו בכתיבת שאילתות SQL אשר מבצעות את הפעולות הנפוצות במסד הנתונים CRUD (יצירה, קריאה, עדכון ומחיקה). גם קשה לנהל את הקוד שאנו כותבים כשהוא מעורבב עם שאילתות SQL שונות. כדי לפתור בעיות אלו אנו יכולים להשתמש ב Active Record. + +Active Record או בקיצור AR הינה שיטת ORM (Object-Relational Mapping) נפוצה. כל מחלקת AR מייצגת טבלה במסד הנתונים אשר התכונות שלה מאופיינות כמשתנים במחלקת ה AR, ואובייקט של המחלקה מייצג שורה אחת בטבלה. פעולות CRUD נפוצות מיושמות בתור מתודות במחלקת ה AR. כתוצאה מכך, אנו יכולים לגשת למידע במסד הנתונים בצורה יותר מונחית-עצמים. לדוגמא, אנו יכולים להשתמש בקוד הבא כדי להכניס שורה חדשה בטבלה `tbl_post`: + +~~~ +[php] +$post=new Post; +$post-»title='כותרת לדוגמא'; +$post-»content='תוכן הודעה לדוגמא'; +$post-»save(); +~~~ + +במסמך זה אנו נתאר כיצד להגדיר מחלקת AR ולהשתמש בה כדי לבצע פעולות CRUD. בחלק הבא אנו נציג כיצד להשתמש ב AR כדי לטפל בקשרים בין טבלאות במסד הנתונים. לנוחות, אנו נשתמש בטבלה הבאה למטרת הצגת הדוגמאות בחלק זה. הערה: במידה והינך עובד עם מסד נתונים מסוג MySQL, יש להחליף את `AUTOINCREMENT` ב `AUTO_INCREMENT` בשאילתה הבאה. + +~~~ +[sql] +CREATE TABLE tbl_post ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + title VARCHAR(128) NOT NULL, + content TEXT NOT NULL, + create_time INTEGER NOT NULL +); +~~~ + +» Note|הערה: שימוש ב-AR לא נועד כדי לפתור את כל הבעיות הקשורות למסדי הנתונים. השימוש הטוב ביותר בו נועד על מנת לבצע שאילות SQL פשוטות (CRUD) ושימוש בתור מודל באפליקציה. מומלץ לא להשתמש בו בעת ביצוע שאילתות SQL מורכבות. למטרה זו מומלץ להשתמש ב DAO. + + +התחברות למסד הנתונים +-------------------------- + +AR משתמך על חיבור למסד הנתונים כדי לבצע פעולות הקשורות אליו. כברירת מחדל, AR מניח שהרכיב `db` מספק אובייקט [CDbConnection] המאפשר התחברות למסד הנתונים. הגדרות האפליקציה הבאות מציגות דוגמא לשימוש: + +~~~ +[php] +return array( + 'components'=»array( + 'db'=»array( + 'class'=»'system.db.CDbConnection', + 'connectionString'=»'sqlite:path/to/dbfile', + // הפעלת מטמון להפחתת עומס + // 'schemaCachingDuration'=»3600, + ), + ), +); +~~~ + +» Tip|טיפ: מאחר וה AR מסתמך על ה metadata של הטבלאות כדי להחליט לגבי סוגי העמודות בטבלאות, לוקח זמן לקרוא מידע זה אודות הטבלאות ולנתח אותו. במידה ותרשים טבלאות מסדי הנתונים שלך לא משתנה, מומלץ להפעיל את המטמון לגבי הנתונים אודות הטבלאות על ידי הגדרת המאפיין [CDbConnection::schemaCachingDuration] לערך הגדול מ-0. + +תמיכה ב AR מוגבלת על פי DBMS. נכון לעכשיו, ה-DBMS הבאים נתמכים: + +- [MySQL 4.1 או יותר](http://www.mysql.com) + +- [PostgreSQL 7.3 או יותר](http://www.postgres.com) + +- [Microsoft SQL Server 2000 או יותר](http://www.microsoft.com/sqlserver/) + +- [Oracle](http://www.oracle.com) + + +» Note|הערה: התמיכה ב SQLServer קיימת מגרסאות 1.0.4 ומעלה. והתמיכה של Oracle קיימת מגרסאות 1.0.5 ומעלה. + +אם ברצונך להשתמש ברכיב אפליקציה שהוא לא `db`, או אם ברצונך לעבוד עם כמה מסדי נתונים בעזרת ה AR, רצוי שתדרוס את [CActiveRecord::getDbConnection]. המחלקה [CActiveRecord] הינה מחלקת הבסיס של כל מחלקות ה AR. + +» Tip|טיפ: ישנם שני דרכים לעבוד עם כמה מסדי נתונים תוך שימוש ב-AR . אם תרשים מסדי הנתונים הוא שונה, תוכל ליצור מחלקת AR בסיסית שונה אשר בתוכה יהיה צורך לדרוס את [getDbConnection|CActiveRecord::getDbConnection] בצורה שונה. דרך נוספת תיהיה לשנות את המשתנה הסטטי [CActiveRecord::db] בצורה דינאמית בעת הצורך. + +הגדרת מחלקת AR +----------------- + +כדי לגשת לטבלה במסד הנתונים, אנו קודם צריכים להגדיר מחלקת AR היורשת מ [CActiveRecord]. כל מחלקת AR מייצגת טבלה במסד הנתונים, ואובייקט של המחלקה מייצג רשומה באותה טבלה. הדוגמא הבאה מציגה את הקוד המינימלי הדרוש למחלקת ה AR המייצגת את הטבלה `tbl_post`. + +~~~ +[php] +class Post extends CActiveRecord +{ + public static function model($className=__CLASS__) + { + return parent::model($className); + } + + public function tableName() + { + return 'tbl_post'; + } +} +~~~ + +» Tip|טיפ: מאחר וניתן לקרוא למחלקות AR במקומות רבים, ניתן לייבא את כל התיקיה המכילה את מחלקת ה AR, במקום לייבא אותם אחד אחד. לדוגמא, אם כל מחלקות ה AR נמצאות תחת התיקיה `protected/models`, ניתן להגדיר את האפליקציה בצורה הבאה: +» ~~~ +» [php] +» return array( +» 'import'=»array( +» 'application.models.*', +» ), +» ); +» ~~~ + +כברירת מחדל, שם מחלקת ה AR הוא זהה לשם של הטבלה במסד הנתונים. יש לדרוס את המתודה [tableName|CActiveRecord::tableName] במידה והם שונים אחד מהשני. המתודה [model|CActiveRecord::model] מוגדרת בצורה הזאת לכל מחלקת AR (הסבר בהמשך). + +» Info|מידע: כדי להשתמש באפשרות [קידומת טבלאות](/doc/guide/database.dao#using-table-prefix) הקיימת מגרסאות 1.1.0, המתודה [tableName|CActiveRecord::tableName] של מחלקת AR צריכה להדרס בצורה הבאה, +» ~~~ +» [php] +» public function tableName() +» { +» return '{{post}}'; +» } +» ~~~ +זאת, במקום להחזיר את שמה המלא של הטבלה, אנו מחזירים את שם הטבלה ללא הקידומת ועוטפים אותה בסוגריים מסולסלות כפולות. + +ניתן לגשת לערכים בעמודות של טבלה כמאפיינים של מחלקת ה AR. לדוגמא, הקוד הבא מגדיר את העמודה (מאפיין) `title`: + +~~~ +[php] +$post=new Post; +$post-»title='הודעה לדוגמא'; +~~~ + +למרות שאנו לא מגדירים ספציפית את המאפיין `title` במחלקת ה `Post`, אנו עדיין יכולים לגשת אליו בקוד המוצג למעלה. הסיבה לכך היא מכיוון ש `title` הינו עמודה בטבלה `tbl_post` , ומחלקת ה AR מאפשרת גישה אליו כמאפיין במחלקה בעזרת פונקצית ה `__get` ב PHP. תזרק שגיאה במידה ויהיה ניסיון לגשת לעמודה אשר לא קיימת בטבלה. + +» Info|מידע: במדריך זה, אנו משתמשים באותיות קטנות לכל שמות הטבלאות והעמודות. זאת מאחר וכל DBMS רגיש באופן שונה לאותיות גדולות-קטנות. לדוגמא, PostgreSQL לא רגיש לאותיות גדולות-קטנות לשמות עמודות בטבלאות כברירת מחדל, ואנו חייבים לתחום את שמות העמודות בתנאים בתוך השאילתה במידה ושם העמודה מכיל שילוב של אותיות גדולות וקטנות. שימוש באותיות קטנות בלבד בשמות פותר בעיה זו. + +מחלקת AR מסתמכת על מפתחות ראשיים המוגדרים בטבלאות. במידה וטבלה לא מכילה מפתח ראשי, יש צורך בהגדרת המפתח הראשי במחלקה על ידי דריסה של המתודה `primaryKey` כפי שמוצג בדוגמא הבאה, + +~~~ +[php] +public function primaryKey() +{ + return 'id'; + // בעבור מפתחות מורכבים, יש להחזיר מערך בצורה הבאה + // return array('pk1', 'pk2'); +} +~~~ + + +יצירת רשומה +--------------- + +בכדי להכניס רשומה חדשה לטבלה במסד הנתונים, אנו יוצרים אובייקט חדש של מחלקת ה AR, מגדירים את המאפיינים שלה בהתאם לעמודות בטבלה, וקוראים למתודה [save|CActiveRecord::save] כדי להשלים את תהליך ההוספה. + +~~~ +[php] +$post=new Post; +$post-»title='כותרת לדוגמא'; +$post-»content='תוכן להודעה לדוגמא'; +$post-»create_time=time(); +$post-»save(); +~~~ + +במידה והמפתח הראשי של הטבלה הינו ערך מספרי אשר עולה אוטומטית (מוגדר כ auto_increment), לאחר ההוספה לטבלה מחלקת ה AR תכיל מפתח ראשי מעודכן. בדוגמא למעלה, המאפיין `id` משקף את המפתח הראשי של ההודעה החדשה שנוצרה כרגע, למרות שאנו לא משנים אותו בצורה ספציפית. + +במידה וישנו ערך ברירת מחדל לעמודה בטבלה (לדוגמא סטרינג או מספר), המאפיין במחלקת ה AR יכיל את אותו ערך ברירת מחדל לאחר יצירת הרשומה החדשה. דרך אחת לשנות ערך ברירת מחדל זה היא על ידי הגדרת המאפיין במחלקת ה AR עם ערך ברירת מחדל חדש: + +~~~ +[php] +class Post extends CActiveRecord +{ + public $title='אנא הזן כותרת'; + ...... +} + +$post=new Post; +echo $post-»title; // זה יציג: אנא הזן כותרת +~~~ + +החל מגרסא 1.0.2, ניתן להגדיר ערך של [CDbExpression] למאפיין כלשהו לפני שמירת הרשומה (בין אם זה יצירת רשומה חדשה או עדכון רשומה קיימת) במסד הנתונים. לדוגמא, בכדי לשמור את הזמן הנוכחי המוחזר באמצעות הפונקציה `()NOW` ב MySQL, אנו יכולים להשתמש בקוד הבא: + +~~~ +[php] +$post=new Post; +$post-»create_time=new CDbExpression('NOW()'); +// $post-»create_time='NOW()'; לא יעבוד מאחר +// 'NOW()' יהיה כסטרינג ולא כפונקציה +$post-»save(); +~~~ + +» Tip|טיפ: אף על פי ש AR מאפשר לנו לבצע פעולות הקשורות למסד הנתונים ללא צורך בכתיבה של שאילתות SQL מסובכות, אנו בדרך כלל נרצה לדעת אילו שאילתות SQL רצות מאחורי ה AR. ניתן לקבל מידע זה על ידי הפעלת אפשרות [התיעוד](/doc/guide/topics.logging) של Yii. לדוגמא, אנו יכולים להפעיל את [CWebLogRoute] בהגדרות האפליקציה, ואנו נראה את השאילתות שבוצעו בסוף כל עמוד. מגרסא 1.0.5, אנו יכולים להגדיר את המאפיין [CDbConnection::enableParamLogging] לערך השווה ל `true` בהגדרות האפליקציה כדי שהפרמטרים התחומים בשאילתה יוצגו גם הם בתיעוד. + +קריאת רשומה +-------------- + +בכדי לקרוא מידע מטבלה במסד הנתונים, אנו קוראים לאחת ממתודות ה `find` הבאות. + +~~~ +[php] +// מצא את הרשומה הראשונה המשביע את התנאי שהועבר +$post=Post::model()-»find($condition,$params); +// מצא את הרשומה עם המפתח הראשי שהועבר +$post=Post::model()-»findByPk($postID,$condition,$params); +// מצא את הרשומה עם המאפיין שהועבר +$post=Post::model()-»findByAttributes($attributes,$condition,$params); +// מצא את הרשומה הראשונה עם השאילתה שהועברה +$post=Post::model()-»findBySql($sql,$params); +~~~ + +בדוגמאות למעלה, אנו קוראים למתודות ה `find` בעזרת `()Post::model`. זכור שהמתודה הסטטית `()model` הינה הכרחית לכל מחלקת AR. המתודה מחזירה אובייקט AR אשר משתמשים בו לגשת למתודות בעלי הרשאה לאותה מחלקה בלבד (משהו דומה למתודות סטטיות במחלקה) באופן מונחה עצמים. + +אם המתודה `find` מוצאת רשומה המשביע את התנאים שהועברו, היא תחזיר אובייקט של `Post` כשהמאפיינים שלה מכילים את הערכים של העמודות של אותה רשומה בטבלה. לאחר מכן אנו יכולים לקרוא את הערכים שנטענו בצורה הרגילה בה אנו ניגשים למאפיינים של המחלקה, לדוגמא, `;echo $post-»title`. + +מתודת ה `find` תחזיר null במידה ולא נמצא שום דבר במסד הנתונים התואם לתנאים שהועברו. + +בעת הקריאה ל `find`, אנו משתמשים ב `condition$` ו `params$` כדי להגדיר את התנאים של השאילתה. כאן `condition$` יכול להוות סטרינג המייצג את הסעיף `WHERE` בשאילתת SQL, ו `params$` הינו מערך של פרמטרים שערכיהם צריכים להתחם במפתחות שהוגדרו מראש ב `condition$`. לדוגמא, + +~~~ +[php] +// מצא את הרשומה איפה ש postID = 10 +$post=Post::model()-»find('postID=:postID', array(':postID'=»10)); +~~~ + +» Note|הערה: בדוגמא למעלה, אנו נצטרך לבצע חיטוי לייחוס של העמודה `postID` בעבור DBMS מסויימים. לדוגמא, במידה ואנחנו משתמשים ב PostgreSQL, אנו נצטרך לכתוב את התנאי בצורה הבאה `postID"=:postID"`, מאחר ו PostgreSQL כברירת מחדל יתייחס לשמות העמודות ללא רגישות לאותיות גדולות-קטנות. + +כמו כן אנו יכולים להשתמש ב `condition$` כדי להגדיר תנאים מורכבים יותר. במקום סטרינג, אנו נותנים ל `condition$` להיות אובייקט של [CDbCriteria], המאפשר לנו להגדיר תנאים נוספים מלבד סעיף ה `WHERE`. לדוגמא, + +~~~ +[php] +$criteria=new CDbCriteria; +$criteria-»select='title'; // בחר רק את העמודה 'title' +$criteria-»condition='postID=:postID'; +$criteria-»params=array(':postID'=»10); +$post=Post::model()-»find($criteria); // $params אינו נחוץ כאן +~~~ + +זכור, שבעת השימוש ב [CDbCriteria] בתור התנאי של השאילתה, הפרמטר `params$` אינו נחוץ מאחר וניתן להגדיר אותו בעזרת [CDbCriteria], כפי שמוצג למעלה. + +כמו כן, במקום שימוש ב [CDbCriteria] ניתן להעביר מערך למתודת ה-`find`. +שמות המפתחות והערכים מתייחסות למאפיינים של התנאים וערכיהם, בהתאם. ניתן לשכתב את הדוגמא למעלה בקוד הבא, + +~~~ +[php] +$post=Post::model()-»find(array( + 'select'=»'title', + 'condition'=»'postID=:postID', + 'params'=»array(':postID'=»10), +)); +~~~ + +» Info|מידע: כשהתנאי של השאילתה עוסק בהתאמת עמודות והערכים שהוגדרו, אנו יכולים להשתמש ב [findByAttributes()|CActiveRecord::findByAttributes]. +אנו נותנים לפרמטר `attributes$` להיות מערך של שמות העמודות בתור המפתחות וערך כל מפתח הינו הערך שאותו אנו רוצים להתאמים בשאילתה. בכמה פריימוורקים (Frameworks), פעולה זו ניתנת לביצוע על ידי קריאה למתודות כמו `findByNameAndTitle`. למרות שגישה זו נראית מושכת, היא גורמת ברוב המקרים לבלבול, קונפליקטים ובעיות של רגישות לאותיות גדולות-קטנות בשמות העמודות. + +כשישנם מספר רב של רשומות התואמות לתנאים שהוצבו בשאילתה, אנו יכולים להביא את כולם יחדיו על ידי שימוש במתודות `findAll` הבאות, לכל אחת מהם ישנה מתודת `find` תואמת, כפי שכבר הסברנו. + +~~~ +[php] +// מצא את כל הרשומות התואמות לתנאי שהועבר +$posts=Post::model()-»findAll($condition,$params); +// מצא את כל הרשומות בעלות המפתח הראשי שהועבר +$posts=Post::model()-»findAllByPk($postIDs,$condition,$params); +// מצא את כל הרשומות התואמות למאפיינים שהועברו +$posts=Post::model()-»findAllByAttributes($attributes,$condition,$params); +// מצא את כל הרשומות אשר תואמות לשאילתה שהועברה +$posts=Post::model()-»findAllBySql($sql,$params); +~~~ + +במידה ולא נמצאו התאמות לתנאים שהוצבו בשאילתה, `findAll` תחזיר מערך ריק. זה שונה מהערך שיוחזור ממתודות `find` המחזירות null במידה ולא נמצאו רשומות. + +מלבד המתודות `find` ו `findAll` המתוארות למעלה, לנוחות השימוש ניתן להשתמש במתודות הבאות גם כן: + +~~~ +[php] +// קבל את מספר הרשומות התואמות לתנאי שהועבר +$n=Post::model()-»count($condition,$params); +// קבל את מספר הרשומות התואמות לשאילתה שהועברה +$n=Post::model()-»countBySql($sql,$params); +// בדוק אם קיימת לפחות רשומה אחת התואמת לתנאי שהועבר +$exists=Post::model()-»exists($condition,$params); +~~~ + +עדכון רשומה +--------------- + +לאחר שאובייקט AR עם ערכים לעמודות, ניתן לשנותם ולשמור אותם בחזרה לטבלה במסד. + +~~~ +[php] +$post=Post::model()-»findByPk(10); +$post-»title='כותרת חדש'; +$post-»save(); // שמור את השינויים במסד +~~~ + +כפי שניתן לראות, אנו משתמשים באותה מתודה [save()|CActiveRecord::save] כדי לבצע פעולות הוספה ועדכון. במידה ואובייקט ה AR נוצר כאובייקט חדש בעזרת שימוש באופרטור `new`, קריאה ל [save()|CActiveRecord::save] תוסיף רשומה חדשה לטבלה במסד הנתונים; במידה ואובייקט ה AR נוצר כתוצאה משימוש במתודה כמו `find` או `findAll`, קריאה ל [save()|CActiveRecord::save] תעדכן את הרשומה הקיימת בטבלה. למעשה, אנו יכולים להשתמש ב [CActiveRecord::isNewRecord] כדי להבחין במידה אובייקט ה AR הינו חדש או לא. + +ניתן לעדכן רשומה אחת או יותר בטבלה במסד הנתונים ללא צורך בטעינתם מראש. AR מספקת את המתודות הבאות לנוחות השימוש: + +~~~ +[php] +// עדכן את הרשומות התואמות לתנאי שהועבר +Post::model()-»updateAll($attributes,$condition,$params); +// עדכן את הרשומות התואמות לתנאי שהועבר ולמפתחות הראשיים שהועברו +Post::model()-»updateByPk($pk,$attributes,$condition,$params); +// עדכן את עמודות הספירה ברשומות התואמות לתנאי שהועבר +Post::model()-»updateCounters($counters,$condition,$params); +~~~ + +בדוגמא למעלה, `attributes$` הינו מערך של שמות העמודות וערכיהם; `counter$` הינו מערך של שמות העמודות וערכיהם הינו מספר עולה; ו `condition$` ו `params$` כפי שהוסבר בחלק הקודם. + +מחיקת רשומה +--------------- + +אנו יכולים למחוק רשומה במידה ואובייקט ה AR נוצר על ידי קבלת המידע מהטבלה במסד הנתונים. + +~~~ +[php] +$post=Post::model()-»findByPk(10); // נניח וישנה הודעה עם המספר 10 +$post-»delete(); // מחק את הרשומה מהטבלה במסד הנתונים +~~~ + +הערה, לאחר המחיקה, אובייקט ה AR נשאר ללא שינוי, אבל אותה שורה בטבלה במסד הנתונים נמחקה. + +לנוחות ניתן להעזר במתודות הבאות כדי למחוק רשומות ללא צורך בטעינה מראש שלהם: + +~~~ +[php] +// מחק את הרשומות התואמות לתנאי שהועבר +Post::model()-»deleteAll($condition,$params); +// מחק את הרשומות התואמות לתנאי שהועבר ולמפתחות שהועברו +Post::model()-»deleteByPk($pk,$condition,$params); +~~~ + +אימות נתונים +--------------- + +כשמוסיפים או מעדכנים רשומה, אנו בדרך כלל צריכים לבדוק במידה והערכים שהוצבו עונים על חוקים מסויימים. זה בהחלט הכרחי במידה והערכים שמוצבים מוגדרים על ידי משתמשי קצה. בדרך כלל, אנו לא יכולים לסמוך על שום דבר המגיע מצד הלקוח. + +AR מבצע אימות נתונים אוטומטית בעת הקריאה ל [()save|CActiveRecord::save]. האימות מבוסס על החוקים שהוגדרו במתודת [()rules|CModel::rules] במחלקת ה AR. למידע נוסף אודות הגדרת חוקי אימות נתונים, יש לקרוא את החלק אודות [הגדרת חוקי אימות נתונים](/doc/guide/form.model#declaring-validation-rules). למטה מוצג קוד בסיסי הנחוץ לשמירה של רשומה: + +~~~ +[php] +if($post-»save()) +{ + // נתונים אומתו והרשומה נוספה/עודכנה +} +else +{ + // נתונים לא תקינים. יש לקרוא למתודה ה getErrors() לקבלת השגיאות שחזרו +} +~~~ + +כשהמידע לעדכון או הוספה של רשומה מתקבל על ידי משתמשי קצה בעזרת טופס HTML , אנו צריכים להציב אותם למאפיינים התואמים במחלקת ה AR. ניתן לבצע זאת בצורה הבאה: + +~~~ +[php] +$post-»title=$_POST['title']; +$post-»content=$_POST['content']; +$post-»save(); +~~~ + +במידה וישנם עמודות רבים, אנו נראה רשימה ארוכה של הצבות כאלו. בכדי להקל על תהליך ההצבה ניתן להעזר במאפיין [attributes|CActiveRecord::attributes] כפי שמוצג בדוגמא למטה. +מידע נוסף ניתן למצוא בחלק [אבטחת הצבת מאפיינים](/doc/guide/form.model#securing-attribute-assignments) ובחלק [יצירת פעולה](/doc/guide/form.action) . + +~~~ +[php] +// נניח ש $_POST['Post'] הינו מערך ששמות המפתחות הינם שמות העמודות בטבלה והערכים שלהם בהתאם +$post-»attributes=$_POST['Post']; +$post-»save(); +~~~ + + +השוואת רשומות +----------------- + +בדומה לשורות בטבלה, אובייקטים של AR מזוהים בצורה יחודית על ידי הערך במפתח הראשי שלהם. לכן, בכדי להשוות בין שני אובייקטים של AR, כל מה אנו צריכים לעשות זה להשוות בין את ערכי המפתחות הראשיים שלהם, בהנחה שהם שייכים לאותה מחלקת AR. למרות, שיהיה יותר קל לקרוא פשוט למתודה [()CActiveRecord::equals]. + +» Info|מידע: בניגוד ליישום ושימוש של AR בפריימוורקס (Frameworks) שונים אחרים, Yii תומכת במפתחות ראשיים מורכבים במחלקות ה AR שלה. מפתח ראשי מורכב מכיל שני עמודות או יותר. מפתח ראשי מורכב מיוצג על ידי מערך ב Yii, בהתאמה. המאפיין [primaryKey|CActiveRecord::primaryKey] מספק את ערך המפתח הראשי של אובייקט AR. + +התאמה אישית +------------- + +[CActiveRecord] מספקת כמה מתודות שניתנים לדריסה על ידי תתי המחלקות שלה כדי לשנות את רצף העבודה שלהם וההתנהלות. + +- [beforeValidate|CModel::beforeValidate] ו [afterValidate|CModel::afterValidate]: מתודות אלו נקראות לפני ואחרי ביצוע אימות הנתונים. + +- [beforeSave|CActiveRecord::beforeSave] ו [afterSave|CActiveRecord::afterSave]: מתודות אלו נקראות לפני ואחרי שמירת אובייקט AR. + +- [beforeDelete|CActiveRecord::beforeDelete] ו [afterDelete|CActiveRecord::afterDelete]: מתודות אלו נקראות לפני ואחרי מחיקת אובייקט AR. + +- [afterConstruct|CActiveRecord::afterConstruct]: מתודה זו רצה בכל פעם שאובייקט AR חדש נוצר בעזרת האופרטור `new`. + +- [beforeFind|CActiveRecord::beforeFind]: מתודה זו רצה לפני שימוש באחד ממתודות ה `find` (לדוגמא `find`, `findAll`). אפשרות זו קיימת מגרסאות 1.0.9 ומעלה. + +- [afterFind|CActiveRecord::afterFind]: מתודה זו רצה לאחר יצירת אובייקט AR כתוצאה מביצוע שאילתה. + + +שימוש בטרנזקציה בעזרת AR +------------------------- + +כל אובייקט AR מכיל מאפיין בשם [dbConnection|CActiveRecord::dbConnection] אשר מייצג אובייקט של [CDbConnection]. לכן אנו יכולים להשתמש באפשרות של [טרנזקציות](/doc/guide/database.dao#using-transactions) המסופקת על ידי ה-DAO של Yii בעת הצורך במהלך השימוש ב-AR: + +~~~ +[php] +$model=Post::model(); +$transaction=$model-»dbConnection-»beginTransaction(); +try +{ + // שימוש ב find ו save הינם שני שלבים אשר ניתן להתערב בהם על ידי בקשות נוספות + // לכן אנו משתמשים בטרנזקציה כדי לוודא המשכיות + $post=$model-»findByPk(10); + $post-»title='כותרת חדשה'; + $post-»save(); + $transaction-»commit(); +} +catch(Exception $e) +{ + $transaction-»rollBack(); +} +~~~ + + +מרחבים מוגדרים +------------ + +» Note|הערה: תמיכה במרחבים מוגדרים נוספה מגרסאות 1.0.5 ומעלה. הרעיון המקורי של מרחבים מוגדרים הגיע מ Ruby on Rails. + +*מרחב מוגדר* מייצג *שם* של תנאי בשאילתה שניתן לאחד אותה ביחד עם עוד מרחבים מוגדרים ולצרף לשאילתה של AR. + +מרחבים מוגדרים בדרך כלל מוגדרים במתודה [CActiveRecord::scopes] בזוגות בפורמט של שם-תנאי. הקוד הבא מגדיר שני מרחבים מוגדרים, `published` ו `recently`, במחלקה של המודל `Post`: + +~~~ +[php] +class Post extends CActiveRecord +{ + ...... + public function scopes() + { + return array( + 'published'=»array( + 'condition'=»'status=1', + ), + 'recently'=»array( + 'order'=»'create_time DESC', + 'limit'=»5, + ), + ); + } +} +~~~ + +כל מרחב מוגדר בתור מערך שניתן לאתחל בעזרתו אובייקט של [CDbCriteria]. לדוגמא, המרחב `recently` מגדיר את המאפיין `order` בערך `create_time DESC` ואת המאפיין `limit` לערך 5, שמתורגם לתנאי בשאילתה שאמור להחזיר את חמשת ההודעות האחרונות. + +שימושם העיקרי של המרחבים המוגדרים הינו *שינוי והגדרה* של קריאה למתודות `find` שונות. ניתן לשרשר כמה מרחבים מוגדרים וכתוצאה מכך לבצע שאילתה הרבה יותר מוגבלת הכוללת יותר תנאים. לדוגמא, בכדי למצוא את ההודעות שפורסמו לאחרונה, אנו יכולים להשתמש בקוד הבא: + +~~~ +[php] +$posts=Post::model()-»published()-»recently()-»findAll(); +~~~ + +בדרך כלל, מרחב מוגדר צריך להיות מוגדר מצד שמאל (לבוא לפני) של המתודה `find`. כל אחד מהם מספק תנאי לשאילתה, אשר בסופו של דבר מתאחד עם תנאים אחרים, כולל את התנאי שהועבר למתודת `find`, ויוצר תנאי אחד גדול. התוצאה הסופית דומה להוספת רשימה של פילטרים לשאילתה. + +החל מגרסא 1.0.6, ניתן להשתמש במרחבים מוגדרים במתודות `update` ו `delete`. לדוגמא, הקוד הבא ימחק את כל ההודעות שנוספו לאחרונה: + +~~~ +[php] +Post::model()-»published()-»recently()-»delete(); +~~~ + +» Note|הערה: ניתן להשתמש במרחבים מוגדרים על מתודות במחלקה הנוכחית. זאת אומרת, המתודה צריכה להקרא על ידי `()ClassName::model`. + +### מרחבים מוגדרים עם פרמטרים + +ניתן להוסיף פרמטרים (סממנים) למרחבים מוגדרים. לדוגמא, אנו נרצה לשנות את הערך של ההודעות במרחב המוגדר בשם `recently`. בכדי לעשות זאת, במקום להגדיר את שם המרחב במתודה [CActiveRecord::scopes], אנו צריכים להגדיר מתודה ששמה הוא זהה לשם המרחב בו אנו משתמשים: + +~~~ +[php] +public function recently($limit=5) +{ + $this-»getDbCriteria()-»mergeWith(array( + 'order'=»'create_time DESC', + 'limit'=»$limit, + )); + return $this; +} +~~~ + +לאחר מכן, אנו משתמשים בביטוי הבא בכדי לקבל את שלושת ההודעות שפורסמו לאחרונה: + +~~~ +[php] +$posts=Post::model()-»published()-»recently(3)-»findAll(); +~~~ + +במידה ולא נעביר את הספרה 3 כפרמטר במתודה `recently` בקוד למעלה, אנו נקבל את חמשת ההודעות שפורסמו לאחרונה כברירת מחדל. + +### מרחבים מוגדרים כברירת מחדל + +במחלקה של מודל ניתן להגדיר מרחב מוגדר אשר יתווסף לכל השאילתות שאנו מריצים בעזרת המחלקה (כולל קישורים לטבלאות אחרות). לדוגמא, אתר התומך בכמה שפות ירצה להציג תוכן באותה שפה שהמשתמש כרגע צופה בה. מאחר וישנם שאילתות רבות בנוגע לתוכן האתר, אנו יכולים להגדיר מרחב מוגדר ברירת מחדל בכדי לפתור בעיה זו. בכדי לבצע זאת, אנו נצטרך לדרוס את המתודה [CActiveRecord::defaultScope] בצורה הבאה, + +~~~ +[php] +class Content extends CActiveRecord +{ + public function defaultScope() + { + return array( + 'condition'=»"language='".Yii::app()-»language."'", + ); + } +} +~~~ + +כעת, הביטוי הבא אוטומטית משתמש בתנאי כפי שהוגדר למעלה: + +~~~ +[php] +$contents=Content::model()-»findAll(); +~~~ + +יש לזכור ששימוש במרחבים מוגדרים כברירת מחדל תקף רק לשאילתות מסוג `SELECT`. הוא אינו תקף לגבי `INSERT`, `UPDATE` ו `DELETE` ופשוט יתעלם מהם. + «div class="revision"»$Id: database.ar.txt 1681 2010-01-08 03:04:35Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/database.arr.txt b/docs/guide/he/database.arr.txt index 6858e40f6..d246536d0 100644 --- a/docs/guide/he/database.arr.txt +++ b/docs/guide/he/database.arr.txt @@ -1,383 +1,383 @@ -Relational Active Record -======================== - -כבר ראינו ולמדנו כיצב להשתמש ב Active Record (AR) כדי לשלוף מידע מטבלה במסד הנתונים. בחלק זה, אנו נסביר כיצד להשתמש ב AR כדי לאחד כמה טבלאות המקושרות אחת לשנייה ולהחזיר את סט הנתונים המאוחד. - -בכדי להשתמש בקישור AR, מומלץ להגדיר את המפתחות הראשיים של הטבלאות העומדות להיות מאוחדות עם טבלאות אחרות כבר מראש. הגדרתם תעזור לשמירת ההמשכיות והאחדות של המידע המקושר. - -כדי לפשט את ההסבר, אנו נשתמש בתרשים מסד הנתונים המוצג בדיאגרמה הבאה בכדי להדגים את הדוגמאות בחלק זה. - -![דיאגרמה](er.png) - -» Info|מידע: תמיכה במפתחות ראשיים חיצוניים תלויה ב DBMS בו משתמשים. SQLite « 3.6.19 אינו תומך במפתחות ראשיים חיצוניים, אך עדיין ניתן להגדיר את המפתחות החיצוניים בעת יצירת הטבלה. - -הצהרת קישורי טבלאות ----------------------- - -לפני שנשתמש ב AR בכדי לבצע שאילתה מקושרת, אנו צריכים ליידע את ה AR כיצד מחלקת AR אחת מקושרת עם אחרת. - -הקישור בין שני מחלקות AR משתייך ישירות לקישורים בין הטבלאות המיוצגות על ידי מחלקות ה AR. מנקודת מבט של מסד נתונים, קישור בין שני טבלאות A ו B ניתן להגדרה על ידי שלושה סוגים: יחיד-ל-רבים (`one-to-many` לדוגמא `tbl_user` ו `tbl_post`), יחיד-ל-יחיד ( `one-to-one` לדוגמא `tbl_user` ו `tbl_profile`) ו רבים-ל-רבים (`many-to-many` לדוגמא `tbl_category` ו `tbl_post`). ב AR ישנם ארבע סוגים של קישורים: - -- `BELONGS_TO`: במידה והקישור בין טבלה א' לבין טבלה ב' הינה יחיד-ל-רבים, אז ב' שייך ל א' (לדוגמא `Post` שייך ל `User`); - -- `HAS_MANY`: במידה והקישור בין טבלה א' לבין טבלה ב' הינה יחיד-ל-רבים, אז א' מכיל הרבה מ ב' (לדוגמא `User` מכיל הרבה `Post`); - -- `HAS_ONE`: זהו מקרה מיוחד של `HAS_MANY` איפה א' מכיל לכל היותר ב' אחד (לדוגמא `User` מכיל לכל היותר `Profile` אחד); - -- `MANY_MANY`: זה מתייחס לקישור של רבים-ל-רבים במסד נתונים. טבלה אסוציאטיבית נחוצה בכדי לחלק את הקישור רבים-ל-רבים לקישור יחיד-ל-רבים, מאחר ומרבית ה DBMS אינם תומכים בקישור מסוג רבים-ל-רבים כברירת מחדל. בדוגמא של תרשים מסד הנתונים, הטבלה `tbl_post_category` משרתת מטרה זו. בטרמינולוגיה של AR אנו יכולים להסביר את `MANY_MANY` כקומבינציה של `BELONGS_TO` ו `HAS_MANY`. לדוגמא, `Post` שייך לכמה `Category` ו `Category` מכיל הרבה `Post`. - -הגדרת קישורים ב AR כרוכה בדריסה של המתודה [relations|CActiveRecord::relations] של המחלקה [CActiveRecord]. המתודה מחזירה מערך של הגדרות קישורים. כל אלמנט במערך קישור בודד בפורמט הבא: - -~~~ -[php] -'VarName'=»array('RelationType', 'ClassName', 'ForeignKey', ...additional options) -~~~ - -`varName` מייצג את שם הקישור; `RelationType` מייצג את סוג הקישור, הוא יכול להיות אחד מהערכים: `self::BELONGS_TO`, `self::HAS_ONE`, `self::HAS_MANY` ו -`self::MANY_MANY`; `ClassName` הינו שם מחלקת ה AR הקשור למחלקת ה AR הנוכחית; ו `ForeignKey` מייצג את המפתח(ות) הראשיים השייכים לקישור זה. אפשרויות נוספות ניתנות להגדרה בסוף האלמנט לכל קישור (הסבר בהמשך). - -הקוד הבא מדגים כיצד אנו מצהירים על הקישורים בין המחלקות `Post` ו `User`. - -~~~ -[php] -class Post extends CActiveRecord -{ - ...... - - public function relations() - { - return array( - 'author'=»array(self::BELONGS_TO, 'User', 'author_id'), - 'categories'=»array(self::MANY_MANY, 'Category', - 'tbl_post_category(post_id, category_id)'), - ); - } -} - -class User extends CActiveRecord -{ - ...... - - public function relations() - { - return array( - 'posts'=»array(self::HAS_MANY, 'Post', 'author_id'), - 'profile'=»array(self::HAS_ONE, 'Profile', 'owner_id'), - ); - } -} -~~~ - -» Info|מידע: מפתח ראשי יכול להיות מורכב, המכיל שני עמודות או יותר. במקרה זה, אנו צריכים לאחד את שמות העמודות ולהפריד אותם בעזרת פסיק או רווח. עבור סוג קישור `MANY_MANY`, שם הטבלה המקושרת צריכה להיות מוגדרת במפתח החיצוני. לדוגמא, הקישור `categories` ב `Post` מוגדרת באמצעות המפתח החיצוני `tbl_post_category(post_id, category_id)`. - -ההצהרה של קישורים במחלקת ה AR מוסיפה בצורה עקיפה מאפיינים למחלקה אשר מהווים ייצוג של הקישורים עצמם. לאחר ביצוע שאילתה המקשרת בין טבלאות, המאפיין הנלמד יאוכלס על ידי הקישור של מחלקת ה AR. לדוגמא, אם `author$` מייצג אובייקט AR של `User`, אנו נוכל להשתמש ב `author-»posts$` בכדי לגשת לקישור של אובייקט ה `Post`. - -ביצוע שאילתות מקושרות ---------------------------- - -הדרך הפשוטה ביותר לביצוע שאילתה מקושרת היא על ידי קריאת מאפיין הקישור של אובייקט AR. במידה והמאפיין לא נקרא קודם לכן, תתבצע שאילתה מקושרת, המאחדת את שני הטבלאות המקושרות ומסננת בעזרת המפתח הראשי של אובייקט ה AR הנוכחי. תוצאת השאילתה תשמר במאפיין כאובייקט(ים) של מחלקת ה AR המקושרת. שיטה זו ידועה בשמה כ *טעינה עצלה* (lazy loading), כלומר, השאילתה המקושרת מתבצעת רק במידה וזמן שניגשים לאבוייקטים המקושרים (זאת אומרת קוראים את המאפיינים של הקישורים בין הטבלאות). הדוגמא הבאה מציגה כיצד להשתמש בשיטה זו: - -~~~ -[php] -// קבלת ההודעה עם id = 10 -$post=Post::model()-»findByPk(10); -// קבלת מידע אודות מפרסם ההודעה, כאן תתבצע שאילתה מקושרת -$author=$post-»author; -~~~ - -» Info|מידע: במידה ולא קיים אובייקט לקישור מסויים, אותו מאפיין במחלקת ה AR המקשרת יכול להיות null או מערך ריק. בעבור קישורי טבלאות מסוג `BELONGS_TO` ו `HAS_ONE`, התוצאה תיהיה null; בעבור קישורי טבלאות מסוג `HAS_MANY` ו `MANY_MANY`, התוצאה תיהיה מערך ריק. -זכור שקישורי הטבלאות מסוג `HAS_MANY` ו `MANY_MANY` מחזירים מערך של אובייקטים, תצטרכו לרוץ על כל התוצאות לפני שיהיה ניתן לגשת לכל אחד כאובייקט, אחרת, תזרק שגיאה של ניסיון לקריאה של מאפיינים לאובייקט שלא קיים. - -גישת ה*טעינה עצלה* נוחה מאוד לשימוש, אך היא לא יעילה במקרים מסויימים. לדוגמא, במידה ואנו רוצים לגשת למידע אודות המפרסם של `N` הודעות, שימוש ב*טעינה עצלה* תבצע `N` שאילתות מקשרות. אנו נצטרך לנקוט בגישה של *טעינה נלהבת* (eager loading) במקרים כאלו. - -גישת ה*טעינה נלהבת* מחזיר את האובייקטים המקושרים לאובייקט AR הראשי. ניתן לבצע זאת על ידי שימוש במתודה [with()|CActiveRecord::with] בשילוב של אחת מפקודות ה [find|CActiveRecord::find] או [findAll|CActiveRecord::findAll] במחלקת AR. לדוגמא, - -~~~ -[php] -$posts=Post::model()-»with('author')-»findAll(); -~~~ - -הקוד למעלה יחזיר מערך של אובייקטים של `Post`. בניגוד לגישה של טעינה עצלה, המאפיין `author` בכל אובייקט של `Post` כבר מאוכלס וקיים בשילוב של האובייקט של `User` לפני שאנו אפילו ניגשים לקרוא את המאפיין. -במקום לבצע שאילתה מקשרת לכל הודעה, גישת הטעינה הנלהבת מחזירה את כל ההודעות ביחד עם המידע אודות המפרסם שלה בשאילתה אחת! - -ניתן להגדיר מספר רב של שמות קישורים במתודה [with|CActiveRecord::with] וגישת הטעינה הנלהבת תחזיר את כולם במכה אחת. לדוגמא, הקוד הבא מחזיר הודעות ביחד עם המידע אודות המפרסם שלהם והקטגוריה בה הם פורסמו: - -~~~ -[php] -$posts=Post::model()-»with('author','categories')-»findAll(); -~~~ - -כמו כן, אנו יכולים לקנן בין קישורים בעזרת הטעינה הנלהבת. במקום רשימה של שמות קישורים, אנו מעבירים רפרזנטציה היררכית של שמות הקישורים למתודה [with|CActiveRecord::with], כפי שמוצג בדוגמא הבאה, - -~~~ -[php] -$posts=Post::model()-»with( - 'author.profile', - 'author.posts', - 'categories')-»findAll(); -~~~ - -הדוגמא למעלה תחזיר את כל ההודעות ביחד עם המידע אודות המפרסם והקטגוריות בהם הם פורסמו. בנוסף היא תחזיר את המידע אודות הפרופיל של המשתמש וההודעות שלו. - -החל מגרסא 1.1.0, ניתן להשתמש בגישת הטעינה הנלהבת בעזרת הגדרת המאפיין [CDbCriteria::with], כפי שמוצג בדוגמא: - -~~~ -[php] -$criteria=new CDbCriteria; -$criteria-»with=array( - 'author.profile', - 'author.posts', - 'categories', -); -$posts=Post::model()-»findAll($criteria); -~~~ - -או - -~~~ -[php] -$posts=Post::model()-»findAll(array( - 'with'=»array( - 'author.profile', - 'author.posts', - 'categories', - ) -); -~~~ - - -אפשרויות שאילתות מקושרות ------------------------- - -ציינו שניתן להגדיר אפשרויות נוספות בהגדרה של קישורים. אפשרויות אלו, המוגדרות כזוגות של שמות מפתחות וערכים, מוגדרות כדי לשנות את השאילתה המקושרת. סיכום של אפשרויות אלו נמצא למטה. - -- `select`: רשימה של עמודות שאותם צריכים לשלוף ממחלקת ה AR המקושרת. ברירת המחדל הינה '*', שזה אומר כל העמודות, שמות העמודות באפשרות זו צריכות להיות יחודיות. - -- `condition`: סעיף ה `WHERE`. כברירת מחדל הוא ריק. שמות העמודות באפשרות זו צריכות להיות יחודיות. - -- `params`: הפרמטרים שצריכים להיות תחומים לשאילתת ה SQL שתווצר. אפשרות זו צריכה להיות מוגדרת כמערך של מפתחות וערכים. אפשרות זו קיימת מגרסאות 1.0.3 ומעלה. - -- `on`: סעיף ה `ON`. התנאי המוגדר כאן יצורף לתנאי של הקישור בעזרת האופרטור `AND`. שמות העמודות באפשרות זו צריכות להיות יחודיות. אפשרות זו לא תקפה לסוג קישור `MANY_MANY`. אפשרות זו קיימת מגרסאות 1.0.2 ומעלה. - -- `order`: סעיף ה `ORDER BY`. כברירת מחדל הוא ריק. שמות העמודות באפשרות זו צריכות להיות יחודיות. - -- `with`: רשימה של אובייקטים המקושרים לאובייקט זה ושיש צורך בלטעון אותם ביחד עם אובייקט זה. דע לך ששימוש לא נכון באפשרות זו יכול לגרום ללולאה אין סופית. - -- `jointType`: סוג האיחוד לקישור זה. כברירת מחדל הוא מוגדר ל `LEFT OUTER JOIN`. - -- `alias`: שם קיצור לטבלה הקשורה לקישור זה. אפשרות זו קיימת מגרסא 1.0.1. כברירת מחדל היא מוגדרת ל null, שאומר שהקיצור לטבלה הוא זהה לשם הקישור עצמו. - -- `together`: במידה והטבלה המקושרת עם קישור זה מאולצת לאיחוד ביחד עם הטבלה הראשית ושאר הטבלאות. אפשרות זו תקפה רק לקישורים מסוג HAS_MANY ו MANY_MANY. במידה ואפשרות זו מוגדרת ל false, הטבלה המקושרת ל HAS_MANY או MANY_MANY תאוחד עם הטבלה הראשית בשאילתה נפרדת, אשר יכול לשפר את הביצועים הכוללים של השאילתה מאחר ופחות נתונים כפולים יוחזרו. כברירת מחדל האפשרות מוגדרת ל true. -למידע נוסף קרא את החלק "ביצועי שאילתה מקושרת". אפשרות זו קיימת מגרסא 1.0.3 ומעלה. - -- `group`: סעיף ה `GROUP BY`. כברירת מחדל הוא ריק. שמות העמודות באפשרות זו צריכות להיות יחודיות. אפשרות זו קיימת מגרסא 1.0.1 ומעלה. - -- `having`: סעיף ה `HAVING`. כברירת מחדל הוא ריק. שמות העמודות באפשרות זו צריכות להיות יחודיות. אפשרות זו קיימת מגרסא 1.0.1 ומעלה. - -- `index`: שמות העמודות שערכיהם צריכות להיות משומשות כמפתחות למערך המאחסן את האובייקטים המקושרים. ללא הגדרת אפשרות זו, אובייקט מקושר ישתמש במפתחות מספריים מ-0 למערך של האובייקטים המקושרים. אפשרות זו ניתנת להגדרה רק על קישורים מסוג `HAS_MANY` ו `MANY_MANY`. אפשרות זו קיימת מגרסא 1.0.7 ומעלה. - -בנוסף, האפשרויות הבאות קיימות גם כן לקישורים מסויימים במהלך טעינה עצלה: - -- `limit`: הגבלת מספר השורות לבחירה. אפשרות זו לא תקפה לקישור מסוג `BELONGS_TO`. - -- `offset`: מאיזה שורה להתחיל לשלוף את הנתונים. אפשרות זו לא תקפה לקישור מסוג `BELONGS_TO`. - -למטה אנו עורכים את הגדרת הקישור של `posts` במחלקה של `User` על ידי הוספת כמה מהאפשרויות למעלה: - -~~~ -[php] -class User extends CActiveRecord -{ - public function relations() - { - return array( - 'posts'=»array(self::HAS_MANY, 'Post', 'author_id', - 'order'=»'posts.create_time DESC', - 'with'=»'categories'), - 'profile'=»array(self::HAS_ONE, 'Profile', 'owner_id'), - ); - } -} -~~~ - -כעת, ברגע שניגש ל `author-»posts$`, אנו נקבל את ההודעות של המפרסם מסודרים לפי תאריך הפרסום שלהם בסדר יורד. כל אובייקט של הודעה מכיל גם את הקטגוריות טעונות בתוכו. - -שמות יחודיים לעמודות ---------------------------- - -כששם העמודות מופיע בשני טבלאות או יותר אשר מקושרות ביחד, יש צורך בלזהות אותם צבורה יחודית. זה נעשה על ידי מתן קידומת לעמודה עם השם המקוצר של הטבלה בה היא נמצאת. - -בשאילתת AR מקושרת, השם המקוצר לטבלה הראשית הינו מוגדר מראש בתור 't' בזמן ששם המקוצר לטבלה המקושרת כברירת מחדל הוא זהה לשמו של הקישור שהוגדר. לדוגמא, בביטוי הבא, השמות המקוצרים לטבלאות `Post` ו `Comment` הינו `t` ו `comments`, בהתאמה: - -~~~ -[php] -$posts=Post::model()-»with('comments')-»findAll(); -~~~ - -כעת, נניח שישנה עמודה בשם `create_time` בשני הטבלאות של `Post` ו `Comment` המייצג את הזמן פרסום ההודעה וזמן פרסום התגובה, ואנו נרצה לשלוף את ההודעות ביחד עם התגובות שלהם וסידור ההודעות על פי תאריך הפרסום שלהם קודם ולאחר מכן סידור התגובות על פי תאריך הפרסום שלהם. אנו נצטרך לתת שם יחודי לעמודה `create_time` בצורה הבאה: - -~~~ -[php] -$posts=Post::model()-»with('comments')-»findAll(array( - 'order'=»'t.create_time, comments.create_time' -)); -~~~ - -» Note|הערה: ההתנהגות של שמות עמודות יחודיים השתנתה החל מגרסא 1.1.0. קודם לכן בגרסאות *.1.0, כברירת מחדל Yii אוטומטית יצרה שם מקוצר לכל טבלה מקושרת, ואנו היינו צריכים להשתמש בקידומת של `.??` כדי להתייחס לשם המקוצר שנוצר אוטומטית. בנוסף, בגרסאות *.1.0, השם המקוצר שם הטבלה הראשית הוא שם הטבלה עצמה. - -אפשרויות דינאמיות לשאילתות מקושרות --------------------------------- - -החל מגרסא 1.0.2, אנו יכולים להשתמש באפשרויות דינאמיות לשאילתות מקושרות במתודה [with|CActiveRecord::with] וגם על ידי שימוש באפשרות `with`. האפשרויות הדינאמיות ידרוס את האפשרויות הקיימות שהוגדרו במתודה [relations|CActiveRecord::relations]. לדוגמא, בעזרת המודל `User` המצויין למעלה, במידה ואנו נרצה להשתמש בגישה של טעינה נלהבת כדי להחזיר את ההודעות השייכות למפרסם *בסדר עולה* (אפשרות ה `order` בהגדרות של הקישור מוגדרת בסדר יורד), אנו יכולים לבצע את הפעולה הבאה: - -~~~ -[php] -User::model()-»with(array( - 'posts'=»array('order'=»'posts.create_time ASC'), - 'profile', -))-»findAll(); -~~~ - -החל מגרסא 1.0.5, אפשרויות דינאמיות של שאילתות מקושרות ניתנות לשימוש בעת שימוש בגישה של טעינה עצלה בכדי לבצע שאילתה מקושרת. בכדי לעשות זאת, אנו נקרא למתודה ששמה הוא זהה לשם של הקישור ונעביר את האפשרויות הדינאמיות למתודה כפרמטר. לדוגמא, הקוד הבא מחזיר את הודעות המשתמש שהסטטוס שלהם הוא 1, `status` שווה ל 1: - -~~~ -[php] -$user=User::model()-»findByPk(1); -$posts=$user-»posts(array('condition'=»'status=1')); -~~~ - - -ביצועי שאילתה מקושרת ----------------------------- - -בפי שתארנו כבר למעלה, השימוש בטעינה נלהבת בעיקר הוא במקרה שאנו צריכים לגשת לאובייקטים מקושרים רבים. הוא יוצר שאילתת SQL גדולה ומורכבת על ידי חיבור כל הטבלאות המקושרות. שאילתת SQL גדולה עדיפה בהרבה מקרים מאחר והיא מפשטת את השינון בהתבסס על העמודות בטבלה מקושרת. למרות, שהיא לא כל כך יעילה במקרים מסויימים. - -נקח כדוגמא מקרה בו אנו צריכים למצוא את ההודעות האחרונות ביחד עם התגובות שלהם. בהנחה שלכל הודעה יש 10 תגובות, שימוש בשאילתת SQL אחת גדולה, יחזיר בחזרה הרבה מידע מיותר מאחר וכל הודעה תחזור על עצמה על כל תגובה שפורסמה בה. עכשיו ננסה גישה אחרת: קודם אנו מבצעים שאילתה לקבלת ההודעות האחרונות, ולאחר מכן אנו מכן שאילתה לקבלת התגובות בתוכה. בגישה חדשה זו, אנו צריכים לבצע שני שאילתות. היתרון הוא שאנו לא מקבלים מידע מיותר בתוצאות השאילתה. - -אז איזה גישה יותר יעילה? אין תשובה מוחלטת. הרצת שאילתה אחת גדולה יכול להיות יותר יעיל מאחר והיא צורכת פחות משאבים ב DBMS על מנת לנתח ולהריץ את שאילתת ה SQL. מצד שני, שימוש בשאילתת SQL אחת, אנו מקבלים בסופו של דבר הרבה תוכן מיותר שדורש יותר זמן בקריאה ועיבוד שלו. - -מסיבה זו, Yii מספקת את האפשרות של `together` בשאילתות כדי שנוכל לבחור בין הגישות השונות לפי הצורך. כברירת מחדל, Yii מאמצת את הגישה הראשונה, כלומר, ביצוע שאילתה אחת גדולה בכדי לבצע טעינה נלהבת. אנו יכולים להגדיר את האפשרות של `together` לערך false בהגדרות של הקישור בכדי שכמה טבלאות יאוחדו בשאילתה נפרדת. לדוגמא, בכדי להשתמש בגישה השנייה כדי לבצע שאילתה שתחזיר את ההודעות האחרונות ביחד עם התגובות שלהם, אנו יכולים להגדיר את הקישור של `comments` במחלקה של `Post` בצורה הבאה, - -~~~ -[php] -public function relations() -{ - return array( - 'comments' =» array(self::HAS_MANY, 'Comment', 'post_id', 'together'=»false), - ); -} -~~~ - -כמו כן אנו יכולים להגדיר אפשרות זו בצורה דינאמית בזמן שאנו מבצעים את הטעינה הנלהבת: - -~~~ -[php] -$posts = Post::model()-»with(array('comments'=»array('together'=»false)))-»findAll(); -~~~ - -» Note|הערה: בגרסאות 1.0, התנהגות ברירת המחדל של Yii תייצר ותריץ `N+1` שאילתות SQL אם ישנם `N` , שאילתות מקושרות מסוג `HAS_MANY` או `MANY_MANY`. כל קישור מסוג `HAS_MANY` או `MANY_MANY` מריץ שאילתה נפרדת. על ידי קריאה למתודה `()together` אחרי `()with`, אנו יכולים לאלץ יצירה והרצה של שאילתת SQL אחת בלבד. לדוגמא, -» ~~~ -» [php] -» $posts=Post::model()-»with( -» 'author.profile', -» 'author.posts', -» 'categories')-»together()-»findAll(); -» ~~~ -» - - -שאילתות סטטיסטיות ------------------ - -» Note|הערה: שאילתות סטטיסטיות נתמכות מגרסאות 1.0.4 ומעלה. - -מלבד השאילתות המקושרות המתוארות למעלה, Yii תומכת במה שנקרא שאילתות סטטיסטיות (או שאילתות מצטברות). זה מתייחס לקבלת המידע המצטבר בנוגע לאובייקט המקושר, כמו מספר התגובות לכל הודעה, ממוצע דירוג לכל מוצר, וכדומה. שאילתות סטטיסטיות ניתנות לביצוע על אובייקטים המקושרים בעזרת `HAS_MANY` (לדוגמא, הודעה מכילה הרבה תגובות) או `MANY_MANY` (לדוגמא, הודעה שייכת לכמה קטגוריות וקטגוריה מכילה כמה הודעות). - -ביצוע שאילתות סטטיסטיות דומה מאוד לביצוע שאילתה מקושרת כפי שתארנו קודם לכן. ראשית עלינו להגדיר את השאילתה הסטטיסטית במתודה [()relations|CActiveRecord::relations] של המחלקה [CActiveRecord] כפי שאנו עושים בשאילתות מקושרות. - -~~~ -[php] -class Post extends CActiveRecord -{ - public function relations() - { - return array( - 'commentCount'=»array(self::STAT, 'Comment', 'post_id'), - 'categoryCount'=»array(self::STAT, 'Category', 'post_category(post_id, category_id)'), - ); - } -} -~~~ - -בדוגמא למעלה, אנו מגדירים שני שאילתות סטטיסטיות: `commentCount` המחשב את מספר התגובות השייכות להודעה, ו `categoryCount` המחשב את מספר הקטגוריות שהודעה שייכת אליו. זכור שהקישור בין `Post` ו `Comment` הוא `HAS_MANY`, בזמן שהקישור בין `Post` לבין `Category` הוא `MANY_MANY` (עם הטבלה המאחדת `post_category`). כפי שניתן לראות, ההגדרה דומה מאוד לזו של הקישורים שהסברנו לגביהם בחלקים הקודמים. ההבדל היחידי במקרה הזה הוא שסוג הקישור הינו `STAT`. - -בעזרת ההגדרה למעלה, אנו יכולים לקבל את מספר התגובות לכל הודעה בעזרת הביטוי הבא `post-»commentCount$`. כשניגש למאפיין זה בפעם הראשונה, תתבצע שאילתת SQL במיוחד לקבלת הנתון הזה. כפי שאנו כבר יודעים, גישה זו נקראת *טעינה עצלה*. אנו יכולים להשתמש *בטעינה נלהבת* במידה ואנו רוצים לדעת את כמות התגובות לכמה הודעות: - -~~~ -[php] -$posts=Post::model()-»with('commentCount', 'categoryCount')-»findAll(); -~~~ - -הקוד למעלה יבצע 3 שאילתות בכדי לקבל את כל ההודעות ביחד עם מספר התגובות שלהם ומספר הקטגוריות. שימוש בגישה של טעינה עצלה, אנו נבצע `1+N*2` שאילתות SQL אם ישנם `N` הודעות. - -כברירת מחדל, שאילתה סטטיסטית תבצע את הביטוי `COUNT` (ולכן תתקבל מספר התגובות ומספר הקטגוריות בדוגמא למעלה). אנו יכולים לשנות זאת על ידי הגדרת אפשרויות נוספות בזמן שאנו מצהירים זאת במתודה [()relations|CActiveRecord::relations]. האפשרויות הזמינות מסוכמות למטה. - -- `select` : הביטוי הסטטיסטי. כברירת מחדל הוא מוגדר כ `COUNT(*)`, האומר ספירה של תתי אובייקטים. - -- `defaultValue` : הערך שיש להגדיר לרשומות שלא מקבלות תוצאה מהשאילתה. - -- `condition`: סעיף ה `WHERE`. ברירת מחדל הוא ריק. - -- `params`: הפרמטרים שצריכים להתחם לשאילתת ה SQL. יש להגדיר אפשרות זו כמערך של מפתחות וערכים. - -- `order` : סעיף ה `ORDER BY`. כברירת מחדל הוא ריק. - -- `group`: סעיף ה `GROUP BY`. כברירת מחדל הוא ריק. - -- `having`: סעיף ה `HAVING`. כברירת מחדל הוא ריק. - - -שאילתות מקושרות עם מרחבים מוגדרים ----------------------------------- - -» Note|הערה: תמיכה במרחבים מוגדרים קיימת החל מגרסאות 1.0.5 ומעלה. - -שאילתה מקושרת ניתנת לביצוע גם בצירוף של [מרחבים מוגדרים](/doc/guide/database.ar#named-scopes). היא מגיעה בשני מצבים. במצב הראשון, מרחבים מוגדרים מצורפים למודל הראשי. במצב השני, מרחבים מוגדרים מצורפים למודלים המקושרים. - -הקוד הבא מציג כיצד לצרף מרחב מוגדר למודל הראשי. - -~~~ -[php] -$posts=Post::model()-»published()-»recently()-»with('comments')-»findAll(); -~~~ - -זה דומה מאוד לשאילתות לא מקושרות. ההבדל היחידי הוא שישנו קריאה למתודה `()with` מיד לאחר השרשור של המרחב המוגדר. השאילתה למעלה תחזיר את ההודעות שפורסמו לאחרונה ביחד עם התגובות שלהם. - -והקוד הבא מציג כיצד לצרף מרחב מוגדר למודל מקושר. - -~~~ -[php] -$posts=Post::model()-»with('comments:recently:approved')-»findAll(); -~~~ - -השאילתה למעלה תחזיר את כל ההודעות ביחד עם התוגובת שאושרו. זכור ש `comments` מתייחס לשם הקישור, בזמן ש `recently` ו `approved` מתייחס למרחב מוגדר שקיים במחלקת המודל של `Comment`. שם הקישור והמרחבים המוגדרים צריכים להיות מופרדים בעזרת נקודותיים ( : ). - -ניתן לצרף מרחבים מוגדרים גם באפשרות של `with` בהגדרת הקישור במתודה [CActiveRecord::relations()]. בדוגמא הבאה, ברגע שאנו ניגשים למאפיין `user-»posts$`, הוא מחזיר את כל התגובות *המאושרות* של ההודעות. - -~~~ -[php] -class User extends CActiveRecord -{ - public function relations() - { - return array( - 'posts'=»array(self::HAS_MANY, 'Post', 'author_id', - 'with'=»'comments:approved'), - ); - } -} -~~~ - -» Note|הערה: מרחבים מוגדרים המצורפים למודלים מקושרים צריכים להיות מוגדרים ב [CActiveRecord::scopes]. כתוצאה מכך לא ניתן להגדיר להם פרמטרים. - +Relational Active Record +======================== + +כבר ראינו ולמדנו כיצב להשתמש ב Active Record (AR) כדי לשלוף מידע מטבלה במסד הנתונים. בחלק זה, אנו נסביר כיצד להשתמש ב AR כדי לאחד כמה טבלאות המקושרות אחת לשנייה ולהחזיר את סט הנתונים המאוחד. + +בכדי להשתמש בקישור AR, מומלץ להגדיר את המפתחות הראשיים של הטבלאות העומדות להיות מאוחדות עם טבלאות אחרות כבר מראש. הגדרתם תעזור לשמירת ההמשכיות והאחדות של המידע המקושר. + +כדי לפשט את ההסבר, אנו נשתמש בתרשים מסד הנתונים המוצג בדיאגרמה הבאה בכדי להדגים את הדוגמאות בחלק זה. + +![דיאגרמה](er.png) + +» Info|מידע: תמיכה במפתחות ראשיים חיצוניים תלויה ב DBMS בו משתמשים. SQLite « 3.6.19 אינו תומך במפתחות ראשיים חיצוניים, אך עדיין ניתן להגדיר את המפתחות החיצוניים בעת יצירת הטבלה. + +הצהרת קישורי טבלאות +---------------------- + +לפני שנשתמש ב AR בכדי לבצע שאילתה מקושרת, אנו צריכים ליידע את ה AR כיצד מחלקת AR אחת מקושרת עם אחרת. + +הקישור בין שני מחלקות AR משתייך ישירות לקישורים בין הטבלאות המיוצגות על ידי מחלקות ה AR. מנקודת מבט של מסד נתונים, קישור בין שני טבלאות A ו B ניתן להגדרה על ידי שלושה סוגים: יחיד-ל-רבים (`one-to-many` לדוגמא `tbl_user` ו `tbl_post`), יחיד-ל-יחיד ( `one-to-one` לדוגמא `tbl_user` ו `tbl_profile`) ו רבים-ל-רבים (`many-to-many` לדוגמא `tbl_category` ו `tbl_post`). ב AR ישנם ארבע סוגים של קישורים: + +- `BELONGS_TO`: במידה והקישור בין טבלה א' לבין טבלה ב' הינה יחיד-ל-רבים, אז ב' שייך ל א' (לדוגמא `Post` שייך ל `User`); + +- `HAS_MANY`: במידה והקישור בין טבלה א' לבין טבלה ב' הינה יחיד-ל-רבים, אז א' מכיל הרבה מ ב' (לדוגמא `User` מכיל הרבה `Post`); + +- `HAS_ONE`: זהו מקרה מיוחד של `HAS_MANY` איפה א' מכיל לכל היותר ב' אחד (לדוגמא `User` מכיל לכל היותר `Profile` אחד); + +- `MANY_MANY`: זה מתייחס לקישור של רבים-ל-רבים במסד נתונים. טבלה אסוציאטיבית נחוצה בכדי לחלק את הקישור רבים-ל-רבים לקישור יחיד-ל-רבים, מאחר ומרבית ה DBMS אינם תומכים בקישור מסוג רבים-ל-רבים כברירת מחדל. בדוגמא של תרשים מסד הנתונים, הטבלה `tbl_post_category` משרתת מטרה זו. בטרמינולוגיה של AR אנו יכולים להסביר את `MANY_MANY` כקומבינציה של `BELONGS_TO` ו `HAS_MANY`. לדוגמא, `Post` שייך לכמה `Category` ו `Category` מכיל הרבה `Post`. + +הגדרת קישורים ב AR כרוכה בדריסה של המתודה [relations|CActiveRecord::relations] של המחלקה [CActiveRecord]. המתודה מחזירה מערך של הגדרות קישורים. כל אלמנט במערך קישור בודד בפורמט הבא: + +~~~ +[php] +'VarName'=»array('RelationType', 'ClassName', 'ForeignKey', ...additional options) +~~~ + +`varName` מייצג את שם הקישור; `RelationType` מייצג את סוג הקישור, הוא יכול להיות אחד מהערכים: `self::BELONGS_TO`, `self::HAS_ONE`, `self::HAS_MANY` ו +`self::MANY_MANY`; `ClassName` הינו שם מחלקת ה AR הקשור למחלקת ה AR הנוכחית; ו `ForeignKey` מייצג את המפתח(ות) הראשיים השייכים לקישור זה. אפשרויות נוספות ניתנות להגדרה בסוף האלמנט לכל קישור (הסבר בהמשך). + +הקוד הבא מדגים כיצד אנו מצהירים על הקישורים בין המחלקות `Post` ו `User`. + +~~~ +[php] +class Post extends CActiveRecord +{ + ...... + + public function relations() + { + return array( + 'author'=»array(self::BELONGS_TO, 'User', 'author_id'), + 'categories'=»array(self::MANY_MANY, 'Category', + 'tbl_post_category(post_id, category_id)'), + ); + } +} + +class User extends CActiveRecord +{ + ...... + + public function relations() + { + return array( + 'posts'=»array(self::HAS_MANY, 'Post', 'author_id'), + 'profile'=»array(self::HAS_ONE, 'Profile', 'owner_id'), + ); + } +} +~~~ + +» Info|מידע: מפתח ראשי יכול להיות מורכב, המכיל שני עמודות או יותר. במקרה זה, אנו צריכים לאחד את שמות העמודות ולהפריד אותם בעזרת פסיק או רווח. עבור סוג קישור `MANY_MANY`, שם הטבלה המקושרת צריכה להיות מוגדרת במפתח החיצוני. לדוגמא, הקישור `categories` ב `Post` מוגדרת באמצעות המפתח החיצוני `tbl_post_category(post_id, category_id)`. + +ההצהרה של קישורים במחלקת ה AR מוסיפה בצורה עקיפה מאפיינים למחלקה אשר מהווים ייצוג של הקישורים עצמם. לאחר ביצוע שאילתה המקשרת בין טבלאות, המאפיין הנלמד יאוכלס על ידי הקישור של מחלקת ה AR. לדוגמא, אם `author$` מייצג אובייקט AR של `User`, אנו נוכל להשתמש ב `author-»posts$` בכדי לגשת לקישור של אובייקט ה `Post`. + +ביצוע שאילתות מקושרות +--------------------------- + +הדרך הפשוטה ביותר לביצוע שאילתה מקושרת היא על ידי קריאת מאפיין הקישור של אובייקט AR. במידה והמאפיין לא נקרא קודם לכן, תתבצע שאילתה מקושרת, המאחדת את שני הטבלאות המקושרות ומסננת בעזרת המפתח הראשי של אובייקט ה AR הנוכחי. תוצאת השאילתה תשמר במאפיין כאובייקט(ים) של מחלקת ה AR המקושרת. שיטה זו ידועה בשמה כ *טעינה עצלה* (lazy loading), כלומר, השאילתה המקושרת מתבצעת רק במידה וזמן שניגשים לאבוייקטים המקושרים (זאת אומרת קוראים את המאפיינים של הקישורים בין הטבלאות). הדוגמא הבאה מציגה כיצד להשתמש בשיטה זו: + +~~~ +[php] +// קבלת ההודעה עם id = 10 +$post=Post::model()-»findByPk(10); +// קבלת מידע אודות מפרסם ההודעה, כאן תתבצע שאילתה מקושרת +$author=$post-»author; +~~~ + +» Info|מידע: במידה ולא קיים אובייקט לקישור מסויים, אותו מאפיין במחלקת ה AR המקשרת יכול להיות null או מערך ריק. בעבור קישורי טבלאות מסוג `BELONGS_TO` ו `HAS_ONE`, התוצאה תיהיה null; בעבור קישורי טבלאות מסוג `HAS_MANY` ו `MANY_MANY`, התוצאה תיהיה מערך ריק. +זכור שקישורי הטבלאות מסוג `HAS_MANY` ו `MANY_MANY` מחזירים מערך של אובייקטים, תצטרכו לרוץ על כל התוצאות לפני שיהיה ניתן לגשת לכל אחד כאובייקט, אחרת, תזרק שגיאה של ניסיון לקריאה של מאפיינים לאובייקט שלא קיים. + +גישת ה*טעינה עצלה* נוחה מאוד לשימוש, אך היא לא יעילה במקרים מסויימים. לדוגמא, במידה ואנו רוצים לגשת למידע אודות המפרסם של `N` הודעות, שימוש ב*טעינה עצלה* תבצע `N` שאילתות מקשרות. אנו נצטרך לנקוט בגישה של *טעינה נלהבת* (eager loading) במקרים כאלו. + +גישת ה*טעינה נלהבת* מחזיר את האובייקטים המקושרים לאובייקט AR הראשי. ניתן לבצע זאת על ידי שימוש במתודה [with()|CActiveRecord::with] בשילוב של אחת מפקודות ה [find|CActiveRecord::find] או [findAll|CActiveRecord::findAll] במחלקת AR. לדוגמא, + +~~~ +[php] +$posts=Post::model()-»with('author')-»findAll(); +~~~ + +הקוד למעלה יחזיר מערך של אובייקטים של `Post`. בניגוד לגישה של טעינה עצלה, המאפיין `author` בכל אובייקט של `Post` כבר מאוכלס וקיים בשילוב של האובייקט של `User` לפני שאנו אפילו ניגשים לקרוא את המאפיין. +במקום לבצע שאילתה מקשרת לכל הודעה, גישת הטעינה הנלהבת מחזירה את כל ההודעות ביחד עם המידע אודות המפרסם שלה בשאילתה אחת! + +ניתן להגדיר מספר רב של שמות קישורים במתודה [with|CActiveRecord::with] וגישת הטעינה הנלהבת תחזיר את כולם במכה אחת. לדוגמא, הקוד הבא מחזיר הודעות ביחד עם המידע אודות המפרסם שלהם והקטגוריה בה הם פורסמו: + +~~~ +[php] +$posts=Post::model()-»with('author','categories')-»findAll(); +~~~ + +כמו כן, אנו יכולים לקנן בין קישורים בעזרת הטעינה הנלהבת. במקום רשימה של שמות קישורים, אנו מעבירים רפרזנטציה היררכית של שמות הקישורים למתודה [with|CActiveRecord::with], כפי שמוצג בדוגמא הבאה, + +~~~ +[php] +$posts=Post::model()-»with( + 'author.profile', + 'author.posts', + 'categories')-»findAll(); +~~~ + +הדוגמא למעלה תחזיר את כל ההודעות ביחד עם המידע אודות המפרסם והקטגוריות בהם הם פורסמו. בנוסף היא תחזיר את המידע אודות הפרופיל של המשתמש וההודעות שלו. + +החל מגרסא 1.1.0, ניתן להשתמש בגישת הטעינה הנלהבת בעזרת הגדרת המאפיין [CDbCriteria::with], כפי שמוצג בדוגמא: + +~~~ +[php] +$criteria=new CDbCriteria; +$criteria-»with=array( + 'author.profile', + 'author.posts', + 'categories', +); +$posts=Post::model()-»findAll($criteria); +~~~ + +או + +~~~ +[php] +$posts=Post::model()-»findAll(array( + 'with'=»array( + 'author.profile', + 'author.posts', + 'categories', + ) +); +~~~ + + +אפשרויות שאילתות מקושרות +------------------------ + +ציינו שניתן להגדיר אפשרויות נוספות בהגדרה של קישורים. אפשרויות אלו, המוגדרות כזוגות של שמות מפתחות וערכים, מוגדרות כדי לשנות את השאילתה המקושרת. סיכום של אפשרויות אלו נמצא למטה. + +- `select`: רשימה של עמודות שאותם צריכים לשלוף ממחלקת ה AR המקושרת. ברירת המחדל הינה '*', שזה אומר כל העמודות, שמות העמודות באפשרות זו צריכות להיות יחודיות. + +- `condition`: סעיף ה `WHERE`. כברירת מחדל הוא ריק. שמות העמודות באפשרות זו צריכות להיות יחודיות. + +- `params`: הפרמטרים שצריכים להיות תחומים לשאילתת ה SQL שתווצר. אפשרות זו צריכה להיות מוגדרת כמערך של מפתחות וערכים. אפשרות זו קיימת מגרסאות 1.0.3 ומעלה. + +- `on`: סעיף ה `ON`. התנאי המוגדר כאן יצורף לתנאי של הקישור בעזרת האופרטור `AND`. שמות העמודות באפשרות זו צריכות להיות יחודיות. אפשרות זו לא תקפה לסוג קישור `MANY_MANY`. אפשרות זו קיימת מגרסאות 1.0.2 ומעלה. + +- `order`: סעיף ה `ORDER BY`. כברירת מחדל הוא ריק. שמות העמודות באפשרות זו צריכות להיות יחודיות. + +- `with`: רשימה של אובייקטים המקושרים לאובייקט זה ושיש צורך בלטעון אותם ביחד עם אובייקט זה. דע לך ששימוש לא נכון באפשרות זו יכול לגרום ללולאה אין סופית. + +- `jointType`: סוג האיחוד לקישור זה. כברירת מחדל הוא מוגדר ל `LEFT OUTER JOIN`. + +- `alias`: שם קיצור לטבלה הקשורה לקישור זה. אפשרות זו קיימת מגרסא 1.0.1. כברירת מחדל היא מוגדרת ל null, שאומר שהקיצור לטבלה הוא זהה לשם הקישור עצמו. + +- `together`: במידה והטבלה המקושרת עם קישור זה מאולצת לאיחוד ביחד עם הטבלה הראשית ושאר הטבלאות. אפשרות זו תקפה רק לקישורים מסוג HAS_MANY ו MANY_MANY. במידה ואפשרות זו מוגדרת ל false, הטבלה המקושרת ל HAS_MANY או MANY_MANY תאוחד עם הטבלה הראשית בשאילתה נפרדת, אשר יכול לשפר את הביצועים הכוללים של השאילתה מאחר ופחות נתונים כפולים יוחזרו. כברירת מחדל האפשרות מוגדרת ל true. +למידע נוסף קרא את החלק "ביצועי שאילתה מקושרת". אפשרות זו קיימת מגרסא 1.0.3 ומעלה. + +- `group`: סעיף ה `GROUP BY`. כברירת מחדל הוא ריק. שמות העמודות באפשרות זו צריכות להיות יחודיות. אפשרות זו קיימת מגרסא 1.0.1 ומעלה. + +- `having`: סעיף ה `HAVING`. כברירת מחדל הוא ריק. שמות העמודות באפשרות זו צריכות להיות יחודיות. אפשרות זו קיימת מגרסא 1.0.1 ומעלה. + +- `index`: שמות העמודות שערכיהם צריכות להיות משומשות כמפתחות למערך המאחסן את האובייקטים המקושרים. ללא הגדרת אפשרות זו, אובייקט מקושר ישתמש במפתחות מספריים מ-0 למערך של האובייקטים המקושרים. אפשרות זו ניתנת להגדרה רק על קישורים מסוג `HAS_MANY` ו `MANY_MANY`. אפשרות זו קיימת מגרסא 1.0.7 ומעלה. + +בנוסף, האפשרויות הבאות קיימות גם כן לקישורים מסויימים במהלך טעינה עצלה: + +- `limit`: הגבלת מספר השורות לבחירה. אפשרות זו לא תקפה לקישור מסוג `BELONGS_TO`. + +- `offset`: מאיזה שורה להתחיל לשלוף את הנתונים. אפשרות זו לא תקפה לקישור מסוג `BELONGS_TO`. + +למטה אנו עורכים את הגדרת הקישור של `posts` במחלקה של `User` על ידי הוספת כמה מהאפשרויות למעלה: + +~~~ +[php] +class User extends CActiveRecord +{ + public function relations() + { + return array( + 'posts'=»array(self::HAS_MANY, 'Post', 'author_id', + 'order'=»'posts.create_time DESC', + 'with'=»'categories'), + 'profile'=»array(self::HAS_ONE, 'Profile', 'owner_id'), + ); + } +} +~~~ + +כעת, ברגע שניגש ל `author-»posts$`, אנו נקבל את ההודעות של המפרסם מסודרים לפי תאריך הפרסום שלהם בסדר יורד. כל אובייקט של הודעה מכיל גם את הקטגוריות טעונות בתוכו. + +שמות יחודיים לעמודות +--------------------------- + +כששם העמודות מופיע בשני טבלאות או יותר אשר מקושרות ביחד, יש צורך בלזהות אותם צבורה יחודית. זה נעשה על ידי מתן קידומת לעמודה עם השם המקוצר של הטבלה בה היא נמצאת. + +בשאילתת AR מקושרת, השם המקוצר לטבלה הראשית הינו מוגדר מראש בתור 't' בזמן ששם המקוצר לטבלה המקושרת כברירת מחדל הוא זהה לשמו של הקישור שהוגדר. לדוגמא, בביטוי הבא, השמות המקוצרים לטבלאות `Post` ו `Comment` הינו `t` ו `comments`, בהתאמה: + +~~~ +[php] +$posts=Post::model()-»with('comments')-»findAll(); +~~~ + +כעת, נניח שישנה עמודה בשם `create_time` בשני הטבלאות של `Post` ו `Comment` המייצג את הזמן פרסום ההודעה וזמן פרסום התגובה, ואנו נרצה לשלוף את ההודעות ביחד עם התגובות שלהם וסידור ההודעות על פי תאריך הפרסום שלהם קודם ולאחר מכן סידור התגובות על פי תאריך הפרסום שלהם. אנו נצטרך לתת שם יחודי לעמודה `create_time` בצורה הבאה: + +~~~ +[php] +$posts=Post::model()-»with('comments')-»findAll(array( + 'order'=»'t.create_time, comments.create_time' +)); +~~~ + +» Note|הערה: ההתנהגות של שמות עמודות יחודיים השתנתה החל מגרסא 1.1.0. קודם לכן בגרסאות *.1.0, כברירת מחדל Yii אוטומטית יצרה שם מקוצר לכל טבלה מקושרת, ואנו היינו צריכים להשתמש בקידומת של `.??` כדי להתייחס לשם המקוצר שנוצר אוטומטית. בנוסף, בגרסאות *.1.0, השם המקוצר שם הטבלה הראשית הוא שם הטבלה עצמה. + +אפשרויות דינאמיות לשאילתות מקושרות +-------------------------------- + +החל מגרסא 1.0.2, אנו יכולים להשתמש באפשרויות דינאמיות לשאילתות מקושרות במתודה [with|CActiveRecord::with] וגם על ידי שימוש באפשרות `with`. האפשרויות הדינאמיות ידרוס את האפשרויות הקיימות שהוגדרו במתודה [relations|CActiveRecord::relations]. לדוגמא, בעזרת המודל `User` המצויין למעלה, במידה ואנו נרצה להשתמש בגישה של טעינה נלהבת כדי להחזיר את ההודעות השייכות למפרסם *בסדר עולה* (אפשרות ה `order` בהגדרות של הקישור מוגדרת בסדר יורד), אנו יכולים לבצע את הפעולה הבאה: + +~~~ +[php] +User::model()-»with(array( + 'posts'=»array('order'=»'posts.create_time ASC'), + 'profile', +))-»findAll(); +~~~ + +החל מגרסא 1.0.5, אפשרויות דינאמיות של שאילתות מקושרות ניתנות לשימוש בעת שימוש בגישה של טעינה עצלה בכדי לבצע שאילתה מקושרת. בכדי לעשות זאת, אנו נקרא למתודה ששמה הוא זהה לשם של הקישור ונעביר את האפשרויות הדינאמיות למתודה כפרמטר. לדוגמא, הקוד הבא מחזיר את הודעות המשתמש שהסטטוס שלהם הוא 1, `status` שווה ל 1: + +~~~ +[php] +$user=User::model()-»findByPk(1); +$posts=$user-»posts(array('condition'=»'status=1')); +~~~ + + +ביצועי שאילתה מקושרת +---------------------------- + +בפי שתארנו כבר למעלה, השימוש בטעינה נלהבת בעיקר הוא במקרה שאנו צריכים לגשת לאובייקטים מקושרים רבים. הוא יוצר שאילתת SQL גדולה ומורכבת על ידי חיבור כל הטבלאות המקושרות. שאילתת SQL גדולה עדיפה בהרבה מקרים מאחר והיא מפשטת את השינון בהתבסס על העמודות בטבלה מקושרת. למרות, שהיא לא כל כך יעילה במקרים מסויימים. + +נקח כדוגמא מקרה בו אנו צריכים למצוא את ההודעות האחרונות ביחד עם התגובות שלהם. בהנחה שלכל הודעה יש 10 תגובות, שימוש בשאילתת SQL אחת גדולה, יחזיר בחזרה הרבה מידע מיותר מאחר וכל הודעה תחזור על עצמה על כל תגובה שפורסמה בה. עכשיו ננסה גישה אחרת: קודם אנו מבצעים שאילתה לקבלת ההודעות האחרונות, ולאחר מכן אנו מכן שאילתה לקבלת התגובות בתוכה. בגישה חדשה זו, אנו צריכים לבצע שני שאילתות. היתרון הוא שאנו לא מקבלים מידע מיותר בתוצאות השאילתה. + +אז איזה גישה יותר יעילה? אין תשובה מוחלטת. הרצת שאילתה אחת גדולה יכול להיות יותר יעיל מאחר והיא צורכת פחות משאבים ב DBMS על מנת לנתח ולהריץ את שאילתת ה SQL. מצד שני, שימוש בשאילתת SQL אחת, אנו מקבלים בסופו של דבר הרבה תוכן מיותר שדורש יותר זמן בקריאה ועיבוד שלו. + +מסיבה זו, Yii מספקת את האפשרות של `together` בשאילתות כדי שנוכל לבחור בין הגישות השונות לפי הצורך. כברירת מחדל, Yii מאמצת את הגישה הראשונה, כלומר, ביצוע שאילתה אחת גדולה בכדי לבצע טעינה נלהבת. אנו יכולים להגדיר את האפשרות של `together` לערך false בהגדרות של הקישור בכדי שכמה טבלאות יאוחדו בשאילתה נפרדת. לדוגמא, בכדי להשתמש בגישה השנייה כדי לבצע שאילתה שתחזיר את ההודעות האחרונות ביחד עם התגובות שלהם, אנו יכולים להגדיר את הקישור של `comments` במחלקה של `Post` בצורה הבאה, + +~~~ +[php] +public function relations() +{ + return array( + 'comments' =» array(self::HAS_MANY, 'Comment', 'post_id', 'together'=»false), + ); +} +~~~ + +כמו כן אנו יכולים להגדיר אפשרות זו בצורה דינאמית בזמן שאנו מבצעים את הטעינה הנלהבת: + +~~~ +[php] +$posts = Post::model()-»with(array('comments'=»array('together'=»false)))-»findAll(); +~~~ + +» Note|הערה: בגרסאות 1.0, התנהגות ברירת המחדל של Yii תייצר ותריץ `N+1` שאילתות SQL אם ישנם `N` , שאילתות מקושרות מסוג `HAS_MANY` או `MANY_MANY`. כל קישור מסוג `HAS_MANY` או `MANY_MANY` מריץ שאילתה נפרדת. על ידי קריאה למתודה `()together` אחרי `()with`, אנו יכולים לאלץ יצירה והרצה של שאילתת SQL אחת בלבד. לדוגמא, +» ~~~ +» [php] +» $posts=Post::model()-»with( +» 'author.profile', +» 'author.posts', +» 'categories')-»together()-»findAll(); +» ~~~ +» + + +שאילתות סטטיסטיות +----------------- + +» Note|הערה: שאילתות סטטיסטיות נתמכות מגרסאות 1.0.4 ומעלה. + +מלבד השאילתות המקושרות המתוארות למעלה, Yii תומכת במה שנקרא שאילתות סטטיסטיות (או שאילתות מצטברות). זה מתייחס לקבלת המידע המצטבר בנוגע לאובייקט המקושר, כמו מספר התגובות לכל הודעה, ממוצע דירוג לכל מוצר, וכדומה. שאילתות סטטיסטיות ניתנות לביצוע על אובייקטים המקושרים בעזרת `HAS_MANY` (לדוגמא, הודעה מכילה הרבה תגובות) או `MANY_MANY` (לדוגמא, הודעה שייכת לכמה קטגוריות וקטגוריה מכילה כמה הודעות). + +ביצוע שאילתות סטטיסטיות דומה מאוד לביצוע שאילתה מקושרת כפי שתארנו קודם לכן. ראשית עלינו להגדיר את השאילתה הסטטיסטית במתודה [()relations|CActiveRecord::relations] של המחלקה [CActiveRecord] כפי שאנו עושים בשאילתות מקושרות. + +~~~ +[php] +class Post extends CActiveRecord +{ + public function relations() + { + return array( + 'commentCount'=»array(self::STAT, 'Comment', 'post_id'), + 'categoryCount'=»array(self::STAT, 'Category', 'post_category(post_id, category_id)'), + ); + } +} +~~~ + +בדוגמא למעלה, אנו מגדירים שני שאילתות סטטיסטיות: `commentCount` המחשב את מספר התגובות השייכות להודעה, ו `categoryCount` המחשב את מספר הקטגוריות שהודעה שייכת אליו. זכור שהקישור בין `Post` ו `Comment` הוא `HAS_MANY`, בזמן שהקישור בין `Post` לבין `Category` הוא `MANY_MANY` (עם הטבלה המאחדת `post_category`). כפי שניתן לראות, ההגדרה דומה מאוד לזו של הקישורים שהסברנו לגביהם בחלקים הקודמים. ההבדל היחידי במקרה הזה הוא שסוג הקישור הינו `STAT`. + +בעזרת ההגדרה למעלה, אנו יכולים לקבל את מספר התגובות לכל הודעה בעזרת הביטוי הבא `post-»commentCount$`. כשניגש למאפיין זה בפעם הראשונה, תתבצע שאילתת SQL במיוחד לקבלת הנתון הזה. כפי שאנו כבר יודעים, גישה זו נקראת *טעינה עצלה*. אנו יכולים להשתמש *בטעינה נלהבת* במידה ואנו רוצים לדעת את כמות התגובות לכמה הודעות: + +~~~ +[php] +$posts=Post::model()-»with('commentCount', 'categoryCount')-»findAll(); +~~~ + +הקוד למעלה יבצע 3 שאילתות בכדי לקבל את כל ההודעות ביחד עם מספר התגובות שלהם ומספר הקטגוריות. שימוש בגישה של טעינה עצלה, אנו נבצע `1+N*2` שאילתות SQL אם ישנם `N` הודעות. + +כברירת מחדל, שאילתה סטטיסטית תבצע את הביטוי `COUNT` (ולכן תתקבל מספר התגובות ומספר הקטגוריות בדוגמא למעלה). אנו יכולים לשנות זאת על ידי הגדרת אפשרויות נוספות בזמן שאנו מצהירים זאת במתודה [()relations|CActiveRecord::relations]. האפשרויות הזמינות מסוכמות למטה. + +- `select` : הביטוי הסטטיסטי. כברירת מחדל הוא מוגדר כ `COUNT(*)`, האומר ספירה של תתי אובייקטים. + +- `defaultValue` : הערך שיש להגדיר לרשומות שלא מקבלות תוצאה מהשאילתה. + +- `condition`: סעיף ה `WHERE`. ברירת מחדל הוא ריק. + +- `params`: הפרמטרים שצריכים להתחם לשאילתת ה SQL. יש להגדיר אפשרות זו כמערך של מפתחות וערכים. + +- `order` : סעיף ה `ORDER BY`. כברירת מחדל הוא ריק. + +- `group`: סעיף ה `GROUP BY`. כברירת מחדל הוא ריק. + +- `having`: סעיף ה `HAVING`. כברירת מחדל הוא ריק. + + +שאילתות מקושרות עם מרחבים מוגדרים +---------------------------------- + +» Note|הערה: תמיכה במרחבים מוגדרים קיימת החל מגרסאות 1.0.5 ומעלה. + +שאילתה מקושרת ניתנת לביצוע גם בצירוף של [מרחבים מוגדרים](/doc/guide/database.ar#named-scopes). היא מגיעה בשני מצבים. במצב הראשון, מרחבים מוגדרים מצורפים למודל הראשי. במצב השני, מרחבים מוגדרים מצורפים למודלים המקושרים. + +הקוד הבא מציג כיצד לצרף מרחב מוגדר למודל הראשי. + +~~~ +[php] +$posts=Post::model()-»published()-»recently()-»with('comments')-»findAll(); +~~~ + +זה דומה מאוד לשאילתות לא מקושרות. ההבדל היחידי הוא שישנו קריאה למתודה `()with` מיד לאחר השרשור של המרחב המוגדר. השאילתה למעלה תחזיר את ההודעות שפורסמו לאחרונה ביחד עם התגובות שלהם. + +והקוד הבא מציג כיצד לצרף מרחב מוגדר למודל מקושר. + +~~~ +[php] +$posts=Post::model()-»with('comments:recently:approved')-»findAll(); +~~~ + +השאילתה למעלה תחזיר את כל ההודעות ביחד עם התוגובת שאושרו. זכור ש `comments` מתייחס לשם הקישור, בזמן ש `recently` ו `approved` מתייחס למרחב מוגדר שקיים במחלקת המודל של `Comment`. שם הקישור והמרחבים המוגדרים צריכים להיות מופרדים בעזרת נקודותיים ( : ). + +ניתן לצרף מרחבים מוגדרים גם באפשרות של `with` בהגדרת הקישור במתודה [CActiveRecord::relations()]. בדוגמא הבאה, ברגע שאנו ניגשים למאפיין `user-»posts$`, הוא מחזיר את כל התגובות *המאושרות* של ההודעות. + +~~~ +[php] +class User extends CActiveRecord +{ + public function relations() + { + return array( + 'posts'=»array(self::HAS_MANY, 'Post', 'author_id', + 'with'=»'comments:approved'), + ); + } +} +~~~ + +» Note|הערה: מרחבים מוגדרים המצורפים למודלים מקושרים צריכים להיות מוגדרים ב [CActiveRecord::scopes]. כתוצאה מכך לא ניתן להגדיר להם פרמטרים. + «div class="revision"»$Id: database.arr.txt 2069 2010-01-08 05:08:29Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/database.dao.txt b/docs/guide/he/database.dao.txt index 84c8ad65e..43639a0e0 100644 --- a/docs/guide/he/database.dao.txt +++ b/docs/guide/he/database.dao.txt @@ -1,195 +1,195 @@ -Data Access Objects (DAO) -========================= - -ה DAO מספק גישת API כללית למידע המאוחסן בסוגי מסדים שונים (DBMS). כתוצאה מכך, ה DBMS היושב מתחת ניתן לשינוי לאחד אחר ללא צורך בשינוי הקוד המשתמש ב DAO בכדי לגשת למידע. - -ה DAO של Yii בנוי על בסיס התוסף של PHP בשם (PDO)](http://php.net/manual/en/book.pdo.php) המספק אפשרויות אחידות לגישה למסדי DBMS נפוצים, כמו MySQL, PostgreSQL. לכן, בכדי להשתמש ב DAO של Yii יש צורך בלהתקין ולהפעיל את התוסף של PDO בשרת, וכמו כן להפעיל את התוספים הספציפים למסד הנתונים בו יהיה שימוש (לדוגמא `PDO_MYSQL`). - -ה DAO של Yii בנוי ברובו על בסיס ארבעת המחלקות הבאות: - - - [CDbConnection]: מייצג התחברות למסד נתונים. - - [CDbCommand]: מייצג שאילתת SQL שצריך להריץ מול מסד נתונים. - - [CDbDataReader]: מייצג תוצאות שאילתת SQL מול מסד נתונים. - - [CDbTransaction]: מייצג טרנזקציה. - -בחלק זה, אנו נציג כיצד להשתמש ב DAO במקרים שונים. - -יצירת התחברות למסד הנתונים --------------------------------- - -בכדי ליצר התחברות למסד הנתונים, יש ליצור אובייקט של [CDbConnection] ולהפעיל אותו. יש להגדיר DSN שמהווה בעצם הסטרינג המכיל את המידע הדרוש להתחברות למסד נתונים. כמו כן יהיה צורך בהגדרת שם משתמש וסיסמא להתחברות למסד הנתונים. תזרק שגיאה במידה ותיהיה בעיה בזמן ניסיון החיבור למסד הנתונים (במידה וה DSN לא תקין או שם משתמש וסיסמא לא נכונים). - -~~~ -[php] -$connection=new CDbConnection($dsn,$username,$password); -// יצירת התחברות רצוי לתחום את זה ב try ... catch -$connection-»active=true; -...... -$connection-»active=false; // סגירת התחברות -~~~ - -הפורמט של ה DSN תלוי בדרייבר של מסד הנתונים ב PDO שמשתמשים בו. בכללי, DSN מכיל את שם הדרייבר ב PDO, לאחריו נקודותיים ( : ), ולאחריו תחביר ההתחברות המדוייק לכל דרייבר. יש לעיין [דוקומנטציה](http://www.php.net/manual/en/pdo.construct.php) למידע מלא. למטה רשימה של DNS נפוצים: - -- SQLite: `sqlite:/path/to/dbfile` -- MySQL: `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` - -מאחר ו [CDbConnection] יורשת מהמחלקה [CApplicationComponent], ניתן להשתמש בו גם [כרכיב](/doc/guide/basics.application#application-component). בכדי לעשות זאת, יש להגדיר רכיב [בהגדרות האפליקציה](/doc/guide/basics.application#application-configuration) בשם `db` (או כל שם אחר) בצורה הבאה, - -~~~ -[php] -array( - ...... - 'components'=»array( - ...... - 'db'=»array( - 'class'=»'CDbConnection', - 'connectionString'=»'mysql:host=localhost;dbname=testdb', - 'username'=»'root', - 'password'=»'password', - 'emulatePrepare'=»true, // יש צורך בהגדרה זו להתקנות MySQL מסויימות - ), - ), -) -~~~ - -לאחר מכן אנו נוכל לגשת לחיבור ה DB בעזרת `Yii::app()-»db` שכבר הופעלה בצורה אוטומטית. אלה אם כן אנו נגדיר באופן ספציפי את המאפיין [CDbConnection::autoConnect] ל false. שימוש בגישה זו, ההתחברות הזו ניתנת לשימוש במקומות שונים בקוד. - -הרצת שאילתות SQL ------------------------- - -לאחר יצירת התחברות למסד הנתונים, ניתן להריץ שאילתות SQL על ידי שימוש ב [CDbCommand]. ניתן לייצר אובייקט של [CDbCommand] על ידי קריאה ל [()CDbConnection::createCommand] עם השאילתה הבאה: - -~~~ -[php] -$command=$connection-»createCommand($sql); -// במידה וצריך ניתן לעדכן את שאילתת ה SQL בצורה הבאה: -// $command-»text=$newSQL; -~~~ - -שאילתת SQL מתבצעת בעזרת [CDbCommand] באחת מהדרכים הבאות: - -- [()execute|CDbCommand::execute]: מבצעת שאילתת SQL אשר לא מחזירה מידע לקריאה, כמו `INSERT`, `UPDATE`, `DELETE`. במידה והיא בוצעה בהצלחה היא תחזיר את מספר השורות שעודכנו. - -- [()query|CDbCommand::query]: מבצעת שאילתת SQL אשר מחזירה שורות של מידע, כמו `SELECT`. במידה והיא בוצעה בהצלחה, היא מחזירה אובייקט של [CDbDataReader] שבעזרתו ניתן יהיה לרוץ עליו לקבלת המידע שורה שורה. לנוחות, ישנם סט של מתודות `()queryXXX` אשר כלולות שבעזרתם ניתן לקבל את התוצאה ישירות. - -תזרק שגיאה במידה והייתה בעיה בהרצת השאילתה. - -~~~ -[php] -$rowCount=$command-»execute(); // ביצוע שאילתה שלא מחזירה מידע לקריאה -$dataReader=$command-»query(); // הרצת שאילתת SQL -$rows=$command-»queryAll(); // הרצת שאילתה והחזרת כל השורות של התוצאה -$row=$command-»queryRow(); // הרצת שאילתה והחזרת השורה הראשונה בתוצאה -$column=$command-»queryColumn(); // הרצת שאילתה והחזרה העמודה הראשונה של התוצאה -$value=$command-»queryScalar(); // הרצת שאילתה והחזרת העמודה הראשונה בשורה הראשונה -~~~ - -שליפת תוצאות שאילתה ----------------------- - -לאחר שהמתודה [()CDbCommand::query] יוצרת את האובייקט של [CDbDataReader], ניתן לקבל את השורות מהמידע שהוחזר על ידי קריאה ל [()CDbDataReader::read] שוב ושוב. ניתן גם להשתמש ב [CDbDataReader] בתוך לולאה `foreach` של PHP בכדי לקבל שורה אחרי שורה. - -~~~ -[php] -$dataReader=$command-»query(); -// קריאה ל read שוב ושוב עד שהוא מחזיר false -while(($row=$dataReader-»read())!==false) { ... } -// שימוש בלולאה על כל התוצאות -foreach($dataReader as $row) { ... } -// קבלת כל התוצאות במכה אחת כמערך -$rows=$dataReader-»readAll(); -~~~ - -» Note|הערה: בניגוד ל [()query|CDbCommand::query], כל המתודות של `queryXXX` מחזירות מידע בצורה ישירה. לדוגמא, [()queryRow|CDbCommand::queryRow] מחזירה מערך המייצג את השורה הראשונה של תוצאת השאילתה. - -שימוש בטרנזקציות ------------------- - -כשאפליקציה מריצה כמה שאילתות, כל אחת קוראת ו/או כותבת מידע למסד הנתונים, חשוב לדעת ולהיות בטוחים שהמסד נתונים מבצע ומריץ את כל השאילתות ולא מפספס אף אחת. טרנזקציה, המיוצגת כאובייקט של [CDbTransaction] ב Yii , ניתנת לשימוש במקרה כזה: - -- התחלת הטרנזקציה. -- הרצת השאילתות אחת אחרי השנייה. כל העדכונים למסד לא מוצגים לעולם החיצון. -- ביצוע הטרנזקציה. כעת ניתן לראות את העדכונים במידה והטרנזקציה הסתיימה בהצלחה. -- במידה ואחת מהשאילתות נכשלת בזמן הרצה, כל הטרנזקציה חוזרת אחורה. - -רצף העבודה המוצג למעלה ניתן ליישום בעזרת הקוד הבא: - -~~~ -[php] -$transaction=$connection-»beginTransaction(); -try -{ - $connection-»createCommand($sql1)-»execute(); - $connection-»createCommand($sql2)-»execute(); - //.... other SQL executions - $transaction-»commit(); -} -catch(Exception $e) // תתבצע שגיאה במידה וישנה בעיה -{ - $transaction-»rollBack(); -} -~~~ - -תיחום פרמטרים ------------------- - -בכדי להמנע מהתקפות בעזרת [הזרקות SQL](http://en.wikipedia.org/wiki/SQL_injection) ובכדי לשפר את הביצועים של שאילתות SQL החוזרות על עצמם, ניתן 'להכין' שאילתת SQL עם מקומות שמורים לפרמטרים אשר יתחלפו עם הפרמטרים המקוריים בזמן תהליך תיחום הפרמטרים. - -המקומות השמורים לפרמטרים יכולים להיות מוגדרים עם שם יחודי (מאופיינים כמילות מפתח יחודיות) או ללא שם (מאופיינים בעזרת סימני שאלה). יש לקרוא ל [()CDbCommand::bindParam] או [()CDbCommand::bindValue] בכדי להחליף את הפרמטר עם הערך שהוגדר לו. אין צורך לעטוף את הפרמטרים בעזרת מרכאות: מסד הנתונים בו אתה משתמש יעשה זאת אוטומטית. יש לתחום את הפרמטרים לפני קריאה למתודה המבצעת את השאילתה. - -~~~ -[php] -// שאילתת SQL עם שני מפתחות יחודיים לפרמטרים :username או :email -$sql="INSERT INTO tbl_user (username, email) VALUES(:username,:email)"; -$command=$connection-»createCommand($sql); -// החלף את המפתח :username עם ערך שהוגדר לו -$command-»bindParam(":username",$username,PDO::PARAM_STR); -// החלף את המפתח :email עם הערך שהוגדר לו -$command-»bindParam(":email",$email,PDO::PARAM_STR); -$command-»execute(); -// הוסף שורה חדשה עם סט חדש של ערכים -$command-»bindParam(":username",$username2,PDO::PARAM_STR); -$command-»bindParam(":email",$email2,PDO::PARAM_STR); -$command-»execute(); -~~~ - -המתודות [()bindParam|CDbCommand::bindParam] ו [()bindValue|CDbCommand::bindValue] הם דומות מאוד. ההבדל היחידי היא שהראשון תוחם פרמטר בעזרת משתנה ב PHP והשני תוחם פרמטר בעזרת ערך. עבור פרמטרים המייצגים בלוקים גדולים של מידע, הראשון הוא עדיף מבחינת ביצועים. - -למידע נוסף אודות תיחום פרמטרים, יש לקרוא [בדוקומנטציה של PHP](http://www.php.net/manual/en/pdostatement.bindparam.php). - -תיחום עמודות ---------------- - -בזמן שליפת תוצאות השאילתה, ניתן לתחום עמודות בעזרת משתנים ב PHP כדי שהם יאוכלסו אוטומטית עם הנתונים האחרונים בכל פעם ששורה חדשה נשלפה ממסד הנתונים בעזרת השאילתה, וניתן יהיה לגשת אליהם. - -~~~ -[php] -$sql="SELECT username, email FROM tbl_user"; -$dataReader=$connection-»createCommand($sql)-»query(); -// תיחום העמודה הראשונה (username) עם המשתנה $username -$dataReader-»bindColumn(1,$username); -// תיחום העמודה השנייה (email) עם המשתנה $email -$dataReader-»bindColumn(2,$email); -while($dataReader-»read()!==false) -{ - // כעת ניתן להשתמש ב $username ו $email אשר מכילים את המידע מהשורה האחרונה שהגיע ממסד הנתונים. -} -~~~ - -שימוש בקידומת לטבלאות ------------------- - -החל מגרסא 1.1.0, Yii מאפשרת תמיכה מובנית לשימוש בקידומת לטבלאות במסד הנתונים. משמעות הקידומת הינה סטרינג אשר מחובר לתחילת שמות הטבלאות במסד בו כרגע משתמשים. בדרך כלל משתמשים באפשרות זו בשרתים שיתופיים אשר מריצים כמה אפליקציות על אותו המסד ומשתמשים בקידומת שונה לטבלאות לכל אפליקציה כדי להבדיל ביניהם. לדוגמא, אפליקציה אחת יכולה להשתמש בקידומת `_tbl` בזמן שאפליקציה נוספת תשתמש בקידומת `_yii`. - -בכדי להשתמש בקידומת לטבלאות, יש להגדיר את המאפיין [CDbConnection::tablePrefix] עם הקידומת הרצויה לשימוש. לאחר מכן, בשאילתות ה SQL יש להשתמש ב `{{שם הטבלה}}` בכדי להתייחס לשמות הטבלאות, כש `שם הטבלה` מתייחס לשם הטבלה במסד ללא הקידומת. לדוגמא, אם המסד נתונים מכיל טבלה בשם `tbl_user` כש `_tbl` מוגדר כקידומת לטבלאות, אז נוכל להשתמש בקוד הבא בכדי לשלוף נתונים מהטבלה הזו: - -~~~ -[php] -$sql='SELECT * FROM {{user}}'; -$users=$connection-»createCommand($sql)-»queryAll(); -~~~ - +Data Access Objects (DAO) +========================= + +ה DAO מספק גישת API כללית למידע המאוחסן בסוגי מסדים שונים (DBMS). כתוצאה מכך, ה DBMS היושב מתחת ניתן לשינוי לאחד אחר ללא צורך בשינוי הקוד המשתמש ב DAO בכדי לגשת למידע. + +ה DAO של Yii בנוי על בסיס התוסף של PHP בשם (PDO)](http://php.net/manual/en/book.pdo.php) המספק אפשרויות אחידות לגישה למסדי DBMS נפוצים, כמו MySQL, PostgreSQL. לכן, בכדי להשתמש ב DAO של Yii יש צורך בלהתקין ולהפעיל את התוסף של PDO בשרת, וכמו כן להפעיל את התוספים הספציפים למסד הנתונים בו יהיה שימוש (לדוגמא `PDO_MYSQL`). + +ה DAO של Yii בנוי ברובו על בסיס ארבעת המחלקות הבאות: + + - [CDbConnection]: מייצג התחברות למסד נתונים. + - [CDbCommand]: מייצג שאילתת SQL שצריך להריץ מול מסד נתונים. + - [CDbDataReader]: מייצג תוצאות שאילתת SQL מול מסד נתונים. + - [CDbTransaction]: מייצג טרנזקציה. + +בחלק זה, אנו נציג כיצד להשתמש ב DAO במקרים שונים. + +יצירת התחברות למסד הנתונים +-------------------------------- + +בכדי ליצר התחברות למסד הנתונים, יש ליצור אובייקט של [CDbConnection] ולהפעיל אותו. יש להגדיר DSN שמהווה בעצם הסטרינג המכיל את המידע הדרוש להתחברות למסד נתונים. כמו כן יהיה צורך בהגדרת שם משתמש וסיסמא להתחברות למסד הנתונים. תזרק שגיאה במידה ותיהיה בעיה בזמן ניסיון החיבור למסד הנתונים (במידה וה DSN לא תקין או שם משתמש וסיסמא לא נכונים). + +~~~ +[php] +$connection=new CDbConnection($dsn,$username,$password); +// יצירת התחברות רצוי לתחום את זה ב try ... catch +$connection-»active=true; +...... +$connection-»active=false; // סגירת התחברות +~~~ + +הפורמט של ה DSN תלוי בדרייבר של מסד הנתונים ב PDO שמשתמשים בו. בכללי, DSN מכיל את שם הדרייבר ב PDO, לאחריו נקודותיים ( : ), ולאחריו תחביר ההתחברות המדוייק לכל דרייבר. יש לעיין [דוקומנטציה](http://www.php.net/manual/en/pdo.construct.php) למידע מלא. למטה רשימה של DNS נפוצים: + +- SQLite: `sqlite:/path/to/dbfile` +- MySQL: `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` + +מאחר ו [CDbConnection] יורשת מהמחלקה [CApplicationComponent], ניתן להשתמש בו גם [כרכיב](/doc/guide/basics.application#application-component). בכדי לעשות זאת, יש להגדיר רכיב [בהגדרות האפליקציה](/doc/guide/basics.application#application-configuration) בשם `db` (או כל שם אחר) בצורה הבאה, + +~~~ +[php] +array( + ...... + 'components'=»array( + ...... + 'db'=»array( + 'class'=»'CDbConnection', + 'connectionString'=»'mysql:host=localhost;dbname=testdb', + 'username'=»'root', + 'password'=»'password', + 'emulatePrepare'=»true, // יש צורך בהגדרה זו להתקנות MySQL מסויימות + ), + ), +) +~~~ + +לאחר מכן אנו נוכל לגשת לחיבור ה DB בעזרת `Yii::app()-»db` שכבר הופעלה בצורה אוטומטית. אלה אם כן אנו נגדיר באופן ספציפי את המאפיין [CDbConnection::autoConnect] ל false. שימוש בגישה זו, ההתחברות הזו ניתנת לשימוש במקומות שונים בקוד. + +הרצת שאילתות SQL +------------------------ + +לאחר יצירת התחברות למסד הנתונים, ניתן להריץ שאילתות SQL על ידי שימוש ב [CDbCommand]. ניתן לייצר אובייקט של [CDbCommand] על ידי קריאה ל [()CDbConnection::createCommand] עם השאילתה הבאה: + +~~~ +[php] +$command=$connection-»createCommand($sql); +// במידה וצריך ניתן לעדכן את שאילתת ה SQL בצורה הבאה: +// $command-»text=$newSQL; +~~~ + +שאילתת SQL מתבצעת בעזרת [CDbCommand] באחת מהדרכים הבאות: + +- [()execute|CDbCommand::execute]: מבצעת שאילתת SQL אשר לא מחזירה מידע לקריאה, כמו `INSERT`, `UPDATE`, `DELETE`. במידה והיא בוצעה בהצלחה היא תחזיר את מספר השורות שעודכנו. + +- [()query|CDbCommand::query]: מבצעת שאילתת SQL אשר מחזירה שורות של מידע, כמו `SELECT`. במידה והיא בוצעה בהצלחה, היא מחזירה אובייקט של [CDbDataReader] שבעזרתו ניתן יהיה לרוץ עליו לקבלת המידע שורה שורה. לנוחות, ישנם סט של מתודות `()queryXXX` אשר כלולות שבעזרתם ניתן לקבל את התוצאה ישירות. + +תזרק שגיאה במידה והייתה בעיה בהרצת השאילתה. + +~~~ +[php] +$rowCount=$command-»execute(); // ביצוע שאילתה שלא מחזירה מידע לקריאה +$dataReader=$command-»query(); // הרצת שאילתת SQL +$rows=$command-»queryAll(); // הרצת שאילתה והחזרת כל השורות של התוצאה +$row=$command-»queryRow(); // הרצת שאילתה והחזרת השורה הראשונה בתוצאה +$column=$command-»queryColumn(); // הרצת שאילתה והחזרה העמודה הראשונה של התוצאה +$value=$command-»queryScalar(); // הרצת שאילתה והחזרת העמודה הראשונה בשורה הראשונה +~~~ + +שליפת תוצאות שאילתה +---------------------- + +לאחר שהמתודה [()CDbCommand::query] יוצרת את האובייקט של [CDbDataReader], ניתן לקבל את השורות מהמידע שהוחזר על ידי קריאה ל [()CDbDataReader::read] שוב ושוב. ניתן גם להשתמש ב [CDbDataReader] בתוך לולאה `foreach` של PHP בכדי לקבל שורה אחרי שורה. + +~~~ +[php] +$dataReader=$command-»query(); +// קריאה ל read שוב ושוב עד שהוא מחזיר false +while(($row=$dataReader-»read())!==false) { ... } +// שימוש בלולאה על כל התוצאות +foreach($dataReader as $row) { ... } +// קבלת כל התוצאות במכה אחת כמערך +$rows=$dataReader-»readAll(); +~~~ + +» Note|הערה: בניגוד ל [()query|CDbCommand::query], כל המתודות של `queryXXX` מחזירות מידע בצורה ישירה. לדוגמא, [()queryRow|CDbCommand::queryRow] מחזירה מערך המייצג את השורה הראשונה של תוצאת השאילתה. + +שימוש בטרנזקציות +------------------ + +כשאפליקציה מריצה כמה שאילתות, כל אחת קוראת ו/או כותבת מידע למסד הנתונים, חשוב לדעת ולהיות בטוחים שהמסד נתונים מבצע ומריץ את כל השאילתות ולא מפספס אף אחת. טרנזקציה, המיוצגת כאובייקט של [CDbTransaction] ב Yii , ניתנת לשימוש במקרה כזה: + +- התחלת הטרנזקציה. +- הרצת השאילתות אחת אחרי השנייה. כל העדכונים למסד לא מוצגים לעולם החיצון. +- ביצוע הטרנזקציה. כעת ניתן לראות את העדכונים במידה והטרנזקציה הסתיימה בהצלחה. +- במידה ואחת מהשאילתות נכשלת בזמן הרצה, כל הטרנזקציה חוזרת אחורה. + +רצף העבודה המוצג למעלה ניתן ליישום בעזרת הקוד הבא: + +~~~ +[php] +$transaction=$connection-»beginTransaction(); +try +{ + $connection-»createCommand($sql1)-»execute(); + $connection-»createCommand($sql2)-»execute(); + //.... other SQL executions + $transaction-»commit(); +} +catch(Exception $e) // תתבצע שגיאה במידה וישנה בעיה +{ + $transaction-»rollBack(); +} +~~~ + +תיחום פרמטרים +------------------ + +בכדי להמנע מהתקפות בעזרת [הזרקות SQL](http://en.wikipedia.org/wiki/SQL_injection) ובכדי לשפר את הביצועים של שאילתות SQL החוזרות על עצמם, ניתן 'להכין' שאילתת SQL עם מקומות שמורים לפרמטרים אשר יתחלפו עם הפרמטרים המקוריים בזמן תהליך תיחום הפרמטרים. + +המקומות השמורים לפרמטרים יכולים להיות מוגדרים עם שם יחודי (מאופיינים כמילות מפתח יחודיות) או ללא שם (מאופיינים בעזרת סימני שאלה). יש לקרוא ל [()CDbCommand::bindParam] או [()CDbCommand::bindValue] בכדי להחליף את הפרמטר עם הערך שהוגדר לו. אין צורך לעטוף את הפרמטרים בעזרת מרכאות: מסד הנתונים בו אתה משתמש יעשה זאת אוטומטית. יש לתחום את הפרמטרים לפני קריאה למתודה המבצעת את השאילתה. + +~~~ +[php] +// שאילתת SQL עם שני מפתחות יחודיים לפרמטרים :username או :email +$sql="INSERT INTO tbl_user (username, email) VALUES(:username,:email)"; +$command=$connection-»createCommand($sql); +// החלף את המפתח :username עם ערך שהוגדר לו +$command-»bindParam(":username",$username,PDO::PARAM_STR); +// החלף את המפתח :email עם הערך שהוגדר לו +$command-»bindParam(":email",$email,PDO::PARAM_STR); +$command-»execute(); +// הוסף שורה חדשה עם סט חדש של ערכים +$command-»bindParam(":username",$username2,PDO::PARAM_STR); +$command-»bindParam(":email",$email2,PDO::PARAM_STR); +$command-»execute(); +~~~ + +המתודות [()bindParam|CDbCommand::bindParam] ו [()bindValue|CDbCommand::bindValue] הם דומות מאוד. ההבדל היחידי היא שהראשון תוחם פרמטר בעזרת משתנה ב PHP והשני תוחם פרמטר בעזרת ערך. עבור פרמטרים המייצגים בלוקים גדולים של מידע, הראשון הוא עדיף מבחינת ביצועים. + +למידע נוסף אודות תיחום פרמטרים, יש לקרוא [בדוקומנטציה של PHP](http://www.php.net/manual/en/pdostatement.bindparam.php). + +תיחום עמודות +--------------- + +בזמן שליפת תוצאות השאילתה, ניתן לתחום עמודות בעזרת משתנים ב PHP כדי שהם יאוכלסו אוטומטית עם הנתונים האחרונים בכל פעם ששורה חדשה נשלפה ממסד הנתונים בעזרת השאילתה, וניתן יהיה לגשת אליהם. + +~~~ +[php] +$sql="SELECT username, email FROM tbl_user"; +$dataReader=$connection-»createCommand($sql)-»query(); +// תיחום העמודה הראשונה (username) עם המשתנה $username +$dataReader-»bindColumn(1,$username); +// תיחום העמודה השנייה (email) עם המשתנה $email +$dataReader-»bindColumn(2,$email); +while($dataReader-»read()!==false) +{ + // כעת ניתן להשתמש ב $username ו $email אשר מכילים את המידע מהשורה האחרונה שהגיע ממסד הנתונים. +} +~~~ + +שימוש בקידומת לטבלאות +------------------ + +החל מגרסא 1.1.0, Yii מאפשרת תמיכה מובנית לשימוש בקידומת לטבלאות במסד הנתונים. משמעות הקידומת הינה סטרינג אשר מחובר לתחילת שמות הטבלאות במסד בו כרגע משתמשים. בדרך כלל משתמשים באפשרות זו בשרתים שיתופיים אשר מריצים כמה אפליקציות על אותו המסד ומשתמשים בקידומת שונה לטבלאות לכל אפליקציה כדי להבדיל ביניהם. לדוגמא, אפליקציה אחת יכולה להשתמש בקידומת `_tbl` בזמן שאפליקציה נוספת תשתמש בקידומת `_yii`. + +בכדי להשתמש בקידומת לטבלאות, יש להגדיר את המאפיין [CDbConnection::tablePrefix] עם הקידומת הרצויה לשימוש. לאחר מכן, בשאילתות ה SQL יש להשתמש ב `{{שם הטבלה}}` בכדי להתייחס לשמות הטבלאות, כש `שם הטבלה` מתייחס לשם הטבלה במסד ללא הקידומת. לדוגמא, אם המסד נתונים מכיל טבלה בשם `tbl_user` כש `_tbl` מוגדר כקידומת לטבלאות, אז נוכל להשתמש בקוד הבא בכדי לשלוף נתונים מהטבלה הזו: + +~~~ +[php] +$sql='SELECT * FROM {{user}}'; +$users=$connection-»createCommand($sql)-»queryAll(); +~~~ + «div class="revision"»$Id: database.dao.txt 1764 2010-02-01 00:09:12Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/database.overview.txt b/docs/guide/he/database.overview.txt index 1a07fa6c3..823c83b8b 100644 --- a/docs/guide/he/database.overview.txt +++ b/docs/guide/he/database.overview.txt @@ -1,8 +1,8 @@ -עבודה עם מסד נתונים -===================== - -Yii מספקת תמיכה נרחבת לתכנות ועבודה מול מסדי נתונים. Yii Data Access Objects (DAO) הבנויה על גבי התוסף של PHP הנקרא PHP Data Objects (PDO), מאפשרת גישה לסוגי מסדי נתונים שונים (DBMS) בממשק אחיד ונוח. אפליקציות המפותחות בעזרת ה DAO של Yii יכולות לעבוד בקלות עם DMBS אחרת ללא צורך בעריכת הקוד אשר ניגש למידע. Active Record (AR) אשר קיים ב Yii, מיושם באמצעות השיטה הנפוצה של Object-Relational Mapping (ORM), אשר מפשטת אף יותר את התכנות בעזרת מסד נתונים. ייצוג טבלה כמחלקה ושורה בטבלה כאובייקט, Yii AR מסירה את המשימה החוזרת על עצמה של כתיבת שאילתות SQL אשר ברוב המקרים מבצעות את אותו הדבר שהם פעלות CRUD (create, read, update, delete - יצירה, קריאה, עדכון ומחיקה) למיניהם. - -למרות שמחלקות ה DAO ו AR במערכת ה Yii מטפלות בכמעט כל הנושאים הקשורים למסדי נתונים, תוכל עדיין להשתמש בספריה שלך המנהלת את מסד הנתונים באפליקציה. למעשה, Yii בנויה בצורה שתוכל לעבוד במקביל עם ספריות צד-שלישי נוספות. - +עבודה עם מסד נתונים +===================== + +Yii מספקת תמיכה נרחבת לתכנות ועבודה מול מסדי נתונים. Yii Data Access Objects (DAO) הבנויה על גבי התוסף של PHP הנקרא PHP Data Objects (PDO), מאפשרת גישה לסוגי מסדי נתונים שונים (DBMS) בממשק אחיד ונוח. אפליקציות המפותחות בעזרת ה DAO של Yii יכולות לעבוד בקלות עם DMBS אחרת ללא צורך בעריכת הקוד אשר ניגש למידע. Active Record (AR) אשר קיים ב Yii, מיושם באמצעות השיטה הנפוצה של Object-Relational Mapping (ORM), אשר מפשטת אף יותר את התכנות בעזרת מסד נתונים. ייצוג טבלה כמחלקה ושורה בטבלה כאובייקט, Yii AR מסירה את המשימה החוזרת על עצמה של כתיבת שאילתות SQL אשר ברוב המקרים מבצעות את אותו הדבר שהם פעלות CRUD (create, read, update, delete - יצירה, קריאה, עדכון ומחיקה) למיניהם. + +למרות שמחלקות ה DAO ו AR במערכת ה Yii מטפלות בכמעט כל הנושאים הקשורים למסדי נתונים, תוכל עדיין להשתמש בספריה שלך המנהלת את מסד הנתונים באפליקציה. למעשה, Yii בנויה בצורה שתוכל לעבוד במקביל עם ספריות צד-שלישי נוספות. + «div class="revision"»$Id: database.overview.txt 163 2008-11-05 12:51:48Z weizhuo $«/div» \ No newline at end of file diff --git a/docs/guide/he/extension.create.txt b/docs/guide/he/extension.create.txt index 15a87860a..309992dd5 100644 --- a/docs/guide/he/extension.create.txt +++ b/docs/guide/he/extension.create.txt @@ -1,177 +1,177 @@ -יצירת הרחבות -=================== - -מאחר והרחבות נועדו לשימוש על ידי מפתחים אחרים, יצירה שלהם דורשת מאמץ נוסף. להלן כמה הנחיות כלליות: - -* הרחבה צריכה להיות נפרדת. כלומר, התלות החיצונית שלה צריכה להיות מינימלית. זה יהיה כאב ראש למשתמשים אם הרחבה תדרוש התקנה של חבילות נוספות, מחלקות או קבצים. -* קבצים הקשורים להרחבה צריכים להיות מסודרים תחת אותה תיקיה ששמה הוא שם ההרחבה. -* שמות המחלקות בהרחבה צריכות להיות מוגדרת עם קידומת כלשהי בכדי להבדיל אותם ממחלקות של הרחבות נוספות. -* הרחבה צריכה להגיע ולהכיל מידע אודות ההתקנה ושימוש בהרחבה (דוקומנטציה). זה יחסוך זמן ומאמץ של מפתחים אחרים שצריכים להשתמש בהרחבה. -* הרחבה צריכה לכלול רישיון מתאים. במידה והינך רוצה שיהיה ניתן להשתמש בהרחבה בפרוייקטים פתוחים וסגורים, יש לשקול שימוש ברישיון מסוג BSD, MIT וכדומה, לא GPL מאחר והוא דורש שהקוד שמריץ אותו יהיה פתוח גם כן. - -בחלק זה, אנו מסבירים כיצד ניתן ליצור הרחבות חדשות, לפי הקטגוריות המתוארות [סקירה](/doc/guide/extension.overview). -תיאורים אלו תקפים גם לרכיבים שהינך יוצר לפרוייקטים שלך בלבד. - -רכיב אפליקציה ---------------------- - -[רכיב אפליקציה](/doc/guide/basics.application#application-component) צריך ליישם את הממשק [IApplicationComponent] או להיות תת-מחלקה (לירוש) של [CApplicationComponent]. המתודה העיקרית שצריך ליישם הינה [IApplicationComponent::init] שבה הרכיב מבצע עבודות אתחול. מתודה זו רצה לאחר היצור של הרכיב והמאפיינים ההתחלתיים (המוגדרים [בהגדרות האפליקציה](/doc/guide/basics.application#application-configuration)) מצורפים. - -כברירת מחדל, רכיב אפליקציה נוצר ומאותחל רק ברגע שניגשים אליו בפעם הראשונה בזמן ניהול הבקשה. במידה ויש ליצור רכיב אפליקציה לאחר יצירת האובייקט של האפליקציה, מומלץ לדרוש מהמשתמש להגדיר את המזהה היחודי שלו במאפיין [CApplication::preload]. - -התנהגות --------- - -בכדי ליצור התנהגות, יש ליישם את הממשק [IBehavior]. לנוחות, Yii מספקת מחלקת בסיס [CBehavior] אשר כבר מיישמת את הממשק הנחוץ ומספקת כמה מתודות שימושיות נוספות. תת-מחלקות צריכות ליישם את המתודות הנוספות שעומדות להיות זמינות לרכיבים אליהם הם יצורפו. - -כשמפתחים התנהגות עבור [CModel] ו [CActiveRecord], ניתן להרחיב מהמחלקות [CModelBehavior] ו [CActiveRecordBehavior], בהתאמה. מחלקות בסיס אלו מציעות אפשרויות נוספות שמיועדות ספציפית ל [CModel] ו [CActiveRecord]. לדוגמא, המחלקה [CActiveRecordBehavior] מיישמת סט של מתודות המגיבות לאירועים אשר מתבצעים במחלקת ActiveRecord. - -הקוד הבא מציג דוגמא להתנהגות של ActiveRecord. כשההתנהגות מצורפת לאובייקט של AR וכשהאובייקט AR נשמר על ידי קריאה למתודה `save()`, הוא אוטומטית יגדיר את המאפיינים `create_time` ו `update_time` עם הזמן הנוכחי בשרת. - -~~~ -[php] -class TimestampBehavior extends CActiveRecordBehavior -{ - public function beforeSave($event) - { - if($this-»owner-»isNewRecord) - $this-»owner-»create_time=time(); - else - $this-»owner-»update_time=time(); - } -} -~~~ - - -וידג'ט ------- - -[וידג'ט](/doc/guide/basics.view#widget) צריך להיות תת מחלקה של [CWidget] או מחלקות היורשות ממנה. - -הדרך הקלה ביותר ליצור וידג'ט היא על ידי הרחבת וידג'ט קיים, דריסה של מתודות הנמצאות בו והגדרת המאפיינים הראשיים שלו. לדוגמא, במידה והינך רוצה לעבוד עם סגנון CSS שונה עבור [CTabView], ניתן להגדיר את המאפיין שלו [CTabView::cssFile] בזמן השימוש בוידג'ט. כמו כן ניתן להרחיב את המחלקה שלו [CTabView] בצורה הבאה בכדי שלא תצטרך להגדיר מאפיין זה בכל פעם שהינך משתמש בוידג'ט. - -~~~ -[php] -class MyTabView extends CTabView -{ - public function init() - { - if($this-»cssFile===null) - { - $file=dirname(__FILE__).DIRECTORY_SEPARATOR.'tabview.css'; - $this-»cssFile=Yii::app()-»getAssetManager()-»publish($file); - } - parent::init(); - } -} -~~~ - -בקוד למעלה, אנו דורסים את המתודה [CWidget::init] ומציבים למאפיין [CTabView::cssFile] את סגנון CSS ברירת המחדל במידה והוא לא הוגדר ספציפית. אנו מוסיפים את קובץ הסגנון תחת התיקיה הנוכחית בה נמצאת המחלקה `MyTabView` בכדי שיהיה ניתן לאחד אותם כהרחבה. מאחר וקובץ הסגנון הוא לא נגיש לווב יהיה צורך בלפרסם אותו בקובץ נכס. - -בכדי ליצור וידג'ט חדש מהתחלה, אנו בעיקר צריכים ליישם שני מתודות: [CWidget::init] ו [CWidget::run]. הראשונה רצה כשאנו משתמשים ב `this-»beginWidget$` בכדי להוסיף את הוידג'ט לתצוגה, והשני רץ ברגע שאנו קוראים ל `this-»endWidget$`. -במידה ואנו רוצים לתפוס את התוכן הנמצא בין שני המתודות הללו, אנו יכולים להתחיל [בפקודת תפסית תצוגה](http://us3.php.net/manual/en/book.outcontrol.php) במתודה [CWidget::init] ולקבל את התוכן שנתפס על ידי [CWidget::run] להמשך העיבוד. - -בדרך כלל וידג'ט דורש הוספה של קבצי CSS, JS וקבצים נוספים בעמוד אשר הוידג'ט משתמש בהם. אנו קוראים לקבצים אלו *נכסים* מאחר והם נשארים ביחד עם המחלקה של הוידג'ט ובדרך כלל לא ניתן לגשת אליהם דרך ממשק הווב. בכדי לאפשר גישה לקבצים אלו דרך הווב, אנו צריכים לפרסם אותם לתיקיה בה יש גישה לווב בעזרת [CWebApplication::assetManager], כפי שמוצג בקוד למעלה. חוץ מזה, שבמידה ואנו רוצים לפרסם קובץ CSS או JS אנו צריכים לרשום אותו על ידי שימוש במחלקה [CClientScript]. - -~~~ -[php] -class MyWidget extends CWidget -{ - protected function registerClientScript() - { - // ..פרסום קבצי CSS ו JS כאן... - $cs=Yii::app()-»clientScript; - $cs-»registerCssFile($cssFile); - $cs-»registerScriptFile($jsFile); - } -} -~~~ - -וידג'ט יכול להכיל קבצי תצוגה משלו. במקרה זה, יש ליצור תיקיה בשם `views` תחת התיקיה המכילה את קובץ המחלקה של הוידג'ט, ויש לשמור את כל קבצי התצוגה של הוידג'ט בתיקיה זו. במחלקה של וידג'ט, בכדי לטעון קובץ תצוגה של הוידג'ט, יש להשתמש ב `$this-»render('viewName')`, בדומה למה שעושים בקונטרולר. - -פעולה ------- - -מחלקת [פעולה](/doc/guide/basics.controller#action) צריכה להיות תת-מחלקה של [CAction] או מחלקות היורשות ממנה. המתודה העיקרית שצריך ליישם לפעולה הינה [IAction::run]. - -פילטר ------- - -[פילטר](/doc/guide/basics.controller#filter) צריך להיות תת מחלקה של [CFilter] או מחלקות היורשות ממנה. המתודות העיקריות שצריך ליישם לפילטר הם [CFilter::preFilter] ו [CFilter::postFilter]. הראשון רץ לפני שהפעולה בקונטרולר מתבצעת בזמן שהשני לאחר הפעולה. - -~~~ -[php] -class MyFilter extends CFilter -{ - protected function preFilter($filterChain) - { - // לוגיקה שרצה לפני שהפעולה בקונטרולר רצה - return true; // יש להחזיר false במידה ורוצים לבטל את הרצת הפעולה - } - - protected function postFilter($filterChain) - { - // לוגיקה שרצה לאחר הרצת הפעולה בקונטרולר - } -} -~~~ - -הפרמטר `filterChain$` הוא אובייקט של [CFilterChain] המכיל מידע אודות הפעולה בקונטרולר שכרגע עוברת בתוך הפילטר. - -קונטרולר ----------- - -[קונטרולר](/doc/guide/basics.controller) המופץ בתור הרחבה המחלקה שלו צריכה להיות תת של [CExtController], במקום [CController]. הסיבה העיקרית הינה ש [CController] מניח שקבצי התצוגה של הקונטרולר נמצאים תחת `application.views.controllerID`, בזמן ש [CExtController] מניח שקבצי התצוגה של הקונטרולר נמצאים תחת התיקיה `views` שהינה תת תיקיה של התיקיה בה נמצא קובץ המחלקה של הקונטרולר. לכן, קל יותר להפיץ את הקונטרולר מאחר וקבצי התצוגה שלו נשארים ביחד עם קובץ המחלקה של הקונטרולר. - -ולידטור (אימות נתונים) ---------- - -ולידטור צריך להיות תת-מחלקה של [CValidator] וליישם את המתודה [CValidator::validateAttribute]. - -~~~ -[php] -class MyValidator extends CValidator -{ - protected function validateAttribute($model,$attribute) - { - $value=$model-»$attribute; - if($value has error) - $model-»addError($attribute,$errorMessage); - } -} -~~~ - -מסוף פקודות ---------------- - -[פקודת מסוף](/doc/guide/topics.console) צריכה לירוש מהמחלקה [CConsoleCommand] וליישם את המתודה [CConsoleCommand::run]. בנוסף, ניתן לדרוס את המתודה [CConsoleCommand::getHelp] בכדי לספק מידע אודות השימוש בפקודה. - -~~~ -[php] -class MyCommand extends CConsoleCommand -{ - public function run($args) - { - // הפרמטר היחידה בפונקציה זו מחזיר מערך עם המידע שהוזן לפקודה - } - - public function getHelp() - { - return 'הסבר כיצד להריץ פקודה זו'; - } -} -~~~ - -מודול ------- - -אנא קרא את החלק אודות מודולים המסביר כיצד לייצר מודול. - -הנחייה כללית לפיתוח מודול הינה שהמודול צריך להיות חלק בפני עצמו. קבצי משאבים (כמו קבצי JS, CSS ותמונות) אשר המודול משתמש בהם צריכים להגיע ביחד עם המודול. והמודול צריך לפרסם אותם כדי שיהיה ניתן לגשת אליהם דרך הווב (עמוד האינטרנט). - -רכיב כללי ------------------ - -פיתוח הרחבה שהינה רכיב כללי זה כמו לכתוב מחלקה. שוב, הרכיב צריך לעמוד בפני עצמו בכדי שמפתחים אחרים יוכלו להשתמש בו. - +יצירת הרחבות +=================== + +מאחר והרחבות נועדו לשימוש על ידי מפתחים אחרים, יצירה שלהם דורשת מאמץ נוסף. להלן כמה הנחיות כלליות: + +* הרחבה צריכה להיות נפרדת. כלומר, התלות החיצונית שלה צריכה להיות מינימלית. זה יהיה כאב ראש למשתמשים אם הרחבה תדרוש התקנה של חבילות נוספות, מחלקות או קבצים. +* קבצים הקשורים להרחבה צריכים להיות מסודרים תחת אותה תיקיה ששמה הוא שם ההרחבה. +* שמות המחלקות בהרחבה צריכות להיות מוגדרת עם קידומת כלשהי בכדי להבדיל אותם ממחלקות של הרחבות נוספות. +* הרחבה צריכה להגיע ולהכיל מידע אודות ההתקנה ושימוש בהרחבה (דוקומנטציה). זה יחסוך זמן ומאמץ של מפתחים אחרים שצריכים להשתמש בהרחבה. +* הרחבה צריכה לכלול רישיון מתאים. במידה והינך רוצה שיהיה ניתן להשתמש בהרחבה בפרוייקטים פתוחים וסגורים, יש לשקול שימוש ברישיון מסוג BSD, MIT וכדומה, לא GPL מאחר והוא דורש שהקוד שמריץ אותו יהיה פתוח גם כן. + +בחלק זה, אנו מסבירים כיצד ניתן ליצור הרחבות חדשות, לפי הקטגוריות המתוארות [סקירה](/doc/guide/extension.overview). +תיאורים אלו תקפים גם לרכיבים שהינך יוצר לפרוייקטים שלך בלבד. + +רכיב אפליקציה +--------------------- + +[רכיב אפליקציה](/doc/guide/basics.application#application-component) צריך ליישם את הממשק [IApplicationComponent] או להיות תת-מחלקה (לירוש) של [CApplicationComponent]. המתודה העיקרית שצריך ליישם הינה [IApplicationComponent::init] שבה הרכיב מבצע עבודות אתחול. מתודה זו רצה לאחר היצור של הרכיב והמאפיינים ההתחלתיים (המוגדרים [בהגדרות האפליקציה](/doc/guide/basics.application#application-configuration)) מצורפים. + +כברירת מחדל, רכיב אפליקציה נוצר ומאותחל רק ברגע שניגשים אליו בפעם הראשונה בזמן ניהול הבקשה. במידה ויש ליצור רכיב אפליקציה לאחר יצירת האובייקט של האפליקציה, מומלץ לדרוש מהמשתמש להגדיר את המזהה היחודי שלו במאפיין [CApplication::preload]. + +התנהגות +-------- + +בכדי ליצור התנהגות, יש ליישם את הממשק [IBehavior]. לנוחות, Yii מספקת מחלקת בסיס [CBehavior] אשר כבר מיישמת את הממשק הנחוץ ומספקת כמה מתודות שימושיות נוספות. תת-מחלקות צריכות ליישם את המתודות הנוספות שעומדות להיות זמינות לרכיבים אליהם הם יצורפו. + +כשמפתחים התנהגות עבור [CModel] ו [CActiveRecord], ניתן להרחיב מהמחלקות [CModelBehavior] ו [CActiveRecordBehavior], בהתאמה. מחלקות בסיס אלו מציעות אפשרויות נוספות שמיועדות ספציפית ל [CModel] ו [CActiveRecord]. לדוגמא, המחלקה [CActiveRecordBehavior] מיישמת סט של מתודות המגיבות לאירועים אשר מתבצעים במחלקת ActiveRecord. + +הקוד הבא מציג דוגמא להתנהגות של ActiveRecord. כשההתנהגות מצורפת לאובייקט של AR וכשהאובייקט AR נשמר על ידי קריאה למתודה `save()`, הוא אוטומטית יגדיר את המאפיינים `create_time` ו `update_time` עם הזמן הנוכחי בשרת. + +~~~ +[php] +class TimestampBehavior extends CActiveRecordBehavior +{ + public function beforeSave($event) + { + if($this-»owner-»isNewRecord) + $this-»owner-»create_time=time(); + else + $this-»owner-»update_time=time(); + } +} +~~~ + + +וידג'ט +------ + +[וידג'ט](/doc/guide/basics.view#widget) צריך להיות תת מחלקה של [CWidget] או מחלקות היורשות ממנה. + +הדרך הקלה ביותר ליצור וידג'ט היא על ידי הרחבת וידג'ט קיים, דריסה של מתודות הנמצאות בו והגדרת המאפיינים הראשיים שלו. לדוגמא, במידה והינך רוצה לעבוד עם סגנון CSS שונה עבור [CTabView], ניתן להגדיר את המאפיין שלו [CTabView::cssFile] בזמן השימוש בוידג'ט. כמו כן ניתן להרחיב את המחלקה שלו [CTabView] בצורה הבאה בכדי שלא תצטרך להגדיר מאפיין זה בכל פעם שהינך משתמש בוידג'ט. + +~~~ +[php] +class MyTabView extends CTabView +{ + public function init() + { + if($this-»cssFile===null) + { + $file=dirname(__FILE__).DIRECTORY_SEPARATOR.'tabview.css'; + $this-»cssFile=Yii::app()-»getAssetManager()-»publish($file); + } + parent::init(); + } +} +~~~ + +בקוד למעלה, אנו דורסים את המתודה [CWidget::init] ומציבים למאפיין [CTabView::cssFile] את סגנון CSS ברירת המחדל במידה והוא לא הוגדר ספציפית. אנו מוסיפים את קובץ הסגנון תחת התיקיה הנוכחית בה נמצאת המחלקה `MyTabView` בכדי שיהיה ניתן לאחד אותם כהרחבה. מאחר וקובץ הסגנון הוא לא נגיש לווב יהיה צורך בלפרסם אותו בקובץ נכס. + +בכדי ליצור וידג'ט חדש מהתחלה, אנו בעיקר צריכים ליישם שני מתודות: [CWidget::init] ו [CWidget::run]. הראשונה רצה כשאנו משתמשים ב `this-»beginWidget$` בכדי להוסיף את הוידג'ט לתצוגה, והשני רץ ברגע שאנו קוראים ל `this-»endWidget$`. +במידה ואנו רוצים לתפוס את התוכן הנמצא בין שני המתודות הללו, אנו יכולים להתחיל [בפקודת תפסית תצוגה](http://us3.php.net/manual/en/book.outcontrol.php) במתודה [CWidget::init] ולקבל את התוכן שנתפס על ידי [CWidget::run] להמשך העיבוד. + +בדרך כלל וידג'ט דורש הוספה של קבצי CSS, JS וקבצים נוספים בעמוד אשר הוידג'ט משתמש בהם. אנו קוראים לקבצים אלו *נכסים* מאחר והם נשארים ביחד עם המחלקה של הוידג'ט ובדרך כלל לא ניתן לגשת אליהם דרך ממשק הווב. בכדי לאפשר גישה לקבצים אלו דרך הווב, אנו צריכים לפרסם אותם לתיקיה בה יש גישה לווב בעזרת [CWebApplication::assetManager], כפי שמוצג בקוד למעלה. חוץ מזה, שבמידה ואנו רוצים לפרסם קובץ CSS או JS אנו צריכים לרשום אותו על ידי שימוש במחלקה [CClientScript]. + +~~~ +[php] +class MyWidget extends CWidget +{ + protected function registerClientScript() + { + // ..פרסום קבצי CSS ו JS כאן... + $cs=Yii::app()-»clientScript; + $cs-»registerCssFile($cssFile); + $cs-»registerScriptFile($jsFile); + } +} +~~~ + +וידג'ט יכול להכיל קבצי תצוגה משלו. במקרה זה, יש ליצור תיקיה בשם `views` תחת התיקיה המכילה את קובץ המחלקה של הוידג'ט, ויש לשמור את כל קבצי התצוגה של הוידג'ט בתיקיה זו. במחלקה של וידג'ט, בכדי לטעון קובץ תצוגה של הוידג'ט, יש להשתמש ב `$this-»render('viewName')`, בדומה למה שעושים בקונטרולר. + +פעולה +------ + +מחלקת [פעולה](/doc/guide/basics.controller#action) צריכה להיות תת-מחלקה של [CAction] או מחלקות היורשות ממנה. המתודה העיקרית שצריך ליישם לפעולה הינה [IAction::run]. + +פילטר +------ + +[פילטר](/doc/guide/basics.controller#filter) צריך להיות תת מחלקה של [CFilter] או מחלקות היורשות ממנה. המתודות העיקריות שצריך ליישם לפילטר הם [CFilter::preFilter] ו [CFilter::postFilter]. הראשון רץ לפני שהפעולה בקונטרולר מתבצעת בזמן שהשני לאחר הפעולה. + +~~~ +[php] +class MyFilter extends CFilter +{ + protected function preFilter($filterChain) + { + // לוגיקה שרצה לפני שהפעולה בקונטרולר רצה + return true; // יש להחזיר false במידה ורוצים לבטל את הרצת הפעולה + } + + protected function postFilter($filterChain) + { + // לוגיקה שרצה לאחר הרצת הפעולה בקונטרולר + } +} +~~~ + +הפרמטר `filterChain$` הוא אובייקט של [CFilterChain] המכיל מידע אודות הפעולה בקונטרולר שכרגע עוברת בתוך הפילטר. + +קונטרולר +---------- + +[קונטרולר](/doc/guide/basics.controller) המופץ בתור הרחבה המחלקה שלו צריכה להיות תת של [CExtController], במקום [CController]. הסיבה העיקרית הינה ש [CController] מניח שקבצי התצוגה של הקונטרולר נמצאים תחת `application.views.controllerID`, בזמן ש [CExtController] מניח שקבצי התצוגה של הקונטרולר נמצאים תחת התיקיה `views` שהינה תת תיקיה של התיקיה בה נמצא קובץ המחלקה של הקונטרולר. לכן, קל יותר להפיץ את הקונטרולר מאחר וקבצי התצוגה שלו נשארים ביחד עם קובץ המחלקה של הקונטרולר. + +ולידטור (אימות נתונים) +--------- + +ולידטור צריך להיות תת-מחלקה של [CValidator] וליישם את המתודה [CValidator::validateAttribute]. + +~~~ +[php] +class MyValidator extends CValidator +{ + protected function validateAttribute($model,$attribute) + { + $value=$model-»$attribute; + if($value has error) + $model-»addError($attribute,$errorMessage); + } +} +~~~ + +מסוף פקודות +--------------- + +[פקודת מסוף](/doc/guide/topics.console) צריכה לירוש מהמחלקה [CConsoleCommand] וליישם את המתודה [CConsoleCommand::run]. בנוסף, ניתן לדרוס את המתודה [CConsoleCommand::getHelp] בכדי לספק מידע אודות השימוש בפקודה. + +~~~ +[php] +class MyCommand extends CConsoleCommand +{ + public function run($args) + { + // הפרמטר היחידה בפונקציה זו מחזיר מערך עם המידע שהוזן לפקודה + } + + public function getHelp() + { + return 'הסבר כיצד להריץ פקודה זו'; + } +} +~~~ + +מודול +------ + +אנא קרא את החלק אודות מודולים המסביר כיצד לייצר מודול. + +הנחייה כללית לפיתוח מודול הינה שהמודול צריך להיות חלק בפני עצמו. קבצי משאבים (כמו קבצי JS, CSS ותמונות) אשר המודול משתמש בהם צריכים להגיע ביחד עם המודול. והמודול צריך לפרסם אותם כדי שיהיה ניתן לגשת אליהם דרך הווב (עמוד האינטרנט). + +רכיב כללי +----------------- + +פיתוח הרחבה שהינה רכיב כללי זה כמו לכתוב מחלקה. שוב, הרכיב צריך לעמוד בפני עצמו בכדי שמפתחים אחרים יוכלו להשתמש בו. + «div class="revision"»$Id: extension.create.txt 1423 2009-09-28 01:54:38Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/extension.integration.txt b/docs/guide/he/extension.integration.txt index b5c8ddb6e..30795a210 100644 --- a/docs/guide/he/extension.integration.txt +++ b/docs/guide/he/extension.integration.txt @@ -1,31 +1,31 @@ -שימוש בספריות צד שלישי -========================= - -Yii בנויה בצורה כזו שניתן להשתמש בספריות צד שלישי בקלות בכדי להרחיב את הפונקציונליות של Yii אף יותר. כשמשתמשים בספריות צד שלישי בפרוייקט, מפתחים בדרך כלל נתקלים בבעיות של שמות מחלקות והוספת קבצים. -מאחר וכל מחלקות הבסיס של Yii מתחילות באות 'C' , הסיכוי שתווצר בעיה עקב שמות מחלקות זהות הוא קטן; ומאחר ו Yii מסתמך על [טעינה אוטומטית בעזרת SPL](http://us3.php.net/manual/en/function.spl-autoload.php) בכדי לבצע את ההוספות של קבצי המחלקות, היא יכולה לשתף פעולה בצורה טובה עם ספריות נוספת אם הם משתמשים באפשרות PHP של טעינה אוטומטית או משתמשים בנתיב הוספת קבצים של PHP בכדי להוסיף קבצי מחלקות. - -למטה אנו מציגים דוגמא כיצד להשתמש ברכיב [Zend_Search_Lucene](http://www.zendframework.com/manual/en/zend.search.lucene.html) מהפריימוורק [Zend](http://www.zendframework.com) בתוך אפליקצית Yii. - -קודם, אנו מחלצים את כל המחלקות של Zend לתיקיה תחת התיקיה `Protected/vendors`, בהנחה ש `protected` הינה [התיקיה הראשית של האפליקציה](/doc/guide/basics.application#application-base-directory). -יש לוודא שהקובץ `protected/vendors/Zend/Search/Lucene.php` קיים. - -שנית, בתחילת קובץ מחלקת קונטרולר, יש להכניס את השורות הבאות: - -~~~ -[php] -Yii::import('application.vendors.*'); -require_once('Zend/Search/Lucene.php'); -~~~ - -הקוד למעלה מצרף את קובץ המחלקה `Lucene.php`. מאחר ואנו משתמשים בנתיב רלטיבי, אנו צריכים לשנות את תיקית הקבצים הראשית של PHP בכדי שניתן יהיה לטעון את הקובץ בצורה נכונה. זה נעשה על ידי קריאה ל `Yii::import` לפני הקריאה ל `require_once`. - -לאחר שההתקנה למעלה מוכנה, אנו יכולים להשתמש במחלקה `Lucene` בתוך פעולה בקונטרולר, כמו בדוגמא הבאה: - -~~~ -[php] -$lucene=new Zend_Search_Lucene($pathOfIndex); -$hits=$lucene-»find(strtolower($keyword)); -~~~ - - +שימוש בספריות צד שלישי +========================= + +Yii בנויה בצורה כזו שניתן להשתמש בספריות צד שלישי בקלות בכדי להרחיב את הפונקציונליות של Yii אף יותר. כשמשתמשים בספריות צד שלישי בפרוייקט, מפתחים בדרך כלל נתקלים בבעיות של שמות מחלקות והוספת קבצים. +מאחר וכל מחלקות הבסיס של Yii מתחילות באות 'C' , הסיכוי שתווצר בעיה עקב שמות מחלקות זהות הוא קטן; ומאחר ו Yii מסתמך על [טעינה אוטומטית בעזרת SPL](http://us3.php.net/manual/en/function.spl-autoload.php) בכדי לבצע את ההוספות של קבצי המחלקות, היא יכולה לשתף פעולה בצורה טובה עם ספריות נוספת אם הם משתמשים באפשרות PHP של טעינה אוטומטית או משתמשים בנתיב הוספת קבצים של PHP בכדי להוסיף קבצי מחלקות. + +למטה אנו מציגים דוגמא כיצד להשתמש ברכיב [Zend_Search_Lucene](http://www.zendframework.com/manual/en/zend.search.lucene.html) מהפריימוורק [Zend](http://www.zendframework.com) בתוך אפליקצית Yii. + +קודם, אנו מחלצים את כל המחלקות של Zend לתיקיה תחת התיקיה `Protected/vendors`, בהנחה ש `protected` הינה [התיקיה הראשית של האפליקציה](/doc/guide/basics.application#application-base-directory). +יש לוודא שהקובץ `protected/vendors/Zend/Search/Lucene.php` קיים. + +שנית, בתחילת קובץ מחלקת קונטרולר, יש להכניס את השורות הבאות: + +~~~ +[php] +Yii::import('application.vendors.*'); +require_once('Zend/Search/Lucene.php'); +~~~ + +הקוד למעלה מצרף את קובץ המחלקה `Lucene.php`. מאחר ואנו משתמשים בנתיב רלטיבי, אנו צריכים לשנות את תיקית הקבצים הראשית של PHP בכדי שניתן יהיה לטעון את הקובץ בצורה נכונה. זה נעשה על ידי קריאה ל `Yii::import` לפני הקריאה ל `require_once`. + +לאחר שההתקנה למעלה מוכנה, אנו יכולים להשתמש במחלקה `Lucene` בתוך פעולה בקונטרולר, כמו בדוגמא הבאה: + +~~~ +[php] +$lucene=new Zend_Search_Lucene($pathOfIndex); +$hits=$lucene-»find(strtolower($keyword)); +~~~ + + «div class="revision"»$Id: extension.integration.txt 1622 2009-12-26 20:56:05Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/extension.overview.txt b/docs/guide/he/extension.overview.txt index 5c77b9eea..e7cc5fb97 100644 --- a/docs/guide/he/extension.overview.txt +++ b/docs/guide/he/extension.overview.txt @@ -1,23 +1,23 @@ -סקירה -======== - -הרחבת Yii היא פעילות נפוצה בזמן הפיתוח. לדוגמא, בזמן כתיבת קונטרולר חדש, הינך מרחיב את Yii על ידי הורשה של מחלקת [CController]; בזמן כתיבת וידג'ט חדש, הינך יורש מהמחלקה [CWidget] או מחלקה של וידג'ט קיים. אם הקוד שהורחב נועד לשימוש חוזר על ידי מפתחי צד-שלישי, אנו קוראים לו *הרחבה*. - -הרחבה בדרך כלל משמשת למטרה אחת. במונחים של Yii, ניתן לסווג אותה בתור, - - * [רכיב באפליקציה](/doc/guide/basics.application#application-component) - * [התנהלות/התנהגות](/doc/guide/basics.component#component-behavior) - * [וידג'ט](/doc/guide/basics.view#widget) - * [קונטרולר](/doc/guide/basics.controller) - * [פעולה](/doc/guide/basics.controller#action) - * [פילטר](/doc/guide/basics.controller#filter) - * [מסוף פקודות](/doc/guide/topics.console) - * ולידטור: ולידטור הינו רכיב היורש מהמחלקה [CValidator] הנועד למטרת אימות נתונים. - * מסייע: מסייע הינו מחלקה עם מתודות סטטיות בלבד. בדומה לפונקציות גלובליות אשר משתמשים בשם המחלקה כמרחב השם לגישה למתודות בשתוכו. - - * [מודול](/doc/guide/basics.module): מודול הינו תת-אפליקציה הבנוי מ: [מודלים](/doc/guide/basics.model), [תצוגה](/doc/guide/basics.view), [קונטרולרים](/doc/guide/basics.controller) ורכיבים נתמכים נוספים. במובנים רבים, מודול מחקה את התכנון של [אפליקציה](/doc/guide/basics.application). ההבדל היחידי הוא שמודול נמצא בתוך אפליקציה. לדוגמא, יש לנו מודול שמטפל בכל הנושא של ניהול משתמשים. - - -הרחבה יכולה להיות גם רכיב שלא נופל באף אחת מהקטגוריות למעלה. למעשה, Yii בנויה בצורה כזו ככה שכמעט כל חתיכת קוד ניתנת להרחבה והתאמה אישית בכדי להתאים אותה לצרכים אישיים. - +סקירה +======== + +הרחבת Yii היא פעילות נפוצה בזמן הפיתוח. לדוגמא, בזמן כתיבת קונטרולר חדש, הינך מרחיב את Yii על ידי הורשה של מחלקת [CController]; בזמן כתיבת וידג'ט חדש, הינך יורש מהמחלקה [CWidget] או מחלקה של וידג'ט קיים. אם הקוד שהורחב נועד לשימוש חוזר על ידי מפתחי צד-שלישי, אנו קוראים לו *הרחבה*. + +הרחבה בדרך כלל משמשת למטרה אחת. במונחים של Yii, ניתן לסווג אותה בתור, + + * [רכיב באפליקציה](/doc/guide/basics.application#application-component) + * [התנהלות/התנהגות](/doc/guide/basics.component#component-behavior) + * [וידג'ט](/doc/guide/basics.view#widget) + * [קונטרולר](/doc/guide/basics.controller) + * [פעולה](/doc/guide/basics.controller#action) + * [פילטר](/doc/guide/basics.controller#filter) + * [מסוף פקודות](/doc/guide/topics.console) + * ולידטור: ולידטור הינו רכיב היורש מהמחלקה [CValidator] הנועד למטרת אימות נתונים. + * מסייע: מסייע הינו מחלקה עם מתודות סטטיות בלבד. בדומה לפונקציות גלובליות אשר משתמשים בשם המחלקה כמרחב השם לגישה למתודות בשתוכו. + + * [מודול](/doc/guide/basics.module): מודול הינו תת-אפליקציה הבנוי מ: [מודלים](/doc/guide/basics.model), [תצוגה](/doc/guide/basics.view), [קונטרולרים](/doc/guide/basics.controller) ורכיבים נתמכים נוספים. במובנים רבים, מודול מחקה את התכנון של [אפליקציה](/doc/guide/basics.application). ההבדל היחידי הוא שמודול נמצא בתוך אפליקציה. לדוגמא, יש לנו מודול שמטפל בכל הנושא של ניהול משתמשים. + + +הרחבה יכולה להיות גם רכיב שלא נופל באף אחת מהקטגוריות למעלה. למעשה, Yii בנויה בצורה כזו ככה שכמעט כל חתיכת קוד ניתנת להרחבה והתאמה אישית בכדי להתאים אותה לצרכים אישיים. + «div class="revision"»$Id: extension.overview.txt 1398 2009-09-06 01:15:01Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/extension.use.txt b/docs/guide/he/extension.use.txt index 1e180eab8..5ccff6b2b 100644 --- a/docs/guide/he/extension.use.txt +++ b/docs/guide/he/extension.use.txt @@ -1,266 +1,266 @@ -שימוש בהרחבות -================ - -שימוש בהרחבה בדרך כלל כולל את שלושת השלבים הבאים: - -1. הורדת ההרחבה [מספרית ההרחבות](http://www.yiiframework.com/extensions/) של Yii. -2. חילוץ קבצי ההרחבה תחת התיקיה `extensions/xyz` הנמצאת בתיקיה [ראשית](/doc/guide/basics.application#application-base-directory) של האפליקציה, כש `xyz`הינו שם ההרחבה. -3. ייבוא, הגדרה ושימוש בהרחבה. - -לכל הרחבה יש שם המייחד אותה משאר ההרחבות. בהתחשב שיש לנו הרחבה בשם `xyz`, אנו תמיד יכולים להשתמש בנתיב המקוצר `ext.xyz` בכדי לאתר את התיקיה הראשית שלה המכילה את כל הקבצים של `xyz`. - -» Note|הערה: הנתיב המקוצר `ext` קיים מגרסא 1.0.8. קודם לכן, היה צורך להשתמש ב `application.extensions` בכדי להתייחס לתיקיה המכילה את כל ההרחבות. בתיאור הבא, אנו מניחים ש `ext` מוגדר. יהיה צורך בלהחליף אותו עם `application.extensions` במידה והינך משתמש בגרסא 1.0.7 ומטה. - -הרחבות שונות כוללות דרישות שונות בנוגע לייבוא שלהם, הגדרות ושימוש. בחלק הבא, אנו מסכמים מקרים נפוצים של שימוש בהרחבות, לפי הקטגוריות שלהם המתוארות [סקירה](/doc/guide/extension.overview). - - -הרחבות Zii --------------- - -לפני שנתחיל להסביר בנוגע לשימוש הרחבות צד-שלישי, אנו נרצה קודם להציג את ספרית ה Zii, שמפותחת על ידי צוות הפיתוח של Yii וכלולה בכל הגרסאות של Yii החל מגרסא 1.1.0. ספרית ה Zii מאוחסנת כפרוייקט גוגל בשם [zii](http://code.google.com/p/zii/). - -בעת שימוש בהרחבה מספרית Zii, יש צורך להתייחס למחקה על ידי שימוש בנתיב מקוצר בפורמטר של `zii.path.to.ClassName`. במקרה הזה, הנתיב הראשי `zii` כבר הוגדר מראש על ידי Yii. נתיב זה מתייחס לתיקיה הראשית של ספריית ה Zii. לדוגמא, כדי להשתמש ב [CGridView], אנו נשתמש בקוד הבא בתוך קובץ תצוגה כשאנו מתייחסים להרחבה: - -~~~ -[php] -$this-»widget('zii.widgets.grid.CGridView', array( - 'dataProvider'=»$dataProvider, -)); -~~~ - - -רכיב באפליקציה ---------------------- - -בכדי להשתמש [ברכיב אפליקציה](/doc/guide/basics.application#application-component), אנו קודם צריכים לשנות את [הגדרות האפליקציה](/doc/guide/basics.application#application-configuration) על ידי הוספת רשומה (אלמנט) חדשה למאפיין של `components`, כפי שמוצג בדוגמא הבאה: - -~~~ -[php] -return array( - // 'preload'=»array('xyz',...), - 'components'=»array( - 'xyz'=»array( - 'class'=»'ext.xyz.XyzClass', - 'property1'=»'value1', - 'property2'=»'value2', - ), - // הגדרות רכיבים נוספים - ), -); -~~~ - -לאחר מכן, אנו יכולים לגשת לרכיב בכל מקום על ידי `Yii::app()-»xyz`. הרכיב יווצר בצורה עצלה (זאת אומרת יווצר כשהוא נקרא בפעם הראשונה), אלה אם כן אנו נרשום את שמו במאפיין של `preload` בהגדרות האפליקציה. - -התנהלות/התנהגות --------- - -[התנהלות/התנהגות](/doc/guide/basics.component#component-behavior) ניתנת לשימוש בכל מיני רכיבים. השימוש בהם כרוך בשני שלבים. בשלב הראשון, ההתנהלות מצורפת לרכיב כלשהו. בשלב השני, מתודת התנהלות נקראת על ידי הרכיב אליו היא צורפה. לדוגמא: - -~~~ -[php] -// הפרמטר הראשון הוא יחודי ומשמש לצורך זיהוי ההתנהלות -$component-»attachBehavior($name,$behavior); -// המתודה test נמצאת במחלקה של $behavior -$component-»test(); -~~~ - -בדרך כלל, התנהלות מצורפת לרכיב דרך הגדרות ולא על ידי קריאה למתודה `attachBehavior`. לדוגמא, בכדי לצרף התנהלות [רכיב](/doc/guide/basics.application#application-component) באפליקציה, אנו נשתמש [בהגדרות](/doc/guide/basics.application#application-configuration) האפליקציה הבאות: - -~~~ -[php] -return array( - 'components'=»array( - 'db'=»array( - 'class'=»'CDbConnection', - 'behaviors'=»array( - 'xyz'=»array( - 'class'=»'ext.xyz.XyzBehavior', - 'property1'=»'value1', - 'property2'=»'value2', - ), - ), - ), - //.... - ), -); -~~~ - -הקוד למעלה, מצרף את ההתנהלות `xyz` לרכיב האפליקציה `db`. אנו עושים זאת מאחר והמחלקה [CApplicationComponent] מגדירה מאפיין בשם `behaviors`. על ידי הגדרת מאפיין זה עם רשימה של הגדרות התנהלות, הרכיב יצרף את ההתנהלויות הללו בזמן האתחול שלו. - -עבור המחלקות [CController], [CFormModel] ו [CActiveRecord] שבדרך כלל דורשות הרחבה, צירוף התנהלויות נעשית על ידי דריסה של המתודה `()behaviors`. המחלקה תצרף אוטומטית את ההתנהלויות המוגדרות במתודה זו בזמן אתחול. לדוגמא, - -~~~ -[php] -public function behaviors() -{ - return array( - 'xyz'=»array( - 'class'=»'ext.xyz.XyzBehavior', - 'property1'=»'value1', - 'property2'=»'value2', - ), - ); -} -~~~ - - -וידג'ט ------- - -שימוש [בוידג'טים](/doc/guide/basics.view#widget) בדרך כלל נעשה בקבצי [תצוגה](/doc/guide/basics.view). נניח ומחלקת הוידג'ט `XyzClass` שייכת להרחבה `xyz`, אנו יכולים להשתמש בו בתוך קובץ תצוגה בצורה הבאה, - -~~~ -[php] -// וידג'ט שאינו דורש תוכן -«?php $this-»widget('ext.xyz.XyzClass', array( - 'property1'=»'value1', - 'property2'=»'value2')); ?» - -// וידג'ט שדורש תוכן -«?php $this-»beginWidget('ext.xyz.XyzClass', array( - 'property1'=»'value1', - 'property2'=»'value2')); ?» - -...תוכן הוידג'ט.... - -«?php $this-»endWidget(); ?» -~~~ - -פעולה ------- - -שימוש [בפעולות](/doc/guide/basics.controller#action) נעשה על ידי [הקונטרולרים](/doc/guide/basics.controller) בכדי לענות על בקשות משתמש ספציפיות. נניח ומחלקת הפעולה `XyzClass` השייכת להרחבה בשם `xyz`, אנו יכולים להשתמש בה על ידי דריסה של המתודה [CController::actions] במחלקה של הקונטרולר: - -~~~ -[php] -class TestController extends CController -{ - public function actions() - { - return array( - 'xyz'=»array( - 'class'=»'ext.xyz.XyzClass', - 'property1'=»'value1', - 'property2'=»'value2', - ), - // פעולות נוספות - ); - } -} -~~~ - -לאחר מכן, ניתן לגשת לפעולה על ידי [הניתוב](/doc/guide/basics.controller#route) `test/xyz`. - -פילטר ------- - -שימוש [פילטרים](/doc/guide/basics.controller#filter) נעשה גם כן על ידי [הקונטרולרים](/doc/guide/basics.controller). המטרה העיקרית שלהם היא הרצתם לפני ואחרי [פעולות](/doc/guide/basics.controller#action) מסויימות בכדי לבצע בדיקות או שינויים אחרים. נניח ומחלקת הפילטר בשם `XyzClass` השייכת להרחבה בשם `xzy`, אנו יכולים להשתמש בו על ידי דריסה של המתודה [CController::filters] במחלקת הקונטרולר: - -~~~ -[php] -class TestController extends CController -{ - public function filters() - { - return array( - array( - 'ext.xyz.XyzClass', - 'property1'=»'value1', - 'property2'=»'value2', - ), - // פילטרים נוספים - ); - } -} -~~~ - -בדוגמא למעלה, אנו יכולים להשתמש באופרטורים +, - באלמנט הראשון במערך בכדי לצרף את הפילטר למספר פעולות בלבד. למידע נוסף יש לקרוא את הדוקומנטציה אודות [CController]. - -קונטרולר ----------- - -[קונטרולר](/doc/guide/basics.controller) מספק סט של פעולות שניתנות לבקשה על ידי המשתמשים. בכדי להשתמש בהרחבה לקונטרולר, אנו צריכים להגדיר את המאפיין [CWebApplication::controllerMap] בקובץ [הגדרות האפליקציה](/doc/guide/basics.application#application-configuration): - -~~~ -[php] -return array( - 'controllerMap'=»array( - 'xyz'=»array( - 'class'=»'ext.xyz.XyzClass', - 'property1'=»'value1', - 'property2'=»'value2', - ), - // קונטרולרים נוספים - ), -); -~~~ - -לאחר מכן, הפעולה `a` ניתנת לגישה על ידי [ניתוב](/doc/guide/basics.controller#route) `xyz/a`. - -ולידטור (אימות נתונים) ---------- - -ולידטור בדרך כלל משומש בתוך מחלקת [מודל](/doc/guide/basics.model) (אחת שיורשת מהמחלקה [CFormModel] או [CActiveRecord]). -נניח וישנה מחלקת ולידטור בשם `XyzClass` השייכת להרחבה בשם `xyz`, אנו יכולים להשתמש בה על ידי דריסה של המתודה [CModel::rules] במחלקת המודל שלנו: - -~~~ -[php] -class MyModel extends CActiveRecord // or CFormModel -{ - public function rules() - { - return array( - array( - 'attr1, attr2', - 'ext.xyz.XyzClass', - 'property1'=»'value1', - 'property2'=»'value2', - ), - // חוקי אימות נתונים נוספים - ); - } -} -~~~ - -מסוף פקודות ---------------- - -הרחבות [מסוף הפקודות](/doc/guide/topics.console) בדרך כלל משפרת את הכלי `yiic` בפקודה נוספת. נניח שיש לנו מחלקה לפקודה `XyzClass` השייכת להרחבה `xyz`, אנו יכולים להשתמש בה על ידי הגדרת ההגדרות למסוף הפקודות באפליקציה: - -~~~ -[php] -return array( - 'commandMap'=»array( - 'xyz'=»array( - 'class'=»'ext.xyz.XyzClass', - 'property1'=»'value1', - 'property2'=»'value2', - ), - // פקודות נוספות - ), -); -~~~ - -לאחר מכן, אנו יכולים להשתמש בכלי `yiic` המכיל פקודה חדשה בשם `xyz`. - -» Note|הערה: קובץ הגדרות של מסוף הפקודות הוא שונה מקובץ ההגדרות של אפליקצית ווב. במידה ואפליקציה נוצרה בעזרת פקודת `yiic webapp`, קובץ ההגדרות של מסוף הפקודות `protected/yiic` הינו `protected/config/console.php`, בזמן שקובץ ההגדרות לאפליקצית ווב הינו `protected/config/main.php`. - - -מודול ------- - -אנא קרא את החלק אודות [מודולים](/doc/guide/basics.module#using-module) וכיצד לעבוד עמם. - -רכיב כללי ------------------ - -בכדי להשתמש [רכיב](/doc/guide/basics.component) כללי, אנו קודם צריכים להוסיף את קובץ המחלקה שלו על ידי שימוש ב - -~~~ -Yii::import('ext.xyz.XyzClass'); -~~~ - -לאחר מכן, אנו יכולים ליצור אובייקט של המחלקה, להגדיר את המאפיינים שלו, ולקרוא למתודות אשר נמצאות בו. כמו כן אנו יכולים להרחיב אותו כדי ליצור תת מחלקה חדשה. - - +שימוש בהרחבות +================ + +שימוש בהרחבה בדרך כלל כולל את שלושת השלבים הבאים: + +1. הורדת ההרחבה [מספרית ההרחבות](http://www.yiiframework.com/extensions/) של Yii. +2. חילוץ קבצי ההרחבה תחת התיקיה `extensions/xyz` הנמצאת בתיקיה [ראשית](/doc/guide/basics.application#application-base-directory) של האפליקציה, כש `xyz`הינו שם ההרחבה. +3. ייבוא, הגדרה ושימוש בהרחבה. + +לכל הרחבה יש שם המייחד אותה משאר ההרחבות. בהתחשב שיש לנו הרחבה בשם `xyz`, אנו תמיד יכולים להשתמש בנתיב המקוצר `ext.xyz` בכדי לאתר את התיקיה הראשית שלה המכילה את כל הקבצים של `xyz`. + +» Note|הערה: הנתיב המקוצר `ext` קיים מגרסא 1.0.8. קודם לכן, היה צורך להשתמש ב `application.extensions` בכדי להתייחס לתיקיה המכילה את כל ההרחבות. בתיאור הבא, אנו מניחים ש `ext` מוגדר. יהיה צורך בלהחליף אותו עם `application.extensions` במידה והינך משתמש בגרסא 1.0.7 ומטה. + +הרחבות שונות כוללות דרישות שונות בנוגע לייבוא שלהם, הגדרות ושימוש. בחלק הבא, אנו מסכמים מקרים נפוצים של שימוש בהרחבות, לפי הקטגוריות שלהם המתוארות [סקירה](/doc/guide/extension.overview). + + +הרחבות Zii +-------------- + +לפני שנתחיל להסביר בנוגע לשימוש הרחבות צד-שלישי, אנו נרצה קודם להציג את ספרית ה Zii, שמפותחת על ידי צוות הפיתוח של Yii וכלולה בכל הגרסאות של Yii החל מגרסא 1.1.0. ספרית ה Zii מאוחסנת כפרוייקט גוגל בשם [zii](http://code.google.com/p/zii/). + +בעת שימוש בהרחבה מספרית Zii, יש צורך להתייחס למחקה על ידי שימוש בנתיב מקוצר בפורמטר של `zii.path.to.ClassName`. במקרה הזה, הנתיב הראשי `zii` כבר הוגדר מראש על ידי Yii. נתיב זה מתייחס לתיקיה הראשית של ספריית ה Zii. לדוגמא, כדי להשתמש ב [CGridView], אנו נשתמש בקוד הבא בתוך קובץ תצוגה כשאנו מתייחסים להרחבה: + +~~~ +[php] +$this-»widget('zii.widgets.grid.CGridView', array( + 'dataProvider'=»$dataProvider, +)); +~~~ + + +רכיב באפליקציה +--------------------- + +בכדי להשתמש [ברכיב אפליקציה](/doc/guide/basics.application#application-component), אנו קודם צריכים לשנות את [הגדרות האפליקציה](/doc/guide/basics.application#application-configuration) על ידי הוספת רשומה (אלמנט) חדשה למאפיין של `components`, כפי שמוצג בדוגמא הבאה: + +~~~ +[php] +return array( + // 'preload'=»array('xyz',...), + 'components'=»array( + 'xyz'=»array( + 'class'=»'ext.xyz.XyzClass', + 'property1'=»'value1', + 'property2'=»'value2', + ), + // הגדרות רכיבים נוספים + ), +); +~~~ + +לאחר מכן, אנו יכולים לגשת לרכיב בכל מקום על ידי `Yii::app()-»xyz`. הרכיב יווצר בצורה עצלה (זאת אומרת יווצר כשהוא נקרא בפעם הראשונה), אלה אם כן אנו נרשום את שמו במאפיין של `preload` בהגדרות האפליקציה. + +התנהלות/התנהגות +-------- + +[התנהלות/התנהגות](/doc/guide/basics.component#component-behavior) ניתנת לשימוש בכל מיני רכיבים. השימוש בהם כרוך בשני שלבים. בשלב הראשון, ההתנהלות מצורפת לרכיב כלשהו. בשלב השני, מתודת התנהלות נקראת על ידי הרכיב אליו היא צורפה. לדוגמא: + +~~~ +[php] +// הפרמטר הראשון הוא יחודי ומשמש לצורך זיהוי ההתנהלות +$component-»attachBehavior($name,$behavior); +// המתודה test נמצאת במחלקה של $behavior +$component-»test(); +~~~ + +בדרך כלל, התנהלות מצורפת לרכיב דרך הגדרות ולא על ידי קריאה למתודה `attachBehavior`. לדוגמא, בכדי לצרף התנהלות [רכיב](/doc/guide/basics.application#application-component) באפליקציה, אנו נשתמש [בהגדרות](/doc/guide/basics.application#application-configuration) האפליקציה הבאות: + +~~~ +[php] +return array( + 'components'=»array( + 'db'=»array( + 'class'=»'CDbConnection', + 'behaviors'=»array( + 'xyz'=»array( + 'class'=»'ext.xyz.XyzBehavior', + 'property1'=»'value1', + 'property2'=»'value2', + ), + ), + ), + //.... + ), +); +~~~ + +הקוד למעלה, מצרף את ההתנהלות `xyz` לרכיב האפליקציה `db`. אנו עושים זאת מאחר והמחלקה [CApplicationComponent] מגדירה מאפיין בשם `behaviors`. על ידי הגדרת מאפיין זה עם רשימה של הגדרות התנהלות, הרכיב יצרף את ההתנהלויות הללו בזמן האתחול שלו. + +עבור המחלקות [CController], [CFormModel] ו [CActiveRecord] שבדרך כלל דורשות הרחבה, צירוף התנהלויות נעשית על ידי דריסה של המתודה `()behaviors`. המחלקה תצרף אוטומטית את ההתנהלויות המוגדרות במתודה זו בזמן אתחול. לדוגמא, + +~~~ +[php] +public function behaviors() +{ + return array( + 'xyz'=»array( + 'class'=»'ext.xyz.XyzBehavior', + 'property1'=»'value1', + 'property2'=»'value2', + ), + ); +} +~~~ + + +וידג'ט +------ + +שימוש [בוידג'טים](/doc/guide/basics.view#widget) בדרך כלל נעשה בקבצי [תצוגה](/doc/guide/basics.view). נניח ומחלקת הוידג'ט `XyzClass` שייכת להרחבה `xyz`, אנו יכולים להשתמש בו בתוך קובץ תצוגה בצורה הבאה, + +~~~ +[php] +// וידג'ט שאינו דורש תוכן +«?php $this-»widget('ext.xyz.XyzClass', array( + 'property1'=»'value1', + 'property2'=»'value2')); ?» + +// וידג'ט שדורש תוכן +«?php $this-»beginWidget('ext.xyz.XyzClass', array( + 'property1'=»'value1', + 'property2'=»'value2')); ?» + +...תוכן הוידג'ט.... + +«?php $this-»endWidget(); ?» +~~~ + +פעולה +------ + +שימוש [בפעולות](/doc/guide/basics.controller#action) נעשה על ידי [הקונטרולרים](/doc/guide/basics.controller) בכדי לענות על בקשות משתמש ספציפיות. נניח ומחלקת הפעולה `XyzClass` השייכת להרחבה בשם `xyz`, אנו יכולים להשתמש בה על ידי דריסה של המתודה [CController::actions] במחלקה של הקונטרולר: + +~~~ +[php] +class TestController extends CController +{ + public function actions() + { + return array( + 'xyz'=»array( + 'class'=»'ext.xyz.XyzClass', + 'property1'=»'value1', + 'property2'=»'value2', + ), + // פעולות נוספות + ); + } +} +~~~ + +לאחר מכן, ניתן לגשת לפעולה על ידי [הניתוב](/doc/guide/basics.controller#route) `test/xyz`. + +פילטר +------ + +שימוש [פילטרים](/doc/guide/basics.controller#filter) נעשה גם כן על ידי [הקונטרולרים](/doc/guide/basics.controller). המטרה העיקרית שלהם היא הרצתם לפני ואחרי [פעולות](/doc/guide/basics.controller#action) מסויימות בכדי לבצע בדיקות או שינויים אחרים. נניח ומחלקת הפילטר בשם `XyzClass` השייכת להרחבה בשם `xzy`, אנו יכולים להשתמש בו על ידי דריסה של המתודה [CController::filters] במחלקת הקונטרולר: + +~~~ +[php] +class TestController extends CController +{ + public function filters() + { + return array( + array( + 'ext.xyz.XyzClass', + 'property1'=»'value1', + 'property2'=»'value2', + ), + // פילטרים נוספים + ); + } +} +~~~ + +בדוגמא למעלה, אנו יכולים להשתמש באופרטורים +, - באלמנט הראשון במערך בכדי לצרף את הפילטר למספר פעולות בלבד. למידע נוסף יש לקרוא את הדוקומנטציה אודות [CController]. + +קונטרולר +---------- + +[קונטרולר](/doc/guide/basics.controller) מספק סט של פעולות שניתנות לבקשה על ידי המשתמשים. בכדי להשתמש בהרחבה לקונטרולר, אנו צריכים להגדיר את המאפיין [CWebApplication::controllerMap] בקובץ [הגדרות האפליקציה](/doc/guide/basics.application#application-configuration): + +~~~ +[php] +return array( + 'controllerMap'=»array( + 'xyz'=»array( + 'class'=»'ext.xyz.XyzClass', + 'property1'=»'value1', + 'property2'=»'value2', + ), + // קונטרולרים נוספים + ), +); +~~~ + +לאחר מכן, הפעולה `a` ניתנת לגישה על ידי [ניתוב](/doc/guide/basics.controller#route) `xyz/a`. + +ולידטור (אימות נתונים) +--------- + +ולידטור בדרך כלל משומש בתוך מחלקת [מודל](/doc/guide/basics.model) (אחת שיורשת מהמחלקה [CFormModel] או [CActiveRecord]). +נניח וישנה מחלקת ולידטור בשם `XyzClass` השייכת להרחבה בשם `xyz`, אנו יכולים להשתמש בה על ידי דריסה של המתודה [CModel::rules] במחלקת המודל שלנו: + +~~~ +[php] +class MyModel extends CActiveRecord // or CFormModel +{ + public function rules() + { + return array( + array( + 'attr1, attr2', + 'ext.xyz.XyzClass', + 'property1'=»'value1', + 'property2'=»'value2', + ), + // חוקי אימות נתונים נוספים + ); + } +} +~~~ + +מסוף פקודות +--------------- + +הרחבות [מסוף הפקודות](/doc/guide/topics.console) בדרך כלל משפרת את הכלי `yiic` בפקודה נוספת. נניח שיש לנו מחלקה לפקודה `XyzClass` השייכת להרחבה `xyz`, אנו יכולים להשתמש בה על ידי הגדרת ההגדרות למסוף הפקודות באפליקציה: + +~~~ +[php] +return array( + 'commandMap'=»array( + 'xyz'=»array( + 'class'=»'ext.xyz.XyzClass', + 'property1'=»'value1', + 'property2'=»'value2', + ), + // פקודות נוספות + ), +); +~~~ + +לאחר מכן, אנו יכולים להשתמש בכלי `yiic` המכיל פקודה חדשה בשם `xyz`. + +» Note|הערה: קובץ הגדרות של מסוף הפקודות הוא שונה מקובץ ההגדרות של אפליקצית ווב. במידה ואפליקציה נוצרה בעזרת פקודת `yiic webapp`, קובץ ההגדרות של מסוף הפקודות `protected/yiic` הינו `protected/config/console.php`, בזמן שקובץ ההגדרות לאפליקצית ווב הינו `protected/config/main.php`. + + +מודול +------ + +אנא קרא את החלק אודות [מודולים](/doc/guide/basics.module#using-module) וכיצד לעבוד עמם. + +רכיב כללי +----------------- + +בכדי להשתמש [רכיב](/doc/guide/basics.component) כללי, אנו קודם צריכים להוסיף את קובץ המחלקה שלו על ידי שימוש ב + +~~~ +Yii::import('ext.xyz.XyzClass'); +~~~ + +לאחר מכן, אנו יכולים ליצור אובייקט של המחלקה, להגדיר את המאפיינים שלו, ולקרוא למתודות אשר נמצאות בו. כמו כן אנו יכולים להרחיב אותו כדי ליצור תת מחלקה חדשה. + + «div class="revision"»$Id: extension.use.txt 1780 2010-02-01 20:32:50Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/form.action.txt b/docs/guide/he/form.action.txt index be3cc935d..925d5411a 100644 --- a/docs/guide/he/form.action.txt +++ b/docs/guide/he/form.action.txt @@ -1,69 +1,69 @@ -יצירת פעולה -=============== - -לאחר שיש ברשותנו מודל, אנו יכולים להתחיל לכתוב לוגיקה שיכולה לתפעל את המודל. אנו כותבים את הלוגיקה הזו בתוך פעולה בקונטרולר. עבור דוגמאת טופס ההתחברות, הקוד הבא הכרחי: - -~~~ -[php] -public function actionLogin() -{ - $model=new LoginForm; - if(isset($_POST['LoginForm'])) - { - // אוסף את הקלט של המשתמש - $model-»attributes=$_POST['LoginForm']; - // מאמת את הקלט של המשתמש במידה והכל עבר בהצלחה המשתמש יועבר לעמוד הקודם - if($model-»validate()) - $this-»redirect(Yii::app()-»user-»returnUrl); - } - // הצג את טופס ההתחברות - $this-»render('login',array('model'=»$model)); -} -~~~ - -בקוד למעלה, אנו קודם יוצרים אובייקט של המודל `LoginForm`; במידה והבקשה הינה בקשת POST (שבעצם אומר שהטופס נשלח), אנו מאכלסים את `model$` עם הנתונים שהגיעו דרך - -~~~ - -$_POST['LoginForm'] - -~~~ - -לאחר מכן אנו מאמתים את הנתונים ובמידה והכל עבר בהצלחה, מעבירים את המשתמש לעמוד הקודם בו הוא היה צריך להתחבר. במידה וההתחברות נכשלת, או המשתמש רואה את הטופס בפעם הראשונה, אנו מציגים לו את קובץ התצוגה `login` שתוכנו יתואר בחלק הבא. - -» Tip|טיפ: בפעולת ה `login`, אנו משתמשים ב `Yii::app()-»user-»returnUrl` בכדי לקבל את הקישור של העמוד הקודם בו המשתמש היה צריך להתחבר. הרכיב `Yii::app()-»user` הוא מסוג [CWebUser] (או מחלקות היורשות ממנו) המייצג מידע אודות המשתמש (לדוגמא שם משתמש, סטטוס). למידע נוסף יש לקרוא אודות [אימות משתמשים וניהול גישות](/doc/guide/topics.auth). - -בו נתייחס לביטוי הבא המופיע בפעולת ה `login` למעלה: - -~~~ -[php] -$model-»attributes=$_POST['LoginForm']; -~~~ - -כפי שכבר הסברנו [באבטחת הצבת מאפיינים](/doc/guide/form.model#securing-attribute-assignments), שורת קוד זו מאכלסת את המודל עם הקלט שהגיע מהמשתמש. המאפיין `attributes` מוגדר על ידי [CModel] המצפה למערך של מפתחות וערכים, ומגדירה כל אחד מהערכים למאפיין המתאים לו במודל. לכן - -~~~ -$_POST['LoginForm'] -~~~ - -מספק לנו מערך זה, הקוד למעלה יכול להיות זהה לקוד הארוך הבא (בהנחה שכל מאפיין נוכח במערך): - -~~~ -[php] -$model-»username=$_POST['LoginForm']['username']; -$model-»password=$_POST['LoginForm']['password']; -$model-»rememberMe=$_POST['LoginForm']['rememberMe']; -~~~ - -» Note|הערה: בכדי לאפשר ל - - -» ~~~ -» $_POST['LoginForm'] -» ~~~ - -» לספק לנו מערך במקום סטרינג, אנו דבקים במוסכמות של מתן שמות מתאימים למאפיינים של מודל בטופס. במיוחד, עבור שדה טקסט בשם המאופיין בעזרת המאפיין `a` של המחלקה `C`, אנו נקרא לו `C[a]`. לדוגמא, אנו נשתמש ב `LoginForm[username]` בכדי לתת שם לשדה טקסט המקושר למאפיין `username` במודל. - -כל מה שנותר לעשות הוא ליצור את קובץ התצוגה `login` שאמור להכיל טופס HTML עם שדות הקלט הדרושים. - +יצירת פעולה +=============== + +לאחר שיש ברשותנו מודל, אנו יכולים להתחיל לכתוב לוגיקה שיכולה לתפעל את המודל. אנו כותבים את הלוגיקה הזו בתוך פעולה בקונטרולר. עבור דוגמאת טופס ההתחברות, הקוד הבא הכרחי: + +~~~ +[php] +public function actionLogin() +{ + $model=new LoginForm; + if(isset($_POST['LoginForm'])) + { + // אוסף את הקלט של המשתמש + $model-»attributes=$_POST['LoginForm']; + // מאמת את הקלט של המשתמש במידה והכל עבר בהצלחה המשתמש יועבר לעמוד הקודם + if($model-»validate()) + $this-»redirect(Yii::app()-»user-»returnUrl); + } + // הצג את טופס ההתחברות + $this-»render('login',array('model'=»$model)); +} +~~~ + +בקוד למעלה, אנו קודם יוצרים אובייקט של המודל `LoginForm`; במידה והבקשה הינה בקשת POST (שבעצם אומר שהטופס נשלח), אנו מאכלסים את `model$` עם הנתונים שהגיעו דרך + +~~~ + +$_POST['LoginForm'] + +~~~ + +לאחר מכן אנו מאמתים את הנתונים ובמידה והכל עבר בהצלחה, מעבירים את המשתמש לעמוד הקודם בו הוא היה צריך להתחבר. במידה וההתחברות נכשלת, או המשתמש רואה את הטופס בפעם הראשונה, אנו מציגים לו את קובץ התצוגה `login` שתוכנו יתואר בחלק הבא. + +» Tip|טיפ: בפעולת ה `login`, אנו משתמשים ב `Yii::app()-»user-»returnUrl` בכדי לקבל את הקישור של העמוד הקודם בו המשתמש היה צריך להתחבר. הרכיב `Yii::app()-»user` הוא מסוג [CWebUser] (או מחלקות היורשות ממנו) המייצג מידע אודות המשתמש (לדוגמא שם משתמש, סטטוס). למידע נוסף יש לקרוא אודות [אימות משתמשים וניהול גישות](/doc/guide/topics.auth). + +בו נתייחס לביטוי הבא המופיע בפעולת ה `login` למעלה: + +~~~ +[php] +$model-»attributes=$_POST['LoginForm']; +~~~ + +כפי שכבר הסברנו [באבטחת הצבת מאפיינים](/doc/guide/form.model#securing-attribute-assignments), שורת קוד זו מאכלסת את המודל עם הקלט שהגיע מהמשתמש. המאפיין `attributes` מוגדר על ידי [CModel] המצפה למערך של מפתחות וערכים, ומגדירה כל אחד מהערכים למאפיין המתאים לו במודל. לכן + +~~~ +$_POST['LoginForm'] +~~~ + +מספק לנו מערך זה, הקוד למעלה יכול להיות זהה לקוד הארוך הבא (בהנחה שכל מאפיין נוכח במערך): + +~~~ +[php] +$model-»username=$_POST['LoginForm']['username']; +$model-»password=$_POST['LoginForm']['password']; +$model-»rememberMe=$_POST['LoginForm']['rememberMe']; +~~~ + +» Note|הערה: בכדי לאפשר ל + + +» ~~~ +» $_POST['LoginForm'] +» ~~~ + +» לספק לנו מערך במקום סטרינג, אנו דבקים במוסכמות של מתן שמות מתאימים למאפיינים של מודל בטופס. במיוחד, עבור שדה טקסט בשם המאופיין בעזרת המאפיין `a` של המחלקה `C`, אנו נקרא לו `C[a]`. לדוגמא, אנו נשתמש ב `LoginForm[username]` בכדי לתת שם לשדה טקסט המקושר למאפיין `username` במודל. + +כל מה שנותר לעשות הוא ליצור את קובץ התצוגה `login` שאמור להכיל טופס HTML עם שדות הקלט הדרושים. + «div class="revision"»$Id: form.action.txt 1837 2010-02-24 22:49:51Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/form.builder.txt b/docs/guide/he/form.builder.txt index 3fb0feefd..0d3677bee 100644 --- a/docs/guide/he/form.builder.txt +++ b/docs/guide/he/form.builder.txt @@ -1,389 +1,389 @@ -שימוש במערכת יצירת הטפסים -================== - -בעת יצירת טפסי HTML, אנו בדרך כלל רואים שאנו כותבים הרבה קוד תצוגה שחוזר על עצמו שקשה להשתמש בו שוב פעם בפרוייקט נוסף. לדוגמא, לכל שדה טקסט, אנו צריכים לשייך אותו עם תוית טקסט ולהציג שגיאות אימות נתונים במידה והם קיימים. -בכדי לשפר את השימוש החוזר בקודים אלו, אנו יכולים להשתמש במערכת יצירת הטפסים הקיימת במערכת ה Yii מגרסא 1.1.0. - -תפיסה בסיסית --------------- - -מערכת יצירת הטפסים משתמשת באובייקט של [CForm] כדי לייצג את המפרט הדרוש לייצוג טופס HTML, הכולל אילו מודלים קשורים לטופס, אילו שדות נתונים נמצאים בטופס, וכיצד להציג את כל הטופס. -מפתחים בעיקר צריכים ליצור ולהגדיר את האובייקט [CForm], לאחר מכן לקרוא למתודת התצוגה שלו בכדי להציג את הטופס. - -מפרט שדות הטופס מסודרים במונחים של אלמנטים של אובייקט הטופס בהיררכיה. בראש ההיררכיה, נמצא האובייקט [CForm]. האובייקט הראשי מתחזק את התתים שבו בשני אוספים שונים: [CForm::buttons] ו [CForm::elements]. הראשון מכיל את האלמנטים המיוצגים ככפתורים (כמו כפתור שליחה, כפתור איפוס), בזמן שהשני מכיל את שדות הזנת הטקסט, טקסט סטטי, ותתי טפסים. תת-טופס הינו אובייקט של [CForm] הנמצא באוסף של האלמנטים ([CForm::elements]) בטופס אחר. הוא יכול להכיל מודל משלו, [CForm::buttons] ו [CForm::elements]. - -כשמשתמשים שולחים טופס, הנתונים שהוזנו בכל השדות בכל ההיררכיה של הטופס נשלחים, כולל את השדות הנמצאים בתתי-טפסים. [CForm] מספק מתודות נוחות לשימוש שבעזרתן ניתן לצרף נתונים מסויימים למאפייני המודלים המתאימים ולבצע אימות נתונים על גביהם. - -יצירת טופס פשוט ----------------------- - -בדוגמא הבאה, אנו מציגים כיצד להשתמש במערכת יצירת הטפסים כדי ליצור טופס התחברות. - -קודם כל, אנו כותבים את הפעולה של ההתחברות: - -~~~ -[php] -public function actionLogin() -{ - $model = new LoginForm; - $form = new CForm('application.views.site.loginForm', $model); - if($form-»submitted('login') && $form-»validate()) - $this-»redirect(array('site/index')); - else - $this-»render('login', array('form'=»$form)); -} -~~~ - -בקוד למעלה, אנו יוצרים אובייקט של [CForm] תוך כדי שימוש במפרט המצביע לנתיב `application.views.site.loginForm` (הסבר לגבי זה בהמשך). -האובייקט [CForm] מקושר עם המודל `LoginForm` כפי שתואר [ביצירת מודל](/doc/guide/form.model). - -כפי שמוצג בקוד, במידה והטופס נשלח וכל השדות אומתו ללא שגיאות, אנו נעביר את המשתמש לעמוד `site/index`. אחרת, אנו נציג שוב פעם את קובץ התצוגה `login` ביחד עם הטופס. - -הנתיב `application.views.site.loginForm` מתייחס לקובץ PHP הנמצא תחת התיקיה `protected/views/site/loginForm.php`. הקובץ צריך להחזיר מערך המכיל את הגדרות הנחוצות עבור [CForm], כפי שמוצג בקוד הבא: - -~~~ -[php] -return array( - 'title'=»'Please provide your login credential', - - 'elements'=»array( - 'username'=»array( - 'type'=»'text', - 'maxlength'=»32, - ), - 'password'=»array( - 'type'=»'password', - 'maxlength'=»32, - ), - 'rememberMe'=»array( - 'type'=»'checkbox', - ) - ), - - 'buttons'=»array( - 'login'=»array( - 'type'=»'submit', - 'label'=»'Login', - ), - ), -); -~~~ - -ההגדרות הינם מערך המכיל מפתחות וערכים המוגדרים למאפיינים של המחלקה [CForm]. המאפיינים החשובים ביותר להגדרה, כפי שכבר הזכרנו, [CForm::elements] ו [CForm::buttons]. כל אחד מהם מקבל מערך המגדיר רשימה של אלמנטים בטופס. אנו נסביר בהרחבה כיצד יש להגדיר אלמנטים של טופס בחלק הבא. - -לבסוף, אנו כותבים את קובץ התצוגה `login`, שיכול להיות פשוט כפי שמוצג בקוד הבא, - -~~~ -[php] -«h1»Login«/h1» - -«div class="form"» -«?php echo $form; ?» -«/div» -~~~ - -» Tip|טיפ: הקוד למעלה, `;echo $render` הוא זהה לקוד `;()echo $form-»render`. וזאת מכיוון שהאובייקט [CForm] מיישם את המתודה `toString__` אשר קוראת למתודה `()render` ומחזירה את התצוגה כסטרינג המייצג את אובייקט הטופס. - - -הגדרת אלמנטים בטופס ------------------------- - -בשימוש במערכת יצירת הטפסים, רוב העבודה שלנו משתנה מכתיבת קוד בקבצי התצוגה להגדרת אלמנטים בטופס. בחלק זה, אנו נתאר כיצד להגדיר את האלמנטים מאפיין [CForm::elements]. אנו לא נתאר אודות [CForm::buttons] מאחר והגדרותיו הם כמעט זהות ל [CForm::elements]. - -המאפיין [CForm::elements] מקבל מערך. כל אלמנט במערך מייצג אלמנט אחד בטופס שיכול להיות שדה נתונים, טקסט סטטי, או תת-טופס. - -### הגדרת אלמנטים כשדות נתונים - -אלמנט שדה נתונים בעיקר מכיל תוית, שדה נתונים, שדה עזרה ותצוגת שגיאה. -הוא חייב להיות מקושר למאפיין במודל מסויים. המפרט של אלמנט שדה נתונים מיוצג כאובייקט של [CFormInputElement]. הקוד הבא במערך [CForm::elements] מגדיר אלמנט שדה נתונים אחד: - -~~~ -[php] -'username'=»array( - 'type'=»'text', - 'maxlength'=»32, -), -~~~ - -קוד זה מציין שהמאפיין של המודל בשם `username`, ושדה הנתונים הוא מסוג `text` ושאורכו - `maxlength` הוא 32. אנו יכולים לציין אפשרויות נוספות במערך למעלה כל עוד שהם מאפיינים שניתנים לכתיבה במחלקה [CFormInputElement]. לדוגמא, אנו יכולים להגדיר את האפשרות [hint|CFormInputElement::hint] המציגה טקסט עזרה עבור שדה זה, או שניתן להגדיר את האפשרות [items|CFormInputElement::items] במידה והשדה הוא תיבת בחירה, תיבת בחירה מרובה, רשימת כפתורי בחירה, רשימת כפתורי רדיו. - -האפשרות [type|CFormInputElement::type] דורשת יותר תשומת לב מהשאר. אפשרות זו מגדירה את סוג שדה הנתונים לתצוגה. לדוגמא, הסוג `text` מעיד על כך שיש צורך להציג שדה טקסט רגיל; הסוג `password` אומר שיש צורך להציג שדה סיסמא. [CFormInputElement] מזהה את הסוגים המובנים הבאים: - - - - text: שדה טקסט - - hidden: שדה מוסתר - - password: שדה סיסמא - - textarea: תיבת טקסט - - file: שדה קובץ - - radio: שדה כפתור רדיו - - checkbox: שדה כפתור בחירה - - listbox: שדה בחירה מרובה - - dropdownlist: שדה תיבת בחירה - - checkboxlist: שדה רשימת כפתורי בחירה - - radiolist: שדה רשימת כפתורי רדיו - - -מלבד הסוגים המובנים, האפשרות [type|CFormInputElement::type] יכולה לקבל שם של מחלקת וידג'ט או נתיב מקוצר עד אל הוידג'ט. המחלקה של הוידג'ט צריכה לירוש מהמחלקה [CInputWidget]. בעת התצוגה של האלמנט, אובייקט של הוידג'ט של השדה הנוכחי יווצר ויוצג. הוידג'ט יוגדר בהתבסס על המפרט הקיים באלמנט. - -### הגדרת טקסט סטטי - -בהרבה מקרים, מלבד שדות נתונים טופס מכיל קוד HTML שנועד לעיצוב הטופס. לדוגמא, יהיה צורך בקו אופקי בכדי להפריד בין חלקים בטופס; יש צורך בתמונה במקום מסויים בטופס בכדי לשפר את המראה החיצוני של הטופס. אנו יכולים להגדיר את קוד ה HTML הזה כטקסט סטטי באוסף האלמנטים של [CForm::elements]. בכדי לבצע זאת, אנו מגדירים אלמנט בתוך המאפיין [CForm::elements] בתור טקסט סטטי במיקום בו אנו רוצים להציג אותו בטופס. לדוגמא, - -~~~ -[php] -return array( - 'elements'=»array( - ...... - 'password'=»array( - 'type'=»'password', - 'maxlength'=»32, - ), - - '«hr /»', - - 'rememberMe'=»array( - 'type'=»'checkbox', - ) - ), - ...... -); -~~~ - -בקוד המוצג למעלה, אנו מוסיפים קו אופקי בין השדה של הסיסמא - `password` לבין השדה של זכור אותי - `rememberMe`. - -השימוש בטקסט סטטי הוא כשהטקסט המיקום שלו אינם רגילים. אם לכל שדה בטופס יהיה צורך בעיצוב באופן פרטני, יהיה צורך בלשנות את אופן התצוגה של הטופס, הסבר לפעולה זו יופיע בהמשך. - -### הגדרת תתי-טפסים - -תתי-טפסים נועדו בכדי לחלק טופס ארוך לכמה טפסים חלקים הקשורים אחד לשני לוגית. לדוגמא, אנו יכולים לחלק את טופס ההרשמה לשני תת-טפסים: פרטי התחברות ופרטי פרופיל. -כל תת-טופס חייב/לא חייב להיות משוייך למודל. בדוגמא של טופס ההרשמה, אם אנו שומרים את פרטי ההתחברות של המשתמש ואת פרטי הפרופיל של המשתמש בשני טבלאות שונות (ולכן שני מודלים נפרדים), לכן כל תת טופס יהיה משוייך למודל שלו. במידה ואנו שומרים את כל הפרטים באותה הטבלה, לכן לאף אחד מתתי-הטפסים לא יהיה מודל מאחר והם משותפים עם המודל של הטופס הראשי. - -תת-טופס מיוצג כאובייקט של [CForm]. בכדי להגדיר תת-טופס, אנו צריכים להגדיר את המאפיין [CForm::elements] עם אלמנט שסוגו הוא `form`: - -~~~ -[php] -return array( - 'elements'=»array( - ...... - 'user'=»array( - 'type'=»'form', - 'title'=»'Login Credential', - 'elements'=»array( - 'username'=»array( - 'type'=»'text', - ), - 'password'=»array( - 'type'=»'password', - ), - 'email'=»array( - 'type'=»'text', - ), - ), - ), - - 'profile'=»array( - 'type'=»'form', - ...... - ), - ...... - ), - ...... -); -~~~ - -כמו בהגדרת הטופס הראשי, אנו בעיקר צריכים להגדיר את המאפיין [CForm::elements] של תתי-הטפסים. -במידה ותת הטופס צריך להיות משוייך עם מודל, ניתן להגדיר את המאפיין [CForm::model] שלו גם כן. - -לפעמים, אנו נרצה לייצג טופס המשתמש במחלקה אחרת ולא ברירת המחדל [CForm]. לדוגמא, כפי שנציג בעוד רגע בחלק זה, אנו יכולים להרחיב את המחלקה [CForm] בכדי להתאים אישית את אופן הצגת הטופס. -על ידי הגדרת סוג האלמנט כ `form`, תת הטופס אוטומטית מיוצג כאובייקט שהמחלקה שלו היא זהה למחלקה של טופס האב. אם נגדיר את סוג האלמנט למשהו כמו `XyzForm` (סטרינג המסתיים ב `Form`), אז תת-הטופס יהיה מיוצג על ידי האובייקט של המחלקה `XyzForm`. - -גישה לאלמנטים בטופס ------------------------ - -גישה לאלמנטים בטופס הינה פשוטה כגישה לאלמנטים מערך. המאפיין [CForm::elements] מחזיר אובייקט של [CFormElementCollection], היורש מהמחלקה [CMap] ומאפשר גישה לאלמנטים שבו כמערך רגיל. לדוגמא, בכדי לגשת לאלמנט `username` בדוגמא של טופס ההתחברות שהוצג, אנו יכולים להשתמש בקוד הבא: - -~~~ -[php] -$username = $form-»elements['username']; -~~~ - -ובכדי לגשת לאלמנט של `email` בדוגמא של טופס ההרשמה, אנו יכולים להשתמש ב - -~~~ -[php] -$email = $form-»elements['user']-»elements['email']; -~~~ - -מאחר ו [CForm] מיישם את הממשק של `array access` עבור המאפיין [CForm::elements], ניתן לפשט את הקוד למעלה בצורה הבאה: - -~~~ -[php] -$username = $form['username']; -$email = $form['user']['email']; -~~~ - - -יצירת שרשור טפסים ----------------------- - -כבר הצגנו את התתי-טפסים. אנו קוראים לטופס עם תתי-טפסים טופס משורשר. בחלק זה, אנו משתמשים בטופס של הרשמת משתמשים בכדי להציג כיצד ליצור טופס משורשר המקושר לכמה מודלים. -אנו מניחים שפרטי המשתמשים מאוחסנים במודל `User`, בזמן שמידע אודות פרופיל המשתמש נמצא תחת המודל `Profile`. - -אנו קודם יוצרים את פעולת ההרשמה `register` בצורה הבאה: - -~~~ -[php] -public function actionRegister() -{ - $form = new CForm('application.views.user.registerForm'); - $form['user']-»model = new User; - $form['profile']-»model = new Profile; - if($form-»submitted('register') && $form-»validate()) - { - $user = $form['user']-»model; - $profile = $form['profile']-»model; - if($user-»save(false)) - { - $profile-»userID = $user-»id; - $profile-»save(false); - $this-»redirect(array('site/index')); - } - } - - $this-»render('register', array('form'=»$form)); -} -~~~ - -בקוד למעלה, אנו יוצרים את הטופס בעזרת שימוש בהגדרות הנמצאות ב `application.views.user.registerForm`. -לאחר השליחה של הטופס ואימות הנתונים עבר בהצלחה, אנו מנסים לשמור את המודלים של `User` ו `Profile`. -אנו שולפים את המודלים של המשתמש והפרופיל על ידי גישה למאפיין `model` תחת האובייקט של תת הטופס. - -מאחר ואימות הנתונים נעשה כבר, אנו קוראים ל `$user-»save(false)` בכדי לדלג על האימות. אנו עושים את אותו הדבר עם המודל של הפרופיל. - - -לאחר מכן, אנו כותבים את קובץ הגדרות הטופס תחת הנתיב `protected/views/user/registerForm.php`: - -~~~ -[php] -return array( - 'elements'=»array( - 'user'=»array( - 'type'=»'form', - 'title'=»'Login information', - 'elements'=»array( - 'username'=»array( - 'type'=»'text', - ), - 'password'=»array( - 'type'=»'password', - ), - 'email'=»array( - 'type'=»'text', - ) - ), - ), - - 'profile'=»array( - 'type'=»'form', - 'title'=»'Profile information', - 'elements'=»array( - 'firstName'=»array( - 'type'=»'text', - ), - 'lastName'=»array( - 'type'=»'text', - ), - ), - ), - ), - - 'buttons'=»array( - 'register'=»array( - 'type'=»'submit', - 'label'=»'Register', - ), - ), -); -~~~ - -בקוד המוצג למעלה, בעת הגדרת כל תת-טופס, אנו מגדירים את המאפיין [CForm::title] השייך לו. -לוגיקת התצוגה ברירת המחדל של הטופס תעטוף כל תת-טופס בתוך מעטפת אשר תשתמש במאפיין זה בכדי להציג את הכותרת של הטופס. - -לבסוף, אנו כותבים את קובץ התצוגה הפשוט של ההרשמה `register`: - -~~~ -[php] -«h1»הרשמה«/h1» - -«div class="form"» -«?php echo $form; ?» -«/div» -~~~ - - -התאמה אישית של תצוגה הטופס ------------------------- - -היתרון בשימוש של מערכת יצירת הטפסים היא ההפרדה מהלוגיקה (הגדרות הטופס נמצאות בקובץ נפרד) והתצוגה (מתודת [CForm::render]). כתוצאה מכך, אנו יכולים להתאים את התצוגה של הטופס באופן אישי על ידי דריסה של המתודה [CForm::render] או לספק קובץ תצוגה חלקי אשר מציג את הטופס. בשני האפשרויות ניתן להשאיר את הגדרות הטופס ללא שינוי ולהשתמש באותו הקוד בצורה קלה. - -כשדורסים את המתודה [CForm::render], יש לרוץ על גבי האלמנטים של [CForm::elements] ו [CForm::buttons] ולקרוא למתודה [CFormElement::render] עבור כל כל אלמנט. לדוגמא, - -~~~ -[php] -class MyForm extends CForm -{ - public function render() - { - $output = $this-»renderBegin(); - - foreach($this-»getElements() as $element) - $output .= $element-»render(); - - $output .= $this-»renderEnd(); - - return $output; - } -} -~~~ - -כמו כן אנו יכולים ליצור קובץ תצוגה `form_` בכדי להציג את הטופס: - -~~~ -[php] -«?php -echo $form-»renderBegin(); - -foreach($form-»getElements() as $element) - echo $element-»render(); - -echo $form-»renderEnd(); -~~~ - -בכדי להשתמש בקובץ התצוגה, ניתן פשוט לקרוא: - -~~~ -[php] -«div class="form"» -$this-»renderPartial('_form', array('form'=»$form)); -«/div» -~~~ - -במידה ותצוגה כללית לא נראית טוב בטופס מסויים (לדוגמא, הטופס צריך דקורציה מיוחדת ולא רגילה עבור אלמנטים מסויימים), אנו יכולים לבצע את הפעולות הבאות בקובץ תצוגה: - -~~~ -[php] -אלמנטים של UI מורכבים כאן - -«?php echo $form['username']; ?» - -אלמנטים של UI מורכבים כאן - -«?php echo $form['password']; ?» - -אלמנטים של UI מורכבים כאן -~~~ - -בשיטה האחרונה,זה נראה שמערכת יצירת הטפסים לא מועילה לנו כל כך, אנו עדיין צריכים לכתוב כמות קוד דומה לטופס שאנו משתמשים בעת שימוש בקוד רגיל. למרות, שהטופס מוגדר בעזרת קובץ הגדרות בנפרד אשר עוזר למפתחים להתמקד יותר בלוגיקה. - - +שימוש במערכת יצירת הטפסים +================== + +בעת יצירת טפסי HTML, אנו בדרך כלל רואים שאנו כותבים הרבה קוד תצוגה שחוזר על עצמו שקשה להשתמש בו שוב פעם בפרוייקט נוסף. לדוגמא, לכל שדה טקסט, אנו צריכים לשייך אותו עם תוית טקסט ולהציג שגיאות אימות נתונים במידה והם קיימים. +בכדי לשפר את השימוש החוזר בקודים אלו, אנו יכולים להשתמש במערכת יצירת הטפסים הקיימת במערכת ה Yii מגרסא 1.1.0. + +תפיסה בסיסית +-------------- + +מערכת יצירת הטפסים משתמשת באובייקט של [CForm] כדי לייצג את המפרט הדרוש לייצוג טופס HTML, הכולל אילו מודלים קשורים לטופס, אילו שדות נתונים נמצאים בטופס, וכיצד להציג את כל הטופס. +מפתחים בעיקר צריכים ליצור ולהגדיר את האובייקט [CForm], לאחר מכן לקרוא למתודת התצוגה שלו בכדי להציג את הטופס. + +מפרט שדות הטופס מסודרים במונחים של אלמנטים של אובייקט הטופס בהיררכיה. בראש ההיררכיה, נמצא האובייקט [CForm]. האובייקט הראשי מתחזק את התתים שבו בשני אוספים שונים: [CForm::buttons] ו [CForm::elements]. הראשון מכיל את האלמנטים המיוצגים ככפתורים (כמו כפתור שליחה, כפתור איפוס), בזמן שהשני מכיל את שדות הזנת הטקסט, טקסט סטטי, ותתי טפסים. תת-טופס הינו אובייקט של [CForm] הנמצא באוסף של האלמנטים ([CForm::elements]) בטופס אחר. הוא יכול להכיל מודל משלו, [CForm::buttons] ו [CForm::elements]. + +כשמשתמשים שולחים טופס, הנתונים שהוזנו בכל השדות בכל ההיררכיה של הטופס נשלחים, כולל את השדות הנמצאים בתתי-טפסים. [CForm] מספק מתודות נוחות לשימוש שבעזרתן ניתן לצרף נתונים מסויימים למאפייני המודלים המתאימים ולבצע אימות נתונים על גביהם. + +יצירת טופס פשוט +---------------------- + +בדוגמא הבאה, אנו מציגים כיצד להשתמש במערכת יצירת הטפסים כדי ליצור טופס התחברות. + +קודם כל, אנו כותבים את הפעולה של ההתחברות: + +~~~ +[php] +public function actionLogin() +{ + $model = new LoginForm; + $form = new CForm('application.views.site.loginForm', $model); + if($form-»submitted('login') && $form-»validate()) + $this-»redirect(array('site/index')); + else + $this-»render('login', array('form'=»$form)); +} +~~~ + +בקוד למעלה, אנו יוצרים אובייקט של [CForm] תוך כדי שימוש במפרט המצביע לנתיב `application.views.site.loginForm` (הסבר לגבי זה בהמשך). +האובייקט [CForm] מקושר עם המודל `LoginForm` כפי שתואר [ביצירת מודל](/doc/guide/form.model). + +כפי שמוצג בקוד, במידה והטופס נשלח וכל השדות אומתו ללא שגיאות, אנו נעביר את המשתמש לעמוד `site/index`. אחרת, אנו נציג שוב פעם את קובץ התצוגה `login` ביחד עם הטופס. + +הנתיב `application.views.site.loginForm` מתייחס לקובץ PHP הנמצא תחת התיקיה `protected/views/site/loginForm.php`. הקובץ צריך להחזיר מערך המכיל את הגדרות הנחוצות עבור [CForm], כפי שמוצג בקוד הבא: + +~~~ +[php] +return array( + 'title'=»'Please provide your login credential', + + 'elements'=»array( + 'username'=»array( + 'type'=»'text', + 'maxlength'=»32, + ), + 'password'=»array( + 'type'=»'password', + 'maxlength'=»32, + ), + 'rememberMe'=»array( + 'type'=»'checkbox', + ) + ), + + 'buttons'=»array( + 'login'=»array( + 'type'=»'submit', + 'label'=»'Login', + ), + ), +); +~~~ + +ההגדרות הינם מערך המכיל מפתחות וערכים המוגדרים למאפיינים של המחלקה [CForm]. המאפיינים החשובים ביותר להגדרה, כפי שכבר הזכרנו, [CForm::elements] ו [CForm::buttons]. כל אחד מהם מקבל מערך המגדיר רשימה של אלמנטים בטופס. אנו נסביר בהרחבה כיצד יש להגדיר אלמנטים של טופס בחלק הבא. + +לבסוף, אנו כותבים את קובץ התצוגה `login`, שיכול להיות פשוט כפי שמוצג בקוד הבא, + +~~~ +[php] +«h1»Login«/h1» + +«div class="form"» +«?php echo $form; ?» +«/div» +~~~ + +» Tip|טיפ: הקוד למעלה, `;echo $render` הוא זהה לקוד `;()echo $form-»render`. וזאת מכיוון שהאובייקט [CForm] מיישם את המתודה `toString__` אשר קוראת למתודה `()render` ומחזירה את התצוגה כסטרינג המייצג את אובייקט הטופס. + + +הגדרת אלמנטים בטופס +------------------------ + +בשימוש במערכת יצירת הטפסים, רוב העבודה שלנו משתנה מכתיבת קוד בקבצי התצוגה להגדרת אלמנטים בטופס. בחלק זה, אנו נתאר כיצד להגדיר את האלמנטים מאפיין [CForm::elements]. אנו לא נתאר אודות [CForm::buttons] מאחר והגדרותיו הם כמעט זהות ל [CForm::elements]. + +המאפיין [CForm::elements] מקבל מערך. כל אלמנט במערך מייצג אלמנט אחד בטופס שיכול להיות שדה נתונים, טקסט סטטי, או תת-טופס. + +### הגדרת אלמנטים כשדות נתונים + +אלמנט שדה נתונים בעיקר מכיל תוית, שדה נתונים, שדה עזרה ותצוגת שגיאה. +הוא חייב להיות מקושר למאפיין במודל מסויים. המפרט של אלמנט שדה נתונים מיוצג כאובייקט של [CFormInputElement]. הקוד הבא במערך [CForm::elements] מגדיר אלמנט שדה נתונים אחד: + +~~~ +[php] +'username'=»array( + 'type'=»'text', + 'maxlength'=»32, +), +~~~ + +קוד זה מציין שהמאפיין של המודל בשם `username`, ושדה הנתונים הוא מסוג `text` ושאורכו - `maxlength` הוא 32. אנו יכולים לציין אפשרויות נוספות במערך למעלה כל עוד שהם מאפיינים שניתנים לכתיבה במחלקה [CFormInputElement]. לדוגמא, אנו יכולים להגדיר את האפשרות [hint|CFormInputElement::hint] המציגה טקסט עזרה עבור שדה זה, או שניתן להגדיר את האפשרות [items|CFormInputElement::items] במידה והשדה הוא תיבת בחירה, תיבת בחירה מרובה, רשימת כפתורי בחירה, רשימת כפתורי רדיו. + +האפשרות [type|CFormInputElement::type] דורשת יותר תשומת לב מהשאר. אפשרות זו מגדירה את סוג שדה הנתונים לתצוגה. לדוגמא, הסוג `text` מעיד על כך שיש צורך להציג שדה טקסט רגיל; הסוג `password` אומר שיש צורך להציג שדה סיסמא. [CFormInputElement] מזהה את הסוגים המובנים הבאים: + + + - text: שדה טקסט + - hidden: שדה מוסתר + - password: שדה סיסמא + - textarea: תיבת טקסט + - file: שדה קובץ + - radio: שדה כפתור רדיו + - checkbox: שדה כפתור בחירה + - listbox: שדה בחירה מרובה + - dropdownlist: שדה תיבת בחירה + - checkboxlist: שדה רשימת כפתורי בחירה + - radiolist: שדה רשימת כפתורי רדיו + + +מלבד הסוגים המובנים, האפשרות [type|CFormInputElement::type] יכולה לקבל שם של מחלקת וידג'ט או נתיב מקוצר עד אל הוידג'ט. המחלקה של הוידג'ט צריכה לירוש מהמחלקה [CInputWidget]. בעת התצוגה של האלמנט, אובייקט של הוידג'ט של השדה הנוכחי יווצר ויוצג. הוידג'ט יוגדר בהתבסס על המפרט הקיים באלמנט. + +### הגדרת טקסט סטטי + +בהרבה מקרים, מלבד שדות נתונים טופס מכיל קוד HTML שנועד לעיצוב הטופס. לדוגמא, יהיה צורך בקו אופקי בכדי להפריד בין חלקים בטופס; יש צורך בתמונה במקום מסויים בטופס בכדי לשפר את המראה החיצוני של הטופס. אנו יכולים להגדיר את קוד ה HTML הזה כטקסט סטטי באוסף האלמנטים של [CForm::elements]. בכדי לבצע זאת, אנו מגדירים אלמנט בתוך המאפיין [CForm::elements] בתור טקסט סטטי במיקום בו אנו רוצים להציג אותו בטופס. לדוגמא, + +~~~ +[php] +return array( + 'elements'=»array( + ...... + 'password'=»array( + 'type'=»'password', + 'maxlength'=»32, + ), + + '«hr /»', + + 'rememberMe'=»array( + 'type'=»'checkbox', + ) + ), + ...... +); +~~~ + +בקוד המוצג למעלה, אנו מוסיפים קו אופקי בין השדה של הסיסמא - `password` לבין השדה של זכור אותי - `rememberMe`. + +השימוש בטקסט סטטי הוא כשהטקסט המיקום שלו אינם רגילים. אם לכל שדה בטופס יהיה צורך בעיצוב באופן פרטני, יהיה צורך בלשנות את אופן התצוגה של הטופס, הסבר לפעולה זו יופיע בהמשך. + +### הגדרת תתי-טפסים + +תתי-טפסים נועדו בכדי לחלק טופס ארוך לכמה טפסים חלקים הקשורים אחד לשני לוגית. לדוגמא, אנו יכולים לחלק את טופס ההרשמה לשני תת-טפסים: פרטי התחברות ופרטי פרופיל. +כל תת-טופס חייב/לא חייב להיות משוייך למודל. בדוגמא של טופס ההרשמה, אם אנו שומרים את פרטי ההתחברות של המשתמש ואת פרטי הפרופיל של המשתמש בשני טבלאות שונות (ולכן שני מודלים נפרדים), לכן כל תת טופס יהיה משוייך למודל שלו. במידה ואנו שומרים את כל הפרטים באותה הטבלה, לכן לאף אחד מתתי-הטפסים לא יהיה מודל מאחר והם משותפים עם המודל של הטופס הראשי. + +תת-טופס מיוצג כאובייקט של [CForm]. בכדי להגדיר תת-טופס, אנו צריכים להגדיר את המאפיין [CForm::elements] עם אלמנט שסוגו הוא `form`: + +~~~ +[php] +return array( + 'elements'=»array( + ...... + 'user'=»array( + 'type'=»'form', + 'title'=»'Login Credential', + 'elements'=»array( + 'username'=»array( + 'type'=»'text', + ), + 'password'=»array( + 'type'=»'password', + ), + 'email'=»array( + 'type'=»'text', + ), + ), + ), + + 'profile'=»array( + 'type'=»'form', + ...... + ), + ...... + ), + ...... +); +~~~ + +כמו בהגדרת הטופס הראשי, אנו בעיקר צריכים להגדיר את המאפיין [CForm::elements] של תתי-הטפסים. +במידה ותת הטופס צריך להיות משוייך עם מודל, ניתן להגדיר את המאפיין [CForm::model] שלו גם כן. + +לפעמים, אנו נרצה לייצג טופס המשתמש במחלקה אחרת ולא ברירת המחדל [CForm]. לדוגמא, כפי שנציג בעוד רגע בחלק זה, אנו יכולים להרחיב את המחלקה [CForm] בכדי להתאים אישית את אופן הצגת הטופס. +על ידי הגדרת סוג האלמנט כ `form`, תת הטופס אוטומטית מיוצג כאובייקט שהמחלקה שלו היא זהה למחלקה של טופס האב. אם נגדיר את סוג האלמנט למשהו כמו `XyzForm` (סטרינג המסתיים ב `Form`), אז תת-הטופס יהיה מיוצג על ידי האובייקט של המחלקה `XyzForm`. + +גישה לאלמנטים בטופס +----------------------- + +גישה לאלמנטים בטופס הינה פשוטה כגישה לאלמנטים מערך. המאפיין [CForm::elements] מחזיר אובייקט של [CFormElementCollection], היורש מהמחלקה [CMap] ומאפשר גישה לאלמנטים שבו כמערך רגיל. לדוגמא, בכדי לגשת לאלמנט `username` בדוגמא של טופס ההתחברות שהוצג, אנו יכולים להשתמש בקוד הבא: + +~~~ +[php] +$username = $form-»elements['username']; +~~~ + +ובכדי לגשת לאלמנט של `email` בדוגמא של טופס ההרשמה, אנו יכולים להשתמש ב + +~~~ +[php] +$email = $form-»elements['user']-»elements['email']; +~~~ + +מאחר ו [CForm] מיישם את הממשק של `array access` עבור המאפיין [CForm::elements], ניתן לפשט את הקוד למעלה בצורה הבאה: + +~~~ +[php] +$username = $form['username']; +$email = $form['user']['email']; +~~~ + + +יצירת שרשור טפסים +---------------------- + +כבר הצגנו את התתי-טפסים. אנו קוראים לטופס עם תתי-טפסים טופס משורשר. בחלק זה, אנו משתמשים בטופס של הרשמת משתמשים בכדי להציג כיצד ליצור טופס משורשר המקושר לכמה מודלים. +אנו מניחים שפרטי המשתמשים מאוחסנים במודל `User`, בזמן שמידע אודות פרופיל המשתמש נמצא תחת המודל `Profile`. + +אנו קודם יוצרים את פעולת ההרשמה `register` בצורה הבאה: + +~~~ +[php] +public function actionRegister() +{ + $form = new CForm('application.views.user.registerForm'); + $form['user']-»model = new User; + $form['profile']-»model = new Profile; + if($form-»submitted('register') && $form-»validate()) + { + $user = $form['user']-»model; + $profile = $form['profile']-»model; + if($user-»save(false)) + { + $profile-»userID = $user-»id; + $profile-»save(false); + $this-»redirect(array('site/index')); + } + } + + $this-»render('register', array('form'=»$form)); +} +~~~ + +בקוד למעלה, אנו יוצרים את הטופס בעזרת שימוש בהגדרות הנמצאות ב `application.views.user.registerForm`. +לאחר השליחה של הטופס ואימות הנתונים עבר בהצלחה, אנו מנסים לשמור את המודלים של `User` ו `Profile`. +אנו שולפים את המודלים של המשתמש והפרופיל על ידי גישה למאפיין `model` תחת האובייקט של תת הטופס. + +מאחר ואימות הנתונים נעשה כבר, אנו קוראים ל `$user-»save(false)` בכדי לדלג על האימות. אנו עושים את אותו הדבר עם המודל של הפרופיל. + + +לאחר מכן, אנו כותבים את קובץ הגדרות הטופס תחת הנתיב `protected/views/user/registerForm.php`: + +~~~ +[php] +return array( + 'elements'=»array( + 'user'=»array( + 'type'=»'form', + 'title'=»'Login information', + 'elements'=»array( + 'username'=»array( + 'type'=»'text', + ), + 'password'=»array( + 'type'=»'password', + ), + 'email'=»array( + 'type'=»'text', + ) + ), + ), + + 'profile'=»array( + 'type'=»'form', + 'title'=»'Profile information', + 'elements'=»array( + 'firstName'=»array( + 'type'=»'text', + ), + 'lastName'=»array( + 'type'=»'text', + ), + ), + ), + ), + + 'buttons'=»array( + 'register'=»array( + 'type'=»'submit', + 'label'=»'Register', + ), + ), +); +~~~ + +בקוד המוצג למעלה, בעת הגדרת כל תת-טופס, אנו מגדירים את המאפיין [CForm::title] השייך לו. +לוגיקת התצוגה ברירת המחדל של הטופס תעטוף כל תת-טופס בתוך מעטפת אשר תשתמש במאפיין זה בכדי להציג את הכותרת של הטופס. + +לבסוף, אנו כותבים את קובץ התצוגה הפשוט של ההרשמה `register`: + +~~~ +[php] +«h1»הרשמה«/h1» + +«div class="form"» +«?php echo $form; ?» +«/div» +~~~ + + +התאמה אישית של תצוגה הטופס +------------------------ + +היתרון בשימוש של מערכת יצירת הטפסים היא ההפרדה מהלוגיקה (הגדרות הטופס נמצאות בקובץ נפרד) והתצוגה (מתודת [CForm::render]). כתוצאה מכך, אנו יכולים להתאים את התצוגה של הטופס באופן אישי על ידי דריסה של המתודה [CForm::render] או לספק קובץ תצוגה חלקי אשר מציג את הטופס. בשני האפשרויות ניתן להשאיר את הגדרות הטופס ללא שינוי ולהשתמש באותו הקוד בצורה קלה. + +כשדורסים את המתודה [CForm::render], יש לרוץ על גבי האלמנטים של [CForm::elements] ו [CForm::buttons] ולקרוא למתודה [CFormElement::render] עבור כל כל אלמנט. לדוגמא, + +~~~ +[php] +class MyForm extends CForm +{ + public function render() + { + $output = $this-»renderBegin(); + + foreach($this-»getElements() as $element) + $output .= $element-»render(); + + $output .= $this-»renderEnd(); + + return $output; + } +} +~~~ + +כמו כן אנו יכולים ליצור קובץ תצוגה `form_` בכדי להציג את הטופס: + +~~~ +[php] +«?php +echo $form-»renderBegin(); + +foreach($form-»getElements() as $element) + echo $element-»render(); + +echo $form-»renderEnd(); +~~~ + +בכדי להשתמש בקובץ התצוגה, ניתן פשוט לקרוא: + +~~~ +[php] +«div class="form"» +$this-»renderPartial('_form', array('form'=»$form)); +«/div» +~~~ + +במידה ותצוגה כללית לא נראית טוב בטופס מסויים (לדוגמא, הטופס צריך דקורציה מיוחדת ולא רגילה עבור אלמנטים מסויימים), אנו יכולים לבצע את הפעולות הבאות בקובץ תצוגה: + +~~~ +[php] +אלמנטים של UI מורכבים כאן + +«?php echo $form['username']; ?» + +אלמנטים של UI מורכבים כאן + +«?php echo $form['password']; ?» + +אלמנטים של UI מורכבים כאן +~~~ + +בשיטה האחרונה,זה נראה שמערכת יצירת הטפסים לא מועילה לנו כל כך, אנו עדיין צריכים לכתוב כמות קוד דומה לטופס שאנו משתמשים בעת שימוש בקוד רגיל. למרות, שהטופס מוגדר בעזרת קובץ הגדרות בנפרד אשר עוזר למפתחים להתמקד יותר בלוגיקה. + + «div class="revision"»$Id: form.builder.txt 1849 2010-03-01 15:54:34Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/form.model.txt b/docs/guide/he/form.model.txt index b0c947953..aa833a495 100644 --- a/docs/guide/he/form.model.txt +++ b/docs/guide/he/form.model.txt @@ -1,325 +1,325 @@ -יצירת מודל -============== - -לפני כתיבת קוד ה HTML הנחוץ לטופס, אנו צריכים להחליט אילו נתונים אנו מצפים לקבל ממשתמשי הקצה ואילו חוקים אותם נתונים צריכים לעמוד בהם. ניתן להעזר במודל בישביל לתעד מידע זה. [מודל](/doc/guide/basics.model), כפי שהוגדר בחלק לגבי מודלים, הוא המקום המרכזי עבור שמירת נתונים ואימות הנתונים המגיעים מהמשתמשים. - -תלוי באופן השימוש שלנו בנתונים המגיעים מהמשתמשים, אנו יכולים ליצור שני סוגים של מודלים. אם המידע שהמשתמש מזין נאסף, משומש ואז נזרק, אנו ניצור [מודל טופס](/doc/guide/basics.model); אם המידע של המשתמש נאסף ונשמר במסד הנתונים, אנו ניצור מודל [AR](/doc/guide/database.ar) במקום. שני המחלקות יורשות מהמחלקה הבסיסית [CModel] שמגדירים את הממשק הכללי הדרוש בטופס. - -» Note|הערה: בדוגמאות בחלק זה אנו בעיקר משתמשים במודל טופס. למרות, שניתן להשתמש באותם דוגמאות על גבי מודל [AR](/doc/guide/database.ar). - -הגדרת מחלקת מודל --------------------- - -למטה אנו יוצרים מחלקת מודל בשם `LoginForm` שאנו נעזרים בה לאיסוף מידע שמשתמש מזין בעמוד ההתחברות. מאחר והמידע של ההתחברות נועד למטרת אימות בלבד ואין צורך לשמור אותו, אנו יוצרים את ה `LoginForm` כמודל טופס. - -~~~ -[php] -class LoginForm extends CFormModel -{ - public $username; - public $password; - public $rememberMe=false; -} -~~~ - -שלושה משתנים מוגדרים ב `LoginForm`: המשתנים המוגדרים הם `username$`, `password$` ו `rememberMe$`. הם נועדו כדי לאפשר למשתמש להזין שם משתמש וסיסמא, ואפשרות לבחור במידה והוא רוצה לשמור את ההתחברות שלו לתקופה ממושכת. מאחר ולמשתנה `rememberMe$` ישנו ערך ברירת מחדל של `false`, כפתור הבחירה של המשתנה הנלמד יהיה לא מסומן כברירת מחדל בטופס. - -» Info|מידע: במקום לקרוא למשתנים הללו מאפיינים, אנו משתמשים במונח *משתנים* בכדי להבדיל אותם ממאפיינים רגילים. מאפיינים נועדו בעיקר לשמירת מידע המגיע מהמשתמש. - -הגדרת חוקי אימות נתונים --------------------------- - -ברגע שהמשתמש שולח את הנתונים שהוא הזין והמודל מאוכלס בנתונים אלו, אנו צריכים לוודא שהנתונים תקינים לפני שאנו משתמשים בהם. זה נעשה על ידי ביצוע אימות נתונים על גבי סט של חוקים. אנו מגדירים את חוקי אימות הנתונים במתודה `()rules` שצריכה להחזיר מערך של הגדרות. - -~~~ -[php] -class LoginForm extends CFormModel -{ - public $username; - public $password; - public $rememberMe=false; - - private $_identity; - - public function rules() - { - return array( - array('username, password', 'required'), - array('rememberMe', 'boolean'), - array('password', 'authenticate'), - ); - } - - public function authenticate($attribute,$params) - { - $this-»_identity=new UserIdentity($this-»username,$this-»password); - if(!$this-»_identity-»authenticate()) - $this-»addError('password','Incorrect username or password.'); - } -} -~~~ - -הקוד למעלה מציין ש `username` ו `password` שניהם הם שדות חובה, `password` צריך להיות מאומת, ו `rememberMe` צריך להיות ערך בוליאני (boolean). - -כל חוק הנמצא במתודה `()rules` צריך להיות מוגדר בפורמט הבא: - -~~~ -[php] -array('AttributeList', 'Validator', 'on'=»'ScenarioList', ...additional options) -~~~ - -כשהערך `AttributeList` הינו סטרינג המופרד בפסיקים של שמות שצריך לאמת אותם מול החוק שהוגדר; `Validator` מגדיר את סוג הבדיקה שיש לבצע על גבי המשתנים שהוגדרו במשתנה קודם לכן; `on` הינו ערך אופציונלי המגדיר באילו תסריטים (מצבים) יש לבצע את האימות הנוכחי; ואפשרויות נוספות הינם זוגות של מפתח=»ערך שנועדו לאתחול של המאפיינים בחוק הנוכחי. - -ישנם שלושה דרכים להגדרת `Validator` בחוק. אפשרות ראשונה, `Validator` יכול להיות שמה של מתודה במחלקת המודל הנוכחי, כמו `authenticate` בדוגמא למעלה. המתודה צריכה להראות בצורה הבאה: - -~~~ -[php] -/** - * @param string the name of the attribute to be validated - * @param array options specified in the validation rule - */ -public function ValidatorName($attribute,$params) { ... } -~~~ - -אפשרות שנייה, `Validator` יכול להיות שם של מחלקת אימות נתונים. כשהחוק יצורף, יווצר אובייקט של המחלקה אשר יבצע את הבדיקה. האפשרויות הנוספות בהגדרת החוק (כפי שתואר למעלה) נועדו כדי להגדיר את מאפייני המחלקה בעת יצירת האובייקט של המחלקה. מחלקת אימות נתונים צריכה להיות תת מחלקה של [CValidator]. - -» Note|הערה: ברגע שמגדירים חוקים למודל AR, אנו יכולים להשתמש באפשרות מיוחדת בשם `on`. האפשרות יכולה להיות `insert` או `update` כדי שהחוק המדובר יצורף רק כשמוסיפים או מעדכנים רשומה, בהתאמה. במידה ולא הוגדר, החוק יצורף בשני המקרים בעת הקריאה ל `()save`. - -אפשרות שלישית, `Validator` יכול להוות שם מקוצר למחלקות אימות נתונים שהוגדרו מראש. בדוגמא למעלה, השם `required` הינו שם מקוצר למחלקה [CRequiredValidator] אשר מוודא שהערך של המשתנה הנבדק אינו ריק. למטה מוצגת הרשימה המלאה לשמות מקוצרים למחלקות בדיקות נתונים המוגדרות מראש: - - -- `boolean`: שם מקוצר עבור המחלקה [CBooleanValidator], מוודא שהמשתנה מכיל ער של - [CBooleanValidator::trueValue] או -[CBooleanValidator::falseValue]. - -- `captcha`: שם מקוצר עבור המחלקה [CCaptchaValidator], המוודא שהערך שהוזן זהה לערך שהוצג על ידי -[CAPTCHA](http://en.wikipedia.org/wiki/Captcha). - -- `compare`: שם מקוצר עבור המחלקה [CCompareValidator], מוודא שהערך שנבדק זהה לערך הנוסף שהוגדר. - -- `email`: שם מקוצר עבור המחלקה [CEmailValidator], מוודא שהערך הנבדק הינו אימייל תקני. - -- `default`: שם מקוצר עבור המחלקה [CDefaultValueValidator], מגדיר ערכים ברירת מחדל עבור המשתנים הנבדקים. - -- `exist`: שם מקוצר עבור המחלקה [CExistValidator], מוודא שהערך הנבדק קיים בעמודה בטבלה הנוכחית. - -- `file`: שם מקוצר עבור המחלקה [CFileValidator], מוודא שהערך הנבדק מכיל את שם הקובץ שהועלה. - -- `filter`: שם מקוצר עבור המחלקה [CFilterValidator], המרת הערך הנבדק דרך פילטר שהוגדר. - -- `in`: שם מקוצר עבור המחלקה [CRangeValidator], מוודא שהערך הנבדק הוא בין רשימת הערכים שהוגדרו. - -- `length`: שם מקוצר עבור המחלקה [CStringValidator], מוודא שאורך הערך הנבדק הוא באורך מסויים. - -- `match`: שם מקוצר עבור המחלקה [CRegularExpressionValidator], מוודא שהערך תואם לביטוי רגולרי. - -- `numerical`: שם מקוצר עבור המחלקה [CNumberValidator], מוודא שהערך הנבדק הוא מספר. - -- `required`: שם מקוצר עבור המחלקה [CRequiredValidator], מוודא שהערך הנבדק אינו ריק. - -- `type`: שם מקוצר עבור המחלקה [CTypeValidator], מוודא שהערך הנבדק הוא מסוג מסויים. - -- `unique`: שם מקוצר עבור המחלקה [CUniqueValidator], מוודא שהערך הינו יחודי בעמודה בטבלה. - -- `url`: שם מקוצר עבור המחלקה [CUrlValidator], מוודא שהערך הנבדק הינו קישור תקני. - -למטה מוצגים כמה דוגמאות לשימוש במחלקות אימות נתונים המוגדרים מראש: - -~~~ -[php] -// שם משתמש הוא הכרחי -array('username', 'required'), -// שם משתמש חייב להיות בין 3 ל 12 תווים -array('username', 'length', 'min'=»3, 'max'=»12), -// בזמן הרשמה הסיסמא צריכה להיות תואמת לאישור הסיסמא -array('password', 'compare', 'compareAttribute'=»'password2', 'on'=»'register'), -// בזמן התחברות הסיסמא צריכה להיות מאומתת -array('password', 'authenticate', 'on'=»'login'), -~~~ - - -אבטחת הצבת מאפיינים ------------------------------- - -לאחר יצירת אובייקט של מודל, אנו בדרך כלל צריכים לאכלס את המאפיינים שלו על ידי המידע שהמשתמש הזין. ניתן לבצע זאת בצורה פשוטה ונוחה על ידי הצבה מאסיבית של מאפיינים: - -~~~ -[php] -$model=new LoginForm; -if(isset($_POST['LoginForm'])) - $model-»attributes=$_POST['LoginForm']; -~~~ - -הפקודה האחרונה נקראת *הצבה מאסיבית* אשר מציבה את כל המאפיינים ב `$_POST['LoginForm']` למאפיינים המתאימים במודל. פקודה זו זהה לקוד הבא: - -~~~ -[php] -foreach($_POST['LoginForm'] as $name=»$value) -{ - if($name is a safe attribute) - $model-»$name=$value; -} -~~~ - -זה הכרחי להגדיר אילו מאפיינים הם מאובטחים. לדוגמא, במידה ונחשוף את העמודה השומרת את המפתח הראשי בטבלה ונגדיר אותו להיות מאפיין מאובטח, ניתן יהיה לשנות את המפתח הראשי של מידע מסויים ובכך לקבל גישה לנתונים שמשתמשים לא צריכים לראות. - -התנאים בהגדרת מאפיינים בטוחים שונים בין הגרסאות 1.0 ו 1.1. בחלק הבא, אנו נסביר לגביהם בנפרד. - -### מאפיינים בטוחים ב 1.1 - -בגרסא 1.1, מאפיין מוגדר כמאובטח אם הוא מופיע בחוקים לאימות הנתונים המוגדרים לעבוד עם התסריט הנוכחי. לדוגמא, - -~~~ -[php] -array('username, password', 'required', 'on'=»'login, register'), -array('email', 'required', 'on'=»'register'), -~~~ - -בקוד למעלה, המאפיינים `username` ו `password` הם שדו חובה בתסריט של `login`, בזמן שהמאפיינים `username`, `password` ו `email` הם שדות חובה בתסריט של `register`. כתוצאה מכך, אם נבצע הצבה מאסיבית של נתונים למאפיינים כשאנו נמצאים בתסריט של `login`, רק המאפיינים `username` ו `password` יוצבו בצורה מאסיבית מאחר ורק מאפיינים אלו מופיעים בחוקים עבור התסריט `login`. -מצד שני, אם התסריט הוא `register`, כל שלושת המאפיינים יוצבו בצורה מאסיבית. - -~~~ -[php] -// בתסריט התחברות -$model=new User('login'); -if(isset($_POST['User'])) - $model-»attributes=$_POST['User']; - -// בתסריט הרשמה -$model=new User('register'); -if(isset($_POST['User'])) - $model-»attributes=$_POST['User']; -~~~ - -אז למה אנחנו משתמשים במדיניות זו בכדי לקבוע אם מאפיין הוא בטוח או לא? -הלוגיקה מאחורי זה היא אם מאפיין כבר מוגדר עם חוק אחד או יותר לאימות הנתונים בו, למה לנו לדאוג בנוגע אליו? - -חשוב לזכור שחוקי אימות הנתונים נועדו לאמת את הנתונים המגיעים מהמשתמש ולא את הנתונים שאנו יוצרים בקוד (לדוגמא זמן נוכחי, ערך המפתח הראשי שנוצר אוטומטית). -לכן, אין להוסיף חוקים לאימות הנתונים לנתונים שלא מגיעים מהמשתמשים. - -לפעמים, אנחנו מגדירים מאפיין בטוח, למרות שאין לנו חוק מסויים עבורו. לדוגמא מאפיין של תוכן מאמר שיכול להכיל כל תוכן שהמשתמש מזין. אנו יכולים להשתמש בחוק מיוחד בשם `safe` בכדי להשיג מטרה זו: - -~~~ -[php] -array('content', 'safe') -~~~ - -לצורך השלמת הקוד, ישנו חוק בשם `unsafe` שנועד להגדיר מאפיין כלא בטוח ולא להכליל אותו בנתונים: - -~~~ -[php] -array('permission', 'unsafe') -~~~ - -בדרך כלל אין שימוש בחוק `unsafe`, והוא ההפך ממה שדובר לגבי חוק ה `safe` עבור מאפיינים בטוחים. - -### מאפיינים בטוחים ב 1.0 - -בגרסאות 1.0, ההחלטה בין אם מאפיין הוא בטוח או לא מבוססת על הערך המוחזר מהמתודה `safeAttributes` והתסריט המוגדר כרגע. כברירת מחדל, המתודה מחזירה את כל מאפייני המחלקה [CFormModel] כמאפיינים בטוחים, בזמן שהינה מחזירה את כל העמודות בטבלה מלבד העמודה של המפתח הראשי עבור המחלקה [CActiveRecord]. -אנו יכולים לדרוס מתודה זו בכדי להגביל את המאפיינים הבטוחים לפי התסריט המוגדר. -לדוגמא, מודל משתמשים יכול להכיל מאפיינים רבים, אבל בזמן השימוש בתסריט `login` אנו נצטרך רק את המאפיינים של שם המשתמש והסיסמא, `username` ו `password`. -אנו יכולים להגדיר את ההגבלה בצורה הבאה: - -~~~ -[php] -public function safeAttributes() -{ - return array( - parent::safeAttributes(), - 'login' =» 'username, password', - ); -} -~~~ - -יותר מדוייק, הערך המוחזר מהמתודה `safeAttributes` צריך להראות כמו התבנית הבאה: - -~~~ -[php] -array( - // מאפיינים אלו ניתנים להצבה מאסיבית בכל תסריט - // שלא הוגדר ספציפית כמו בדוגמאות למטה - 'attr1, attr2, ...', - * - // מאפיינים אלו ניתנים להצבה מאסיבית רק בזמן השימוש בתסריט מספר 1 - 'scenario1' =» 'attr2, attr3, ...', - * - // מאפיינים אלו ניתנים להצבה מאסיבית רק בזמן השימוש בתסריט מספר 2 - 'scenario2' =» 'attr1, attr3, ...', -) -~~~ - -במידה והמודל אינו רגיש כלפי תסריט (כלומר, משתמשים בו עם תסריט אחד בלבד, או כל התסריטים משתמשים באותם המאפיינים הבטוחים), ניתן לפשט את הערך המוחזר בסטרינג בודד: - -~~~ -[php] -'attr1, attr2, ...' -~~~ - -עבור מאפיינים שהם לא בטוחים, אנו צריכים להגדיר אותם בצורה פרטנית בהתאם לערכים שהם אמורים להכיל, לדוגמא, - -~~~ -[php] -$model-»permission='admin'; -$model-»id=1; -~~~ - - -ביצוע אימות הנתונים ---------------------- - -לאחר שהמודל מאוכלס עם הנתונים שהמשתמש הזין, אנו קוראים למתודה [()CModel::validate] בכדי להריץ את תהליך אימות הנתונים. המתודה מחזירה ערך המציין אם תהליך אימות הנתונים עבר בהצלחה או לא. עבור מודלים של [CActiveRecord], אימות הנתונים יתבצע בצורה אוטומטית ברגע שנקרא למתודה [()CActiveRecord::save]. - -אנו יכולים לציין את התסריט הנוכחי על ידי הגדרת המאפיין [scenario|CModel::scenario] ועל ידי כך לציין אילו חוקים יצורפו לבדיקה זו. - -אימות הנתונים מתבצע על בסיס התסריט. המאפיין [scenario|CModel::scenario] מגדיר איזה תסריט המודל כרגע משתמש בו ובאילו חוקים יש להשתמש באימות הנתונים כרגע. לדוגמא, בתסריט של `login`, אנו נרצה רק לאמת את שם המשתמש והסיסמא - `username` ו `password` הנמצאים במודל של המשתמשים; בזמן שבתסריט `register`, אנו נצטרך לאמת יותר נתונים, כמו `email`, `address`, וכדומה. -הדוגמא הבאה מציגה כיצד לבצע אימות נתונים תחת תסריט `register`: - -~~~ -[php] -// יוצר מודל כשמצב התסריט מוגדר ל `register`. הקוד זהה ל: -// $model=new User; -// $model-»scenario='register'; -$model=new User('register'); - -// מאכלס את הנתונים שהגיע מהמשתמש אל המודל -$model-»attributes=$_POST['User']; - -// מבצע את הבדיקה -if($model-»validate()) // במידה והנתונים תקינים - ... -else - ... -~~~ - -בכדי להגדיר חוקים מסויימים עבור תסריטים מסויימים יש להגדיר את המאפיין `on` בהגדרה של החוק עצמו. במידה והמאפיין `on` לא הוגדר, זה אומר שהחוק צריך להיות תקף בכל התסריטים האפשריים. לדוגמא, - -~~~ -[php] -public function rules() -{ - return array( - array('username, password', 'required'), - array('password_repeat', 'required', 'on'=»'register'), - array('password', 'compare', 'on'=»'register'), - ); -} -~~~ - -החוק הראשון יצורף לכל התסריטים, בזמן ששני הבאים יצורפו רק לתסריט של `register`. - - -קבלת שגיאות אימות נתונים ----------------------------- - -לאחר ביצוע האימות, כל השגיאות הקיימות יאוחסנו תחת האובייקט של המודל. אנו שולפים את הודעות השגיאות על ידי קריאה למתודה [()CModel::getErrors] -או [()CModel::getError]. ההבדל בין השניים הוא שהראשון יחזיר את *כל* השגיאות עבור מאפיין כלשהו במודל. בזמן שהשני מחזיר את השגיאה *הראשונה* עבור מאפיין כלשהו. - -תוויות מאפיינים ----------------- - -בעת עיצוב טופס, אנחנו בדרך כלל צריכים להציג תווית עבור כל שדה. התוית אומרת למשתמש איזה סוג מידע הוא צריך להזין לשדה. למרות שאנו יכולים להוסיף זאת ישירות בתוך קוד ה HTML, יש עדיפות להגדיר את התויות של השדות במחלקה של המודל בכדי לקבל יותר גמישות ולנוחות השימוש. - -כברירת מחדל, [CModel] יפשט את החזרת השם של מאפיין כלשהו המוגדר בו. ניתן להגדיר זאת על ידי דריסה של המתודה [()attributeLabels|CModel::attributeLabels]. כפי שאנו נראה בחלקים הבאים, הגדרת התויות של מאפיינים במודל יאפשר לנו ליצור טופס בצורה הרבה יותר מהירה ויעילה. - - +יצירת מודל +============== + +לפני כתיבת קוד ה HTML הנחוץ לטופס, אנו צריכים להחליט אילו נתונים אנו מצפים לקבל ממשתמשי הקצה ואילו חוקים אותם נתונים צריכים לעמוד בהם. ניתן להעזר במודל בישביל לתעד מידע זה. [מודל](/doc/guide/basics.model), כפי שהוגדר בחלק לגבי מודלים, הוא המקום המרכזי עבור שמירת נתונים ואימות הנתונים המגיעים מהמשתמשים. + +תלוי באופן השימוש שלנו בנתונים המגיעים מהמשתמשים, אנו יכולים ליצור שני סוגים של מודלים. אם המידע שהמשתמש מזין נאסף, משומש ואז נזרק, אנו ניצור [מודל טופס](/doc/guide/basics.model); אם המידע של המשתמש נאסף ונשמר במסד הנתונים, אנו ניצור מודל [AR](/doc/guide/database.ar) במקום. שני המחלקות יורשות מהמחלקה הבסיסית [CModel] שמגדירים את הממשק הכללי הדרוש בטופס. + +» Note|הערה: בדוגמאות בחלק זה אנו בעיקר משתמשים במודל טופס. למרות, שניתן להשתמש באותם דוגמאות על גבי מודל [AR](/doc/guide/database.ar). + +הגדרת מחלקת מודל +-------------------- + +למטה אנו יוצרים מחלקת מודל בשם `LoginForm` שאנו נעזרים בה לאיסוף מידע שמשתמש מזין בעמוד ההתחברות. מאחר והמידע של ההתחברות נועד למטרת אימות בלבד ואין צורך לשמור אותו, אנו יוצרים את ה `LoginForm` כמודל טופס. + +~~~ +[php] +class LoginForm extends CFormModel +{ + public $username; + public $password; + public $rememberMe=false; +} +~~~ + +שלושה משתנים מוגדרים ב `LoginForm`: המשתנים המוגדרים הם `username$`, `password$` ו `rememberMe$`. הם נועדו כדי לאפשר למשתמש להזין שם משתמש וסיסמא, ואפשרות לבחור במידה והוא רוצה לשמור את ההתחברות שלו לתקופה ממושכת. מאחר ולמשתנה `rememberMe$` ישנו ערך ברירת מחדל של `false`, כפתור הבחירה של המשתנה הנלמד יהיה לא מסומן כברירת מחדל בטופס. + +» Info|מידע: במקום לקרוא למשתנים הללו מאפיינים, אנו משתמשים במונח *משתנים* בכדי להבדיל אותם ממאפיינים רגילים. מאפיינים נועדו בעיקר לשמירת מידע המגיע מהמשתמש. + +הגדרת חוקי אימות נתונים +-------------------------- + +ברגע שהמשתמש שולח את הנתונים שהוא הזין והמודל מאוכלס בנתונים אלו, אנו צריכים לוודא שהנתונים תקינים לפני שאנו משתמשים בהם. זה נעשה על ידי ביצוע אימות נתונים על גבי סט של חוקים. אנו מגדירים את חוקי אימות הנתונים במתודה `()rules` שצריכה להחזיר מערך של הגדרות. + +~~~ +[php] +class LoginForm extends CFormModel +{ + public $username; + public $password; + public $rememberMe=false; + + private $_identity; + + public function rules() + { + return array( + array('username, password', 'required'), + array('rememberMe', 'boolean'), + array('password', 'authenticate'), + ); + } + + public function authenticate($attribute,$params) + { + $this-»_identity=new UserIdentity($this-»username,$this-»password); + if(!$this-»_identity-»authenticate()) + $this-»addError('password','Incorrect username or password.'); + } +} +~~~ + +הקוד למעלה מציין ש `username` ו `password` שניהם הם שדות חובה, `password` צריך להיות מאומת, ו `rememberMe` צריך להיות ערך בוליאני (boolean). + +כל חוק הנמצא במתודה `()rules` צריך להיות מוגדר בפורמט הבא: + +~~~ +[php] +array('AttributeList', 'Validator', 'on'=»'ScenarioList', ...additional options) +~~~ + +כשהערך `AttributeList` הינו סטרינג המופרד בפסיקים של שמות שצריך לאמת אותם מול החוק שהוגדר; `Validator` מגדיר את סוג הבדיקה שיש לבצע על גבי המשתנים שהוגדרו במשתנה קודם לכן; `on` הינו ערך אופציונלי המגדיר באילו תסריטים (מצבים) יש לבצע את האימות הנוכחי; ואפשרויות נוספות הינם זוגות של מפתח=»ערך שנועדו לאתחול של המאפיינים בחוק הנוכחי. + +ישנם שלושה דרכים להגדרת `Validator` בחוק. אפשרות ראשונה, `Validator` יכול להיות שמה של מתודה במחלקת המודל הנוכחי, כמו `authenticate` בדוגמא למעלה. המתודה צריכה להראות בצורה הבאה: + +~~~ +[php] +/** + * @param string the name of the attribute to be validated + * @param array options specified in the validation rule + */ +public function ValidatorName($attribute,$params) { ... } +~~~ + +אפשרות שנייה, `Validator` יכול להיות שם של מחלקת אימות נתונים. כשהחוק יצורף, יווצר אובייקט של המחלקה אשר יבצע את הבדיקה. האפשרויות הנוספות בהגדרת החוק (כפי שתואר למעלה) נועדו כדי להגדיר את מאפייני המחלקה בעת יצירת האובייקט של המחלקה. מחלקת אימות נתונים צריכה להיות תת מחלקה של [CValidator]. + +» Note|הערה: ברגע שמגדירים חוקים למודל AR, אנו יכולים להשתמש באפשרות מיוחדת בשם `on`. האפשרות יכולה להיות `insert` או `update` כדי שהחוק המדובר יצורף רק כשמוסיפים או מעדכנים רשומה, בהתאמה. במידה ולא הוגדר, החוק יצורף בשני המקרים בעת הקריאה ל `()save`. + +אפשרות שלישית, `Validator` יכול להוות שם מקוצר למחלקות אימות נתונים שהוגדרו מראש. בדוגמא למעלה, השם `required` הינו שם מקוצר למחלקה [CRequiredValidator] אשר מוודא שהערך של המשתנה הנבדק אינו ריק. למטה מוצגת הרשימה המלאה לשמות מקוצרים למחלקות בדיקות נתונים המוגדרות מראש: + + +- `boolean`: שם מקוצר עבור המחלקה [CBooleanValidator], מוודא שהמשתנה מכיל ער של + [CBooleanValidator::trueValue] או +[CBooleanValidator::falseValue]. + +- `captcha`: שם מקוצר עבור המחלקה [CCaptchaValidator], המוודא שהערך שהוזן זהה לערך שהוצג על ידי +[CAPTCHA](http://en.wikipedia.org/wiki/Captcha). + +- `compare`: שם מקוצר עבור המחלקה [CCompareValidator], מוודא שהערך שנבדק זהה לערך הנוסף שהוגדר. + +- `email`: שם מקוצר עבור המחלקה [CEmailValidator], מוודא שהערך הנבדק הינו אימייל תקני. + +- `default`: שם מקוצר עבור המחלקה [CDefaultValueValidator], מגדיר ערכים ברירת מחדל עבור המשתנים הנבדקים. + +- `exist`: שם מקוצר עבור המחלקה [CExistValidator], מוודא שהערך הנבדק קיים בעמודה בטבלה הנוכחית. + +- `file`: שם מקוצר עבור המחלקה [CFileValidator], מוודא שהערך הנבדק מכיל את שם הקובץ שהועלה. + +- `filter`: שם מקוצר עבור המחלקה [CFilterValidator], המרת הערך הנבדק דרך פילטר שהוגדר. + +- `in`: שם מקוצר עבור המחלקה [CRangeValidator], מוודא שהערך הנבדק הוא בין רשימת הערכים שהוגדרו. + +- `length`: שם מקוצר עבור המחלקה [CStringValidator], מוודא שאורך הערך הנבדק הוא באורך מסויים. + +- `match`: שם מקוצר עבור המחלקה [CRegularExpressionValidator], מוודא שהערך תואם לביטוי רגולרי. + +- `numerical`: שם מקוצר עבור המחלקה [CNumberValidator], מוודא שהערך הנבדק הוא מספר. + +- `required`: שם מקוצר עבור המחלקה [CRequiredValidator], מוודא שהערך הנבדק אינו ריק. + +- `type`: שם מקוצר עבור המחלקה [CTypeValidator], מוודא שהערך הנבדק הוא מסוג מסויים. + +- `unique`: שם מקוצר עבור המחלקה [CUniqueValidator], מוודא שהערך הינו יחודי בעמודה בטבלה. + +- `url`: שם מקוצר עבור המחלקה [CUrlValidator], מוודא שהערך הנבדק הינו קישור תקני. + +למטה מוצגים כמה דוגמאות לשימוש במחלקות אימות נתונים המוגדרים מראש: + +~~~ +[php] +// שם משתמש הוא הכרחי +array('username', 'required'), +// שם משתמש חייב להיות בין 3 ל 12 תווים +array('username', 'length', 'min'=»3, 'max'=»12), +// בזמן הרשמה הסיסמא צריכה להיות תואמת לאישור הסיסמא +array('password', 'compare', 'compareAttribute'=»'password2', 'on'=»'register'), +// בזמן התחברות הסיסמא צריכה להיות מאומתת +array('password', 'authenticate', 'on'=»'login'), +~~~ + + +אבטחת הצבת מאפיינים +------------------------------ + +לאחר יצירת אובייקט של מודל, אנו בדרך כלל צריכים לאכלס את המאפיינים שלו על ידי המידע שהמשתמש הזין. ניתן לבצע זאת בצורה פשוטה ונוחה על ידי הצבה מאסיבית של מאפיינים: + +~~~ +[php] +$model=new LoginForm; +if(isset($_POST['LoginForm'])) + $model-»attributes=$_POST['LoginForm']; +~~~ + +הפקודה האחרונה נקראת *הצבה מאסיבית* אשר מציבה את כל המאפיינים ב `$_POST['LoginForm']` למאפיינים המתאימים במודל. פקודה זו זהה לקוד הבא: + +~~~ +[php] +foreach($_POST['LoginForm'] as $name=»$value) +{ + if($name is a safe attribute) + $model-»$name=$value; +} +~~~ + +זה הכרחי להגדיר אילו מאפיינים הם מאובטחים. לדוגמא, במידה ונחשוף את העמודה השומרת את המפתח הראשי בטבלה ונגדיר אותו להיות מאפיין מאובטח, ניתן יהיה לשנות את המפתח הראשי של מידע מסויים ובכך לקבל גישה לנתונים שמשתמשים לא צריכים לראות. + +התנאים בהגדרת מאפיינים בטוחים שונים בין הגרסאות 1.0 ו 1.1. בחלק הבא, אנו נסביר לגביהם בנפרד. + +### מאפיינים בטוחים ב 1.1 + +בגרסא 1.1, מאפיין מוגדר כמאובטח אם הוא מופיע בחוקים לאימות הנתונים המוגדרים לעבוד עם התסריט הנוכחי. לדוגמא, + +~~~ +[php] +array('username, password', 'required', 'on'=»'login, register'), +array('email', 'required', 'on'=»'register'), +~~~ + +בקוד למעלה, המאפיינים `username` ו `password` הם שדו חובה בתסריט של `login`, בזמן שהמאפיינים `username`, `password` ו `email` הם שדות חובה בתסריט של `register`. כתוצאה מכך, אם נבצע הצבה מאסיבית של נתונים למאפיינים כשאנו נמצאים בתסריט של `login`, רק המאפיינים `username` ו `password` יוצבו בצורה מאסיבית מאחר ורק מאפיינים אלו מופיעים בחוקים עבור התסריט `login`. +מצד שני, אם התסריט הוא `register`, כל שלושת המאפיינים יוצבו בצורה מאסיבית. + +~~~ +[php] +// בתסריט התחברות +$model=new User('login'); +if(isset($_POST['User'])) + $model-»attributes=$_POST['User']; + +// בתסריט הרשמה +$model=new User('register'); +if(isset($_POST['User'])) + $model-»attributes=$_POST['User']; +~~~ + +אז למה אנחנו משתמשים במדיניות זו בכדי לקבוע אם מאפיין הוא בטוח או לא? +הלוגיקה מאחורי זה היא אם מאפיין כבר מוגדר עם חוק אחד או יותר לאימות הנתונים בו, למה לנו לדאוג בנוגע אליו? + +חשוב לזכור שחוקי אימות הנתונים נועדו לאמת את הנתונים המגיעים מהמשתמש ולא את הנתונים שאנו יוצרים בקוד (לדוגמא זמן נוכחי, ערך המפתח הראשי שנוצר אוטומטית). +לכן, אין להוסיף חוקים לאימות הנתונים לנתונים שלא מגיעים מהמשתמשים. + +לפעמים, אנחנו מגדירים מאפיין בטוח, למרות שאין לנו חוק מסויים עבורו. לדוגמא מאפיין של תוכן מאמר שיכול להכיל כל תוכן שהמשתמש מזין. אנו יכולים להשתמש בחוק מיוחד בשם `safe` בכדי להשיג מטרה זו: + +~~~ +[php] +array('content', 'safe') +~~~ + +לצורך השלמת הקוד, ישנו חוק בשם `unsafe` שנועד להגדיר מאפיין כלא בטוח ולא להכליל אותו בנתונים: + +~~~ +[php] +array('permission', 'unsafe') +~~~ + +בדרך כלל אין שימוש בחוק `unsafe`, והוא ההפך ממה שדובר לגבי חוק ה `safe` עבור מאפיינים בטוחים. + +### מאפיינים בטוחים ב 1.0 + +בגרסאות 1.0, ההחלטה בין אם מאפיין הוא בטוח או לא מבוססת על הערך המוחזר מהמתודה `safeAttributes` והתסריט המוגדר כרגע. כברירת מחדל, המתודה מחזירה את כל מאפייני המחלקה [CFormModel] כמאפיינים בטוחים, בזמן שהינה מחזירה את כל העמודות בטבלה מלבד העמודה של המפתח הראשי עבור המחלקה [CActiveRecord]. +אנו יכולים לדרוס מתודה זו בכדי להגביל את המאפיינים הבטוחים לפי התסריט המוגדר. +לדוגמא, מודל משתמשים יכול להכיל מאפיינים רבים, אבל בזמן השימוש בתסריט `login` אנו נצטרך רק את המאפיינים של שם המשתמש והסיסמא, `username` ו `password`. +אנו יכולים להגדיר את ההגבלה בצורה הבאה: + +~~~ +[php] +public function safeAttributes() +{ + return array( + parent::safeAttributes(), + 'login' =» 'username, password', + ); +} +~~~ + +יותר מדוייק, הערך המוחזר מהמתודה `safeAttributes` צריך להראות כמו התבנית הבאה: + +~~~ +[php] +array( + // מאפיינים אלו ניתנים להצבה מאסיבית בכל תסריט + // שלא הוגדר ספציפית כמו בדוגמאות למטה + 'attr1, attr2, ...', + * + // מאפיינים אלו ניתנים להצבה מאסיבית רק בזמן השימוש בתסריט מספר 1 + 'scenario1' =» 'attr2, attr3, ...', + * + // מאפיינים אלו ניתנים להצבה מאסיבית רק בזמן השימוש בתסריט מספר 2 + 'scenario2' =» 'attr1, attr3, ...', +) +~~~ + +במידה והמודל אינו רגיש כלפי תסריט (כלומר, משתמשים בו עם תסריט אחד בלבד, או כל התסריטים משתמשים באותם המאפיינים הבטוחים), ניתן לפשט את הערך המוחזר בסטרינג בודד: + +~~~ +[php] +'attr1, attr2, ...' +~~~ + +עבור מאפיינים שהם לא בטוחים, אנו צריכים להגדיר אותם בצורה פרטנית בהתאם לערכים שהם אמורים להכיל, לדוגמא, + +~~~ +[php] +$model-»permission='admin'; +$model-»id=1; +~~~ + + +ביצוע אימות הנתונים +--------------------- + +לאחר שהמודל מאוכלס עם הנתונים שהמשתמש הזין, אנו קוראים למתודה [()CModel::validate] בכדי להריץ את תהליך אימות הנתונים. המתודה מחזירה ערך המציין אם תהליך אימות הנתונים עבר בהצלחה או לא. עבור מודלים של [CActiveRecord], אימות הנתונים יתבצע בצורה אוטומטית ברגע שנקרא למתודה [()CActiveRecord::save]. + +אנו יכולים לציין את התסריט הנוכחי על ידי הגדרת המאפיין [scenario|CModel::scenario] ועל ידי כך לציין אילו חוקים יצורפו לבדיקה זו. + +אימות הנתונים מתבצע על בסיס התסריט. המאפיין [scenario|CModel::scenario] מגדיר איזה תסריט המודל כרגע משתמש בו ובאילו חוקים יש להשתמש באימות הנתונים כרגע. לדוגמא, בתסריט של `login`, אנו נרצה רק לאמת את שם המשתמש והסיסמא - `username` ו `password` הנמצאים במודל של המשתמשים; בזמן שבתסריט `register`, אנו נצטרך לאמת יותר נתונים, כמו `email`, `address`, וכדומה. +הדוגמא הבאה מציגה כיצד לבצע אימות נתונים תחת תסריט `register`: + +~~~ +[php] +// יוצר מודל כשמצב התסריט מוגדר ל `register`. הקוד זהה ל: +// $model=new User; +// $model-»scenario='register'; +$model=new User('register'); + +// מאכלס את הנתונים שהגיע מהמשתמש אל המודל +$model-»attributes=$_POST['User']; + +// מבצע את הבדיקה +if($model-»validate()) // במידה והנתונים תקינים + ... +else + ... +~~~ + +בכדי להגדיר חוקים מסויימים עבור תסריטים מסויימים יש להגדיר את המאפיין `on` בהגדרה של החוק עצמו. במידה והמאפיין `on` לא הוגדר, זה אומר שהחוק צריך להיות תקף בכל התסריטים האפשריים. לדוגמא, + +~~~ +[php] +public function rules() +{ + return array( + array('username, password', 'required'), + array('password_repeat', 'required', 'on'=»'register'), + array('password', 'compare', 'on'=»'register'), + ); +} +~~~ + +החוק הראשון יצורף לכל התסריטים, בזמן ששני הבאים יצורפו רק לתסריט של `register`. + + +קבלת שגיאות אימות נתונים +---------------------------- + +לאחר ביצוע האימות, כל השגיאות הקיימות יאוחסנו תחת האובייקט של המודל. אנו שולפים את הודעות השגיאות על ידי קריאה למתודה [()CModel::getErrors] +או [()CModel::getError]. ההבדל בין השניים הוא שהראשון יחזיר את *כל* השגיאות עבור מאפיין כלשהו במודל. בזמן שהשני מחזיר את השגיאה *הראשונה* עבור מאפיין כלשהו. + +תוויות מאפיינים +---------------- + +בעת עיצוב טופס, אנחנו בדרך כלל צריכים להציג תווית עבור כל שדה. התוית אומרת למשתמש איזה סוג מידע הוא צריך להזין לשדה. למרות שאנו יכולים להוסיף זאת ישירות בתוך קוד ה HTML, יש עדיפות להגדיר את התויות של השדות במחלקה של המודל בכדי לקבל יותר גמישות ולנוחות השימוש. + +כברירת מחדל, [CModel] יפשט את החזרת השם של מאפיין כלשהו המוגדר בו. ניתן להגדיר זאת על ידי דריסה של המתודה [()attributeLabels|CModel::attributeLabels]. כפי שאנו נראה בחלקים הבאים, הגדרת התויות של מאפיינים במודל יאפשר לנו ליצור טופס בצורה הרבה יותר מהירה ויעילה. + + «div class="revision"»$Id: form.model.txt 1919 2010-03-15 17:25:48Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/form.overview.txt b/docs/guide/he/form.overview.txt index e9d786931..82a751f78 100644 --- a/docs/guide/he/form.overview.txt +++ b/docs/guide/he/form.overview.txt @@ -1,17 +1,17 @@ -עבודה עם טפסים -================= - -איסוף מידע מהמשתמש בעזרת טפסי HTML הוא אחד מהמטלות הגדולות בפיתוח אפליקציות ווב. מלבד עיצוב הטפסים, מפתחים צריכים להציג את הטופס עם מידע קיים או -להגדיר ערכים שיהוו בתור ערכי ברירת המחדל בטופס, לאמת את הקלט של המשתמש, הצגת שגיאות מותאמות לקלט לא תקני, ושמירת הנתונים למאגר נתונים קבוע כלשהו. -Yii מפשטת את כל התהליך הזה עם הארכיטקטורה שלה. - -השלבים הבאים בדרך כלל נחוצים כדי לעבוד עם טפסים במערכת ה Yii: - - 1. יצירת מודל המייצג את המידע אשר נאסף מהטופס. - 2. יצירת קונטרולר עם מתודה אשר מגיבה בעת שליחת הטופס. - 3. יצירת טופס בסקריפט התצוגה אשר משותף למתודה בקונטרולר. - -בחלקים הבאים אנחנו מציגים כל שלב בפרטי פרטים. - - +עבודה עם טפסים +================= + +איסוף מידע מהמשתמש בעזרת טפסי HTML הוא אחד מהמטלות הגדולות בפיתוח אפליקציות ווב. מלבד עיצוב הטפסים, מפתחים צריכים להציג את הטופס עם מידע קיים או +להגדיר ערכים שיהוו בתור ערכי ברירת המחדל בטופס, לאמת את הקלט של המשתמש, הצגת שגיאות מותאמות לקלט לא תקני, ושמירת הנתונים למאגר נתונים קבוע כלשהו. +Yii מפשטת את כל התהליך הזה עם הארכיטקטורה שלה. + +השלבים הבאים בדרך כלל נחוצים כדי לעבוד עם טפסים במערכת ה Yii: + + 1. יצירת מודל המייצג את המידע אשר נאסף מהטופס. + 2. יצירת קונטרולר עם מתודה אשר מגיבה בעת שליחת הטופס. + 3. יצירת טופס בסקריפט התצוגה אשר משותף למתודה בקונטרולר. + +בחלקים הבאים אנחנו מציגים כל שלב בפרטי פרטים. + + «div class="revision"»$Id: form.overview.txt 163 2008-11-05 12:51:48Z weizhuo $«/div» \ No newline at end of file diff --git a/docs/guide/he/form.table.txt b/docs/guide/he/form.table.txt index 33b4249db..a9a30af2d 100644 --- a/docs/guide/he/form.table.txt +++ b/docs/guide/he/form.table.txt @@ -1,74 +1,74 @@ -איסוף קלט מאסיבי -======================== - -לפעמים אנו צריכים לאסוף את הקלט של המשתמש בצורה מאסיבית. זאת אומרת, המשתמש יכול להזין מידע עבור מספר רב של מודלים ולשלוח אותם בבת אחת. אנו קוראים לזה *קלט מאסיבי (טבלאי)* מאחר ושדות הקלט מוצגים בטבלת HTML בדרך כלל. - -בכדי לעבוד עם נתונים טבלאיים, אנו קודם צריכים ליצור או לאכלס מערך אובייקטים של מודלים, תלוי במידה ואנו רוצים להזין תוכן חדש או לעדכן תוכן קיים. לאחר מכן אנו שולפים את הקלט של המשתמש מהמשתנה `POST_$` ומגדירים אותו לכל מודל. הבדל קטן מקלט המתקבל ממודל אחד הוא שאנו שולפים את המידע על ידי שימוש ב - -~~~ -$_POST['ModelClass'][$i] -~~~ - - -במקום ב `$_POST['ModelClass']` - - -~~~ -[php] -public function actionBatchUpdate() -{ - // שליפת מידע בצורה מאסיבית - // בנחה שכל ערך הוא מודל של המחלקה `Item` - $items=$this-»getItemsToUpdate(); - if(isset($_POST['Item'])) - { - $valid=true; - foreach($items as $i=»$item) - { - if(isset($_POST['Item'][$i])) - $item-»attributes=$_POST['Item'][$i]; - $valid=$valid && $item-»validate(); - } - if($valid) // כל הערכים תקינים - // פעולות לביצוע - } - // הצג את הטופס - $this-»render('batchUpdate',array('items'=»$items)); -} -~~~ - -לאחר שיש לנו את הפעולה כתובה, אנו צריכים לעבוד על קובץ התצוגה `batchUpdate` המציג את השדות בטבלת HTML. - - -~~~ -[php] -«div class="form"» -«?php echo CHtml::beginForm(); ?» -«table» -«tr»«th»Name«/th»«th»Price«/th»«th»Count«/th»«th»Description«/th»«/tr» -«?php foreach($items as $i=»$item): ?» -«tr» -«td»«?php echo CHtml::activeTextField($item,"[$i]name"); ?»«/td» -«td»«?php echo CHtml::activeTextField($item,"[$i]price"); ?»«/td» -«td»«?php echo CHtml::activeTextField($item,"[$i]count"); ?»«/td» -«td»«?php echo CHtml::activeTextArea($item,"[$i]description"); ?»«/td» -«/tr» -«?php endforeach; ?» -«/table» - -«?php echo CHtml::submitButton('Save'); ?» -«?php echo CHtml::endForm(); ?» -«/div»«!-- form --» -~~~ - -יש לשים לב למעלה שאנו משתמשים ב - -~~~ -"[$i]name" -~~~ - -במקום ב `"name"` כפרמטר שני בעת הקריאה ל [CHtml::activeTextField]. - -במידה ויש שגיאה כלשהי באימות הנתונים, שדות הקלט המכילים את השגיאות יקבלו מחלקת CSS אוטומטית בכדי להציג שישנה שגיאה בשדה, בידיוק כמו בשימוש עם מודל אחד כפי שהוסבר קודם לכן. - +איסוף קלט מאסיבי +======================== + +לפעמים אנו צריכים לאסוף את הקלט של המשתמש בצורה מאסיבית. זאת אומרת, המשתמש יכול להזין מידע עבור מספר רב של מודלים ולשלוח אותם בבת אחת. אנו קוראים לזה *קלט מאסיבי (טבלאי)* מאחר ושדות הקלט מוצגים בטבלת HTML בדרך כלל. + +בכדי לעבוד עם נתונים טבלאיים, אנו קודם צריכים ליצור או לאכלס מערך אובייקטים של מודלים, תלוי במידה ואנו רוצים להזין תוכן חדש או לעדכן תוכן קיים. לאחר מכן אנו שולפים את הקלט של המשתמש מהמשתנה `POST_$` ומגדירים אותו לכל מודל. הבדל קטן מקלט המתקבל ממודל אחד הוא שאנו שולפים את המידע על ידי שימוש ב + +~~~ +$_POST['ModelClass'][$i] +~~~ + + +במקום ב `$_POST['ModelClass']` + + +~~~ +[php] +public function actionBatchUpdate() +{ + // שליפת מידע בצורה מאסיבית + // בנחה שכל ערך הוא מודל של המחלקה `Item` + $items=$this-»getItemsToUpdate(); + if(isset($_POST['Item'])) + { + $valid=true; + foreach($items as $i=»$item) + { + if(isset($_POST['Item'][$i])) + $item-»attributes=$_POST['Item'][$i]; + $valid=$valid && $item-»validate(); + } + if($valid) // כל הערכים תקינים + // פעולות לביצוע + } + // הצג את הטופס + $this-»render('batchUpdate',array('items'=»$items)); +} +~~~ + +לאחר שיש לנו את הפעולה כתובה, אנו צריכים לעבוד על קובץ התצוגה `batchUpdate` המציג את השדות בטבלת HTML. + + +~~~ +[php] +«div class="form"» +«?php echo CHtml::beginForm(); ?» +«table» +«tr»«th»Name«/th»«th»Price«/th»«th»Count«/th»«th»Description«/th»«/tr» +«?php foreach($items as $i=»$item): ?» +«tr» +«td»«?php echo CHtml::activeTextField($item,"[$i]name"); ?»«/td» +«td»«?php echo CHtml::activeTextField($item,"[$i]price"); ?»«/td» +«td»«?php echo CHtml::activeTextField($item,"[$i]count"); ?»«/td» +«td»«?php echo CHtml::activeTextArea($item,"[$i]description"); ?»«/td» +«/tr» +«?php endforeach; ?» +«/table» + +«?php echo CHtml::submitButton('Save'); ?» +«?php echo CHtml::endForm(); ?» +«/div»«!-- form --» +~~~ + +יש לשים לב למעלה שאנו משתמשים ב + +~~~ +"[$i]name" +~~~ + +במקום ב `"name"` כפרמטר שני בעת הקריאה ל [CHtml::activeTextField]. + +במידה ויש שגיאה כלשהי באימות הנתונים, שדות הקלט המכילים את השגיאות יקבלו מחלקת CSS אוטומטית בכדי להציג שישנה שגיאה בשדה, בידיוק כמו בשימוש עם מודל אחד כפי שהוסבר קודם לכן. + «div class="revision"»$Id: form.table.txt 2161 2009-12-26 20:56:05Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/form.view.txt b/docs/guide/he/form.view.txt index 3719fbd7b..2e55b7fb4 100644 --- a/docs/guide/he/form.view.txt +++ b/docs/guide/he/form.view.txt @@ -1,88 +1,88 @@ -יצירת טופס -============= - -כתיבת קובץ התצוגה של ההתחברות הוא מאוד ברור וישיר. אנו מתחילים עם תג של פתיחת טופס `form` שהמאפיין `action` שלו צריך להיות הקישור של הפעולה של ההתחברות `login` שתוארה קודם לכן. לאחר מכן אנו מוסיפים תויות ושדות טקסט למאפיינים המוגדרים במחלקת המודל `LoginForm`. לבסוף אנו מוסיפים כפתור שליחה שניתן ללחיצה על ידי משתמשים בכדי לשלוח את הטופס. כל אלו ניתנים לביצוע על ידי קוד HTML פשוט. - -Yii מספקת כמה מחלקות המסייעות לפשט את הרכבת התצוגה. לדוגמא, בכדי ליצור שדה טקסט, אנו יכולים לקרוא ל [()CHtml::textField]; בכדי ליצור שדה תיבת בחירה, אנו יכולים לקרוא ל [()CHtml::dropDownList]. - -» Info|מידע: יש התוהים מה היתרון בשימוש במתודות אלו המסייעות לכתיבת התצוגה אם הם דורשות את אותו כמות קוד בהשוואה לכתיבת HTML טהור. התשובה לכך היא שהמתודות הללו מספקות יותר מסתם קוד HTML. -לדוגמא, הקוד הבא יוצר שדה טקסט אשר יכול לגרום לשליחת הטופס אם הערך שלו השתנה על ידי המשתמש. -» ~~~ -» [php] -» CHtml::textField($name,$value,array('submit'=»'')); -» ~~~ -» במקרה אחר זה היה דורש לכתוב קוד JS בכל מקום בדף. - -בחלק הבא, אנו משתמשים ב בכדי ליצור טופס התחברות. אנו מניחים שהמשתנה `model$` מייצג אובייקט של המחלקה `LoginForm`. - -~~~ -[php] -«div class="form"» -«?php echo CHtml::beginForm(); ?» - - «?php echo CHtml::errorSummary($model); ?» - - «div class="row"» - «?php echo CHtml::activeLabel($model,'username'); ?» - «?php echo CHtml::activeTextField($model,'username') ?» - «/div» - - «div class="row"» - «?php echo CHtml::activeLabel($model,'password'); ?» - «?php echo CHtml::activePasswordField($model,'password') ?» - «/div» - - «div class="row rememberMe"» - «?php echo CHtml::activeCheckBox($model,'rememberMe'); ?» - «?php echo CHtml::activeLabel($model,'rememberMe'); ?» - «/div» - - «div class="row submit"» - «?php echo CHtml::submitButton('Login'); ?» - «/div» - -«?php echo CHtml::endForm(); ?» -«/div»«!-- form --» -~~~ - -הקוד למעלה יוצר טופס דינאמי יותר. לדוגמא, [()CHtml::activeLabel] יוצר תוית המקושרת עם המאפיין בתוך המודל. במידה והמאפיין מכיל שגיאת קלט, מחלקת ה CSS של התוית תשתנה ל `error`, המשנה את מראה התוית עם סגנון עיצוב מתאים. בדומה, [()CHtml::activeTextField] יוצר שדה טקסט למאפיין במודל וגם כן משנה את מחלקת ה CSS של התוית במידה וישנה שגיאת קלט. - -במידה ואנחנו משתמשים בקובץ הסגנונות `form.css` המגיע ביחד עם הסקריפט `yiic`, הטופס שנוצר יראה בדומה לדוגמא הבאה: - -![עמוד ההתחברות](login1.png) - -![עמוד ההתחברות עם שגיאות](login2.png) - -החל מגרסא 1.1.1, ישנו וידג'ט חדש בשם [CActiveForm] המפשט את יצירת הטופס. הוידג'ט מסוגל לתמוך באימות נתונים רציף גם בצד הלקוח ובצד השרת. על ידי שימוש ב [CActiveForm], ניתן לכתוב את קוד התצוגה המוצג למעלה בצורה הבאה: - -~~~ -[php] -«div class="form"» -«?php $form=$this-»beginWidget('CActiveForm'); ?» - - «?php echo $form-»errorSummary($model); ?» - - «div class="row"» - «?php echo $form-»label($model,'username'); ?» - «?php echo $form-»textField($model,'username') ?» - «/div» - - «div class="row"» - «?php echo $form-»label($model,'password'); ?» - «?php echo $form-»passwordField($model,'password') ?» - «/div» - - «div class="row rememberMe"» - «?php echo $form-»checkBox($model,'rememberMe'); ?» - «?php echo $form-»label($model,'rememberMe'); ?» - «/div» - - «div class="row submit"» - «?php echo CHtml::submitButton('Login'); ?» - «/div» - -«?php $this-»endWidget(); ?» -«/div»«!-- form --» -~~~ - +יצירת טופס +============= + +כתיבת קובץ התצוגה של ההתחברות הוא מאוד ברור וישיר. אנו מתחילים עם תג של פתיחת טופס `form` שהמאפיין `action` שלו צריך להיות הקישור של הפעולה של ההתחברות `login` שתוארה קודם לכן. לאחר מכן אנו מוסיפים תויות ושדות טקסט למאפיינים המוגדרים במחלקת המודל `LoginForm`. לבסוף אנו מוסיפים כפתור שליחה שניתן ללחיצה על ידי משתמשים בכדי לשלוח את הטופס. כל אלו ניתנים לביצוע על ידי קוד HTML פשוט. + +Yii מספקת כמה מחלקות המסייעות לפשט את הרכבת התצוגה. לדוגמא, בכדי ליצור שדה טקסט, אנו יכולים לקרוא ל [()CHtml::textField]; בכדי ליצור שדה תיבת בחירה, אנו יכולים לקרוא ל [()CHtml::dropDownList]. + +» Info|מידע: יש התוהים מה היתרון בשימוש במתודות אלו המסייעות לכתיבת התצוגה אם הם דורשות את אותו כמות קוד בהשוואה לכתיבת HTML טהור. התשובה לכך היא שהמתודות הללו מספקות יותר מסתם קוד HTML. +לדוגמא, הקוד הבא יוצר שדה טקסט אשר יכול לגרום לשליחת הטופס אם הערך שלו השתנה על ידי המשתמש. +» ~~~ +» [php] +» CHtml::textField($name,$value,array('submit'=»'')); +» ~~~ +» במקרה אחר זה היה דורש לכתוב קוד JS בכל מקום בדף. + +בחלק הבא, אנו משתמשים ב בכדי ליצור טופס התחברות. אנו מניחים שהמשתנה `model$` מייצג אובייקט של המחלקה `LoginForm`. + +~~~ +[php] +«div class="form"» +«?php echo CHtml::beginForm(); ?» + + «?php echo CHtml::errorSummary($model); ?» + + «div class="row"» + «?php echo CHtml::activeLabel($model,'username'); ?» + «?php echo CHtml::activeTextField($model,'username') ?» + «/div» + + «div class="row"» + «?php echo CHtml::activeLabel($model,'password'); ?» + «?php echo CHtml::activePasswordField($model,'password') ?» + «/div» + + «div class="row rememberMe"» + «?php echo CHtml::activeCheckBox($model,'rememberMe'); ?» + «?php echo CHtml::activeLabel($model,'rememberMe'); ?» + «/div» + + «div class="row submit"» + «?php echo CHtml::submitButton('Login'); ?» + «/div» + +«?php echo CHtml::endForm(); ?» +«/div»«!-- form --» +~~~ + +הקוד למעלה יוצר טופס דינאמי יותר. לדוגמא, [()CHtml::activeLabel] יוצר תוית המקושרת עם המאפיין בתוך המודל. במידה והמאפיין מכיל שגיאת קלט, מחלקת ה CSS של התוית תשתנה ל `error`, המשנה את מראה התוית עם סגנון עיצוב מתאים. בדומה, [()CHtml::activeTextField] יוצר שדה טקסט למאפיין במודל וגם כן משנה את מחלקת ה CSS של התוית במידה וישנה שגיאת קלט. + +במידה ואנחנו משתמשים בקובץ הסגנונות `form.css` המגיע ביחד עם הסקריפט `yiic`, הטופס שנוצר יראה בדומה לדוגמא הבאה: + +![עמוד ההתחברות](login1.png) + +![עמוד ההתחברות עם שגיאות](login2.png) + +החל מגרסא 1.1.1, ישנו וידג'ט חדש בשם [CActiveForm] המפשט את יצירת הטופס. הוידג'ט מסוגל לתמוך באימות נתונים רציף גם בצד הלקוח ובצד השרת. על ידי שימוש ב [CActiveForm], ניתן לכתוב את קוד התצוגה המוצג למעלה בצורה הבאה: + +~~~ +[php] +«div class="form"» +«?php $form=$this-»beginWidget('CActiveForm'); ?» + + «?php echo $form-»errorSummary($model); ?» + + «div class="row"» + «?php echo $form-»label($model,'username'); ?» + «?php echo $form-»textField($model,'username') ?» + «/div» + + «div class="row"» + «?php echo $form-»label($model,'password'); ?» + «?php echo $form-»passwordField($model,'password') ?» + «/div» + + «div class="row rememberMe"» + «?php echo $form-»checkBox($model,'rememberMe'); ?» + «?php echo $form-»label($model,'rememberMe'); ?» + «/div» + + «div class="row submit"» + «?php echo CHtml::submitButton('Login'); ?» + «/div» + +«?php $this-»endWidget(); ?» +«/div»«!-- form --» +~~~ + «div class="revision"»$Id: form.view.txt 1751 2010-01-25 17:21:31Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/index.txt b/docs/guide/he/index.txt index 3855c6e79..f420bfa93 100644 --- a/docs/guide/he/index.txt +++ b/docs/guide/he/index.txt @@ -1,17 +1,17 @@ -המדריך המלא - Yii -================================================ - - -יש לזכור לקרוא את [תנאי השימוש](http://www.yiiframework.com/doc/terms/) במערכת זו לפני השימוש בה. - -מתרגמי המדריך הם - --------------- - - -* ואדים גבריאל [בלוג](http://www.vadimg.com) | [תמיכה בעברית](http://he.yiiframework.co.il). - -2008-2010 © Yii LLC. - - +המדריך המלא - Yii +================================================ + + +יש לזכור לקרוא את [תנאי השימוש](http://www.yiiframework.com/doc/terms/) במערכת זו לפני השימוש בה. + +מתרגמי המדריך הם + +-------------- + + +* ואדים גבריאל [בלוג](http://www.vadimg.com) | [תמיכה בעברית](http://he.yiiframework.co.il). + +2008-2010 © Yii LLC. + + «div class="revision"»$Id: index.txt 1679 2010-01-07 21:03:36Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/quickstart.first-app-yiic.txt b/docs/guide/he/quickstart.first-app-yiic.txt index a6a0d66ea..cd5234221 100644 --- a/docs/guide/he/quickstart.first-app-yiic.txt +++ b/docs/guide/he/quickstart.first-app-yiic.txt @@ -1,77 +1,77 @@ -יישום פעולות CRUD בעזרת `yiic shell` (לא נתמך) -============================================== - -» Note|הערה: יצירת הקוד בעזרת `yiic shell` יצא מכלל שימוש החל מגרסא 1.1.2. נא להשתמש ביוצר הקוד הנרחב והמבוסס ווב בשם [Gii](/doc/guide/topics.gii), במקום. - -יש לפתוח חלון פקודות, להריץ את הפקודות הרשומות מטה: - -~~~ -% cd WebRoot/testdrive -% protected/yiic shell -Yii Interactive Tool v1.1 -Please type 'help' for help. Type 'exit' to quit. -»» model User tbl_user - generate models/User.php - generate fixtures/tbl_user.php - generate unit/UserTest.php - -The following model classes are successfully generated: - User - -If you have a 'db' database connection, you can test these models now with: - $model=User::model()-»find(); - print_r($model); - -»» crud User - generate UserController.php - generate UserTest.php - mkdir D:/testdrive/protected/views/user - generate create.php - generate update.php - generate index.php - generate view.php - generate admin.php - generate _form.php - generate _view.php - -Crud 'user' has been successfully created. You may access it via: -http://hostname/path/to/index.php?r=user -~~~ - -בקוד המוצג למעלה, אנו משתמשים בפקודות `yiic shell` בכדי לתקשר עם האפליקציה שלנו. בחלון, אנו מריצים שני פקודות נוספות: `model User tbl_user` ו `crud User`. הראשון יוצר מחלקת מודל בשם `User` עבור הטבלה `tbl_user`, בזמן שהפקודה השנייה מנתחת את המודל `User` ויוצרת את הקוד המיישם את פעולות ה CRUD השונות. - -» Note|הערה: יתכן ותתקל בשגיאות כמו `could not find driver...`, למרות שבעת ביצוע בדיקות הדרישות של מערכת Yii מצויין שה-PDO פעיל. במידה וזה קורה, תוכל לנסות להריץ את הכלי `yiic` בצורה הבאה, -» ~~~ -» % php -c path/to/php.ini protected/yiic.php shell -» ~~~ -» -» כש `path/to/php.ini` מייצג את קובץ הגדרות ה-PHP הנכון. - -כעת נוכל לראות את העבודה בפעולה שהרגע יצרנו: - -~~~ -http://hostname/testdrive/index.php?r=user -~~~ - -זה יציג רשימה של רשומות מטבלת `tbl_user`. - -לחץ על כפתור `Create User` בעמוד. אנו נגיע לעמוד ההתחברות אם עדיין לא התחברנו. לאחר ההתחברות, אנו נראה טופס המאפשר לנו להוסיף משתמש חדש. יש להשלים את הטופס וללחוץ על כפתור `Create`. במידה וישנם שגיאות בשדות, תופיע שגיאה שתמנע מאתנו לשלוח את הטופס. בחזרה לעמוד רשימת המשתמשים, כעת אנו נוכל לראות את המשתמש שהרגע הוספנו מופיע ברשימה. - -ניתן לחזור על הפעולות למעלה בכדי להוסיף משתמשים נוספים. שים לב שעמוד תצוגת המשתמשים יציג עמודים באופן אוטומטי אם ישנם יותר מדי משתמשים לתצוגה בעמוד אחד. - -אם אנו נתחבר כמנהלים ראשיים בעזרת הפרטים `admin/admin`, אנו נוכל לצפות בעמוד ניהול המשתמשים בקישור הבא: - -~~~ -http://hostname/testdrive/index.php?r=user/admin -~~~ - -עמוד זה יציג לנו את רשימת המשתמשים בתצוגה טבלאית. אנו יכולים ללחוץ על אחד מהכותרות בטבלה בכדי למיין את הטבלה על פי אותה כותרת שלחצנו הרגע. אנו יכולים ללחוץ על כל אחד מהמידע בשורות בכדי לצפות, לעדכן או למחוק את המידע בשורה הנ"ל. אנו יכולים לדפדף בין עמודים. כמו כן, אנו יכולים לסנן ולחפש אחר המידע אותו אנו רוצים לראות. - -כל האפשרויות הללו מגיעות ללא שום צורך בכתיבת שורת קוד אחת. - -![עמוד ניהול משתמשים](first-app6.png) - -![עמוד הוספת משתמש חדש](first-app7.png) - - +יישום פעולות CRUD בעזרת `yiic shell` (לא נתמך) +============================================== + +» Note|הערה: יצירת הקוד בעזרת `yiic shell` יצא מכלל שימוש החל מגרסא 1.1.2. נא להשתמש ביוצר הקוד הנרחב והמבוסס ווב בשם [Gii](/doc/guide/topics.gii), במקום. + +יש לפתוח חלון פקודות, להריץ את הפקודות הרשומות מטה: + +~~~ +% cd WebRoot/testdrive +% protected/yiic shell +Yii Interactive Tool v1.1 +Please type 'help' for help. Type 'exit' to quit. +»» model User tbl_user + generate models/User.php + generate fixtures/tbl_user.php + generate unit/UserTest.php + +The following model classes are successfully generated: + User + +If you have a 'db' database connection, you can test these models now with: + $model=User::model()-»find(); + print_r($model); + +»» crud User + generate UserController.php + generate UserTest.php + mkdir D:/testdrive/protected/views/user + generate create.php + generate update.php + generate index.php + generate view.php + generate admin.php + generate _form.php + generate _view.php + +Crud 'user' has been successfully created. You may access it via: +http://hostname/path/to/index.php?r=user +~~~ + +בקוד המוצג למעלה, אנו משתמשים בפקודות `yiic shell` בכדי לתקשר עם האפליקציה שלנו. בחלון, אנו מריצים שני פקודות נוספות: `model User tbl_user` ו `crud User`. הראשון יוצר מחלקת מודל בשם `User` עבור הטבלה `tbl_user`, בזמן שהפקודה השנייה מנתחת את המודל `User` ויוצרת את הקוד המיישם את פעולות ה CRUD השונות. + +» Note|הערה: יתכן ותתקל בשגיאות כמו `could not find driver...`, למרות שבעת ביצוע בדיקות הדרישות של מערכת Yii מצויין שה-PDO פעיל. במידה וזה קורה, תוכל לנסות להריץ את הכלי `yiic` בצורה הבאה, +» ~~~ +» % php -c path/to/php.ini protected/yiic.php shell +» ~~~ +» +» כש `path/to/php.ini` מייצג את קובץ הגדרות ה-PHP הנכון. + +כעת נוכל לראות את העבודה בפעולה שהרגע יצרנו: + +~~~ +http://hostname/testdrive/index.php?r=user +~~~ + +זה יציג רשימה של רשומות מטבלת `tbl_user`. + +לחץ על כפתור `Create User` בעמוד. אנו נגיע לעמוד ההתחברות אם עדיין לא התחברנו. לאחר ההתחברות, אנו נראה טופס המאפשר לנו להוסיף משתמש חדש. יש להשלים את הטופס וללחוץ על כפתור `Create`. במידה וישנם שגיאות בשדות, תופיע שגיאה שתמנע מאתנו לשלוח את הטופס. בחזרה לעמוד רשימת המשתמשים, כעת אנו נוכל לראות את המשתמש שהרגע הוספנו מופיע ברשימה. + +ניתן לחזור על הפעולות למעלה בכדי להוסיף משתמשים נוספים. שים לב שעמוד תצוגת המשתמשים יציג עמודים באופן אוטומטי אם ישנם יותר מדי משתמשים לתצוגה בעמוד אחד. + +אם אנו נתחבר כמנהלים ראשיים בעזרת הפרטים `admin/admin`, אנו נוכל לצפות בעמוד ניהול המשתמשים בקישור הבא: + +~~~ +http://hostname/testdrive/index.php?r=user/admin +~~~ + +עמוד זה יציג לנו את רשימת המשתמשים בתצוגה טבלאית. אנו יכולים ללחוץ על אחד מהכותרות בטבלה בכדי למיין את הטבלה על פי אותה כותרת שלחצנו הרגע. אנו יכולים ללחוץ על כל אחד מהמידע בשורות בכדי לצפות, לעדכן או למחוק את המידע בשורה הנ"ל. אנו יכולים לדפדף בין עמודים. כמו כן, אנו יכולים לסנן ולחפש אחר המידע אותו אנו רוצים לראות. + +כל האפשרויות הללו מגיעות ללא שום צורך בכתיבת שורת קוד אחת. + +![עמוד ניהול משתמשים](first-app6.png) + +![עמוד הוספת משתמש חדש](first-app7.png) + + «div class="revision"»$Id: quickstart.first-app-yiic.txt 2098 2010-04-11 03:54:25Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/quickstart.first-app.txt b/docs/guide/he/quickstart.first-app.txt index 4c493d21e..d352f9ac8 100644 --- a/docs/guide/he/quickstart.first-app.txt +++ b/docs/guide/he/quickstart.first-app.txt @@ -1,204 +1,204 @@ -יצירת אפליקצית Yii ראשונה -============================== - -בכדי לקבל ניסיון התחלתי עם Yii, אנו מתארים בחלק זה כיצד ליצור את האפליקציה הראשונה שלנו ב Yii. אנו נשתמש בכלי `yiic` המאפשר יצירה אוטומטית של קוד בעבור משימות מסויימות. לנוחות, אנו מניחים ש `YiiRoot` הינה התיקיה בה Yii מותקן, ו `WebRoot` הינה התיקיה הראשית של שרת הווב שלנו. - -יש להריץ את `yiic` בשורת הפקודות בצורה הבאה: - -~~~ -% YiiRoot/framework/yiic webapp WebRoot/testdrive -~~~ - -» Note|הערה: בעת הרצת `yiic` על גבי מחשבים Mac OS, Linux או Unix, יהיה צורך בשינוי הרשאות של קובץ ה `yiic` בכדי שיהיה ניתן להריץ אותו. לחלופין, ניתן להריץ את הכלי בצורה הבאה, -» ~~~ -» % cd WebRoot/testdrive -» % php YiiRoot/framework/yiic.php webapp WebRoot/testdrive -» ~~~ - -זה יצור שלד לאפליקצית Yii תחת התיקיה `WebRoot/testdrive`. לאפליקציה יש מבנה של תקיות שנחוץ למרבית אפליקציות ה Yii. - -ללא צורך בכתיבת שורת קוד אחת, אנו יכולים לנסות את האפליקציה שיצרנו כרגע על ידי כניסה לקישור הבא בדפדפן: - -~~~ -http://hostname/testdrive/index.php -~~~ - -כפי שניתן לראות, לאפליקציה יש ארבע עמודים: עמוד הבית, עמוד האודות, עמוד ההתחברות ועמוד היצירת קשר. עמוד יצירת הקשר מציג טופס יצירת קשר שמשתמש יכול להזין ולשלוח למנהל האתר, ועמוד ההתחברות מאפשר למשתמש לאמת את עצמו לפני שהוא ניגש לתוכן שהוא מוגבל לבעלי גישה בלבד. - -![עמוד הבית](first-app1.png) - -![עמוד יצירת הקשר](first-app2.png) - -![עמוד יצירת קשר עם שגיאות](first-app3.png) - -![עמוד יצירת קשר לאחר שליחת הטופס בהצלחה](first-app4.png) - -![עמוד התחברות](first-app5.png) - - -הדיאגרמה הבאה מציגה את מבנה התיקיות של האפליקציה שלנו. -אנא קרא אודות [מוסכמות](/doc/guide/basics.convention#directory) בכדי לקבל הסבר מדוייק למבנה זה. - -~~~ -testdrive/ - index.php קובץ הכניסה הראשי של האפליקציה - index-test.php קובץ הכניסה הראשי למטרת בדיקות - assets/ מכיל קבצי נכסים הנגישים למשתמשים - css/ מכיל קבצי css - images/ מכיל תמונות - themes/ מכיל תבניות עיצוב לאפליקציה - protected/ מכיל קבצים מוגנים של האפליקציה - yiic קובץ פקודות עבור לינוקס ויוניקס - yiic.bat קובץ פקודות עבור וינדווס - yiic.php קובץ פקודות PHP - commands/ מכיל פקודות 'yiic' מותאמים אישית - shell/ מכיל פקודות 'yiic shell' מותאמים אישית - components/ מכיל רכיבים אשר משתמשים בהם שוב ושוב - Controller.php מחלקת הבסיס לכל הקונטרולרים - Identity.php מחלקת הזיהוי של משתמשים - config/ מכיל קבצי הגדרות - console.php קובץ הגדרות לאפליקציה דרך מסוף - main.php קובץ הגדרות לאפליקצית ווב - test.php קובץ הגדרות לבדיקות פונקצנליות - controllers/ מכיל קבצי מחלקות קונטרולר - SiteController.php קונטרולר ברירת המחדל - data/ מכיל את מסדי הנתונים לדוגמא - schema.mysql.sql תרשים מסד הנתונים של MySQL - schema.sqlite.sql תרשים מסד נתונים של SQLite - testdrive.db קובץ מסד הנתונים לדוגמא של SQLite - extensions/ מכיל הרחבות צד שלישי - messages/ מכיל קבצי תרגום - models/ מכיל קבצי מחלקות המודל - LoginForm.php מודל הטופס לפעולה של התחברות - ContactForm.php מודל הטופס לפעולה של יצירת קשר - runtime/ מכיל קבצים זמניים - tests/ מכיל קבצי סקריפט לבדיקה - views/ מכיל קבצי תצוגה ותבניות של קונטרולרים - layouts/ מכיל קבצי תבניות - main.php התבנית הראשית אשר מופיע בכל העמודים - column1.php תבנית לעמודים אשר משתמשים בעיצוב של עמודה אחת - column2.php תבנית לעמודים אשר משתמשים בעיצוב של שני עמודות - site/ מכיל קבצי תצוגה לקונטרולר 'site' - pages/ מכיל עמודים סטטיים - about.php קובץ התצוגה לעמוד אודות - contact.php קובץ תצוגה לעמוד יצירת קשר - error.php קובץ תצוגה להצגת שגיאות במידה ויש - index.php קובץ תצוגה לפעולה 'index' - login.php קובץ תצוגה לפעולה 'login' -~~~ - -התחברות למסד נתונים ----------------------- - -מרבית אפליקציות הווב מגובות בעזרת מסד נתונים. אפליקצית הניסיון שלנו היא אחת מהם. בכדי להשתמש במסד נתונים, אנו צריכים להגדיר לאפליקציה כיצד להתחבר אליה. זה נעשה בקובץ הגדרות האפליקציה אשר ממוקם `WebRoot/testdrive/protected/config/main.php`, המובלט בצורה הבאה, - -~~~ -[php] -return array( - ...... - 'components'=»array( - ...... - 'db'=»array( - 'connectionString'=»'sqlite:protected/data/testdrive.db', - ), - ), - ...... -); -~~~ - -הקוד למעלה מנחה את Yii שהאפליקציה צריכה להתחבר למסד נתונים SQLite תחת `WebRoot/testdrive/protected/data/testdrive.db` בעת הצורך. מסד הנתונים SQLite כבר כלול בשלד האפליקציה שיצרנו זה עתה. מסד נתונים מכיל טבלה אחת בלבד בשם `tbl_user`: - -~~~ -[sql] -CREATE TABLE tbl_user ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - username VARCHAR(128) NOT NULL, - password VARCHAR(128) NOT NULL, - email VARCHAR(128) NOT NULL -); -~~~ - -במידה והינך רוצה לעבוד עם מסד נתונים מסוג MySQL במקום, תוכל להשתמש בקובץ התרשים של MySQL המצורף הנמצא ב `WebRoot/testdrive/protected/data/schema.mysql.sql` בכדי ליצור את מסד הנתונים. - -» Note|הערה: בכדי להשתמש באפשריות מסדי הנתונים, אנו צריכים להתקין ולהפעיל את תוסף ה PHP בשם PDO ואת התוספים הספציפים לדרייברים בהם אנו נשתמש. בעבור אפליקצית הבדיקה הזו אנו צריכים להפעיל את התוספים `php_pdo` ו `php_pdo_sqlite`. - -יישום פעולו CRUD ----------------------------- - -עכשיו זה החלק המהנה. אנו נרצה ליישם את פעולות ה-CRUD שזה קיצור של יצירה, קריאה, עדכון, מחיקה (Create, Read, Update, Delete) עבור הטבלה `User` שהרגע יצרנו. אפשרות זו הכרחית עבור אפליקציות אשר תלויות במשתמשים במערכת. במקום לבזבז את הזמן לכתיבת הקוד עצמו, אנו נשתמש ב-Gii -- מחולל קוד מבוסס אינטרנט (ווב). - -» Info|מידע: Gii קיים החל מגרסא 1.1.2. עבור גרסאות ישנות יותר, אנו יכולים להשתמש בכלי `yiic` בכדי להשיג את אותה המטרה. למידע נוסף, אנא עיין במדריך ליצירת פעולות CRUD על ידי הכלי `yiic`. [יישום פעולות CRUD בעזרת yiic](/doc/guide/quickstart.first-app-yiic) - -### הגדרת Gii - -בכדי להשתמש ב Gii, אנו קודם צריכים לערוך את הקובץ `WebRoot/testdrive/protected/config/main.php`, הידוע בשמו כ-קובץ [הגדרות](/doc/guide/basics.application#application-configuration) האפליקציה. - -~~~ -[php] -return array( - ...... - 'import'=»array( - 'application.models.*', - 'application.components.*', - ), - - 'modules'=»array( - 'gii'=»array( - 'class'=»'system.gii.GiiModule', - 'password'=»'יש להזין סיסמא כאן', - ), - ), -); -~~~ - -לאחר מכן, יש לגשת לקישור `http://hostname/testdrive/index.php?r=gii`. אנו נראה מסך המבקש מאתנו להזין סיסמא, אותה סיסמא שציינו בשלב הקודם בקוד המוצג למעלה. - -### יצירת מודל משתמשים - -לאחר ההתחברות, יש ללחוץ על הקישור `Model Generator`. זה יביא אותנו לעמוד יצירת מודל הבא, - -![יצירת מודל](gii-model.png) - -בשדה של `Table Name` יש להזין `tbl_user`. בשדה של `Model Class`, יש להזין `User`. לאחר מכן יש ללחוץ על כפתור `Preview`. זה יציג לנו את הקוד שיווצר. כעת יש ללחוץ על כפתור `Generate`. קובץ חדש בשם `User.php` יווצר תחת התיקיה `protected/models`. כפי שאנו נסביר במהלך המדריך הזה, מחלקת המודל `User` שהרגע יצרנו מאפשרת לנו לתקשר עם הטבלה `tbl_user` אשר נמצאת תחת המחלקה באופן מונחה עצמים. - -### יצירת קוד CRUD - -לאחר יצירת קובץ מחלקת המודל, אנו ניצור את הקוד אשר מיישם את פעולות ה CRUD עבור המשתמש. אנו בוחרים `במחולל ה-CRUD` במערכת ה-Gii, כפי שמוצג בתמונה למטה, - -![מחולל CRUD](gii-crud.png) - -בשדה `Model Class`, יש להזין `User`. בשדה `Controller ID`, יש להזין `user` (באותיות קטנות בלבד). עכשיו יש ללחוץ על כפתור התצוגה מקדימה - `Preview` ולאחר מכן על כפתור היצירה - `Generate`. סיימנו עם יצירת הקוד עבור פעולות ה-CRUD. - -### גישה לעמודים הנוצרו על ידי CRUD - -הבא ונראה את התוצאה הסופית של העבודה שנעשתה: - -~~~ -http://hostname/testdrive/index.php?r=user -~~~ - -עמוד זה יציג רשימה של רשומות משתמשים מהטבלה `tbl_user`. - -לחץ על כפתור ה `Create User` בעמוד. אנו נעבור לעמוד ההתחברות במידה ולא התחברנו עדיין. לאחר ההחברות, אנו נראה טופס שמאפשר לנו להוסיף משתמש חדש. מלא את הטופס ולחץ על כפתור `Create`. -במידה וישנם שגיאות כלשהם בטופס או בתוכן שהוזן, תוצג שגיאה שתמנע מאיתנו לשלוח את הטופס. בחזרה לעמוד רשימת המשתמשים, אנו אמורים לראות את המשתמשים החדשים אותם יצרנו ברשימה. - -חזור על השלבים למעלה כדי להוסיף עוד משתמשים. שים לב שעמוד רשימת המשתמשים יציג עמודים אוטומטית אם ישנם יותר מדי משתמשים המוצגים בעמוד אחד. - -אם נתחבר כמנהלים עם הפרטים `admin/admin`, אנו נוכל לצפות בעמוד ניהול המשתמשים בקישור הבא: - -~~~ -http://hostname/testdrive/index.php?r=user/admin -~~~ - -עמוד זה יציג את רשימת המשתמשים בפורמט טבלאי. אנו נוכל ללחוץ על כותרות העמודות בכדי למיין את הרשימה לפי אותה עמודה עליה לחצנו. נוכל ללחוץ על הכפתורים בכל רשומה בכדי לצפות, לערוך או למחוק את אותה רשומה. -אנו יכולים לצפות בעמודים שונים. כמו כן אנו יכולים לסנן ולחפש אחר מידע אותו אנו מחפשים. - -כל האפשרויות הללו מגיעות ללא צורך בכתיבת שורת קוד אחת! - -![עמוד ניהול משתמשים](first-app6.png) - -![עמוד יצירת משתמש חדש](first-app7.png) - - - +יצירת אפליקצית Yii ראשונה +============================== + +בכדי לקבל ניסיון התחלתי עם Yii, אנו מתארים בחלק זה כיצד ליצור את האפליקציה הראשונה שלנו ב Yii. אנו נשתמש בכלי `yiic` המאפשר יצירה אוטומטית של קוד בעבור משימות מסויימות. לנוחות, אנו מניחים ש `YiiRoot` הינה התיקיה בה Yii מותקן, ו `WebRoot` הינה התיקיה הראשית של שרת הווב שלנו. + +יש להריץ את `yiic` בשורת הפקודות בצורה הבאה: + +~~~ +% YiiRoot/framework/yiic webapp WebRoot/testdrive +~~~ + +» Note|הערה: בעת הרצת `yiic` על גבי מחשבים Mac OS, Linux או Unix, יהיה צורך בשינוי הרשאות של קובץ ה `yiic` בכדי שיהיה ניתן להריץ אותו. לחלופין, ניתן להריץ את הכלי בצורה הבאה, +» ~~~ +» % cd WebRoot/testdrive +» % php YiiRoot/framework/yiic.php webapp WebRoot/testdrive +» ~~~ + +זה יצור שלד לאפליקצית Yii תחת התיקיה `WebRoot/testdrive`. לאפליקציה יש מבנה של תקיות שנחוץ למרבית אפליקציות ה Yii. + +ללא צורך בכתיבת שורת קוד אחת, אנו יכולים לנסות את האפליקציה שיצרנו כרגע על ידי כניסה לקישור הבא בדפדפן: + +~~~ +http://hostname/testdrive/index.php +~~~ + +כפי שניתן לראות, לאפליקציה יש ארבע עמודים: עמוד הבית, עמוד האודות, עמוד ההתחברות ועמוד היצירת קשר. עמוד יצירת הקשר מציג טופס יצירת קשר שמשתמש יכול להזין ולשלוח למנהל האתר, ועמוד ההתחברות מאפשר למשתמש לאמת את עצמו לפני שהוא ניגש לתוכן שהוא מוגבל לבעלי גישה בלבד. + +![עמוד הבית](first-app1.png) + +![עמוד יצירת הקשר](first-app2.png) + +![עמוד יצירת קשר עם שגיאות](first-app3.png) + +![עמוד יצירת קשר לאחר שליחת הטופס בהצלחה](first-app4.png) + +![עמוד התחברות](first-app5.png) + + +הדיאגרמה הבאה מציגה את מבנה התיקיות של האפליקציה שלנו. +אנא קרא אודות [מוסכמות](/doc/guide/basics.convention#directory) בכדי לקבל הסבר מדוייק למבנה זה. + +~~~ +testdrive/ + index.php קובץ הכניסה הראשי של האפליקציה + index-test.php קובץ הכניסה הראשי למטרת בדיקות + assets/ מכיל קבצי נכסים הנגישים למשתמשים + css/ מכיל קבצי css + images/ מכיל תמונות + themes/ מכיל תבניות עיצוב לאפליקציה + protected/ מכיל קבצים מוגנים של האפליקציה + yiic קובץ פקודות עבור לינוקס ויוניקס + yiic.bat קובץ פקודות עבור וינדווס + yiic.php קובץ פקודות PHP + commands/ מכיל פקודות 'yiic' מותאמים אישית + shell/ מכיל פקודות 'yiic shell' מותאמים אישית + components/ מכיל רכיבים אשר משתמשים בהם שוב ושוב + Controller.php מחלקת הבסיס לכל הקונטרולרים + Identity.php מחלקת הזיהוי של משתמשים + config/ מכיל קבצי הגדרות + console.php קובץ הגדרות לאפליקציה דרך מסוף + main.php קובץ הגדרות לאפליקצית ווב + test.php קובץ הגדרות לבדיקות פונקצנליות + controllers/ מכיל קבצי מחלקות קונטרולר + SiteController.php קונטרולר ברירת המחדל + data/ מכיל את מסדי הנתונים לדוגמא + schema.mysql.sql תרשים מסד הנתונים של MySQL + schema.sqlite.sql תרשים מסד נתונים של SQLite + testdrive.db קובץ מסד הנתונים לדוגמא של SQLite + extensions/ מכיל הרחבות צד שלישי + messages/ מכיל קבצי תרגום + models/ מכיל קבצי מחלקות המודל + LoginForm.php מודל הטופס לפעולה של התחברות + ContactForm.php מודל הטופס לפעולה של יצירת קשר + runtime/ מכיל קבצים זמניים + tests/ מכיל קבצי סקריפט לבדיקה + views/ מכיל קבצי תצוגה ותבניות של קונטרולרים + layouts/ מכיל קבצי תבניות + main.php התבנית הראשית אשר מופיע בכל העמודים + column1.php תבנית לעמודים אשר משתמשים בעיצוב של עמודה אחת + column2.php תבנית לעמודים אשר משתמשים בעיצוב של שני עמודות + site/ מכיל קבצי תצוגה לקונטרולר 'site' + pages/ מכיל עמודים סטטיים + about.php קובץ התצוגה לעמוד אודות + contact.php קובץ תצוגה לעמוד יצירת קשר + error.php קובץ תצוגה להצגת שגיאות במידה ויש + index.php קובץ תצוגה לפעולה 'index' + login.php קובץ תצוגה לפעולה 'login' +~~~ + +התחברות למסד נתונים +---------------------- + +מרבית אפליקציות הווב מגובות בעזרת מסד נתונים. אפליקצית הניסיון שלנו היא אחת מהם. בכדי להשתמש במסד נתונים, אנו צריכים להגדיר לאפליקציה כיצד להתחבר אליה. זה נעשה בקובץ הגדרות האפליקציה אשר ממוקם `WebRoot/testdrive/protected/config/main.php`, המובלט בצורה הבאה, + +~~~ +[php] +return array( + ...... + 'components'=»array( + ...... + 'db'=»array( + 'connectionString'=»'sqlite:protected/data/testdrive.db', + ), + ), + ...... +); +~~~ + +הקוד למעלה מנחה את Yii שהאפליקציה צריכה להתחבר למסד נתונים SQLite תחת `WebRoot/testdrive/protected/data/testdrive.db` בעת הצורך. מסד הנתונים SQLite כבר כלול בשלד האפליקציה שיצרנו זה עתה. מסד נתונים מכיל טבלה אחת בלבד בשם `tbl_user`: + +~~~ +[sql] +CREATE TABLE tbl_user ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + username VARCHAR(128) NOT NULL, + password VARCHAR(128) NOT NULL, + email VARCHAR(128) NOT NULL +); +~~~ + +במידה והינך רוצה לעבוד עם מסד נתונים מסוג MySQL במקום, תוכל להשתמש בקובץ התרשים של MySQL המצורף הנמצא ב `WebRoot/testdrive/protected/data/schema.mysql.sql` בכדי ליצור את מסד הנתונים. + +» Note|הערה: בכדי להשתמש באפשריות מסדי הנתונים, אנו צריכים להתקין ולהפעיל את תוסף ה PHP בשם PDO ואת התוספים הספציפים לדרייברים בהם אנו נשתמש. בעבור אפליקצית הבדיקה הזו אנו צריכים להפעיל את התוספים `php_pdo` ו `php_pdo_sqlite`. + +יישום פעולו CRUD +---------------------------- + +עכשיו זה החלק המהנה. אנו נרצה ליישם את פעולות ה-CRUD שזה קיצור של יצירה, קריאה, עדכון, מחיקה (Create, Read, Update, Delete) עבור הטבלה `User` שהרגע יצרנו. אפשרות זו הכרחית עבור אפליקציות אשר תלויות במשתמשים במערכת. במקום לבזבז את הזמן לכתיבת הקוד עצמו, אנו נשתמש ב-Gii -- מחולל קוד מבוסס אינטרנט (ווב). + +» Info|מידע: Gii קיים החל מגרסא 1.1.2. עבור גרסאות ישנות יותר, אנו יכולים להשתמש בכלי `yiic` בכדי להשיג את אותה המטרה. למידע נוסף, אנא עיין במדריך ליצירת פעולות CRUD על ידי הכלי `yiic`. [יישום פעולות CRUD בעזרת yiic](/doc/guide/quickstart.first-app-yiic) + +### הגדרת Gii + +בכדי להשתמש ב Gii, אנו קודם צריכים לערוך את הקובץ `WebRoot/testdrive/protected/config/main.php`, הידוע בשמו כ-קובץ [הגדרות](/doc/guide/basics.application#application-configuration) האפליקציה. + +~~~ +[php] +return array( + ...... + 'import'=»array( + 'application.models.*', + 'application.components.*', + ), + + 'modules'=»array( + 'gii'=»array( + 'class'=»'system.gii.GiiModule', + 'password'=»'יש להזין סיסמא כאן', + ), + ), +); +~~~ + +לאחר מכן, יש לגשת לקישור `http://hostname/testdrive/index.php?r=gii`. אנו נראה מסך המבקש מאתנו להזין סיסמא, אותה סיסמא שציינו בשלב הקודם בקוד המוצג למעלה. + +### יצירת מודל משתמשים + +לאחר ההתחברות, יש ללחוץ על הקישור `Model Generator`. זה יביא אותנו לעמוד יצירת מודל הבא, + +![יצירת מודל](gii-model.png) + +בשדה של `Table Name` יש להזין `tbl_user`. בשדה של `Model Class`, יש להזין `User`. לאחר מכן יש ללחוץ על כפתור `Preview`. זה יציג לנו את הקוד שיווצר. כעת יש ללחוץ על כפתור `Generate`. קובץ חדש בשם `User.php` יווצר תחת התיקיה `protected/models`. כפי שאנו נסביר במהלך המדריך הזה, מחלקת המודל `User` שהרגע יצרנו מאפשרת לנו לתקשר עם הטבלה `tbl_user` אשר נמצאת תחת המחלקה באופן מונחה עצמים. + +### יצירת קוד CRUD + +לאחר יצירת קובץ מחלקת המודל, אנו ניצור את הקוד אשר מיישם את פעולות ה CRUD עבור המשתמש. אנו בוחרים `במחולל ה-CRUD` במערכת ה-Gii, כפי שמוצג בתמונה למטה, + +![מחולל CRUD](gii-crud.png) + +בשדה `Model Class`, יש להזין `User`. בשדה `Controller ID`, יש להזין `user` (באותיות קטנות בלבד). עכשיו יש ללחוץ על כפתור התצוגה מקדימה - `Preview` ולאחר מכן על כפתור היצירה - `Generate`. סיימנו עם יצירת הקוד עבור פעולות ה-CRUD. + +### גישה לעמודים הנוצרו על ידי CRUD + +הבא ונראה את התוצאה הסופית של העבודה שנעשתה: + +~~~ +http://hostname/testdrive/index.php?r=user +~~~ + +עמוד זה יציג רשימה של רשומות משתמשים מהטבלה `tbl_user`. + +לחץ על כפתור ה `Create User` בעמוד. אנו נעבור לעמוד ההתחברות במידה ולא התחברנו עדיין. לאחר ההחברות, אנו נראה טופס שמאפשר לנו להוסיף משתמש חדש. מלא את הטופס ולחץ על כפתור `Create`. +במידה וישנם שגיאות כלשהם בטופס או בתוכן שהוזן, תוצג שגיאה שתמנע מאיתנו לשלוח את הטופס. בחזרה לעמוד רשימת המשתמשים, אנו אמורים לראות את המשתמשים החדשים אותם יצרנו ברשימה. + +חזור על השלבים למעלה כדי להוסיף עוד משתמשים. שים לב שעמוד רשימת המשתמשים יציג עמודים אוטומטית אם ישנם יותר מדי משתמשים המוצגים בעמוד אחד. + +אם נתחבר כמנהלים עם הפרטים `admin/admin`, אנו נוכל לצפות בעמוד ניהול המשתמשים בקישור הבא: + +~~~ +http://hostname/testdrive/index.php?r=user/admin +~~~ + +עמוד זה יציג את רשימת המשתמשים בפורמט טבלאי. אנו נוכל ללחוץ על כותרות העמודות בכדי למיין את הרשימה לפי אותה עמודה עליה לחצנו. נוכל ללחוץ על הכפתורים בכל רשומה בכדי לצפות, לערוך או למחוק את אותה רשומה. +אנו יכולים לצפות בעמודים שונים. כמו כן אנו יכולים לסנן ולחפש אחר מידע אותו אנו מחפשים. + +כל האפשרויות הללו מגיעות ללא צורך בכתיבת שורת קוד אחת! + +![עמוד ניהול משתמשים](first-app6.png) + +![עמוד יצירת משתמש חדש](first-app7.png) + + + «div class="revision"»$Id: quickstart.first-app.txt 2125 2010-01-21 16:54:29Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/quickstart.installation.txt b/docs/guide/he/quickstart.installation.txt index b34ca01a0..2e6b75f98 100644 --- a/docs/guide/he/quickstart.installation.txt +++ b/docs/guide/he/quickstart.installation.txt @@ -1,28 +1,28 @@ -התקנה -============ - -התקנת מערכת ה Yii כוללת את הצעדים הבאים: - - 1. הורדת המערכת מהאתר הרשמי [yiiframework.com](http://www.yiiframework.com/). - 2. חלץ את קבצי המערכת אל תיקיה אשר נתן לגשת אליה דרך הדפדפן - -» Tip|טיפ: מערכת ה Yii אינה צריכה להיות תחת תיקיה ציבורים בשרת אשר כל משתמש יכול לגשת אליה. -אפליקציה הכתובה בעזרת Yii מכילה קובץ הרצה אחד שרק הוא אמור להיות תחת תיקיה ציבורית אשר חשופה לכולם. -תיקיות וקבצים אחרים הכוללים את התיקיה של Yii צריכים להיות תחת תיקיה שלא ניתן יהיה לגשת אליה ומוגדרת בתור תיקיה פרטים ולא ציבורים, מאחר וזה יכול לגרום -לבעיות אבטחה. - -דרישות ------------- - -לאחר התקנת המערכת Yii, תרצה לאמת שהשרת שבו אתה מריץ אותו תומך בכל הדרישות של המערכת. -תוכל לעשות זאת על ידי כניסה דרך הדפדפן לסקריפט הבדיקה שמגיע עם ההתקנה של המערכת וניתן להגיע אליו בקישור הבא: - -~~~ -http://hostname/path/to/yii/requirements/index.php -~~~ - -דרישות המינימום של Yii הם שהשרת יתמוך ב PHP 5.1.0 ומעלה. Yii נבדק עם גבי שרת [Apache HTTP -server](http://httpd.apache.org/) על גבי מערכת ההפעלה מסוג וינדוס ולינוקס. -כמו כן ניתן להריץ את Yii על גבי שרתים אחרים התומכים בגרסא ההכרחית של PHP 5. - +התקנה +============ + +התקנת מערכת ה Yii כוללת את הצעדים הבאים: + + 1. הורדת המערכת מהאתר הרשמי [yiiframework.com](http://www.yiiframework.com/). + 2. חלץ את קבצי המערכת אל תיקיה אשר נתן לגשת אליה דרך הדפדפן + +» Tip|טיפ: מערכת ה Yii אינה צריכה להיות תחת תיקיה ציבורים בשרת אשר כל משתמש יכול לגשת אליה. +אפליקציה הכתובה בעזרת Yii מכילה קובץ הרצה אחד שרק הוא אמור להיות תחת תיקיה ציבורית אשר חשופה לכולם. +תיקיות וקבצים אחרים הכוללים את התיקיה של Yii צריכים להיות תחת תיקיה שלא ניתן יהיה לגשת אליה ומוגדרת בתור תיקיה פרטים ולא ציבורים, מאחר וזה יכול לגרום +לבעיות אבטחה. + +דרישות +------------ + +לאחר התקנת המערכת Yii, תרצה לאמת שהשרת שבו אתה מריץ אותו תומך בכל הדרישות של המערכת. +תוכל לעשות זאת על ידי כניסה דרך הדפדפן לסקריפט הבדיקה שמגיע עם ההתקנה של המערכת וניתן להגיע אליו בקישור הבא: + +~~~ +http://hostname/path/to/yii/requirements/index.php +~~~ + +דרישות המינימום של Yii הם שהשרת יתמוך ב PHP 5.1.0 ומעלה. Yii נבדק עם גבי שרת [Apache HTTP +server](http://httpd.apache.org/) על גבי מערכת ההפעלה מסוג וינדוס ולינוקס. +כמו כן ניתן להריץ את Yii על גבי שרתים אחרים התומכים בגרסא ההכרחית של PHP 5. + «div class="revision"»$Id: quickstart.installation.txt 1622 2009-12-26 20:56:05Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/quickstart.what-is-yii.txt b/docs/guide/he/quickstart.what-is-yii.txt index ff73a5e6b..6fc845ea3 100644 --- a/docs/guide/he/quickstart.what-is-yii.txt +++ b/docs/guide/he/quickstart.what-is-yii.txt @@ -1,26 +1,26 @@ -מה זה Yii -=========== - -Yii הינה פריימוורק PHP המבוסס על רכיבים, בעל ביצועים גבוהים המאפשר פיתוח של אפליקציות ווב גדולות וכבדות במהירות ויעילות. הוא מאפשר את המקסימום בפיתוח אפליקציות ווב ויכול לזרז בצורה משמעותית את תהליך הפיתוח של האפליקציה. השם Yii (המבוטא כ 'Yee' או `[Ji:]`) שמשמעותו *קל*, *יעיל* ו *ניתן להרחבה*. - -דרישות ------------- - -בכדי להריץ אפליקציות אשר בנויות על גבי Yii, יש צורך בשרת התומך ב PHP 5.1.0 או יותר. - -למפתחים אשר רוצים להשתמש ב Yii, הבנת תכנות מונחה עצמים (OOP) מאוד יעזור, מאחר ו Yii הינה פריימוורק הבנוי כולה ב OOP. - -מה השימוש הטוב ביותר עבור Yii? ---------------------- - -Yii הינה פריימורק כללי המאפשר פיתוח של כמעט כל אפליקצית ווב אפשרית. בגלל שהיא קלת-משקל ומצויידת בכל טכנולוגיות המטמון המתוחכמות והאחרונות, היא מותאמת במיוחד לפיתוח של אפליקציות בעלות תעבורה גבוהה במיוחד, כמו פורטלים, פורומים, מערכות ניהול תוכן, חנויות וירטואליות, וכדומה. - -כיצד אפשר להשוות בין Yii לשאר הפריימוורקס? ------------------------------------------- - -כמו ברוב הפריימוורקים, Yii בנויה על גבי MVC. - -Yii מעפילה על גבי פריימוורקים אחרים מהסיבות שהיא יעילה, מכילה אפשרויות רבות, ומתועדות בצורה נרחבת וברורה. Yii בנויה בזהירות מהתחלה בכדי להתאים לצרכים של אפליקציות ווב רציניות. -הוא אינו תוצר לואי מפרוייקט מסויים ולא מצבור של ספריות צד-שלישי. זו התוצאה של הניסיון הרב של צוות הפיתוח שחקר וחישב תוך כדי הסתכלות על פריימוורקס PHP והאפליקציות הפופולריות ביותר. - +מה זה Yii +=========== + +Yii הינה פריימוורק PHP המבוסס על רכיבים, בעל ביצועים גבוהים המאפשר פיתוח של אפליקציות ווב גדולות וכבדות במהירות ויעילות. הוא מאפשר את המקסימום בפיתוח אפליקציות ווב ויכול לזרז בצורה משמעותית את תהליך הפיתוח של האפליקציה. השם Yii (המבוטא כ 'Yee' או `[Ji:]`) שמשמעותו *קל*, *יעיל* ו *ניתן להרחבה*. + +דרישות +------------ + +בכדי להריץ אפליקציות אשר בנויות על גבי Yii, יש צורך בשרת התומך ב PHP 5.1.0 או יותר. + +למפתחים אשר רוצים להשתמש ב Yii, הבנת תכנות מונחה עצמים (OOP) מאוד יעזור, מאחר ו Yii הינה פריימוורק הבנוי כולה ב OOP. + +מה השימוש הטוב ביותר עבור Yii? +--------------------- + +Yii הינה פריימורק כללי המאפשר פיתוח של כמעט כל אפליקצית ווב אפשרית. בגלל שהיא קלת-משקל ומצויידת בכל טכנולוגיות המטמון המתוחכמות והאחרונות, היא מותאמת במיוחד לפיתוח של אפליקציות בעלות תעבורה גבוהה במיוחד, כמו פורטלים, פורומים, מערכות ניהול תוכן, חנויות וירטואליות, וכדומה. + +כיצד אפשר להשוות בין Yii לשאר הפריימוורקס? +------------------------------------------ + +כמו ברוב הפריימוורקים, Yii בנויה על גבי MVC. + +Yii מעפילה על גבי פריימוורקים אחרים מהסיבות שהיא יעילה, מכילה אפשרויות רבות, ומתועדות בצורה נרחבת וברורה. Yii בנויה בזהירות מהתחלה בכדי להתאים לצרכים של אפליקציות ווב רציניות. +הוא אינו תוצר לואי מפרוייקט מסויים ולא מצבור של ספריות צד-שלישי. זו התוצאה של הניסיון הרב של צוות הפיתוח שחקר וחישב תוך כדי הסתכלות על פריימוורקס PHP והאפליקציות הפופולריות ביותר. + «div class="revision"»$Id: quickstart.what-is-yii.txt 1622 2009-12-26 20:56:05Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/test.fixture.txt b/docs/guide/he/test.fixture.txt index e873238e8..4597848c8 100644 --- a/docs/guide/he/test.fixture.txt +++ b/docs/guide/he/test.fixture.txt @@ -1,60 +1,60 @@ -הגדרת נתונים התחלתיים -================= - -בדיקות אוטומטיות בדרך כלל מתבצעות מספר רב של פעמים. בכדי לוודא שתהליך הבדיקה יכול לחזור על עצמו, אנו נרצה להריץ אתה הבדיקה במצב ידוע הנקרא *fixture*. לדוגמא, בכדי לבדוק את אפשרות פרסום הודעה באפליקצית בלוג, בכל פעם שאנו מריצים את הבדיקה, הטבלאות השומרות מידע הקשור להודעות (לדוגמא, טבלת ה `Post`, טבלת ה `Comment`) יש צורך בלשחזר את הטבלה למצב התחלתי כלשהו. הדוקומנטציה של [PHPUnit ](http://www.phpunit.de/wiki/Documentation) מתארת באופן מעמיק לגבי התקנת טבלאות קבועות כללית. בחלק זה, אנו בעיקר נתאר כיצד להגדיר מידע קבוע עבור טבלאות, כפי שתארנו בדוגמא. - -הגדרת טבלאות קבועות (fixture) הוא אחד החלקים שלוקחים הכי הרבה זמן להגדרה בעבור אפליקציה שמשתמשת במסד נתונים לצורך שמירת מידע. Yii מספקת רכיב בשם [CDbFixtureManager] בכדי לפשט בעיה זו. רכיב זה מבצע את הפעולות בהבאות בעת הרצת סט של בדיקות: - -* לפני שכל הבדיקות רצות, הרכיב מאפס את כל הטבלאות הרצויות למצב התחלתי וידוע. -* לפני הרצת מתודת בדיקה אחת, היא מאפסת את הטבלאות הרצויות למצב התחלתי וידוע. -* במהלך הרצת מתודת בדיקה, היא מאפשרת גישה לשורות הקשורות לאותה בדיקה. - -בכדי להשתמש ב [CDbFixtureManager], אנו מגדירים אותו [קובץ הגדרות האפליקציה](/doc/guide/basics.application#application-configuration) בצורה הבאה, - -~~~ -[php] -return array( - 'components'=»array( - 'fixture'=»array( - 'class'=»'system.test.CDbFixtureManager', - ), - ), -); -~~~ - -לאחר מכן אנו מספקים את המידע אודות הטבלאות הקבועות תחת התיקיה `protected/tests/fixtures`. ניתן לשנות את התיקיה לתיקיה במיקום אחר על ידי הגדרת המאפיין [CDbFixtureManager::basePath] בהגדרות האפליקציה. המידע אודות הנתונים ההתחלתיים לטבלאות מסודר כאוסף של קבצי PHP הנקראים קבצי fixture. כל קובץ התחלתי לטבלאות מחזיר מערך המייצג את המידע ההתחלתי לטבלה מסויימת. שם הקובץ הוא זהה לשם הטבלה. להלן דוגמא למידע עבור הטבלה `Post` הנשמר בקובץ בשם `Post.php`: - -~~~ -[php] -«?php -return array( - 'sample1'=»array( - 'title'=»'test post 1', - 'content'=»'test post content 1', - 'createTime'=»1230952187, - 'authorId'=»1, - ), - 'sample2'=»array( - 'title'=»'test post 2', - 'content'=»'test post content 2', - 'createTime'=»1230952287, - 'authorId'=»1, - ), -); -~~~ - -כפי שניתן לראות, ישנם שני שורות של מידע המוחזרות בקוד המוצג למעלה. כל שורה מיוצגת כמערך שמפתחותיו הם שמות העמודות בטבלה והערכים של המפתחות הם הערכים השמורים בעמודות בטבלה. בנוסף, כל שורה מאונדקסת על ידי סטרינג (לדוגמא `sample1`, `sample2`) הנקראים *שמות מקוצרים לשורות*. אחר כך, כשאנו נכתוב סקריפטים לבדיקה, אנו יכולים להתייחס לכל שורה על ידי שמה המקוצר. אנו נסביר לגבי זה בהרחבה בחלק הבא. - -כפי שניתן לראות אנו לא מגדירים את העמודה `id` בקוד למעלה. זה מכיוון שהעמודה `id` מוגדרת כמפתח ראשי שערכו יתמלא באופן אוטומטי בכל שורה חדשה שנוסיף. - -ברגע שהרכיב [CDbFixtureManager] יקרא בפעם הראשונה, הוא יעבור על כל קבצי הטבלאות הקבועות ויאפס אותם למצב התחלתי. הוא מאפס טבלה על ידי ריקון הטבלה מתוכן, מאפס את המספר העולה של העמודה המכילה את המפתח הראשי בטבלה, ואז מוסיפה את הנתונים מקבצי הטבלאות הקבועים לטבלה. - -לפעמים, אנו לא נרצה לאפס כל טבלה המכילה מצב התחלתי כלשהו לפני שאנו מריצים בדיקות, בגלל שאיפוס כמה טבלאות קבועות יכול לקחת הרבה זמן. במקרה זה, אנו יכולים לכתוב סקריפט PHP בכדי לבצע את העבודה ההתחלתית בצורה מותאמת אישית. הסקריפט צריך להשמר בקובץ בשם `init.php` תחת התיקיה המכילה את קבצי הטבלאות הקבועות. ברגע שהרכיב [CDbFixtureManager] יזהה את הקיום של קובץ זה, הוא יריץ את הקובץ הזה במקום לאפס כל טבלה. - -אפשרות נוספת היא שאנו לא נרצה להשתמש בדרך ברירת המחדל של איפוס טבלה, כלומר, איפוס הטבלה והוספת הנתונים הקבועים לתוכה. למקרה זה, אנו יכולים לכתוב סקריפט התחלתי ספציפית לקובץ 'מידע התחלתי לטבלה' מסויים. הסקריפט צריך להשמר בקובץ PHP תחת שם הטבלה ולאחריו `.init.php`. לדוגמא, הקובץ ההתחלתי עבור הטבלה `Post` יהיה `Post.init.php`. ברגע שהרכיב [CDbFixtureManager] יזהה שסקריפט זה קיים, הוא יריץ את הסקריפט הזה במקום להריץ את הסקריפט אשר מאפס את הטבלה בצורה הרגילה. - -» Tip|טיפ: במידה ויהיו הרבה קבצי 'מידע התחלתי לטבלה' כלומר הרבה קבצים של מצב התחלתי עבור טבלאות, זה יכול לעלות את משך זמן הבדיקה בצורה משמעותית. מסיבה זו, עליך להגדיר קבצי 'מידע התחלתי לטבלה' עבור טבלאות שהתוכן שלהם משתנה בלבד במהלך הבדיקה. טבלאות אשר משמשות עבור קישורים בין טבלה אחת לשנייה תוכנם לא משתנה לכן אין צורך בליצור עבורם קבצי 'מידע התחלתי לטבלה'. - -בשני החלקים הבאים, אנו נתאר כיצד להשתמש במצבים ההתחלתיים המנוהלים על ידי [CDbFixtureManager] בבדיקות יחידה ובדיקות פונקציונליות. - +הגדרת נתונים התחלתיים +================= + +בדיקות אוטומטיות בדרך כלל מתבצעות מספר רב של פעמים. בכדי לוודא שתהליך הבדיקה יכול לחזור על עצמו, אנו נרצה להריץ אתה הבדיקה במצב ידוע הנקרא *fixture*. לדוגמא, בכדי לבדוק את אפשרות פרסום הודעה באפליקצית בלוג, בכל פעם שאנו מריצים את הבדיקה, הטבלאות השומרות מידע הקשור להודעות (לדוגמא, טבלת ה `Post`, טבלת ה `Comment`) יש צורך בלשחזר את הטבלה למצב התחלתי כלשהו. הדוקומנטציה של [PHPUnit ](http://www.phpunit.de/wiki/Documentation) מתארת באופן מעמיק לגבי התקנת טבלאות קבועות כללית. בחלק זה, אנו בעיקר נתאר כיצד להגדיר מידע קבוע עבור טבלאות, כפי שתארנו בדוגמא. + +הגדרת טבלאות קבועות (fixture) הוא אחד החלקים שלוקחים הכי הרבה זמן להגדרה בעבור אפליקציה שמשתמשת במסד נתונים לצורך שמירת מידע. Yii מספקת רכיב בשם [CDbFixtureManager] בכדי לפשט בעיה זו. רכיב זה מבצע את הפעולות בהבאות בעת הרצת סט של בדיקות: + +* לפני שכל הבדיקות רצות, הרכיב מאפס את כל הטבלאות הרצויות למצב התחלתי וידוע. +* לפני הרצת מתודת בדיקה אחת, היא מאפסת את הטבלאות הרצויות למצב התחלתי וידוע. +* במהלך הרצת מתודת בדיקה, היא מאפשרת גישה לשורות הקשורות לאותה בדיקה. + +בכדי להשתמש ב [CDbFixtureManager], אנו מגדירים אותו [קובץ הגדרות האפליקציה](/doc/guide/basics.application#application-configuration) בצורה הבאה, + +~~~ +[php] +return array( + 'components'=»array( + 'fixture'=»array( + 'class'=»'system.test.CDbFixtureManager', + ), + ), +); +~~~ + +לאחר מכן אנו מספקים את המידע אודות הטבלאות הקבועות תחת התיקיה `protected/tests/fixtures`. ניתן לשנות את התיקיה לתיקיה במיקום אחר על ידי הגדרת המאפיין [CDbFixtureManager::basePath] בהגדרות האפליקציה. המידע אודות הנתונים ההתחלתיים לטבלאות מסודר כאוסף של קבצי PHP הנקראים קבצי fixture. כל קובץ התחלתי לטבלאות מחזיר מערך המייצג את המידע ההתחלתי לטבלה מסויימת. שם הקובץ הוא זהה לשם הטבלה. להלן דוגמא למידע עבור הטבלה `Post` הנשמר בקובץ בשם `Post.php`: + +~~~ +[php] +«?php +return array( + 'sample1'=»array( + 'title'=»'test post 1', + 'content'=»'test post content 1', + 'createTime'=»1230952187, + 'authorId'=»1, + ), + 'sample2'=»array( + 'title'=»'test post 2', + 'content'=»'test post content 2', + 'createTime'=»1230952287, + 'authorId'=»1, + ), +); +~~~ + +כפי שניתן לראות, ישנם שני שורות של מידע המוחזרות בקוד המוצג למעלה. כל שורה מיוצגת כמערך שמפתחותיו הם שמות העמודות בטבלה והערכים של המפתחות הם הערכים השמורים בעמודות בטבלה. בנוסף, כל שורה מאונדקסת על ידי סטרינג (לדוגמא `sample1`, `sample2`) הנקראים *שמות מקוצרים לשורות*. אחר כך, כשאנו נכתוב סקריפטים לבדיקה, אנו יכולים להתייחס לכל שורה על ידי שמה המקוצר. אנו נסביר לגבי זה בהרחבה בחלק הבא. + +כפי שניתן לראות אנו לא מגדירים את העמודה `id` בקוד למעלה. זה מכיוון שהעמודה `id` מוגדרת כמפתח ראשי שערכו יתמלא באופן אוטומטי בכל שורה חדשה שנוסיף. + +ברגע שהרכיב [CDbFixtureManager] יקרא בפעם הראשונה, הוא יעבור על כל קבצי הטבלאות הקבועות ויאפס אותם למצב התחלתי. הוא מאפס טבלה על ידי ריקון הטבלה מתוכן, מאפס את המספר העולה של העמודה המכילה את המפתח הראשי בטבלה, ואז מוסיפה את הנתונים מקבצי הטבלאות הקבועים לטבלה. + +לפעמים, אנו לא נרצה לאפס כל טבלה המכילה מצב התחלתי כלשהו לפני שאנו מריצים בדיקות, בגלל שאיפוס כמה טבלאות קבועות יכול לקחת הרבה זמן. במקרה זה, אנו יכולים לכתוב סקריפט PHP בכדי לבצע את העבודה ההתחלתית בצורה מותאמת אישית. הסקריפט צריך להשמר בקובץ בשם `init.php` תחת התיקיה המכילה את קבצי הטבלאות הקבועות. ברגע שהרכיב [CDbFixtureManager] יזהה את הקיום של קובץ זה, הוא יריץ את הקובץ הזה במקום לאפס כל טבלה. + +אפשרות נוספת היא שאנו לא נרצה להשתמש בדרך ברירת המחדל של איפוס טבלה, כלומר, איפוס הטבלה והוספת הנתונים הקבועים לתוכה. למקרה זה, אנו יכולים לכתוב סקריפט התחלתי ספציפית לקובץ 'מידע התחלתי לטבלה' מסויים. הסקריפט צריך להשמר בקובץ PHP תחת שם הטבלה ולאחריו `.init.php`. לדוגמא, הקובץ ההתחלתי עבור הטבלה `Post` יהיה `Post.init.php`. ברגע שהרכיב [CDbFixtureManager] יזהה שסקריפט זה קיים, הוא יריץ את הסקריפט הזה במקום להריץ את הסקריפט אשר מאפס את הטבלה בצורה הרגילה. + +» Tip|טיפ: במידה ויהיו הרבה קבצי 'מידע התחלתי לטבלה' כלומר הרבה קבצים של מצב התחלתי עבור טבלאות, זה יכול לעלות את משך זמן הבדיקה בצורה משמעותית. מסיבה זו, עליך להגדיר קבצי 'מידע התחלתי לטבלה' עבור טבלאות שהתוכן שלהם משתנה בלבד במהלך הבדיקה. טבלאות אשר משמשות עבור קישורים בין טבלה אחת לשנייה תוכנם לא משתנה לכן אין צורך בליצור עבורם קבצי 'מידע התחלתי לטבלה'. + +בשני החלקים הבאים, אנו נתאר כיצד להשתמש במצבים ההתחלתיים המנוהלים על ידי [CDbFixtureManager] בבדיקות יחידה ובדיקות פונקציונליות. + «div class="revision"»$Id: test.fixture.txt 1148 2009-06-18 22:06:22Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/test.functional.txt b/docs/guide/he/test.functional.txt index 040f9b6a2..77d7d77e4 100644 --- a/docs/guide/he/test.functional.txt +++ b/docs/guide/he/test.functional.txt @@ -1,67 +1,67 @@ -בדיקות פונקציונליות -================== - -לפני קריאה של חלק זה, מומלץ לקרוא קודם את הדוקומנטציה של [Selenium](http://seleniumhq.org/docs/) ושל [PHPUnit](http://www.phpunit.de/wiki/Documentation). אנו מסכמים בחלק זה את העקרונות הבסיסים לכתיבת בדיקות פונקציונליות ב Yii: - -* בדומה לבדיקות יחידה, בדיקה פונקציונלית נכתבת במונחים של מחלקה `XyzTest` היורשת מהמחלקה [CWebTestCase], כש `Xyz` מתייחס למחלקה הנבדקת. בגלל שהמחלקה `PHPUnit_Extensions_SeleniumTestCase` הינה מחלקת אב למחלקה [CWebTestCase], אנו יכולים להשתמש בכל המתודות הנמצאות בה. - -* מחלקת הבדיקה הפונקציונלית נשמרת בקובץ PHP בשם `XyzTest.php`. על פי המוסכמות, הקובץ נשמר תחת `protected/tests/functional`. - -* מחלקת הבדיקות בעיקר מכילה סט של מתודות לבדיקה בשם `testAbc`, כש `Abc` הוא בדרך כלל שם האפשרות שנבדקת. לדוגמא, בכדי לבדוק את אפשרות התחברות המשתמשים אנו יכולים לקרוא למתודה בשם `testLogin`. - -* מתודת בדיקה בדרך כלל מכילה רצף של ביטויים אשר ישלחו פקודות עבור Selenium בכדי לתקשר מול אפליקצית הווב הנבדקת. בנוסף היא מכילה בדיקות לוודא שהאפליקציה מגיבה כמו שצריך. - -לפני שנסביר כיצד לכתוב בדיקות פונקציונליות, הבא נבדוק את הקובץ `WebTestCase.php` שנוצר על ידי פקודת הכלי `yiic webapp`. קובץ זה מגדיר את המחלקה `WebTestCase` שיכול לשרת כמחלקת בסיס לכל מחלקות הבדיקה הפונקציונליות. - -~~~ -[php] -define('TEST_BASE_URL','http://localhost/yii/demos/blog/index-test.php/'); - -class WebTestCase extends CWebTestCase -{ - /** - * Sets up before each test method runs. - * This mainly sets the base URL for the test application. - */ - protected function setUp() - { - parent::setUp(); - $this-»setBrowserUrl(TEST_BASE_URL); - } - - ...... -} -~~~ - -המחלקה `WebTestCase` בעיקר מגדירה את הקישורים של העמודים אותם צריך לבדוק. לאחר מכן במתודות בדיקה, אנחנו יכולים להשתמש בקישורים רלטיבים בכדי להגדיר אילו עמודים לבדוק. - -כמו כן אנחנו צריכים לשים לב שבקישור הבדיקה הבסיסי, אנחנו משתמשים ב `index-test.php` כקובץ הכניסה הראשי במקום `index.php`. ההבדל היחידי בין `index-test.php` ו `index.php` הוא שהקודם משתמש בקובץ הגדרות `test.php` והשני משתמש בקובץ הגדרות `main.php`. - -כעת אנו נסביר כיצד לבדוק את האפשרות של תצוגה הודעה [בבלוג](http://www.yiiframework.com/demos/blog). קודם אנו כותבים את מחלקת הבדיקה בצורה הבאה, בידיעה שמחלקת הבדיקה שאנו כותבים יורשת ממחלקת הבסיס שכרגע הסברנו עליה: - -~~~ -[php] -class PostTest extends WebTestCase -{ - public $fixtures=array( - 'posts'=»'Post', - ); - - public function testShow() - { - $this-»open('post/1'); - // וודא שכותרת קיימת להודעה - $this-»assertTextPresent($this-»posts['sample1']['title']); - // וודא שקיים טופס לתגובה - $this-»assertTextPresent('Leave a Comment'); - } - - ...... -} -~~~ - -בדומה לכתיבת מחלקה לבדיקת יחידה, אנו מגדירים את הטבלאות הקבועות שאנו משתמשים בבדיקה זו. כאן אנו מצביעים שהטבלה הקבועה `Post` צריכה להיות בשימוש. במתודת הבדיקה `testShow`, אנו קודם אומרים ל Selenium-RC לפתוח את הקישור `post/1`. זהו קישור רלטיבי, והקישור המלא נוצר על ידי איחוד הקישור הזה לקישור הבסיס שהגדרנו במחלקת הבסיס (זאת אומרת `http://localhost/yii/demos/blog/index-test.php/post/1`). לאחר מכן אנו מאמתים שאנו יכולים למצוא את הכותרת של ההודעה `sample1` בעמוד הנוכחי. ואנו מוודאים שהעמוד מכיל את הטקסט `Leave a Comment`. - -» Tip|טיפ: לפני הרצת בדיקות פונקציונליות, יש להפעיל את שרת Selenium-RC. ניתן לבצע זאת על ידי הרצת הפקודה `java -jar selenium-server.jar` תחת התיקיה בה מותקן שרת ה Selenium-RC. - +בדיקות פונקציונליות +================== + +לפני קריאה של חלק זה, מומלץ לקרוא קודם את הדוקומנטציה של [Selenium](http://seleniumhq.org/docs/) ושל [PHPUnit](http://www.phpunit.de/wiki/Documentation). אנו מסכמים בחלק זה את העקרונות הבסיסים לכתיבת בדיקות פונקציונליות ב Yii: + +* בדומה לבדיקות יחידה, בדיקה פונקציונלית נכתבת במונחים של מחלקה `XyzTest` היורשת מהמחלקה [CWebTestCase], כש `Xyz` מתייחס למחלקה הנבדקת. בגלל שהמחלקה `PHPUnit_Extensions_SeleniumTestCase` הינה מחלקת אב למחלקה [CWebTestCase], אנו יכולים להשתמש בכל המתודות הנמצאות בה. + +* מחלקת הבדיקה הפונקציונלית נשמרת בקובץ PHP בשם `XyzTest.php`. על פי המוסכמות, הקובץ נשמר תחת `protected/tests/functional`. + +* מחלקת הבדיקות בעיקר מכילה סט של מתודות לבדיקה בשם `testAbc`, כש `Abc` הוא בדרך כלל שם האפשרות שנבדקת. לדוגמא, בכדי לבדוק את אפשרות התחברות המשתמשים אנו יכולים לקרוא למתודה בשם `testLogin`. + +* מתודת בדיקה בדרך כלל מכילה רצף של ביטויים אשר ישלחו פקודות עבור Selenium בכדי לתקשר מול אפליקצית הווב הנבדקת. בנוסף היא מכילה בדיקות לוודא שהאפליקציה מגיבה כמו שצריך. + +לפני שנסביר כיצד לכתוב בדיקות פונקציונליות, הבא נבדוק את הקובץ `WebTestCase.php` שנוצר על ידי פקודת הכלי `yiic webapp`. קובץ זה מגדיר את המחלקה `WebTestCase` שיכול לשרת כמחלקת בסיס לכל מחלקות הבדיקה הפונקציונליות. + +~~~ +[php] +define('TEST_BASE_URL','http://localhost/yii/demos/blog/index-test.php/'); + +class WebTestCase extends CWebTestCase +{ + /** + * Sets up before each test method runs. + * This mainly sets the base URL for the test application. + */ + protected function setUp() + { + parent::setUp(); + $this-»setBrowserUrl(TEST_BASE_URL); + } + + ...... +} +~~~ + +המחלקה `WebTestCase` בעיקר מגדירה את הקישורים של העמודים אותם צריך לבדוק. לאחר מכן במתודות בדיקה, אנחנו יכולים להשתמש בקישורים רלטיבים בכדי להגדיר אילו עמודים לבדוק. + +כמו כן אנחנו צריכים לשים לב שבקישור הבדיקה הבסיסי, אנחנו משתמשים ב `index-test.php` כקובץ הכניסה הראשי במקום `index.php`. ההבדל היחידי בין `index-test.php` ו `index.php` הוא שהקודם משתמש בקובץ הגדרות `test.php` והשני משתמש בקובץ הגדרות `main.php`. + +כעת אנו נסביר כיצד לבדוק את האפשרות של תצוגה הודעה [בבלוג](http://www.yiiframework.com/demos/blog). קודם אנו כותבים את מחלקת הבדיקה בצורה הבאה, בידיעה שמחלקת הבדיקה שאנו כותבים יורשת ממחלקת הבסיס שכרגע הסברנו עליה: + +~~~ +[php] +class PostTest extends WebTestCase +{ + public $fixtures=array( + 'posts'=»'Post', + ); + + public function testShow() + { + $this-»open('post/1'); + // וודא שכותרת קיימת להודעה + $this-»assertTextPresent($this-»posts['sample1']['title']); + // וודא שקיים טופס לתגובה + $this-»assertTextPresent('Leave a Comment'); + } + + ...... +} +~~~ + +בדומה לכתיבת מחלקה לבדיקת יחידה, אנו מגדירים את הטבלאות הקבועות שאנו משתמשים בבדיקה זו. כאן אנו מצביעים שהטבלה הקבועה `Post` צריכה להיות בשימוש. במתודת הבדיקה `testShow`, אנו קודם אומרים ל Selenium-RC לפתוח את הקישור `post/1`. זהו קישור רלטיבי, והקישור המלא נוצר על ידי איחוד הקישור הזה לקישור הבסיס שהגדרנו במחלקת הבסיס (זאת אומרת `http://localhost/yii/demos/blog/index-test.php/post/1`). לאחר מכן אנו מאמתים שאנו יכולים למצוא את הכותרת של ההודעה `sample1` בעמוד הנוכחי. ואנו מוודאים שהעמוד מכיל את הטקסט `Leave a Comment`. + +» Tip|טיפ: לפני הרצת בדיקות פונקציונליות, יש להפעיל את שרת Selenium-RC. ניתן לבצע זאת על ידי הרצת הפקודה `java -jar selenium-server.jar` תחת התיקיה בה מותקן שרת ה Selenium-RC. + «div class="revision"»$Id: test.functional.txt 1662 2010-01-04 19:15:10Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/test.overview.txt b/docs/guide/he/test.overview.txt index b809af921..e440afdbd 100644 --- a/docs/guide/he/test.overview.txt +++ b/docs/guide/he/test.overview.txt @@ -1,100 +1,100 @@ -סקירה -======== - -» Note|הערה: בכדי לבצע את הפעולות בחלקים הבאים יש לעבוד עם Yii 1.1 ומעלה. התמיכה בבדיקות קיימת רק מגרסאות 1.1 ומעלה. למרות, שאין זה אומר שאינכם יכולים לבדוק את האפליקציות שלכם שפותחו בעזרת Yii 1.0. ישנם עשרות כלים שתוכלו להעזר בהם בכדי לבצע בדיקות על גבי האפליקציה, כמו לדוגמא [PHPUnit](http://www.phpunit.de/), [SimpleTest](http://www.simpletest.org/). - -בדיקות הן תהליך הכרחי בפיתוח תוכנה. בין אם אנו מודעים לזה או לא, אנו מבצעים בדיקות כל הזמן בזמן שאנו מפתחים אפליקציה. לדוגמא, כשאנו כותבים מחלקה ב PHP, אנו משתמשים בביטויים כמו `echo` או `die` בכדי לראות אם הקוד שכתבנו עובד כמו שצריך; כשאנו מיישמים עמוד כלשהו המכיל קוד HTML מורכב, אנו יכולים להכניס בדיקות שונות בכדי לדעת אם העמוד מתקשר עמנו כצפוי או לא. מפתחים מתקדמים יותר בדרך כלל כותבים קוד מסויים שמבצע את התהליך הזה אוטומטית כדי שבכל פעם שהם צריכים לבדוק משהו, הם צריכים רק לכתוב את הקוד ולתת למחשב לבצע את פעולות הבדיקה. זה ידוע בשם *בדיקות אוטומטיות*, שזהו הנושא העיקרי של חלק זה. - -התמיכה בבדיקות שמגיע עם Yii כוללת *בדיקות יחידה* ו *בדיקות פונקציונליות*. - -בדיקות יחידה מאמתות שיחידה אחת בקוד עובדת כמו שצריך. בתכנות מונחה עצמים, יחידת הקוד הבסיסית ביותר הינה מחלקה. לכן בדיקות היחידה צריכות לאמת שכל המתודות במחלקה עובדות כמו שצריך. זאת, לאחר שהמתודה מקבלת פרמטרים שונים היא צריכה לבדוק שהיא מחזירה תוצאה נכונה. בדיקות יחידה בדרך כלל נכתבות על ידי אותם מפתחים הכותבים את המחלקה. - -בדיקה פונקציונלית בדרך כלל בודקת שאפשרות מסויימת (לדוגמא ניהול הודעות במערכת בלוגים) עובדת כמו שצריך. בהשוואה לבדיקת יחידה, בדיקה פונקציונלית יושבת ברמה גבוהה יותר מאחר והאפשרות הנבדקת בדרך כלל מכילה כמה מחלקות. בדיקות פונקציונליות בדרך כלל נכתבות על ידי משתמשים שיודעים טוב מאוד את דרישות המערכת (בין אם זה המפתחים או מהנדסים). - -פיתוח מונחה-בדיקות -======================= - -למטה מופיעה רשימה של תהליך הפיתוח במה שנקרא [פיתוח מונחה-בדיקות](http://he.wikipedia.org/wiki/%D7%A4%D7%99%D7%AA%D7%95%D7%97_%D7%9E%D7%95%D7%A0%D7%97%D7%94_%D7%91%D7%93%D7%99%D7%A7%D7%95%D7%AA) : - -1. יצירת בדיקה חדשה המכסה אפשרות ליישום. הבדיקה צריכה להכשל בהרצה הראשונה שלה מאחר והאפשרות עדיין לא קיימת. -2. הרץ את כל הבדיקות וודא שהבדיקה החדשה תכשל. -3. כתוב קוד כדי שהבדיקה החדשה שנוצר לא תכשל. -4. הרץ את כל הבדיקות וודא שהם כולם עובדות כמו שצריך. -5. בדוק/כתוב מחדש את הקוד שנכתב לאחרונה והרץ שוב את כל הבדיקות וודא שכולם עובדות כמו שצריך. - -חזור על השלבים 1-5 בכדי להגיע לסיום יישום האפשרות החדשה. - -הגדרת סביבת בדיקות -====================== - -הבדיקות הנתמכות ב Yii דורשות [PHPUnit](http://www.phpunit.de/) 3.3+ ו [Selenium Remote Control](http://seleniumhq.org/projects/remote-control/) 1.0+. אנא קראה את הדוקומנטציה שלהם בכדי לדעת כיצד להתקין אותם. - -לאחר מכן אנו נשתמש בכלי `yiic webapp` במסוף בכדי ליצור אפליקציה חדשה, הכלי יצור את הקבצים והתיקיות הבאות בכדי שאנו נוכל לבצע בדיקות: - -~~~ -testdrive/ - protected/ מכיל קבצים מוגנים של האפליקציה - tests/ מכיל בדיקות לאפליקציה - fixtures/ מכיל טבלאות קבועות למסד נתונים - functional/ מכיל בדיקות פונקציונליות - unit/ מכיל בדיקות יחידה - report/ מכיל דוחות בדיקה - bootstrap.php הסקריפט שרץ בהתחלה - phpunit.xml קובץ הגדרות של PHPUnit - WebTestCase.php מחלקת הבסיס לכל הבדיקות באפליקציה -~~~ - -כפי שמוצג למעלה, קוד הבדיקות שנכתוב ימוקם בדרך כלל באחת משלושת התיקיות: `fixtures`, `functional` ו `unit`, והתיקיה `report` יכיל את הדוחות של הבדיקות שנעשו. - -בכדי להריץ בדיקות (בין אם זה בדיקות יחידה או פונקציונליות), אנו יכולים להריץ את הפקודות הבאות בחלון המסוף: - -~~~ -% cd testdrive/protected/tests -% phpunit functional/PostTest.php // הרצת בדיקה אחת -% phpunit --verbose functional // הרצת כל הבדיקות תחת 'functional' -% phpunit --coverage-html ./report unit -~~~ - -בקוד למעלה, הפקודה האחרונה תריץ את כל הבדיקות הנמצאות בתיקיה `unit` ותיצור דוח בתוך התיקיה `report`. יש לדעת שבכדי שיווצר דוח אודות הבדיקה יש להתקין ולהפעיל את התוסף [xdebug ](http://www.xdebug.org/). - - -סקריפט ההתחלה של הבדיקות -==================== - -בואו נבדוק מה יכול להיות בתוך קובץ ה `bootstrap.php`. קובץ זה חשוב מאחר והוא דומה לקובץ [הכניסה הראשי](/doc/guide/basics.entry) והוא נקודת ההתחלה בכל פעם שאנו מריצים בדיקות. - -~~~ -[php] -$yiit='path/to/yii/framework/yiit.php'; -$config=dirname(__FILE__).'/../config/test.php'; -require_once($yiit); -require_once(dirname(__FILE__).'/WebTestCase.php'); -Yii::createWebApplication($config); -~~~ - -בקוד למעלה, אנו קודם מצרפים את הקובץ `yiit.php` מהתיקיה של הפריימוורק, אשר מגדירה ערכים גלובליים ומוסיפה את מחלקות הבסיס הנחוצות. לאחר מכן אנו יוצרים אובייקט של אפליקצית ווב בעזרת קובץ ההגדרות `test.php`. אם נבדוק את קובץ ה `test.php`, אנו נראה שהוא יורש מקובץ ההגדרות `main.php` ומוסיף רכיב אפליקציה בשם `fixture` שמחלקתו הינה [CDbFixtureManager]. אנו נסביר אודות אפשרות זו בחלק הבא. -ֿ -~~~ -[php] -return CMap::mergeArray( - require(dirname(__FILE__).'/main.php'), - array( - 'components'=»array( - 'fixture'=»array( - 'class'=»'system.test.CDbFixtureManager', - ), - /* הסר הערה זו בכדי להגדיר מסד נתונים לבדיקות - 'db'=»array( - 'connectionString'=»'DSN for test database', - ), - */ - ), - ) -); -~~~ - -כשאנו מריצים בדיקות המערבות מסד נתונים, מומלץ להגדיר מסד נתונים לצרכי בדיקה בלבד בכדי שהבדיקות לא יפריעו לפעילויות בזמן תהליך הפיתוח או תהליך התפוקה. בכדי לבצע זאת, אנו צריכים להסיר את ההערה מסביב להגדרות ה `db` בקוד המוצג למעלה ולהגדיר את המאפיין `connectionString` עם ה DSN המתאים למסד הנתונים בו אנו משתמשים. - -בעזרת קובץ סקריפט זה, כשאנו מריצים בדיקות יחידה, אנו נקבל אובייקט של האפליקציה שהוא כמעט זהה לאובייקט שרץ לאפליקצית הווב. ההבדל היחידי הוא שהוא משתמש באפשרות של `fixtures` והוא משתמש במסד נתונים לצרכי בדיקה. - +סקירה +======== + +» Note|הערה: בכדי לבצע את הפעולות בחלקים הבאים יש לעבוד עם Yii 1.1 ומעלה. התמיכה בבדיקות קיימת רק מגרסאות 1.1 ומעלה. למרות, שאין זה אומר שאינכם יכולים לבדוק את האפליקציות שלכם שפותחו בעזרת Yii 1.0. ישנם עשרות כלים שתוכלו להעזר בהם בכדי לבצע בדיקות על גבי האפליקציה, כמו לדוגמא [PHPUnit](http://www.phpunit.de/), [SimpleTest](http://www.simpletest.org/). + +בדיקות הן תהליך הכרחי בפיתוח תוכנה. בין אם אנו מודעים לזה או לא, אנו מבצעים בדיקות כל הזמן בזמן שאנו מפתחים אפליקציה. לדוגמא, כשאנו כותבים מחלקה ב PHP, אנו משתמשים בביטויים כמו `echo` או `die` בכדי לראות אם הקוד שכתבנו עובד כמו שצריך; כשאנו מיישמים עמוד כלשהו המכיל קוד HTML מורכב, אנו יכולים להכניס בדיקות שונות בכדי לדעת אם העמוד מתקשר עמנו כצפוי או לא. מפתחים מתקדמים יותר בדרך כלל כותבים קוד מסויים שמבצע את התהליך הזה אוטומטית כדי שבכל פעם שהם צריכים לבדוק משהו, הם צריכים רק לכתוב את הקוד ולתת למחשב לבצע את פעולות הבדיקה. זה ידוע בשם *בדיקות אוטומטיות*, שזהו הנושא העיקרי של חלק זה. + +התמיכה בבדיקות שמגיע עם Yii כוללת *בדיקות יחידה* ו *בדיקות פונקציונליות*. + +בדיקות יחידה מאמתות שיחידה אחת בקוד עובדת כמו שצריך. בתכנות מונחה עצמים, יחידת הקוד הבסיסית ביותר הינה מחלקה. לכן בדיקות היחידה צריכות לאמת שכל המתודות במחלקה עובדות כמו שצריך. זאת, לאחר שהמתודה מקבלת פרמטרים שונים היא צריכה לבדוק שהיא מחזירה תוצאה נכונה. בדיקות יחידה בדרך כלל נכתבות על ידי אותם מפתחים הכותבים את המחלקה. + +בדיקה פונקציונלית בדרך כלל בודקת שאפשרות מסויימת (לדוגמא ניהול הודעות במערכת בלוגים) עובדת כמו שצריך. בהשוואה לבדיקת יחידה, בדיקה פונקציונלית יושבת ברמה גבוהה יותר מאחר והאפשרות הנבדקת בדרך כלל מכילה כמה מחלקות. בדיקות פונקציונליות בדרך כלל נכתבות על ידי משתמשים שיודעים טוב מאוד את דרישות המערכת (בין אם זה המפתחים או מהנדסים). + +פיתוח מונחה-בדיקות +======================= + +למטה מופיעה רשימה של תהליך הפיתוח במה שנקרא [פיתוח מונחה-בדיקות](http://he.wikipedia.org/wiki/%D7%A4%D7%99%D7%AA%D7%95%D7%97_%D7%9E%D7%95%D7%A0%D7%97%D7%94_%D7%91%D7%93%D7%99%D7%A7%D7%95%D7%AA) : + +1. יצירת בדיקה חדשה המכסה אפשרות ליישום. הבדיקה צריכה להכשל בהרצה הראשונה שלה מאחר והאפשרות עדיין לא קיימת. +2. הרץ את כל הבדיקות וודא שהבדיקה החדשה תכשל. +3. כתוב קוד כדי שהבדיקה החדשה שנוצר לא תכשל. +4. הרץ את כל הבדיקות וודא שהם כולם עובדות כמו שצריך. +5. בדוק/כתוב מחדש את הקוד שנכתב לאחרונה והרץ שוב את כל הבדיקות וודא שכולם עובדות כמו שצריך. + +חזור על השלבים 1-5 בכדי להגיע לסיום יישום האפשרות החדשה. + +הגדרת סביבת בדיקות +====================== + +הבדיקות הנתמכות ב Yii דורשות [PHPUnit](http://www.phpunit.de/) 3.3+ ו [Selenium Remote Control](http://seleniumhq.org/projects/remote-control/) 1.0+. אנא קראה את הדוקומנטציה שלהם בכדי לדעת כיצד להתקין אותם. + +לאחר מכן אנו נשתמש בכלי `yiic webapp` במסוף בכדי ליצור אפליקציה חדשה, הכלי יצור את הקבצים והתיקיות הבאות בכדי שאנו נוכל לבצע בדיקות: + +~~~ +testdrive/ + protected/ מכיל קבצים מוגנים של האפליקציה + tests/ מכיל בדיקות לאפליקציה + fixtures/ מכיל טבלאות קבועות למסד נתונים + functional/ מכיל בדיקות פונקציונליות + unit/ מכיל בדיקות יחידה + report/ מכיל דוחות בדיקה + bootstrap.php הסקריפט שרץ בהתחלה + phpunit.xml קובץ הגדרות של PHPUnit + WebTestCase.php מחלקת הבסיס לכל הבדיקות באפליקציה +~~~ + +כפי שמוצג למעלה, קוד הבדיקות שנכתוב ימוקם בדרך כלל באחת משלושת התיקיות: `fixtures`, `functional` ו `unit`, והתיקיה `report` יכיל את הדוחות של הבדיקות שנעשו. + +בכדי להריץ בדיקות (בין אם זה בדיקות יחידה או פונקציונליות), אנו יכולים להריץ את הפקודות הבאות בחלון המסוף: + +~~~ +% cd testdrive/protected/tests +% phpunit functional/PostTest.php // הרצת בדיקה אחת +% phpunit --verbose functional // הרצת כל הבדיקות תחת 'functional' +% phpunit --coverage-html ./report unit +~~~ + +בקוד למעלה, הפקודה האחרונה תריץ את כל הבדיקות הנמצאות בתיקיה `unit` ותיצור דוח בתוך התיקיה `report`. יש לדעת שבכדי שיווצר דוח אודות הבדיקה יש להתקין ולהפעיל את התוסף [xdebug ](http://www.xdebug.org/). + + +סקריפט ההתחלה של הבדיקות +==================== + +בואו נבדוק מה יכול להיות בתוך קובץ ה `bootstrap.php`. קובץ זה חשוב מאחר והוא דומה לקובץ [הכניסה הראשי](/doc/guide/basics.entry) והוא נקודת ההתחלה בכל פעם שאנו מריצים בדיקות. + +~~~ +[php] +$yiit='path/to/yii/framework/yiit.php'; +$config=dirname(__FILE__).'/../config/test.php'; +require_once($yiit); +require_once(dirname(__FILE__).'/WebTestCase.php'); +Yii::createWebApplication($config); +~~~ + +בקוד למעלה, אנו קודם מצרפים את הקובץ `yiit.php` מהתיקיה של הפריימוורק, אשר מגדירה ערכים גלובליים ומוסיפה את מחלקות הבסיס הנחוצות. לאחר מכן אנו יוצרים אובייקט של אפליקצית ווב בעזרת קובץ ההגדרות `test.php`. אם נבדוק את קובץ ה `test.php`, אנו נראה שהוא יורש מקובץ ההגדרות `main.php` ומוסיף רכיב אפליקציה בשם `fixture` שמחלקתו הינה [CDbFixtureManager]. אנו נסביר אודות אפשרות זו בחלק הבא. +ֿ +~~~ +[php] +return CMap::mergeArray( + require(dirname(__FILE__).'/main.php'), + array( + 'components'=»array( + 'fixture'=»array( + 'class'=»'system.test.CDbFixtureManager', + ), + /* הסר הערה זו בכדי להגדיר מסד נתונים לבדיקות + 'db'=»array( + 'connectionString'=»'DSN for test database', + ), + */ + ), + ) +); +~~~ + +כשאנו מריצים בדיקות המערבות מסד נתונים, מומלץ להגדיר מסד נתונים לצרכי בדיקה בלבד בכדי שהבדיקות לא יפריעו לפעילויות בזמן תהליך הפיתוח או תהליך התפוקה. בכדי לבצע זאת, אנו צריכים להסיר את ההערה מסביב להגדרות ה `db` בקוד המוצג למעלה ולהגדיר את המאפיין `connectionString` עם ה DSN המתאים למסד הנתונים בו אנו משתמשים. + +בעזרת קובץ סקריפט זה, כשאנו מריצים בדיקות יחידה, אנו נקבל אובייקט של האפליקציה שהוא כמעט זהה לאובייקט שרץ לאפליקצית הווב. ההבדל היחידי הוא שהוא משתמש באפשרות של `fixtures` והוא משתמש במסד נתונים לצרכי בדיקה. + «div class="revision"»$Id: test.overview.txt 1813 2010-02-18 21:33:16Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/test.unit.txt b/docs/guide/he/test.unit.txt index edcb9d000..2a2cb0b11 100644 --- a/docs/guide/he/test.unit.txt +++ b/docs/guide/he/test.unit.txt @@ -1,79 +1,79 @@ -בדיקת יחידות -============ - -מאחר ומערכת הבדיקות של Yii בנויה על גבי [PHPUnit](http://www.phpunit.de/), מומלץ לעבור על [דוקומנטציה](http://www.phpunit.de/wiki/Documentation) של PHPUnit קודם בכדי לקבל מושג והבנה ראשונית בנוגע לכתיבת בדיקות. אנו מסכמים את העקרונות הבסיסיים אודות כתיבת בדיקות ב Yii. - -* בדיקת יחידה נכתבת במונחים של מחלקה `XyzTest` היורשת ממחלקת הבסיס [CTestCase] או [CDbTestCase]. כש `Xyz` הינה המחלקה שאותה בודקים. לדוגמא, בכדי לבדוק את המחלקה `Post`, אנו נקרא ליחידת הבדיקה כ `PostTest` לפי המוסכמות. המחלקה הבסיסית [CTestCase] נועדה לבדיקות יחידה כלליות, והמחלקה הבסיסית [CDbTestCase] תואמת לבדיקת מחלקות [AR](/doc/guide/database.ar). בגלל ש `PHPUnit_Framework_TestCase` הינה מחלקת אב לשני המחלקות, אנו יכולים להשתמש בכל המתודות של מחלקה זו. - -* מחלקת בדיקת היחידה נשמרת בקובץ PHP בשם `XyzTest.php`. לפי המוסכמות, ניתן לשמור את קובץ הבדיקה תחת התיקיה `protected/tests/unit`. - -* מחלקת הבדיקה מכילה בעיקר סט של מתודות בשם `testAbc`, כש `Abc` בדרך כלל הינו שם מתודת המחלקה לבדיקה. - -* מתודת בדיקה בדרך כלל מכילה רצף של ביטויי בדיקות (לדוגמא `assertTrue`, `assertEquals`) המשרתות כנקודות ביקורת לבדיקת התנהגות של המחלקה. - -בחלק הבא, אנו בעיקר נסביר כיצד לכתוב בדיקות יחידה עבור מחלקות [AR](/doc/guide/database.ar). אנו נרחיב את המחלקות שלנו ממחלקת הבסיס [CDbTestCase] מאחר והיא מספקת תמיכה בקיבוע מסדי הנתונים אשר הצגנו בחלק הקודם. - -נניח שאנו רוצים לבדוק את מחלקת המודל `Comment` [בדמו של הבלוג](http://www.yiiframework.com/demos/blog/). אנו מתחילים על ידי יצירת מחלקה בשם `CommentTest` ושמירתו תחת `protected/tests/unit/CommentTest.php`: - -~~~ -[php] -class CommentTest extends CDbTestCase -{ - public $fixtures=array( - 'posts'=»'Post', - 'comments'=»'Comment', - ); - - ...... -} -~~~ - -במחלקה, אנו מגדירים את המאפיין `fixtures` למערך הקובע אילו טבלאות קבועות יהיו בשימוש בבדיקה זו. המערך מייצג מיפוי של טבלאות קבועות בבדיקה לבין שמות מחלקות מודלים או שמות טבלאות (לדוגמא מטבלה קבוע `posts` למחלקת מודל `Posts` ). זכור, בעת מיפוי לשמות טבלאות קבועות למטרת הבדיקה, אנו צריכים להגדיר קידומת של נקודותיים (:) לשם הטבלה (לדוגמא `Post:` ) בכדי להבדיל אותו משם מודל המחלקה. ובעת שימוש בשמות של מחלקות מודלים, הטבלאות המדוברות יחשבו כטבלאות בדיקה קבועות. כפי שהסברנו קודם, שמות הטבלאות הקבועות יאופסו למצב ידוע כלשהו בכל פעם שמתודת בדיקה תרוץ. - -שמות טבלאות קבועות מאפשרות לנו לגשת למידע הקבוע במתודות בדיקה בדרך מאוד נוחה. הקוד הבא מציג את השימוש האופיני: - -~~~ -[php] -// החזרת כל השורות בטבלה הקבועה `Comments` -$comments = $this-»comments; -// החזרת השורה ששמה המקוצר הוא `sample1` בטבלה `Post` -$post = $this-»posts['sample1']; -// החזרת אובייקט ה AR המייצג את השורה `sample1` -$post = $this-»posts('sample1'); -~~~ - -» Note|הערה: במידה וטבלה קבועה הוצהרה על ידי שם הטבלה שלה (לדוגמא `'posts'=»':Post'`), אז השימוש השלישי בקוד למעלה הוא אינו תקין מאחר ואין לנו מידע לגבי מחלקת המודל אשר משוייכת לטבלה. - -לאחר מכן, אנו כותבים את מתודת ה `testApprove` בכדי לבדוק את המתודה `approve` במחלקת המודל `Comment`. הקוד הוא מאוד ישיר: אנו קודם מוסיפים תגובה שהיא במצב המתנה; לאחר מכן אנו מוודאים שתגובה זו היא במצב המתנה על ידי קבלתה ממסד הנתונים; ולבסוף אנו קוראים למתודת `approve` ומאמתים שהססטוס השתנה בצורה נכונה. - -~~~ -[php] -public function testApprove() -{ - // הוספת תגובה במצב המתנה - $comment=new Comment; - $comment-»setAttributes(array( - 'content'=»'comment 1', - 'status'=»Comment::STATUS_PENDING, - 'createTime'=»time(), - 'author'=»'me', - 'email'=»'me@example.com', - 'postId'=»$this-»posts['sample1']['id'], - ),false); - $this-»assertTrue($comment-»save(false)); - - // אימות במידה והתגובה באמת במצב המתנה - $comment=Comment::model()-»findByPk($comment-»id); - $this-»assertTrue($comment instanceof Comment); - $this-»assertEquals(Comment::STATUS_PENDING,$comment-»status); - - // קריאה למתודה approve ובדיקת התגובה שהיא נמצאת במצב פעיל - $comment-»approve(); - $this-»assertEquals(Comment::STATUS_APPROVED,$comment-»status); - $comment=Comment::model()-»findByPk($comment-»id); - $this-»assertEquals(Comment::STATUS_APPROVED,$comment-»status); -} -~~~ - - +בדיקת יחידות +============ + +מאחר ומערכת הבדיקות של Yii בנויה על גבי [PHPUnit](http://www.phpunit.de/), מומלץ לעבור על [דוקומנטציה](http://www.phpunit.de/wiki/Documentation) של PHPUnit קודם בכדי לקבל מושג והבנה ראשונית בנוגע לכתיבת בדיקות. אנו מסכמים את העקרונות הבסיסיים אודות כתיבת בדיקות ב Yii. + +* בדיקת יחידה נכתבת במונחים של מחלקה `XyzTest` היורשת ממחלקת הבסיס [CTestCase] או [CDbTestCase]. כש `Xyz` הינה המחלקה שאותה בודקים. לדוגמא, בכדי לבדוק את המחלקה `Post`, אנו נקרא ליחידת הבדיקה כ `PostTest` לפי המוסכמות. המחלקה הבסיסית [CTestCase] נועדה לבדיקות יחידה כלליות, והמחלקה הבסיסית [CDbTestCase] תואמת לבדיקת מחלקות [AR](/doc/guide/database.ar). בגלל ש `PHPUnit_Framework_TestCase` הינה מחלקת אב לשני המחלקות, אנו יכולים להשתמש בכל המתודות של מחלקה זו. + +* מחלקת בדיקת היחידה נשמרת בקובץ PHP בשם `XyzTest.php`. לפי המוסכמות, ניתן לשמור את קובץ הבדיקה תחת התיקיה `protected/tests/unit`. + +* מחלקת הבדיקה מכילה בעיקר סט של מתודות בשם `testAbc`, כש `Abc` בדרך כלל הינו שם מתודת המחלקה לבדיקה. + +* מתודת בדיקה בדרך כלל מכילה רצף של ביטויי בדיקות (לדוגמא `assertTrue`, `assertEquals`) המשרתות כנקודות ביקורת לבדיקת התנהגות של המחלקה. + +בחלק הבא, אנו בעיקר נסביר כיצד לכתוב בדיקות יחידה עבור מחלקות [AR](/doc/guide/database.ar). אנו נרחיב את המחלקות שלנו ממחלקת הבסיס [CDbTestCase] מאחר והיא מספקת תמיכה בקיבוע מסדי הנתונים אשר הצגנו בחלק הקודם. + +נניח שאנו רוצים לבדוק את מחלקת המודל `Comment` [בדמו של הבלוג](http://www.yiiframework.com/demos/blog/). אנו מתחילים על ידי יצירת מחלקה בשם `CommentTest` ושמירתו תחת `protected/tests/unit/CommentTest.php`: + +~~~ +[php] +class CommentTest extends CDbTestCase +{ + public $fixtures=array( + 'posts'=»'Post', + 'comments'=»'Comment', + ); + + ...... +} +~~~ + +במחלקה, אנו מגדירים את המאפיין `fixtures` למערך הקובע אילו טבלאות קבועות יהיו בשימוש בבדיקה זו. המערך מייצג מיפוי של טבלאות קבועות בבדיקה לבין שמות מחלקות מודלים או שמות טבלאות (לדוגמא מטבלה קבוע `posts` למחלקת מודל `Posts` ). זכור, בעת מיפוי לשמות טבלאות קבועות למטרת הבדיקה, אנו צריכים להגדיר קידומת של נקודותיים (:) לשם הטבלה (לדוגמא `Post:` ) בכדי להבדיל אותו משם מודל המחלקה. ובעת שימוש בשמות של מחלקות מודלים, הטבלאות המדוברות יחשבו כטבלאות בדיקה קבועות. כפי שהסברנו קודם, שמות הטבלאות הקבועות יאופסו למצב ידוע כלשהו בכל פעם שמתודת בדיקה תרוץ. + +שמות טבלאות קבועות מאפשרות לנו לגשת למידע הקבוע במתודות בדיקה בדרך מאוד נוחה. הקוד הבא מציג את השימוש האופיני: + +~~~ +[php] +// החזרת כל השורות בטבלה הקבועה `Comments` +$comments = $this-»comments; +// החזרת השורה ששמה המקוצר הוא `sample1` בטבלה `Post` +$post = $this-»posts['sample1']; +// החזרת אובייקט ה AR המייצג את השורה `sample1` +$post = $this-»posts('sample1'); +~~~ + +» Note|הערה: במידה וטבלה קבועה הוצהרה על ידי שם הטבלה שלה (לדוגמא `'posts'=»':Post'`), אז השימוש השלישי בקוד למעלה הוא אינו תקין מאחר ואין לנו מידע לגבי מחלקת המודל אשר משוייכת לטבלה. + +לאחר מכן, אנו כותבים את מתודת ה `testApprove` בכדי לבדוק את המתודה `approve` במחלקת המודל `Comment`. הקוד הוא מאוד ישיר: אנו קודם מוסיפים תגובה שהיא במצב המתנה; לאחר מכן אנו מוודאים שתגובה זו היא במצב המתנה על ידי קבלתה ממסד הנתונים; ולבסוף אנו קוראים למתודת `approve` ומאמתים שהססטוס השתנה בצורה נכונה. + +~~~ +[php] +public function testApprove() +{ + // הוספת תגובה במצב המתנה + $comment=new Comment; + $comment-»setAttributes(array( + 'content'=»'comment 1', + 'status'=»Comment::STATUS_PENDING, + 'createTime'=»time(), + 'author'=»'me', + 'email'=»'me@example.com', + 'postId'=»$this-»posts['sample1']['id'], + ),false); + $this-»assertTrue($comment-»save(false)); + + // אימות במידה והתגובה באמת במצב המתנה + $comment=Comment::model()-»findByPk($comment-»id); + $this-»assertTrue($comment instanceof Comment); + $this-»assertEquals(Comment::STATUS_PENDING,$comment-»status); + + // קריאה למתודה approve ובדיקת התגובה שהיא נמצאת במצב פעיל + $comment-»approve(); + $this-»assertEquals(Comment::STATUS_APPROVED,$comment-»status); + $comment=Comment::model()-»findByPk($comment-»id); + $this-»assertEquals(Comment::STATUS_APPROVED,$comment-»status); +} +~~~ + + «div class="revision"»$Id: test.unit.txt 1745 2010-01-24 14:23:36Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/toc.txt b/docs/guide/he/toc.txt index 9677c952a..663005344 100644 --- a/docs/guide/he/toc.txt +++ b/docs/guide/he/toc.txt @@ -1,70 +1,70 @@ -* התחלה - - [סקירה](index) - - [שינויים ואפשרויות חדשות](changes) - - [עדכון המערכת מגרסא 1.0 לגרסא 1.1](upgrade) - - [מה זה Yii](quickstart.what-is-yii) - - [התקנה](quickstart.installation) - - [יצירת אפליקציה בפעם הראשונה](quickstart.first-app) - -* יסודות המערכת - - [מודל-תצוגה-קונטרולר (MVC)](basics.mvc) - - [אתחול המערכת](basics.entry) - - [אפליקציות](basics.application) - - [קונטרולר](basics.controller) - - [מודל](basics.model) - - [תצוגה](basics.view) - - [רכיב](basics.component) - - [מודול](basics.module) - - [נתיבי קבצים ומרחבי שמות](basics.namespace) - - [מוסכמות לאופן כתיבת הקוד](basics.convention) - - [רצף עבודת התכנות](basics.workflow) - -* עבודה עם טפסים - - [סקירה](form.overview) - - [יצירת מודל](form.model) - - [יצירת פעולה](form.action) - - [יצירת טופס](form.view) - - [איסוף קלט טבלאי](form.table) -- [שימוש במערכת יצירת הטפסים](form.builder) - -* עבודה עם מסדי נתונים - - [סקירה](database.overview) - - [שימוש ב DAO](database.dao) - - [שימוש ב AR](database.ar) - - [שימוש ב ARR](database.arr) - -* ניהול מטמון - - [סקירה](caching.overview) - - [מטמון נתונים](caching.data) - - [מטמון בחלקים](caching.fragment) - - [מטמון עמודים](caching.page) - - [תוכן דינאמי](caching.dynamic) - -* הרחבת Yii - - [סקירה](extension.overview) - - [שימוש בתוספות](extension.use) - - [יצירת תוספות](extension.create) - - [שימוש בספריות צד שלישי](extension.integration) - -* הרצת בדיקות - - [סקירה](test.overview) - - [נתונים התחלתיים](test.fixture) - - [בדיקות יחידה](test.unit) - - [בדיקות פונקציונליות](test.functional) - -* נושאים מיוחדים - - [יצירת קוד אוטומטי](topics.gii) - - [ניהול קישורים](topics.url) - - [אימות משתמשים וניהול גישות](topics.auth) - - [ניהול תבניות ועיצוב](topics.theming) - - [תיעוד פעולות](topics.logging) - - [ניהול וטיפול בשגיאות](topics.error) - - [שרותים חיצוניים](topics.webservice) - - [ניהול ותמיכה בשפות](topics.i18n) - - [תחביר תצוגה נוסף](topics.prado) - - [אפלקציות מסוף ובקרה](topics.console) - - [אבטחה](topics.security) - - [שיפור ביצועי המערכת](topics.performance) - - [יצירת קןד בעזרת כלי שורת הפקודות (לא נתמך)](quickstart.first-app-yiic) - +* התחלה + - [סקירה](index) + - [שינויים ואפשרויות חדשות](changes) + - [עדכון המערכת מגרסא 1.0 לגרסא 1.1](upgrade) + - [מה זה Yii](quickstart.what-is-yii) + - [התקנה](quickstart.installation) + - [יצירת אפליקציה בפעם הראשונה](quickstart.first-app) + +* יסודות המערכת + - [מודל-תצוגה-קונטרולר (MVC)](basics.mvc) + - [אתחול המערכת](basics.entry) + - [אפליקציות](basics.application) + - [קונטרולר](basics.controller) + - [מודל](basics.model) + - [תצוגה](basics.view) + - [רכיב](basics.component) + - [מודול](basics.module) + - [נתיבי קבצים ומרחבי שמות](basics.namespace) + - [מוסכמות לאופן כתיבת הקוד](basics.convention) + - [רצף עבודת התכנות](basics.workflow) + +* עבודה עם טפסים + - [סקירה](form.overview) + - [יצירת מודל](form.model) + - [יצירת פעולה](form.action) + - [יצירת טופס](form.view) + - [איסוף קלט טבלאי](form.table) +- [שימוש במערכת יצירת הטפסים](form.builder) + +* עבודה עם מסדי נתונים + - [סקירה](database.overview) + - [שימוש ב DAO](database.dao) + - [שימוש ב AR](database.ar) + - [שימוש ב ARR](database.arr) + +* ניהול מטמון + - [סקירה](caching.overview) + - [מטמון נתונים](caching.data) + - [מטמון בחלקים](caching.fragment) + - [מטמון עמודים](caching.page) + - [תוכן דינאמי](caching.dynamic) + +* הרחבת Yii + - [סקירה](extension.overview) + - [שימוש בתוספות](extension.use) + - [יצירת תוספות](extension.create) + - [שימוש בספריות צד שלישי](extension.integration) + +* הרצת בדיקות + - [סקירה](test.overview) + - [נתונים התחלתיים](test.fixture) + - [בדיקות יחידה](test.unit) + - [בדיקות פונקציונליות](test.functional) + +* נושאים מיוחדים + - [יצירת קוד אוטומטי](topics.gii) + - [ניהול קישורים](topics.url) + - [אימות משתמשים וניהול גישות](topics.auth) + - [ניהול תבניות ועיצוב](topics.theming) + - [תיעוד פעולות](topics.logging) + - [ניהול וטיפול בשגיאות](topics.error) + - [שרותים חיצוניים](topics.webservice) + - [ניהול ותמיכה בשפות](topics.i18n) + - [תחביר תצוגה נוסף](topics.prado) + - [אפלקציות מסוף ובקרה](topics.console) + - [אבטחה](topics.security) + - [שיפור ביצועי המערכת](topics.performance) + - [יצירת קןד בעזרת כלי שורת הפקודות (לא נתמך)](quickstart.first-app-yiic) + «div class="revision"»$Id: toc.txt 2098 2009-12-18 19:39:18Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/topics.auth.txt b/docs/guide/he/topics.auth.txt index ba8951e78..0b772bef8 100644 --- a/docs/guide/he/topics.auth.txt +++ b/docs/guide/he/topics.auth.txt @@ -1,376 +1,376 @@ -אימות משתמשים וניהול גישות -================================ - -אימות וניהול גישות עבור משתמשים דרוש הם פרט הכרחי עבור עמודים אשר מוגבלים למשתמשים מסויימים. אימות משתמשים נועדה לוודא שהמשתמש שמאומת הוא אכן מי שהוא טוען שהוא. פעולה זו בדרך כלל כרוכה בהזנה של שם משתמש וסיסמא, אך יכולה לכלול דרכים נוספות כדי להציג את הזהות, כמו כרטיס חכם, טביעות אצבע, וכדומה. ניהול גישות הינה פעולה שמבררת אם המשתמש, אשר מחובר (לאחר ביצוע האימות), מורשה לתפעל משאבים מסויימים. -זה בדרך כלל נקבע על ידי בירור אם אותו משתמש הוא בעל תפקיד אשר יכול לגשת לאותם משאבים. - -Yii מגיעה עם מערכת אימות וניהול משתמשים מובנת אשר קלה לשימוש וניתנת להתאמה אישית לצרכים מיוחדים. - -החלק המרכזי במערכת אימות המשתמשים הוא רכיב המשתמשים אשר מוגדר מראש שהינו אובייקט המיישם את הממשק [IWebUser]. רכיב המשתמשים מייצג את המידע הקבוע אודות זהות המשתמש הנוכחי. אנו יכולים לגשת אליו בכל מקום בעזרת `Yii::app()-»user`. - -על ידי שימוש ברכיב המשתמשים אנו יכולים לבדוק אם משתמש מחובר או לא בעזרת [CWebUser::isGuest]; אנו יכולים [לחבר|CWebUser::login] [ולנתק|CWebUser::logout] משתמש; אנו יכולים לבדוק אם משתמש מורשה לבצע פעולות מסויימות בעזרת קריאה ל [CWebUser::checkAccess]; כמו כן אנו יכולים להשיג את [המזהה היחודי|CWebUser::name] של המשתמש הנוכחי ומידע נוסף אודות זהות המשתמש. - -הגדרת מחלקת זהות משתמש ------------------------ - -בכדי לזהות משתמש, אנו מגדירים מחלקת זהות משתמש אשר כוללת את לוגיקת האימות עצמה. מחלקת זהות המשתמש צריכה ליישם את הממשק [IUserIdentity]. מחלקות שונות ניתנות ליישום עבור גישות אימות משתמשים שונות (לדוגמא, OpenID, LDAP). התחלה טובה תיהיה על ידי הרחבה של המחלקה [CUserIdentity] שהינה המחלקה הבסיסית עבור אימות המשתמשים בהתבסס על שם משתמש וסיסמא. - -העבודה העיקרית בהגדרת מחלקת זהות חדשה היא היישום של המתודה [IUserIdentity::authenticate]. מחלקת הזהות יכולה להגדיר מידע נוסף אודות זהות המשתמש שצריך להיות קבוע ונגיש במשך כל הזמן בו המשתמש נמצא באתר. - -בדוגמא הבאה, אנו מאמתים את השם משתמש והסיסמא שסופקו אל מול טבלת המשתמשים הנמצאת במסד בעזרת [Active Record](/doc/guide/database.ar). כמו כן אנו דורסים את המתודה `getId` כדי שתחזיר את המשתנה `id_` המוגדר במהלך אימות המשתמש (כברירת מחדל היישום מחזיר את שם המשתמש כמזהה יחודי של המשתמש). במהלך האימות, אנו שומרים את הערך `title` שנשלף מהמסד בתור מידע קבוע באותו השם על ידי קריאה ל [CBaseUserIdentity::setState]. - -~~~ -[php] -class UserIdentity extends CUserIdentity -{ - private $_id; - public function authenticate() - { - $record=User::model()-»findByAttributes(array('username'=»$this-»username)); - if($record===null) - $this-»errorCode=self::ERROR_USERNAME_INVALID; - else if($record-»password!==md5($this-»password)) - $this-»errorCode=self::ERROR_PASSWORD_INVALID; - else - { - $this-»_id=$record-»id; - $this-»setState('title', $record-»title); - $this-»errorCode=self::ERROR_NONE; - } - return !$this-»errorCode; - } - - public function getId() - { - return $this-»_id; - } -} -~~~ - -הנתונים הנשמרים במידע הקבוע (על ידי קריאה ל [CBaseUserIdentity::setState]) יועברו ל [CWebUser] אשר שומר אותם באחסון קבוע, כמו לדוגמא Session. לנתונים אלו ניתן לגשת כמאפיינים של [CWebUser]. לדוגמא, אנו יכולים להשיג את הערך `title` של המשתמש הנוכחי בעזרת `Yii::app()-»user-»title` (אפשרות זו קיימת החל מגרסא 1.0.3. בגרסאות קודמות, אנו צריכים להשתמש ב `Yii::app()-»user-»getState('title')`). - -» Info|מידע: כברירת מחדל, [CWebUser] משתמש ב Session כאחסון קבוע למידע אודות זהות המשתמש. במידה והאפשרות של התחברות עם עוגיות פעילה (על ידי הגדרת המאפיין [CWebUser::allowAutoLogin] ל True ), ניתן לשמור את המידע אודות זהות המשתמש בעוגיה. וודא שאינך שומר ערכים רגישים בתוך האחסון הקבוע של זהות המשתמש (לדוגמא סיסמא). - -התחברות והתנתקות ----------------- - -שימוש במחלקת הזהות ורכיב ניהול המשתמשים, אנו יכולים ליישם פעולות התחברות וניתוק בקלות. - -~~~ -[php] -// חיבור משתמש עם שם המשתמש והסיסמא -$identity=new UserIdentity($username,$password); -if($identity-»authenticate()) - Yii::app()-»user-»login($identity); -else - echo $identity-»errorMessage; -...... -// ניתוק המשתמש הנוכחי -Yii::app()-»user-»logout(); -~~~ - -כברירת מחדל, משתמש ינותק אחרי זמן מסויים של חוסר פעילות, תלוי בהגדרות [session](http://www.php.net/manual/en/session.configuration.php). -בכדי לשנות התנהגות זו, אנו יכולים להגדיר את המאפיין [allowAutoLogin|CWebUser::allowAutoLogin] של רכיב המשתמשים לערך השווה ל true ולהעביר את משך זמן החיבור הפעיל כפרמטר למתודה [CWebUser::login]. -כתוצאה המשתמש יהיה מחובר במשך הזמן שהוגדר, גם אם הוא סוגר את הדפדפן. יש לזכור שאפשרות זו דורשת מדפדפן המשתמש לאפשר שמירת עוגיות. - -~~~ -[php] -// חיבור המשתמש למשך 7 ימים -// יש לוודא שהערך allowAutoLogin הוגדר ל true -Yii::app()-»user-»login($identity,3600*24*7); -~~~ - -פילטר ניהול גישות ---------------------- - -פילטר ניהול גישות הינה תרשים הרשאות המקדים את הפעולה שהמשתמש רוצה לבצע ובודק אם אותו משתמש יכול לבצע אותה. ההרשאות מבוססות על שם משתמש, כתובת IP וסוגי בקשות. ניהול הגישות מתבצע על ידי פילטר בשם ["accessControl"|CController::filterAccessControl]. - -» Tip|טיפ: פילטר ניהול גישות הוא מספק עבור מקרים פשוטים. עבור מקרים יותר מסובכים של ניהול גישות והרשאות, ניתן להשתמש בניהול גישות המבוסס על פי תפקידים (RBAC (או קבוצות)) אשר מפורט בהמשך. - -בכדי לשלוט בגישה לפעולות בקונטרולר, אנו מתקינים את פילטר ניהול הגישות על ידי דריסה של המתודה [CController::filters] (ראה -[Filter](/doc/guide/basics.controller#filter) למידע נוסף אודות התקנת פילטר). - -~~~ -[php] -class PostController extends CController -{ - ...... - public function filters() - { - return array( - 'accessControl', - ); - } -} -~~~ - -בקוד המוצג למעלה, אנו מגדירים שפילטר ניהול הגישות מצורף לכל פעולה של הקונטרולר `PostController`. חוקי ההרשאה המפורטים של פילטר זה מוגדרים על ידי דריסה של המתודה [CController::accessRules] במחלקה של הקונטרולר. - -~~~ -[php] -class PostController extends CController -{ - ...... - public function accessRules() - { - return array( - array('deny', - 'actions'=»array('create', 'edit'), - 'users'=»array('?'), - ), - array('allow', - 'actions'=»array('delete'), - 'roles'=»array('admin'), - ), - array('deny', - 'actions'=»array('delete'), - 'users'=»array('*'), - ), - ); - } -} -~~~ - -הקוד למעלה מגדיר 3 חוקים, כל אחד מיוצג כמערך. האלמנט הראשון במערך הוא `'allow'` או `'deny'` ושאר האלמנטים מגדירים את הפרמטרים של דפוס החוק. חוקים אלו מבצעים: הפעולות `action` ו `create` לא ניתנות לביצוע על ידי משתמשים אנונימיים; פעולת ה `delete` ניתנת לביצוע על ידי משתמשים עם תפקיד של `admin`; ופעולת ה `delete` לא ניתנת לביצוע על ידי כל אחד. - -חוקי הגישות מוערכים אחד אחד בסדר בהם הם הוגדרו. החוק הראשון שתואם לדפו הנוכחי (לדוגמא שם משתמש, תפקיד, כתובת IP, כתובת) קובעת את תוצאת ההרשאה. אם חוק זה הוא חוק `allow`, הפעולה ניתנת לביצוע; במידה וחוק זה הוא `deny`, הפעולה לא ניתנת לביצוע; אם אף אחד מהחוקים לא תואם להקשר, הפעולה עדיין ניתנת לביצוע. - -» Tip|טיפ: בכדי לוודא שהפעולה לא תתבצע תחת הקשר מסויים, רצוי להגדיר חוק התואם לכל של `deny` בסוף סט החוקים, כבדוגמא הבאה: -» ~~~ -» [php] -» return array( -» // ...חוקים נוספים.... -» // החוק הבא מונע גישה לפעולה של מחיקה `delete` בכל ההקשרים -» array('deny', -» 'actions'=»array('delete'), -» ), -» ); -» ~~~ -» הסיבה שאנו מוסיפים את החוק הזה בסוף הוא כדי למנוע הרצה של הפעולה `delete` במידה ולא נמצא שום הקשר למשתמש הנוכחי. אחרת הפעולה תרוץ אם לאף אחד מהחוקים שמעליו לא נמצאה התאמה. - -לכל הקשר בחוקים ניתן להגדיר את הפרמטרים הבאים: - -- [actions|CAccessRule::actions]: מציין אילו פעולות החוק הזה תקף לגביהם ויהיה ניסיון התאמה. הגדרה זו צריכה להיות מערך של אלמנטים שהם שמות היחודיים של הפעולות. ההשוואה בין השמות אינה רגישה לאותיות גדולות-קטנות. - -- [controllers|CAccessRule::controllers]: מציין אילו קונטרולרים החוק הזה תקף לגביהם ויהיה ניסיון התאמה. הגדרה זו צריכה להיות מערך של אלמנטים שהם שמות היחודיים של הקונטרולרים. ההשוואה בין השמות אינה רגישה לאותיות גדולות-קטנות. אפשרות זו קיימת החל מגרסא 1.0.4. - -- [users|CAccessRule::users]: מציין אילו משתמשים החוק הזה תקף לגביהם ויהיה ניסיון התאמה. הערך שעליו תתבצע ההשואה הוא שם המשתמש - [name|CWebUser::name] ברכיב המשתמשים. ההשוואה בין השמות אינה רגישה לאותיות גדולות-קטנות. ניתן להשתמש כאן בשלושה תווים מיוחדים: - - - `*`: כל משתמש, הכולל גם את המשתמשים האנונימיים והמשתמשים שפרטיהם אומתו (מחוברים). - - `?`: משתמשים אנונימים. - - `@`: משתמשים אשר פרטיהם אומתו (מחוברים). - -- [roles|CAccessRule::roles]: מציין אילו תפקידים (קבוצות) החוק הזה תקף לגביהם ויהיה ניסיון התאמה. אפשרות זו נעזרת [בניהול גישות המבוססת על תפקידים (קבוצות)](#role-based-access-control) אשר מתוארת בחלק הבא. חוק זה מצורף אם [CWebUser::checkAccess] מחזיר ערך true לאחד התפקידים. זכור, רצוי להשתמש בתפקידים בחוק שהוא מוגדר כ `allow` ולא `deny` מאחר ועל פי ההגדרה, תפקיד מייצג הרשאה לבצע פעולה מסויימת. כמו כן, רצוי לזכור שלמרות שאנו משתמשים במונח תפקידים (`roles`) במקרה הזה, הערך שלו יכול להיות כל ערך הרשאתי, הכולל תפקידים, משימות ופעולות. - -- [ips|CAccessRule::ips]: מציין אילו כתובות IP החוק הזה תקף לגביהם ויהיה ניסיון התאמה עם הכתובת של המשתמש הנוכחי. - -- [verbs|CAccessRule::verbs]: מציין אילו סוגי בקשות (`GET`, `POST`) החוק הזה תקף לגביהם ויהיה ניסיון התאמה עם הבקשה הנוכחית. השוואה זו אינה רגישה לאותיות גדולות-קטנות. - -- [expression|CAccessRule::expression]: מציין ביטוי ב PHP שהתוצאה מעידה אם החוק הזה תואם או לא. בביטוי, ניתן להשתמש במשתנה `user$` אשר מתייחס ל `Yii::app()-»user`. אפשרות זו קיימת החל מגרסא 1.0.3. - -### טיפול בתוצאת האימות (הגישה) - -ברגע שהגישה נדחית, כלומר, המשתמש אינו מורשה לבצע את הפעולה המיוחסת, אחד משני המקרים הבאים יכול לקרות: - -- במידה והמשתמש אינו מחובר והמאפיין [loginUrl|CWebUser::loginUrl] של רכיב המשתמשים מוגדר להיות עמוד ההתחברות, המשתמש יועבר לעמוד זה. יש לזכור שכברירת מחדל, [loginUrl|CWebUser::loginUrl] מכוון לעמוד `site/login`. - -- אחרת תזרק שגיאה חריגה (Exception) ותוצג שגיאת HTTP 304. - -בעת ההגדרה של המאפיין [loginUrl|CWebUser::loginUrl], ניתן לציין נתיב רלטיבי או מלא. כמו כן ניתן להגדיר את המאפיין כמערך שמציין כתובת לקונטרולר/פעולה ואז יעשה שימוש ב [CWebApplication::createUrl] בכדי להפוך את המערך לקישור נכון. האלמנט הראשון במערך מציין את [הנתיב](/doc/guide/basics.controller#route) לקונטרולר של עמוד ההתחברות והפעולה שצריך להריץ, ושאר האלמנטים בפורמט של מפתח=»ערך הינם פרמטרים (GET) אשר יתווספו לקישור בעת יצירתו. לדוגמא, - -~~~ -[php] -array( - ...... - 'components'=»array( - 'user'=»array( - // זהו בעצם ערך ברירת המחדל - 'loginUrl'=»array('site/login'), - ), - ), -) -~~~ - -במידה והמשתמש הועבר לעמוד ההתחברות והוא התחבר בהצלחה, אנו נרצה להעביר אותו לאותו עמוד שבו הוא היה קודם לכן כשהגישה שלו נדחתה. כיצד אנו יודעים מהו הקישור לעמוד זה? אנו יכולים לקבל מידע זה מהמאפיין [returnUrl|CWebUser::returnUrl] של רכיב המשתמשים. לכן אנו יכולים לכתוב את הקוד הבא כדי לבצע את ההעברה לעמוד בו הוא היה קודם: - -~~~ -[php] -Yii::app()-»request-»redirect(Yii::app()-»user-»returnUrl); -~~~ - -ניהול גישות על פי תפקידים (קבוצות משתמשים) -------------------------- - -ניהול גישות על פי תפקידים (RBAC) מספק מקום מרכזי לניהול הגישות בצורה פשוטה אך יעילה. יש לעיין במאמר [Wiki](http://en.wikipedia.org/wiki/Role-based_access_control) כדי לקבל פרטים והשוואות של RBAC מול אפשרויות ניהול גישות מסורתיות אחרות. - -Yii מיישם תכנית RBAC היררכי בעזרת הרכיב [authManager|CWebApplication::authManager]. בחלק הבא, אנו קודם מציגים את המושגים העקריים בהם משתמשים בעיקר בתכנית זו; בסוף החלק אנו מציגים כיצד להשתמש באימות נתונים כדי לבדוק אפשרויות גישה שונות. - -### סקירה - -העיקרון העומד מאחורי ה RBAC במערכת ה-Yii הוא *פריט הרשאה*. פריט הרשאה הינו סמכות לבצע פעולה מסויימת (לדוגמא יצירת הודעה חדשה, ניהול משתמשים). בהתאם לגרעיניות וקהל היעד, ניתן לסווג פריטי הרשאה כ *פעולות*, *משימות* ו *תפקידים*. תפקיד מכיל משימות, משימה מכילה פעולות, ופעולה הינה הרשאה שהיא אטומית. לדוגמא, במידה ויש לנו מערכת עם תפקיד בשם `administrator` המכיל משימות של `ניהול הודעות` ו `ניהול משתמשים`. המשימה `ניהול משתמשים` מכילה פעולות כמו `יצירת משתמש`, `עדכון משתמש` ו `מחיקת משתמש`. למקסימום גמישות, Yii מאפשרת לתפקיד להכיל תפקידים או פעולות נוספות, למשימה להכיל משימות נוספות, ופעולות להכיל פעולות נוספות (זאת אומרת לשרשר בין תפקידים משימות ופעולות). - -פריט הרשאה מזוהה בצורה יחודית על ידי שמו. - -פריט הרשאה יכול להיות מקושר *קוד עסקי*. קוד עסקי הינו חתיכת קוד PHP אשר ירוץ בעת בדיקת הגישה תוך התחשבות בפריט ההרשאתי. רק ברגע שהקוד שירוץ יחזיר True, אז המשתמש יחשב כבעל גישה המיוצג על ידי הפריט הזה. לדוגמא, בזמן שאנו מגדירים פעולה `updatePost`, אנו נרצה להוסיף קוד עסקי שבודק שמספר המשתמש הנוכחי הוא זהה למספר המשתמש של המפרסם שפרסם את ההודעהת ככה שרק המשתמש שפרסם את ההודעה יהיה ראשי לערוך אותה. - -בעזרת שימוש בפריטי הרשאה, אנו יכולים לבנות *היררכית הרשאות*. פריט `A` הוא אב לפריט אחר בשם `B` בהיררכיה אם `A` מכיל את `B` (כלומר `A` יורש את ההרשאות המיוצגות על ידי `B`). פריט יכול להכיל כמה תתים, כמו כן הוא יכול להכיל כמה פריטים מעליו (אבות). לכן, היררכית הרשאות הינו גרף ולא עץ. בהיררכיה זו, פריטי תפקידים יושבים ברמה הגבוה ביותר, פעולות יושבות ברמה הנמוכה ביותר, בזמן שמשימות יושבות באמצע. - -ברגע שיש ברשותנו היררכית הרשאות, אנו יכולים להקצות תפקידים בהירככיה זו למשתמשים באפליקציה. משתמש, ברגע שתפקיד מוקצה לו, יהיו ברשותו ההרשאות המיוצגות על ידי אותו תפקיד. לדוגמא, אם נקצה את התפקיד `administrator` למשתמש, הוא יוכל לבצע את המשימות של מנהל ראשי, משימות הכוללות לדוגמא `ניהול הודעות` ו `ניהול משתמשים` (והפעולות המקבילות למשימות אלו). - -עכשיו החלק המהנה מתחיל. בפעולה בקונטרולר, אנו רוצים לבדוק אם המשתמש הנוכחי יכול למחוק את ההודעה שהוגדרה. שימוש בהיררכית RBAC וההקצאות, ניתן לבצע זאת בצורה פשוטה בדרך הבאה: - -~~~ -[php] -if(Yii::app()-»user-»checkAccess('deletePost')) -{ - // מחיקת ההודעה -} -~~~ - -### הגדרת ניהול הרשאות - -לפני שאנו ניגשים להגדרת היררכית הרשאות ולבצע בדיקות גישה, אנו צריכים להגדיר את הרכיב [authManager|CWebApplication::authManager] באפליקציה. Yii מספקת שני סוגים של ניהול גישות: [CPhpAuthManager] ו -[CDbAuthManager]. הראשון משתמש בקובץ סקריפט PHP כדי לאחסן את נתוני ההרשאות, בזמן שהשני שומר את נתוני ההרשאות במסד הנתונים. כשאנו מגדירים את רכיב האפליקציה [authManager|CWebApplication::authManager], אנו צריכים לציין איזה מחלקה אנו נשתמש בה ומה הם הערכים ההתחלתיים לרכיב. לדוגמא, - -~~~ -[php] -return array( - 'components'=»array( - 'db'=»array( - 'class'=»'CDbConnection', - 'connectionString'=»'sqlite:path/to/file.db', - ), - 'authManager'=»array( - 'class'=»'CDbAuthManager', - 'connectionID'=»'db', - ), - ), -); -~~~ - -לאחר מכן אנו יכולים לגשת אל הרכיב [authManager|CWebApplication::authManager] בעזרת `Yii::app()-»authManager`. - -### הגדרת היררכית הרשאות - -הגדרת היררכית הרשאות כרוכה בשלושה שלבים: הגדרת פריטי הרשאה, יצירת קישורים בין פריטי הרשאה, והקצאת תפקידים למשתמשים באפליקציה. הרכיב [authManager|CWebApplication::authManager] מספק סט שלם של מתודות (API) כדי להשיג מטרות אלו. - -כדי להגדיר פריט הרשאה, יש לקרוא לאחת מהמתודות הבאות, תלוי בסוג הפריט: - - - [CAuthManager::createRole] - - [CAuthManager::createTask] - - [CAuthManager::createOperation] - -ברגע שיש לנו סט של פריטי הרשאה, אנו יכולים לקרוא למתודות הבאות בכדי ליצור קשרים בין פריטי ההרשאה השונים: - - - [CAuthManager::addItemChild] - - [CAuthManager::removeItemChild] - - [CAuthItem::addChild] - - [CAuthItem::removeChild] - -ולבסוף, אנו קוראים למתודות הבאות כדי להקצות תפקידים למשתמשים בודדים: - - - [CAuthManager::assign] - - [CAuthManager::revoke] - -למטה אנו מציגים דוגמא ליצירת היררכית הרשאות עם המתודות שהוצגו קודם לכן: - -~~~ -[php] -$auth=Yii::app()-»authManager; - -$auth-»createOperation('createPost','יצירת הודעה'); -$auth-»createOperation('readPost','קריאת הודעה'); -$auth-»createOperation('updatePost','עריכת הודעה'); -$auth-»createOperation('deletePost','מחיקת הודעה'); - -$bizRule='return Yii::app()-»user-»id==$params["post"]-»authID;'; -$task=$auth-»createTask('updateOwnPost','עריכת הודעה על ידי המפרסם בלבד',$bizRule); -$task-»addChild('updatePost'); - -$role=$auth-»createRole('reader'); -$role-»addChild('readPost'); - -$role=$auth-»createRole('author'); -$role-»addChild('reader'); -$role-»addChild('createPost'); -$role-»addChild('updateOwnPost'); - -$role=$auth-»createRole('editor'); -$role-»addChild('reader'); -$role-»addChild('updatePost'); - -$role=$auth-»createRole('admin'); -$role-»addChild('editor'); -$role-»addChild('author'); -$role-»addChild('deletePost'); - -$auth-»assign('reader','readerA'); -$auth-»assign('author','authorB'); -$auth-»assign('editor','editorC'); -$auth-»assign('admin','adminD'); -~~~ - -» Info|מידע: למרות שהקוד למעלה נראה ארוך ומשעמם, הוא נועד בעיקר לצרכי הדגמה לשימוש במתודות. מפתחים בדרך כלל צריכים לפתח ממשקים אשר מפשטים את הפעולות ההלו למשתמשי הקצה ויוצרים היררכיה הרשאות באופן אינטואיטיבי. - -### שימוש בקודים עסקיים - -כשאנו מגדירים היררכית הרשאות, אנו יכולים לקשר תפקיד, משימה או פעולה למה שנקרא *קוד עסקי*. כמו כן אנו יכולים לקשר קוד עסקי כשאנו מקצים תפקיד למשתמש. קוד עסקי הינו חתיכת קוד PHP שירוץ ברגע שאנו נבצע בדיקת גישה. הערך המוחזר מהקוד העסקי נועד כדי לקבוע אם התפקיד או המשימה חלה על המשתמש הנוכחי. בדוגמא למעלה, אנו קישרנו קוד עסקי למשימה `updateOwnPost`. בקוד העסקי אנו פשוט בודקים אם מספר המזהה היחודי של המשתמש הנוכחי הוא זהה למספר המשתמש שפרסם את ההודעה. פרטי ההודעה במערך ה `params$` מסופק על ידי המפתחים בעת ביצוע בדיקת הגישה. - -### בדיקת גישה - -בכדי לבצע בדיקת גישה, אנו קודם צריכים לדעת את שם פריט ההרשאה. לדוגמא, כדי לבדוק אם המשתמש הנוכחי יכול ליצור הודעה, אנו נבדוק אם יש לו הרשאה המיוצגת על ידי הפעולה `createPost`. אז אנו קוראים ל-[CWebUser::checkAccess] כדי לבצע את בדיקת הגישה: - -~~~ -[php] -if(Yii::app()-»user-»checkAccess('createPost')) -{ - // יצירת הודעה -} -~~~ - -אם פריט ההרשאה מקושר עם קוד עסקי שדורש פרמטרים נוספים, אנו יכולים להעביר אותם גם כן. לדוגמא, בכדי לבדוק אם משתמש יכול לערוך הודעה, אנו נכתוב, - -~~~ -[php] -$params=array('post'=»$post); -if(Yii::app()-»user-»checkAccess('updateOwnPost',$params)) -{ - // עדכון הודעה -} -~~~ - - -### שימוש בתפקידים מוגדרים מראש - -» Note|הערה: אפשרות תפקידי ברירת המחדל קיימת החל מגרסא 1.0.3. - -אפליקציות ווב רבות צריכות כמה תפקידים מאוד מיוחדים שיוקצו לכל המשתמשים או לרובם. לדוגמא, אנו נרצה להקצות הרשאות לכל המשתמשים המחוברים (שאומתו). זה דורש הרבה עבודת תחזוקה אם אנו נגדיר ונשמור את כל ההקצאות הללו במפורש. אנו יכולים לנצל את האפשרות של *תפקידי ברירת מחדל* (*default roles*) כדי לפתור בעיה זו. - -תפקיד ברירת מחדל הינו תפקיד אשר הוקצה לכל משתמש, הכוללים את האורחים והמשתמשים כאחד. אנו לא צריכים להקצות תפקידים אלו עבור כל משתמש בנפרד. ברגע שמתבצעת קריאה של [CWebUser::checkAccess], תפקידי ברירת המחדל נבדקים ראשונים במידה והם הוקצו למשתמש. - -תפקידי ברירת מחדל צריכים להיות מוגדרים במאפיין [CAuthManager::defaultRoles]. לדוגמא, הקוד הבא מגדיר שני תפקידים כתפקידים ברירת מחדל: `authenticated` ו `guest`. - -~~~ -[php] -return array( - 'components'=»array( - 'authManager'=»array( - 'class'=»'CDbAuthManager', - 'defaultRoles'=»array('authenticated', 'guest'), - ), - ), -); -~~~ - -מאחר ותפקיד ברירת מחדל מוקצה לכל משתמש, הוא בדרך כלל צריך להיות מקושר עם קוד עסקי בכדי לקבוע אם אותו תפקיד באמת חל על המשתמש הנוכחי. לדוגמא, הקוד הבא מגדיר שני תפקידים, `authenticated` ו `guest`, אשר למעשה חל על משתמשים מחוברים ומשתמשים שהינם אורחים, בהתאמה. - -~~~ -[php] -$bizRule='return !Yii::app()-»user-»isGuest;'; -$auth-»createRole('authenticated', 'authenticated user', $bizRule); - -$bizRule='return Yii::app()-»user-»isGuest;'; -$auth-»createRole('guest', 'guest user', $bizRule); -~~~ - +אימות משתמשים וניהול גישות +================================ + +אימות וניהול גישות עבור משתמשים דרוש הם פרט הכרחי עבור עמודים אשר מוגבלים למשתמשים מסויימים. אימות משתמשים נועדה לוודא שהמשתמש שמאומת הוא אכן מי שהוא טוען שהוא. פעולה זו בדרך כלל כרוכה בהזנה של שם משתמש וסיסמא, אך יכולה לכלול דרכים נוספות כדי להציג את הזהות, כמו כרטיס חכם, טביעות אצבע, וכדומה. ניהול גישות הינה פעולה שמבררת אם המשתמש, אשר מחובר (לאחר ביצוע האימות), מורשה לתפעל משאבים מסויימים. +זה בדרך כלל נקבע על ידי בירור אם אותו משתמש הוא בעל תפקיד אשר יכול לגשת לאותם משאבים. + +Yii מגיעה עם מערכת אימות וניהול משתמשים מובנת אשר קלה לשימוש וניתנת להתאמה אישית לצרכים מיוחדים. + +החלק המרכזי במערכת אימות המשתמשים הוא רכיב המשתמשים אשר מוגדר מראש שהינו אובייקט המיישם את הממשק [IWebUser]. רכיב המשתמשים מייצג את המידע הקבוע אודות זהות המשתמש הנוכחי. אנו יכולים לגשת אליו בכל מקום בעזרת `Yii::app()-»user`. + +על ידי שימוש ברכיב המשתמשים אנו יכולים לבדוק אם משתמש מחובר או לא בעזרת [CWebUser::isGuest]; אנו יכולים [לחבר|CWebUser::login] [ולנתק|CWebUser::logout] משתמש; אנו יכולים לבדוק אם משתמש מורשה לבצע פעולות מסויימות בעזרת קריאה ל [CWebUser::checkAccess]; כמו כן אנו יכולים להשיג את [המזהה היחודי|CWebUser::name] של המשתמש הנוכחי ומידע נוסף אודות זהות המשתמש. + +הגדרת מחלקת זהות משתמש +----------------------- + +בכדי לזהות משתמש, אנו מגדירים מחלקת זהות משתמש אשר כוללת את לוגיקת האימות עצמה. מחלקת זהות המשתמש צריכה ליישם את הממשק [IUserIdentity]. מחלקות שונות ניתנות ליישום עבור גישות אימות משתמשים שונות (לדוגמא, OpenID, LDAP). התחלה טובה תיהיה על ידי הרחבה של המחלקה [CUserIdentity] שהינה המחלקה הבסיסית עבור אימות המשתמשים בהתבסס על שם משתמש וסיסמא. + +העבודה העיקרית בהגדרת מחלקת זהות חדשה היא היישום של המתודה [IUserIdentity::authenticate]. מחלקת הזהות יכולה להגדיר מידע נוסף אודות זהות המשתמש שצריך להיות קבוע ונגיש במשך כל הזמן בו המשתמש נמצא באתר. + +בדוגמא הבאה, אנו מאמתים את השם משתמש והסיסמא שסופקו אל מול טבלת המשתמשים הנמצאת במסד בעזרת [Active Record](/doc/guide/database.ar). כמו כן אנו דורסים את המתודה `getId` כדי שתחזיר את המשתנה `id_` המוגדר במהלך אימות המשתמש (כברירת מחדל היישום מחזיר את שם המשתמש כמזהה יחודי של המשתמש). במהלך האימות, אנו שומרים את הערך `title` שנשלף מהמסד בתור מידע קבוע באותו השם על ידי קריאה ל [CBaseUserIdentity::setState]. + +~~~ +[php] +class UserIdentity extends CUserIdentity +{ + private $_id; + public function authenticate() + { + $record=User::model()-»findByAttributes(array('username'=»$this-»username)); + if($record===null) + $this-»errorCode=self::ERROR_USERNAME_INVALID; + else if($record-»password!==md5($this-»password)) + $this-»errorCode=self::ERROR_PASSWORD_INVALID; + else + { + $this-»_id=$record-»id; + $this-»setState('title', $record-»title); + $this-»errorCode=self::ERROR_NONE; + } + return !$this-»errorCode; + } + + public function getId() + { + return $this-»_id; + } +} +~~~ + +הנתונים הנשמרים במידע הקבוע (על ידי קריאה ל [CBaseUserIdentity::setState]) יועברו ל [CWebUser] אשר שומר אותם באחסון קבוע, כמו לדוגמא Session. לנתונים אלו ניתן לגשת כמאפיינים של [CWebUser]. לדוגמא, אנו יכולים להשיג את הערך `title` של המשתמש הנוכחי בעזרת `Yii::app()-»user-»title` (אפשרות זו קיימת החל מגרסא 1.0.3. בגרסאות קודמות, אנו צריכים להשתמש ב `Yii::app()-»user-»getState('title')`). + +» Info|מידע: כברירת מחדל, [CWebUser] משתמש ב Session כאחסון קבוע למידע אודות זהות המשתמש. במידה והאפשרות של התחברות עם עוגיות פעילה (על ידי הגדרת המאפיין [CWebUser::allowAutoLogin] ל True ), ניתן לשמור את המידע אודות זהות המשתמש בעוגיה. וודא שאינך שומר ערכים רגישים בתוך האחסון הקבוע של זהות המשתמש (לדוגמא סיסמא). + +התחברות והתנתקות +---------------- + +שימוש במחלקת הזהות ורכיב ניהול המשתמשים, אנו יכולים ליישם פעולות התחברות וניתוק בקלות. + +~~~ +[php] +// חיבור משתמש עם שם המשתמש והסיסמא +$identity=new UserIdentity($username,$password); +if($identity-»authenticate()) + Yii::app()-»user-»login($identity); +else + echo $identity-»errorMessage; +...... +// ניתוק המשתמש הנוכחי +Yii::app()-»user-»logout(); +~~~ + +כברירת מחדל, משתמש ינותק אחרי זמן מסויים של חוסר פעילות, תלוי בהגדרות [session](http://www.php.net/manual/en/session.configuration.php). +בכדי לשנות התנהגות זו, אנו יכולים להגדיר את המאפיין [allowAutoLogin|CWebUser::allowAutoLogin] של רכיב המשתמשים לערך השווה ל true ולהעביר את משך זמן החיבור הפעיל כפרמטר למתודה [CWebUser::login]. +כתוצאה המשתמש יהיה מחובר במשך הזמן שהוגדר, גם אם הוא סוגר את הדפדפן. יש לזכור שאפשרות זו דורשת מדפדפן המשתמש לאפשר שמירת עוגיות. + +~~~ +[php] +// חיבור המשתמש למשך 7 ימים +// יש לוודא שהערך allowAutoLogin הוגדר ל true +Yii::app()-»user-»login($identity,3600*24*7); +~~~ + +פילטר ניהול גישות +--------------------- + +פילטר ניהול גישות הינה תרשים הרשאות המקדים את הפעולה שהמשתמש רוצה לבצע ובודק אם אותו משתמש יכול לבצע אותה. ההרשאות מבוססות על שם משתמש, כתובת IP וסוגי בקשות. ניהול הגישות מתבצע על ידי פילטר בשם ["accessControl"|CController::filterAccessControl]. + +» Tip|טיפ: פילטר ניהול גישות הוא מספק עבור מקרים פשוטים. עבור מקרים יותר מסובכים של ניהול גישות והרשאות, ניתן להשתמש בניהול גישות המבוסס על פי תפקידים (RBAC (או קבוצות)) אשר מפורט בהמשך. + +בכדי לשלוט בגישה לפעולות בקונטרולר, אנו מתקינים את פילטר ניהול הגישות על ידי דריסה של המתודה [CController::filters] (ראה +[Filter](/doc/guide/basics.controller#filter) למידע נוסף אודות התקנת פילטר). + +~~~ +[php] +class PostController extends CController +{ + ...... + public function filters() + { + return array( + 'accessControl', + ); + } +} +~~~ + +בקוד המוצג למעלה, אנו מגדירים שפילטר ניהול הגישות מצורף לכל פעולה של הקונטרולר `PostController`. חוקי ההרשאה המפורטים של פילטר זה מוגדרים על ידי דריסה של המתודה [CController::accessRules] במחלקה של הקונטרולר. + +~~~ +[php] +class PostController extends CController +{ + ...... + public function accessRules() + { + return array( + array('deny', + 'actions'=»array('create', 'edit'), + 'users'=»array('?'), + ), + array('allow', + 'actions'=»array('delete'), + 'roles'=»array('admin'), + ), + array('deny', + 'actions'=»array('delete'), + 'users'=»array('*'), + ), + ); + } +} +~~~ + +הקוד למעלה מגדיר 3 חוקים, כל אחד מיוצג כמערך. האלמנט הראשון במערך הוא `'allow'` או `'deny'` ושאר האלמנטים מגדירים את הפרמטרים של דפוס החוק. חוקים אלו מבצעים: הפעולות `action` ו `create` לא ניתנות לביצוע על ידי משתמשים אנונימיים; פעולת ה `delete` ניתנת לביצוע על ידי משתמשים עם תפקיד של `admin`; ופעולת ה `delete` לא ניתנת לביצוע על ידי כל אחד. + +חוקי הגישות מוערכים אחד אחד בסדר בהם הם הוגדרו. החוק הראשון שתואם לדפו הנוכחי (לדוגמא שם משתמש, תפקיד, כתובת IP, כתובת) קובעת את תוצאת ההרשאה. אם חוק זה הוא חוק `allow`, הפעולה ניתנת לביצוע; במידה וחוק זה הוא `deny`, הפעולה לא ניתנת לביצוע; אם אף אחד מהחוקים לא תואם להקשר, הפעולה עדיין ניתנת לביצוע. + +» Tip|טיפ: בכדי לוודא שהפעולה לא תתבצע תחת הקשר מסויים, רצוי להגדיר חוק התואם לכל של `deny` בסוף סט החוקים, כבדוגמא הבאה: +» ~~~ +» [php] +» return array( +» // ...חוקים נוספים.... +» // החוק הבא מונע גישה לפעולה של מחיקה `delete` בכל ההקשרים +» array('deny', +» 'actions'=»array('delete'), +» ), +» ); +» ~~~ +» הסיבה שאנו מוסיפים את החוק הזה בסוף הוא כדי למנוע הרצה של הפעולה `delete` במידה ולא נמצא שום הקשר למשתמש הנוכחי. אחרת הפעולה תרוץ אם לאף אחד מהחוקים שמעליו לא נמצאה התאמה. + +לכל הקשר בחוקים ניתן להגדיר את הפרמטרים הבאים: + +- [actions|CAccessRule::actions]: מציין אילו פעולות החוק הזה תקף לגביהם ויהיה ניסיון התאמה. הגדרה זו צריכה להיות מערך של אלמנטים שהם שמות היחודיים של הפעולות. ההשוואה בין השמות אינה רגישה לאותיות גדולות-קטנות. + +- [controllers|CAccessRule::controllers]: מציין אילו קונטרולרים החוק הזה תקף לגביהם ויהיה ניסיון התאמה. הגדרה זו צריכה להיות מערך של אלמנטים שהם שמות היחודיים של הקונטרולרים. ההשוואה בין השמות אינה רגישה לאותיות גדולות-קטנות. אפשרות זו קיימת החל מגרסא 1.0.4. + +- [users|CAccessRule::users]: מציין אילו משתמשים החוק הזה תקף לגביהם ויהיה ניסיון התאמה. הערך שעליו תתבצע ההשואה הוא שם המשתמש - [name|CWebUser::name] ברכיב המשתמשים. ההשוואה בין השמות אינה רגישה לאותיות גדולות-קטנות. ניתן להשתמש כאן בשלושה תווים מיוחדים: + + - `*`: כל משתמש, הכולל גם את המשתמשים האנונימיים והמשתמשים שפרטיהם אומתו (מחוברים). + - `?`: משתמשים אנונימים. + - `@`: משתמשים אשר פרטיהם אומתו (מחוברים). + +- [roles|CAccessRule::roles]: מציין אילו תפקידים (קבוצות) החוק הזה תקף לגביהם ויהיה ניסיון התאמה. אפשרות זו נעזרת [בניהול גישות המבוססת על תפקידים (קבוצות)](#role-based-access-control) אשר מתוארת בחלק הבא. חוק זה מצורף אם [CWebUser::checkAccess] מחזיר ערך true לאחד התפקידים. זכור, רצוי להשתמש בתפקידים בחוק שהוא מוגדר כ `allow` ולא `deny` מאחר ועל פי ההגדרה, תפקיד מייצג הרשאה לבצע פעולה מסויימת. כמו כן, רצוי לזכור שלמרות שאנו משתמשים במונח תפקידים (`roles`) במקרה הזה, הערך שלו יכול להיות כל ערך הרשאתי, הכולל תפקידים, משימות ופעולות. + +- [ips|CAccessRule::ips]: מציין אילו כתובות IP החוק הזה תקף לגביהם ויהיה ניסיון התאמה עם הכתובת של המשתמש הנוכחי. + +- [verbs|CAccessRule::verbs]: מציין אילו סוגי בקשות (`GET`, `POST`) החוק הזה תקף לגביהם ויהיה ניסיון התאמה עם הבקשה הנוכחית. השוואה זו אינה רגישה לאותיות גדולות-קטנות. + +- [expression|CAccessRule::expression]: מציין ביטוי ב PHP שהתוצאה מעידה אם החוק הזה תואם או לא. בביטוי, ניתן להשתמש במשתנה `user$` אשר מתייחס ל `Yii::app()-»user`. אפשרות זו קיימת החל מגרסא 1.0.3. + +### טיפול בתוצאת האימות (הגישה) + +ברגע שהגישה נדחית, כלומר, המשתמש אינו מורשה לבצע את הפעולה המיוחסת, אחד משני המקרים הבאים יכול לקרות: + +- במידה והמשתמש אינו מחובר והמאפיין [loginUrl|CWebUser::loginUrl] של רכיב המשתמשים מוגדר להיות עמוד ההתחברות, המשתמש יועבר לעמוד זה. יש לזכור שכברירת מחדל, [loginUrl|CWebUser::loginUrl] מכוון לעמוד `site/login`. + +- אחרת תזרק שגיאה חריגה (Exception) ותוצג שגיאת HTTP 304. + +בעת ההגדרה של המאפיין [loginUrl|CWebUser::loginUrl], ניתן לציין נתיב רלטיבי או מלא. כמו כן ניתן להגדיר את המאפיין כמערך שמציין כתובת לקונטרולר/פעולה ואז יעשה שימוש ב [CWebApplication::createUrl] בכדי להפוך את המערך לקישור נכון. האלמנט הראשון במערך מציין את [הנתיב](/doc/guide/basics.controller#route) לקונטרולר של עמוד ההתחברות והפעולה שצריך להריץ, ושאר האלמנטים בפורמט של מפתח=»ערך הינם פרמטרים (GET) אשר יתווספו לקישור בעת יצירתו. לדוגמא, + +~~~ +[php] +array( + ...... + 'components'=»array( + 'user'=»array( + // זהו בעצם ערך ברירת המחדל + 'loginUrl'=»array('site/login'), + ), + ), +) +~~~ + +במידה והמשתמש הועבר לעמוד ההתחברות והוא התחבר בהצלחה, אנו נרצה להעביר אותו לאותו עמוד שבו הוא היה קודם לכן כשהגישה שלו נדחתה. כיצד אנו יודעים מהו הקישור לעמוד זה? אנו יכולים לקבל מידע זה מהמאפיין [returnUrl|CWebUser::returnUrl] של רכיב המשתמשים. לכן אנו יכולים לכתוב את הקוד הבא כדי לבצע את ההעברה לעמוד בו הוא היה קודם: + +~~~ +[php] +Yii::app()-»request-»redirect(Yii::app()-»user-»returnUrl); +~~~ + +ניהול גישות על פי תפקידים (קבוצות משתמשים) +------------------------- + +ניהול גישות על פי תפקידים (RBAC) מספק מקום מרכזי לניהול הגישות בצורה פשוטה אך יעילה. יש לעיין במאמר [Wiki](http://en.wikipedia.org/wiki/Role-based_access_control) כדי לקבל פרטים והשוואות של RBAC מול אפשרויות ניהול גישות מסורתיות אחרות. + +Yii מיישם תכנית RBAC היררכי בעזרת הרכיב [authManager|CWebApplication::authManager]. בחלק הבא, אנו קודם מציגים את המושגים העקריים בהם משתמשים בעיקר בתכנית זו; בסוף החלק אנו מציגים כיצד להשתמש באימות נתונים כדי לבדוק אפשרויות גישה שונות. + +### סקירה + +העיקרון העומד מאחורי ה RBAC במערכת ה-Yii הוא *פריט הרשאה*. פריט הרשאה הינו סמכות לבצע פעולה מסויימת (לדוגמא יצירת הודעה חדשה, ניהול משתמשים). בהתאם לגרעיניות וקהל היעד, ניתן לסווג פריטי הרשאה כ *פעולות*, *משימות* ו *תפקידים*. תפקיד מכיל משימות, משימה מכילה פעולות, ופעולה הינה הרשאה שהיא אטומית. לדוגמא, במידה ויש לנו מערכת עם תפקיד בשם `administrator` המכיל משימות של `ניהול הודעות` ו `ניהול משתמשים`. המשימה `ניהול משתמשים` מכילה פעולות כמו `יצירת משתמש`, `עדכון משתמש` ו `מחיקת משתמש`. למקסימום גמישות, Yii מאפשרת לתפקיד להכיל תפקידים או פעולות נוספות, למשימה להכיל משימות נוספות, ופעולות להכיל פעולות נוספות (זאת אומרת לשרשר בין תפקידים משימות ופעולות). + +פריט הרשאה מזוהה בצורה יחודית על ידי שמו. + +פריט הרשאה יכול להיות מקושר *קוד עסקי*. קוד עסקי הינו חתיכת קוד PHP אשר ירוץ בעת בדיקת הגישה תוך התחשבות בפריט ההרשאתי. רק ברגע שהקוד שירוץ יחזיר True, אז המשתמש יחשב כבעל גישה המיוצג על ידי הפריט הזה. לדוגמא, בזמן שאנו מגדירים פעולה `updatePost`, אנו נרצה להוסיף קוד עסקי שבודק שמספר המשתמש הנוכחי הוא זהה למספר המשתמש של המפרסם שפרסם את ההודעהת ככה שרק המשתמש שפרסם את ההודעה יהיה ראשי לערוך אותה. + +בעזרת שימוש בפריטי הרשאה, אנו יכולים לבנות *היררכית הרשאות*. פריט `A` הוא אב לפריט אחר בשם `B` בהיררכיה אם `A` מכיל את `B` (כלומר `A` יורש את ההרשאות המיוצגות על ידי `B`). פריט יכול להכיל כמה תתים, כמו כן הוא יכול להכיל כמה פריטים מעליו (אבות). לכן, היררכית הרשאות הינו גרף ולא עץ. בהיררכיה זו, פריטי תפקידים יושבים ברמה הגבוה ביותר, פעולות יושבות ברמה הנמוכה ביותר, בזמן שמשימות יושבות באמצע. + +ברגע שיש ברשותנו היררכית הרשאות, אנו יכולים להקצות תפקידים בהירככיה זו למשתמשים באפליקציה. משתמש, ברגע שתפקיד מוקצה לו, יהיו ברשותו ההרשאות המיוצגות על ידי אותו תפקיד. לדוגמא, אם נקצה את התפקיד `administrator` למשתמש, הוא יוכל לבצע את המשימות של מנהל ראשי, משימות הכוללות לדוגמא `ניהול הודעות` ו `ניהול משתמשים` (והפעולות המקבילות למשימות אלו). + +עכשיו החלק המהנה מתחיל. בפעולה בקונטרולר, אנו רוצים לבדוק אם המשתמש הנוכחי יכול למחוק את ההודעה שהוגדרה. שימוש בהיררכית RBAC וההקצאות, ניתן לבצע זאת בצורה פשוטה בדרך הבאה: + +~~~ +[php] +if(Yii::app()-»user-»checkAccess('deletePost')) +{ + // מחיקת ההודעה +} +~~~ + +### הגדרת ניהול הרשאות + +לפני שאנו ניגשים להגדרת היררכית הרשאות ולבצע בדיקות גישה, אנו צריכים להגדיר את הרכיב [authManager|CWebApplication::authManager] באפליקציה. Yii מספקת שני סוגים של ניהול גישות: [CPhpAuthManager] ו +[CDbAuthManager]. הראשון משתמש בקובץ סקריפט PHP כדי לאחסן את נתוני ההרשאות, בזמן שהשני שומר את נתוני ההרשאות במסד הנתונים. כשאנו מגדירים את רכיב האפליקציה [authManager|CWebApplication::authManager], אנו צריכים לציין איזה מחלקה אנו נשתמש בה ומה הם הערכים ההתחלתיים לרכיב. לדוגמא, + +~~~ +[php] +return array( + 'components'=»array( + 'db'=»array( + 'class'=»'CDbConnection', + 'connectionString'=»'sqlite:path/to/file.db', + ), + 'authManager'=»array( + 'class'=»'CDbAuthManager', + 'connectionID'=»'db', + ), + ), +); +~~~ + +לאחר מכן אנו יכולים לגשת אל הרכיב [authManager|CWebApplication::authManager] בעזרת `Yii::app()-»authManager`. + +### הגדרת היררכית הרשאות + +הגדרת היררכית הרשאות כרוכה בשלושה שלבים: הגדרת פריטי הרשאה, יצירת קישורים בין פריטי הרשאה, והקצאת תפקידים למשתמשים באפליקציה. הרכיב [authManager|CWebApplication::authManager] מספק סט שלם של מתודות (API) כדי להשיג מטרות אלו. + +כדי להגדיר פריט הרשאה, יש לקרוא לאחת מהמתודות הבאות, תלוי בסוג הפריט: + + - [CAuthManager::createRole] + - [CAuthManager::createTask] + - [CAuthManager::createOperation] + +ברגע שיש לנו סט של פריטי הרשאה, אנו יכולים לקרוא למתודות הבאות בכדי ליצור קשרים בין פריטי ההרשאה השונים: + + - [CAuthManager::addItemChild] + - [CAuthManager::removeItemChild] + - [CAuthItem::addChild] + - [CAuthItem::removeChild] + +ולבסוף, אנו קוראים למתודות הבאות כדי להקצות תפקידים למשתמשים בודדים: + + - [CAuthManager::assign] + - [CAuthManager::revoke] + +למטה אנו מציגים דוגמא ליצירת היררכית הרשאות עם המתודות שהוצגו קודם לכן: + +~~~ +[php] +$auth=Yii::app()-»authManager; + +$auth-»createOperation('createPost','יצירת הודעה'); +$auth-»createOperation('readPost','קריאת הודעה'); +$auth-»createOperation('updatePost','עריכת הודעה'); +$auth-»createOperation('deletePost','מחיקת הודעה'); + +$bizRule='return Yii::app()-»user-»id==$params["post"]-»authID;'; +$task=$auth-»createTask('updateOwnPost','עריכת הודעה על ידי המפרסם בלבד',$bizRule); +$task-»addChild('updatePost'); + +$role=$auth-»createRole('reader'); +$role-»addChild('readPost'); + +$role=$auth-»createRole('author'); +$role-»addChild('reader'); +$role-»addChild('createPost'); +$role-»addChild('updateOwnPost'); + +$role=$auth-»createRole('editor'); +$role-»addChild('reader'); +$role-»addChild('updatePost'); + +$role=$auth-»createRole('admin'); +$role-»addChild('editor'); +$role-»addChild('author'); +$role-»addChild('deletePost'); + +$auth-»assign('reader','readerA'); +$auth-»assign('author','authorB'); +$auth-»assign('editor','editorC'); +$auth-»assign('admin','adminD'); +~~~ + +» Info|מידע: למרות שהקוד למעלה נראה ארוך ומשעמם, הוא נועד בעיקר לצרכי הדגמה לשימוש במתודות. מפתחים בדרך כלל צריכים לפתח ממשקים אשר מפשטים את הפעולות ההלו למשתמשי הקצה ויוצרים היררכיה הרשאות באופן אינטואיטיבי. + +### שימוש בקודים עסקיים + +כשאנו מגדירים היררכית הרשאות, אנו יכולים לקשר תפקיד, משימה או פעולה למה שנקרא *קוד עסקי*. כמו כן אנו יכולים לקשר קוד עסקי כשאנו מקצים תפקיד למשתמש. קוד עסקי הינו חתיכת קוד PHP שירוץ ברגע שאנו נבצע בדיקת גישה. הערך המוחזר מהקוד העסקי נועד כדי לקבוע אם התפקיד או המשימה חלה על המשתמש הנוכחי. בדוגמא למעלה, אנו קישרנו קוד עסקי למשימה `updateOwnPost`. בקוד העסקי אנו פשוט בודקים אם מספר המזהה היחודי של המשתמש הנוכחי הוא זהה למספר המשתמש שפרסם את ההודעה. פרטי ההודעה במערך ה `params$` מסופק על ידי המפתחים בעת ביצוע בדיקת הגישה. + +### בדיקת גישה + +בכדי לבצע בדיקת גישה, אנו קודם צריכים לדעת את שם פריט ההרשאה. לדוגמא, כדי לבדוק אם המשתמש הנוכחי יכול ליצור הודעה, אנו נבדוק אם יש לו הרשאה המיוצגת על ידי הפעולה `createPost`. אז אנו קוראים ל-[CWebUser::checkAccess] כדי לבצע את בדיקת הגישה: + +~~~ +[php] +if(Yii::app()-»user-»checkAccess('createPost')) +{ + // יצירת הודעה +} +~~~ + +אם פריט ההרשאה מקושר עם קוד עסקי שדורש פרמטרים נוספים, אנו יכולים להעביר אותם גם כן. לדוגמא, בכדי לבדוק אם משתמש יכול לערוך הודעה, אנו נכתוב, + +~~~ +[php] +$params=array('post'=»$post); +if(Yii::app()-»user-»checkAccess('updateOwnPost',$params)) +{ + // עדכון הודעה +} +~~~ + + +### שימוש בתפקידים מוגדרים מראש + +» Note|הערה: אפשרות תפקידי ברירת המחדל קיימת החל מגרסא 1.0.3. + +אפליקציות ווב רבות צריכות כמה תפקידים מאוד מיוחדים שיוקצו לכל המשתמשים או לרובם. לדוגמא, אנו נרצה להקצות הרשאות לכל המשתמשים המחוברים (שאומתו). זה דורש הרבה עבודת תחזוקה אם אנו נגדיר ונשמור את כל ההקצאות הללו במפורש. אנו יכולים לנצל את האפשרות של *תפקידי ברירת מחדל* (*default roles*) כדי לפתור בעיה זו. + +תפקיד ברירת מחדל הינו תפקיד אשר הוקצה לכל משתמש, הכוללים את האורחים והמשתמשים כאחד. אנו לא צריכים להקצות תפקידים אלו עבור כל משתמש בנפרד. ברגע שמתבצעת קריאה של [CWebUser::checkAccess], תפקידי ברירת המחדל נבדקים ראשונים במידה והם הוקצו למשתמש. + +תפקידי ברירת מחדל צריכים להיות מוגדרים במאפיין [CAuthManager::defaultRoles]. לדוגמא, הקוד הבא מגדיר שני תפקידים כתפקידים ברירת מחדל: `authenticated` ו `guest`. + +~~~ +[php] +return array( + 'components'=»array( + 'authManager'=»array( + 'class'=»'CDbAuthManager', + 'defaultRoles'=»array('authenticated', 'guest'), + ), + ), +); +~~~ + +מאחר ותפקיד ברירת מחדל מוקצה לכל משתמש, הוא בדרך כלל צריך להיות מקושר עם קוד עסקי בכדי לקבוע אם אותו תפקיד באמת חל על המשתמש הנוכחי. לדוגמא, הקוד הבא מגדיר שני תפקידים, `authenticated` ו `guest`, אשר למעשה חל על משתמשים מחוברים ומשתמשים שהינם אורחים, בהתאמה. + +~~~ +[php] +$bizRule='return !Yii::app()-»user-»isGuest;'; +$auth-»createRole('authenticated', 'authenticated user', $bizRule); + +$bizRule='return Yii::app()-»user-»isGuest;'; +$auth-»createRole('guest', 'guest user', $bizRule); +~~~ + «div class="revision"»$Id: topics.auth.txt 1808 2010-02-17 21:49:42Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/topics.console.txt b/docs/guide/he/topics.console.txt index ed5e8d883..a49f5f1f1 100644 --- a/docs/guide/he/topics.console.txt +++ b/docs/guide/he/topics.console.txt @@ -1,58 +1,58 @@ -אפליקציות מסוף ובקרה -==================== - -אפליקציות ווב משתמשות באפליקציות מסוף בעיקר עבור עבודות כמו, יצירת קוד, קומפילציה של אינדקסים של חיפוש, שליחת אימיילים, וכדומה. Yii מספקת פריימוורק לכתיבת אפליקציות מסוף בשיטה של תכנות מונחה עצמים ושיטתי. - -Yii מייצגת כל משימה במסוף במונחים של פקודה -[command|CConsoleCommand], והאובייקט של [console application|CConsoleApplication] נועד לשדר בקשת שורת פקודות לפקודה המתאימה. האובייקט של האפליקציה נוצר בסקריפט הכניסה הראשי. בכדי להריץ משימת מסוף, אנו מריצים את הפקודה הבאה בשורת הפקודות בצורה הבאה, - -~~~ -php entryScript.php CommandName Param0 Param1 ... -~~~ - -`CommandName` מתייחס לשם הפקודה (שאינו רגיש לאותיות גדולות-קטנות), ו `Param0` , `Param1` וכן הלאה הם פרמטרים המועברים לאובייקט של הפקודה. - -סקריפט הכניסה הראשי לאפליקצית מסוף בדרך כלל נכתב בצורה הבאה, בדומה לסקריפט של אפליקצית ווב, - -~~~ -[php] -defined('YII_DEBUG') or define('YII_DEBUG',true); -//צירוף הקובץ הראשי של Yii -require_once('path/to/yii/framework/yii.php'); -// יצירת אובייקט אפליקציה והרצתו -$configFile='path/to/config/file.php'; -Yii::createConsoleApplication($configFile)-»run(); -~~~ - -לאחר מכן אנו יוצרים מחלקות שצריכות להיות תחת המחלקה הבסיסית [CConsoleCommand] שמהוות בעצם כפקודות. -כל מחלקה שמהווה פקודה צריכה להקרא בשם הפקודה שלה והוספת `Command` לסוף השם. לדוגמא, בכדי להגדיר פקודת `email` אנו צריכים ליצור מחלקה בשם `EmailCommand`. כל המחלקות של הפקודות צריכות להיות ממוקמות תחת התיקיה `commands` הנמצאת בתוך התיקיה [הראשית](/doc/guide/basics.application#application-base-directory) של האפליקציה. - -» Tip|טיפ: על ידי הגדרת המאפיין [CConsoleApplication::commandMap], ניתן להגדיר פקודות בשמות שונים אשר לאו דווקא צריכים לעקוב אחרי מוסכמות נתינת השמות שתוארו למעלה והנמצאים בתיקיות שונות. - -יצירת מחלקת פקודות בעיקר כוללת יישום של המתודה [CConsoleCommand::run]. פרמטרים של הפקודה מועברים אל מתודה זו כמערך. למטה מוצגת דוגמא: - -~~~ -[php] -class EmailCommand extends CConsoleCommand -{ - public function run($args) - { - $receiver=$args[0]; - // send email to $receiver - } -} -~~~ - -בכל זמן בפקודה, אנו יכולים לגשת לאובייקט של אפליקצית המסוף בעזרת `Yii::app()`. בדומה לאובייקט אפליקצית ווב, ניתן להגדיר אפליקציות מסוף בעזרת קובץ הגדרות אפליקציה. לדוגמא, אנו יכולים להגדיר רכיב אפליקציה בשם `db` בכדי לגשת למסד נתונים. ההגדרות בדרך כלל מאופיינות כקובץ PHP המחזיר מערך של הגדרות המועברות למתודה ההתחלתית של מחלקת אפליקצית הווב (או [createConsoleApplication|YiiBase::createConsoleApplication] בסקריפט הכניסה הראשי). - -שימוש בכלי `yiic` ---------------------- - -אנו השתמשנו בכלי `yiic` בכדי ליצור את [האפליקציה הראשונה](/doc/guide/quickstart.first-app) שלנו. כלי ה `yiic` למעשה מיושם כאפליקצית מסוף שקוב הכניסה הראשי שלה הוא `framework/yiic.php`. בעזרת השימוש ב `yiic`, אנו יכולים לבצע משימות כמו יצירת שלד לאפליקציה חדשה, יצירת מחלקה עבור קונטרולר או מודל, יצירת קוד הדרוש עבור פעולות CRUD, קבלת הודעות שצריכות להיות מתורגמות, וכדומה. - -אנו יכולים לשפר את `yiic` על ידי הוספת פקודות מותאמות אישית. בכדי לעשות זאת, אנו צריכים להתחיל עם שלד של אפליקציה חדשה הנוצרה בעזרת הפקודה `yiic webapp`, כפי שהוסבר [יצירת אפליקציה ראשונה בעזרת Yii](/doc/guide/quickstart.first-app). הפקודה `yiic webapp` תיצור שני קבצים תחת התיקיה `protected`: הראשון `yiic` ו `yiic.bat` הם הגרסאות *המקומיות* של הכלי `yiic` שנוצרו במיוחד עבור אפליקצית הווב. - -לאחר מכן אנו יכולים ליצור פקודות בעצמנו תחת התיקיה `protected/commands`. הרצת הכלי המקומי `yiic` אנו נראה את הפקודות שלנו רשומות ביחד עם הפקודות המובנות המגיעות עם המערכת. כמו כן אנו יכולים ליצור פקודות בעצמנו כדי שנוכל להשתמש בהם בעזרת כלי ה `yiic shell`. בכדי לבצע זאת, פשוט יש לשמור את מחלקת הפקודה תחת התיקיה `protected/commands/shell`. - -החל מגרסא 1.1.1, אנו יכולים ליצור פקודות גלובאליות אשר ניתנות לשיתוף על גבי כל אפליקציות ה Yii הרצות על אותו שרת. כדי ליצור פקודות אלו, יש להגדיר משתנה שרתי ( "סביבתי" משתנה אשר נמצא תחת המשתנה הגלובאלי SERVER_$ בשרת ) בשם `YII_CONSOLE_COMMANDS` אשר צריך להיות מכוון לתיקיה קיימת בשרת. לאחר הגדרת המשתנה אנו מניחים את כל המחלקות של הפקודות שיצרנו שאנו רוצים שהם יהיו גלובאלים תחת התיקיה הזו, ואנו נראה את הפקודות הללו בכל מקום בו נשתמש בכלי `yiic`. - +אפליקציות מסוף ובקרה +==================== + +אפליקציות ווב משתמשות באפליקציות מסוף בעיקר עבור עבודות כמו, יצירת קוד, קומפילציה של אינדקסים של חיפוש, שליחת אימיילים, וכדומה. Yii מספקת פריימוורק לכתיבת אפליקציות מסוף בשיטה של תכנות מונחה עצמים ושיטתי. + +Yii מייצגת כל משימה במסוף במונחים של פקודה -[command|CConsoleCommand], והאובייקט של [console application|CConsoleApplication] נועד לשדר בקשת שורת פקודות לפקודה המתאימה. האובייקט של האפליקציה נוצר בסקריפט הכניסה הראשי. בכדי להריץ משימת מסוף, אנו מריצים את הפקודה הבאה בשורת הפקודות בצורה הבאה, + +~~~ +php entryScript.php CommandName Param0 Param1 ... +~~~ + +`CommandName` מתייחס לשם הפקודה (שאינו רגיש לאותיות גדולות-קטנות), ו `Param0` , `Param1` וכן הלאה הם פרמטרים המועברים לאובייקט של הפקודה. + +סקריפט הכניסה הראשי לאפליקצית מסוף בדרך כלל נכתב בצורה הבאה, בדומה לסקריפט של אפליקצית ווב, + +~~~ +[php] +defined('YII_DEBUG') or define('YII_DEBUG',true); +//צירוף הקובץ הראשי של Yii +require_once('path/to/yii/framework/yii.php'); +// יצירת אובייקט אפליקציה והרצתו +$configFile='path/to/config/file.php'; +Yii::createConsoleApplication($configFile)-»run(); +~~~ + +לאחר מכן אנו יוצרים מחלקות שצריכות להיות תחת המחלקה הבסיסית [CConsoleCommand] שמהוות בעצם כפקודות. +כל מחלקה שמהווה פקודה צריכה להקרא בשם הפקודה שלה והוספת `Command` לסוף השם. לדוגמא, בכדי להגדיר פקודת `email` אנו צריכים ליצור מחלקה בשם `EmailCommand`. כל המחלקות של הפקודות צריכות להיות ממוקמות תחת התיקיה `commands` הנמצאת בתוך התיקיה [הראשית](/doc/guide/basics.application#application-base-directory) של האפליקציה. + +» Tip|טיפ: על ידי הגדרת המאפיין [CConsoleApplication::commandMap], ניתן להגדיר פקודות בשמות שונים אשר לאו דווקא צריכים לעקוב אחרי מוסכמות נתינת השמות שתוארו למעלה והנמצאים בתיקיות שונות. + +יצירת מחלקת פקודות בעיקר כוללת יישום של המתודה [CConsoleCommand::run]. פרמטרים של הפקודה מועברים אל מתודה זו כמערך. למטה מוצגת דוגמא: + +~~~ +[php] +class EmailCommand extends CConsoleCommand +{ + public function run($args) + { + $receiver=$args[0]; + // send email to $receiver + } +} +~~~ + +בכל זמן בפקודה, אנו יכולים לגשת לאובייקט של אפליקצית המסוף בעזרת `Yii::app()`. בדומה לאובייקט אפליקצית ווב, ניתן להגדיר אפליקציות מסוף בעזרת קובץ הגדרות אפליקציה. לדוגמא, אנו יכולים להגדיר רכיב אפליקציה בשם `db` בכדי לגשת למסד נתונים. ההגדרות בדרך כלל מאופיינות כקובץ PHP המחזיר מערך של הגדרות המועברות למתודה ההתחלתית של מחלקת אפליקצית הווב (או [createConsoleApplication|YiiBase::createConsoleApplication] בסקריפט הכניסה הראשי). + +שימוש בכלי `yiic` +--------------------- + +אנו השתמשנו בכלי `yiic` בכדי ליצור את [האפליקציה הראשונה](/doc/guide/quickstart.first-app) שלנו. כלי ה `yiic` למעשה מיושם כאפליקצית מסוף שקוב הכניסה הראשי שלה הוא `framework/yiic.php`. בעזרת השימוש ב `yiic`, אנו יכולים לבצע משימות כמו יצירת שלד לאפליקציה חדשה, יצירת מחלקה עבור קונטרולר או מודל, יצירת קוד הדרוש עבור פעולות CRUD, קבלת הודעות שצריכות להיות מתורגמות, וכדומה. + +אנו יכולים לשפר את `yiic` על ידי הוספת פקודות מותאמות אישית. בכדי לעשות זאת, אנו צריכים להתחיל עם שלד של אפליקציה חדשה הנוצרה בעזרת הפקודה `yiic webapp`, כפי שהוסבר [יצירת אפליקציה ראשונה בעזרת Yii](/doc/guide/quickstart.first-app). הפקודה `yiic webapp` תיצור שני קבצים תחת התיקיה `protected`: הראשון `yiic` ו `yiic.bat` הם הגרסאות *המקומיות* של הכלי `yiic` שנוצרו במיוחד עבור אפליקצית הווב. + +לאחר מכן אנו יכולים ליצור פקודות בעצמנו תחת התיקיה `protected/commands`. הרצת הכלי המקומי `yiic` אנו נראה את הפקודות שלנו רשומות ביחד עם הפקודות המובנות המגיעות עם המערכת. כמו כן אנו יכולים ליצור פקודות בעצמנו כדי שנוכל להשתמש בהם בעזרת כלי ה `yiic shell`. בכדי לבצע זאת, פשוט יש לשמור את מחלקת הפקודה תחת התיקיה `protected/commands/shell`. + +החל מגרסא 1.1.1, אנו יכולים ליצור פקודות גלובאליות אשר ניתנות לשיתוף על גבי כל אפליקציות ה Yii הרצות על אותו שרת. כדי ליצור פקודות אלו, יש להגדיר משתנה שרתי ( "סביבתי" משתנה אשר נמצא תחת המשתנה הגלובאלי SERVER_$ בשרת ) בשם `YII_CONSOLE_COMMANDS` אשר צריך להיות מכוון לתיקיה קיימת בשרת. לאחר הגדרת המשתנה אנו מניחים את כל המחלקות של הפקודות שיצרנו שאנו רוצים שהם יהיו גלובאלים תחת התיקיה הזו, ואנו נראה את הפקודות הללו בכל מקום בו נשתמש בכלי `yiic`. + «div class="revision"»$Id: topics.console.txt 1870 2010-03-09 22:23:19Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/topics.error.txt b/docs/guide/he/topics.error.txt index 54fe0488c..a2f83e273 100644 --- a/docs/guide/he/topics.error.txt +++ b/docs/guide/he/topics.error.txt @@ -1,104 +1,104 @@ -ניהול וטיפול בשגיאות -============== - -Yii מספקת פריימוורק מלא עבור טיפול בשגיאות המבוססת על מנגנון החריגות (exceptions) ב PHP 5. ברגע שהאפליקציה נוצרת, בכדי לטפל בבקשות משתמשים נכנסות, היא רושמת את המתודה [handleError|CApplication::handleError] בכדי לטפל באזהרות והתראות של PHP; והיא רושמת את המתודה [handleException|CApplication::handleException] בכדי לטפל בחריגים (exceptions) שלא נתפסו. -כתוצאה מכך, במידה ותזרק שגיאה/אזהרה/התראה של PHP או שיזרק חריג (exception) שלא נתפס במהלך הרצת האפליקציה, אחד ממתודות ניהול השגיאות יכנסו לפעולה ויתחילו בתהליך טיפול בשגיאות. - - -» Tip|טיפ: הרישום של המתודות המטפלות בשגיאות נעשה בתהליך היצירה של האפליקציה על ידי קריאה לפונקציות PHP -[set_exception_handler](http://www.php.net/manual/en/function.set-exception-handler.php) -ו [set_error_handler](http://www.php.net/manual/en/function.set-error-handler.php). - -אם אינך רוצה ש Yii יטפל בשגיאות, תוכל להגדיר את המשתנים הקבועים `YII_ENABLE_ERROR_HANDLER` ו `YII_ENABLE_EXCEPTION_HANDLER` לערך -false [בסקריפט הכניסה הראשי](/doc/guide/basics.entry). - -כברירת מחדל, [errorHandler|CApplication::errorHandler] (או [exceptionHandler|CApplication::exceptionHandler]) מעלים את האירוע [onError|CApplication::onError] או [onException|CApplication::onException]. במידה והשגיאה לא מטופלת על ידי מנהל אירוע כלשהו שהוגדר לאירוע מסוג זה, הוא יקרא לעזרה מרכיב האפליקציה [errorHandler|CErrorHandler] . - -זריקת שגיאות חריגות (Exceptions) ------------------- - -זריקת שגיאות חריגות ב Yii אינה שונה מזריקת שגיאות חריגות ב PHP. ניתן להשתמש בתחביר הבא כדי לזרוק שגיאה חריגה בעת הצורך: - -~~~ -[php] -throw new ExceptionClass('ExceptionMessage'); -~~~ - -Yii מגדירה שני מחלקות עבור שגיאות חריגות: [CException] ו [CHttpException]. הקודמת היא מחלקה כללית לשגיאות חריגות, בזמן שהשנייה מייצגת שגיאה חריגה שצריכה להיות מוצגת למשתמשי הקצה. כמו כן השנייה מכילה מאפיין [statusCode|CHttpException::statusCode] המייצג את מספר הסטטוס של בקשת ה HTTP. המחלקה של השגיאה החריגה מחליטה כיצד להציגה, כפי שנסביר בהמשך. - -» Tip|טיפ: זריקת שגיאה חריגה מסוג [CHttpException] הינה דרך פשוטה לדיוון על שגיאות שבוצעו על ידי פעולות לקויות של המשתמשים. לדוגמא, במידה והמשתמש הזין מספר הודעה לא תקני בקישור, אנו יכולים לבצע את הפעולה הבאה בכדי להציג שגיאה 404 (העמוד לא נמצא): - -~~~ -[php] -// אם מספר ההודעה לא תקני -throw new CHttpException(404,'העמוד שחפשת לא נמצא או לא קיים.'); -~~~ - -הצגת שגיאות ------------------ - -ברגע שהשגיאה מכוונת אל רכיב האפליקציה [CErrorHandler], הרכיב בוחר קובץ תצוגה מתאים להצגת השגיאה. במידה והשגיאה נועדה לתצוגה של משתמשים, כמו למשל [CHttpException], הרכיב משתמש בקובץ תצוגה בשם `errorXXX`, כש `XXX` מייצג את סטטוס השגיאה של HTTP (למשל 400, 404, 500). במידה והשגיאה הינה פנימית וצריך להציגה רק למפתחים, קובץ תצוגה בשם `exception` יכנס לשימוש במקום. במקרה השני, תוצג השגיאה המדוייקת שהתרחשה ואיפה זה קרה בידיוק בקבצים. - -» Info|מידע: בזמן שהאפליקציה רצה במצב [תפוקתי (חי)](/doc/guide/basics.entry#debug-mode), כל השגיאות כולל את השגיאות הפנימיות יוצגו בעזרת קובץ תצוגה `errorXXX`. וזה מכיוון וקבצי התצוגה האחרים מכילים מידע רגיש שלא אמור להיות מוצג למשתמשי הקצה ולכן מופעל רק במצב פיתוח. במקרה זה, מפתחים צריכים להסתמך על תיעוד השגיאות בכדי לדעת מהו הגורם המדוייק לשגיאה. - -[CErrorHandler] מאתר את קובץ התצוגה המתאים בסדר הבא: - - 1. `WebRoot/themes/ThemeName/views/system`: זוהי תיקית המערכת - `system` תחת התבנית הנמצאת בשימוש כרגע. - - 2. `WebRoot/protected/views/system`: זוהי תיקית המערכת - `system` ברירת המחדל של האפליקציה. - - 3. `yii/framework/views`: זוהי תיקית המקור המגיע ביחד עם הפריימוורק של Yii. - -לכן, במידה ואנו רוצים להתאים אישית את תצוגת השגיאות, אנו צריכים פשוט ליצור קבצי תצוגה עבור השגיאות תחת התיקיה `system` באפליקציה או תחת כל תבנית בה אנו משתמשים. -כל קובץ הינו קובץ PHP רגיל המכיל בעיקר קוד HTML. למידע נוסף, יש לעיין קבצי תצוגת השגיאות תחת התיקיה `view` בתיקיה בה נמצאים הקבצים של הפריימוורק. - -### טיפול בשגיאות בעזרת פעולה - -החל מגרסא 1.0.6, Yii מאפשר שימוש [בפעולה של קונטרולר](/doc/guide/basics.controller#action) בכדי לטפל בתצוגת השגיאות. בכדי לעשות זאת, אנו צריכים להגדיר את מנהל השגיאה בהגדרות האפליקציה בצורה הבאה: - -~~~ -[php] -return array( - ...... - 'components'=»array( - 'errorHandler'=»array( - 'errorAction'=»'site/error', - ), - ), -); -~~~ - -בקוד למעלה, אנו מגדירים את המאפיין [CErrorHandler::errorAction] לנתיב `site/error` אשר מכוון לפעולה `error` בקונטרולר `SiteController`. אנו יכולים להשתמש בניתוב שונה במידה וצריך. - -אנו יכולים לכתוב את פעולת `error` בצורה הבאה: - -~~~ -[php] -public function actionError() -{ - if($error=Yii::app()-»errorHandler-»error) - $this-»render('error', $error); -} -~~~ - -בפעולה, אנו קודם מקבלים את המידע המדוייק אודות השגיאה מהמאפיין [CErrorHandler::error]. במידה והוא לא ריק, אנו מציגים את קובץ התצוגה `error` ביחד עם המידע אודות השגיאה. -המידע אודות השגיאה החוזר מהמאפיין [CErrorHandler::error] הוא מערך המכיל את האלמנטים הבאים: - - * `code`: קוד הסטטוס של HTTP (לדוגמא 400, 404, 500) - * `type`: סוג השגיאה (לדוגמא [CHttpException], `PHP Error`); - * `message`: הודעת השגיאה; - * `file`: שם קובץ ה PHP איפה שהתרחשה השגיאה; - * `line`: מספר השורה בקובץ ה PHP היכן שהתרחשה השגיאה; - * `trace`: רשימת הקבצים שהמערכת הריצה עד הגעתה לקובץ המכיל את השגיאה; - * `source`: קוד המקור המכיל את השגיאה. - -» Tip|טיפ: הסיבה שאנו בודקים אם המאפיין [CErrorHandler::error] הוא ריק היא מכיוון שהמשתמש יכול לבקש את הפעולה `error` ישירות ולהריץ אותה, ובמקרה זה לא תיהיה שגיאה. -מאחר ואנו מעבירים את המערך של `error$` ישירות לקובץ התצוגה, הוא אוטומטית מורחב למשתנים בודדים. כתוצאה מכך, בקובץ התצוגה אנו יכולים לגשת ישירות למשתנים כמו `code$` ו `type$`. - -תיעוד הודעות ---------------- - -הודעה ברמה של `error` תמיד תתעוד ברגע שתתבצע שגיאה. במידה והשגיאה בוצעה על ידי אזהרה או התראה של PHP, ההודעה תתעוד תחת קטגוריה `php`, במידה והשגיאה בוצעה על ידי שגיאה חריגה שלא נתפסה, הקטגוריה תיהיה `exception.ExceptionClassName` (עבור שגיאות חריגות של המחלקה [CHttpException] קוד הסטטוס של השגיאה תחת המאפיין [statusCode|CHttpException::statusCode] יצורף גם הוא לשם הקטגוריה). -לכן ניתן להשתמש באפשרות [התיעוד](/doc/guide/topics.logging) בכדי לנטר שגיאות הקוראות במהלך הרצת האפליקציה. - +ניהול וטיפול בשגיאות +============== + +Yii מספקת פריימוורק מלא עבור טיפול בשגיאות המבוססת על מנגנון החריגות (exceptions) ב PHP 5. ברגע שהאפליקציה נוצרת, בכדי לטפל בבקשות משתמשים נכנסות, היא רושמת את המתודה [handleError|CApplication::handleError] בכדי לטפל באזהרות והתראות של PHP; והיא רושמת את המתודה [handleException|CApplication::handleException] בכדי לטפל בחריגים (exceptions) שלא נתפסו. +כתוצאה מכך, במידה ותזרק שגיאה/אזהרה/התראה של PHP או שיזרק חריג (exception) שלא נתפס במהלך הרצת האפליקציה, אחד ממתודות ניהול השגיאות יכנסו לפעולה ויתחילו בתהליך טיפול בשגיאות. + + +» Tip|טיפ: הרישום של המתודות המטפלות בשגיאות נעשה בתהליך היצירה של האפליקציה על ידי קריאה לפונקציות PHP +[set_exception_handler](http://www.php.net/manual/en/function.set-exception-handler.php) +ו [set_error_handler](http://www.php.net/manual/en/function.set-error-handler.php). + +אם אינך רוצה ש Yii יטפל בשגיאות, תוכל להגדיר את המשתנים הקבועים `YII_ENABLE_ERROR_HANDLER` ו `YII_ENABLE_EXCEPTION_HANDLER` לערך +false [בסקריפט הכניסה הראשי](/doc/guide/basics.entry). + +כברירת מחדל, [errorHandler|CApplication::errorHandler] (או [exceptionHandler|CApplication::exceptionHandler]) מעלים את האירוע [onError|CApplication::onError] או [onException|CApplication::onException]. במידה והשגיאה לא מטופלת על ידי מנהל אירוע כלשהו שהוגדר לאירוע מסוג זה, הוא יקרא לעזרה מרכיב האפליקציה [errorHandler|CErrorHandler] . + +זריקת שגיאות חריגות (Exceptions) +------------------ + +זריקת שגיאות חריגות ב Yii אינה שונה מזריקת שגיאות חריגות ב PHP. ניתן להשתמש בתחביר הבא כדי לזרוק שגיאה חריגה בעת הצורך: + +~~~ +[php] +throw new ExceptionClass('ExceptionMessage'); +~~~ + +Yii מגדירה שני מחלקות עבור שגיאות חריגות: [CException] ו [CHttpException]. הקודמת היא מחלקה כללית לשגיאות חריגות, בזמן שהשנייה מייצגת שגיאה חריגה שצריכה להיות מוצגת למשתמשי הקצה. כמו כן השנייה מכילה מאפיין [statusCode|CHttpException::statusCode] המייצג את מספר הסטטוס של בקשת ה HTTP. המחלקה של השגיאה החריגה מחליטה כיצד להציגה, כפי שנסביר בהמשך. + +» Tip|טיפ: זריקת שגיאה חריגה מסוג [CHttpException] הינה דרך פשוטה לדיוון על שגיאות שבוצעו על ידי פעולות לקויות של המשתמשים. לדוגמא, במידה והמשתמש הזין מספר הודעה לא תקני בקישור, אנו יכולים לבצע את הפעולה הבאה בכדי להציג שגיאה 404 (העמוד לא נמצא): + +~~~ +[php] +// אם מספר ההודעה לא תקני +throw new CHttpException(404,'העמוד שחפשת לא נמצא או לא קיים.'); +~~~ + +הצגת שגיאות +----------------- + +ברגע שהשגיאה מכוונת אל רכיב האפליקציה [CErrorHandler], הרכיב בוחר קובץ תצוגה מתאים להצגת השגיאה. במידה והשגיאה נועדה לתצוגה של משתמשים, כמו למשל [CHttpException], הרכיב משתמש בקובץ תצוגה בשם `errorXXX`, כש `XXX` מייצג את סטטוס השגיאה של HTTP (למשל 400, 404, 500). במידה והשגיאה הינה פנימית וצריך להציגה רק למפתחים, קובץ תצוגה בשם `exception` יכנס לשימוש במקום. במקרה השני, תוצג השגיאה המדוייקת שהתרחשה ואיפה זה קרה בידיוק בקבצים. + +» Info|מידע: בזמן שהאפליקציה רצה במצב [תפוקתי (חי)](/doc/guide/basics.entry#debug-mode), כל השגיאות כולל את השגיאות הפנימיות יוצגו בעזרת קובץ תצוגה `errorXXX`. וזה מכיוון וקבצי התצוגה האחרים מכילים מידע רגיש שלא אמור להיות מוצג למשתמשי הקצה ולכן מופעל רק במצב פיתוח. במקרה זה, מפתחים צריכים להסתמך על תיעוד השגיאות בכדי לדעת מהו הגורם המדוייק לשגיאה. + +[CErrorHandler] מאתר את קובץ התצוגה המתאים בסדר הבא: + + 1. `WebRoot/themes/ThemeName/views/system`: זוהי תיקית המערכת - `system` תחת התבנית הנמצאת בשימוש כרגע. + + 2. `WebRoot/protected/views/system`: זוהי תיקית המערכת - `system` ברירת המחדל של האפליקציה. + + 3. `yii/framework/views`: זוהי תיקית המקור המגיע ביחד עם הפריימוורק של Yii. + +לכן, במידה ואנו רוצים להתאים אישית את תצוגת השגיאות, אנו צריכים פשוט ליצור קבצי תצוגה עבור השגיאות תחת התיקיה `system` באפליקציה או תחת כל תבנית בה אנו משתמשים. +כל קובץ הינו קובץ PHP רגיל המכיל בעיקר קוד HTML. למידע נוסף, יש לעיין קבצי תצוגת השגיאות תחת התיקיה `view` בתיקיה בה נמצאים הקבצים של הפריימוורק. + +### טיפול בשגיאות בעזרת פעולה + +החל מגרסא 1.0.6, Yii מאפשר שימוש [בפעולה של קונטרולר](/doc/guide/basics.controller#action) בכדי לטפל בתצוגת השגיאות. בכדי לעשות זאת, אנו צריכים להגדיר את מנהל השגיאה בהגדרות האפליקציה בצורה הבאה: + +~~~ +[php] +return array( + ...... + 'components'=»array( + 'errorHandler'=»array( + 'errorAction'=»'site/error', + ), + ), +); +~~~ + +בקוד למעלה, אנו מגדירים את המאפיין [CErrorHandler::errorAction] לנתיב `site/error` אשר מכוון לפעולה `error` בקונטרולר `SiteController`. אנו יכולים להשתמש בניתוב שונה במידה וצריך. + +אנו יכולים לכתוב את פעולת `error` בצורה הבאה: + +~~~ +[php] +public function actionError() +{ + if($error=Yii::app()-»errorHandler-»error) + $this-»render('error', $error); +} +~~~ + +בפעולה, אנו קודם מקבלים את המידע המדוייק אודות השגיאה מהמאפיין [CErrorHandler::error]. במידה והוא לא ריק, אנו מציגים את קובץ התצוגה `error` ביחד עם המידע אודות השגיאה. +המידע אודות השגיאה החוזר מהמאפיין [CErrorHandler::error] הוא מערך המכיל את האלמנטים הבאים: + + * `code`: קוד הסטטוס של HTTP (לדוגמא 400, 404, 500) + * `type`: סוג השגיאה (לדוגמא [CHttpException], `PHP Error`); + * `message`: הודעת השגיאה; + * `file`: שם קובץ ה PHP איפה שהתרחשה השגיאה; + * `line`: מספר השורה בקובץ ה PHP היכן שהתרחשה השגיאה; + * `trace`: רשימת הקבצים שהמערכת הריצה עד הגעתה לקובץ המכיל את השגיאה; + * `source`: קוד המקור המכיל את השגיאה. + +» Tip|טיפ: הסיבה שאנו בודקים אם המאפיין [CErrorHandler::error] הוא ריק היא מכיוון שהמשתמש יכול לבקש את הפעולה `error` ישירות ולהריץ אותה, ובמקרה זה לא תיהיה שגיאה. +מאחר ואנו מעבירים את המערך של `error$` ישירות לקובץ התצוגה, הוא אוטומטית מורחב למשתנים בודדים. כתוצאה מכך, בקובץ התצוגה אנו יכולים לגשת ישירות למשתנים כמו `code$` ו `type$`. + +תיעוד הודעות +--------------- + +הודעה ברמה של `error` תמיד תתעוד ברגע שתתבצע שגיאה. במידה והשגיאה בוצעה על ידי אזהרה או התראה של PHP, ההודעה תתעוד תחת קטגוריה `php`, במידה והשגיאה בוצעה על ידי שגיאה חריגה שלא נתפסה, הקטגוריה תיהיה `exception.ExceptionClassName` (עבור שגיאות חריגות של המחלקה [CHttpException] קוד הסטטוס של השגיאה תחת המאפיין [statusCode|CHttpException::statusCode] יצורף גם הוא לשם הקטגוריה). +לכן ניתן להשתמש באפשרות [התיעוד](/doc/guide/topics.logging) בכדי לנטר שגיאות הקוראות במהלך הרצת האפליקציה. + «div class="revision"»$Id: topics.error.txt 1064 2009-05-26 00:15:33Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/topics.gii.txt b/docs/guide/he/topics.gii.txt index cfc405fb3..d454760e6 100644 --- a/docs/guide/he/topics.gii.txt +++ b/docs/guide/he/topics.gii.txt @@ -1,280 +1,280 @@ -יצירת קוד אוטומטית -========================= - -החל מגרסא 1.1.2, Yii מצויידת עם מערכת מבוססת ווב ליצירת קוד בשם *Gii*. מערכת זו הינה תחליף עבור הכלי הקודם בשם `yiic shell` שרץ על גבי שורת הפקודות. בחלק זה, אנו נדגים כיצד להשתמש ב-Gii וכיצד להרחיב את Gii כדי לזרז את תהליך הפיתוח. - -שימוש ב-Gii ---------- - -Gii מיושמת במונחים של מודול וחייבת להיות בשימוש תחת אפליקצית Yii קיימת. כדי להשתמש ב-Gii, אנו קודם עורכים את הגדרות האפליקציה בצורה הבאה: - -~~~ -[php] -return array( - ...... - 'modules'=»array( - 'gii'=»array( - 'class'=»'system.gii.GiiModule', - 'password'=»'סיסמא כלשהי', - // 'ipFilters'=»array(...רשימה של כתובות אייפי...), - // 'newFileMode'=»0666, - // 'newDirMode'=»0777, - ), - ), -); -~~~ - -בקוד המוצג למעלה, אנו מגדירים מודול בשם `gii` שהמחלקה שלו הינה [GiiModule]. כמו כן אנו מגדירים סיסמא עבור המודול שאנו נצטרך להזין בעת הכניסה ל-Gii. - -כברירת מחדל ומטעמי בטחון, Gii מוגדרת ככה שיהיה ניתן לגשת אל המודול רק משרת `localhost`. אם אנו רוצים לאפשר גישה למחשבים בטוחים נוספים, אנו יכולים להגדיר את המאפיין [GiiModule::ipFilters] כפי שמוצג בקוד למעלה. - -בגלל ש-Gii יוצר קוד ושומר אותו בקבצים חדשים באפליקציה הנוכחית, אנו צריכים לוודא ששרת הווב מכיל את ההרשאות המתאימות לבצע פעולות אלו. בקוד המוצג למעלה המאפיינים [GiiModule::newFileMode] ו [GiiModule::newDirMode] שולטים באופן שבו הקבצים החדשים והתיקיות נוצרים. - -» Note|הערה: Gii נועד בעיקר ככלי פיתוח. לכן, רצוי להתקינו על סביבת פיתוח. מאחר וכלי זה יכול לייצר קבצי PHP חדשים באפליקציה, אנו צריכים לשים לב לאמצעי האבטחה שבו (לדוגמא סיסמא, כתובות IP). - -כעת אנו יכולים לגשת למערכת Gii דרך הקישור `http://hostname/path/to/index.php?r=gii`. כאן אנו מניחים שהקישור `http://hostname/path/to/index.php` הוא הקישור לכניסה לאפליקצית ה-Yii הנוכחית. - -במידה והאפליקציה הנוכחית משתמשת בפורמט `path` עבור הקישורים (ראה [ניהול קישורים](/doc/guide/topics.url)), אנו יכולים לגשת למודול Gii דרך הקישור `http://hostnamepath/to/index.php/gii`. אנו נצטרך להוסיף את הכללים הבאים לראש הכללים הקיימים באפליקציה: - -~~~ -[php] -'components'=»array( - ...... - 'urlManager'=»array( - 'urlFormat'=»'path', - 'rules'=»array( - 'gii'=»'gii', - 'gii/«controller:\w+»'=»'gii/«controller»', - 'gii/«controller:\w+»/«action:\w+»'=»'gii/«controller»/«action»', - ...כללים נוכחים... - ), - ), -) -~~~ - -Gii מגיעה עם כמה אפשרויות ליצירת קוד כברירת מחדל. כל אפשרות יצירת קוד אחראית ליצירת קוד ספציפי. לדוגמא, אפשרות יצירת הקונטרולר יוצרת מחלקת קונטרולר ביחד עם כמה קבצי תצוגה עבור פעולות; אפשרות יצירת המודל יוצרת מחלקת ActiveRecord עבור טבלה במסד הנתונים. - -רצף העבודה הבסיסי לשימוש באפשרות יצירת קוד הינה: - -1. כניסה לעמוד יצירת הקוד; -2. מילוי שדות המגדירות את הפרמטרים עבור יצירת הקוד. לדוגמא, בכדי להשתמש באפשרות יצירת קוד מודול כדי ליצור מודול חדש, יש לציין את המזהה יחודי עבור המודול; -3. יש ללחוץ על כפתור `Preview` בכדי לצפות בתצוגה המקדימה עבור הקוד שיווצר. תוכל לראות טבלה המציגה רשימה של קבצים ליצירה. ניתן ללחוץ על כל אחד מהם בכדי לצפות בתצוגה המקדימה של הקוד; -4. יש ללחוץ על כפתור `Generate` כדי ליצור את הקבצים; -5. יש לעיין בהודעות התיעוד של יצירת הקוד. - -הרחבת Gii -------------- - -למרות שאפשרויות יצירת הקוד המגיעות כברירת מחדל עם Gii הינם רחבות, אנו בדרך כלל נרצה להתאים אותם אישית או ליצור אפשרויות חדשות אשר תואמות לדרישות ולטעם שלנו. לדוגמא, אנו נרצה שהקוד שנוצר יהיה בסגנון שלנו, או שאנו נרצה שהקוד שנוצר יהיה נתמך תחת כמה שפות שונות. ניתן להשיג את כל אלו בעזרת Gii. - -ניתן להרחיב את Gii בשני דרכים: התאמה אישית של התבניות הקיימות ליצירת הקוד, וכתיבת אפשרויות יצירת קוד חדשות. - -### מבנה אפשרות יצירת קוד - -אפשרות יצירת קוד נמצאת בתיקיה ששמה הוא שם אפשרות היצירה עצמה. התיקיה בדרך כלל מכילה את התוכן הבא: - -~~~ -model/ התיקיה הראשית לאפשרות יצירת המודל - ModelCode.php קוד המודל אשר יוצר את הקוד - ModelGenerator.php קונטרולר יצירת הקוד - views/ מכיל קבצי תצוגה עבור אפשרות יצירת הקוד - index.php קובץ תצוגה ברירת המחדל - templates/ מכיל תבניות קוד - default/ תבנית ברירת המחדל - model.php תבנית הקוד לאפשרות יצירת קוד עבור מחלקת מודל -~~~ - -### נתיב חיפוש של אפשרויות היצירה - -Gii מחפש אחר אפשרויות יצירה תחת תיקיות מסויימות המוגדרות בעזרת המאפיין [GiiModule::generatorPaths]. כשיש צורך בהתאמה אישית, אנו יכולים להגדיר מאפיין זה בהגדרות האפליקציה בצורה הבאה, - -~~~ -[php] -return array( - 'modules'=»array( - 'gii'=»array( - 'class'=»'system.gii.GiiModule', - 'generatorPaths'=»array( - 'application.gii', // נתיב מקוצר - ), - ), - ), -); -~~~ - -ההגדרות המוצגות למעלה מורות ל-Gii לחפש אחר אפשרויות יצירת קוד תחת התיקיה בנתיב המקוצר `application.gii`, בנוסף לתיקית ברירת המחדל `system.gii.generators`. - -ניתן לשמור אפשרויות יצירת קוד באותו השם אך בנתיבי חיפוש שונים. במקרה זה, אפשרות היצירה שצויינה קודם לכן תחת התיקיה שהוגדרה במאפיין [GiiModule::generatorPaths] תקבל עדיפות. - -### התאמת תבניות קוד - -זוהי הדרך הקלה ביותר והנפוצה ביותר להרחבת Gii. אנו משתמשים בדוגמא בכדי להסביר כיצד להתאים אישית את תבניות הקוד. נניח שאנו רוצים להתאים אישית את הקוד הנוצר על ידי אפשרות יצירת מודל. - -אנו קודם יוצרים תיקיה בשם `protected/gii/model/templates/compact`. כאן `model` אומר שאנו עומדים *לדרוס* את אפשרות יצירת המודל המגיעה כברירת מחדל עם המערכת. ו `templates/compact` אומר שאנו מוסיפים תבנית חדשה בשם `compact`. - -לאחר מכן אנו עורכים את קובץ הגדרות האפליקציה כדי להוסיף `application.gii` למאפיין [GiiModule::generatorPaths], כפי שמוצג בחלק הקודם. - -כעת יש לפתוח את עמוד יצירת קוד המודל. לחיצה על שדה `Code Template`. אנו נוכל לראות רשימה אשר מכילה את תיקית התבנית שהרגע יצרנו בשם `compact`. אך אם נבחר את התבנית הזו עבור אפשרות יצירת הקוד, אנו נראה שגיאה. זה מכיוון שעדיין לא הוספנו שום קובץ המשמש כתבנית קוד בתיקית התבניות החדשה שהוספנו בשם `compact`. - -יש להעתיק את הקובץ מהנתיב `framework/gii/generators/model/templates/default/model.php` אל `protected/gii/model/templates/compact`. כעת אם ננסה ליצור שום פעם עם התבנית `compact`, זה אמור לעבוד. למרות, שהקוד שיווצר הוא לא שונה מהקוד שנוצר על ידי תבנית ברירת המחדל בשם `default` מאחר ומשם העתקנו את הקובץ אך לא בצענו בו שינויים כלשהם. - -כעת זה הזמן לבצע את עבודת ההתאמה האמיתית. פתח את הקובץ `protected/gii/model/templates/compact/model.php` כדי לערוך אותו. זכור שקובץ זה יהיה בשימוש כקובץ תצוגה, שאומר שהוא יכול להכיל ביטויים ב-PHP. הבא ונערוך את התבנית כדי שהמתודה `()attributeLabels` בקוד שנוצר תשתמש ב-`()Yii::t` כדי לתרגם את תוויות המאפיינים: - -~~~ -[php] -public function attributeLabels() -{ - return array( -«?php foreach($labels as $name=»$label): ?» - «?php echo "'$name' =» Yii::t('application', '$label'),\n"; ?» -«?php endforeach; ?» - ); -} -~~~ - -בכל תבנית קוד, אנו יכולים לגשת לכמה משתנים המוגדרים מראש. כמו למשל `labels$` בדוגמא למעלה. משתנים אלו נוצרים על ידי אפשרות יצירת הקוד המקביל לקובץ התצוגה. אפשרויות יצירה שונות מספקות משתנים שונים בתבניות הקוד שלהן. יש לקרוא את התיאור בתבניות הקוד ברירת המחדל בהרחבה. - -### הוספת אפשרות יצירת קוד - -בחלק זה, אנו מציגים כיצד ניתן ליצור אפשרות יצירת קוד היוצרת מחלקת וידג'ט חדשה. - -אנו קודם יוצרים תיקיה בשם `protected/gii/widget`. תחת תיקיה זו, אנו ניצור את הקבצים הבאים: - -* `WidgetGenerator.php`: מכיל את מחלקת הקונטרולר `WidgetGenerator`. זהו הקובץ הראשי של אפשרות יצירת הוידג'ט. - -* `WidgetCode.php`: מכיל את מחלקת המודל `WidgetCode`. מחלקה זו מכילה את הלוגיקה העיקרית ליצירת קוד. - -* `views/index.php`: קובץ התצוגה המציג את טופס אפשרות יצירת הקוד. - -* `templates/default/widget.php`: קובץ הקוד ברירת המחדל ליצירת מחלקת הוידג'ט. - - -#### יצירת `WidgetGenerator.php` - -קובץ ה-`WidgetGenerator.php` הוא פשוט מאוד. הוא מכיל את הקוד הבא בלבד: - -~~~ -[php] -class WidgetGenerator extends CCodeGenerator -{ - public $codeModel='application.gii.widget.WidgetCode'; -} -~~~ - -בקוד המוצג למעלה, אנו מציינים שאפשרות היצירה תשתמש במחלקת מודל שהנתיב שלה הוא `application.gii.widget.WidgetCode`. המחלקה `WidgetGenerator` מרחיבה את [CCodeGenerator] אשר מיישמת הרבה אפשרויות ופונקציונליות, הכוללות את פעולות הקונטרולר הדרושות לתיאום תהליך יצירת הקוד. - -#### יצירת `WidgetCode.php` - -קובץ `WidgetCode.php` מכיל את מחלקת המודל `WidgetCode` המכילה את הלוגיקה העיקרית ליצירת מחלקת וידג'ט המבוססת על נתוני ההזנה של המשתמש. בדוגמא זו, אנו מניחים שהנתונים שאנו מצפים מהמשתמש הם רק שם המחלקה של הוידג'ט. הקוד עבור `WidgetCode` נראה כך: - -~~~ -[php] -class WidgetCode extends CCodeModel -{ - public $className; - - public function rules() - { - return array_merge(parent::rules(), array( - array('className', 'required'), - - array('className', 'match', 'pattern'=»'/^\w+$/'), - - )); - } - - public function attributeLabels() - { - return array_merge(parent::attributeLabels(), array( - 'className'=»'Widget Class Name', - )); - } - - public function prepare() - { - $path=Yii::getPathOfAlias('application.components.' . $this-»className) . '.php'; - $code=$this-»render($this-»templatepath.'/widget.php'); - - $this-»files[]=new CCodeFile($path, $code); - } -} -~~~ - -מחלקת הקוד `WidgetCode` מרחיבה את המחלקה [CCodeModel]. בדומה למחלקת מודל רגילה, במחלקה זו אנו יכולים להגדיר מתודות `()rules` ו `()attributeLabels` כדי לאמת את הנתונים שהמשתמש מזין ולהציג תוויות עבור המאפיינים במחלקה, בהתאמה. יש לזכור שמאחר והמחלקה הבסיסית [CCodeModel] כבר מגדירה כמה כללים ותוויות, אנו צריכים לאחד אותם עם הכללים והתוויות שאנו מגדירים במחלקה זו. - -המתודה `()prepare` מכינה מראש את הקוד שיווצר. המטרה העיקרית שלו היא להכין רשימת אובייקטים של [CCodeFile], כל אחד מייצג קובץ קוד שנוצר. בדוגמא שלנו, אנו צריכים רק ליצור אובייקט [CCodeFile] אחד המייצג את קובץ המחלקה של הוידג'ט הנוצר. מחלקת הוידג'ט החדשה שתווצר תמוקם תחת התיקיה `protected/components`. אנו קוראים למתודה [CCodeFile::render] כדי ליצור את הקוד עצמו. מתודה זו מכילה את תבנית הקוד כסקריפט PHP ומחזירה את תוצאת הפלט המווה את הקוד שנוצר. - -#### יצירת `views/index.php` - -כשברשותנו הקונטרולר (`WidgetGenerator`) והמודל (`WidgetCode`), הגיע הזמן ליצור את קובץ התצוגה `views/index.php`. - -~~~ -[php] -«h1»אפשרות יצירת וידג'ט«/h1» - -«?php $form=$this-»beginWidget('CCodeForm', array('model'=»$model)); ?» - - «div class="row"» - «?php echo $form-»labelEx($model,'className'); ?» - «?php echo $form-»textField($model,'className',array('size'=»65)); ?» - «div class="tooltip"» - שם מחלקת המודל חייבת להכיל אותיות בלבד - «/div» - «?php echo $form-»error($model,'className'); ?» - «/div» - -«?php $this-»endWidget(); ?» -~~~ - -בקוד המוצג למעלה, אנו בעיקר מציגים טופס בעזרת הוידג'ט [CCodeForm]. בטופס זה, אנו מציגים את שדה הטקסט המהווה את הערך עבור המאפיין `className` במחלקה `WidgetCode`. - -בעת יצירת הטופס, אנו יכולים לנצל שני אפשרויות אשר נמצאות בוידג'ט [CCodeForm]. האחד הוא טקסט עזרה קטן (tooltip). האחר הוא שדה טקסט דביק. - -אם כבר התנסת באחד מאפשרויות יצירת הקוד הנמצאות במערכת, תוכל לשים לב שבעת קביעת פוקוס בשדה טקסט כלשהו, יופיע טקסט עזרה קטן ליד השדה. ניתן לבצע זאת בצורה פשוטה על ידי הוספת DIV עם הגדרת האלמנט 'class` בשם `tooltip` ליד השדה בו רוצים להציג את ההודעה הקטנה. - -עבור שדות מסויימים, אנו נרצה לשמור את הערך התקין האחרון שהיה בהם כדי לחסוך למשתמש להזין את אותו הערך שוב ושוב בכל פעם שהם משתמשים באפשרות יצירת הקוד כדי ליצור קוד. לדוגמא השדה שבו אנו מציינים את שם המחלקה הבסיסית של הקונטרולרים באפשרות יצירת הקוד לקונטרולרים המגיע עם המערכת. שדות דביקים אלו מוצגים בהתחלה כטקסט מסומן סטטי. אם נלחץ עליהם, אם יהפכו לשדות טקסט שניתן לכתוב בהם טקסט כלשהו. - -בכדי להגדיר שדה טקסט בתור שדה דביק, אנו צריכים לבצע שני דברים. - -ראשית, אנו צריכים להגדיר כלל אימות בשם `sticky` עבור המאפיין המקביל במודל. לדוגמא, אפשרות יצירת קונטרולרים המגיע עם המערכת מכילה את הכללים הבאים המגדירים את המאפיינים `baseClass` ו `actions` כמאפיינים דביקים: - -~~~ -[php] -public function rules() -{ - return array_merge(parent::rules(), array( - ...... - array('baseClass, actions', 'sticky'), - )); -} -~~~ - -שנית, אנו צריכים להוסיף מאפיין `class` בשם `sticky` לתג `div` של שדה הטקסט בתצוגה, בצורה הבאה: - -~~~ -[php] -«div class="row sticky"» - ..שדה טקסט כאן... -«/div» -~~~ - -#### יצירת `templates/default/widget.php` - -לבסוף, אנו יוצרים את תבנית הקוד `templates/default/widget.php`. כפי שהסברנו קודם לכן, השימוש של קובץ זה הוא בדומה לקובץ תצוגה המכיל פקודות וביטויים ב PHP. בתבנית קוד, אנו תמיד יכולים לגשת למשתנה `this$` המתייחס לאובייקט של המודל. בדוגמא שלנו, `this$` מתייחס לאובייקט של `WidgetModel`. לכן אנו יכולים לשלוף את שם המחלקה שהמשתמש הזין בטופס בעזרת `this-»className$`. - -~~~ -[php] -«?php echo '«?php'; ?» - -class «?php echo $this-»className; ?» extends CWidget -{ - public function run() - { - - } -} -~~~ - -זה מסכם את החלק לגבי יצירת אפשרות יצירת קוד חדשה. כעת אנו יכולים לגשת לאפשרות יצירת הקוד שהרגע יצרנו על ידי כניסה לקישור `http://hostname/path/to/index.php?r=gii/widget`. - +יצירת קוד אוטומטית +========================= + +החל מגרסא 1.1.2, Yii מצויידת עם מערכת מבוססת ווב ליצירת קוד בשם *Gii*. מערכת זו הינה תחליף עבור הכלי הקודם בשם `yiic shell` שרץ על גבי שורת הפקודות. בחלק זה, אנו נדגים כיצד להשתמש ב-Gii וכיצד להרחיב את Gii כדי לזרז את תהליך הפיתוח. + +שימוש ב-Gii +--------- + +Gii מיושמת במונחים של מודול וחייבת להיות בשימוש תחת אפליקצית Yii קיימת. כדי להשתמש ב-Gii, אנו קודם עורכים את הגדרות האפליקציה בצורה הבאה: + +~~~ +[php] +return array( + ...... + 'modules'=»array( + 'gii'=»array( + 'class'=»'system.gii.GiiModule', + 'password'=»'סיסמא כלשהי', + // 'ipFilters'=»array(...רשימה של כתובות אייפי...), + // 'newFileMode'=»0666, + // 'newDirMode'=»0777, + ), + ), +); +~~~ + +בקוד המוצג למעלה, אנו מגדירים מודול בשם `gii` שהמחלקה שלו הינה [GiiModule]. כמו כן אנו מגדירים סיסמא עבור המודול שאנו נצטרך להזין בעת הכניסה ל-Gii. + +כברירת מחדל ומטעמי בטחון, Gii מוגדרת ככה שיהיה ניתן לגשת אל המודול רק משרת `localhost`. אם אנו רוצים לאפשר גישה למחשבים בטוחים נוספים, אנו יכולים להגדיר את המאפיין [GiiModule::ipFilters] כפי שמוצג בקוד למעלה. + +בגלל ש-Gii יוצר קוד ושומר אותו בקבצים חדשים באפליקציה הנוכחית, אנו צריכים לוודא ששרת הווב מכיל את ההרשאות המתאימות לבצע פעולות אלו. בקוד המוצג למעלה המאפיינים [GiiModule::newFileMode] ו [GiiModule::newDirMode] שולטים באופן שבו הקבצים החדשים והתיקיות נוצרים. + +» Note|הערה: Gii נועד בעיקר ככלי פיתוח. לכן, רצוי להתקינו על סביבת פיתוח. מאחר וכלי זה יכול לייצר קבצי PHP חדשים באפליקציה, אנו צריכים לשים לב לאמצעי האבטחה שבו (לדוגמא סיסמא, כתובות IP). + +כעת אנו יכולים לגשת למערכת Gii דרך הקישור `http://hostname/path/to/index.php?r=gii`. כאן אנו מניחים שהקישור `http://hostname/path/to/index.php` הוא הקישור לכניסה לאפליקצית ה-Yii הנוכחית. + +במידה והאפליקציה הנוכחית משתמשת בפורמט `path` עבור הקישורים (ראה [ניהול קישורים](/doc/guide/topics.url)), אנו יכולים לגשת למודול Gii דרך הקישור `http://hostnamepath/to/index.php/gii`. אנו נצטרך להוסיף את הכללים הבאים לראש הכללים הקיימים באפליקציה: + +~~~ +[php] +'components'=»array( + ...... + 'urlManager'=»array( + 'urlFormat'=»'path', + 'rules'=»array( + 'gii'=»'gii', + 'gii/«controller:\w+»'=»'gii/«controller»', + 'gii/«controller:\w+»/«action:\w+»'=»'gii/«controller»/«action»', + ...כללים נוכחים... + ), + ), +) +~~~ + +Gii מגיעה עם כמה אפשרויות ליצירת קוד כברירת מחדל. כל אפשרות יצירת קוד אחראית ליצירת קוד ספציפי. לדוגמא, אפשרות יצירת הקונטרולר יוצרת מחלקת קונטרולר ביחד עם כמה קבצי תצוגה עבור פעולות; אפשרות יצירת המודל יוצרת מחלקת ActiveRecord עבור טבלה במסד הנתונים. + +רצף העבודה הבסיסי לשימוש באפשרות יצירת קוד הינה: + +1. כניסה לעמוד יצירת הקוד; +2. מילוי שדות המגדירות את הפרמטרים עבור יצירת הקוד. לדוגמא, בכדי להשתמש באפשרות יצירת קוד מודול כדי ליצור מודול חדש, יש לציין את המזהה יחודי עבור המודול; +3. יש ללחוץ על כפתור `Preview` בכדי לצפות בתצוגה המקדימה עבור הקוד שיווצר. תוכל לראות טבלה המציגה רשימה של קבצים ליצירה. ניתן ללחוץ על כל אחד מהם בכדי לצפות בתצוגה המקדימה של הקוד; +4. יש ללחוץ על כפתור `Generate` כדי ליצור את הקבצים; +5. יש לעיין בהודעות התיעוד של יצירת הקוד. + +הרחבת Gii +------------- + +למרות שאפשרויות יצירת הקוד המגיעות כברירת מחדל עם Gii הינם רחבות, אנו בדרך כלל נרצה להתאים אותם אישית או ליצור אפשרויות חדשות אשר תואמות לדרישות ולטעם שלנו. לדוגמא, אנו נרצה שהקוד שנוצר יהיה בסגנון שלנו, או שאנו נרצה שהקוד שנוצר יהיה נתמך תחת כמה שפות שונות. ניתן להשיג את כל אלו בעזרת Gii. + +ניתן להרחיב את Gii בשני דרכים: התאמה אישית של התבניות הקיימות ליצירת הקוד, וכתיבת אפשרויות יצירת קוד חדשות. + +### מבנה אפשרות יצירת קוד + +אפשרות יצירת קוד נמצאת בתיקיה ששמה הוא שם אפשרות היצירה עצמה. התיקיה בדרך כלל מכילה את התוכן הבא: + +~~~ +model/ התיקיה הראשית לאפשרות יצירת המודל + ModelCode.php קוד המודל אשר יוצר את הקוד + ModelGenerator.php קונטרולר יצירת הקוד + views/ מכיל קבצי תצוגה עבור אפשרות יצירת הקוד + index.php קובץ תצוגה ברירת המחדל + templates/ מכיל תבניות קוד + default/ תבנית ברירת המחדל + model.php תבנית הקוד לאפשרות יצירת קוד עבור מחלקת מודל +~~~ + +### נתיב חיפוש של אפשרויות היצירה + +Gii מחפש אחר אפשרויות יצירה תחת תיקיות מסויימות המוגדרות בעזרת המאפיין [GiiModule::generatorPaths]. כשיש צורך בהתאמה אישית, אנו יכולים להגדיר מאפיין זה בהגדרות האפליקציה בצורה הבאה, + +~~~ +[php] +return array( + 'modules'=»array( + 'gii'=»array( + 'class'=»'system.gii.GiiModule', + 'generatorPaths'=»array( + 'application.gii', // נתיב מקוצר + ), + ), + ), +); +~~~ + +ההגדרות המוצגות למעלה מורות ל-Gii לחפש אחר אפשרויות יצירת קוד תחת התיקיה בנתיב המקוצר `application.gii`, בנוסף לתיקית ברירת המחדל `system.gii.generators`. + +ניתן לשמור אפשרויות יצירת קוד באותו השם אך בנתיבי חיפוש שונים. במקרה זה, אפשרות היצירה שצויינה קודם לכן תחת התיקיה שהוגדרה במאפיין [GiiModule::generatorPaths] תקבל עדיפות. + +### התאמת תבניות קוד + +זוהי הדרך הקלה ביותר והנפוצה ביותר להרחבת Gii. אנו משתמשים בדוגמא בכדי להסביר כיצד להתאים אישית את תבניות הקוד. נניח שאנו רוצים להתאים אישית את הקוד הנוצר על ידי אפשרות יצירת מודל. + +אנו קודם יוצרים תיקיה בשם `protected/gii/model/templates/compact`. כאן `model` אומר שאנו עומדים *לדרוס* את אפשרות יצירת המודל המגיעה כברירת מחדל עם המערכת. ו `templates/compact` אומר שאנו מוסיפים תבנית חדשה בשם `compact`. + +לאחר מכן אנו עורכים את קובץ הגדרות האפליקציה כדי להוסיף `application.gii` למאפיין [GiiModule::generatorPaths], כפי שמוצג בחלק הקודם. + +כעת יש לפתוח את עמוד יצירת קוד המודל. לחיצה על שדה `Code Template`. אנו נוכל לראות רשימה אשר מכילה את תיקית התבנית שהרגע יצרנו בשם `compact`. אך אם נבחר את התבנית הזו עבור אפשרות יצירת הקוד, אנו נראה שגיאה. זה מכיוון שעדיין לא הוספנו שום קובץ המשמש כתבנית קוד בתיקית התבניות החדשה שהוספנו בשם `compact`. + +יש להעתיק את הקובץ מהנתיב `framework/gii/generators/model/templates/default/model.php` אל `protected/gii/model/templates/compact`. כעת אם ננסה ליצור שום פעם עם התבנית `compact`, זה אמור לעבוד. למרות, שהקוד שיווצר הוא לא שונה מהקוד שנוצר על ידי תבנית ברירת המחדל בשם `default` מאחר ומשם העתקנו את הקובץ אך לא בצענו בו שינויים כלשהם. + +כעת זה הזמן לבצע את עבודת ההתאמה האמיתית. פתח את הקובץ `protected/gii/model/templates/compact/model.php` כדי לערוך אותו. זכור שקובץ זה יהיה בשימוש כקובץ תצוגה, שאומר שהוא יכול להכיל ביטויים ב-PHP. הבא ונערוך את התבנית כדי שהמתודה `()attributeLabels` בקוד שנוצר תשתמש ב-`()Yii::t` כדי לתרגם את תוויות המאפיינים: + +~~~ +[php] +public function attributeLabels() +{ + return array( +«?php foreach($labels as $name=»$label): ?» + «?php echo "'$name' =» Yii::t('application', '$label'),\n"; ?» +«?php endforeach; ?» + ); +} +~~~ + +בכל תבנית קוד, אנו יכולים לגשת לכמה משתנים המוגדרים מראש. כמו למשל `labels$` בדוגמא למעלה. משתנים אלו נוצרים על ידי אפשרות יצירת הקוד המקביל לקובץ התצוגה. אפשרויות יצירה שונות מספקות משתנים שונים בתבניות הקוד שלהן. יש לקרוא את התיאור בתבניות הקוד ברירת המחדל בהרחבה. + +### הוספת אפשרות יצירת קוד + +בחלק זה, אנו מציגים כיצד ניתן ליצור אפשרות יצירת קוד היוצרת מחלקת וידג'ט חדשה. + +אנו קודם יוצרים תיקיה בשם `protected/gii/widget`. תחת תיקיה זו, אנו ניצור את הקבצים הבאים: + +* `WidgetGenerator.php`: מכיל את מחלקת הקונטרולר `WidgetGenerator`. זהו הקובץ הראשי של אפשרות יצירת הוידג'ט. + +* `WidgetCode.php`: מכיל את מחלקת המודל `WidgetCode`. מחלקה זו מכילה את הלוגיקה העיקרית ליצירת קוד. + +* `views/index.php`: קובץ התצוגה המציג את טופס אפשרות יצירת הקוד. + +* `templates/default/widget.php`: קובץ הקוד ברירת המחדל ליצירת מחלקת הוידג'ט. + + +#### יצירת `WidgetGenerator.php` + +קובץ ה-`WidgetGenerator.php` הוא פשוט מאוד. הוא מכיל את הקוד הבא בלבד: + +~~~ +[php] +class WidgetGenerator extends CCodeGenerator +{ + public $codeModel='application.gii.widget.WidgetCode'; +} +~~~ + +בקוד המוצג למעלה, אנו מציינים שאפשרות היצירה תשתמש במחלקת מודל שהנתיב שלה הוא `application.gii.widget.WidgetCode`. המחלקה `WidgetGenerator` מרחיבה את [CCodeGenerator] אשר מיישמת הרבה אפשרויות ופונקציונליות, הכוללות את פעולות הקונטרולר הדרושות לתיאום תהליך יצירת הקוד. + +#### יצירת `WidgetCode.php` + +קובץ `WidgetCode.php` מכיל את מחלקת המודל `WidgetCode` המכילה את הלוגיקה העיקרית ליצירת מחלקת וידג'ט המבוססת על נתוני ההזנה של המשתמש. בדוגמא זו, אנו מניחים שהנתונים שאנו מצפים מהמשתמש הם רק שם המחלקה של הוידג'ט. הקוד עבור `WidgetCode` נראה כך: + +~~~ +[php] +class WidgetCode extends CCodeModel +{ + public $className; + + public function rules() + { + return array_merge(parent::rules(), array( + array('className', 'required'), + + array('className', 'match', 'pattern'=»'/^\w+$/'), + + )); + } + + public function attributeLabels() + { + return array_merge(parent::attributeLabels(), array( + 'className'=»'Widget Class Name', + )); + } + + public function prepare() + { + $path=Yii::getPathOfAlias('application.components.' . $this-»className) . '.php'; + $code=$this-»render($this-»templatepath.'/widget.php'); + + $this-»files[]=new CCodeFile($path, $code); + } +} +~~~ + +מחלקת הקוד `WidgetCode` מרחיבה את המחלקה [CCodeModel]. בדומה למחלקת מודל רגילה, במחלקה זו אנו יכולים להגדיר מתודות `()rules` ו `()attributeLabels` כדי לאמת את הנתונים שהמשתמש מזין ולהציג תוויות עבור המאפיינים במחלקה, בהתאמה. יש לזכור שמאחר והמחלקה הבסיסית [CCodeModel] כבר מגדירה כמה כללים ותוויות, אנו צריכים לאחד אותם עם הכללים והתוויות שאנו מגדירים במחלקה זו. + +המתודה `()prepare` מכינה מראש את הקוד שיווצר. המטרה העיקרית שלו היא להכין רשימת אובייקטים של [CCodeFile], כל אחד מייצג קובץ קוד שנוצר. בדוגמא שלנו, אנו צריכים רק ליצור אובייקט [CCodeFile] אחד המייצג את קובץ המחלקה של הוידג'ט הנוצר. מחלקת הוידג'ט החדשה שתווצר תמוקם תחת התיקיה `protected/components`. אנו קוראים למתודה [CCodeFile::render] כדי ליצור את הקוד עצמו. מתודה זו מכילה את תבנית הקוד כסקריפט PHP ומחזירה את תוצאת הפלט המווה את הקוד שנוצר. + +#### יצירת `views/index.php` + +כשברשותנו הקונטרולר (`WidgetGenerator`) והמודל (`WidgetCode`), הגיע הזמן ליצור את קובץ התצוגה `views/index.php`. + +~~~ +[php] +«h1»אפשרות יצירת וידג'ט«/h1» + +«?php $form=$this-»beginWidget('CCodeForm', array('model'=»$model)); ?» + + «div class="row"» + «?php echo $form-»labelEx($model,'className'); ?» + «?php echo $form-»textField($model,'className',array('size'=»65)); ?» + «div class="tooltip"» + שם מחלקת המודל חייבת להכיל אותיות בלבד + «/div» + «?php echo $form-»error($model,'className'); ?» + «/div» + +«?php $this-»endWidget(); ?» +~~~ + +בקוד המוצג למעלה, אנו בעיקר מציגים טופס בעזרת הוידג'ט [CCodeForm]. בטופס זה, אנו מציגים את שדה הטקסט המהווה את הערך עבור המאפיין `className` במחלקה `WidgetCode`. + +בעת יצירת הטופס, אנו יכולים לנצל שני אפשרויות אשר נמצאות בוידג'ט [CCodeForm]. האחד הוא טקסט עזרה קטן (tooltip). האחר הוא שדה טקסט דביק. + +אם כבר התנסת באחד מאפשרויות יצירת הקוד הנמצאות במערכת, תוכל לשים לב שבעת קביעת פוקוס בשדה טקסט כלשהו, יופיע טקסט עזרה קטן ליד השדה. ניתן לבצע זאת בצורה פשוטה על ידי הוספת DIV עם הגדרת האלמנט 'class` בשם `tooltip` ליד השדה בו רוצים להציג את ההודעה הקטנה. + +עבור שדות מסויימים, אנו נרצה לשמור את הערך התקין האחרון שהיה בהם כדי לחסוך למשתמש להזין את אותו הערך שוב ושוב בכל פעם שהם משתמשים באפשרות יצירת הקוד כדי ליצור קוד. לדוגמא השדה שבו אנו מציינים את שם המחלקה הבסיסית של הקונטרולרים באפשרות יצירת הקוד לקונטרולרים המגיע עם המערכת. שדות דביקים אלו מוצגים בהתחלה כטקסט מסומן סטטי. אם נלחץ עליהם, אם יהפכו לשדות טקסט שניתן לכתוב בהם טקסט כלשהו. + +בכדי להגדיר שדה טקסט בתור שדה דביק, אנו צריכים לבצע שני דברים. + +ראשית, אנו צריכים להגדיר כלל אימות בשם `sticky` עבור המאפיין המקביל במודל. לדוגמא, אפשרות יצירת קונטרולרים המגיע עם המערכת מכילה את הכללים הבאים המגדירים את המאפיינים `baseClass` ו `actions` כמאפיינים דביקים: + +~~~ +[php] +public function rules() +{ + return array_merge(parent::rules(), array( + ...... + array('baseClass, actions', 'sticky'), + )); +} +~~~ + +שנית, אנו צריכים להוסיף מאפיין `class` בשם `sticky` לתג `div` של שדה הטקסט בתצוגה, בצורה הבאה: + +~~~ +[php] +«div class="row sticky"» + ..שדה טקסט כאן... +«/div» +~~~ + +#### יצירת `templates/default/widget.php` + +לבסוף, אנו יוצרים את תבנית הקוד `templates/default/widget.php`. כפי שהסברנו קודם לכן, השימוש של קובץ זה הוא בדומה לקובץ תצוגה המכיל פקודות וביטויים ב PHP. בתבנית קוד, אנו תמיד יכולים לגשת למשתנה `this$` המתייחס לאובייקט של המודל. בדוגמא שלנו, `this$` מתייחס לאובייקט של `WidgetModel`. לכן אנו יכולים לשלוף את שם המחלקה שהמשתמש הזין בטופס בעזרת `this-»className$`. + +~~~ +[php] +«?php echo '«?php'; ?» + +class «?php echo $this-»className; ?» extends CWidget +{ + public function run() + { + + } +} +~~~ + +זה מסכם את החלק לגבי יצירת אפשרות יצירת קוד חדשה. כעת אנו יכולים לגשת לאפשרות יצירת הקוד שהרגע יצרנו על ידי כניסה לקישור `http://hostname/path/to/index.php?r=gii/widget`. + «div class="revision"»$Id: topics.gii.txt 2066 2010-04-11 03:54:25Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/topics.i18n.txt b/docs/guide/he/topics.i18n.txt index adbd66701..599938b4a 100644 --- a/docs/guide/he/topics.i18n.txt +++ b/docs/guide/he/topics.i18n.txt @@ -1,166 +1,166 @@ -ריבוי שפות / לוקליזציה -==================== - -[לוקליזציה](http://he.wikipedia.org/wiki/%D7%9C%D7%95%D7%A7%D7%9C%D7%99%D7%96%D7%A6%D7%99%D7%94) או בקיצור (I18N) מתייחס לתהליך עיצוב אפליקצית תוכנה שהיא תוכל להיות תואמת למגוון של שפות ואזורים ללא ביצוע שינויים הדנסיים. עבור אפליקציות ווב, יש לאפשרות זו חשיבות רבה מאחר ומשתמשים יכולים להגיע מכל מקום בעולם. - -Yii מספק תמיכה עבור I18N בכמה היבטים. - -- מספק מידע אזורי עבור כל שפה אפשרית וגרסא אפשרית. -- מספק שירות תרגום הודעות וקבצים. -- מספק תצוגה של תאריכים וזמן בהתבסס על איזורים. -- מספק תצוגה של מספרים בהתבסס על איזורים. - -בחלק הבא, אנו נפרט לגבי כל אחד ואחד מההיבטים הרשומים למעלה. - -שפה ואיזור -------------------- - -איזור הוא סט של פרמטרים המגדיר את שפת המשתמש, המדינה והגדרות נוספות שהמשתמש רוצה לראות בממשק שלו. איזור בדרך כלל מזוהה על ידי מזהה יחודי המכיל את המזהה היחודי של השפה והמזהה היחודי של המחוז. לדוגמא, המזהה היחודי `en_US` מייצג את האיזור של אנגלית - ארה"ב. למטרת עקביות, כל המזהים היחודיים ב-Yii נכתבים בפורמט של `LanguageID` או `LanguageID_RegionID` באותיות קטנות בלבד (לדוגמא `en`, `en_us`). - -מידע איזורי מיוצג כאובייקט של [CLocale]. אובייקט זה מספק מידע ספציפי עבור האיזור הנמצא בשימוש, הכולל את סמלי המטבעות, סמלי ספרות, פורמט תצוגה של ספרות, פורמט תצוגה של תאריכים ושעות, ושמות הקשורים לתאריכים. - -עם קבלת המזהה היחודי של האיזור או השפה, ניתן לקבל את האובייקט של [CLocale] על ידי קריאה ל `CLocale::getInstance($localeID)` או `CApplication::getLocale($localeID)`. - -> Info|מידע: Yii מגיע עם מידע איזורי עבור כמעט כל שפה ואיזור. המידע מתקבל מ-[Common Locale Data Repository](http://unicode.org/cldr/) (CLDR). עבור כל שפה, רק חלק מהמידע המקורי ב CLDR נמצא בקבצי המידע של כל שפה מאחר והמידע המקורי מכיל מידע רב שבדרך כלל לא נעשה בו שימוש. החל מגרסא 1.1.0, משתמשים יכולים לספק את המידע אודות השפה שלהם בהתאמה אישית. בכדי לעשות זאת, יש להגדיר את המאפיין [CApplication::localeDataPath] עם הנתיב לתיקיה המכילה את המידע עבור השפות. יש לעיין בקבצי המידע של השפות הנמצאות תחת `framework/i18n/data` בכדי ליצור קבצים מותאמים אישית. - -עבור אפליקצית Yii, אנו מבדילים בין השפה [הנמצאת בשימוש|CApplication::language] לבין [שפת המקור|CApplication::sourceLanguage]. שפת השימוש היא השפה של המשתמשים שמשתמשים בה, בזמן ששפת המקור מתייחסת לשפה שקבצי המקור כתובים בה. לוקליזציה נעשית רק בזמן ששני השפות הללו שונות אחת מהשנייה. - -ניתן להגדיר את [שפת השימוש|CApplication::language] [בהגדרות האפליקציה](/doc/guide/basics.application#application-configuration), או ניתן לשנותה בצורה דינאמית לפני כל ביצוע של לוקליזציה. - -> Tip|טיפ: לפעמים, אנו נרצה להגדיר את שפת השימוש לשפה המועדפת על ידי המשתמש (המוגדרת בהגדרות הדפדפן של המשתמש). בכדי לבצע זאת, אנו יכולים לקבל את השפה המועדפת על המשתמש על ידי [CHttpRequest::preferredLanguage]. - -תרגום ------------ - -האפשרות הנחוצה ביותר בלוקליזציה (I18N) הינו התרגום, הכולל תרגום הודעות ותצוגה. הראשון מתרגם הודעת טקסט לשפה הרצויה, בזמן שהשני מתרגם קובץ שלם לשפה הרצויה. - -בקשת תרגום כוללת את האובייקט שצריך לתרגם, שפת המקור בו האובייקט כתוב, ושפת היעד אליו האובייקט יתורגם. באפליקצית Yii, שפת המקור מוגדרת כברירת מחדל לערך המוגדר במאפיין [שפת המקור באפליקציה|CApplication::sourceLanguage] בזמן ששפת היעד מוגדרת לערך המיוצג על ידי המאפיין [שפת היעד באפליקציה|CApplication::language]. במידה ושפת המקור והיעד זהים התרגום לא מתבצע. - -### תרגום הודעות - -תרגום הודעות מתבצע על ידי שימוש ב-[()Yii::t|YiiBase::t]. המתודה מתרגמת את ההודעה [שפת המקור|CApplication::sourceLanguage] אל [שפת היעד|CApplication::language]. - -בזמן תרגום הודעה, יש לציין את הקטגוריה מאחר וההודעה יכולה להיות מתורגמת בצורה שונה תחת קטגוריות שונות (הקשר). הקטגוריה `yii` שמורה עבור הודעות הנמצאות בשימוש על ידי בסיס המערכת של Yii. - -הודעות יכולות להכיל פרמטרים אשר יוחלפו עם הערכים שלהם בזמן השימוש (קריאה) למתודה [()Yii::t|YiiBase::t]. לדוגמא, בבקשת תרגום ההודעה הבאה הפרמטר `{alias}` בהודעה המקורית יוחלף עם הערך שהוגדר לו. - -~~~ -[php] -Yii::t('app', 'Path alias "{alias}" is redefined.', - array('{alias}'=>$alias)) -~~~ - -> Note|הערה: הודעות שדורשות תרגום חייבות להכיל תווים קבועים בלבד. אסור שהודעות אלו יכילו משתנים אשר ישנו את תוכן ההודעה. לדוגמא, - -~~~ -"Invalid -{$message} content." -~~~ - -יש להשתמש בפרמטרים כמו בדוגמא הקודמת במידה וההודעה צריכה להכיל משתנים ומידע אשר משתנה בהתאם לפרמטר כלשהו. - -הודעות מתורגמות שמורות בספריה הנקראת *מקור הודעות* (*message source*). מקור הודעה מיוצג כאובייקט של [CMessageSource] או מחלקות היורשות ממנו. בעת הקריאה למתודה [()Yii::t|YiiBase::t], המתודה תחפש את ההודעה במקור ההודעות ותחזיר את הגרסא המתורגמת של ההודעה במידה והיא נמצאה. - -Yii מגיע עם סוגים שונים של מקור הודעות (אשר משמשים כמקום אחסון לשמירת ההודעות). כמו כן, הינך יכול להרחיב את המחלקה [CMessageSource] בכדי ליצור מקור הודעות מותאם אישית בעצמך. - -- [CPhpMessageSource]: הודעות התרגום שמורות במערך של אלמנטים המכילים מפתח=>ערך. ההודעה המקורית היא המפתח בכל אלמנט במערך והתרגום הוא הערך בכל אלמנט במערך. כל מערך מייצג תרגום של הודעות בקטגוריה ספציפית והמערך שמור בקובץ PHP נפרד ששמו של הקובץ הוא שם הקטגוריה. קבצי התרגום של אותה שפה נמצאים תחת אותה תיקיה ששמה הוא שם המזהה היחודי של השפה (לדוגמא en, he, de). וכל תיקיות השפות הללו נמצאות תחת התיקיה המוגדרת על ידי [basePath|CPhpMessageSource::basePath]. - -- [CGettextMessageSource]: קבצי התרגום נשמרים כקבצי [GNU -Gettext](http://www.gnu.org/software/gettext/). - -- [CDbMessageSource]: ההודעות המתורגמות שמורות בטבלאות במסד הנתונים. למידע נוסף יש לעיין בדוקומנטציה של [CDbMessageSource]. - -תרגום ההודעות נטען [כרכיב אפליקציה](/doc/guide/basics.application#application-component). המערכת טוענת מראש רכיב אפליקציה בשם [messages|CApplication::messages] כדי לאחסן הודעות אשר נמצאות בשימוש על ידי האפליקציה של המשתמש. כברירת מחדל, סוג הרכיב של תרגום ההודעות הינו [CPhpMessageSource] והתיקיה בה שמורים קבצי ה-PHP המכילים את התרגומים נמצא תחת `protected/messages`. - -לסיכום, בכדי להשתמש בתרגום הודעות, יש לבצע את השלבים הבאים: - - -1. להשתמש ב [()Yii::t|YiiBase::t] במקומות שההודעות צריכות להיות על פי שפה; - -2. ליצור קבצי PHP אשר יכילו את התרגום תחת `protected/messages/LocaleID/CategoryName.php`. כל קובץ מחזיר מערך של הודעות מתורגמות. יש לזכור שדוגמא זו יוצאת מנוקדת הנחה שהינך משתמש ברכיב תרגום ההודעות ברירת המחדל שהוא [CPhpMessageSource] כדי לאחסן את ההודעות המתורגמות. - -3. יש להגדיר את [CApplication::sourceLanguage] ו [CApplication::language] - -> Tip|טיפ: הכלי `yiic` בתוך המערכת של Yii יכול להיות לכלי עזר שימושי לניהול תרגום הודעות בעת השימוש ב-[CPhpMessageSource] כרכיב התרגום. הפקודה `message` יכולה לחלץ הודעות שדורשות תרגום מקבצי מקור בצורה אוטומטית ולאחד אותם עם קבצי תרגום קיימים. - -החל מגרסא 1.0.10, בזמן השימוש ב-[CPhpMessageSource] לניהול ההודעות, הודעות עבור מחלקות של וידג'טים, תוספים, מודולים ניתן לנהל ולהשתמש בהם בצורה מיוחדת. במיוחד, אם הודעה שייכת לתוסף ששם המחלקה שלו הוא `Xyz`, אז ניתן להגדיר את שם הקטגוריה של ההודעה בפורמט של `Xyz.categoryName`. קובץ ההודעות יהיה `BasePath/messages/LanguageID/categoryName.php`, כש-`BasePath` מתייחס לתיקיה שבה קובץ המחלקה של התוסף נמצא. ובעת השימוש ב `Yii::t()` בכדי לתרגם הודעה של תוסף, יש להשתמש בפורמט הבא במקום: - -~~~ -[php] -Yii::t('Xyz.categoryName', 'הודעה לתרגום') -~~~ - -החל מגרסא 1.0.2, Yii הוסיפה תמיכה עבור [פורמט בחירה|CChoiceFormat]. פורמט בחירה מתייחס לבחירת נתון מתורגם בהתאם לערך המספרי. לדוגמא, בשפה האנגלית המילה `book` יכולה להיות במונח של יחיד או רבים בהתאם לכמות הספרים, בזמן שבשפות אחרות המילה הזו נשארת באותו מצב עבור יחיד או רבים (כמו סינית) או מכילה כללים יותר מסובכים עבור רבים (כמו ברוסית). פורמט בחירה פותר בעיה זו באופן פשוט אך יעיל. - -בכדי להשתמש בפורמט בחירה, הודעה מתורגמת צריכה להכיל רצף של זוגות ביטויים המופרדים ב-`|`, כפי שמוצג בדוגמא הבאה: - -~~~ -[php] -'expr1#message1|expr2#message2|expr3#message3' -~~~ - -כשהביטוי `exprN` מתייחס לביטוי PHP אשר תוצאתו שווה לערך בוליאני (true/false) המעיד אם ההודעה המדוברת היא זו שצריכה להיות מוצגת. רק ההודעה הראשונה אשר תוצאת הביטוי תחזיר true תוצג. -ביטוי יכול להכיל משתנה מיוחד בשם `n` (שים לב שם המשתנה הוא `n` לא `n$`) אשר מייצג את הערך המספרי כפרמטר ראשון שעליו מתבצעת הבדיקה. לדוגמא, נניח והודעה מתורגמת הינה: - -~~~ -[php] -'n==1#one book|n>1#many books' -~~~ - -ואנו מעבירים ערך מספרי השווה ל 2 למערך הפרמטרים של ההודעה בעת הקריאה ל [()Yii::t|YiiBase::t], אנו נקבל `many book` בהודעה המתורגמת הסופית. - -בכדי לקצר את התחביר, אם הביטוי הוא מספר, הוא מקבל יחס של `n==Number`. לכן, את ההודעה המתורגמת למעלה ניתן לכתוב בצורה הבאה: - -~~~ -[php] -'1#one book|n>1#many books' -~~~ - - -### תרגום קבצים - -תרגום קבצים נעשה על ידי קריאה ל - [CApplication::findLocalizedFile()]. עם הנתיב לקובץ שצריך לתרגם, המתודה תחפש את הקובץ עם אותו השם תחת תיקיה ששמה הוא שם האיזור לתרגום (`LocaleID`). במידה ונמצא הנתיב לקובץ יוחזר; אחרת, הנתיב המקורי יוחזר. - -תרגום קבצים נועד בעיקר בעת הצגת קובץ תצוגה. בעת הקריאה לאחת ממתודות התצוגה בקונטרולר או וידג'ט, קבצי התצוגה יתורגמו אוטומטית. לדוגמא, אם [שפת היעד|CApplication::language] הינה `zh_cn` בזמן [ששפת המקור|CApplication::sourceLanguage] הינה `en_us`, הצגת קובץ תצוגה בשם `edit` יגרום לחיפוש אחר קובץ התצוגה תחת התיקיה `protected/views/ControllerID/zh_cn/edit.php`. במידה והקובץ נמצא, גרסא מתורגמת זו היא זו שתוצג; אחרת, הקובץ `protected/views/ControllerID/edit.php` יוצג במקום. - -ניתן להשתמש בתרגום קבצים למטרות אחרות, לדוגמא, הצגת תמונה מתורגמת או טעינת קובץ המבוסס על פי השפה. - -תצוגת תאריך ושעה ------------------------- - -תאריך ושעה בדרך כלל מוצג בפורמט שונה עבור מדינות ואיזורים שונים. המטרה של פורמט תאריך ושעה הוא ליצור תאריך או שעה בהתאם לאיזור/שפה הנמצאת בשימוש כרגע. Yii מספקת מחלקה בשם [CDateFormatter] למטרה זו. - -כל אובייקט של [CDateFormatter] מקושר עם איזור (שפה מסויימת) מסויים. בכדי לגשת לאובייקט המציג את התאריך והשעה עם שפת היעד לאורך כל האפליקציה, אנו יכולים פשוט לגשת למאפיין [dateFormatter|CApplication::dateFormatter] באפליקציה. - -מחלקת [CDateFormatter] מספקת בעיקר שני מתודות עבור תצוגה של זמן בפורמט UNIX. - -- [format|CDateFormatter::format]: מתודה זו מציגה את הזמן הנתון בפורמט UNIX כסטרינג רגיל בהתאם לדפוס שהוגדר, לדוגמא, - -~~~ -$dateFormatter->format('yyyy-MM-dd',$timestamp) -~~~ - -- [formatDateTime|CDateFormatter::formatDateTime]: מתודה זו מציגה את הזמן הנתון בפורמט UNIX כסטרינג רגיל בהתאם לדפוס שהוגדר **מראש** בהגדרות השפה (לדוגמא, `short` , `long`) - -תצוגת ספרות (מספרים) ------------------ - -בדומה לתאריך ושעה, תצוגת מספרים היא שונה במדינות ואיזורים שונים. תצוגת ספרות כוללת מספרים עשרוניים, שערי מט"ח, ואחוזים. Yii מספקת את המחלקה [CNumberFormatter] למטרות אלו. - -בכדי להשתמש בתצוגת ספרות על פי שפת היעד לאורך כל האפליקציה ניתן להשתמש במאפיין [numberFormatter|CApplication::numberFormatter] באפליקציה. - -המחלקה [CNumberFormatter] מספקת את המתודות הבאות בכדי להציג מספר שלם או עשרוני. - -- [format|CNumberFormatter::format]: מתודה זו מציגה את המספר הנתון בהתאם לדפוס שהוגדר, לדוגמא, - -~~~ -$numberFormatter->format('#,##0.00',$number) -~~~ - -- [formatDecimal|CNumberFormatter::formatDecimal]: מתודה זו מציגה את המספר הנתון בהתאם לדפוס העשרוני שהוגדר מראש בהגדרות שפת היעד (השפה בה משתמשים כרגע באפליקציה). - -- [formatCurrency|CNumberFormatter::formatCurrency]: מתודה זו מציגה את המספר הנתון או שער מט"ח בהתאם לדפוס של שערי מט"ח שהוגדר מראש בהגדרות שפת היעד (השפה בה משתמשים כרגע באפליקציה). - -- [formatPercentage|CNumberFormatter::formatPercentage]: מתודה זו מציגה את המספר הנתון בהתאם לדפוס של תצוגת אחוזים שהוגדר מראש בהגדרות שפת היעד (השפה בה משתמשים כרגע באפליקציה). - +ריבוי שפות / לוקליזציה +==================== + +[לוקליזציה](http://he.wikipedia.org/wiki/%D7%9C%D7%95%D7%A7%D7%9C%D7%99%D7%96%D7%A6%D7%99%D7%94) או בקיצור (I18N) מתייחס לתהליך עיצוב אפליקצית תוכנה שהיא תוכל להיות תואמת למגוון של שפות ואזורים ללא ביצוע שינויים הדנסיים. עבור אפליקציות ווב, יש לאפשרות זו חשיבות רבה מאחר ומשתמשים יכולים להגיע מכל מקום בעולם. + +Yii מספק תמיכה עבור I18N בכמה היבטים. + +- מספק מידע אזורי עבור כל שפה אפשרית וגרסא אפשרית. +- מספק שירות תרגום הודעות וקבצים. +- מספק תצוגה של תאריכים וזמן בהתבסס על איזורים. +- מספק תצוגה של מספרים בהתבסס על איזורים. + +בחלק הבא, אנו נפרט לגבי כל אחד ואחד מההיבטים הרשומים למעלה. + +שפה ואיזור +------------------- + +איזור הוא סט של פרמטרים המגדיר את שפת המשתמש, המדינה והגדרות נוספות שהמשתמש רוצה לראות בממשק שלו. איזור בדרך כלל מזוהה על ידי מזהה יחודי המכיל את המזהה היחודי של השפה והמזהה היחודי של המחוז. לדוגמא, המזהה היחודי `en_US` מייצג את האיזור של אנגלית - ארה"ב. למטרת עקביות, כל המזהים היחודיים ב-Yii נכתבים בפורמט של `LanguageID` או `LanguageID_RegionID` באותיות קטנות בלבד (לדוגמא `en`, `en_us`). + +מידע איזורי מיוצג כאובייקט של [CLocale]. אובייקט זה מספק מידע ספציפי עבור האיזור הנמצא בשימוש, הכולל את סמלי המטבעות, סמלי ספרות, פורמט תצוגה של ספרות, פורמט תצוגה של תאריכים ושעות, ושמות הקשורים לתאריכים. + +עם קבלת המזהה היחודי של האיזור או השפה, ניתן לקבל את האובייקט של [CLocale] על ידי קריאה ל `CLocale::getInstance($localeID)` או `CApplication::getLocale($localeID)`. + +> Info|מידע: Yii מגיע עם מידע איזורי עבור כמעט כל שפה ואיזור. המידע מתקבל מ-[Common Locale Data Repository](http://unicode.org/cldr/) (CLDR). עבור כל שפה, רק חלק מהמידע המקורי ב CLDR נמצא בקבצי המידע של כל שפה מאחר והמידע המקורי מכיל מידע רב שבדרך כלל לא נעשה בו שימוש. החל מגרסא 1.1.0, משתמשים יכולים לספק את המידע אודות השפה שלהם בהתאמה אישית. בכדי לעשות זאת, יש להגדיר את המאפיין [CApplication::localeDataPath] עם הנתיב לתיקיה המכילה את המידע עבור השפות. יש לעיין בקבצי המידע של השפות הנמצאות תחת `framework/i18n/data` בכדי ליצור קבצים מותאמים אישית. + +עבור אפליקצית Yii, אנו מבדילים בין השפה [הנמצאת בשימוש|CApplication::language] לבין [שפת המקור|CApplication::sourceLanguage]. שפת השימוש היא השפה של המשתמשים שמשתמשים בה, בזמן ששפת המקור מתייחסת לשפה שקבצי המקור כתובים בה. לוקליזציה נעשית רק בזמן ששני השפות הללו שונות אחת מהשנייה. + +ניתן להגדיר את [שפת השימוש|CApplication::language] [בהגדרות האפליקציה](/doc/guide/basics.application#application-configuration), או ניתן לשנותה בצורה דינאמית לפני כל ביצוע של לוקליזציה. + +> Tip|טיפ: לפעמים, אנו נרצה להגדיר את שפת השימוש לשפה המועדפת על ידי המשתמש (המוגדרת בהגדרות הדפדפן של המשתמש). בכדי לבצע זאת, אנו יכולים לקבל את השפה המועדפת על המשתמש על ידי [CHttpRequest::preferredLanguage]. + +תרגום +----------- + +האפשרות הנחוצה ביותר בלוקליזציה (I18N) הינו התרגום, הכולל תרגום הודעות ותצוגה. הראשון מתרגם הודעת טקסט לשפה הרצויה, בזמן שהשני מתרגם קובץ שלם לשפה הרצויה. + +בקשת תרגום כוללת את האובייקט שצריך לתרגם, שפת המקור בו האובייקט כתוב, ושפת היעד אליו האובייקט יתורגם. באפליקצית Yii, שפת המקור מוגדרת כברירת מחדל לערך המוגדר במאפיין [שפת המקור באפליקציה|CApplication::sourceLanguage] בזמן ששפת היעד מוגדרת לערך המיוצג על ידי המאפיין [שפת היעד באפליקציה|CApplication::language]. במידה ושפת המקור והיעד זהים התרגום לא מתבצע. + +### תרגום הודעות + +תרגום הודעות מתבצע על ידי שימוש ב-[()Yii::t|YiiBase::t]. המתודה מתרגמת את ההודעה [שפת המקור|CApplication::sourceLanguage] אל [שפת היעד|CApplication::language]. + +בזמן תרגום הודעה, יש לציין את הקטגוריה מאחר וההודעה יכולה להיות מתורגמת בצורה שונה תחת קטגוריות שונות (הקשר). הקטגוריה `yii` שמורה עבור הודעות הנמצאות בשימוש על ידי בסיס המערכת של Yii. + +הודעות יכולות להכיל פרמטרים אשר יוחלפו עם הערכים שלהם בזמן השימוש (קריאה) למתודה [()Yii::t|YiiBase::t]. לדוגמא, בבקשת תרגום ההודעה הבאה הפרמטר `{alias}` בהודעה המקורית יוחלף עם הערך שהוגדר לו. + +~~~ +[php] +Yii::t('app', 'Path alias "{alias}" is redefined.', + array('{alias}'=>$alias)) +~~~ + +> Note|הערה: הודעות שדורשות תרגום חייבות להכיל תווים קבועים בלבד. אסור שהודעות אלו יכילו משתנים אשר ישנו את תוכן ההודעה. לדוגמא, + +~~~ +"Invalid +{$message} content." +~~~ + +יש להשתמש בפרמטרים כמו בדוגמא הקודמת במידה וההודעה צריכה להכיל משתנים ומידע אשר משתנה בהתאם לפרמטר כלשהו. + +הודעות מתורגמות שמורות בספריה הנקראת *מקור הודעות* (*message source*). מקור הודעה מיוצג כאובייקט של [CMessageSource] או מחלקות היורשות ממנו. בעת הקריאה למתודה [()Yii::t|YiiBase::t], המתודה תחפש את ההודעה במקור ההודעות ותחזיר את הגרסא המתורגמת של ההודעה במידה והיא נמצאה. + +Yii מגיע עם סוגים שונים של מקור הודעות (אשר משמשים כמקום אחסון לשמירת ההודעות). כמו כן, הינך יכול להרחיב את המחלקה [CMessageSource] בכדי ליצור מקור הודעות מותאם אישית בעצמך. + +- [CPhpMessageSource]: הודעות התרגום שמורות במערך של אלמנטים המכילים מפתח=>ערך. ההודעה המקורית היא המפתח בכל אלמנט במערך והתרגום הוא הערך בכל אלמנט במערך. כל מערך מייצג תרגום של הודעות בקטגוריה ספציפית והמערך שמור בקובץ PHP נפרד ששמו של הקובץ הוא שם הקטגוריה. קבצי התרגום של אותה שפה נמצאים תחת אותה תיקיה ששמה הוא שם המזהה היחודי של השפה (לדוגמא en, he, de). וכל תיקיות השפות הללו נמצאות תחת התיקיה המוגדרת על ידי [basePath|CPhpMessageSource::basePath]. + +- [CGettextMessageSource]: קבצי התרגום נשמרים כקבצי [GNU +Gettext](http://www.gnu.org/software/gettext/). + +- [CDbMessageSource]: ההודעות המתורגמות שמורות בטבלאות במסד הנתונים. למידע נוסף יש לעיין בדוקומנטציה של [CDbMessageSource]. + +תרגום ההודעות נטען [כרכיב אפליקציה](/doc/guide/basics.application#application-component). המערכת טוענת מראש רכיב אפליקציה בשם [messages|CApplication::messages] כדי לאחסן הודעות אשר נמצאות בשימוש על ידי האפליקציה של המשתמש. כברירת מחדל, סוג הרכיב של תרגום ההודעות הינו [CPhpMessageSource] והתיקיה בה שמורים קבצי ה-PHP המכילים את התרגומים נמצא תחת `protected/messages`. + +לסיכום, בכדי להשתמש בתרגום הודעות, יש לבצע את השלבים הבאים: + + +1. להשתמש ב [()Yii::t|YiiBase::t] במקומות שההודעות צריכות להיות על פי שפה; + +2. ליצור קבצי PHP אשר יכילו את התרגום תחת `protected/messages/LocaleID/CategoryName.php`. כל קובץ מחזיר מערך של הודעות מתורגמות. יש לזכור שדוגמא זו יוצאת מנוקדת הנחה שהינך משתמש ברכיב תרגום ההודעות ברירת המחדל שהוא [CPhpMessageSource] כדי לאחסן את ההודעות המתורגמות. + +3. יש להגדיר את [CApplication::sourceLanguage] ו [CApplication::language] + +> Tip|טיפ: הכלי `yiic` בתוך המערכת של Yii יכול להיות לכלי עזר שימושי לניהול תרגום הודעות בעת השימוש ב-[CPhpMessageSource] כרכיב התרגום. הפקודה `message` יכולה לחלץ הודעות שדורשות תרגום מקבצי מקור בצורה אוטומטית ולאחד אותם עם קבצי תרגום קיימים. + +החל מגרסא 1.0.10, בזמן השימוש ב-[CPhpMessageSource] לניהול ההודעות, הודעות עבור מחלקות של וידג'טים, תוספים, מודולים ניתן לנהל ולהשתמש בהם בצורה מיוחדת. במיוחד, אם הודעה שייכת לתוסף ששם המחלקה שלו הוא `Xyz`, אז ניתן להגדיר את שם הקטגוריה של ההודעה בפורמט של `Xyz.categoryName`. קובץ ההודעות יהיה `BasePath/messages/LanguageID/categoryName.php`, כש-`BasePath` מתייחס לתיקיה שבה קובץ המחלקה של התוסף נמצא. ובעת השימוש ב `Yii::t()` בכדי לתרגם הודעה של תוסף, יש להשתמש בפורמט הבא במקום: + +~~~ +[php] +Yii::t('Xyz.categoryName', 'הודעה לתרגום') +~~~ + +החל מגרסא 1.0.2, Yii הוסיפה תמיכה עבור [פורמט בחירה|CChoiceFormat]. פורמט בחירה מתייחס לבחירת נתון מתורגם בהתאם לערך המספרי. לדוגמא, בשפה האנגלית המילה `book` יכולה להיות במונח של יחיד או רבים בהתאם לכמות הספרים, בזמן שבשפות אחרות המילה הזו נשארת באותו מצב עבור יחיד או רבים (כמו סינית) או מכילה כללים יותר מסובכים עבור רבים (כמו ברוסית). פורמט בחירה פותר בעיה זו באופן פשוט אך יעיל. + +בכדי להשתמש בפורמט בחירה, הודעה מתורגמת צריכה להכיל רצף של זוגות ביטויים המופרדים ב-`|`, כפי שמוצג בדוגמא הבאה: + +~~~ +[php] +'expr1#message1|expr2#message2|expr3#message3' +~~~ + +כשהביטוי `exprN` מתייחס לביטוי PHP אשר תוצאתו שווה לערך בוליאני (true/false) המעיד אם ההודעה המדוברת היא זו שצריכה להיות מוצגת. רק ההודעה הראשונה אשר תוצאת הביטוי תחזיר true תוצג. +ביטוי יכול להכיל משתנה מיוחד בשם `n` (שים לב שם המשתנה הוא `n` לא `n$`) אשר מייצג את הערך המספרי כפרמטר ראשון שעליו מתבצעת הבדיקה. לדוגמא, נניח והודעה מתורגמת הינה: + +~~~ +[php] +'n==1#one book|n>1#many books' +~~~ + +ואנו מעבירים ערך מספרי השווה ל 2 למערך הפרמטרים של ההודעה בעת הקריאה ל [()Yii::t|YiiBase::t], אנו נקבל `many book` בהודעה המתורגמת הסופית. + +בכדי לקצר את התחביר, אם הביטוי הוא מספר, הוא מקבל יחס של `n==Number`. לכן, את ההודעה המתורגמת למעלה ניתן לכתוב בצורה הבאה: + +~~~ +[php] +'1#one book|n>1#many books' +~~~ + + +### תרגום קבצים + +תרגום קבצים נעשה על ידי קריאה ל - [CApplication::findLocalizedFile()]. עם הנתיב לקובץ שצריך לתרגם, המתודה תחפש את הקובץ עם אותו השם תחת תיקיה ששמה הוא שם האיזור לתרגום (`LocaleID`). במידה ונמצא הנתיב לקובץ יוחזר; אחרת, הנתיב המקורי יוחזר. + +תרגום קבצים נועד בעיקר בעת הצגת קובץ תצוגה. בעת הקריאה לאחת ממתודות התצוגה בקונטרולר או וידג'ט, קבצי התצוגה יתורגמו אוטומטית. לדוגמא, אם [שפת היעד|CApplication::language] הינה `zh_cn` בזמן [ששפת המקור|CApplication::sourceLanguage] הינה `en_us`, הצגת קובץ תצוגה בשם `edit` יגרום לחיפוש אחר קובץ התצוגה תחת התיקיה `protected/views/ControllerID/zh_cn/edit.php`. במידה והקובץ נמצא, גרסא מתורגמת זו היא זו שתוצג; אחרת, הקובץ `protected/views/ControllerID/edit.php` יוצג במקום. + +ניתן להשתמש בתרגום קבצים למטרות אחרות, לדוגמא, הצגת תמונה מתורגמת או טעינת קובץ המבוסס על פי השפה. + +תצוגת תאריך ושעה +------------------------ + +תאריך ושעה בדרך כלל מוצג בפורמט שונה עבור מדינות ואיזורים שונים. המטרה של פורמט תאריך ושעה הוא ליצור תאריך או שעה בהתאם לאיזור/שפה הנמצאת בשימוש כרגע. Yii מספקת מחלקה בשם [CDateFormatter] למטרה זו. + +כל אובייקט של [CDateFormatter] מקושר עם איזור (שפה מסויימת) מסויים. בכדי לגשת לאובייקט המציג את התאריך והשעה עם שפת היעד לאורך כל האפליקציה, אנו יכולים פשוט לגשת למאפיין [dateFormatter|CApplication::dateFormatter] באפליקציה. + +מחלקת [CDateFormatter] מספקת בעיקר שני מתודות עבור תצוגה של זמן בפורמט UNIX. + +- [format|CDateFormatter::format]: מתודה זו מציגה את הזמן הנתון בפורמט UNIX כסטרינג רגיל בהתאם לדפוס שהוגדר, לדוגמא, + +~~~ +$dateFormatter->format('yyyy-MM-dd',$timestamp) +~~~ + +- [formatDateTime|CDateFormatter::formatDateTime]: מתודה זו מציגה את הזמן הנתון בפורמט UNIX כסטרינג רגיל בהתאם לדפוס שהוגדר **מראש** בהגדרות השפה (לדוגמא, `short` , `long`) + +תצוגת ספרות (מספרים) +----------------- + +בדומה לתאריך ושעה, תצוגת מספרים היא שונה במדינות ואיזורים שונים. תצוגת ספרות כוללת מספרים עשרוניים, שערי מט"ח, ואחוזים. Yii מספקת את המחלקה [CNumberFormatter] למטרות אלו. + +בכדי להשתמש בתצוגת ספרות על פי שפת היעד לאורך כל האפליקציה ניתן להשתמש במאפיין [numberFormatter|CApplication::numberFormatter] באפליקציה. + +המחלקה [CNumberFormatter] מספקת את המתודות הבאות בכדי להציג מספר שלם או עשרוני. + +- [format|CNumberFormatter::format]: מתודה זו מציגה את המספר הנתון בהתאם לדפוס שהוגדר, לדוגמא, + +~~~ +$numberFormatter->format('#,##0.00',$number) +~~~ + +- [formatDecimal|CNumberFormatter::formatDecimal]: מתודה זו מציגה את המספר הנתון בהתאם לדפוס העשרוני שהוגדר מראש בהגדרות שפת היעד (השפה בה משתמשים כרגע באפליקציה). + +- [formatCurrency|CNumberFormatter::formatCurrency]: מתודה זו מציגה את המספר הנתון או שער מט"ח בהתאם לדפוס של שערי מט"ח שהוגדר מראש בהגדרות שפת היעד (השפה בה משתמשים כרגע באפליקציה). + +- [formatPercentage|CNumberFormatter::formatPercentage]: מתודה זו מציגה את המספר הנתון בהתאם לדפוס של תצוגת אחוזים שהוגדר מראש בהגדרות שפת היעד (השפה בה משתמשים כרגע באפליקציה). +
$Id: topics.i18n.txt 2069 2009-12-26 20:56:05Z qiang.xue $
\ No newline at end of file diff --git a/docs/guide/he/topics.logging.txt b/docs/guide/he/topics.logging.txt index 600b72e16..29545d3c2 100644 --- a/docs/guide/he/topics.logging.txt +++ b/docs/guide/he/topics.logging.txt @@ -1,149 +1,149 @@ -תיעוד -======= - -Yii מספקת אפשרות לתיעוד גמישה ונרחבת. הודעות אשר מתועדות ניתנות לסיווג על פי רמות וקטגוריות. שימוש בפילטרים של רמות וקטגוריות, ניתן לנתב את ההודעות הנבחרות ליעדים שונים, כמו קבצים, אימיילים, תצוגה בחלונות בדפדפן וכדומה. - -תיעוד הודעות ---------------- - -ניתן לתעד הודעות על ידי קריאה ל [Yii::log] או [Yii::trace]. ההבדל בין שני המתודות הללו היא שהמתודה השנייה מתעדת הודעה רק בזמן שהאפליקציה נמצאת במצב של [ניפוי שגיאות](/doc/guide/basics.entry#debug-mode). - -~~~ -[php] -Yii::log($message, $level, $category); -Yii::trace($message, $category); -~~~ - -בעת תיעוד הודעה, אנו צריכים להגדיר את הקטגוריה והרמה שלו. קטגוריה הינה סטרינג בפורמט של `xxx.yyy.zzz` בדומה [שמות מקוצרים](/doc/guide/basics.namespace). לדוגמא, אם ההודעה מתועדת תחת [CController], אנו יכולים להשתמש בקטגוריה `system.web.CController`. רמת ההודעה צריכה להיות אחד מהערכים הבאים: - -- `trace`: זוהי הרמה הנמצאת בשימוש על ידי [Yii::trace]. היא נועדה לעקוב אחר תהליך יצירת האפליקציה בזמן הפיתוח. - -- `info`: רמה זו נועדה לתיעוד של הודעות כלליות. - -- `profile`: רמה זו נועדה לתיעוד של הודעות פרופיל, אשר נרחיב עליהם בהמשך. - -- `warning`: רמה זו נועדה לתיעוד של הודעות אזהרה. - -- `error`: רמה זו נועדה לתיעוד של הודעות שגיאה. - -ניתוב הודעות ---------------- - -הודעות המתועדות על ידי [Yii::log] או [Yii::trace] נשמרות בזיכרון. אנו בדרך כלל צריכים להציג אותם בדפדפן, או לשמור אותם במקום אחסון קבוע כמו קבצים, אימיילים. זה נקרא *ניתוב הודעות*, כלומר, שליחת ההודעות ליעד שונה. - -באפליקציות Yii, ניתוב הודעות מנוהל על ידי הרכיב [CLogRouter]. הוא מנהל סט של מה שנקרא *ניתוב תיעודים*. כל נתב מייצג יעד עבור ההודעות. ניתן לבצע פילטר על גבי הודעות המועברות בנתב על פי הרמות והקטגוריות שלהן. - -בכדי להשתמש בניתוב הודעות, אנו צריכים להתקין ולטעון מראש את רכיב האפליקציה [CLogRouter]. כמו כן אנו צריכים להגדיר את המאפיין [routes|CLogRouter::routes] עם הנתבים בהם אנו רוצים להשתמש. הקוד הבא מציג דוגמא [לקובץ הגדרות](/doc/guide/basics.application#application-configuration) אשר מוגדר להשתמש עם ניתוב הודעות: - -~~~ -[php] -array( - ...... - 'preload'=»array('log'), - 'components'=»array( - ...... - 'log'=»array( - 'class'=»'CLogRouter', - 'routes'=»array( - array( - 'class'=»'CFileLogRoute', - 'levels'=»'trace, info', - 'categories'=»'system.*', - ), - array( - 'class'=»'CEmailLogRoute', - 'levels'=»'error, warning', - 'emails'=»'admin@example.com', - ), - ), - ), - ), -) -~~~ - -בדוגמא למעלה, יש ברשותנו שני נתבים. הנתב הראשון הינו [CFileLogRoute] השומר את ההודעות בקובץ תחת התיקיה הזמנית באפליקציה. רק ההודעות שרמה שלהן היא `trace` או `info` ושהקטגוריה שלהם מתחילה ב `system` נשמרות. הנתב השני הינו [CEmailLogRoute] אשר שולח את ההודעות לכתובות האימיילים המוגדרות. רק ההודעות שהרמה שלהן היא `error` או `warning` נשלחות. - -ניתן להשתמש בסוגי הנתבים הבאים באפליקציות Yii: - - - - - [CDbLogRoute]: שומר את ההודעות בטבלה במסד הנתונים. - - [CEmailLogRoute]: שולח את ההודעות אל אימיילים שהוגדרו מראש. - - [CFileLogRoute]: שומר את ההודעות בקובץ תחת התיקיה הזמנית של האפליקציה. - - [CWebLogRoute]: מציג את ההודעות בסוף כל עמוד בדפדפן. - - [CProfileLogRoute]: מציג את הודעות הפרופיל בסוף כל עמוד בדפדפן. - -» Info|מידע: ניתוב הודעות מתבצע בסוף הבקשה הנוכחית כשהאירוע [onEndRequest|CApplication::onEndRequest] מתרחש. בכדי לעצור את טעינת הסקריפט וביצוע הבקשה במקום כלשהו, יש לקרוא למתודה [()CApplication::end] במקום `()die` או `()exit`, בגלל ש-[()CApplication::end] יטען את האירוע [onEndRequest|CApplication::onEndRequest] כדי שיהיה ניתן לתעד ולנתב את ההודעות כראוי. - -### סינון הודעות - -כפי שכבר ציינו, ניתן לסנן הודעות על פי הרמות והקטגוריות בהם הן נמצאות לפני שהן נשלחות לניתוב כלשהו. זה נעשה על ידי הגדרת המאפיינים [levels|CLogRoute::levels] ו [categories|CLogRoute::categories] של הנתב המדובר. מספר רב של רמות או קטגוריות צריכות להיות משורשרות (מחוברות אחת לשנייה) בעזרת פסיק ( , ). - -מאחר וקטגוריות הודעות הם בפורמט של `xxx.yyy.zzz`, אנו יכולים להתייחס אליהם כהיררכית קטגוריות. במיוחד, אנו אומרים ש `xxx` הוא האב של `xxx.yyy` שהוא האב של `xxx.yyy.zzz`. לכן אנו יכולים להשתמש ב-`*.xxx` בכדי לייצג את הקטגוריה `xxx` וכל תתי הקטגוריות הנמצאות תחתיו. - -### תיעוד מידע בהקשר מסויים - -החל מגרסא 1.0.6, אנו יכולים להגדיר לתעד מידע נוסף בהקשר מסויים, כמו לדוגמא משתנים מוגדרים מראש של PHP (כמו `GET_$` ו `SERVER_$`), מספר מזהה ב Session, שם משתמש וכדומה. זה נעשה על ידי הגדרת המאפיין [CLogRoute::filter] בנתב מסויים לפילטר המתאים. - -המערכת מגיעה עם מחלקה נוחה בשם [CLogFilter] שניתן להשתמש בה כפילטר הדרוש ברוב המקרים. כברירת מחדל, [CLogFilter] יתעד הודעות עם משתנים כמו `GET_$` ו `SERVER_$` אשר מכילים בדרך כלל מידע חשוב בהקשר למערכת. כמו כן, ניתן להגדיר את [CLogFilter] כדי שיספק קידומת עבור הודעות עם מספר מזהה יחודי כלשהו, שם משתמש וכדומה, אשר יכול לפשט בצורה משמעותית את החיפוש הגלובלי כשאנו בודקים את ההודעות המתועדות הרבות. - -ההגדרות הבאות מציגות כיצד להפעיל תיעוד מידע בהקשר מסויים. דע שכל נתב הודעות יכול להכיל את פילטר ההודעות שלו. וכברירת מחדל, נתב הודעות לא מכיל פילטר הודעות. - -~~~ -[php] -array( - ...... - 'preload'=»array('log'), - 'components'=»array( - ...... - 'log'=»array( - 'class'=»'CLogRouter', - 'routes'=»array( - array( - 'class'=»'CFileLogRoute', - 'levels'=»'error', - 'filter'=»'CLogFilter', - ), - ...נתבי הודעות נוספים... - ), - ), - ), -) -~~~ - -החל מגרסא 1.0.7, Yii מאפשרת לשמור את המיקום (קובץ ושורה) שההודעה תועדה עבור הודעות אשר מתועדות על ידי `Yii::trace`. אפשרות זו כבויה כברירת מחלד מאחר והיא משפיעה לרעה על ביצועי המערכת. בכדי להפעיל אפשרות זו, יש להגדיר פשוט משתנה גלובלי `YII_TRACE_LEVEL` בתחילת הסקריפט (בקובץ `index.php` לפני הטעינה של הקובץ `yii.php`) עם ערך הגדול מ-0. Yii תוסיף עבור כל הודעה מתועדת את הקובץ והשורה שבה ההודעה תועדה. המספר המוגדר בערך הגלובלי `YII_TRACE_LEVEL` הוא כדי שהמערכת תדע להציג את עומק הקריאה, זאת אומרת שאם ההודעה תועדה בקובץ שנטען שלישי מתחילת הסקריפט (מהרצת קובץ `index.php`) ואנו הגדרנו את המשתנה `YII_TRACE_LEVEL` למספר 2 רק שני הקבצים האחרונים בדרך להודעה יתועדו. מידע זה שימושי בעיקר בזמן פיתוח מאחר וזה יכול לעזור בזיהוי המקומות בהם ההודעה תועדה. - -שימוש במשתנה הגלובלי `YII_TRACE_LEVEL` נעשה בצורה הבאה: - -~~~ -[php] -define('YII_TRACE_LEVEL', 2); -~~~ - -תיעוד ביצועים (פרופיל) ---------------------- - -תיעוד ביצועים הינה אפשרות מיוחדת בתיעוד ההודעות. שימוש בתיעוד ביצועים נעשה בעיקר כדי למדוד את הזמן הדרוש עבור הרצת קוד מסויים בכדי לדעת איפה נמצא צוואר הבקבוק. - -בכדי להשתמש בתיעוד ביצועים, אנו צריכים לזהות אילו חלקי קוד צריכים להיות מתועדים. אנו מסמנים את ההתחלה והסוף עבור כל חתיכת קוד על ידי הוספת המתודות הבאות: - -~~~ -[php] -Yii::beginProfile('blockID'); -...חתיכת הקוד שאנו רוצים לבצע עליו את תיעוד הביצועים.... -Yii::endProfile('blockID'); -~~~ - -`blockID` הינו שם יחודי המזהה את הקוד הספציפי הזה. - -יש לזכור, קטעי קוד צריכים להיות משורשרים בהיררכיה נכונה. זאת אומרת, קטע קוד לא יכול להצטלב עם קטע קוד אחר. הוא חייב להיות מקביל או תחום לגמרי על ידי קטע קוד אחר. - -בכדי להציג את תוצאות תיעוד הביצועים, אנו צריכים להתקין רכיב אפליקציה בשם [CLogRouter] אשר משתמש בניתוב מסוג [CProfileLogRoute]. זה דומה למה שאנו עושים עם ניתובי הודעות רגילים. הנתב [CProfileLogRoute] יציג את תוצאות הביצועים בסוף העמוד בדפדפן. - -### תיעוד ביצועי שאילתות SQL - -תיעוד ביצועים הוא שימושי במיוחד בעת השימוש במסד נתונים מאחר ושאילתות SQL הם הגורם העיקרי לצווארי בקבוק באפליקציה. אנו יכולים לציין באופן ידני ביטויים של `beginProfile` ו `endProfile` במקומות מסויימים בכדי למדוד את הזמן הנדרש עבור כל שאילתת SQL, החל מגרסא 1.0.6, Yii מספקת גישה יותר שיטתית כדי לפתור בעיה זו. - -על ידי הגדרת המאפיין [CDbConnection::enableProfiling] לערך השווה ל `true` בהגדרות האפליקציה, כל שאילתית SQL שתתבצע תתועד. ניתן להציג תוצאות אלו על ידי שימוש בנתב [CProfileLogRoute] שהצגנו קודם לכן, המציג לנו כמה זמן נמדד עבור כל שאילתת SQL. כמו כן אנו יכולים לקרוא למתודה [()CDbConnection::getStats] כדי לשלוף את מספר שאילתות SQL שבוצעו ואת הזמן הכולל שלקח לבצע אותם. - +תיעוד +======= + +Yii מספקת אפשרות לתיעוד גמישה ונרחבת. הודעות אשר מתועדות ניתנות לסיווג על פי רמות וקטגוריות. שימוש בפילטרים של רמות וקטגוריות, ניתן לנתב את ההודעות הנבחרות ליעדים שונים, כמו קבצים, אימיילים, תצוגה בחלונות בדפדפן וכדומה. + +תיעוד הודעות +--------------- + +ניתן לתעד הודעות על ידי קריאה ל [Yii::log] או [Yii::trace]. ההבדל בין שני המתודות הללו היא שהמתודה השנייה מתעדת הודעה רק בזמן שהאפליקציה נמצאת במצב של [ניפוי שגיאות](/doc/guide/basics.entry#debug-mode). + +~~~ +[php] +Yii::log($message, $level, $category); +Yii::trace($message, $category); +~~~ + +בעת תיעוד הודעה, אנו צריכים להגדיר את הקטגוריה והרמה שלו. קטגוריה הינה סטרינג בפורמט של `xxx.yyy.zzz` בדומה [שמות מקוצרים](/doc/guide/basics.namespace). לדוגמא, אם ההודעה מתועדת תחת [CController], אנו יכולים להשתמש בקטגוריה `system.web.CController`. רמת ההודעה צריכה להיות אחד מהערכים הבאים: + +- `trace`: זוהי הרמה הנמצאת בשימוש על ידי [Yii::trace]. היא נועדה לעקוב אחר תהליך יצירת האפליקציה בזמן הפיתוח. + +- `info`: רמה זו נועדה לתיעוד של הודעות כלליות. + +- `profile`: רמה זו נועדה לתיעוד של הודעות פרופיל, אשר נרחיב עליהם בהמשך. + +- `warning`: רמה זו נועדה לתיעוד של הודעות אזהרה. + +- `error`: רמה זו נועדה לתיעוד של הודעות שגיאה. + +ניתוב הודעות +--------------- + +הודעות המתועדות על ידי [Yii::log] או [Yii::trace] נשמרות בזיכרון. אנו בדרך כלל צריכים להציג אותם בדפדפן, או לשמור אותם במקום אחסון קבוע כמו קבצים, אימיילים. זה נקרא *ניתוב הודעות*, כלומר, שליחת ההודעות ליעד שונה. + +באפליקציות Yii, ניתוב הודעות מנוהל על ידי הרכיב [CLogRouter]. הוא מנהל סט של מה שנקרא *ניתוב תיעודים*. כל נתב מייצג יעד עבור ההודעות. ניתן לבצע פילטר על גבי הודעות המועברות בנתב על פי הרמות והקטגוריות שלהן. + +בכדי להשתמש בניתוב הודעות, אנו צריכים להתקין ולטעון מראש את רכיב האפליקציה [CLogRouter]. כמו כן אנו צריכים להגדיר את המאפיין [routes|CLogRouter::routes] עם הנתבים בהם אנו רוצים להשתמש. הקוד הבא מציג דוגמא [לקובץ הגדרות](/doc/guide/basics.application#application-configuration) אשר מוגדר להשתמש עם ניתוב הודעות: + +~~~ +[php] +array( + ...... + 'preload'=»array('log'), + 'components'=»array( + ...... + 'log'=»array( + 'class'=»'CLogRouter', + 'routes'=»array( + array( + 'class'=»'CFileLogRoute', + 'levels'=»'trace, info', + 'categories'=»'system.*', + ), + array( + 'class'=»'CEmailLogRoute', + 'levels'=»'error, warning', + 'emails'=»'admin@example.com', + ), + ), + ), + ), +) +~~~ + +בדוגמא למעלה, יש ברשותנו שני נתבים. הנתב הראשון הינו [CFileLogRoute] השומר את ההודעות בקובץ תחת התיקיה הזמנית באפליקציה. רק ההודעות שרמה שלהן היא `trace` או `info` ושהקטגוריה שלהם מתחילה ב `system` נשמרות. הנתב השני הינו [CEmailLogRoute] אשר שולח את ההודעות לכתובות האימיילים המוגדרות. רק ההודעות שהרמה שלהן היא `error` או `warning` נשלחות. + +ניתן להשתמש בסוגי הנתבים הבאים באפליקציות Yii: + + + + - [CDbLogRoute]: שומר את ההודעות בטבלה במסד הנתונים. + - [CEmailLogRoute]: שולח את ההודעות אל אימיילים שהוגדרו מראש. + - [CFileLogRoute]: שומר את ההודעות בקובץ תחת התיקיה הזמנית של האפליקציה. + - [CWebLogRoute]: מציג את ההודעות בסוף כל עמוד בדפדפן. + - [CProfileLogRoute]: מציג את הודעות הפרופיל בסוף כל עמוד בדפדפן. + +» Info|מידע: ניתוב הודעות מתבצע בסוף הבקשה הנוכחית כשהאירוע [onEndRequest|CApplication::onEndRequest] מתרחש. בכדי לעצור את טעינת הסקריפט וביצוע הבקשה במקום כלשהו, יש לקרוא למתודה [()CApplication::end] במקום `()die` או `()exit`, בגלל ש-[()CApplication::end] יטען את האירוע [onEndRequest|CApplication::onEndRequest] כדי שיהיה ניתן לתעד ולנתב את ההודעות כראוי. + +### סינון הודעות + +כפי שכבר ציינו, ניתן לסנן הודעות על פי הרמות והקטגוריות בהם הן נמצאות לפני שהן נשלחות לניתוב כלשהו. זה נעשה על ידי הגדרת המאפיינים [levels|CLogRoute::levels] ו [categories|CLogRoute::categories] של הנתב המדובר. מספר רב של רמות או קטגוריות צריכות להיות משורשרות (מחוברות אחת לשנייה) בעזרת פסיק ( , ). + +מאחר וקטגוריות הודעות הם בפורמט של `xxx.yyy.zzz`, אנו יכולים להתייחס אליהם כהיררכית קטגוריות. במיוחד, אנו אומרים ש `xxx` הוא האב של `xxx.yyy` שהוא האב של `xxx.yyy.zzz`. לכן אנו יכולים להשתמש ב-`*.xxx` בכדי לייצג את הקטגוריה `xxx` וכל תתי הקטגוריות הנמצאות תחתיו. + +### תיעוד מידע בהקשר מסויים + +החל מגרסא 1.0.6, אנו יכולים להגדיר לתעד מידע נוסף בהקשר מסויים, כמו לדוגמא משתנים מוגדרים מראש של PHP (כמו `GET_$` ו `SERVER_$`), מספר מזהה ב Session, שם משתמש וכדומה. זה נעשה על ידי הגדרת המאפיין [CLogRoute::filter] בנתב מסויים לפילטר המתאים. + +המערכת מגיעה עם מחלקה נוחה בשם [CLogFilter] שניתן להשתמש בה כפילטר הדרוש ברוב המקרים. כברירת מחדל, [CLogFilter] יתעד הודעות עם משתנים כמו `GET_$` ו `SERVER_$` אשר מכילים בדרך כלל מידע חשוב בהקשר למערכת. כמו כן, ניתן להגדיר את [CLogFilter] כדי שיספק קידומת עבור הודעות עם מספר מזהה יחודי כלשהו, שם משתמש וכדומה, אשר יכול לפשט בצורה משמעותית את החיפוש הגלובלי כשאנו בודקים את ההודעות המתועדות הרבות. + +ההגדרות הבאות מציגות כיצד להפעיל תיעוד מידע בהקשר מסויים. דע שכל נתב הודעות יכול להכיל את פילטר ההודעות שלו. וכברירת מחדל, נתב הודעות לא מכיל פילטר הודעות. + +~~~ +[php] +array( + ...... + 'preload'=»array('log'), + 'components'=»array( + ...... + 'log'=»array( + 'class'=»'CLogRouter', + 'routes'=»array( + array( + 'class'=»'CFileLogRoute', + 'levels'=»'error', + 'filter'=»'CLogFilter', + ), + ...נתבי הודעות נוספים... + ), + ), + ), +) +~~~ + +החל מגרסא 1.0.7, Yii מאפשרת לשמור את המיקום (קובץ ושורה) שההודעה תועדה עבור הודעות אשר מתועדות על ידי `Yii::trace`. אפשרות זו כבויה כברירת מחלד מאחר והיא משפיעה לרעה על ביצועי המערכת. בכדי להפעיל אפשרות זו, יש להגדיר פשוט משתנה גלובלי `YII_TRACE_LEVEL` בתחילת הסקריפט (בקובץ `index.php` לפני הטעינה של הקובץ `yii.php`) עם ערך הגדול מ-0. Yii תוסיף עבור כל הודעה מתועדת את הקובץ והשורה שבה ההודעה תועדה. המספר המוגדר בערך הגלובלי `YII_TRACE_LEVEL` הוא כדי שהמערכת תדע להציג את עומק הקריאה, זאת אומרת שאם ההודעה תועדה בקובץ שנטען שלישי מתחילת הסקריפט (מהרצת קובץ `index.php`) ואנו הגדרנו את המשתנה `YII_TRACE_LEVEL` למספר 2 רק שני הקבצים האחרונים בדרך להודעה יתועדו. מידע זה שימושי בעיקר בזמן פיתוח מאחר וזה יכול לעזור בזיהוי המקומות בהם ההודעה תועדה. + +שימוש במשתנה הגלובלי `YII_TRACE_LEVEL` נעשה בצורה הבאה: + +~~~ +[php] +define('YII_TRACE_LEVEL', 2); +~~~ + +תיעוד ביצועים (פרופיל) +--------------------- + +תיעוד ביצועים הינה אפשרות מיוחדת בתיעוד ההודעות. שימוש בתיעוד ביצועים נעשה בעיקר כדי למדוד את הזמן הדרוש עבור הרצת קוד מסויים בכדי לדעת איפה נמצא צוואר הבקבוק. + +בכדי להשתמש בתיעוד ביצועים, אנו צריכים לזהות אילו חלקי קוד צריכים להיות מתועדים. אנו מסמנים את ההתחלה והסוף עבור כל חתיכת קוד על ידי הוספת המתודות הבאות: + +~~~ +[php] +Yii::beginProfile('blockID'); +...חתיכת הקוד שאנו רוצים לבצע עליו את תיעוד הביצועים.... +Yii::endProfile('blockID'); +~~~ + +`blockID` הינו שם יחודי המזהה את הקוד הספציפי הזה. + +יש לזכור, קטעי קוד צריכים להיות משורשרים בהיררכיה נכונה. זאת אומרת, קטע קוד לא יכול להצטלב עם קטע קוד אחר. הוא חייב להיות מקביל או תחום לגמרי על ידי קטע קוד אחר. + +בכדי להציג את תוצאות תיעוד הביצועים, אנו צריכים להתקין רכיב אפליקציה בשם [CLogRouter] אשר משתמש בניתוב מסוג [CProfileLogRoute]. זה דומה למה שאנו עושים עם ניתובי הודעות רגילים. הנתב [CProfileLogRoute] יציג את תוצאות הביצועים בסוף העמוד בדפדפן. + +### תיעוד ביצועי שאילתות SQL + +תיעוד ביצועים הוא שימושי במיוחד בעת השימוש במסד נתונים מאחר ושאילתות SQL הם הגורם העיקרי לצווארי בקבוק באפליקציה. אנו יכולים לציין באופן ידני ביטויים של `beginProfile` ו `endProfile` במקומות מסויימים בכדי למדוד את הזמן הנדרש עבור כל שאילתת SQL, החל מגרסא 1.0.6, Yii מספקת גישה יותר שיטתית כדי לפתור בעיה זו. + +על ידי הגדרת המאפיין [CDbConnection::enableProfiling] לערך השווה ל `true` בהגדרות האפליקציה, כל שאילתית SQL שתתבצע תתועד. ניתן להציג תוצאות אלו על ידי שימוש בנתב [CProfileLogRoute] שהצגנו קודם לכן, המציג לנו כמה זמן נמדד עבור כל שאילתת SQL. כמו כן אנו יכולים לקרוא למתודה [()CDbConnection::getStats] כדי לשלוף את מספר שאילתות SQL שבוצעו ואת הזמן הכולל שלקח לבצע אותם. + «div class="revision"»$Id: topics.logging.txt 1147 2009-06-18 19:14:12Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/topics.performance.txt b/docs/guide/he/topics.performance.txt index 1ade227de..42107ed9d 100644 --- a/docs/guide/he/topics.performance.txt +++ b/docs/guide/he/topics.performance.txt @@ -1,111 +1,111 @@ -שיפור ביצועי המערכת -================== - -ביצועי אפליקצית ווב מושפעים על ידי גורמים רבים. גישה למסד נתונים, פעולות הקשורות לקבצים, תעבורת שרת כל אלו הם גורמים פוטנציאלים. Yii מנסה בכל ההיבטים להוריד את ההשפעה על ביצועי האפליקציה הנגרמים על ידי הפריימוורק. אך עדיין, ישנם המון מקומות באפליקצית המשתמש שניתן לשפר בכדי להעצים את ביצועי המערכת. - -הפעלת תוסף APC ----------------------- - -הפעלת התוסף [APC](http://www.php.net/manual/en/book.apc.php) בשרת היא כנראה הדרך הקלה ביותר לשיפור הביצועים של אפליקציה. התוסף מבצע אופטימיזציה של קוד ה PHP ושומר אותו במטמון ובכך מונע את הזמן הדרוש לעיבוד קוד ה PHP עבור כל בקשה נכנסת. - -כיבוי אפשרות ניפוי השגיאות --------------------- - -כיבוי אפשרות ניפוי השגיאות הינה דרך קלה נוספת לשיפור הביצועים. אפליקציה רצה במצב של ניפוי שגיאות במידה והמשתנה הקבוע `YII_DEBUG` מוגדר כ true. מצב ניפוי שגיאות הינו שימוש בזמן תהליך הפיתוח, אך זה ישפיע לרעה על ביצועי האפליקציה מאחר וכמה רכיבים גורמים לנטל נוסף במצב של ניפוי שגיאות. לדוגמא, מערכת התיעודים במערכת שומרת תיעודים נוספים עבור כל הודעה הנשמרת בתיעוד במצב של ניפוי שגיאות. - -שימוש ב `yiilite.php` -------------------- - -במידה והתוסף [APC](http://www.php.net/manual/en/book.apc.php) מופעל, אנו יכולים להחליף את הקובץ `yii.php` בקובץ הרצה ראשוני אחר בשם `yiilite.php` בכדי לשפר אף יותר את ביצועי האפליקציה הרצה על גבי מערכת ה Yii. - -הקובץ `yiilite.php` מגיע עם כל גרסא של Yii. קובץ זה הוא התוצאה של איחוד כמה מחלקות נפוצות לקובץ אחת. ההערות והודעות התיעוד הוסרו מקובץ מאוחד זה. לכן, שימוש ב `yiilite.php` יקטין את מספר הקבצים המתווספים במהלך הרצת האפליקציה וימנע הרצה של פקודות המתעדות פעולות את הפעולות הנעשות באפליקציה. - -» Note|הערה: שימוש ב `yiilite.php` ללא APC יכול למעשה להשפיע על ביצועי המערכת **לרעה**, מאחר ו `yiilite.php` מכיל מחלקות שלמעשה לא משומשות בכל בקשה ויהיה צורך בזמן עיבוד נוסף ומיותר. -כמו כן, נראו מקרים בהם השימוש ב `yiilite.php` אובחן להיות איטי יותר בהגדרות שונות על שרתים שונים, גם כשהתוסף APC מופעל. הדרך הטובה ביותר לשפוט במידה וכדאי להשתמש ב `yiilite.php` או לא היא על ידי הרצת בדיקת עומס על גבי הדוגמא של `hello world` המצורפת למערכת. - -שימוש בטכניקות מטמון ------------------------- - -כפי שמדובר בחלק אודות [מטמון](/doc/guide/caching.overview), Yii מספק פתרונות מטמון שונים שיכולים לשפר את הביצועים של האפליקציה בצורה משמעותית. במידה ויצירת תוכן מסויים דורש זמן רב, אנו יכולים להשתמש בגישה של [מטמון נתונים](/doc/guide/caching.data) בכדי להפחית את תדירות יצירת הנתונים; במידה וחלק מהעמוד נשאר סטטי, אנו יכולים להשתמש [במטמון בחלקים](/doc/guide/caching.fragment) בכדי להפחית את תדירות עיבוד התצוגה; במידה וכל העמוד נשאר סטטי, אנו יכולים להשתמש [במטמון עמודים](/doc/guide/caching.page) בכדי להפחית ולחסוך בתדירות עיבוד התצוגה של העמוד כולו. - -במידה והאפליקציה משתמשת ב [Active Record](/doc/guide/database.ar), אנו יכולים להפעיל את המטמון עבור תרשים מסד הנתונים בכדי לשמור את התרשים (שבדרך כלל לא משתנה) במטמון ולחסוך את זמן עיבוד תרשים מסד הנתונים בכל בקשה. ניתן לבצע זאת על ידי הגדרת המאפיין [CDbConnection::schemaCachingDuration] לערך הגדול מ 0. - -מלבד שיטות המטמון שהצגנו כרגע שהם ברמת האפליקציה, אנו יכולים להשתמש במטמון ברמת השרת בכדי להאיץ את האפליקציה עוד יותר. בעצם, התוסף [APC](/doc/guide/topics.performance#enabling-apc-extension) שתארנו לגביו קודם לכן שייך לקטגוריה זו. -ישנם עוד שיטות לשימוש במטמון בצד השרת כמו, [Zend Optimizer](http://Zend.com/ZendOptimizer), -[eAccelerator](http://eaccelerator.net/), -[Squid](http://www.squid-cache.org/) ואחרים. - -אופטימיזצית מסד הנתונים ---------------------- - -שליפת מידע ממסד נתונים הוא בדרך כלל צוואר הבקבוק באפליקצית ווב. למרות ששימוש במטמון יכול להקל על הפגיעה בביצועים, זה אינו פותר את הבעיה לחלוטין. ברגע שמסד הנתונים מכיל כמות עצומה של נתונים והתוכן השמור במטמון תוקפו פג ויש לחדשו, שליפת הנתונים האחרונים ממסד הנתונים יכול לקחת זמן רב ללא עיצוב נכון של מסד הנתונים והשאילתה. - -יש לעצב את האינדקסים במסד הנתונים בצורה נבונה. אינדוקס יכול לזרז שאילתות `SELECT` בצורה משמעותית, אך זה יכול להאט שאילתות `INSERT` , `UPDATE`, או `DELETE`. - -עבור שאילתות מורכבות, מומלץ ליצור טבלת VIEW במסד הנתונים במקום לבצע את השאילתות בקוד ה PHP ולבקש מה DBMS (מסד הנתונים) לעבד אותם שוב ושוב. - -אין להשתמש יותר מדי ב [Active Record](/doc/guide/database.ar). למרות ש [Active Record](/doc/guide/database.ar) הוא שימוש בעיצוב הנתונים באופן מונחה עצמים (OOP), הוא למעשה פוגע לרעה בביצועי המערכת מאחר והוא יוצר אובייקט אחד או יותר המייצג כל שורה בתוצאות השאילתה. עבור אפליקציות המכילות מידע רב, שימוש ב [DAO](/doc/guide/database.dao) או API של מסדי נתונים ברמה נמוכה יותר יכולה להיות בחירה טובה יותר. - -לבסוף, יש להשתמש ב `LIMIT` בשאילתות `SELECT`. זה מונע שליפה אדירה של נתונים בבת אחת ממסד הנתונים ובזבוז זכרון המערכת המוקצה ל PHP. - -צמצום מספר קבצי ספריט ------------------------ - -עמודים מורכבים בדרך כלל צריכים לכלול הרבה קבצי JS ו CSS חיצוניים. מאחר וכל קובץ נוסף יגרום לבקשה הלוך חזור לשרת, אנו צריכים לצמצם את מספר קבצי הסקריפט החיצוניים על ידי איחוד של כולם לקובץ אחד. אנו גם צריכים לשקול להקטין את גודל כל קובץ בכדי להפחית מהזמן הנדרש לשלוח ולקבלו מהשרת. ישנם כלים רבים מסביב שיכולים לעזור בשני היטבים אלו. - -עבור עמוד הנוצר על ידי Yii, סביר להניח שישנם קבצי סקריפט המוצגים על ידי רכיבים שאנו לא רוצים לערוך (לדוגמא, רכיבים בסיסיים במערכת ה Yii, רכיבי צד-שלישי). בכדי לצמצם את מספר קבצי הסקריפט הללו, אנו צריכים לבצע שני דברים. - -» Note|הערה: האפשרות של `scriptMap` שאנו נתאר בחלק הבא קיימת החל מגרסא 1.0.3. - -קודם כל, אנו מגדירים את הסקריפטים שאנו רוצים לצמצם על ידי הגדרת המאפיין [scriptMap|CClientScript::scriptMap] של רכיב המחלקה [clientScript|CWebApplication::clientScript]. -ניתן לבצע זאת בהגדרות האפליקציה או בקוד. לדוגמא, - -~~~ -[php] -$cs=Yii::app()-»clientScript; -$cs-»scriptMap=array( - 'jquery.js'=»'/js/all.js', - 'jquery.ajaxqueue.js'=»'/js/all.js', - 'jquery.metadata.js'=»'/js/all.js', - ...... -); -~~~ - -מה שהקוד למעלה עושה הוא ממפה את קבצי הסקריפט הרשומים למעלה לקישור `js/all.js/`. במידה ואחד מקבצי הסקריפט הרשומים למעלה יצורף על ידי רכיב כלשהו, Yii יצרף את הקישור (פעם אחת) במקום את הקישור של כל אחד מהסקריפטים. - -שנית, אנו צריכים להשתמש בכלי כלשהו לאיחוד (ואולי גם כיווץ התוכן) קבצי הסקריפט לקובץ סקריפט אחד ולשמור אותו כ `js/all.js`. - -» Note|הערה: יש לבצע את האיחוד והכיווץ ידנית, נכון לעכשיו Yii לא מאפשרת לעשות זאת תחת הרכיבים שלה. - -אפשרות זו ניתנת לביצוע גם על גבי קבצי CSS. - -כמו כן אנו יכולים לשפר את מהירות טעינת העמוד בעזרת [Google AJAX Libraries API](http://code.google.com/apis/ajaxlibs/). לדוגמא, אנו יכולים לצרף את `jquery.js` מהשרתים של Google במקום לצרף את אותו הקובץ מהשרתים שלנו. בכדי לעשות זאת, אנו קודם צריכים להגדיר את המאפיין `scriptMap` בצורה הבאה, - -~~~ -[php] -$cs=Yii::app()-»clientScript; -$cs-»scriptMap=array( - 'jquery.js'=»false, - 'jquery.ajaxqueue.js'=»false, - 'jquery.metadata.js'=»false, - ...... -); -~~~ - -על ידי מיפוי קבצים אלו ל false, אנו מונעים מ Yii מליצור את הקוד המצרף קבצים אלו. במקום, אנו כותבים את הקוד הבא בעמודים שלנו בכדי לצרף את הסקריפטים אחד אחד מהשרתים של Google, - -~~~ -[php] -«head» -«?php echo CGoogleApi::init(); ?» - -«?php echo CHtml::script( - CGoogleApi::load('jquery','1.3.2') . "\n" . - CGoogleApi::load('jquery.ajaxqueue.js') . "\n" . - CGoogleApi::load('jquery.metadata.js') -); ?» -...... -«/head» -~~~ - +שיפור ביצועי המערכת +================== + +ביצועי אפליקצית ווב מושפעים על ידי גורמים רבים. גישה למסד נתונים, פעולות הקשורות לקבצים, תעבורת שרת כל אלו הם גורמים פוטנציאלים. Yii מנסה בכל ההיבטים להוריד את ההשפעה על ביצועי האפליקציה הנגרמים על ידי הפריימוורק. אך עדיין, ישנם המון מקומות באפליקצית המשתמש שניתן לשפר בכדי להעצים את ביצועי המערכת. + +הפעלת תוסף APC +---------------------- + +הפעלת התוסף [APC](http://www.php.net/manual/en/book.apc.php) בשרת היא כנראה הדרך הקלה ביותר לשיפור הביצועים של אפליקציה. התוסף מבצע אופטימיזציה של קוד ה PHP ושומר אותו במטמון ובכך מונע את הזמן הדרוש לעיבוד קוד ה PHP עבור כל בקשה נכנסת. + +כיבוי אפשרות ניפוי השגיאות +-------------------- + +כיבוי אפשרות ניפוי השגיאות הינה דרך קלה נוספת לשיפור הביצועים. אפליקציה רצה במצב של ניפוי שגיאות במידה והמשתנה הקבוע `YII_DEBUG` מוגדר כ true. מצב ניפוי שגיאות הינו שימוש בזמן תהליך הפיתוח, אך זה ישפיע לרעה על ביצועי האפליקציה מאחר וכמה רכיבים גורמים לנטל נוסף במצב של ניפוי שגיאות. לדוגמא, מערכת התיעודים במערכת שומרת תיעודים נוספים עבור כל הודעה הנשמרת בתיעוד במצב של ניפוי שגיאות. + +שימוש ב `yiilite.php` +------------------- + +במידה והתוסף [APC](http://www.php.net/manual/en/book.apc.php) מופעל, אנו יכולים להחליף את הקובץ `yii.php` בקובץ הרצה ראשוני אחר בשם `yiilite.php` בכדי לשפר אף יותר את ביצועי האפליקציה הרצה על גבי מערכת ה Yii. + +הקובץ `yiilite.php` מגיע עם כל גרסא של Yii. קובץ זה הוא התוצאה של איחוד כמה מחלקות נפוצות לקובץ אחת. ההערות והודעות התיעוד הוסרו מקובץ מאוחד זה. לכן, שימוש ב `yiilite.php` יקטין את מספר הקבצים המתווספים במהלך הרצת האפליקציה וימנע הרצה של פקודות המתעדות פעולות את הפעולות הנעשות באפליקציה. + +» Note|הערה: שימוש ב `yiilite.php` ללא APC יכול למעשה להשפיע על ביצועי המערכת **לרעה**, מאחר ו `yiilite.php` מכיל מחלקות שלמעשה לא משומשות בכל בקשה ויהיה צורך בזמן עיבוד נוסף ומיותר. +כמו כן, נראו מקרים בהם השימוש ב `yiilite.php` אובחן להיות איטי יותר בהגדרות שונות על שרתים שונים, גם כשהתוסף APC מופעל. הדרך הטובה ביותר לשפוט במידה וכדאי להשתמש ב `yiilite.php` או לא היא על ידי הרצת בדיקת עומס על גבי הדוגמא של `hello world` המצורפת למערכת. + +שימוש בטכניקות מטמון +------------------------ + +כפי שמדובר בחלק אודות [מטמון](/doc/guide/caching.overview), Yii מספק פתרונות מטמון שונים שיכולים לשפר את הביצועים של האפליקציה בצורה משמעותית. במידה ויצירת תוכן מסויים דורש זמן רב, אנו יכולים להשתמש בגישה של [מטמון נתונים](/doc/guide/caching.data) בכדי להפחית את תדירות יצירת הנתונים; במידה וחלק מהעמוד נשאר סטטי, אנו יכולים להשתמש [במטמון בחלקים](/doc/guide/caching.fragment) בכדי להפחית את תדירות עיבוד התצוגה; במידה וכל העמוד נשאר סטטי, אנו יכולים להשתמש [במטמון עמודים](/doc/guide/caching.page) בכדי להפחית ולחסוך בתדירות עיבוד התצוגה של העמוד כולו. + +במידה והאפליקציה משתמשת ב [Active Record](/doc/guide/database.ar), אנו יכולים להפעיל את המטמון עבור תרשים מסד הנתונים בכדי לשמור את התרשים (שבדרך כלל לא משתנה) במטמון ולחסוך את זמן עיבוד תרשים מסד הנתונים בכל בקשה. ניתן לבצע זאת על ידי הגדרת המאפיין [CDbConnection::schemaCachingDuration] לערך הגדול מ 0. + +מלבד שיטות המטמון שהצגנו כרגע שהם ברמת האפליקציה, אנו יכולים להשתמש במטמון ברמת השרת בכדי להאיץ את האפליקציה עוד יותר. בעצם, התוסף [APC](/doc/guide/topics.performance#enabling-apc-extension) שתארנו לגביו קודם לכן שייך לקטגוריה זו. +ישנם עוד שיטות לשימוש במטמון בצד השרת כמו, [Zend Optimizer](http://Zend.com/ZendOptimizer), +[eAccelerator](http://eaccelerator.net/), +[Squid](http://www.squid-cache.org/) ואחרים. + +אופטימיזצית מסד הנתונים +--------------------- + +שליפת מידע ממסד נתונים הוא בדרך כלל צוואר הבקבוק באפליקצית ווב. למרות ששימוש במטמון יכול להקל על הפגיעה בביצועים, זה אינו פותר את הבעיה לחלוטין. ברגע שמסד הנתונים מכיל כמות עצומה של נתונים והתוכן השמור במטמון תוקפו פג ויש לחדשו, שליפת הנתונים האחרונים ממסד הנתונים יכול לקחת זמן רב ללא עיצוב נכון של מסד הנתונים והשאילתה. + +יש לעצב את האינדקסים במסד הנתונים בצורה נבונה. אינדוקס יכול לזרז שאילתות `SELECT` בצורה משמעותית, אך זה יכול להאט שאילתות `INSERT` , `UPDATE`, או `DELETE`. + +עבור שאילתות מורכבות, מומלץ ליצור טבלת VIEW במסד הנתונים במקום לבצע את השאילתות בקוד ה PHP ולבקש מה DBMS (מסד הנתונים) לעבד אותם שוב ושוב. + +אין להשתמש יותר מדי ב [Active Record](/doc/guide/database.ar). למרות ש [Active Record](/doc/guide/database.ar) הוא שימוש בעיצוב הנתונים באופן מונחה עצמים (OOP), הוא למעשה פוגע לרעה בביצועי המערכת מאחר והוא יוצר אובייקט אחד או יותר המייצג כל שורה בתוצאות השאילתה. עבור אפליקציות המכילות מידע רב, שימוש ב [DAO](/doc/guide/database.dao) או API של מסדי נתונים ברמה נמוכה יותר יכולה להיות בחירה טובה יותר. + +לבסוף, יש להשתמש ב `LIMIT` בשאילתות `SELECT`. זה מונע שליפה אדירה של נתונים בבת אחת ממסד הנתונים ובזבוז זכרון המערכת המוקצה ל PHP. + +צמצום מספר קבצי ספריט +----------------------- + +עמודים מורכבים בדרך כלל צריכים לכלול הרבה קבצי JS ו CSS חיצוניים. מאחר וכל קובץ נוסף יגרום לבקשה הלוך חזור לשרת, אנו צריכים לצמצם את מספר קבצי הסקריפט החיצוניים על ידי איחוד של כולם לקובץ אחד. אנו גם צריכים לשקול להקטין את גודל כל קובץ בכדי להפחית מהזמן הנדרש לשלוח ולקבלו מהשרת. ישנם כלים רבים מסביב שיכולים לעזור בשני היטבים אלו. + +עבור עמוד הנוצר על ידי Yii, סביר להניח שישנם קבצי סקריפט המוצגים על ידי רכיבים שאנו לא רוצים לערוך (לדוגמא, רכיבים בסיסיים במערכת ה Yii, רכיבי צד-שלישי). בכדי לצמצם את מספר קבצי הסקריפט הללו, אנו צריכים לבצע שני דברים. + +» Note|הערה: האפשרות של `scriptMap` שאנו נתאר בחלק הבא קיימת החל מגרסא 1.0.3. + +קודם כל, אנו מגדירים את הסקריפטים שאנו רוצים לצמצם על ידי הגדרת המאפיין [scriptMap|CClientScript::scriptMap] של רכיב המחלקה [clientScript|CWebApplication::clientScript]. +ניתן לבצע זאת בהגדרות האפליקציה או בקוד. לדוגמא, + +~~~ +[php] +$cs=Yii::app()-»clientScript; +$cs-»scriptMap=array( + 'jquery.js'=»'/js/all.js', + 'jquery.ajaxqueue.js'=»'/js/all.js', + 'jquery.metadata.js'=»'/js/all.js', + ...... +); +~~~ + +מה שהקוד למעלה עושה הוא ממפה את קבצי הסקריפט הרשומים למעלה לקישור `js/all.js/`. במידה ואחד מקבצי הסקריפט הרשומים למעלה יצורף על ידי רכיב כלשהו, Yii יצרף את הקישור (פעם אחת) במקום את הקישור של כל אחד מהסקריפטים. + +שנית, אנו צריכים להשתמש בכלי כלשהו לאיחוד (ואולי גם כיווץ התוכן) קבצי הסקריפט לקובץ סקריפט אחד ולשמור אותו כ `js/all.js`. + +» Note|הערה: יש לבצע את האיחוד והכיווץ ידנית, נכון לעכשיו Yii לא מאפשרת לעשות זאת תחת הרכיבים שלה. + +אפשרות זו ניתנת לביצוע גם על גבי קבצי CSS. + +כמו כן אנו יכולים לשפר את מהירות טעינת העמוד בעזרת [Google AJAX Libraries API](http://code.google.com/apis/ajaxlibs/). לדוגמא, אנו יכולים לצרף את `jquery.js` מהשרתים של Google במקום לצרף את אותו הקובץ מהשרתים שלנו. בכדי לעשות זאת, אנו קודם צריכים להגדיר את המאפיין `scriptMap` בצורה הבאה, + +~~~ +[php] +$cs=Yii::app()-»clientScript; +$cs-»scriptMap=array( + 'jquery.js'=»false, + 'jquery.ajaxqueue.js'=»false, + 'jquery.metadata.js'=»false, + ...... +); +~~~ + +על ידי מיפוי קבצים אלו ל false, אנו מונעים מ Yii מליצור את הקוד המצרף קבצים אלו. במקום, אנו כותבים את הקוד הבא בעמודים שלנו בכדי לצרף את הסקריפטים אחד אחד מהשרתים של Google, + +~~~ +[php] +«head» +«?php echo CGoogleApi::init(); ?» + +«?php echo CHtml::script( + CGoogleApi::load('jquery','1.3.2') . "\n" . + CGoogleApi::load('jquery.ajaxqueue.js') . "\n" . + CGoogleApi::load('jquery.metadata.js') +); ?» +...... +«/head» +~~~ + «div class="revision"»$Id: topics.performance.txt 1622 2009-12-26 20:56:05Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/topics.prado.txt b/docs/guide/he/topics.prado.txt index b2933546d..d4bf8e3bd 100644 --- a/docs/guide/he/topics.prado.txt +++ b/docs/guide/he/topics.prado.txt @@ -1,185 +1,185 @@ -שימוש בתחביר תצוגה נוסף -================================= - -Yii מאפשרת למפתחים להשתמש בתחביר התצוגה המועדף עליהם (לדוגמא Prado, Smarty וכדומה) בכדי לכתוב את קבצי התצוגה של הקונטרולר או הוידג'ט. זה נעשה על ידי כתיבת והתקנת רכיב אפליקציה [viewRenderer|CWebApplication::viewRenderer]. מעבד התצוגה (הרנדרר) מיירט את הקריאות של [CBaseController::renderFile], מקמפל את קובץ התצוגה עם תחביר תצוגה מותאם אישית, ומציג (מרנדרר) את התוצאה המקומפלת. - -» Info|מידע: מומלץ להשתמש בתחביר תצוגה מותאם אישית רק כאשר כותבים קבצי תצוגה שסביר להניח שלא יעשה בהם שימוש חוזר. אחרת, משתמשים אשר משתמשים בקבצי התצוגה יהיו מוכרחים לעבוד עם אותו תחביר תצוגה באפליקציות שלהם. - -בחלק הבא, אנו מציגים כיצד להשתמש [CPradoViewRenderer], מעבד תצוגה (רנדרר) אשר מאפשר למפתחים להשתמש בתחביר תצוגה הדומה לזה שנמצא ב ‎[Prado](http://www.pradosoft.com/). עבור מפתחים הרוצים לכתוב את מעבד התצוגה שלהם, ניתן להשתמש ב [CPradoViewRenderer] כמחלקת עזר. - -שימש ב `CPradoViewRenderer` --------------------------- - -בכדי להשתמש ב [CPradoViewRenderer], אנו צריכים להגדיר את האפליקציה בצורה הבאה: - -~~~ -[php] -return array( - 'components'=»array( - ......, - 'viewRenderer'=»array( - 'class'=»'CPradoViewRenderer', - ), - ), -); -~~~ - -כברירת מחדל, [CPradoViewRenderer] מקמפל את קוד המקור של קבצי התצוגה ושומר את תוצאת הקימפול בקובץ PHP תחת התיקיה [הזמנית](/doc/guide/basics.convention#directory). -רק כאשר קובץ המקור של התצוגה משתנה, קבצי ה PHP השמורים בתיקיה הזמנית יווצרו מחדש. לכן, שימוש ב [CPradoViewRenderer] כמעט ולא משפיע לרעה על ביצועי המערכת. - -» Tip|טיפ: למרות ש [CPradoViewRenderer] מציג בעיקר תגיות תחביר חדשות עבור קבצי התצוגה בכדי לפשט ולזרז את כתיבתם, ניתן עדיין להשתמש בקוד PHP בתוך קבצי התצוגה כפי שבדרך כלל נעשה. - -בחלק הבא, אנו נציג את התגיות החדשות הנתמכות על ידי [CPradoViewRenderer]. - -### תגיות PHP קצרות - -תגיות PHP קצרות הם קיצורי דרך לכתיבת ביטויים ב PHP בתוך קבצי התצוגה. התגית - - -~~~ -«%= expression %» - -~~~ - - מתורגמת ל - - -~~~ - -«?php echo expression ?» - -~~~ - - - בזמן שהתגית - - -~~~ - -«% statement %» - -~~~ - - - מתורגמת ל - -~~~ - -«?php statement ?» -~~~ - - - לדוגמא, - -~~~ -[php] -«%= CHtml::textField($name,'value'); %» -«% foreach($models as $model): %» -~~~ - -מתורגם לקוד ה PHP הבא - -~~~ -[php] -«?php echo CHtml::textField($name,'value'); ?» -«?php foreach($models as $model): ?» -~~~ - -### תגיות רכיבים - -תגיות רכיבים נועדו לשימוש בעיקר בכדי להכניס [וידג'ט](/doc/guide/basics.view#widget) לקובץ התצוגה. תגית זו משתמשת בתחביר הבא: - -~~~ -[php] -«com:WidgetClass property1=value1 property2=value2 ...» - // תוכן עבור הוידג'ט -«/com:WidgetClass» - -// וידג'ט ללא תוכן -«com:WidgetClass property1=value1 property2=value2 .../» -~~~ - -כש `WidgetClass` מייצגת את שם המחלקה של הוידג'ט או [הנתיב המקוצר אליה](/doc/guide/basics.namespace), והערכים ההתחלתיים של מאפייני המחלקה יכולים להיות עטופים במרכאות או ביטויים ב PHP אשר עטופים ב סוגריים מסולסלות. לדוגמא, - -~~~ -[php] -«com:CCaptcha captchaAction="captcha" showRefreshButton={false} /» -~~~ - -יתורגם לקוד ה PHP הבא - -~~~ -[php] -«?php $this-»widget('CCaptcha', array( - 'captchaAction'=»'captcha', - 'showRefreshButton'=»false)); ?» -~~~ - -» Note|הערה: הערך עבור ה מוגדר כ `{false}` במקום `"false"` מאחר והשני מעיד על סטרינג במקום ערך בוליאני. - -### תגיות מטמון - -תגיות מטמון הם קיצור לשימוש [במטמון בחלקים](/doc/guide/caching.fragment). התחביר שלו הוא כמו בדוגמא הבאה, - -~~~ -[php] -«cache:fragmentID property1=value1 property2=value2 ...» - // content being cached -«/cache:fragmentID » -~~~ - -כש `fragmentID` צריך להיות המזהה היחודי של המטמון שמזהה יחודית את התוכן הנשמר במטמון, וזוגות המאפיינים אחריו נועדו להגדרת המאפיינים ההתחלתיים של המטמון. לדוגמא, - -~~~ -[php] -«cache:profile duration={3600}» - // פרופיל משתמש כאן -«/cache:profile » -~~~ - -יתורגם לקוד ה PHP הבא - -~~~ -[php] -«?php if($this-»cache('profile', array('duration'=»3600))): ?» - // פרופיל משתמש כאן -«?php $this-»endCache(); endif; ?» -~~~ - -### תגיות קליפ - -כמו בתגיות המטמון, תגיות קליפ הינם קיצור דרך לקריאה של [CBaseController::beginClip] ו [CBaseController::endClip] בקובץ תצוגה. התחביר שלהם הוא כפי שמוצג בדוגמא הבאה, - -~~~ -[php] -«clip:clipID» - // תוכן הקליפ -«/clip:clipID » -~~~ - -כש `clipID` הוא מזהה יחודי עבור תוכן הקליפ. תגיות הקליפ יתורגמו לקוד ה PHP הבא - -~~~ -[php] -«?php $this-»beginClip('clipID'); ?» - // תוכן הקליפ -«?php $this-»endClip(); ?» -~~~ - -### תגיות הערות - -תגיות הערות נועדו לכתיבת הערות בתצוגה שיש להציג אותם רק למפתחים. תגיות הערות אלו יוסרו כשקובץ התצוגה יוצג למשתמשי הקצה. התחביר עבור תגיות הערות הוא כבדוגמא הבאה, - -~~~ -[php] -«!--- -הערות שלא יוצגו למשתמשי הקצה ----» -~~~ - -ערבוב פורמט תבניות ------------------------ - -החל מגרסא 1.1.2, ניתן לערבב שימוש של כמה תחבירי תבניות עם תחביר PHP רגיל. בכדי לעשות זאת, המאפיין [CViewRenderer::fileExtension] של מעבד התבניות המותקן כרגע צריך להיות מוגדר עם ערך השונה מ `php.`. לדוגמא, אם המאפיין מוגדר לערך `tpl.`, אז כל קובץ תצוגה המסתיים ב `tpl.` יועבד ויוצג בעזרת מעבד התבניות המותקן, בזמן שכל שאר קבצי התצוגה המסתיימים עם `php.` יקבלו יחס של קבצי PHP רגילים. - +שימוש בתחביר תצוגה נוסף +================================= + +Yii מאפשרת למפתחים להשתמש בתחביר התצוגה המועדף עליהם (לדוגמא Prado, Smarty וכדומה) בכדי לכתוב את קבצי התצוגה של הקונטרולר או הוידג'ט. זה נעשה על ידי כתיבת והתקנת רכיב אפליקציה [viewRenderer|CWebApplication::viewRenderer]. מעבד התצוגה (הרנדרר) מיירט את הקריאות של [CBaseController::renderFile], מקמפל את קובץ התצוגה עם תחביר תצוגה מותאם אישית, ומציג (מרנדרר) את התוצאה המקומפלת. + +» Info|מידע: מומלץ להשתמש בתחביר תצוגה מותאם אישית רק כאשר כותבים קבצי תצוגה שסביר להניח שלא יעשה בהם שימוש חוזר. אחרת, משתמשים אשר משתמשים בקבצי התצוגה יהיו מוכרחים לעבוד עם אותו תחביר תצוגה באפליקציות שלהם. + +בחלק הבא, אנו מציגים כיצד להשתמש [CPradoViewRenderer], מעבד תצוגה (רנדרר) אשר מאפשר למפתחים להשתמש בתחביר תצוגה הדומה לזה שנמצא ב ‎[Prado](http://www.pradosoft.com/). עבור מפתחים הרוצים לכתוב את מעבד התצוגה שלהם, ניתן להשתמש ב [CPradoViewRenderer] כמחלקת עזר. + +שימש ב `CPradoViewRenderer` +-------------------------- + +בכדי להשתמש ב [CPradoViewRenderer], אנו צריכים להגדיר את האפליקציה בצורה הבאה: + +~~~ +[php] +return array( + 'components'=»array( + ......, + 'viewRenderer'=»array( + 'class'=»'CPradoViewRenderer', + ), + ), +); +~~~ + +כברירת מחדל, [CPradoViewRenderer] מקמפל את קוד המקור של קבצי התצוגה ושומר את תוצאת הקימפול בקובץ PHP תחת התיקיה [הזמנית](/doc/guide/basics.convention#directory). +רק כאשר קובץ המקור של התצוגה משתנה, קבצי ה PHP השמורים בתיקיה הזמנית יווצרו מחדש. לכן, שימוש ב [CPradoViewRenderer] כמעט ולא משפיע לרעה על ביצועי המערכת. + +» Tip|טיפ: למרות ש [CPradoViewRenderer] מציג בעיקר תגיות תחביר חדשות עבור קבצי התצוגה בכדי לפשט ולזרז את כתיבתם, ניתן עדיין להשתמש בקוד PHP בתוך קבצי התצוגה כפי שבדרך כלל נעשה. + +בחלק הבא, אנו נציג את התגיות החדשות הנתמכות על ידי [CPradoViewRenderer]. + +### תגיות PHP קצרות + +תגיות PHP קצרות הם קיצורי דרך לכתיבת ביטויים ב PHP בתוך קבצי התצוגה. התגית + + +~~~ +«%= expression %» + +~~~ + + מתורגמת ל + + +~~~ + +«?php echo expression ?» + +~~~ + + + בזמן שהתגית + + +~~~ + +«% statement %» + +~~~ + + + מתורגמת ל + +~~~ + +«?php statement ?» +~~~ + + + לדוגמא, + +~~~ +[php] +«%= CHtml::textField($name,'value'); %» +«% foreach($models as $model): %» +~~~ + +מתורגם לקוד ה PHP הבא + +~~~ +[php] +«?php echo CHtml::textField($name,'value'); ?» +«?php foreach($models as $model): ?» +~~~ + +### תגיות רכיבים + +תגיות רכיבים נועדו לשימוש בעיקר בכדי להכניס [וידג'ט](/doc/guide/basics.view#widget) לקובץ התצוגה. תגית זו משתמשת בתחביר הבא: + +~~~ +[php] +«com:WidgetClass property1=value1 property2=value2 ...» + // תוכן עבור הוידג'ט +«/com:WidgetClass» + +// וידג'ט ללא תוכן +«com:WidgetClass property1=value1 property2=value2 .../» +~~~ + +כש `WidgetClass` מייצגת את שם המחלקה של הוידג'ט או [הנתיב המקוצר אליה](/doc/guide/basics.namespace), והערכים ההתחלתיים של מאפייני המחלקה יכולים להיות עטופים במרכאות או ביטויים ב PHP אשר עטופים ב סוגריים מסולסלות. לדוגמא, + +~~~ +[php] +«com:CCaptcha captchaAction="captcha" showRefreshButton={false} /» +~~~ + +יתורגם לקוד ה PHP הבא + +~~~ +[php] +«?php $this-»widget('CCaptcha', array( + 'captchaAction'=»'captcha', + 'showRefreshButton'=»false)); ?» +~~~ + +» Note|הערה: הערך עבור ה מוגדר כ `{false}` במקום `"false"` מאחר והשני מעיד על סטרינג במקום ערך בוליאני. + +### תגיות מטמון + +תגיות מטמון הם קיצור לשימוש [במטמון בחלקים](/doc/guide/caching.fragment). התחביר שלו הוא כמו בדוגמא הבאה, + +~~~ +[php] +«cache:fragmentID property1=value1 property2=value2 ...» + // content being cached +«/cache:fragmentID » +~~~ + +כש `fragmentID` צריך להיות המזהה היחודי של המטמון שמזהה יחודית את התוכן הנשמר במטמון, וזוגות המאפיינים אחריו נועדו להגדרת המאפיינים ההתחלתיים של המטמון. לדוגמא, + +~~~ +[php] +«cache:profile duration={3600}» + // פרופיל משתמש כאן +«/cache:profile » +~~~ + +יתורגם לקוד ה PHP הבא + +~~~ +[php] +«?php if($this-»cache('profile', array('duration'=»3600))): ?» + // פרופיל משתמש כאן +«?php $this-»endCache(); endif; ?» +~~~ + +### תגיות קליפ + +כמו בתגיות המטמון, תגיות קליפ הינם קיצור דרך לקריאה של [CBaseController::beginClip] ו [CBaseController::endClip] בקובץ תצוגה. התחביר שלהם הוא כפי שמוצג בדוגמא הבאה, + +~~~ +[php] +«clip:clipID» + // תוכן הקליפ +«/clip:clipID » +~~~ + +כש `clipID` הוא מזהה יחודי עבור תוכן הקליפ. תגיות הקליפ יתורגמו לקוד ה PHP הבא + +~~~ +[php] +«?php $this-»beginClip('clipID'); ?» + // תוכן הקליפ +«?php $this-»endClip(); ?» +~~~ + +### תגיות הערות + +תגיות הערות נועדו לכתיבת הערות בתצוגה שיש להציג אותם רק למפתחים. תגיות הערות אלו יוסרו כשקובץ התצוגה יוצג למשתמשי הקצה. התחביר עבור תגיות הערות הוא כבדוגמא הבאה, + +~~~ +[php] +«!--- +הערות שלא יוצגו למשתמשי הקצה +---» +~~~ + +ערבוב פורמט תבניות +----------------------- + +החל מגרסא 1.1.2, ניתן לערבב שימוש של כמה תחבירי תבניות עם תחביר PHP רגיל. בכדי לעשות זאת, המאפיין [CViewRenderer::fileExtension] של מעבד התבניות המותקן כרגע צריך להיות מוגדר עם ערך השונה מ `php.`. לדוגמא, אם המאפיין מוגדר לערך `tpl.`, אז כל קובץ תצוגה המסתיים ב `tpl.` יועבד ויוצג בעזרת מעבד התבניות המותקן, בזמן שכל שאר קבצי התצוגה המסתיימים עם `php.` יקבלו יחס של קבצי PHP רגילים. + «div class="revision"»$Id: topics.prado.txt 1983 2010-03-31 19:46:37Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/topics.security.txt b/docs/guide/he/topics.security.txt index 56b56c214..c9808cdc8 100644 --- a/docs/guide/he/topics.security.txt +++ b/docs/guide/he/topics.security.txt @@ -1,93 +1,93 @@ -אבטחה -======== - -מניעת פריצת האפליקציה בעזרת XSS -------------------------------- - -Cross-site scripting הידוע כ XSS קורה כשאפליקצית ווב אוספת נתונים זדוניים מהמשתמש. בדרך כלל התוקפים יזריקו קוד Javascript, VBScript, ActiveX, HTML או פלאש אל אפליקציה שאינה מוגנת כראוי וניתנת לחדירה על ידי התקפות מסוג זה בכדי לרמות משתמשים אחרים ולאסוף מידע מהם. לדוגמא, פורום שנכתב בצורה לא טובה יכול להציג את הודעות המשתמש בנושאים ללא שום בדיקה קודם לכן. -תוקף יכול להזריק חתיכת קוד Javascript זדוני לתוך ההודעה ככה שברגע שמשתמשים יקראו את ההודעה הזו, קוד ה Javascript ירוץ באופן בלתי צפוי על גבי המחשב שלהם. - -אחד מהצעדים החושבים ביותר למניעת התקפות XSS הוא לבדוק את הקלט שהגיע מהמשתמש לפני הצגתו. ניתן לבצע קידוד ל HTML על הקלט שהגיע מהמשתמש ולהשיג מטרה זו. למרות, שבמקרים מסויימים קידוד ה-HTML אינו מומלץ מאחר והוא מבטל את כל תגיות ה HTML . - -Yii משלבת את אפשרות השימוש ב [HTMLPurifier](http://htmlpurifier.org/) ומספקת למפתחים רכיב שימוש בשם [CHtmlPurifier] הפועל כמעטפת עבור [HTMLPurifier](http://htmlpurifier.org/). -רכיב זה מסוגל להסיר את כל הקד הזדוני אשר מבוקר באופן יסודי, מאובטח אך עדיין מאפשר שימוש ברשימה של כמה תגים שאינם זדוניים (שניתנים להגדרה באופן אישי) ומוודא שהתוכן שעבר סינון תואם לתקן. - -ניתן להשתמש ברכיב [CHtmlPurifier] [כוידג'ט](/doc/guide/basics.view#widget) או [כפילטר](/doc/guide/basics.controller#filter). כשמשתמשים בו כוידג'ט, [CHtmlPurifier] יטהר את התוכן הנמצא בתוכו בקובץ תצוגה. לדוגמא, - -~~~ -[php] -«?php $this-»beginWidget('CHtmlPurifier'); ?» -הצגת הודעות שהגיעו מהמשתמש כאן שיש לסנן אותם מקוד זדוני -«?php $this-»endWidget(); ?» -~~~ - -מניעת התקפות CSRF -------------------------------------- - -התקפות Cross-Site Request Forgery (CSRF) מתבצעות כשאתר זדוני גורם לדפדפן המשתמש לבצע פעולה לא רצויה על אתר אמין. לדוגמא, באתר הזדוני קיים עמוד המכיל תג תמונה («img .. ») שהערך `src` שלו מפנה לאתר בנקאי: `http://bank.example/withdraw?transfer=10000&to=someone`. במידה ולמשתמש יש עוגיה השומרת את פרטי ההתחברות שלו באתר הבנקאי ההוא והמשתמש מבקש בטעות בעמוד הזדוני, הפעולה של העברה של 10000 דולר למשהו תתבצע. בניגוד ל XSS אשר מנצל את האמון שיש למשתמש עבור אתר מסויים, CSRF מנצל את האמון של אתר למשתמש מסויים. - -בכדי למנוע התקפות CSRF,ג חשוב לציית לחוק שבקשות `GET` המתקבלות מאפשרת רק לשלוף מידע בלא לשנות מידע שנמצא על השרת. ובקשות `POST`, הם צריכות להכיל ערך רנדומלי כלשהו שהשרת יכול לזהותו כדי לוודא שהטופס הנשלח נשלח והגיע אל אותו המקור (ששלח את הטופס). - -Yii מיישם אפשרות למניעת CSRF בכדי לנסות להביס התקפות מבוססות `POST`. אפשרות זו מבוססת על ידי כך שהיא שומרת ערך רנדומלי בעוגיה ומשווה את הערך הזה עם הערך שנשלח בעזרת בקשת ה `POST`. - -כברירת מחדל, ההגנה מול CSRF כבויה. בכדי להפעיל אותה, יש להגדיר את הרכיב [CHttpRequest] [בהגדרות האפליקציה](/doc/guide/basics.application#application-configuration) בצורה הבאה, - -~~~ -[php] -return array( - 'components'=»array( - 'request'=»array( - 'enableCsrfValidation'=»true, - ), - ), -); -~~~ - -ובכדי להציג טופס, יש להשתמש ב [CHtml::form] במקום לכתוב את קוד ה HTML של הטופס ידנית. השימוש ב [CHtml::form] תשבץ את הערך הרנדומלי הרצוי בשדה נסתר בטופס כדי שיהיה ניתן לאמתו בעת הבדיקה עבור CSRF. - - -מניעת התקפות מבוססות עוגיות ------------------------- - -הגנת העוגיות מהתקפות מהווה חשיבות רבה, מאחר והמזהים היחודיים במערכת נשמרים בעוגיות בדרך כלל (לדוגמא sessions). במידה ומשהו מקבל גישה למזהה היחודי של ה Session, הוא בעצם הבעלים של כל המידע הרלוונטי ל Session זה. - -ישנם כמה אמצעים למניעת התקפות על עוגיות. - - -* אפליקציה יכולה להשתמש ב SSL בכדי ליצור ערוץ תקשורת מאובטח ולהעביר את העוגיות על גבי חיבור HTTPS שהינו חיבור מאובטח לשרת. לכן, תופקים לא יוכלו לפענח את התוכן הנמצא בעוגיות המועברות. -* יש להגדיר את התפוגה של ה Sessions בהתאם, כולל כל העוגיות והסימונים הנשארים על ידי ה Sessions, בכדי להפחית את הסבירות של להיות מותקף. -* יש למנוע XSS הגורם לקוד זדוני לרוץ על גבי הדפדפן של המשתמש ולחשוף את העוגיות שלו. -* אימות נתוני העוגיות ולגלות אם הם שונו. - -Yii מיישמת אפשרות של אימות העוגיות בכדי למנוע את האפשרות שהעוגיות השתנו במהלך הבקשות. במיוחד, היא מבצעת בדיקת HMAC עבור הנתונים הנמצאים בעוגיות אם אפשרות בדיקת העוגיות מופעלת. - -אפשרות בדיקת העוגיות כבויה כברירת מחדל. בכדי להפעילה, יש להגדיר את רכיב האפליקציה [CHttpRequest] [בהגדרות האפליקציה](/doc/guide/basics.application#application-configuration) בצורה הבאה, - -~~~ -[php] -return array( - 'components'=»array( - 'request'=»array( - 'enableCookieValidation'=»true, - ), - ), -); -~~~ - - -בכדי להשתמש באפשרות של בדיקת העוגיות הסופקת על ידי Yii, אנו גם צריכים לגשת לעוגיות על ידי שימוש באוסף [cookies|CHttpRequest::cookies], במקום לגשת אליהם ישירות בעזרת `COOKIES_$` לדוגמא, - - -~~~ -[php] -// שליפת העוגיה עם שם ספציפי -$cookie=Yii::app()-»request-»cookies[$name]; -$value=$cookie-»value; -...... -// שמירת עוגיה -$cookie=new CHttpCookie($name,$value); -Yii::app()-»request-»cookies[$name]=$cookie; -~~~ - - +אבטחה +======== + +מניעת פריצת האפליקציה בעזרת XSS +------------------------------- + +Cross-site scripting הידוע כ XSS קורה כשאפליקצית ווב אוספת נתונים זדוניים מהמשתמש. בדרך כלל התוקפים יזריקו קוד Javascript, VBScript, ActiveX, HTML או פלאש אל אפליקציה שאינה מוגנת כראוי וניתנת לחדירה על ידי התקפות מסוג זה בכדי לרמות משתמשים אחרים ולאסוף מידע מהם. לדוגמא, פורום שנכתב בצורה לא טובה יכול להציג את הודעות המשתמש בנושאים ללא שום בדיקה קודם לכן. +תוקף יכול להזריק חתיכת קוד Javascript זדוני לתוך ההודעה ככה שברגע שמשתמשים יקראו את ההודעה הזו, קוד ה Javascript ירוץ באופן בלתי צפוי על גבי המחשב שלהם. + +אחד מהצעדים החושבים ביותר למניעת התקפות XSS הוא לבדוק את הקלט שהגיע מהמשתמש לפני הצגתו. ניתן לבצע קידוד ל HTML על הקלט שהגיע מהמשתמש ולהשיג מטרה זו. למרות, שבמקרים מסויימים קידוד ה-HTML אינו מומלץ מאחר והוא מבטל את כל תגיות ה HTML . + +Yii משלבת את אפשרות השימוש ב [HTMLPurifier](http://htmlpurifier.org/) ומספקת למפתחים רכיב שימוש בשם [CHtmlPurifier] הפועל כמעטפת עבור [HTMLPurifier](http://htmlpurifier.org/). +רכיב זה מסוגל להסיר את כל הקד הזדוני אשר מבוקר באופן יסודי, מאובטח אך עדיין מאפשר שימוש ברשימה של כמה תגים שאינם זדוניים (שניתנים להגדרה באופן אישי) ומוודא שהתוכן שעבר סינון תואם לתקן. + +ניתן להשתמש ברכיב [CHtmlPurifier] [כוידג'ט](/doc/guide/basics.view#widget) או [כפילטר](/doc/guide/basics.controller#filter). כשמשתמשים בו כוידג'ט, [CHtmlPurifier] יטהר את התוכן הנמצא בתוכו בקובץ תצוגה. לדוגמא, + +~~~ +[php] +«?php $this-»beginWidget('CHtmlPurifier'); ?» +הצגת הודעות שהגיעו מהמשתמש כאן שיש לסנן אותם מקוד זדוני +«?php $this-»endWidget(); ?» +~~~ + +מניעת התקפות CSRF +------------------------------------- + +התקפות Cross-Site Request Forgery (CSRF) מתבצעות כשאתר זדוני גורם לדפדפן המשתמש לבצע פעולה לא רצויה על אתר אמין. לדוגמא, באתר הזדוני קיים עמוד המכיל תג תמונה («img .. ») שהערך `src` שלו מפנה לאתר בנקאי: `http://bank.example/withdraw?transfer=10000&to=someone`. במידה ולמשתמש יש עוגיה השומרת את פרטי ההתחברות שלו באתר הבנקאי ההוא והמשתמש מבקש בטעות בעמוד הזדוני, הפעולה של העברה של 10000 דולר למשהו תתבצע. בניגוד ל XSS אשר מנצל את האמון שיש למשתמש עבור אתר מסויים, CSRF מנצל את האמון של אתר למשתמש מסויים. + +בכדי למנוע התקפות CSRF,ג חשוב לציית לחוק שבקשות `GET` המתקבלות מאפשרת רק לשלוף מידע בלא לשנות מידע שנמצא על השרת. ובקשות `POST`, הם צריכות להכיל ערך רנדומלי כלשהו שהשרת יכול לזהותו כדי לוודא שהטופס הנשלח נשלח והגיע אל אותו המקור (ששלח את הטופס). + +Yii מיישם אפשרות למניעת CSRF בכדי לנסות להביס התקפות מבוססות `POST`. אפשרות זו מבוססת על ידי כך שהיא שומרת ערך רנדומלי בעוגיה ומשווה את הערך הזה עם הערך שנשלח בעזרת בקשת ה `POST`. + +כברירת מחדל, ההגנה מול CSRF כבויה. בכדי להפעיל אותה, יש להגדיר את הרכיב [CHttpRequest] [בהגדרות האפליקציה](/doc/guide/basics.application#application-configuration) בצורה הבאה, + +~~~ +[php] +return array( + 'components'=»array( + 'request'=»array( + 'enableCsrfValidation'=»true, + ), + ), +); +~~~ + +ובכדי להציג טופס, יש להשתמש ב [CHtml::form] במקום לכתוב את קוד ה HTML של הטופס ידנית. השימוש ב [CHtml::form] תשבץ את הערך הרנדומלי הרצוי בשדה נסתר בטופס כדי שיהיה ניתן לאמתו בעת הבדיקה עבור CSRF. + + +מניעת התקפות מבוססות עוגיות +------------------------ + +הגנת העוגיות מהתקפות מהווה חשיבות רבה, מאחר והמזהים היחודיים במערכת נשמרים בעוגיות בדרך כלל (לדוגמא sessions). במידה ומשהו מקבל גישה למזהה היחודי של ה Session, הוא בעצם הבעלים של כל המידע הרלוונטי ל Session זה. + +ישנם כמה אמצעים למניעת התקפות על עוגיות. + + +* אפליקציה יכולה להשתמש ב SSL בכדי ליצור ערוץ תקשורת מאובטח ולהעביר את העוגיות על גבי חיבור HTTPS שהינו חיבור מאובטח לשרת. לכן, תופקים לא יוכלו לפענח את התוכן הנמצא בעוגיות המועברות. +* יש להגדיר את התפוגה של ה Sessions בהתאם, כולל כל העוגיות והסימונים הנשארים על ידי ה Sessions, בכדי להפחית את הסבירות של להיות מותקף. +* יש למנוע XSS הגורם לקוד זדוני לרוץ על גבי הדפדפן של המשתמש ולחשוף את העוגיות שלו. +* אימות נתוני העוגיות ולגלות אם הם שונו. + +Yii מיישמת אפשרות של אימות העוגיות בכדי למנוע את האפשרות שהעוגיות השתנו במהלך הבקשות. במיוחד, היא מבצעת בדיקת HMAC עבור הנתונים הנמצאים בעוגיות אם אפשרות בדיקת העוגיות מופעלת. + +אפשרות בדיקת העוגיות כבויה כברירת מחדל. בכדי להפעילה, יש להגדיר את רכיב האפליקציה [CHttpRequest] [בהגדרות האפליקציה](/doc/guide/basics.application#application-configuration) בצורה הבאה, + +~~~ +[php] +return array( + 'components'=»array( + 'request'=»array( + 'enableCookieValidation'=»true, + ), + ), +); +~~~ + + +בכדי להשתמש באפשרות של בדיקת העוגיות הסופקת על ידי Yii, אנו גם צריכים לגשת לעוגיות על ידי שימוש באוסף [cookies|CHttpRequest::cookies], במקום לגשת אליהם ישירות בעזרת `COOKIES_$` לדוגמא, + + +~~~ +[php] +// שליפת העוגיה עם שם ספציפי +$cookie=Yii::app()-»request-»cookies[$name]; +$value=$cookie-»value; +...... +// שמירת עוגיה +$cookie=new CHttpCookie($name,$value); +Yii::app()-»request-»cookies[$name]=$cookie; +~~~ + + «div class="revision"»$Id: topics.security.txt 1458 2009-10-16 15:03:59Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/topics.theming.txt b/docs/guide/he/topics.theming.txt index 28736303f..787acad8c 100644 --- a/docs/guide/he/topics.theming.txt +++ b/docs/guide/he/topics.theming.txt @@ -1,227 +1,227 @@ - ניהול תבניות ועיצוב -======= - -תבנית הינה דרך שיטתית לשינוי התצוגה של העמודים באפליקצית ווב. על ידי הוספת תבנית חדשה, המראה הכללי של האפליקציה משתנה באופן מיידי ודרמטי. - -ב Yii, כל תבנית מיוצגת כתיקיה המכילה קבצי תצוגה, קבצי תבנית, קבצי סקריפט (Js, CSS), תמונות, וכדומה. שם התבנית הוא שם התיקיה בה הוא נמצא. כל התבניות נמצאות תחת אותה תיקיה `WebRoot/themes`. בכל זמן נתון, רק תבנית אחת יכולה להיות פעילה. - -> Tip|טיפ: תיקית התבניות ברירת המחדל `WebRoot/themes` ניתנת לשינוי לתיקיה אחרת בשרת. בכדי לבצע זאת יש להגדיר את המאפיינים [basePath|CThemeManager::basePath] ו [baseUrl|CThemeManager::baseUrl] של רכיב האפליקציה [themeManager|CWebApplication::themeManager]. - -בכדי להפעיל תבנית, יש להגדיר את המאפיין [theme|CWebApplication::theme] של האפליקציה לשם התבנית הרצוי שבו רוצים להשתמש. ניתן לבצע הגדרה זו בקובץ [הגדרות](/doc/guide/basics.application#application-configuration) האפליקציה או במהלך הרצת האפליקציה בפעולות בקונטרולר. - -> Note|הערה: שמות התבניות רגישות לאותיות גדולות-קטנות. אם יהיה ניסיון להפעיל תבנית אשר אינה קיימת, שימוש ב `Yii::app()->theme` יחזיר null. - -התכנים הנמצאים בתיקית תבנית צריכים להיות מאורגנים באותה צורה כמו אלו הנמצאים תחת התיקיה [הראשית](/doc/guide/basics.application#application-base-directory) של האפליקציה. לדוגמא, כל קבצי התצוגה צריכים להמצא תחת התיקיה `views`, קבצי תבניות צריכים להמצא תחת התיקיה `views/layouts`, וקבצי תצוגה מערכתיים צריכים להמצא תחת התיקיה `views/system`. לדוגמא, אם אנו רוצים להחליף את קובץ התצוגה `create` השייך לקונטרולר `PostController` עם קובץ התצוגה של התבנית `classic`, אנו צריכים לשמור את קובץ התצוגה החדש תחת התיקיה `WebRoot/themes/classic/views/post/create.php`. - -עבור קבצי תצוגה השייכים לקונטרולר הנמצא בתוך [מודול](/doc/guide/basics.module) מסויים, קבצי התצוגה המקבילים צריכים גם הם להיות ממקומים תחת התיקיה `views`. לדוגמא, אם קובץ הקונטרולר הנ"ל `PostController` נמצא תחת מודול בשם `forum`, אנו צריכים לשמור את קובץ התצוגה `create` שהוא משתמש בו כ `WebRoot/themes/classic/views/forum/post/create.php`. במידה והמודול `forum` נמצא תחת מודול נוסף בשם `support`, אז אנו נשמור את קובץ התצוגה הנ"ל כ `WebRoot/themes/classic/views/support/forum/post/create.php`. - -> Note|הערה: מאחר ותיקית קבצי התצוגה `views` יכולה להכיל מידע הרגיש מבחינת אבטחה, היא צריכה להיות מוגדרת כתיקיה שלא ניתן לצפות בתוכנה על ידי משתמשי הקצה. - -כשאנו נשתמש במתודות [render|CController::render] או [renderPartial|CController::renderPartial] בכדי להציג קובץ תצוגה, קובץ התצוגה הנ"ל וקובץ התבנית יטענו מהתיקיה של התבנית הנמצאת בשימוש כרגע. במידה והם נמצאו, קבצים אלו יוצגו. במידה ולא, קבצי התצוגה נסוגו לנתיב ברירת המחדל שלהם תחת [viewPath|CController::viewPath] ו [layoutPath|CWebApplication::layoutPath]. - -> Tip|טיפ: בתוך קובץ תצוגה, אנו בדרך כלל צריכים לטעון משאבים (תמונות, קבצי סקריפט) מאותה תיקיה של התבנית בה אנו משתמשים כרגע. לדוגמא, אנו רוצים להציג תמונה הנמצאת בתיקיה `images` תחת התיקיה של התבנית הפעילה כרגע. שימוש במאפיין [baseUrl|CTheme::baseUrl] של התבנית הפעילה כרגע, אנו יכולים ליצור קישור לתמונה בצורה הבאה, -> -> ~~~ -> [php] -> Yii::app()->theme->baseUrl . '/images/FileName.gif' -> ~~~ -> - -להלן דוגמא לארגון תיקיות של אפליקציה המשתמשת בשני תבניות `basic` ו `fancy`. - -~~~ -WebRoot/ - assets - protected/ - .htaccess - components/ - controllers/ - models/ - views/ - layouts/ - main.php - site/ - index.php - themes/ - basic/ - views/ - .htaccess - layouts/ - main.php - site/ - index.php - fancy/ - views/ - .htaccess - layouts/ - main.php - site/ - index.php -~~~ - -בהגדרות האפליקציה, אם נגדיר - -~~~ -[php] -return array( - 'theme'=>'basic', - ...... -); -~~~ - -אז התבנית `basic` תיהיה פעילה, אשר אומר שקובץ תבנית האפליקציה יטען מהתיקיה `themes/basic/views/layouts`, וקובץ התצוגה `index` יטען מהנתיב `themes/basic/views/site`. במידה וקובץ תצוגה לא נמצא בתבנית, האפליקציה תסוג לקובץ התצוגה הנמצא תחת התיקיה `protected/views`. - - -התאמה גלובאלית לוידג'טים ----------------------------- - -> Note|הערה: אפשרות זו קיימת החל מגרסא 1.1.3. - -בעת השימוש בוידג'ט שסופק על ידי Yii או גורם צד שלישי, אנו בדרך כלל צריכים להתאים אותו לדרישות הספציפיות שלנו. לדוגמא, אנו נרצה לשנות את ערך ברירת המחדל של [CLinkPager::maxButtonCount] מ 10 כברירת מחדל ל 5. אנו יכולים לבצע זאת על ידי הצבת פרמטר בעת הקריאה ל-[CBaseController::widget] בעת יצירת הוידג'ט. אך דרך זו הופכת להיות טרחה מאחר ואנו חוזרים על אותו הפעולה שוב ושוב במקומות שונים באפליקציה בכל מקום בו אנו משתמשים ב-[CLinkPager]. - -~~~ -[php] -$this->widget('CLinkPager', array( - 'pages'=>$pagination, - 'maxButtonCount'=>5, - 'cssFile'=>false, -)); -~~~ - -בעזרת שימוש בהגדרות גלובאליות לוידג'טים, אנו צריכים להגדיר את הערכים הללו במקום אחד בלבד, לדוגמא, קובץ הגדרות האפליקציה. פעולה זו הופכת את ניהול ותחזוקת הוידג'טים לקלה יותר. - -בכדי להשתמש באפשרות של הגדרות גלובאליות עבור וידג'טים, אנו צריכים להגדיר את [widgetFactory|CWebApplication::widgetFactory] בצורה הבאה: - -~~~ -[php] -return array( - 'components'=>array( - 'widgetFactory'=>array( - 'widgets'=>array( - 'CLinkPager'=>array( - 'maxButtonCount'=>5, - 'cssFile'=>false, - ), - 'CJuiDatePicker'=>array( - 'language'=>'ru', - ), - ), - ), - ), -); -~~~ - -בקוד המוצג למעלה, אנו מגדירים את ההגדרות הגלובאליות עבור הוידג'טים [CLinkPager] ו [CJuiDatePicker] על ידי הגדרת המאפין [CWidgetFactory::widgets]. יש לזכור שהגדרות גלובאליות עבור כל וידג'ט מיוצגות כמפתח->ערך במערך, כשהמפתח מייצג את שם המחלקה של הוידג'ט והערך מייצג מערך של הגדרות גלובאליות לוידג'ט. - -כעת, בכל פעם שאנו ניצור וידג'ט [CLinkPager] בקובץ תצוגה, המאפיינים המוגדרים למעלה יוצבו כברירת מחדל לוידג'ט, וכל מה שאנו צריכים כדי ליצור את הוידג'ט הוא לכתוב את הקוד הבא: - -~~~ -[php] -$this->widget('CLinkPager', array( - 'pages'=>$pagination, -)); -~~~ - -אנו תמיד יכולים לדרוס את ההגדרות הגלובלאיות בעת הצורך. לדוגמא, אם בקובץ תצוגה כלשהו אנו נרצה להגדיר את המאפיין `maxButtonCount` ל-2, אנו יכולים לבצע את זאת בעזרת הקוד הבא: - -~~~ -[php] -$this->widget('CLinkPager', array( - 'pages'=>$pagination, - 'maxButtonCount'=>2, -)); -~~~ - - -עיצוב ----- - -> Note|הערה: אפשרות העיצוב קיימת החל מגרסא 1.1.0. - -בזמן שאנו משתמשים בתבנית בכדי לשנות את המראה החיצוני של האפליקציה, אנו יכולים להשתמש בעיצובים בכדי לשנות את עיצוב [הוידג'טים](/doc/guide/basics.view#widget) הנמצאים בתוך קבצי התצוגה. - -עיצוב הוא מערך של אלמנטים שבעזרתו ניתן לאתחל מאפיינים של וידג'ט. עיצוב שייך למחלקה של וידג'ט, ומחלקה של וידג'ט יכולה להכיל כמה עיצובים המזוהים על פי שמותיהם. לדוגמא, אנו יכולים להגדיר עיצוב לוידג'ט [CLinkPager] ושמו של העיצוב הוא `classic`. - -בכדי להשתמש באפשרות של העיצובים, אנו קודם צריכים לערוך את הגדרות האפליקציה על ידי התקנת הרכיב `widgetFactory`: - -~~~ -[php] -return array( - 'components'=>array( - 'widgetFactory'=>array( - 'enableSkin'=>true, - ), - ), -); -~~~ - -זכור שבגרסאות קודמות ל 1.1.3, בכדי להשתמש בעיצובים עבור וידג'טים יש צורך להשתמש בקוד הבא: - -~~~ -[php] -return array( - 'components'=>array( - 'widgetFactory'=>array( - 'class'=>'CWidgetFactory', - ), - ), -); -~~~ - -לאחר מכן אנו יוצרים את העיצובים הדרושים. עיצובים השייכים לאותו מחלקת וידג'ט נשמרים בקובץ PHP אחד ששמו הוא שם המחלקה של הוידג'ט. כל קבצי העיצובים הללו נשמרים תחת `protected/views/skins`, כברירת מחדל. במידה ויש צורך לשנות מיקום זה לתיקיה אחרת, ניתן להגדיר את המאפיין `skinPath` של הרכיב `widgetFactory`. לדוגמא, אנו יכולים ליצור תחת התיקיה `protected/views/skins` קובץ בשם `CLinkPage.php` שתוכנו הוא הקוד הבא, - -~~~ -[php] -array( - 'nextPageLabel'=>'>>', - 'prevPageLabel'=>'<<', - ), - 'classic'=>array( - 'header'=>'', - 'maxButtonCount'=>5, - ), -); -~~~ - -בקוד המוצג למעלה, אנו יוצרים שני עיצובים עבור הוידג'ט [CLinkPager] והם `default` ו `classic`. הראשון הוא העיצוב שיצורף לכל אובייקט של הוידג'ט [CLinkPager] שאנו לא מגדירים את המאפיין `skin` באובייקט בצורה ספציפית, בזמן שהשני הוא העיצוב שיצורף לאובייקט של הוידג'ט [CLinkPager] שהמאפיין `skin` שלו מוגדר כ `classic`. לדוגמא, בקוד התצוגה הבאה, הוידג'ט הראשון משתמש בעיצוב `default` בזמן שהשני משתמש בעיצוב `classic`: - -~~~ -[php] -widget('CLinkPager'); ?> - -widget('CLinkPager', array('skin'=>'classic')); ?> -~~~ - -אם ניצור וידג'ט עם כשאנו מגדירים את המאפיינים ההתחלתיים בקובץ התצוגה, מאפיינים אלו יתאחדו עם המאפיינים המוגדרים בעיצוב אך יקבלו עדיפות על פני אלו שהוגדרו בעיצוב. לדוגמא, הקוד הבא יוצר וידג'ט שמאפייניו ההתחלתיים יהיו - -~~~ -array('header'=>'', 'maxButtonCount'=>6, 'cssFile'=>false) -~~~ - -שהם התוצאה של איחוד המאפיינים ההתחלתיים שהוגדרו בקובץ התצוגה ובעיצוב `classic`. - -~~~ -[php] -widget('CLinkPager', array( - 'skin'=>'classic', - 'maxButtonCount'=>6, - 'cssFile'=>false, -)); ?> -~~~ - -יש לזכור שהשימוש בעיצובים אינו דורש שימוש בתבניות. למרות, בזמן השימוש בתבניות, המערכת תחפש את העיצובים תחת התיקיה `skins` של קבצי התצוגה של התבנית הפעילה (לדוגמא `WebRoot/themes/classic/views/skins`). במידה והעיצוב קיים בתיקית קבצי התצוגה של התבנית הפעילה ובתיקית קבצי התצוגה של האפליקציה, קבצי העיצוב הנמצאים תחת התבנית הפעילה יקלו עדיפות. - -במידה והוידג'ט משתמש בעיצוב שאינו קיים, המערכת תיצור את הוידג'ט כרגיל ללא שום שגיאה. - -> Info|מידע: שימוש בעיצובים יכול להשפיע לרעה על הביצועים של האפליקציה מאחר והמערכת צריכה לחפש את קובץ העיצוב בפעם הראשונה שהוידג'ט נוצר. - -עיצוב הוא דומה להגדרות וידג'ט גלובאליות. ההבדלים העיקריים הם: - -- עיצוב קשור יותר למאפיינים המייצגים תצוגה בוידג'ט; -- וידג'ט יכול להכיל מספר רב של עיצובים; -- ניתן להגדיר תבניות עבור עיצוב; -- שימוש בעיצוב יקר יותר מבחינת משאבים מאשר שימוש בהגדרות וידג'ט גלובאליות. - + ניהול תבניות ועיצוב +======= + +תבנית הינה דרך שיטתית לשינוי התצוגה של העמודים באפליקצית ווב. על ידי הוספת תבנית חדשה, המראה הכללי של האפליקציה משתנה באופן מיידי ודרמטי. + +ב Yii, כל תבנית מיוצגת כתיקיה המכילה קבצי תצוגה, קבצי תבנית, קבצי סקריפט (Js, CSS), תמונות, וכדומה. שם התבנית הוא שם התיקיה בה הוא נמצא. כל התבניות נמצאות תחת אותה תיקיה `WebRoot/themes`. בכל זמן נתון, רק תבנית אחת יכולה להיות פעילה. + +> Tip|טיפ: תיקית התבניות ברירת המחדל `WebRoot/themes` ניתנת לשינוי לתיקיה אחרת בשרת. בכדי לבצע זאת יש להגדיר את המאפיינים [basePath|CThemeManager::basePath] ו [baseUrl|CThemeManager::baseUrl] של רכיב האפליקציה [themeManager|CWebApplication::themeManager]. + +בכדי להפעיל תבנית, יש להגדיר את המאפיין [theme|CWebApplication::theme] של האפליקציה לשם התבנית הרצוי שבו רוצים להשתמש. ניתן לבצע הגדרה זו בקובץ [הגדרות](/doc/guide/basics.application#application-configuration) האפליקציה או במהלך הרצת האפליקציה בפעולות בקונטרולר. + +> Note|הערה: שמות התבניות רגישות לאותיות גדולות-קטנות. אם יהיה ניסיון להפעיל תבנית אשר אינה קיימת, שימוש ב `Yii::app()->theme` יחזיר null. + +התכנים הנמצאים בתיקית תבנית צריכים להיות מאורגנים באותה צורה כמו אלו הנמצאים תחת התיקיה [הראשית](/doc/guide/basics.application#application-base-directory) של האפליקציה. לדוגמא, כל קבצי התצוגה צריכים להמצא תחת התיקיה `views`, קבצי תבניות צריכים להמצא תחת התיקיה `views/layouts`, וקבצי תצוגה מערכתיים צריכים להמצא תחת התיקיה `views/system`. לדוגמא, אם אנו רוצים להחליף את קובץ התצוגה `create` השייך לקונטרולר `PostController` עם קובץ התצוגה של התבנית `classic`, אנו צריכים לשמור את קובץ התצוגה החדש תחת התיקיה `WebRoot/themes/classic/views/post/create.php`. + +עבור קבצי תצוגה השייכים לקונטרולר הנמצא בתוך [מודול](/doc/guide/basics.module) מסויים, קבצי התצוגה המקבילים צריכים גם הם להיות ממקומים תחת התיקיה `views`. לדוגמא, אם קובץ הקונטרולר הנ"ל `PostController` נמצא תחת מודול בשם `forum`, אנו צריכים לשמור את קובץ התצוגה `create` שהוא משתמש בו כ `WebRoot/themes/classic/views/forum/post/create.php`. במידה והמודול `forum` נמצא תחת מודול נוסף בשם `support`, אז אנו נשמור את קובץ התצוגה הנ"ל כ `WebRoot/themes/classic/views/support/forum/post/create.php`. + +> Note|הערה: מאחר ותיקית קבצי התצוגה `views` יכולה להכיל מידע הרגיש מבחינת אבטחה, היא צריכה להיות מוגדרת כתיקיה שלא ניתן לצפות בתוכנה על ידי משתמשי הקצה. + +כשאנו נשתמש במתודות [render|CController::render] או [renderPartial|CController::renderPartial] בכדי להציג קובץ תצוגה, קובץ התצוגה הנ"ל וקובץ התבנית יטענו מהתיקיה של התבנית הנמצאת בשימוש כרגע. במידה והם נמצאו, קבצים אלו יוצגו. במידה ולא, קבצי התצוגה נסוגו לנתיב ברירת המחדל שלהם תחת [viewPath|CController::viewPath] ו [layoutPath|CWebApplication::layoutPath]. + +> Tip|טיפ: בתוך קובץ תצוגה, אנו בדרך כלל צריכים לטעון משאבים (תמונות, קבצי סקריפט) מאותה תיקיה של התבנית בה אנו משתמשים כרגע. לדוגמא, אנו רוצים להציג תמונה הנמצאת בתיקיה `images` תחת התיקיה של התבנית הפעילה כרגע. שימוש במאפיין [baseUrl|CTheme::baseUrl] של התבנית הפעילה כרגע, אנו יכולים ליצור קישור לתמונה בצורה הבאה, +> +> ~~~ +> [php] +> Yii::app()->theme->baseUrl . '/images/FileName.gif' +> ~~~ +> + +להלן דוגמא לארגון תיקיות של אפליקציה המשתמשת בשני תבניות `basic` ו `fancy`. + +~~~ +WebRoot/ + assets + protected/ + .htaccess + components/ + controllers/ + models/ + views/ + layouts/ + main.php + site/ + index.php + themes/ + basic/ + views/ + .htaccess + layouts/ + main.php + site/ + index.php + fancy/ + views/ + .htaccess + layouts/ + main.php + site/ + index.php +~~~ + +בהגדרות האפליקציה, אם נגדיר + +~~~ +[php] +return array( + 'theme'=>'basic', + ...... +); +~~~ + +אז התבנית `basic` תיהיה פעילה, אשר אומר שקובץ תבנית האפליקציה יטען מהתיקיה `themes/basic/views/layouts`, וקובץ התצוגה `index` יטען מהנתיב `themes/basic/views/site`. במידה וקובץ תצוגה לא נמצא בתבנית, האפליקציה תסוג לקובץ התצוגה הנמצא תחת התיקיה `protected/views`. + + +התאמה גלובאלית לוידג'טים +---------------------------- + +> Note|הערה: אפשרות זו קיימת החל מגרסא 1.1.3. + +בעת השימוש בוידג'ט שסופק על ידי Yii או גורם צד שלישי, אנו בדרך כלל צריכים להתאים אותו לדרישות הספציפיות שלנו. לדוגמא, אנו נרצה לשנות את ערך ברירת המחדל של [CLinkPager::maxButtonCount] מ 10 כברירת מחדל ל 5. אנו יכולים לבצע זאת על ידי הצבת פרמטר בעת הקריאה ל-[CBaseController::widget] בעת יצירת הוידג'ט. אך דרך זו הופכת להיות טרחה מאחר ואנו חוזרים על אותו הפעולה שוב ושוב במקומות שונים באפליקציה בכל מקום בו אנו משתמשים ב-[CLinkPager]. + +~~~ +[php] +$this->widget('CLinkPager', array( + 'pages'=>$pagination, + 'maxButtonCount'=>5, + 'cssFile'=>false, +)); +~~~ + +בעזרת שימוש בהגדרות גלובאליות לוידג'טים, אנו צריכים להגדיר את הערכים הללו במקום אחד בלבד, לדוגמא, קובץ הגדרות האפליקציה. פעולה זו הופכת את ניהול ותחזוקת הוידג'טים לקלה יותר. + +בכדי להשתמש באפשרות של הגדרות גלובאליות עבור וידג'טים, אנו צריכים להגדיר את [widgetFactory|CWebApplication::widgetFactory] בצורה הבאה: + +~~~ +[php] +return array( + 'components'=>array( + 'widgetFactory'=>array( + 'widgets'=>array( + 'CLinkPager'=>array( + 'maxButtonCount'=>5, + 'cssFile'=>false, + ), + 'CJuiDatePicker'=>array( + 'language'=>'ru', + ), + ), + ), + ), +); +~~~ + +בקוד המוצג למעלה, אנו מגדירים את ההגדרות הגלובאליות עבור הוידג'טים [CLinkPager] ו [CJuiDatePicker] על ידי הגדרת המאפין [CWidgetFactory::widgets]. יש לזכור שהגדרות גלובאליות עבור כל וידג'ט מיוצגות כמפתח->ערך במערך, כשהמפתח מייצג את שם המחלקה של הוידג'ט והערך מייצג מערך של הגדרות גלובאליות לוידג'ט. + +כעת, בכל פעם שאנו ניצור וידג'ט [CLinkPager] בקובץ תצוגה, המאפיינים המוגדרים למעלה יוצבו כברירת מחדל לוידג'ט, וכל מה שאנו צריכים כדי ליצור את הוידג'ט הוא לכתוב את הקוד הבא: + +~~~ +[php] +$this->widget('CLinkPager', array( + 'pages'=>$pagination, +)); +~~~ + +אנו תמיד יכולים לדרוס את ההגדרות הגלובלאיות בעת הצורך. לדוגמא, אם בקובץ תצוגה כלשהו אנו נרצה להגדיר את המאפיין `maxButtonCount` ל-2, אנו יכולים לבצע את זאת בעזרת הקוד הבא: + +~~~ +[php] +$this->widget('CLinkPager', array( + 'pages'=>$pagination, + 'maxButtonCount'=>2, +)); +~~~ + + +עיצוב +---- + +> Note|הערה: אפשרות העיצוב קיימת החל מגרסא 1.1.0. + +בזמן שאנו משתמשים בתבנית בכדי לשנות את המראה החיצוני של האפליקציה, אנו יכולים להשתמש בעיצובים בכדי לשנות את עיצוב [הוידג'טים](/doc/guide/basics.view#widget) הנמצאים בתוך קבצי התצוגה. + +עיצוב הוא מערך של אלמנטים שבעזרתו ניתן לאתחל מאפיינים של וידג'ט. עיצוב שייך למחלקה של וידג'ט, ומחלקה של וידג'ט יכולה להכיל כמה עיצובים המזוהים על פי שמותיהם. לדוגמא, אנו יכולים להגדיר עיצוב לוידג'ט [CLinkPager] ושמו של העיצוב הוא `classic`. + +בכדי להשתמש באפשרות של העיצובים, אנו קודם צריכים לערוך את הגדרות האפליקציה על ידי התקנת הרכיב `widgetFactory`: + +~~~ +[php] +return array( + 'components'=>array( + 'widgetFactory'=>array( + 'enableSkin'=>true, + ), + ), +); +~~~ + +זכור שבגרסאות קודמות ל 1.1.3, בכדי להשתמש בעיצובים עבור וידג'טים יש צורך להשתמש בקוד הבא: + +~~~ +[php] +return array( + 'components'=>array( + 'widgetFactory'=>array( + 'class'=>'CWidgetFactory', + ), + ), +); +~~~ + +לאחר מכן אנו יוצרים את העיצובים הדרושים. עיצובים השייכים לאותו מחלקת וידג'ט נשמרים בקובץ PHP אחד ששמו הוא שם המחלקה של הוידג'ט. כל קבצי העיצובים הללו נשמרים תחת `protected/views/skins`, כברירת מחדל. במידה ויש צורך לשנות מיקום זה לתיקיה אחרת, ניתן להגדיר את המאפיין `skinPath` של הרכיב `widgetFactory`. לדוגמא, אנו יכולים ליצור תחת התיקיה `protected/views/skins` קובץ בשם `CLinkPage.php` שתוכנו הוא הקוד הבא, + +~~~ +[php] +array( + 'nextPageLabel'=>'>>', + 'prevPageLabel'=>'<<', + ), + 'classic'=>array( + 'header'=>'', + 'maxButtonCount'=>5, + ), +); +~~~ + +בקוד המוצג למעלה, אנו יוצרים שני עיצובים עבור הוידג'ט [CLinkPager] והם `default` ו `classic`. הראשון הוא העיצוב שיצורף לכל אובייקט של הוידג'ט [CLinkPager] שאנו לא מגדירים את המאפיין `skin` באובייקט בצורה ספציפית, בזמן שהשני הוא העיצוב שיצורף לאובייקט של הוידג'ט [CLinkPager] שהמאפיין `skin` שלו מוגדר כ `classic`. לדוגמא, בקוד התצוגה הבאה, הוידג'ט הראשון משתמש בעיצוב `default` בזמן שהשני משתמש בעיצוב `classic`: + +~~~ +[php] +widget('CLinkPager'); ?> + +widget('CLinkPager', array('skin'=>'classic')); ?> +~~~ + +אם ניצור וידג'ט עם כשאנו מגדירים את המאפיינים ההתחלתיים בקובץ התצוגה, מאפיינים אלו יתאחדו עם המאפיינים המוגדרים בעיצוב אך יקבלו עדיפות על פני אלו שהוגדרו בעיצוב. לדוגמא, הקוד הבא יוצר וידג'ט שמאפייניו ההתחלתיים יהיו + +~~~ +array('header'=>'', 'maxButtonCount'=>6, 'cssFile'=>false) +~~~ + +שהם התוצאה של איחוד המאפיינים ההתחלתיים שהוגדרו בקובץ התצוגה ובעיצוב `classic`. + +~~~ +[php] +widget('CLinkPager', array( + 'skin'=>'classic', + 'maxButtonCount'=>6, + 'cssFile'=>false, +)); ?> +~~~ + +יש לזכור שהשימוש בעיצובים אינו דורש שימוש בתבניות. למרות, בזמן השימוש בתבניות, המערכת תחפש את העיצובים תחת התיקיה `skins` של קבצי התצוגה של התבנית הפעילה (לדוגמא `WebRoot/themes/classic/views/skins`). במידה והעיצוב קיים בתיקית קבצי התצוגה של התבנית הפעילה ובתיקית קבצי התצוגה של האפליקציה, קבצי העיצוב הנמצאים תחת התבנית הפעילה יקלו עדיפות. + +במידה והוידג'ט משתמש בעיצוב שאינו קיים, המערכת תיצור את הוידג'ט כרגיל ללא שום שגיאה. + +> Info|מידע: שימוש בעיצובים יכול להשפיע לרעה על הביצועים של האפליקציה מאחר והמערכת צריכה לחפש את קובץ העיצוב בפעם הראשונה שהוידג'ט נוצר. + +עיצוב הוא דומה להגדרות וידג'ט גלובאליות. ההבדלים העיקריים הם: + +- עיצוב קשור יותר למאפיינים המייצגים תצוגה בוידג'ט; +- וידג'ט יכול להכיל מספר רב של עיצובים; +- ניתן להגדיר תבניות עבור עיצוב; +- שימוש בעיצוב יקר יותר מבחינת משאבים מאשר שימוש בהגדרות וידג'ט גלובאליות. +
$Id: topics.theming.txt 2172 2009-10-17 01:49:02Z qiang.xue $
\ No newline at end of file diff --git a/docs/guide/he/topics.url.txt b/docs/guide/he/topics.url.txt index 3c0c688d1..816ab83c8 100644 --- a/docs/guide/he/topics.url.txt +++ b/docs/guide/he/topics.url.txt @@ -1,262 +1,262 @@ -ניהול קישורים -============== - -ניהול קישורים מלא עבור אפליקציות ווב כרוך בשני היבטים. ראשית, ברגע שבקשת המשתמש מתקבלת בפורמט של קישור, האפליקציה צריכה לעבד את הקישור לפרמטרים שניתן להבין אותם. שנית, האפליקציה צריכה לספק דרך ליצירת קישורים בצורה כזו שהאפליקציה תוכל להבין את הקישורים הנוצרים לאחר מכן. עבור אפליקצית Yii, היבטים אלו ממומשים בעזרת [CUrlManager]. - -יצירת קישורים -------------- - -למרות שניתן לכתוב קישורים מלאים בצורה הרגילה והמלאה בקבצי תצוגה, רצוי ליצור אותם בצורה דינאמית כדי לאפשר מקסימום גמישות: - -~~~ -[php] -$url=$this-»createUrl($route,$params); -~~~ - -האובייקט `this$` מתייחס לאובייקט של הקונטרולר הנוכחי; `route$` מציין את [נתב](/doc/guide/basics.controller#route) של הבקשה; ו `params$` הנו מערך של אלמנטים המייצג רשימה של פרמטרים (GET) אשר יצורפו לקישור. - -כברירת מחדל, קישורים הנוצרים על ידי [createUrl|CController::createUrl] הם בפורמט הנקרא `get`. לדוגמא, בהנחה שיש ברשותנו את הנתונים - - - -~~~ - -$route='post/read', $params=array('id' =» 100) -~~~ - - -אנו נקבל את הקישור הבא: - -~~~ -/index.php?r=post/read&id=100 -~~~ - -כשהפרמטרים מופיעים בקישור כרשימה של `מפתח=ערך` המחוברים אחד לשני בעזרת התו `&`, והפרמטר `r` מציין את בקשת [הנתב](/doc/guide/basics.controller#route). הפורמט הזה אינו ידידותי כל כך למשתמש מאחר והוא מכיל כמה ערכים שהם אינם בהכרח תווים אלפאנומריים (אותיות ומספרים). - -אנו יכולים ליצור את אותו הקישור בצורה נקייה וידידותית יותר למשתמש על ידי שימוש בפורמט שנקרא `path` המסיר את השאילתה מהמחרוזת של הקישור ומוסיף את הפרמטרים (GET) כחלק מהנתיב: - -~~~ -/index.php/post/read/id/100 -~~~ - -בכדי לשנות את פורמט הקישורים, אנו צריכים להגדיר את המאפיין [urlManager|CWebApplication::urlManager] בהגדרות האפליקציה כדי שבעת השימוש ב-[createUrl|CController::createUrl] המתודה תדע להחליף את הקישורים לפורמט החדש והאפליקציה תוכל להבין ולנתח את הקישורים החדשים: - -~~~ -[php] -array( - ...... - 'components'=»array( - ...... - 'urlManager'=»array( - 'urlFormat'=»'path', - ), - ), -); -~~~ - -זכור שאנו לא צריכים לציין את שם המחלקה של הרכיב [urlManager|CWebApplication::urlManager] מאחר והוא כבר מוגדר מראש כ-[CUrlManager] ב [CWebApplication]. - -» Tip|טיפ: הקישורים הנוצרים על ידי המתודה [createUrl|CController::createUrl] הם קישורים יחסיים (רלטיביים ללא ה //:http ). כדי לקבל את הקישור המלא (הכולל את //:http) אנו יכולים להשתמש ב `Yii::app()-»hostInfo` לפני שאנו קוראים למתודה [createUrl|CController::createUrl], או לקרוא למתודה [createAbsoluteUrl|CController::createAbsoluteUrl]. - -קישורים ידידותיים ------------------- - -בעת השימוש בפורמט `path` כפורמט של הקישורים, אנו יכולים לקבוע כמה כללים עבור הקישורים שלנו כדי לגרום להם להיות ידידותיים אף יותר. לדוגמא, אנו יכולים ליצור קישור קצר כמו `post/100/`, במקום הקישור הארוך `index.php/post/read/id/100/`. כללי הקישורים משומשים על ידי [CUrlManager] עבור יצירת הקישורים וניתוחם. - -כדי להגדיר כללים לקישורים, אנו צריכים להגדיר את המאפיין [rules|CUrlManager::rules] של הרכיב [urlManager|CWebApplication::urlManager]: - -~~~ -[php] -array( - ...... - 'components'=»array( - ...... - 'urlManager'=»array( - 'urlFormat'=»'path', - 'rules'=»array( - 'pattern1'=»'route1', - 'pattern2'=»'route2', - 'pattern3'=»'route3', - ), - ), - ), -); -~~~ - -הכללים מוגדרים כמערך המכיל זוגות של דפוס-נתב, כל אחד מקביל לכלל אחד. הדפוס של הכלל הוא סטרינג הנועד להתאים את החלק המכיל את המידע אודות הנתיב המגיע מבקשת המשתמש. והנתב של הכלל צריך להתייחס [לנתב](/doc/guide/basics.controller#route) תקין של קונטרולר. - -מלבד פורמט הכללים המוצג למעלה, כלל יכול להכיל אפשרויות בתור פרמטרים, כפי שמוצג בדוגמא הבאה: - -~~~ -[php] -'pattern1'=»array('route1', 'urlSuffix'=»'.xml', 'caseSensitive'=»false) -~~~ - -בקוד המוצג למעלה, המערך מכיל רשימה של אפשרויות המותאמות אישית. החל מגרסא 1.1.0, ניתן להשתמש באפשרויות הבאות: - -- [urlSuffix|CUrlRule::urlSuffix]: סיומת הקישור המוגדרת ספציפית לכלל זה. כברירת מחדל אפשרות זו מוגדרת כ-null, שאומר שימוש בערך המוגדר במאפיין [CUrlManager::urlSuffix]. - -- [caseSensitive|CUrlRule::caseSensitive]: אפשרות זו מציינת אם הכלל הזה רגיש לאותיות גדולות-קטנות. כברירת מחדל אפשרות זו מוגדרת כ-null, שאומר שימוש בערך המוגדר במאפיין [CUrlManager::caseSensitive]. - -- [defaultParams|CUrlRule::defaultParams]: הפרמטרים (GET בפורמט של מפתח=»ערך) שיופיעו בקישור. שהכלל הזה ינותח עבור הבקשה הנכנסת, הערכים שהוגדרו במאפיין זה יוזרקו לפרמטרים של GET_$ באפליקציה. - -- [matchValue|CUrlRule::matchValue]: אפשרות זו קובעת אם הערכים בפרמטרים (GET) צריכים להיות תואמים לדפוס של כלל הקישור. כברירת מחדל אפשרות זו מוגדר כ-null, שאומר שימוש בערך המוגדר במאפיין [CUrlManager::matchValue]. במידה והאפשרות הזו מוגדרת לערך השווה ל-false, זה אומר שיהיה שימוש בכלל ליצירת הקישור אם הניתוב ושמות הפרמטרים תואמים לאלו שהוגדרו במאפיין זה. במידה והערך הזה מוגדר ל-true, אז הערכים בפרמטרים שהוגדרו צריכים להיות תואמים לערכים המוגדרים בדפוס של הכלל. יש לזכור שהגדרת הערך הזה ל-true משפיע על הביצועים של האפליקציה לרעה. - -### שימוש בשמות פרמטרים - -כלל יכול להיות מקושר לכמה פרמטרים (GET). פרמטרים אלו מופיעים בדפוס הכלל כסימונים מיוחדים בפורמט הבא: - -~~~ -<ParamName:ParamPattern> -~~~ - -כשהערך `ParamName` מציין את שם הפרמטר GET, והפרמטר האופציונלי `ParamPattern` מציין את הביטוי הרגולרי שבעזרתו תתבצע בדיקת התאמה לערך בפרמטר GET. במקרה והערך `ParamPattern` חסר, זה אומר שהפרמטר צריך להיות תואם לכל תו מלבד סלאש (`/`). בעת יצירת הקישור, הסימונים המיוחדים הללו בפרמטרים יוחלפו עם הערכים המקבלים שלהם; בעת ניתוח קישור, הפרמטרים שהתקבלו יאוכלסו אל תוך השתנה GET_$ עם התוצאה שנותחה. - -הבא ונשתמש בכמה דוגמאות כדי להסביר כיצד כללי קישורים עובדים. אנו מניחים שהכללים שלנו מכילים שלוש כללים: - -~~~ -[php] -array( - 'posts'=»'post/list', - 'post/«id:\d+»'=»'post/read', - 'post/«year:\d{4}»/«title»'=»'post/read', -) -~~~ - -- קריאה ל- - -~~~ -$this-»createUrl('post/list') -~~~ - -תיצור `index.php/posts/`. החוק הראשון נמצא תואם. - -- קריאה ל- - -~~~ -$this-»createUrl('post/read',array('id'=»100)) -~~~ - -תיצור `index.php/post/100/`. החוק השני נמצא תואם. - -- קריאה ל- - -~~~ -$this-»createUrl('post/read',array('year'=»2008,'title'=»'a -sample post')) -~~~ - -תיצור `index.php/post/2008/a%20sample%20post/`. החוק השלישי נמצא תואם. - -- קריאה ל- - -~~~ -$this-»createUrl('post/read') -~~~ - -תיצור `index.php/post/read/`. אף אחד מהכללים לא נמצא תואם. - - -בקצרה, בעת השימוש ב-[createUrl|CController::createUrl] כדי ליצור קישור, הנתב והפרמטרים המועברים למתודה שעליהם מתבצעת התאמה כדי להחליט איזה כלל תואם לקישור זה ויש צורך להשתמש בו. אם כל פרמטר המצורף לכלל נמצא בפרמטרים של GET שהועברו למתודה [createUrl|CController::createUrl], ואם הנתב של הכלל תואם לפרמטר של הניתוב שהועבר למתודה, כלל זה יכנס לשימוש בכדי ליצור את הקישור. - -אם הפרמטרים GET שהועברו למתודה [createUrl|CController::createUrl] הם יותר מאלו שדרושים על ידי הכלל, הפרמטרים הנוספים יופיעו במחרוזת השאילתה (בקישור). לדוגמא, - -אם נקרא ל- - -~~~ -$this-»createUrl('post/read',array('id'=»100,'year'=»2008)) -~~~ - -אנו נקבל את הקישור `index.php/post/100?year=2008/`. כדי להציג את הפרמטרים הנוספים הללו כחלק ידידותי של הקישור (כדי שיופרדו על ידי סלאש (`/`) כמו השאר ), אנו צריכים להוסיף את `*/` אל סוף הכלל. -לכן, בעזרת הכלל - - -~~~ -post/«id:\d+»/* -~~~ - -אנו יכולים לקבל קישור הנראה כך: `index.php/post/100/year/2008/`. - -כפי שכבר ציינו, ישנה מטרה נוספת לכללים והיא לנתח את הבקשות הנכנסות. כמובן, שזהו תהליך הפוך מיצירת הקישור. לדוגמא, ברגע שהמשתמש מבקש את `index.php/post/100/`, הכלל השני בדוגמא למעלה יכנס לשימוש מאחר והוא זה שהותאם ראשון, אשר מכוון לנתב `post/read` (קונטרולר `post` והפעולה `read` בתוכו) ניתן לגשת לפרמטר GET - -~~~ -array('id'=»100) -~~~ - -בעזרת GET_$. - - -» Note|הערה: שימוש בכללים עבור הקישורים ישפיע על ביצועי האפליקציה לרעה. הסיבה לכך היא שבעת ניתוח קישור הבקשה, [CUrlManager] ינסה להתאים אותו מול כל הכללים הקיימים עד שאחד יהיה תואם. ככל שישנם יותר כללים, גדלה ההשפעה לרעה על הביצועים. לכן, אפליקציה עם כניסות רבות ותעבורה גדולה צריכה לצמצם את מספר הכללים שנעשה בהם שימוש. - -### שימוש בפרמטרים במסלולים - -החל מגרסא 1.0.5, אנו יכולים לפנות לפרמטרים בחלק של המסלול בכלל אשר אנו מגדירים. אפשרות זו מתירה לכלל להיות תואם למספר מסלולים בהתבסס על הקריטריון שנמצא תואם. כמו כן זה יכול לעזור להפחית את מספר הכללים הדרושים לאפליקציה, ובכך לשפר את ביצועי המערכת. - -אנו משתמשים בכללים הבאים כדי להדגים כיצד להגדיר כללים עם פרמטרים: - -~~~ -[php] -array( - '«_c:(post|comment)»/«id:\d+»/«_a:(create|update|delete)»' =» '«_c»/«_a»', - '«_c:(post|comment)»/«id:\d+»' =» '«_c»/read', - '«_c:(post|comment)»s' =» '«_c»/list', -) -~~~ - -בקוד המוצג למעלה, אנו משתמשים בשני פרמטרים בחלק של המסלול בכלל: `c_` ו `a_`. הראשון תואם לקונטרולר שהוא `post` או `comment`, בזמן שהשני תואם לשם פעולה שיכולה להיות `create`, `update` או `delete`. ניתן לשנות את שמות הפרמטרים כל עוד הם לא יתנגשו עם פרמטרים שיכולים להופיע כפרמטרים של GET בקישור. - -שימוש בכללים המוצגים למעלה, הקישור `index.php/post/123/create/` ינותח כמסלול אל `post/create` עם הפרמטרים `id=123` מופיעים במערך של GET_$. ונניח שיש ברשותנו את המסלול `comment/list` ופרמטר GET שהוא `page=2`, אנו יכולים ליצור קישור `index.php/comments?page=2`. - -### הגדרת פרמטרים כסאב-דומיינים - -החל מגרסא 1.0.11, ניתן להוסיף את החלק של הסאב-דומיינים לכללים כדי לנתח וליצור קישורים. ניתן להגדיר פרמטר כסאב-דומיין כדי לחלץ אותו לאחר מכן כפרמטר GET. לדוגמא, את הקישור `http://admin.example.com/en/profile` ניתן לנתח אל תוך פרמטרים של `user=admin`ו `lang=en`. מצד שני, ניתן להשתמש בכללים המכילים סאב-דומיינים ליצירת קישורים. - -בכדי להשתמש בכללים המכילים פרמטרים בחלק של הסאב-דומיין, יש להגדיר את הכללים עם פרטי הדומיין, לדוגמא: - -~~~ -[php] -array( - 'http://«user:\w+».example.com/«lang:\w+»/profile' =» 'user/profile', -) -~~~ - -הדוגמא למעלה מציינת שהקטע הראשון בסאב-דומיין צריך להיות מיוחס כפרמטר `user` בזמן שהקטע הראשון בחלק לאחר הדומיין צריך להיות מיוחס כפרמטר `lang`. הכלל מתייחס למסלול `user/profile`. - -יש לזכור ש-[CUrlManager::showScriptName] לא יקח בחשבון כקישור יווצר בעזרת כלל המשתמש בפרמטרים בסאב-דומיין. - -כמו כן יש לזכור שכללים עם פרמטרים כסאב-דומיין לא צריכים להכיל את שם תת-התיקיה אם האפליקציה נמצאת בתת-תיקיה של תיקית הווב הראשית. לדוגמא, אם האפליקציה נמצאת תחת `http://www.example.com/sandbox/blog`, אז אנו עדיין צריכים להשתמש באותה השיטה כמו שהוסבר למעלה ללא ציון שם תת-התיקיה `sandbox/blog` אלה פשוט `blog`. - -### הסתרת `index.php` - -ישנו דבר נוסף שאנו יכולים לבצע כדי לנקות את הקישורים אף יותר, להסתיר את קובץ הכניסה הראשי `index.php` מהקישורים. זה דורש מאתנו להגדיר את השרת ואת הרכיב [urlManager|CWebApplication::urlManager]. - -אנו קודם צריכים להגדיר את שרת הווב ככה שקישורים המגיעים ללא ציון סקריפט הכניסה הראשי עדיין יהיה ניתן לנהל אותם בעזרת הסקריפט. עבור שרתי [Apache](http://httpd.apache.org/), ניתן לבצע זאת על ידי הפעלת התוסף `mod_rewrite` המנהל את כל הנושא של ניתוב מסלולים. אנו יכולים ליצור את הקובץ `wwwroot/blog/.htaccess/` עם התוכן הבא. -דע שניתן להשתמש באותו התוכן בקובץ הגדרות שרת Apache בתוך האלמנט `Directory` עבור `wwwroot/blog/`. - -~~~ -Options +FollowSymLinks -IndexIgnore */* -RewriteEngine on - -# if a directory or a file exists, use it directly -RewriteCond %{REQUEST_FILENAME} !-f -RewriteCond %{REQUEST_FILENAME} !-d - -# otherwise forward it to index.php -RewriteRule . index.php -~~~ - -לאחר מכן אנו מגדירים את המאפיין [showScriptName|CUrlManager::showScriptName] הנמצא ברכיב האפליקציה [urlManager|CWebApplication::urlManager] לערך השווה ל-`false`. - -כעת אם אנו נקרא למתודה - -~~~ -$this-»createUrl('post/read',array('id'=»100)) -~~~ - -אנו נקבל את הקישור `post/100/`. יותר חשוב, שהאפליקציה שלנו תוכל לנתח ולזהות את הקישור. - -### זיוף סיומת הקישור - -אנו גם יכולים להוסיף סיומת לקישורים. לדוגמא, אנו יכולים לקבל `post/100.html/` במקום `post/100/`. זה נותן תחושה לקישור שהוא עמוד סטטי. בכדי לבצע זאת, עליכם פשוט להגדיר את המאפיין [urlSuffix|CUrlManager::urlSuffix] ברכיב האפליקציה [urlManager|CWebApplication::urlManager] לכל סיומת שתרצו (לדוגמא, `html.` או `xml.` וכדומה...). - +ניהול קישורים +============== + +ניהול קישורים מלא עבור אפליקציות ווב כרוך בשני היבטים. ראשית, ברגע שבקשת המשתמש מתקבלת בפורמט של קישור, האפליקציה צריכה לעבד את הקישור לפרמטרים שניתן להבין אותם. שנית, האפליקציה צריכה לספק דרך ליצירת קישורים בצורה כזו שהאפליקציה תוכל להבין את הקישורים הנוצרים לאחר מכן. עבור אפליקצית Yii, היבטים אלו ממומשים בעזרת [CUrlManager]. + +יצירת קישורים +------------- + +למרות שניתן לכתוב קישורים מלאים בצורה הרגילה והמלאה בקבצי תצוגה, רצוי ליצור אותם בצורה דינאמית כדי לאפשר מקסימום גמישות: + +~~~ +[php] +$url=$this-»createUrl($route,$params); +~~~ + +האובייקט `this$` מתייחס לאובייקט של הקונטרולר הנוכחי; `route$` מציין את [נתב](/doc/guide/basics.controller#route) של הבקשה; ו `params$` הנו מערך של אלמנטים המייצג רשימה של פרמטרים (GET) אשר יצורפו לקישור. + +כברירת מחדל, קישורים הנוצרים על ידי [createUrl|CController::createUrl] הם בפורמט הנקרא `get`. לדוגמא, בהנחה שיש ברשותנו את הנתונים + + + +~~~ + +$route='post/read', $params=array('id' =» 100) +~~~ + + +אנו נקבל את הקישור הבא: + +~~~ +/index.php?r=post/read&id=100 +~~~ + +כשהפרמטרים מופיעים בקישור כרשימה של `מפתח=ערך` המחוברים אחד לשני בעזרת התו `&`, והפרמטר `r` מציין את בקשת [הנתב](/doc/guide/basics.controller#route). הפורמט הזה אינו ידידותי כל כך למשתמש מאחר והוא מכיל כמה ערכים שהם אינם בהכרח תווים אלפאנומריים (אותיות ומספרים). + +אנו יכולים ליצור את אותו הקישור בצורה נקייה וידידותית יותר למשתמש על ידי שימוש בפורמט שנקרא `path` המסיר את השאילתה מהמחרוזת של הקישור ומוסיף את הפרמטרים (GET) כחלק מהנתיב: + +~~~ +/index.php/post/read/id/100 +~~~ + +בכדי לשנות את פורמט הקישורים, אנו צריכים להגדיר את המאפיין [urlManager|CWebApplication::urlManager] בהגדרות האפליקציה כדי שבעת השימוש ב-[createUrl|CController::createUrl] המתודה תדע להחליף את הקישורים לפורמט החדש והאפליקציה תוכל להבין ולנתח את הקישורים החדשים: + +~~~ +[php] +array( + ...... + 'components'=»array( + ...... + 'urlManager'=»array( + 'urlFormat'=»'path', + ), + ), +); +~~~ + +זכור שאנו לא צריכים לציין את שם המחלקה של הרכיב [urlManager|CWebApplication::urlManager] מאחר והוא כבר מוגדר מראש כ-[CUrlManager] ב [CWebApplication]. + +» Tip|טיפ: הקישורים הנוצרים על ידי המתודה [createUrl|CController::createUrl] הם קישורים יחסיים (רלטיביים ללא ה //:http ). כדי לקבל את הקישור המלא (הכולל את //:http) אנו יכולים להשתמש ב `Yii::app()-»hostInfo` לפני שאנו קוראים למתודה [createUrl|CController::createUrl], או לקרוא למתודה [createAbsoluteUrl|CController::createAbsoluteUrl]. + +קישורים ידידותיים +------------------ + +בעת השימוש בפורמט `path` כפורמט של הקישורים, אנו יכולים לקבוע כמה כללים עבור הקישורים שלנו כדי לגרום להם להיות ידידותיים אף יותר. לדוגמא, אנו יכולים ליצור קישור קצר כמו `post/100/`, במקום הקישור הארוך `index.php/post/read/id/100/`. כללי הקישורים משומשים על ידי [CUrlManager] עבור יצירת הקישורים וניתוחם. + +כדי להגדיר כללים לקישורים, אנו צריכים להגדיר את המאפיין [rules|CUrlManager::rules] של הרכיב [urlManager|CWebApplication::urlManager]: + +~~~ +[php] +array( + ...... + 'components'=»array( + ...... + 'urlManager'=»array( + 'urlFormat'=»'path', + 'rules'=»array( + 'pattern1'=»'route1', + 'pattern2'=»'route2', + 'pattern3'=»'route3', + ), + ), + ), +); +~~~ + +הכללים מוגדרים כמערך המכיל זוגות של דפוס-נתב, כל אחד מקביל לכלל אחד. הדפוס של הכלל הוא סטרינג הנועד להתאים את החלק המכיל את המידע אודות הנתיב המגיע מבקשת המשתמש. והנתב של הכלל צריך להתייחס [לנתב](/doc/guide/basics.controller#route) תקין של קונטרולר. + +מלבד פורמט הכללים המוצג למעלה, כלל יכול להכיל אפשרויות בתור פרמטרים, כפי שמוצג בדוגמא הבאה: + +~~~ +[php] +'pattern1'=»array('route1', 'urlSuffix'=»'.xml', 'caseSensitive'=»false) +~~~ + +בקוד המוצג למעלה, המערך מכיל רשימה של אפשרויות המותאמות אישית. החל מגרסא 1.1.0, ניתן להשתמש באפשרויות הבאות: + +- [urlSuffix|CUrlRule::urlSuffix]: סיומת הקישור המוגדרת ספציפית לכלל זה. כברירת מחדל אפשרות זו מוגדרת כ-null, שאומר שימוש בערך המוגדר במאפיין [CUrlManager::urlSuffix]. + +- [caseSensitive|CUrlRule::caseSensitive]: אפשרות זו מציינת אם הכלל הזה רגיש לאותיות גדולות-קטנות. כברירת מחדל אפשרות זו מוגדרת כ-null, שאומר שימוש בערך המוגדר במאפיין [CUrlManager::caseSensitive]. + +- [defaultParams|CUrlRule::defaultParams]: הפרמטרים (GET בפורמט של מפתח=»ערך) שיופיעו בקישור. שהכלל הזה ינותח עבור הבקשה הנכנסת, הערכים שהוגדרו במאפיין זה יוזרקו לפרמטרים של GET_$ באפליקציה. + +- [matchValue|CUrlRule::matchValue]: אפשרות זו קובעת אם הערכים בפרמטרים (GET) צריכים להיות תואמים לדפוס של כלל הקישור. כברירת מחדל אפשרות זו מוגדר כ-null, שאומר שימוש בערך המוגדר במאפיין [CUrlManager::matchValue]. במידה והאפשרות הזו מוגדרת לערך השווה ל-false, זה אומר שיהיה שימוש בכלל ליצירת הקישור אם הניתוב ושמות הפרמטרים תואמים לאלו שהוגדרו במאפיין זה. במידה והערך הזה מוגדר ל-true, אז הערכים בפרמטרים שהוגדרו צריכים להיות תואמים לערכים המוגדרים בדפוס של הכלל. יש לזכור שהגדרת הערך הזה ל-true משפיע על הביצועים של האפליקציה לרעה. + +### שימוש בשמות פרמטרים + +כלל יכול להיות מקושר לכמה פרמטרים (GET). פרמטרים אלו מופיעים בדפוס הכלל כסימונים מיוחדים בפורמט הבא: + +~~~ +<ParamName:ParamPattern> +~~~ + +כשהערך `ParamName` מציין את שם הפרמטר GET, והפרמטר האופציונלי `ParamPattern` מציין את הביטוי הרגולרי שבעזרתו תתבצע בדיקת התאמה לערך בפרמטר GET. במקרה והערך `ParamPattern` חסר, זה אומר שהפרמטר צריך להיות תואם לכל תו מלבד סלאש (`/`). בעת יצירת הקישור, הסימונים המיוחדים הללו בפרמטרים יוחלפו עם הערכים המקבלים שלהם; בעת ניתוח קישור, הפרמטרים שהתקבלו יאוכלסו אל תוך השתנה GET_$ עם התוצאה שנותחה. + +הבא ונשתמש בכמה דוגמאות כדי להסביר כיצד כללי קישורים עובדים. אנו מניחים שהכללים שלנו מכילים שלוש כללים: + +~~~ +[php] +array( + 'posts'=»'post/list', + 'post/«id:\d+»'=»'post/read', + 'post/«year:\d{4}»/«title»'=»'post/read', +) +~~~ + +- קריאה ל- + +~~~ +$this-»createUrl('post/list') +~~~ + +תיצור `index.php/posts/`. החוק הראשון נמצא תואם. + +- קריאה ל- + +~~~ +$this-»createUrl('post/read',array('id'=»100)) +~~~ + +תיצור `index.php/post/100/`. החוק השני נמצא תואם. + +- קריאה ל- + +~~~ +$this-»createUrl('post/read',array('year'=»2008,'title'=»'a +sample post')) +~~~ + +תיצור `index.php/post/2008/a%20sample%20post/`. החוק השלישי נמצא תואם. + +- קריאה ל- + +~~~ +$this-»createUrl('post/read') +~~~ + +תיצור `index.php/post/read/`. אף אחד מהכללים לא נמצא תואם. + + +בקצרה, בעת השימוש ב-[createUrl|CController::createUrl] כדי ליצור קישור, הנתב והפרמטרים המועברים למתודה שעליהם מתבצעת התאמה כדי להחליט איזה כלל תואם לקישור זה ויש צורך להשתמש בו. אם כל פרמטר המצורף לכלל נמצא בפרמטרים של GET שהועברו למתודה [createUrl|CController::createUrl], ואם הנתב של הכלל תואם לפרמטר של הניתוב שהועבר למתודה, כלל זה יכנס לשימוש בכדי ליצור את הקישור. + +אם הפרמטרים GET שהועברו למתודה [createUrl|CController::createUrl] הם יותר מאלו שדרושים על ידי הכלל, הפרמטרים הנוספים יופיעו במחרוזת השאילתה (בקישור). לדוגמא, + +אם נקרא ל- + +~~~ +$this-»createUrl('post/read',array('id'=»100,'year'=»2008)) +~~~ + +אנו נקבל את הקישור `index.php/post/100?year=2008/`. כדי להציג את הפרמטרים הנוספים הללו כחלק ידידותי של הקישור (כדי שיופרדו על ידי סלאש (`/`) כמו השאר ), אנו צריכים להוסיף את `*/` אל סוף הכלל. +לכן, בעזרת הכלל - + +~~~ +post/«id:\d+»/* +~~~ + +אנו יכולים לקבל קישור הנראה כך: `index.php/post/100/year/2008/`. + +כפי שכבר ציינו, ישנה מטרה נוספת לכללים והיא לנתח את הבקשות הנכנסות. כמובן, שזהו תהליך הפוך מיצירת הקישור. לדוגמא, ברגע שהמשתמש מבקש את `index.php/post/100/`, הכלל השני בדוגמא למעלה יכנס לשימוש מאחר והוא זה שהותאם ראשון, אשר מכוון לנתב `post/read` (קונטרולר `post` והפעולה `read` בתוכו) ניתן לגשת לפרמטר GET + +~~~ +array('id'=»100) +~~~ + +בעזרת GET_$. + + +» Note|הערה: שימוש בכללים עבור הקישורים ישפיע על ביצועי האפליקציה לרעה. הסיבה לכך היא שבעת ניתוח קישור הבקשה, [CUrlManager] ינסה להתאים אותו מול כל הכללים הקיימים עד שאחד יהיה תואם. ככל שישנם יותר כללים, גדלה ההשפעה לרעה על הביצועים. לכן, אפליקציה עם כניסות רבות ותעבורה גדולה צריכה לצמצם את מספר הכללים שנעשה בהם שימוש. + +### שימוש בפרמטרים במסלולים + +החל מגרסא 1.0.5, אנו יכולים לפנות לפרמטרים בחלק של המסלול בכלל אשר אנו מגדירים. אפשרות זו מתירה לכלל להיות תואם למספר מסלולים בהתבסס על הקריטריון שנמצא תואם. כמו כן זה יכול לעזור להפחית את מספר הכללים הדרושים לאפליקציה, ובכך לשפר את ביצועי המערכת. + +אנו משתמשים בכללים הבאים כדי להדגים כיצד להגדיר כללים עם פרמטרים: + +~~~ +[php] +array( + '«_c:(post|comment)»/«id:\d+»/«_a:(create|update|delete)»' =» '«_c»/«_a»', + '«_c:(post|comment)»/«id:\d+»' =» '«_c»/read', + '«_c:(post|comment)»s' =» '«_c»/list', +) +~~~ + +בקוד המוצג למעלה, אנו משתמשים בשני פרמטרים בחלק של המסלול בכלל: `c_` ו `a_`. הראשון תואם לקונטרולר שהוא `post` או `comment`, בזמן שהשני תואם לשם פעולה שיכולה להיות `create`, `update` או `delete`. ניתן לשנות את שמות הפרמטרים כל עוד הם לא יתנגשו עם פרמטרים שיכולים להופיע כפרמטרים של GET בקישור. + +שימוש בכללים המוצגים למעלה, הקישור `index.php/post/123/create/` ינותח כמסלול אל `post/create` עם הפרמטרים `id=123` מופיעים במערך של GET_$. ונניח שיש ברשותנו את המסלול `comment/list` ופרמטר GET שהוא `page=2`, אנו יכולים ליצור קישור `index.php/comments?page=2`. + +### הגדרת פרמטרים כסאב-דומיינים + +החל מגרסא 1.0.11, ניתן להוסיף את החלק של הסאב-דומיינים לכללים כדי לנתח וליצור קישורים. ניתן להגדיר פרמטר כסאב-דומיין כדי לחלץ אותו לאחר מכן כפרמטר GET. לדוגמא, את הקישור `http://admin.example.com/en/profile` ניתן לנתח אל תוך פרמטרים של `user=admin`ו `lang=en`. מצד שני, ניתן להשתמש בכללים המכילים סאב-דומיינים ליצירת קישורים. + +בכדי להשתמש בכללים המכילים פרמטרים בחלק של הסאב-דומיין, יש להגדיר את הכללים עם פרטי הדומיין, לדוגמא: + +~~~ +[php] +array( + 'http://«user:\w+».example.com/«lang:\w+»/profile' =» 'user/profile', +) +~~~ + +הדוגמא למעלה מציינת שהקטע הראשון בסאב-דומיין צריך להיות מיוחס כפרמטר `user` בזמן שהקטע הראשון בחלק לאחר הדומיין צריך להיות מיוחס כפרמטר `lang`. הכלל מתייחס למסלול `user/profile`. + +יש לזכור ש-[CUrlManager::showScriptName] לא יקח בחשבון כקישור יווצר בעזרת כלל המשתמש בפרמטרים בסאב-דומיין. + +כמו כן יש לזכור שכללים עם פרמטרים כסאב-דומיין לא צריכים להכיל את שם תת-התיקיה אם האפליקציה נמצאת בתת-תיקיה של תיקית הווב הראשית. לדוגמא, אם האפליקציה נמצאת תחת `http://www.example.com/sandbox/blog`, אז אנו עדיין צריכים להשתמש באותה השיטה כמו שהוסבר למעלה ללא ציון שם תת-התיקיה `sandbox/blog` אלה פשוט `blog`. + +### הסתרת `index.php` + +ישנו דבר נוסף שאנו יכולים לבצע כדי לנקות את הקישורים אף יותר, להסתיר את קובץ הכניסה הראשי `index.php` מהקישורים. זה דורש מאתנו להגדיר את השרת ואת הרכיב [urlManager|CWebApplication::urlManager]. + +אנו קודם צריכים להגדיר את שרת הווב ככה שקישורים המגיעים ללא ציון סקריפט הכניסה הראשי עדיין יהיה ניתן לנהל אותם בעזרת הסקריפט. עבור שרתי [Apache](http://httpd.apache.org/), ניתן לבצע זאת על ידי הפעלת התוסף `mod_rewrite` המנהל את כל הנושא של ניתוב מסלולים. אנו יכולים ליצור את הקובץ `wwwroot/blog/.htaccess/` עם התוכן הבא. +דע שניתן להשתמש באותו התוכן בקובץ הגדרות שרת Apache בתוך האלמנט `Directory` עבור `wwwroot/blog/`. + +~~~ +Options +FollowSymLinks +IndexIgnore */* +RewriteEngine on + +# if a directory or a file exists, use it directly +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d + +# otherwise forward it to index.php +RewriteRule . index.php +~~~ + +לאחר מכן אנו מגדירים את המאפיין [showScriptName|CUrlManager::showScriptName] הנמצא ברכיב האפליקציה [urlManager|CWebApplication::urlManager] לערך השווה ל-`false`. + +כעת אם אנו נקרא למתודה + +~~~ +$this-»createUrl('post/read',array('id'=»100)) +~~~ + +אנו נקבל את הקישור `post/100/`. יותר חשוב, שהאפליקציה שלנו תוכל לנתח ולזהות את הקישור. + +### זיוף סיומת הקישור + +אנו גם יכולים להוסיף סיומת לקישורים. לדוגמא, אנו יכולים לקבל `post/100.html/` במקום `post/100/`. זה נותן תחושה לקישור שהוא עמוד סטטי. בכדי לבצע זאת, עליכם פשוט להגדיר את המאפיין [urlSuffix|CUrlManager::urlSuffix] ברכיב האפליקציה [urlManager|CWebApplication::urlManager] לכל סיומת שתרצו (לדוגמא, `html.` או `xml.` וכדומה...). + «div class="revision"»$Id: topics.url.txt 1809 2010-02-17 22:08:34Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/topics.webservice.txt b/docs/guide/he/topics.webservice.txt index f91377202..0372362bb 100644 --- a/docs/guide/he/topics.webservice.txt +++ b/docs/guide/he/topics.webservice.txt @@ -1,173 +1,173 @@ -שירותים חיצוניים -=========== - -[שרותים חיצוניים](http://en.wikipedia.org/wiki/Web_service) הינם מערכת אשר נועדה לתמוך את הקשר בין שני מחשבים על גבי רשת. במונחים של אפליקצית ווב, זה בדרך כלל מתייחס API שניתן לגשת אליו דרך הרשת ולבצע את פעולותיו על השרת החיצוני המבקש את השירות. -לדוגמא, אפליקצית צד לקוח של [Flex](http://www.adobe.com/products/flex/) יכול להריץ פונקציה הנמצאת בצד השרת של האפליקציה וכתובה ב PHP. השירותים החיצוניים מסתמכים על [SOAP](http://en.wikipedia.org/wiki/SOAP) כשכבת היסוד של הפרוטוקול המקשר. - -Yii מספקת את [CWebService] ו [CWebServiceAction] בכדי לפשט את תהליך העבודה ליישום שירותים אלו באפליקצית ווב. ה API מקובצים למחלקות, הנקראות *ספקי שירותים*. Yii מחולל אוטומטית לכל מחלקה מפרט [WSDL](http://www.w3.org/TR/wsdl) המתאר אילו פקודות API קיימות ואיך ניתן להריצן על ידי הלקוח. ברגע שפקודת API מורצת על ידי לקוח, Yii תאתחל את ספק השירות המתאים ותקרא ל API הרצוי בכדי למלא את הבקשה. - -» Note|הערה: א מסתמך על התוסף של PHP בשם [SOAP](http://www.php.net/manual/en/ref.soap.php). יש לוודא שהוא מותקן ומופעל לפני השימוש בדוגמאות בחלק זה. - -הגדרת ספק שירות -------------------------- - -כפי שציינו למעלה, ספק שירות הינו מחלקה אשר מגדירה מתודות שניתן לגשת אליהם מרחוק. Yii מסתמך על [תיעוד הערות](http://java.sun.com/j2se/javadoc/writingdoccomments/) ו [התבוננות מחלקה](http://php.net/manual/en/book.reflection.php) בכדי לזהות אילו מתודות ניתן לגשת אליהם מרחוק אילו פרמטרים הם מקבלים והערך שהם מחזירים. - -נתחיל עם שירות אגרות חוב. שירות זה מאפשר ללקוח לבקש את המחיר עבור אגרת החוב מסויימת. אנו מגדירים את ספק השירות בצורה הבאה. שימו לב שאנו מגדירים את מחלקת הספק `StockController` על ידי הרחבה של המחלקה [CController]. זה לא חובה. אנו נסביר למה אנו עושים זאת בהמשך. - -~~~ -[php] -class StockController extends CController -{ - /** - * @param string the symbol of the stock - * @return float the stock price - * @soap - */ - public function getPrice($symbol) - { - $prices=array('IBM'=»100, 'GOOGLE'=»350); - return isset($prices[$symbol])?$prices[$symbol]:0; - // החזרת הערך עבור אגרת החוב המנוקבת - } -} -~~~ - -בקוד למעלה, אנו מגדירים את המתודה `getPrice` להיות שירות API על ידי סימון המתודה עם התג `soap@` בתוך הערות התיעוד של המתודה. אנו מסתמכים על הערות התיעוד של המתודה בכדי לציין את סוג הנתון שהפרמטר הראשון במתודה מקבל ואת סוג הנתון המוחזר על ידי מתודה זו. ניתן להגדיר מתודות API נוספות על ידי הגדרתם בצורה דומה. - -הגדרת פעולת ספק שירות ----------------------------- - -לאחר הגדרת ספק השירות, אנו צריכים לאפשר גישה ללקוחות הרוצים להשתמש בה. במיוחד, אנו רוצים ליצור פעולה בקונטרולר כדי לחשוף את השירות. ניתן לבצע זאת בצורה פשוטה על ידי הגדרת פעולה [CWebServiceAction] בתוך מחלקת קונטרולר. עבור הדוגמא שלנו, אנו פשוט נוסיף אותו למחלקה `StockController`. - - -~~~ -[php] -class StockController extends CController -{ - public function actions() - { - return array( - 'quote'=»array( - 'class'=»'CWebServiceAction', - ), - ); - } - - /** - * @param string the symbol of the stock - * @return float the stock price - * @soap - */ - public function getPrice($symbol) - { - // החזרת הערך עבור אגרת החוב המנוקבת - } -} -~~~ - -זה כל מה שאנו צריכים לעשות בכדי ליצור שירות חיצוני! במידה וניגש לפעולה שיצרנו על ידי גישה לקישור `http://hostname/path/to/index.php?r=stock/quote`, אנו נראה הרבה תוכן XML שהוא בעצם ה WSDL לשירות החיצוני שהרגע הגדרנו. - -» Tip|הערה: כברירת מחדל, [CWebServiceAction] מניח שהקונטרולר הנוכחי הוא מספק השירות. זו הסיבה לזה שהגדרנו את המתודה `getPrice` בתוך המחלקה `StockController`. - -שימוש בשירות חיצוני ---------------------- - -בכדי להשלים את הדוגמא, אנו ניצור 'לקוח' שיצרוך את השירות החיצוני שיצרנו כרגע. הלקוח שצורך את השירות גם הוא כתוב ב PHP, אבל זה יכול להיות כתוב בשפות אחרות, כמו, `Java`, `C#`, `Flex`, ואחרים. - -~~~ -[php] -$client=new SoapClient('http://hostname/path/to/index.php?r=stock/quote'); -echo $client-»getPrice('GOOGLE'); -~~~ - -הרצת הסקריפט למעלה במסוף או בדפדפן, ואנו נראה `350` שזהו המחיר עבור `GOOGLE`. - -סוגי נתונים ----------- - -בעת הגדרת מתודות מחלקה ומאפיינים שניתן לגשת אליהם מרחוק, אנו צריכים להגדיר את סוגי הנתונים עבור הנתונים המגיעים והיוצאים. סוגי הנתונים הפרימיטיבים הבאים ניתנים לשימוש: - - - str/string: ממופה אל `xsd:string`; - - int/integer: ממופה אל `xsd:int`; - - float/double: ממופה אל `xsd:float`; - - bool/boolean: ממופה אל `xsd:boolean`; - - date: ממופה אל `xsd:date`; - - time: ממופה אל `xsd:time`; - - datetime: ממופה אל `xsd:dateTime`; - - array: ממופה אל `xsd:string`; - - object: ממופה אל `xsd:struct`; - - mixed: ממופה אל `xsd:anyType`. - -במידה וסוג הנתון לא נמצא ברשימה של הסוגים הפרימיטיבים, הוא נחשב כסוג מורכב המכיל מאפיינים. סוג מורכב מיוצג במונחים של מחלקה, והמאפיינים שלו הם המשתנים במחלקה המסומנים בעזרת `soap@` בתוך הערות התיעוד של המאפיין. - -כמו כן אנו יכולים להשתמש במערך כסוג הנתון על ידי צירוף של `[]` לסוף סוג הנתון הפרימיטיבי או המורכב. בצורה זו אנו מגדירים מערך של הסוג המיוחס. - -למטה מוצגת דוגמא להגדרת ה API עבור `getPosts` המחזיר מערך של אובייקטים של המחלקה `Post`. - -~~~ -[php] -class PostController extends CController -{ - /** - * @return Post[] a list of posts - * @soap - */ - public function getPosts() - { - return Post::model()-»findAll(); - } -} - -class Post extends CActiveRecord -{ - /** - * @var integer post ID - * @soap - */ - public $id; - /** - * @var string post title - * @soap - */ - public $title; - - public static function model($className=__CLASS__) - { - return parent::model($className); - } -} -~~~ - -מיפוי מחלקות -------------- - -בכדי לקבל את הפרמטרים של הסוגים המורכבים מהלקוח, אפליקציה צריכה להגדיר את המיפוי בין סוגי ה WSDL לבין מחלקות ה PHP המקבילים. -זה נעשה על ידי הגדרת המאפיין [classMap|CWebServiceAction::classMap] של המחלקה [CWebServiceAction]. - -~~~ -[php] -class PostController extends CController -{ - public function actions() - { - return array( - 'service'=»array( - 'class'=»'CWebServiceAction', - 'classMap'=»array( - 'Post'=»'Post', // או פשוט `Post` - ), - ), - ); - } - ...... -} -~~~ - - -יירוט הרצת מתודות מרחוק -------------------------------------- - -על ידי יישום הממשק [IWebServiceProvider], ספק שירות יכול ליירט הרצת מתודות מרחוק. במתודה [IWebServiceProvider::beforeWebMethod], הספק יכול לקבל את האובייקט הנוכחי של [CWebService] ולקבל את שם המתודה המבוקשת כרגע דרך [CWebService::methodName]. היא יכולה להחזיר false אם המתודה לא צריכה לרוץ מכל סיבה שהיא (לדוגמא גישה לא מורשית). - +שירותים חיצוניים +=========== + +[שרותים חיצוניים](http://en.wikipedia.org/wiki/Web_service) הינם מערכת אשר נועדה לתמוך את הקשר בין שני מחשבים על גבי רשת. במונחים של אפליקצית ווב, זה בדרך כלל מתייחס API שניתן לגשת אליו דרך הרשת ולבצע את פעולותיו על השרת החיצוני המבקש את השירות. +לדוגמא, אפליקצית צד לקוח של [Flex](http://www.adobe.com/products/flex/) יכול להריץ פונקציה הנמצאת בצד השרת של האפליקציה וכתובה ב PHP. השירותים החיצוניים מסתמכים על [SOAP](http://en.wikipedia.org/wiki/SOAP) כשכבת היסוד של הפרוטוקול המקשר. + +Yii מספקת את [CWebService] ו [CWebServiceAction] בכדי לפשט את תהליך העבודה ליישום שירותים אלו באפליקצית ווב. ה API מקובצים למחלקות, הנקראות *ספקי שירותים*. Yii מחולל אוטומטית לכל מחלקה מפרט [WSDL](http://www.w3.org/TR/wsdl) המתאר אילו פקודות API קיימות ואיך ניתן להריצן על ידי הלקוח. ברגע שפקודת API מורצת על ידי לקוח, Yii תאתחל את ספק השירות המתאים ותקרא ל API הרצוי בכדי למלא את הבקשה. + +» Note|הערה: א מסתמך על התוסף של PHP בשם [SOAP](http://www.php.net/manual/en/ref.soap.php). יש לוודא שהוא מותקן ומופעל לפני השימוש בדוגמאות בחלק זה. + +הגדרת ספק שירות +------------------------- + +כפי שציינו למעלה, ספק שירות הינו מחלקה אשר מגדירה מתודות שניתן לגשת אליהם מרחוק. Yii מסתמך על [תיעוד הערות](http://java.sun.com/j2se/javadoc/writingdoccomments/) ו [התבוננות מחלקה](http://php.net/manual/en/book.reflection.php) בכדי לזהות אילו מתודות ניתן לגשת אליהם מרחוק אילו פרמטרים הם מקבלים והערך שהם מחזירים. + +נתחיל עם שירות אגרות חוב. שירות זה מאפשר ללקוח לבקש את המחיר עבור אגרת החוב מסויימת. אנו מגדירים את ספק השירות בצורה הבאה. שימו לב שאנו מגדירים את מחלקת הספק `StockController` על ידי הרחבה של המחלקה [CController]. זה לא חובה. אנו נסביר למה אנו עושים זאת בהמשך. + +~~~ +[php] +class StockController extends CController +{ + /** + * @param string the symbol of the stock + * @return float the stock price + * @soap + */ + public function getPrice($symbol) + { + $prices=array('IBM'=»100, 'GOOGLE'=»350); + return isset($prices[$symbol])?$prices[$symbol]:0; + // החזרת הערך עבור אגרת החוב המנוקבת + } +} +~~~ + +בקוד למעלה, אנו מגדירים את המתודה `getPrice` להיות שירות API על ידי סימון המתודה עם התג `soap@` בתוך הערות התיעוד של המתודה. אנו מסתמכים על הערות התיעוד של המתודה בכדי לציין את סוג הנתון שהפרמטר הראשון במתודה מקבל ואת סוג הנתון המוחזר על ידי מתודה זו. ניתן להגדיר מתודות API נוספות על ידי הגדרתם בצורה דומה. + +הגדרת פעולת ספק שירות +---------------------------- + +לאחר הגדרת ספק השירות, אנו צריכים לאפשר גישה ללקוחות הרוצים להשתמש בה. במיוחד, אנו רוצים ליצור פעולה בקונטרולר כדי לחשוף את השירות. ניתן לבצע זאת בצורה פשוטה על ידי הגדרת פעולה [CWebServiceAction] בתוך מחלקת קונטרולר. עבור הדוגמא שלנו, אנו פשוט נוסיף אותו למחלקה `StockController`. + + +~~~ +[php] +class StockController extends CController +{ + public function actions() + { + return array( + 'quote'=»array( + 'class'=»'CWebServiceAction', + ), + ); + } + + /** + * @param string the symbol of the stock + * @return float the stock price + * @soap + */ + public function getPrice($symbol) + { + // החזרת הערך עבור אגרת החוב המנוקבת + } +} +~~~ + +זה כל מה שאנו צריכים לעשות בכדי ליצור שירות חיצוני! במידה וניגש לפעולה שיצרנו על ידי גישה לקישור `http://hostname/path/to/index.php?r=stock/quote`, אנו נראה הרבה תוכן XML שהוא בעצם ה WSDL לשירות החיצוני שהרגע הגדרנו. + +» Tip|הערה: כברירת מחדל, [CWebServiceAction] מניח שהקונטרולר הנוכחי הוא מספק השירות. זו הסיבה לזה שהגדרנו את המתודה `getPrice` בתוך המחלקה `StockController`. + +שימוש בשירות חיצוני +--------------------- + +בכדי להשלים את הדוגמא, אנו ניצור 'לקוח' שיצרוך את השירות החיצוני שיצרנו כרגע. הלקוח שצורך את השירות גם הוא כתוב ב PHP, אבל זה יכול להיות כתוב בשפות אחרות, כמו, `Java`, `C#`, `Flex`, ואחרים. + +~~~ +[php] +$client=new SoapClient('http://hostname/path/to/index.php?r=stock/quote'); +echo $client-»getPrice('GOOGLE'); +~~~ + +הרצת הסקריפט למעלה במסוף או בדפדפן, ואנו נראה `350` שזהו המחיר עבור `GOOGLE`. + +סוגי נתונים +---------- + +בעת הגדרת מתודות מחלקה ומאפיינים שניתן לגשת אליהם מרחוק, אנו צריכים להגדיר את סוגי הנתונים עבור הנתונים המגיעים והיוצאים. סוגי הנתונים הפרימיטיבים הבאים ניתנים לשימוש: + + - str/string: ממופה אל `xsd:string`; + - int/integer: ממופה אל `xsd:int`; + - float/double: ממופה אל `xsd:float`; + - bool/boolean: ממופה אל `xsd:boolean`; + - date: ממופה אל `xsd:date`; + - time: ממופה אל `xsd:time`; + - datetime: ממופה אל `xsd:dateTime`; + - array: ממופה אל `xsd:string`; + - object: ממופה אל `xsd:struct`; + - mixed: ממופה אל `xsd:anyType`. + +במידה וסוג הנתון לא נמצא ברשימה של הסוגים הפרימיטיבים, הוא נחשב כסוג מורכב המכיל מאפיינים. סוג מורכב מיוצג במונחים של מחלקה, והמאפיינים שלו הם המשתנים במחלקה המסומנים בעזרת `soap@` בתוך הערות התיעוד של המאפיין. + +כמו כן אנו יכולים להשתמש במערך כסוג הנתון על ידי צירוף של `[]` לסוף סוג הנתון הפרימיטיבי או המורכב. בצורה זו אנו מגדירים מערך של הסוג המיוחס. + +למטה מוצגת דוגמא להגדרת ה API עבור `getPosts` המחזיר מערך של אובייקטים של המחלקה `Post`. + +~~~ +[php] +class PostController extends CController +{ + /** + * @return Post[] a list of posts + * @soap + */ + public function getPosts() + { + return Post::model()-»findAll(); + } +} + +class Post extends CActiveRecord +{ + /** + * @var integer post ID + * @soap + */ + public $id; + /** + * @var string post title + * @soap + */ + public $title; + + public static function model($className=__CLASS__) + { + return parent::model($className); + } +} +~~~ + +מיפוי מחלקות +------------- + +בכדי לקבל את הפרמטרים של הסוגים המורכבים מהלקוח, אפליקציה צריכה להגדיר את המיפוי בין סוגי ה WSDL לבין מחלקות ה PHP המקבילים. +זה נעשה על ידי הגדרת המאפיין [classMap|CWebServiceAction::classMap] של המחלקה [CWebServiceAction]. + +~~~ +[php] +class PostController extends CController +{ + public function actions() + { + return array( + 'service'=»array( + 'class'=»'CWebServiceAction', + 'classMap'=»array( + 'Post'=»'Post', // או פשוט `Post` + ), + ), + ); + } + ...... +} +~~~ + + +יירוט הרצת מתודות מרחוק +------------------------------------- + +על ידי יישום הממשק [IWebServiceProvider], ספק שירות יכול ליירט הרצת מתודות מרחוק. במתודה [IWebServiceProvider::beforeWebMethod], הספק יכול לקבל את האובייקט הנוכחי של [CWebService] ולקבל את שם המתודה המבוקשת כרגע דרך [CWebService::methodName]. היא יכולה להחזיר false אם המתודה לא צריכה לרוץ מכל סיבה שהיא (לדוגמא גישה לא מורשית). + «div class="revision"»$Id: topics.webservice.txt 1808 2010-02-17 21:49:42Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/he/upgrade.txt b/docs/guide/he/upgrade.txt index 4e27132cd..9802e385c 100644 --- a/docs/guide/he/upgrade.txt +++ b/docs/guide/he/upgrade.txt @@ -1,63 +1,63 @@ -עדכון מגרסא 1.0 לגרסא 1.1 -================================= - -שינויים הקשורים לתסריטים במודלים ------------------------------------- - -- המתודה ()CModel::safeAttributes הוסרה. מאפיינים בטוחים מוגדרים כעת במתודה ()CModel::rules כאלמנטים שיש לאמת אותם לכל דבר בעזרת פונקציות אימות נתונים. - -- המתודות ()CModel::validate(), CModel::beforeValidate ו ()CModel::afterValidate השתנו. במתודות ()CModel::setAttributes(), CModel::getSafeAttributeNames הפרמטר 'scenario` הוסר. בכדי להגדיר את התסריט של המודל יש להגדיר את המאפיין CModel::scenario. - -- המתודה ()CModel::getValidators השתנתה והמתודה ()CModel::getValidatorsForAttribute הוסרה. המתודה ג עכשיו מחזירה רק את הולידטורים הקשורים לתסריט שהוגדר במאפיין במודל. - -- המתודה ()CModel::isAttributeRequired ו ()CModel::getValidatorsForAttribute השתנו. פרמטר התסריט הוסר. התסריט של המודל יכנס לשימוש במקום. - -- המאפיין CHtml::scenario הוסר. המחלקה CHtml תשתמש במאפיין התסריט של המודל. - -שינויים הקשורים לטעינה נלהבת ב ARR ---------------------------------------------------------------- - -- כברירת מחדל, יווצר ביטוי JOIN אחד לכל הקישורים המעורבים בטעינה הנלהבת. במידה וההגדרות `LIMIT` או `OFFSET` בשאילתה בטבלה הראשית מוגדרים, השאילתה תתבצע לבד קודם, לאחר מכן תתבצע שאילתה נוספת שתחזיר את כל האובייקטים המקושרים אליה. קודם לכן בגרסא 1.0, כברירת מחדל התבצעו `N+1` שאילתות SQL במידה וטעינה נלהבת מכילה `N` קישורים מסוג `HAS_MANY` או `MANY_MANY`. - -שינויים הקשורים לשמות מקוצרים לטבלאות ב ARR ------------------------------------------------------------- - -- שם הקיצור ברירת המחדל לטבלה מקושרת כעת הוא כשם הקישור עצמו. קודם לכן בגרסא 1.0, כברירת מחדל Yii היה יוצר שם מקוצר לכל טבלה מקושרת באופן אוטומטי, ואנו היינו צריכים להשתמש בקידומת `.??` בכדי להתייחס לשם המקוצר שנוצר אוטומטית. - -- שם המקוצר לטבלה הראשית בשאילתת AR הינו מוגדר ספציפית ל `t`. קודם לכן בגרסא 1.0, השם המקוצר היה זהה לשם הטבלה. זה יגרום לקוד AR נוכחי להפסיק לעבוד במידה והם הגדירו קידומת לעמודות בתור שם הטבלה ספציפית. הפתרון הוא להחליף את הקידומות הללו בקידומת `t.`. - -שינויים הקשורים לאיסוף קלט טבלאי ----------------------------------- - -- שמות מאפיינים, שימוש ב - - -~~~ -[php] - -Field[$i] - -~~~ -לא תקף יותר. כעת יש צורך בלכתוב אותם בצורה הבאה - -~~~ -[php] - -[$i]Field - -~~~ -בכדי שיהיה ניתן לתמוך במערכים עם שדות שונים, לדוגמא - -~~~ -[php] - -[$i]Field[$index] -~~~ - - -שינויים נוספים -------------- - -- החתימה של המתודה ההתחלתית של המחלקה [CActiveRecord] השתנתה. הפרמטר הראשון (רשימת התכונות) הוסר. - +עדכון מגרסא 1.0 לגרסא 1.1 +================================= + +שינויים הקשורים לתסריטים במודלים +------------------------------------ + +- המתודה ()CModel::safeAttributes הוסרה. מאפיינים בטוחים מוגדרים כעת במתודה ()CModel::rules כאלמנטים שיש לאמת אותם לכל דבר בעזרת פונקציות אימות נתונים. + +- המתודות ()CModel::validate(), CModel::beforeValidate ו ()CModel::afterValidate השתנו. במתודות ()CModel::setAttributes(), CModel::getSafeAttributeNames הפרמטר 'scenario` הוסר. בכדי להגדיר את התסריט של המודל יש להגדיר את המאפיין CModel::scenario. + +- המתודה ()CModel::getValidators השתנתה והמתודה ()CModel::getValidatorsForAttribute הוסרה. המתודה ג עכשיו מחזירה רק את הולידטורים הקשורים לתסריט שהוגדר במאפיין במודל. + +- המתודה ()CModel::isAttributeRequired ו ()CModel::getValidatorsForAttribute השתנו. פרמטר התסריט הוסר. התסריט של המודל יכנס לשימוש במקום. + +- המאפיין CHtml::scenario הוסר. המחלקה CHtml תשתמש במאפיין התסריט של המודל. + +שינויים הקשורים לטעינה נלהבת ב ARR +--------------------------------------------------------------- + +- כברירת מחדל, יווצר ביטוי JOIN אחד לכל הקישורים המעורבים בטעינה הנלהבת. במידה וההגדרות `LIMIT` או `OFFSET` בשאילתה בטבלה הראשית מוגדרים, השאילתה תתבצע לבד קודם, לאחר מכן תתבצע שאילתה נוספת שתחזיר את כל האובייקטים המקושרים אליה. קודם לכן בגרסא 1.0, כברירת מחדל התבצעו `N+1` שאילתות SQL במידה וטעינה נלהבת מכילה `N` קישורים מסוג `HAS_MANY` או `MANY_MANY`. + +שינויים הקשורים לשמות מקוצרים לטבלאות ב ARR +------------------------------------------------------------ + +- שם הקיצור ברירת המחדל לטבלה מקושרת כעת הוא כשם הקישור עצמו. קודם לכן בגרסא 1.0, כברירת מחדל Yii היה יוצר שם מקוצר לכל טבלה מקושרת באופן אוטומטי, ואנו היינו צריכים להשתמש בקידומת `.??` בכדי להתייחס לשם המקוצר שנוצר אוטומטית. + +- שם המקוצר לטבלה הראשית בשאילתת AR הינו מוגדר ספציפית ל `t`. קודם לכן בגרסא 1.0, השם המקוצר היה זהה לשם הטבלה. זה יגרום לקוד AR נוכחי להפסיק לעבוד במידה והם הגדירו קידומת לעמודות בתור שם הטבלה ספציפית. הפתרון הוא להחליף את הקידומות הללו בקידומת `t.`. + +שינויים הקשורים לאיסוף קלט טבלאי +---------------------------------- + +- שמות מאפיינים, שימוש ב + + +~~~ +[php] + +Field[$i] + +~~~ +לא תקף יותר. כעת יש צורך בלכתוב אותם בצורה הבאה + +~~~ +[php] + +[$i]Field + +~~~ +בכדי שיהיה ניתן לתמוך במערכים עם שדות שונים, לדוגמא + +~~~ +[php] + +[$i]Field[$index] +~~~ + + +שינויים נוספים +------------- + +- החתימה של המתודה ההתחלתית של המחלקה [CActiveRecord] השתנתה. הפרמטר הראשון (רשימת התכונות) הוסר. + «div class="revision"»$Id: upgrade.txt 1819 2010-02-18 22:56:55Z qiang.xue $«/div» \ No newline at end of file diff --git a/docs/guide/id/quickstart.apache-nginx-config.txt b/docs/guide/id/quickstart.apache-nginx-config.txt index fae046d29..e47f43a52 100644 --- a/docs/guide/id/quickstart.apache-nginx-config.txt +++ b/docs/guide/id/quickstart.apache-nginx-config.txt @@ -1,78 +1,78 @@ -Konfigurasi Apache dan Nginx -=================================== - -Apache ------- - -Yii siap berjalan dengan konfigurasi default web server Apache. File `.htaccess` di dalam framework Yii dan folder aplikasi melarang pengaksesan file-file. Untuk menyembunyikan file bootstrap (biasanya `index.php`) di dalam URL Anda bisa menambahkan `mod_rewrite` ke dalam file `.htaccess` di document root atau ke konfigurasi virtual host: - -~~~ -RewriteEngine on - -# jika sebuah direktori atau file ada, maka langsung gunakan ini -RewriteCond %{REQUEST_FILENAME} !-f -RewriteCond %{REQUEST_FILENAME} !-d -# jika tidak ada, maka arahkan ke index.php -RewriteRule . index.php -~~~ - - -Nginx ------ - -Anda dapat menggunakan Yii dengan [Nginx](http://wiki.nginx.org/) dan PHP dengan [FPM SAPI](http://php.net/install.fpm). -Berikut ini merupakan contoh konfigurasi host. File ini mendefinisikan file bootstrap dan membuat Yii menangkap semua request ke file yang tidak ada, sehingga memungkinkan kita untuk memiliki URL yang cantik. - -~~~ -server { - set $host_path "/www/mysite"; - access_log /www/mysite/log/access.log main; - - server_name mysite; - root $host_path/htdocs; - set $yii_bootstrap "index.php"; - - charset utf-8; - - location / { - index index.html $yii_bootstrap; - try_files $uri $uri/ $yii_bootstrap?$args; - } - - location ~ ^/(protected|framework|themes/\w+/views) { - deny all; - } - - #avoid processing of calls to unexisting static files by yii - location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { - try_files $uri =404; - } - - # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 - # - location ~ \.php { - fastcgi_split_path_info ^(.+\.php)(.*)$; - - #let yii catch the calls to unexising PHP files - set $fsn /$yii_bootstrap; - if (-f $document_root$fastcgi_script_name){ - set $fsn $fastcgi_script_name; - } - - fastcgi_pass 127.0.0.1:9000; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fsn; - - #PATH_INFO and PATH_TRANSLATED can be omitted, but RFC 3875 specifies them for CGI - fastcgi_param PATH_INFO $fastcgi_path_info; - fastcgi_param PATH_TRANSLATED $document_root$fsn; - } - - location ~ /\.ht { - deny all; - } -} -~~~ -Anda bisa set cgi.fix_pathinfo=0 di dalam php.ini jika menggunakan konfigurasi di atas guna menghindari pemanggilan stat() sistem yang tidak diperlukan. - +Konfigurasi Apache dan Nginx +=================================== + +Apache +------ + +Yii siap berjalan dengan konfigurasi default web server Apache. File `.htaccess` di dalam framework Yii dan folder aplikasi melarang pengaksesan file-file. Untuk menyembunyikan file bootstrap (biasanya `index.php`) di dalam URL Anda bisa menambahkan `mod_rewrite` ke dalam file `.htaccess` di document root atau ke konfigurasi virtual host: + +~~~ +RewriteEngine on + +# jika sebuah direktori atau file ada, maka langsung gunakan ini +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +# jika tidak ada, maka arahkan ke index.php +RewriteRule . index.php +~~~ + + +Nginx +----- + +Anda dapat menggunakan Yii dengan [Nginx](http://wiki.nginx.org/) dan PHP dengan [FPM SAPI](http://php.net/install.fpm). +Berikut ini merupakan contoh konfigurasi host. File ini mendefinisikan file bootstrap dan membuat Yii menangkap semua request ke file yang tidak ada, sehingga memungkinkan kita untuk memiliki URL yang cantik. + +~~~ +server { + set $host_path "/www/mysite"; + access_log /www/mysite/log/access.log main; + + server_name mysite; + root $host_path/htdocs; + set $yii_bootstrap "index.php"; + + charset utf-8; + + location / { + index index.html $yii_bootstrap; + try_files $uri $uri/ $yii_bootstrap?$args; + } + + location ~ ^/(protected|framework|themes/\w+/views) { + deny all; + } + + #avoid processing of calls to unexisting static files by yii + location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { + try_files $uri =404; + } + + # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 + # + location ~ \.php { + fastcgi_split_path_info ^(.+\.php)(.*)$; + + #let yii catch the calls to unexising PHP files + set $fsn /$yii_bootstrap; + if (-f $document_root$fastcgi_script_name){ + set $fsn $fastcgi_script_name; + } + + fastcgi_pass 127.0.0.1:9000; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fsn; + + #PATH_INFO and PATH_TRANSLATED can be omitted, but RFC 3875 specifies them for CGI + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param PATH_TRANSLATED $document_root$fsn; + } + + location ~ /\.ht { + deny all; + } +} +~~~ +Anda bisa set cgi.fix_pathinfo=0 di dalam php.ini jika menggunakan konfigurasi di atas guna menghindari pemanggilan stat() sistem yang tidak diperlukan. +
$Id: quickstart.apache-nginx-config.txt 3512 2011-12-27 16:50:03Z haertl.mike $
\ No newline at end of file diff --git a/docs/guide/it/quickstart.apache-nginx-config.txt b/docs/guide/it/quickstart.apache-nginx-config.txt index e2f4919d5..88f6feee9 100644 --- a/docs/guide/it/quickstart.apache-nginx-config.txt +++ b/docs/guide/it/quickstart.apache-nginx-config.txt @@ -1,84 +1,84 @@ -Configurazione Apache e Nginx -=================================== - -Apache ------- - -Yii è già pronto per lavorare con la configurazione di default di un web server Apache. -I file .htaccess nelle cartelle del framework e dell'applicazione scritta con Yii limitano -l'accesso alle risorse. Per nascondere il file di avvio (solitamente index.php) -negli URLs bisogna aggiungere le istruzioni mod_rewrite al file .htaccess nella -cartella radice del web server o alla configurazione del virtual host: - -~~~ -RewriteEngine on - -# if a directory or a file exists, use it directly -RewriteCond %{REQUEST_FILENAME} !-f -RewriteCond %{REQUEST_FILENAME} !-d -# otherwise forward it to index.php -RewriteRule . index.php -~~~ - - -Nginx ------ - -Puoi utilizzare Yii con [Nginx](http://wiki.nginx.org/) e PHP con [FPM SAPI](http://php.net/install.fpm). -Ecco un esempio di configurazione dell'host. Qui viene definito il file di avvio -e permette a Yii di catturare tutte le richieste di file inesistenti che consente -di ottenere URL leggibili. - -~~~ -server { - set $host_path "/www/mysite"; - access_log /www/mysite/log/access.log main; - - server_name mysite; - root $host_path/htdocs; - set $yii_bootstrap "index.php"; - - charset utf-8; - - location / { - index index.html $yii_bootstrap; - try_files $uri $uri/ $yii_bootstrap?$args; - } - - location ~ ^/(protected|framework|themes/\w+/views) { - deny all; - } - - #avoid processing of calls to unexisting static files by yii - location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { - try_files $uri =404; - } - - # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 - # - location ~ \.php { - fastcgi_split_path_info ^(.+\.php)(.*)$; - - #let yii catch the calls to unexising PHP files - set $fsn /$yii_bootstrap; - if (-f $document_root$fastcgi_script_name){ - set $fsn $fastcgi_script_name; - } - - fastcgi_pass 127.0.0.1:9000; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fsn; - - #PATH_INFO and PATH_TRANSLATED can be omitted, but RFC 3875 specifies them for CGI - fastcgi_param PATH_INFO $fastcgi_path_info; - fastcgi_param PATH_TRANSLATED $document_root$fsn; - } - - location ~ /\.ht { - deny all; - } -} -~~~ -Usando questa configurazione, per evitare molte chiamate di sistema stat(), puoi settare cgi.fix_pathinfo=0 nel file php.ini. - +Configurazione Apache e Nginx +=================================== + +Apache +------ + +Yii è già pronto per lavorare con la configurazione di default di un web server Apache. +I file .htaccess nelle cartelle del framework e dell'applicazione scritta con Yii limitano +l'accesso alle risorse. Per nascondere il file di avvio (solitamente index.php) +negli URLs bisogna aggiungere le istruzioni mod_rewrite al file .htaccess nella +cartella radice del web server o alla configurazione del virtual host: + +~~~ +RewriteEngine on + +# if a directory or a file exists, use it directly +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +# otherwise forward it to index.php +RewriteRule . index.php +~~~ + + +Nginx +----- + +Puoi utilizzare Yii con [Nginx](http://wiki.nginx.org/) e PHP con [FPM SAPI](http://php.net/install.fpm). +Ecco un esempio di configurazione dell'host. Qui viene definito il file di avvio +e permette a Yii di catturare tutte le richieste di file inesistenti che consente +di ottenere URL leggibili. + +~~~ +server { + set $host_path "/www/mysite"; + access_log /www/mysite/log/access.log main; + + server_name mysite; + root $host_path/htdocs; + set $yii_bootstrap "index.php"; + + charset utf-8; + + location / { + index index.html $yii_bootstrap; + try_files $uri $uri/ $yii_bootstrap?$args; + } + + location ~ ^/(protected|framework|themes/\w+/views) { + deny all; + } + + #avoid processing of calls to unexisting static files by yii + location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { + try_files $uri =404; + } + + # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 + # + location ~ \.php { + fastcgi_split_path_info ^(.+\.php)(.*)$; + + #let yii catch the calls to unexising PHP files + set $fsn /$yii_bootstrap; + if (-f $document_root$fastcgi_script_name){ + set $fsn $fastcgi_script_name; + } + + fastcgi_pass 127.0.0.1:9000; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fsn; + + #PATH_INFO and PATH_TRANSLATED can be omitted, but RFC 3875 specifies them for CGI + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param PATH_TRANSLATED $document_root$fsn; + } + + location ~ /\.ht { + deny all; + } +} +~~~ +Usando questa configurazione, per evitare molte chiamate di sistema stat(), puoi settare cgi.fix_pathinfo=0 nel file php.ini. +
$Id: quickstart.apache-nginx-config.txt 3512 2011-12-27 16:50:03Z haertl.mike $
\ No newline at end of file diff --git a/docs/guide/ja/basics.model.txt b/docs/guide/ja/basics.model.txt index a54609bf2..774b472a7 100644 --- a/docs/guide/ja/basics.model.txt +++ b/docs/guide/ja/basics.model.txt @@ -1,28 +1,28 @@ -モデル -===== - -モデルは [CModel] または [CModel] を継承したクラスのインスタンスです。 -モデルはデータや関連するビジネスルールを保持するために使用されます。 - -モデルは単一のデータオブジェクトを表します。 -それは、データベーステーブルの行であったり、または、ユーザ入力フィールドを持った HTML フォームであったりします。 -データオブジェクトのそれぞれのフィールドは、モデルの属性として表されます。 -そして、属性はラベルを持ち、一連のルールに対する正当性を検証することができます。 - -Yii はフォームモデルとアクティブレコードの 2 種類のモデルを実装しています。 -両方とも同じベースクラス [CModel] を継承しています。 - -フォームモデルは [CFormModel] のインスタンスです。フォームモデルは、 -ユーザ入力から収集したデータを保持するために使用されます。 -そのようなデータは頻繁に収集され、使用され、そして破棄されます。 -たとえば、ログインページにおいて、エンドユーザから提供されるユーザ名と -パスワード情報を表すために、フォームモデルを使うことが出来ます。 -詳細は、[フォーム概要](/doc/guide/form.overview) を参照してください。 - -アクティブレコード (AR) はオブジェクト指向形式でデータベースアクセスを抽象化するために使用するデザインパターンです。 -AR オブジェクトは [CActiveRecord] クラスまたはそのサブクラスのインスタンスで、データベーステーブルの単一の行を表します。 -行のフィールド(カラム)は AR オブジェクトのプロパティとして表されます。 -詳細は、[アクティブレコード](/doc/guide/database.ar) を参照してください。 - -モデルを定義するときのベストプラクティスについては、 [MVC のベストプラクティス](/doc/guide/basics.best-practices) の +モデル +===== + +モデルは [CModel] または [CModel] を継承したクラスのインスタンスです。 +モデルはデータや関連するビジネスルールを保持するために使用されます。 + +モデルは単一のデータオブジェクトを表します。 +それは、データベーステーブルの行であったり、または、ユーザ入力フィールドを持った HTML フォームであったりします。 +データオブジェクトのそれぞれのフィールドは、モデルの属性として表されます。 +そして、属性はラベルを持ち、一連のルールに対する正当性を検証することができます。 + +Yii はフォームモデルとアクティブレコードの 2 種類のモデルを実装しています。 +両方とも同じベースクラス [CModel] を継承しています。 + +フォームモデルは [CFormModel] のインスタンスです。フォームモデルは、 +ユーザ入力から収集したデータを保持するために使用されます。 +そのようなデータは頻繁に収集され、使用され、そして破棄されます。 +たとえば、ログインページにおいて、エンドユーザから提供されるユーザ名と +パスワード情報を表すために、フォームモデルを使うことが出来ます。 +詳細は、[フォーム概要](/doc/guide/form.overview) を参照してください。 + +アクティブレコード (AR) はオブジェクト指向形式でデータベースアクセスを抽象化するために使用するデザインパターンです。 +AR オブジェクトは [CActiveRecord] クラスまたはそのサブクラスのインスタンスで、データベーステーブルの単一の行を表します。 +行のフィールド(カラム)は AR オブジェクトのプロパティとして表されます。 +詳細は、[アクティブレコード](/doc/guide/database.ar) を参照してください。 + +モデルを定義するときのベストプラクティスについては、 [MVC のベストプラクティス](/doc/guide/basics.best-practices) の モデルの章を参照して下さい。 \ No newline at end of file diff --git a/docs/guide/ja/basics.view.txt b/docs/guide/ja/basics.view.txt index 96f578d35..4c67781ca 100644 --- a/docs/guide/ja/basics.view.txt +++ b/docs/guide/ja/basics.view.txt @@ -1,129 +1,129 @@ -ビュー -==== - -ビューは主にユーザインタフェースの要素によって構成される PHP スクリプトです。 -ビューには PHP 文を含める事ができますが、その PHP 文でデータモデルを変更すべきではなく、また、比較的シンプルなものに留めておくことを推奨します。 -ロジック部とプレゼンテーション部の分離の精神に基づいて、ロジックの大部分はビューではなく、コントローラかモデルに置くべきです。 - -ビューは、描画の際にビュースクリプトファイルを識別するために使われる名前を持ちます。 -ビューの名前はビュースクリプトファイル名と同じです。 -たとえば、ビュー `edit` は `edit.php` という名前のビュースクリプトファイルを参照します。 -ビューを描画するためには、ビュー名を引数にして [CController::render()] をコールします。 -このメソッドは `protected/views/ControllerID` ディレクトリ下にある、対応するビューファイルを探します。 - -ビュースクリプト中では、`$this` を使用してコントローラインスタンスへのアクセスが可能です。 -従って、ビューの中では `$this->propertyName` という形でコントローラのプロパティを参照できます。 - -さらに、データをビューに渡すために、次の方法を使用することができます: - -~~~ -[php] -$this->render('edit', array( - 'var1'=>$value1, - 'var2'=>$value2, -)); -~~~ - -上記では、[render()|CController::render] メソッドは 2 番目の配列のパラメータを変数へ展開します。 -その結果、ビュースクリプト内では、ローカル変数 `$var1` と `$var2` としてアクセスできます。 - -レイアウト ------- - -レイアウトは、ビューを装飾するために使用される特別なビューで、通常、いくつかのビューに共通しているユーザインタフェースの部分を含みます。 -たとえば、以下のように、レイアウトはヘッダーとフッターを含み、その間にビューを埋め込むことが出来ます。 - -~~~ -[php] -...... ここにヘッダ ...... - -...... ここにフッタ ...... -~~~ - -ここで、`$content` がビューのレンダリング結果を格納するものです。 - -レイアウトは [render()|CController::render] をコールするとき、暗黙に適用されます。 -デフォルトでは、ビュースクリプト `protected/views/layouts/main.php` がレイアウトとして使用されます。 -これは、 [CWebApplication::layout] か [CController::layout] のいずれかを変更することで、カスタマイズ可能です。 -レイアウトを適用せずにビューの描画を行うには、代わりに [renderPartial()|CController::renderPartial] をコールします。 - -ウィジェット ------- - -ウィジェットは、[CWidget] か [CWidget] の子クラスのインスタンスです。 -これは、主に表示的な用途を持つコンポーネントです。 -ウィジェットは、通常、複雑ではあっても自己完結したユーザインタフェースを生成するためにビュースクリプトに埋め込まれます。 -たとえば、カレンダーウィジェットは複雑なカレンダーユーザインタフェースを表示させるために使用出来ます。 -ウィジェットは、ユーザインタフェースコードの再利用性を高めてくれます。 - -ウィジェットを使うには、ビュースクリプト内で以下のようにします: - -~~~ -[php] -beginWidget('path.to.WidgetClass'); ?> -...ウィジェットによりキャプチャされる本文 (body content)... -endWidget(); ?> -~~~ - -または - -~~~ -[php] -widget('path.to.WidgetClass'); ?> -~~~ - -後の方法は、ウィジェットが本文 (body content) を必要としないときに用いられます。 - -ウィジェットを構成して挙動をカスタマイズするためには、[CBaseController::beginWidget] もしくは [CBaseController::widget] を呼び出す際に、プロパティの初期値を設定します。 -たとえば、[CMaskedTextField] ウィジェットを使用する際に、使用されるマスクを指定したい場合は、下記のように、プロパティの初期値を配列として渡します。 -ここで、配列のキーはプロパティ名、配列の値は対応するウィジェットプロパティの初期値です。 - -~~~ -[php] -widget('CMaskedTextField',array( - 'mask'=>'99/99/9999' -)); -?> -~~~ - -新しいウィジェットを定義するには、[CWidget] を継承し、[init()|CWidget::init] と [run()|CWidget::run] メソッドを上書きします: - -~~~ -[php] -class MyWidget extends CWidget -{ - public function init() - { - // CController::beginWidget() によってこのメソッドが呼ばれる - } - - public function run() - { - // CController::endWidget() によってこのメソッドが呼ばれる - } -} -~~~ - -コントローラのように、ウィジェットはそれ自身のビューを持つことができます。 -デフォルトでは、ウィジェットのビューファイルは、そのビューファイルのあるディレクトリの `views` サブディレクトリに位置します。 -コントローラと同じように、ビューは [CWidget::render()] を呼び出すことで描画させることができます。 -唯一の違いは、ウィジェットビューにはレイアウトを適用できないことです。 -さらにビュー中の `$this` はコントローラインスタンスを参照するのではなく、このウィジェットインスタンスを参照します。 - -> Tip|ヒント: [CWidgetFactory::widgets] を使ってサイト全体のレベルでウィジェットを初期構成すると、基本になる設定を非常に楽に構成することができます。 -> 詳細については [テーマ](/doc/guide/topics.theming#sec-5) のページを参照して下さい。 - -システムビュー ------------ - -システムビューは、Yii によってエラーやロギング情報を表示するために使用されるビューです。 -たとえば、存在しないコントローラやアクションがユーザによりリクエストされた場合、 -Yii はそのエラーを説明する例外を投げ、特定のシステムビューを使用して、その例外を表示します。 - -システムビュー名は、いくつかのルールに基づきます。 -`errorXXX` のような名前は、エラーコード `XXX` の [CHttpException] エラー表示のためのビューを指します。 -たとえば、もし [CHttpException] がエラーコード404により発生した場合、`error404` ビューが表示されます。 - -Yii は `framework/views` 以下に、1セットのデフォルトシステムビューを提供します。 -システムビューをカスタマイズしたい場合、`protected/views/system` 以下に、同じファイル名のビューファイルを作成してください。 +ビュー +==== + +ビューは主にユーザインタフェースの要素によって構成される PHP スクリプトです。 +ビューには PHP 文を含める事ができますが、その PHP 文でデータモデルを変更すべきではなく、また、比較的シンプルなものに留めておくことを推奨します。 +ロジック部とプレゼンテーション部の分離の精神に基づいて、ロジックの大部分はビューではなく、コントローラかモデルに置くべきです。 + +ビューは、描画の際にビュースクリプトファイルを識別するために使われる名前を持ちます。 +ビューの名前はビュースクリプトファイル名と同じです。 +たとえば、ビュー `edit` は `edit.php` という名前のビュースクリプトファイルを参照します。 +ビューを描画するためには、ビュー名を引数にして [CController::render()] をコールします。 +このメソッドは `protected/views/ControllerID` ディレクトリ下にある、対応するビューファイルを探します。 + +ビュースクリプト中では、`$this` を使用してコントローラインスタンスへのアクセスが可能です。 +従って、ビューの中では `$this->propertyName` という形でコントローラのプロパティを参照できます。 + +さらに、データをビューに渡すために、次の方法を使用することができます: + +~~~ +[php] +$this->render('edit', array( + 'var1'=>$value1, + 'var2'=>$value2, +)); +~~~ + +上記では、[render()|CController::render] メソッドは 2 番目の配列のパラメータを変数へ展開します。 +その結果、ビュースクリプト内では、ローカル変数 `$var1` と `$var2` としてアクセスできます。 + +レイアウト +------ + +レイアウトは、ビューを装飾するために使用される特別なビューで、通常、いくつかのビューに共通しているユーザインタフェースの部分を含みます。 +たとえば、以下のように、レイアウトはヘッダーとフッターを含み、その間にビューを埋め込むことが出来ます。 + +~~~ +[php] +...... ここにヘッダ ...... + +...... ここにフッタ ...... +~~~ + +ここで、`$content` がビューのレンダリング結果を格納するものです。 + +レイアウトは [render()|CController::render] をコールするとき、暗黙に適用されます。 +デフォルトでは、ビュースクリプト `protected/views/layouts/main.php` がレイアウトとして使用されます。 +これは、 [CWebApplication::layout] か [CController::layout] のいずれかを変更することで、カスタマイズ可能です。 +レイアウトを適用せずにビューの描画を行うには、代わりに [renderPartial()|CController::renderPartial] をコールします。 + +ウィジェット +------ + +ウィジェットは、[CWidget] か [CWidget] の子クラスのインスタンスです。 +これは、主に表示的な用途を持つコンポーネントです。 +ウィジェットは、通常、複雑ではあっても自己完結したユーザインタフェースを生成するためにビュースクリプトに埋め込まれます。 +たとえば、カレンダーウィジェットは複雑なカレンダーユーザインタフェースを表示させるために使用出来ます。 +ウィジェットは、ユーザインタフェースコードの再利用性を高めてくれます。 + +ウィジェットを使うには、ビュースクリプト内で以下のようにします: + +~~~ +[php] +beginWidget('path.to.WidgetClass'); ?> +...ウィジェットによりキャプチャされる本文 (body content)... +endWidget(); ?> +~~~ + +または + +~~~ +[php] +widget('path.to.WidgetClass'); ?> +~~~ + +後の方法は、ウィジェットが本文 (body content) を必要としないときに用いられます。 + +ウィジェットを構成して挙動をカスタマイズするためには、[CBaseController::beginWidget] もしくは [CBaseController::widget] を呼び出す際に、プロパティの初期値を設定します。 +たとえば、[CMaskedTextField] ウィジェットを使用する際に、使用されるマスクを指定したい場合は、下記のように、プロパティの初期値を配列として渡します。 +ここで、配列のキーはプロパティ名、配列の値は対応するウィジェットプロパティの初期値です。 + +~~~ +[php] +widget('CMaskedTextField',array( + 'mask'=>'99/99/9999' +)); +?> +~~~ + +新しいウィジェットを定義するには、[CWidget] を継承し、[init()|CWidget::init] と [run()|CWidget::run] メソッドを上書きします: + +~~~ +[php] +class MyWidget extends CWidget +{ + public function init() + { + // CController::beginWidget() によってこのメソッドが呼ばれる + } + + public function run() + { + // CController::endWidget() によってこのメソッドが呼ばれる + } +} +~~~ + +コントローラのように、ウィジェットはそれ自身のビューを持つことができます。 +デフォルトでは、ウィジェットのビューファイルは、そのビューファイルのあるディレクトリの `views` サブディレクトリに位置します。 +コントローラと同じように、ビューは [CWidget::render()] を呼び出すことで描画させることができます。 +唯一の違いは、ウィジェットビューにはレイアウトを適用できないことです。 +さらにビュー中の `$this` はコントローラインスタンスを参照するのではなく、このウィジェットインスタンスを参照します。 + +> Tip|ヒント: [CWidgetFactory::widgets] を使ってサイト全体のレベルでウィジェットを初期構成すると、基本になる設定を非常に楽に構成することができます。 +> 詳細については [テーマ](/doc/guide/topics.theming#sec-5) のページを参照して下さい。 + +システムビュー +----------- + +システムビューは、Yii によってエラーやロギング情報を表示するために使用されるビューです。 +たとえば、存在しないコントローラやアクションがユーザによりリクエストされた場合、 +Yii はそのエラーを説明する例外を投げ、特定のシステムビューを使用して、その例外を表示します。 + +システムビュー名は、いくつかのルールに基づきます。 +`errorXXX` のような名前は、エラーコード `XXX` の [CHttpException] エラー表示のためのビューを指します。 +たとえば、もし [CHttpException] がエラーコード404により発生した場合、`error404` ビューが表示されます。 + +Yii は `framework/views` 以下に、1セットのデフォルトシステムビューを提供します。 +システムビューをカスタマイズしたい場合、`protected/views/system` 以下に、同じファイル名のビューファイルを作成してください。 diff --git a/docs/guide/ja/quickstart.apache-nginx-config.txt b/docs/guide/ja/quickstart.apache-nginx-config.txt index 655c9df46..8c95d97d0 100644 --- a/docs/guide/ja/quickstart.apache-nginx-config.txt +++ b/docs/guide/ja/quickstart.apache-nginx-config.txt @@ -1,85 +1,85 @@ -Apache と Nginx の構成 -=================================== - -Apache ------- - -Yii は、既定の構成の Apache ウェブサーバで動作させることが出来ます。 -Yii の フレームワークのフォルダとアプリケーションのフォルダにある .htaccess ファイルが、保護されるべきリソースへのアクセスを制限します。 -URL からブートストラップファイル (通常は index.php) を隠すために、ドキュメントルートの .htaccess ファイルか、バーチャルホスト構成ファイルに、mod_rewrite の指示を追加することが出来ます。 - -~~~ -RewriteEngine on - -# httpd が隠しファイル (.htaccess, .svn, .git など) を送出するのを防ぐ -RedirectMatch 403 /\..*$ -# ディレクトリまたはファイルが存在する場合は、直接それを使う -RewriteCond %{REQUEST_FILENAME} !-f -RewriteCond %{REQUEST_FILENAME} !-d -# それ以外は index.php に回送する -RewriteRule . index.php -~~~ - - -Nginx ------ - -Yii は、[Nginx](http://wiki.nginx.org/) と PHP + [FPM SAPI](http://php.net/install.fpm) の組合せでも動作します。 -以下はホスト構成のサンプルです。このホスト構成は、ブートストラップファイルを定義し、存在しないファイルに対するすべてのリクエストを yii が捕捉するようにして、見栄えの良い URL を利用できるようにしています。 - -~~~ -server { - set $host_path "/www/mysite"; - access_log /www/mysite/log/access.log main; - - server_name mysite; - root $host_path/htdocs; - set $yii_bootstrap "index.php"; - - charset utf-8; - - location / { - index index.html $yii_bootstrap; - try_files $uri $uri/ /$yii_bootstrap?$args; - } - - location ~ ^/(protected|framework|nbproject|themes/\w+/views) { - deny all; - } - - # 存在しない静的ファイルに対する呼出しが yii によって処理されるのを避ける - location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { - try_files $uri =404; - } - - # 127.0.0.1:9000 をリスンしている FastCGI サーバに PHP スクリプトを渡す - # - location ~ \.php { - fastcgi_split_path_info ^(.+\.php)(.*)$; - - # 存在しない PHP ファイルに対する呼出しを yii に捕捉させる - set $fsn /$yii_bootstrap; - if (-f $document_root$fastcgi_script_name){ - set $fsn $fastcgi_script_name; - } - - fastcgi_pass 127.0.0.1:9000; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fsn; - - # PATH_INFO と PATH_TRANSLATED は省略可能だが、RFC 3875 では CGI に必要とされている - fastcgi_param PATH_INFO $fastcgi_path_info; - fastcgi_param PATH_TRANSLATED $document_root$fsn; - } - - # nginx が隠しファイル (.htaccess, .svn, .git など) を送出するのを防ぐ - location ~ /\. { - deny all; - access_log off; - log_not_found off; - } -} -~~~ -この構成を使う場合、php.ini で `cgi.fix_pathinfo=0` を設定して、システム関数 stat() の不要な呼出しの多くを回避することが出来ます。 - +Apache と Nginx の構成 +=================================== + +Apache +------ + +Yii は、既定の構成の Apache ウェブサーバで動作させることが出来ます。 +Yii の フレームワークのフォルダとアプリケーションのフォルダにある .htaccess ファイルが、保護されるべきリソースへのアクセスを制限します。 +URL からブートストラップファイル (通常は index.php) を隠すために、ドキュメントルートの .htaccess ファイルか、バーチャルホスト構成ファイルに、mod_rewrite の指示を追加することが出来ます。 + +~~~ +RewriteEngine on + +# httpd が隠しファイル (.htaccess, .svn, .git など) を送出するのを防ぐ +RedirectMatch 403 /\..*$ +# ディレクトリまたはファイルが存在する場合は、直接それを使う +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +# それ以外は index.php に回送する +RewriteRule . index.php +~~~ + + +Nginx +----- + +Yii は、[Nginx](http://wiki.nginx.org/) と PHP + [FPM SAPI](http://php.net/install.fpm) の組合せでも動作します。 +以下はホスト構成のサンプルです。このホスト構成は、ブートストラップファイルを定義し、存在しないファイルに対するすべてのリクエストを yii が捕捉するようにして、見栄えの良い URL を利用できるようにしています。 + +~~~ +server { + set $host_path "/www/mysite"; + access_log /www/mysite/log/access.log main; + + server_name mysite; + root $host_path/htdocs; + set $yii_bootstrap "index.php"; + + charset utf-8; + + location / { + index index.html $yii_bootstrap; + try_files $uri $uri/ /$yii_bootstrap?$args; + } + + location ~ ^/(protected|framework|nbproject|themes/\w+/views) { + deny all; + } + + # 存在しない静的ファイルに対する呼出しが yii によって処理されるのを避ける + location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { + try_files $uri =404; + } + + # 127.0.0.1:9000 をリスンしている FastCGI サーバに PHP スクリプトを渡す + # + location ~ \.php { + fastcgi_split_path_info ^(.+\.php)(.*)$; + + # 存在しない PHP ファイルに対する呼出しを yii に捕捉させる + set $fsn /$yii_bootstrap; + if (-f $document_root$fastcgi_script_name){ + set $fsn $fastcgi_script_name; + } + + fastcgi_pass 127.0.0.1:9000; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fsn; + + # PATH_INFO と PATH_TRANSLATED は省略可能だが、RFC 3875 では CGI に必要とされている + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param PATH_TRANSLATED $document_root$fsn; + } + + # nginx が隠しファイル (.htaccess, .svn, .git など) を送出するのを防ぐ + location ~ /\. { + deny all; + access_log off; + log_not_found off; + } +} +~~~ +この構成を使う場合、php.ini で `cgi.fix_pathinfo=0` を設定して、システム関数 stat() の不要な呼出しの多くを回避することが出来ます。 +
$Id$
\ No newline at end of file diff --git a/docs/guide/pl/quickstart.apache-nginx-config.txt b/docs/guide/pl/quickstart.apache-nginx-config.txt index d9ca24d43..13952572c 100644 --- a/docs/guide/pl/quickstart.apache-nginx-config.txt +++ b/docs/guide/pl/quickstart.apache-nginx-config.txt @@ -1,78 +1,78 @@ -Konfiguracja Apache-a oraz Nginx-a -=================================== - -Apache ------- - -Yii jest gotowe do pracy z domyślną konfiguracją serwera Apache. Pliki .htaccess w folderach frameworku oraz aplikacji ograniczają dostęp do odpowiednich zasobów. W celu ukrycia pliku rozruchowego (zazwyczaj index.php) w adresie URL, możesz dodać następujące instrukcje mod_rewrite do pliku .htaccess w głównym katalogu lub konfiguracji wirtualnego hosta: - -~~~ -RewriteEngine on - -# jeśli katalog lub plik istnieją, użyj ich -RewriteCond %{REQUEST_FILENAME} !-f -RewriteCond %{REQUEST_FILENAME} !-d -# w przecwinym przypadku przekaż do index.php -RewriteRule . index.php -~~~ - - -Nginx ------ - -Możesz używać Yii z [Nginx-em](http://wiki.nginx.org/) i PHP z [FPM SAPI](http://php.net/install.fpm). -Poniżej znajduje się przykładowa konfiguracja hosta. Definiuje ona plik rozruchowy i pozwala yii przechwytywać żądania prowadzące do nieistniejących plików, co pozwala nam posiadać ładnie wyglądające URLe. - -~~~ -server { - set $host_path "/www/mysite"; - access_log /www/mysite/log/access.log main; - - server_name mysite; - root $host_path/htdocs; - set $yii_bootstrap "index.php"; - - charset utf-8; - - location / { - index index.html $yii_bootstrap; - try_files $uri $uri/ $yii_bootstrap?$args; - } - - location ~ ^/(protected|framework|nbproject|themes/\w+/views) { - deny all; - } - - #unikaj przetwarzania przez Yii wywołań do nieistniejących statycznych plików - location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { - try_files $uri =404; - } - - # przekaż skrypty PHP do serwera FastCGI nasłuchującego 127.0.0.1:9000 - # - location ~ \.php { - fastcgi_split_path_info ^(.+\.php)(.*)$; - - #let yii catch the calls to unexising PHP files - set $fsn /$yii_bootstrap; - if (-f $document_root$fastcgi_script_name){ - set $fsn $fastcgi_script_name; - } - - fastcgi_pass 127.0.0.1:9000; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fsn; - - #PATH_INFO and PATH_TRANSLATED can be omitted, but RFC 3875 specifies them for CGI - fastcgi_param PATH_INFO $fastcgi_path_info; - fastcgi_param PATH_TRANSLATED $document_root$fsn; - } - - location ~ /\.ht { - deny all; - } -} -~~~ -Używając tej konfiguracji możesz ustawić cgi.fix_pathinfo=0 w pliku php.ini w celu uniknięcia wielu niepotrzebnych wywołań systemowych stat(). - +Konfiguracja Apache-a oraz Nginx-a +=================================== + +Apache +------ + +Yii jest gotowe do pracy z domyślną konfiguracją serwera Apache. Pliki .htaccess w folderach frameworku oraz aplikacji ograniczają dostęp do odpowiednich zasobów. W celu ukrycia pliku rozruchowego (zazwyczaj index.php) w adresie URL, możesz dodać następujące instrukcje mod_rewrite do pliku .htaccess w głównym katalogu lub konfiguracji wirtualnego hosta: + +~~~ +RewriteEngine on + +# jeśli katalog lub plik istnieją, użyj ich +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +# w przecwinym przypadku przekaż do index.php +RewriteRule . index.php +~~~ + + +Nginx +----- + +Możesz używać Yii z [Nginx-em](http://wiki.nginx.org/) i PHP z [FPM SAPI](http://php.net/install.fpm). +Poniżej znajduje się przykładowa konfiguracja hosta. Definiuje ona plik rozruchowy i pozwala yii przechwytywać żądania prowadzące do nieistniejących plików, co pozwala nam posiadać ładnie wyglądające URLe. + +~~~ +server { + set $host_path "/www/mysite"; + access_log /www/mysite/log/access.log main; + + server_name mysite; + root $host_path/htdocs; + set $yii_bootstrap "index.php"; + + charset utf-8; + + location / { + index index.html $yii_bootstrap; + try_files $uri $uri/ $yii_bootstrap?$args; + } + + location ~ ^/(protected|framework|nbproject|themes/\w+/views) { + deny all; + } + + #unikaj przetwarzania przez Yii wywołań do nieistniejących statycznych plików + location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { + try_files $uri =404; + } + + # przekaż skrypty PHP do serwera FastCGI nasłuchującego 127.0.0.1:9000 + # + location ~ \.php { + fastcgi_split_path_info ^(.+\.php)(.*)$; + + #let yii catch the calls to unexising PHP files + set $fsn /$yii_bootstrap; + if (-f $document_root$fastcgi_script_name){ + set $fsn $fastcgi_script_name; + } + + fastcgi_pass 127.0.0.1:9000; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fsn; + + #PATH_INFO and PATH_TRANSLATED can be omitted, but RFC 3875 specifies them for CGI + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param PATH_TRANSLATED $document_root$fsn; + } + + location ~ /\.ht { + deny all; + } +} +~~~ +Używając tej konfiguracji możesz ustawić cgi.fix_pathinfo=0 w pliku php.ini w celu uniknięcia wielu niepotrzebnych wywołań systemowych stat(). +
$Id: quickstart.apache-nginx-config.txt 3512 2011-12-27 16:50:03Z haertl.mike $
\ No newline at end of file diff --git a/docs/guide/pt/basics.convention.txt b/docs/guide/pt/basics.convention.txt index 74b894de5..a128b9f4f 100644 --- a/docs/guide/pt/basics.convention.txt +++ b/docs/guide/pt/basics.convention.txt @@ -1,167 +1,167 @@ -Convenções -=========== - -O Yii favorece convenções sobre configurações. Siga as convenções e você -poderá criar aplicações sofisticadas sem ter que escrever ou gerenciar -configurações complexas. Evidentemente, o Yii ainda podem ser personalizados em quase -todos os aspectos, com configurações, quando necessário. - -Abaixo descrevemos convenções que são recomendadas para programar com o Yii. -Por conveniência, assumimos que `WebRoot` é o diretório onde está instalada uma aplicação desenvolvida com o Yii framework. - - -URL ---- - -Por padrão, o Yii reconhece URLs com o seguinte formato: - -~~~ -http://hostname/index.php?r=ControllerID/ActionID -~~~ - -A variável `r`, passada via GET, refere-se a -[rota](/doc/guide/basics.controller#route), que pode ser interpretada pelo Yii -como um controle e uma ação. Se o id da ação (`ActionID`) for omitido, o controle irá -utilizar a ação padrão (definida através da propriedade [CController::defaultAction]); e se -o id do controle (`ControllerID`) também for omitido (ou a variável `r` estiver ausente), a -aplicação irá utilizar o controle padrão (definido através da propriedade -[CWebApplication::defaultController]). - -Com a ajuda da classe [CUrlManager], é possível criar e reconhecer -URLs mais amigáveis, ao estilo SEO, tais como -`http://hostname/ControllerID/ActionID.html`. Esta funcionalidade é abordada em -detalhes em [Gerenciamento de URL](/doc/guide/topics.url). - -Código ----- - -O Yii recomenda que nomes de variáveis, funções e nomes de classe sejam escritos no formato Camel Case, -onde inicia-se cada palavra com letra maiúscula e junta-se todas, sem espaços entre elas. -Variáveis e nomes de funções devem ter a sua primeira palavra totalmente em letras minúsculas, -a fim de diferencia-los dos nomes das classes (por exemplo, `$basePath`, -`runController()`, `LinkPager`). Para as variáveis privadas membros de classe, -é recomendado prefixar seus nomes com um underscore (por exemplo, -`$_actionList`). - -Como não há suporte a namespaces antes do PHP 5.3.0, é recomendado -que as classes sejam denominadas de uma forma única, para evitar conflitos com nomes de -classes de terceiros. Por esta razão, todas as classes do Yii framework são -prefixadas com a letra "C". - -Existe uma regra especial para as classes de controle, onde deve-se adicionar -o sufixo `Controller` ao nome da classe. O ID do controle é, então, definido como o nome -da classe, com a primeira letra minúscula, e a palavra `Controller` removida. -Por exemplo, a classe `PageController` terá o ID `page`. Esta regra -torna a aplicação mais segura. Também deixa mais limpas as URLs relacionados aos -controles (por exemplo, `/index.php?r=page/index` em vez de -`/index.php?r=PageController/index`). - -Configuração -------------- - -A configuração é um vetor de pares chave-valor. Cada chave representa o -nome de uma propriedade do objeto a ser configurado, e cada valor, o -valor inicial da propriedade correspondente. Por exemplo, `array('name'=>'Minha -aplicação', 'basePath'=>'/protected')` inicializa as propriedades `name` e -`basePath` com os valores correspondentes no vetor. - -Qualquer propriedades "alterável" de um objeto pode ser configurada. Se não forem configuradas, -as propriedades assumirão seus valores padrão. Ao configurar uma propriedade, -vale a pena ler a documentação correspondente, para que o -valor inicial seja configurado corretamente. - -Arquivo ----- - -As convenções para nomenclatura e utilização de arquivos dependem seus tipos. - -Arquivos de classe devem ser nomeados de acordo com a classe pública que contém. Por -exemplo, a classe [CController] está no arquivo `CController.php`. Uma -classe pública é uma classe que pode ser utilizada por qualquer outra. Cada arquivo de -classe deve conter, no máximo, uma classe pública. Classes privadas (aquelas -que são utilizadas apenas por uma única classe pública) podem residir no mesmo arquivo com -a classe que a utiliza. - -Os arquivos das visões devem ser nomeados de acordo com o seus nomes. Por exemplo, a visão `index` -está no arquivo `index.php`. O arquivo de uma visão contém um script -com código HTML e PHP, utilizado, principalmente para apresentação de conteúdo. - -Arquivos de configuração podem ser nomeadas arbitrariamente. Um arquivo de configuração é um -script em PHP cuja única finalidade é a de retornar um vetor associativo -representando a configuração. - -Diretório ---------- - -O Yii assume um conjunto predefinido de diretórios utilizados para diversas finalidades. Cada um -deles pode ser personalizado, se necessário. - -- `WebRoot/protected`: este é o [diretório base -da aplicação](/doc/guide/basics.application#application-base-directory), onde estão todos os -scripts PHP que precisão estar seguros e os arquivos de dados. O Yii tem um apelido (alias) padrão -chamado `application`, associado a este caminho. Este diretório, e -tudo dentro dele, deve estar protegido para não ser acessado via web. Ele -pode ser alterado através da propriedade [CWebApplication::basePath]. - -- `WebRoot/protected/runtime`: este diretório armazena arquivos privados temporários -gerados durante a execução da aplicação. Este diretório deve ter -permissão de escrita para o processo do servidor Web. Ele pode ser alterado através da -propriedade [CApplication::runtimePath]. - -- `WebRoot/protected/extensions`: este diretório armazena todas as extensões -de terceiros. Ele pode ser alterado através da propriedade [CApplication::extensionPath]. - -- `WebRoot/protected/modules`: este diretório contém todos os -[módulos](/doc/guide/basics.module) da aplicação, cada um representado como um subdiretório. - -- `WebRoot/protected/controllers`: neste diretório estão os arquivos de classe -de todos os controles. Ele pode ser alterado através da propriedade [CWebApplication::controllerPath]. - -- `WebRoot/protected/views`: este diretório possui todos os arquivos das visões, -incluindo as visões dos controles, visões do layout e visões do sistema. Ele pode ser alterado -através da propriedade [CWebApplication::viewPath]. - -- `WebRoot/protected/views/ControllerID`: neste diretório estão os arquivos das visões -para um controle específico. Aqui, `ControllerID` é o ID -do controle. Ele pode ser alterado através da propriedade [CController::viewPath]. - -- `WebRoot/protected/views/layouts`: este diretório possui todos os arquivos de visão -do layout. Ele pode ser alterado através da propriedade [CWebApplication::layoutPath]. - -- `WebRoot/protected/views/system`: este diretório mantém todos os arquivos -de visões do sistema. Visões do sistema são templates utilizados para exibir exceções e -erros. Ele pode ser alterado através da propriedade [CWebApplication::systemViewPath]. - -- `WebRoot/assets`: este diretório mantém os assets publicados. Um -asset é um arquivo privado que pode ser publicado para se tornar acessível aos -usuários, via web. Este diretório deve ter permissão de escrita para o processo do servidor Web. Ele pode ser -alterado através da propriedade [CAssetManager::basePath]. - -- `WebRoot/themes`: este diretório armazena vários temas que podem ser -aplicados à aplicação. Cada subdiretório representa um único tema -cujo nome é o nome do tema. Ele pode ser alterado através da propriedade -[CThemeManager::basePath]. - -Banco de Dados --------------- - -A maioria das aplicações web utilizam algum tipo de banco de dados. Como boa prática, -propomos as seguintes convenções para a criação de nomes de tabelas e colunas. Note -que nenhuma delas é obrigatória para a utilização do Yii. - - - Nomes de tabelas e colunas devem utilizar apenas letras minúsculas. - - - As palavras de um nome devem ser separadas por underscores (ex.: `product_order`). - - - Para as tabelas, você pode utilizar nomes no singular ou no plural, mas nunca -ambos ao mesmo tempo. Para simplificar, recomendamos a utilização de nomes no singular. - - - Os nomes das tabelas devem ser prefixados com um token como, por exemplo, -`tbl_`. Isso é especialmente útil em casos onde as tabelas de uma aplicação estão -no mesmo banco de dados utilizado por tabelas de outra aplicação. Assim, os dois -conjuntos de tabelas podem ser lidos separadamente utilizando os prefixos dos nomes -das tabelas. - -
$Id: basics.convention.txt 2345 2010-08-28 12:51:08Z mdomba $
- -
$Id: basics.convention.txt 749 2009-02-26 02:11:31Z qiang.xue $
+Convenções +=========== + +O Yii favorece convenções sobre configurações. Siga as convenções e você +poderá criar aplicações sofisticadas sem ter que escrever ou gerenciar +configurações complexas. Evidentemente, o Yii ainda podem ser personalizados em quase +todos os aspectos, com configurações, quando necessário. + +Abaixo descrevemos convenções que são recomendadas para programar com o Yii. +Por conveniência, assumimos que `WebRoot` é o diretório onde está instalada uma aplicação desenvolvida com o Yii framework. + + +URL +--- + +Por padrão, o Yii reconhece URLs com o seguinte formato: + +~~~ +http://hostname/index.php?r=ControllerID/ActionID +~~~ + +A variável `r`, passada via GET, refere-se a +[rota](/doc/guide/basics.controller#route), que pode ser interpretada pelo Yii +como um controle e uma ação. Se o id da ação (`ActionID`) for omitido, o controle irá +utilizar a ação padrão (definida através da propriedade [CController::defaultAction]); e se +o id do controle (`ControllerID`) também for omitido (ou a variável `r` estiver ausente), a +aplicação irá utilizar o controle padrão (definido através da propriedade +[CWebApplication::defaultController]). + +Com a ajuda da classe [CUrlManager], é possível criar e reconhecer +URLs mais amigáveis, ao estilo SEO, tais como +`http://hostname/ControllerID/ActionID.html`. Esta funcionalidade é abordada em +detalhes em [Gerenciamento de URL](/doc/guide/topics.url). + +Código +---- + +O Yii recomenda que nomes de variáveis, funções e nomes de classe sejam escritos no formato Camel Case, +onde inicia-se cada palavra com letra maiúscula e junta-se todas, sem espaços entre elas. +Variáveis e nomes de funções devem ter a sua primeira palavra totalmente em letras minúsculas, +a fim de diferencia-los dos nomes das classes (por exemplo, `$basePath`, +`runController()`, `LinkPager`). Para as variáveis privadas membros de classe, +é recomendado prefixar seus nomes com um underscore (por exemplo, +`$_actionList`). + +Como não há suporte a namespaces antes do PHP 5.3.0, é recomendado +que as classes sejam denominadas de uma forma única, para evitar conflitos com nomes de +classes de terceiros. Por esta razão, todas as classes do Yii framework são +prefixadas com a letra "C". + +Existe uma regra especial para as classes de controle, onde deve-se adicionar +o sufixo `Controller` ao nome da classe. O ID do controle é, então, definido como o nome +da classe, com a primeira letra minúscula, e a palavra `Controller` removida. +Por exemplo, a classe `PageController` terá o ID `page`. Esta regra +torna a aplicação mais segura. Também deixa mais limpas as URLs relacionados aos +controles (por exemplo, `/index.php?r=page/index` em vez de +`/index.php?r=PageController/index`). + +Configuração +------------- + +A configuração é um vetor de pares chave-valor. Cada chave representa o +nome de uma propriedade do objeto a ser configurado, e cada valor, o +valor inicial da propriedade correspondente. Por exemplo, `array('name'=>'Minha +aplicação', 'basePath'=>'/protected')` inicializa as propriedades `name` e +`basePath` com os valores correspondentes no vetor. + +Qualquer propriedades "alterável" de um objeto pode ser configurada. Se não forem configuradas, +as propriedades assumirão seus valores padrão. Ao configurar uma propriedade, +vale a pena ler a documentação correspondente, para que o +valor inicial seja configurado corretamente. + +Arquivo +---- + +As convenções para nomenclatura e utilização de arquivos dependem seus tipos. + +Arquivos de classe devem ser nomeados de acordo com a classe pública que contém. Por +exemplo, a classe [CController] está no arquivo `CController.php`. Uma +classe pública é uma classe que pode ser utilizada por qualquer outra. Cada arquivo de +classe deve conter, no máximo, uma classe pública. Classes privadas (aquelas +que são utilizadas apenas por uma única classe pública) podem residir no mesmo arquivo com +a classe que a utiliza. + +Os arquivos das visões devem ser nomeados de acordo com o seus nomes. Por exemplo, a visão `index` +está no arquivo `index.php`. O arquivo de uma visão contém um script +com código HTML e PHP, utilizado, principalmente para apresentação de conteúdo. + +Arquivos de configuração podem ser nomeadas arbitrariamente. Um arquivo de configuração é um +script em PHP cuja única finalidade é a de retornar um vetor associativo +representando a configuração. + +Diretório +--------- + +O Yii assume um conjunto predefinido de diretórios utilizados para diversas finalidades. Cada um +deles pode ser personalizado, se necessário. + +- `WebRoot/protected`: este é o [diretório base +da aplicação](/doc/guide/basics.application#application-base-directory), onde estão todos os +scripts PHP que precisão estar seguros e os arquivos de dados. O Yii tem um apelido (alias) padrão +chamado `application`, associado a este caminho. Este diretório, e +tudo dentro dele, deve estar protegido para não ser acessado via web. Ele +pode ser alterado através da propriedade [CWebApplication::basePath]. + +- `WebRoot/protected/runtime`: este diretório armazena arquivos privados temporários +gerados durante a execução da aplicação. Este diretório deve ter +permissão de escrita para o processo do servidor Web. Ele pode ser alterado através da +propriedade [CApplication::runtimePath]. + +- `WebRoot/protected/extensions`: este diretório armazena todas as extensões +de terceiros. Ele pode ser alterado através da propriedade [CApplication::extensionPath]. + +- `WebRoot/protected/modules`: este diretório contém todos os +[módulos](/doc/guide/basics.module) da aplicação, cada um representado como um subdiretório. + +- `WebRoot/protected/controllers`: neste diretório estão os arquivos de classe +de todos os controles. Ele pode ser alterado através da propriedade [CWebApplication::controllerPath]. + +- `WebRoot/protected/views`: este diretório possui todos os arquivos das visões, +incluindo as visões dos controles, visões do layout e visões do sistema. Ele pode ser alterado +através da propriedade [CWebApplication::viewPath]. + +- `WebRoot/protected/views/ControllerID`: neste diretório estão os arquivos das visões +para um controle específico. Aqui, `ControllerID` é o ID +do controle. Ele pode ser alterado através da propriedade [CController::viewPath]. + +- `WebRoot/protected/views/layouts`: este diretório possui todos os arquivos de visão +do layout. Ele pode ser alterado através da propriedade [CWebApplication::layoutPath]. + +- `WebRoot/protected/views/system`: este diretório mantém todos os arquivos +de visões do sistema. Visões do sistema são templates utilizados para exibir exceções e +erros. Ele pode ser alterado através da propriedade [CWebApplication::systemViewPath]. + +- `WebRoot/assets`: este diretório mantém os assets publicados. Um +asset é um arquivo privado que pode ser publicado para se tornar acessível aos +usuários, via web. Este diretório deve ter permissão de escrita para o processo do servidor Web. Ele pode ser +alterado através da propriedade [CAssetManager::basePath]. + +- `WebRoot/themes`: este diretório armazena vários temas que podem ser +aplicados à aplicação. Cada subdiretório representa um único tema +cujo nome é o nome do tema. Ele pode ser alterado através da propriedade +[CThemeManager::basePath]. + +Banco de Dados +-------------- + +A maioria das aplicações web utilizam algum tipo de banco de dados. Como boa prática, +propomos as seguintes convenções para a criação de nomes de tabelas e colunas. Note +que nenhuma delas é obrigatória para a utilização do Yii. + + - Nomes de tabelas e colunas devem utilizar apenas letras minúsculas. + + - As palavras de um nome devem ser separadas por underscores (ex.: `product_order`). + + - Para as tabelas, você pode utilizar nomes no singular ou no plural, mas nunca +ambos ao mesmo tempo. Para simplificar, recomendamos a utilização de nomes no singular. + + - Os nomes das tabelas devem ser prefixados com um token como, por exemplo, +`tbl_`. Isso é especialmente útil em casos onde as tabelas de uma aplicação estão +no mesmo banco de dados utilizado por tabelas de outra aplicação. Assim, os dois +conjuntos de tabelas podem ser lidos separadamente utilizando os prefixos dos nomes +das tabelas. + +
$Id: basics.convention.txt 2345 2010-08-28 12:51:08Z mdomba $
+ +
$Id: basics.convention.txt 749 2009-02-26 02:11:31Z qiang.xue $
diff --git a/docs/guide/pt/basics.namespace.txt b/docs/guide/pt/basics.namespace.txt index 44ebc0e61..9535c93d4 100644 --- a/docs/guide/pt/basics.namespace.txt +++ b/docs/guide/pt/basics.namespace.txt @@ -1,73 +1,73 @@ -Path Alias e Namespace -====================== - -O Yii utiliza path aliases (apelidos para caminhos) extensivamente. Um path alias, é um apelido associado -ao caminho de um diretório ou arquivo.Um path alias utiliza a sintaxe de ponto para separar seus itens, similar a forma largamente adotada em namespaces: - - -~~~ -RootAlias.path.to.target -~~~ - -Onde `RootAlias` é o nome de um diretório existente. Ao executar o método [YiiBase::setPathOfAlias()], -podemos definir novos apelidos para caminhos. Por conveniência, o Yii já possui predefinidos os seguintes apelidos: - -- `system`: refere-se ao diretório do Yii framework; -- `application`: refere-se ao [diretório base](/doc/guide/basics.application#application-base-directory) da aplicação; -- `webroot`: refere-se ao diretório que contém o arquivo do [script de entrada](/doc/guide/basics.entry). Esse apelido está disponível desde a versão 1.0.3. +Path Alias e Namespace +====================== + +O Yii utiliza path aliases (apelidos para caminhos) extensivamente. Um path alias, é um apelido associado +ao caminho de um diretório ou arquivo.Um path alias utiliza a sintaxe de ponto para separar seus itens, similar a forma largamente adotada em namespaces: + + +~~~ +RootAlias.path.to.target +~~~ + +Onde `RootAlias` é o nome de um diretório existente. Ao executar o método [YiiBase::setPathOfAlias()], +podemos definir novos apelidos para caminhos. Por conveniência, o Yii já possui predefinidos os seguintes apelidos: + +- `system`: refere-se ao diretório do Yii framework; +- `application`: refere-se ao [diretório base](/doc/guide/basics.application#application-base-directory) da aplicação; +- `webroot`: refere-se ao diretório que contém o arquivo do [script de entrada](/doc/guide/basics.entry). Esse apelido está disponível desde a versão 1.0.3. - `ext`: refere-se ao diretório que contém todas as [extensões](/doc/guide/extension.overview) de terceiros. Esse apelido está disponível desde a versão 1.0.8. - -Além disso, se a aplicação utiliza [módulos](/doc/guide/basics.module), um apelido de diretório raiz (root alias) é predefinido para cada módulo, apontando para o diretório base do módulo correspondente. Esta funcionalidade está disponível desde a versão 1.0.3. - -Ao usar o método [YiiBase::getPathOfAlias()], um apelido pode ser traduzido para o seu -caminho correspondente. Por exemplo, `system.web.CController` seria -traduzido para `yii/framework/web/CController`. - -A utilização de apelidos é muito conveniente para importar a definição de uma classe. -Por exemplo, se quisermos incluir a definição da classe [CController] -podemos fazer o seguinte: - -~~~ -[php] -Yii::import('system.web.CController'); -~~~ - -O método [import|YiiBase::import] é mais eficiente que o `include` e o `require` do PHP. -Com ele, a definição da classe que está sendo importada -não é incluída até que seja referenciada pela primeira vez. Importar -o mesmo namespace várias vezes, também é muito mais rápido do que utilizar o `include_once` -e o `require_once`. - -> Tip|Dica: Quando referenciamos uma das classes do Yii Framework, não precisamos -importa-la ou inclui-la. Todas as classes Yii são pré-importadas. - -Podemos também utilizar a seguinte sintaxe para importar todo um diretório de uma só vez, de forma que -os arquivos de classe dentro dele sejam automaticamente incluídos, quando necessário. - - -~~~ -[php] -Yii::import('system.web.*'); -~~~ - -Além do método [import|YiiBase::import], apelidos são utilizados em vários outros -locais para se referir a classes. Por exemplo, um apelido pode ser passado para o método -[Yii::createComponent()] para criar uma instância da classe informada, -mesmo que o arquivo da classe ainda não tenha sido incluído. - -Não confunda um path alias com um namespace. Um namespace refere-se a um agrupamento lógico -de nomes de classes para que eles possam ser diferenciadas de outros -nomes das classes, mesmo que eles sejam iguais. Já um path alias é utilizado para -referenciar um arquivo de classe ou um diretório. Um path alias não conflita com um -namespace. - -> Tip|Dica: Como o PHP, antes da versão 5.3.0, não dá suporte a namespaces, -você não pode criar instâncias de duas classes que tenham o mesmo -nome, mas definições diferentes. Por isso, todas as classes do Yii framework -são prefixadas com uma letra "C" (que significa 'class'), de modo que elas possam -ser diferenciadas das classes definidas pelo usuário. Recomenda-se que o -prefixo "C" seja reservado somente para utilização do Yii framework, e que classes criadas pelos usuário -sejam prefixadas com outras letras. - -
$Id: basics.namespace.txt 1400 2009-09-07 12:45:17Z qiang.xue $
+ +Além disso, se a aplicação utiliza [módulos](/doc/guide/basics.module), um apelido de diretório raiz (root alias) é predefinido para cada módulo, apontando para o diretório base do módulo correspondente. Esta funcionalidade está disponível desde a versão 1.0.3. + +Ao usar o método [YiiBase::getPathOfAlias()], um apelido pode ser traduzido para o seu +caminho correspondente. Por exemplo, `system.web.CController` seria +traduzido para `yii/framework/web/CController`. + +A utilização de apelidos é muito conveniente para importar a definição de uma classe. +Por exemplo, se quisermos incluir a definição da classe [CController] +podemos fazer o seguinte: + +~~~ +[php] +Yii::import('system.web.CController'); +~~~ + +O método [import|YiiBase::import] é mais eficiente que o `include` e o `require` do PHP. +Com ele, a definição da classe que está sendo importada +não é incluída até que seja referenciada pela primeira vez. Importar +o mesmo namespace várias vezes, também é muito mais rápido do que utilizar o `include_once` +e o `require_once`. + +> Tip|Dica: Quando referenciamos uma das classes do Yii Framework, não precisamos +importa-la ou inclui-la. Todas as classes Yii são pré-importadas. + +Podemos também utilizar a seguinte sintaxe para importar todo um diretório de uma só vez, de forma que +os arquivos de classe dentro dele sejam automaticamente incluídos, quando necessário. + + +~~~ +[php] +Yii::import('system.web.*'); +~~~ + +Além do método [import|YiiBase::import], apelidos são utilizados em vários outros +locais para se referir a classes. Por exemplo, um apelido pode ser passado para o método +[Yii::createComponent()] para criar uma instância da classe informada, +mesmo que o arquivo da classe ainda não tenha sido incluído. + +Não confunda um path alias com um namespace. Um namespace refere-se a um agrupamento lógico +de nomes de classes para que eles possam ser diferenciadas de outros +nomes das classes, mesmo que eles sejam iguais. Já um path alias é utilizado para +referenciar um arquivo de classe ou um diretório. Um path alias não conflita com um +namespace. + +> Tip|Dica: Como o PHP, antes da versão 5.3.0, não dá suporte a namespaces, +você não pode criar instâncias de duas classes que tenham o mesmo +nome, mas definições diferentes. Por isso, todas as classes do Yii framework +são prefixadas com uma letra "C" (que significa 'class'), de modo que elas possam +ser diferenciadas das classes definidas pelo usuário. Recomenda-se que o +prefixo "C" seja reservado somente para utilização do Yii framework, e que classes criadas pelos usuário +sejam prefixadas com outras letras. + +
$Id: basics.namespace.txt 1400 2009-09-07 12:45:17Z qiang.xue $
diff --git a/docs/guide/pt/topics.console.txt b/docs/guide/pt/topics.console.txt index e2657b87b..bcb723885 100644 --- a/docs/guide/pt/topics.console.txt +++ b/docs/guide/pt/topics.console.txt @@ -1,226 +1,226 @@ -Aplicativos de Console -==================== - -Aplicativos de console são utilizados para realizar algum trabalho offline necessário -por um aplicativo Web online como, geração de código, compilação do índice de busca, -envio de e-mail, etc. O Yii fornece um framework para escrever aplicativos de console -de uma maneira orientada a objetos. Ele permite que um aplicativo de console acesse os -recursos (por exemplo, conexões a banco de dados) que são utilizados por uma aplicação -Web online. - - -Visão Geral --------- - -O Yii representa cada tarefa de console como um [comando|CConsoleCommand]. -Um comando de console é uma classe que estende de [CConsoleCommand]. - -Quando utilizamos a ferramenta `yiic webapp` para criar o esqueleto inicial de uma -aplicação Yii, podemos encontrar dois arquivos no diretório `protected`: - -* `yiic`: este é um script executável utilizado no Linux/Unix; -* `yiic.bat`: este é um arquivo batch executável utilizado no Windows. - -Na janela de console, podemos informar os seguintes comandos: - -~~~ -cd protected -yiic help -~~~ - -Este comando irá mostrar uma lista dos comandos de console disponíveis. Por padrão, -os comandos de console disponíveis, incluem os fornecedidos pelo Yii Framework -(chamados de **comandos de sistema**) e aqueles desenvolvidos pelos usuários para -aplicações individuais (chamados **comandos de usuário**). - -Para descobrir como utilizar um determinado comando, podemos executar: - -~~~ -yiic help -~~~ - -E para executar um comando, podemos utilizar o seguinte formato: - -~~~ -yiic [parâmetros...] -~~~ - - -Criando Comandos ------------------ - -Comandos de console são armazenados como arquivos de classe dentro do diretório -especificado em [CConsoleApplication::commandPath]. Por padrão, o diretório -`protected/commands` é utilizado. - -Uma classe de comando de console deve estender [CConsoleCommand]. Seu nome -deve ter o formato `XyzCommand`, onde `Xyz` referencia o nome do comando com a primeira letra -em maiúsculo. Para exemplificar, o comando `sitemap` deve usar o nome de classe `SitemapCommand`. -Comandos de console são case-sensitive, ou seja, há diferença entre maiúsculas de minúsculas. - -> Tip|Dica: Através da propriedade [CConsoleApplication::commandMap], podemos ter classes de comandos -> com diferentes convenções de nomenclatura e localizadas em diferentes diretórios. - -Para criar um comando, precisamos sobrescrever o método [CConsoleCommand::run()] -ou desenvolver uma ou mais ações de comando (isto será explicado na próxima sessão). - -Quando executamos um comando de console, o método [CConsoleCommand::run()] será -invocado pela aplicação. Qualquer parâmetro de comando será passado -para o método, de acordo com a assinatura de método a seguir: - -~~~ -[php] -public function run($args) { ... } -~~~ - -onde `$args` refere-se aos parâmetros extras informados na linha de comando. - -Dentro de um comando de console, utilizamos `Yii::app()` para acessar a instância -da aplicação de console, através da qual podemos acessar recursos, tal como a conexão ao banco de dados -(ex: `Yii::app()->db`). Como é possível perceber, a utilização é similar ao modo como é -feita em aplicações Web. - -> Info|Informação: A partir da versão 1.1.1, também podemos criar comandos globais que são -compartilhados para **todas** as aplicações Yii no mesmo computador. Para isso, definimos -uma variável de ambiente com o nome `YII_CONSOLE_COMMANDS` que deverá apontar para um -diretório existente. Dentro deste diretório colocamos os arquivos de classe de comandos. - - -Ações em Comandos de Console ----------------------- - -> Note|Nota: Esta funcionalidade está disponível desde a versão 1.1.5. - -Um comando de console, muitas vezes precisa lidar com diferentes parâmetros de linha de comando, -alguns necessários, outros opcionais. Um comando de console também pode precisar fornecer vários -sub-comandos para lidar com diferentes sub-tarefas. Estes trabalhos podem ser simplificados -utilizando ações de comando de console. - -Uma ação de comando de console é um método da classe de comando de console. -O nome do método deve ter o formato `actionXyz`, onde `Xyz` refere-se ao nome -da ação, com a primeira letra em maicúsculo. Por exemplo, o método `actionIndex` -define uma ação chamada `index`. - -Para executar uma ação específica, podemos usar o seguinte formado de comando de console: - -~~~ -yiic --option1=value --option2=value2 ... -~~~ - -Os pares de opção-valor adicionais, serão passados como parâmetros de chamada para o método da ação. -O valor da opção `xyz` será passado como o parâmetro `$xyz` para o método da ação. -Por exemplo, se definir a seguinte classe de comando: - -~~~ -[php] -class SitemapCommand extends CConsoleCommand -{ - public function actionIndex($type, $limit=5) { ... } - public function actionInit() { ... } -} -~~~ - -Então, os seguintes comandos de console irão todos resultar na chamada `actionIndex('News', 5)`: - -~~~ -yiic sitemap index --type=News --limit=5 - -// $limit possui valor padrão -yiic sitemap index --type=News - -// $limit possui valor padrão -// como 'index' é uma ação padrão, podemos omitir o nome da ação -yiic sitemap --type=News - -// a ordem das opções não tem importância -yiic sitemap index --limit=5 --type=News -~~~ - -Se uma opção é informada sem valor (ex: `--type` ao invés de `--type=News`), o valor do parâmetro -correspondente a ação será assumido como um valor boleano `true`. - -> Note|Nota: Não são suportados formatos de opções alternativos como -> `--type News`, `-t News`. - -Um parâmetro pode ter um valor em array apenas declarando-o como um tipo sugerido de array: - -~~~ -[php] -public function actionIndex(array $types) { ... } -~~~ - -Para fornecer os valores ao array, devemos simplesmente repetir a mesma opção na linha de comando, se necessário: - -~~~ -yiic sitemap index --types=News --types=Article -~~~ - -O comando acima irá por fim chamar `actionIndex(array('News', 'Article'))` - -A partir da versão 1.1.6, o Yii também suporta o uso de parâmetros de ação anônimos e opções globais. - -Parâmetros anônimos referem-se aos parâmetros de linha de comando que não estão no formato de opções. -Por exemplo, no comando `yiic sitemap index --limit=5 News` nós temos um parâmetro anônimo cujo valor -é `News` enquanto o parâmetro nomeado `limit` contém o valor 5. - -Para usar parâmetros anônimos, a ação de comando deve ser declarada com um parâmetro nomeado `$args`, Para exemplificar, - -~~~ -[php] -public function actionIndex($limit=10, $args=array()) {...} -~~~ - -O array `$args` vai guardar todos os valores dos parâmetros anônimos disponíveis. - -Opções globais referem-se às opções de linha de comando que são compartilhadas por todas as ações de um comando. -Por exemplo, em um comando que prevê diversas ações, pode ser que cada ação precisa reconhecer uma -opção nomeada `$verbose`. Enquanto podemos declarar um parâmetro `$verbose` em cada método de ação, -uma forma melhor, é declará-la como uma **variável pública** na classe de comando, que transformará -`$verbose` em uma opção global. - -~~~ -[php] -class SitemapCommand extends CConsoleCommand -{ - public $verbose=false; - public function actionIndex($type) {...} -} -~~~ - -O comando abaixo nos permite executar um comando com a opção `verbose`: - -~~~ -yiic sitemap index --verbose=1 --type=News -~~~ - - -Customizando a Aplicação de Console --------------------------------- - -Por padrão, se uma aplicação é criada utilizando a ferramenta `yiic webapp`, a configuração -para a aplicação de console será criada em `protected/config/console.php`. Como um arquivo de configuração -de uma aplicação Web, este arquivo é um script PHP que retorna um array representando os valores iniciais -das propriedades para uma instância de aplicação de console. Como resultado, qualquer propriedade -pública de [CConsoleApplication], pode ser configurada neste arquivo. - -Como os comandos de console geralmente são criados para servir uma aplicação Web, -eles precisam acessar os recursos (como as conexões a banco de dados) que serão utilizado por essa aplicação. -Nós podemos fazê-lo no arquivo de configuração da aplicação de console, como a seguir: - -~~~ -[php] -return array( - ...... - 'components'=>array( - 'db'=>array( - ...... - ), - ), -); -~~~ - -Como podemos ver, o formato da configuração é muito semelhante ao que é utilizado na -configuração de um aplicativo Web. Isto porque [CConsoleApplication] e [CWebApplication] -compartilham a mesma classe base. - -
$Id: topics.console.txt 2867 2011-01-15 10:22:03Z haertl.mike $
+Aplicativos de Console +==================== + +Aplicativos de console são utilizados para realizar algum trabalho offline necessário +por um aplicativo Web online como, geração de código, compilação do índice de busca, +envio de e-mail, etc. O Yii fornece um framework para escrever aplicativos de console +de uma maneira orientada a objetos. Ele permite que um aplicativo de console acesse os +recursos (por exemplo, conexões a banco de dados) que são utilizados por uma aplicação +Web online. + + +Visão Geral +-------- + +O Yii representa cada tarefa de console como um [comando|CConsoleCommand]. +Um comando de console é uma classe que estende de [CConsoleCommand]. + +Quando utilizamos a ferramenta `yiic webapp` para criar o esqueleto inicial de uma +aplicação Yii, podemos encontrar dois arquivos no diretório `protected`: + +* `yiic`: este é um script executável utilizado no Linux/Unix; +* `yiic.bat`: este é um arquivo batch executável utilizado no Windows. + +Na janela de console, podemos informar os seguintes comandos: + +~~~ +cd protected +yiic help +~~~ + +Este comando irá mostrar uma lista dos comandos de console disponíveis. Por padrão, +os comandos de console disponíveis, incluem os fornecedidos pelo Yii Framework +(chamados de **comandos de sistema**) e aqueles desenvolvidos pelos usuários para +aplicações individuais (chamados **comandos de usuário**). + +Para descobrir como utilizar um determinado comando, podemos executar: + +~~~ +yiic help +~~~ + +E para executar um comando, podemos utilizar o seguinte formato: + +~~~ +yiic [parâmetros...] +~~~ + + +Criando Comandos +----------------- + +Comandos de console são armazenados como arquivos de classe dentro do diretório +especificado em [CConsoleApplication::commandPath]. Por padrão, o diretório +`protected/commands` é utilizado. + +Uma classe de comando de console deve estender [CConsoleCommand]. Seu nome +deve ter o formato `XyzCommand`, onde `Xyz` referencia o nome do comando com a primeira letra +em maiúsculo. Para exemplificar, o comando `sitemap` deve usar o nome de classe `SitemapCommand`. +Comandos de console são case-sensitive, ou seja, há diferença entre maiúsculas de minúsculas. + +> Tip|Dica: Através da propriedade [CConsoleApplication::commandMap], podemos ter classes de comandos +> com diferentes convenções de nomenclatura e localizadas em diferentes diretórios. + +Para criar um comando, precisamos sobrescrever o método [CConsoleCommand::run()] +ou desenvolver uma ou mais ações de comando (isto será explicado na próxima sessão). + +Quando executamos um comando de console, o método [CConsoleCommand::run()] será +invocado pela aplicação. Qualquer parâmetro de comando será passado +para o método, de acordo com a assinatura de método a seguir: + +~~~ +[php] +public function run($args) { ... } +~~~ + +onde `$args` refere-se aos parâmetros extras informados na linha de comando. + +Dentro de um comando de console, utilizamos `Yii::app()` para acessar a instância +da aplicação de console, através da qual podemos acessar recursos, tal como a conexão ao banco de dados +(ex: `Yii::app()->db`). Como é possível perceber, a utilização é similar ao modo como é +feita em aplicações Web. + +> Info|Informação: A partir da versão 1.1.1, também podemos criar comandos globais que são +compartilhados para **todas** as aplicações Yii no mesmo computador. Para isso, definimos +uma variável de ambiente com o nome `YII_CONSOLE_COMMANDS` que deverá apontar para um +diretório existente. Dentro deste diretório colocamos os arquivos de classe de comandos. + + +Ações em Comandos de Console +---------------------- + +> Note|Nota: Esta funcionalidade está disponível desde a versão 1.1.5. + +Um comando de console, muitas vezes precisa lidar com diferentes parâmetros de linha de comando, +alguns necessários, outros opcionais. Um comando de console também pode precisar fornecer vários +sub-comandos para lidar com diferentes sub-tarefas. Estes trabalhos podem ser simplificados +utilizando ações de comando de console. + +Uma ação de comando de console é um método da classe de comando de console. +O nome do método deve ter o formato `actionXyz`, onde `Xyz` refere-se ao nome +da ação, com a primeira letra em maicúsculo. Por exemplo, o método `actionIndex` +define uma ação chamada `index`. + +Para executar uma ação específica, podemos usar o seguinte formado de comando de console: + +~~~ +yiic --option1=value --option2=value2 ... +~~~ + +Os pares de opção-valor adicionais, serão passados como parâmetros de chamada para o método da ação. +O valor da opção `xyz` será passado como o parâmetro `$xyz` para o método da ação. +Por exemplo, se definir a seguinte classe de comando: + +~~~ +[php] +class SitemapCommand extends CConsoleCommand +{ + public function actionIndex($type, $limit=5) { ... } + public function actionInit() { ... } +} +~~~ + +Então, os seguintes comandos de console irão todos resultar na chamada `actionIndex('News', 5)`: + +~~~ +yiic sitemap index --type=News --limit=5 + +// $limit possui valor padrão +yiic sitemap index --type=News + +// $limit possui valor padrão +// como 'index' é uma ação padrão, podemos omitir o nome da ação +yiic sitemap --type=News + +// a ordem das opções não tem importância +yiic sitemap index --limit=5 --type=News +~~~ + +Se uma opção é informada sem valor (ex: `--type` ao invés de `--type=News`), o valor do parâmetro +correspondente a ação será assumido como um valor boleano `true`. + +> Note|Nota: Não são suportados formatos de opções alternativos como +> `--type News`, `-t News`. + +Um parâmetro pode ter um valor em array apenas declarando-o como um tipo sugerido de array: + +~~~ +[php] +public function actionIndex(array $types) { ... } +~~~ + +Para fornecer os valores ao array, devemos simplesmente repetir a mesma opção na linha de comando, se necessário: + +~~~ +yiic sitemap index --types=News --types=Article +~~~ + +O comando acima irá por fim chamar `actionIndex(array('News', 'Article'))` + +A partir da versão 1.1.6, o Yii também suporta o uso de parâmetros de ação anônimos e opções globais. + +Parâmetros anônimos referem-se aos parâmetros de linha de comando que não estão no formato de opções. +Por exemplo, no comando `yiic sitemap index --limit=5 News` nós temos um parâmetro anônimo cujo valor +é `News` enquanto o parâmetro nomeado `limit` contém o valor 5. + +Para usar parâmetros anônimos, a ação de comando deve ser declarada com um parâmetro nomeado `$args`, Para exemplificar, + +~~~ +[php] +public function actionIndex($limit=10, $args=array()) {...} +~~~ + +O array `$args` vai guardar todos os valores dos parâmetros anônimos disponíveis. + +Opções globais referem-se às opções de linha de comando que são compartilhadas por todas as ações de um comando. +Por exemplo, em um comando que prevê diversas ações, pode ser que cada ação precisa reconhecer uma +opção nomeada `$verbose`. Enquanto podemos declarar um parâmetro `$verbose` em cada método de ação, +uma forma melhor, é declará-la como uma **variável pública** na classe de comando, que transformará +`$verbose` em uma opção global. + +~~~ +[php] +class SitemapCommand extends CConsoleCommand +{ + public $verbose=false; + public function actionIndex($type) {...} +} +~~~ + +O comando abaixo nos permite executar um comando com a opção `verbose`: + +~~~ +yiic sitemap index --verbose=1 --type=News +~~~ + + +Customizando a Aplicação de Console +-------------------------------- + +Por padrão, se uma aplicação é criada utilizando a ferramenta `yiic webapp`, a configuração +para a aplicação de console será criada em `protected/config/console.php`. Como um arquivo de configuração +de uma aplicação Web, este arquivo é um script PHP que retorna um array representando os valores iniciais +das propriedades para uma instância de aplicação de console. Como resultado, qualquer propriedade +pública de [CConsoleApplication], pode ser configurada neste arquivo. + +Como os comandos de console geralmente são criados para servir uma aplicação Web, +eles precisam acessar os recursos (como as conexões a banco de dados) que serão utilizado por essa aplicação. +Nós podemos fazê-lo no arquivo de configuração da aplicação de console, como a seguir: + +~~~ +[php] +return array( + ...... + 'components'=>array( + 'db'=>array( + ...... + ), + ), +); +~~~ + +Como podemos ver, o formato da configuração é muito semelhante ao que é utilizado na +configuração de um aplicativo Web. Isto porque [CConsoleApplication] e [CWebApplication] +compartilham a mesma classe base. + +
$Id: topics.console.txt 2867 2011-01-15 10:22:03Z haertl.mike $
diff --git a/docs/guide/pt/topics.webservice.txt b/docs/guide/pt/topics.webservice.txt index f72f4cf62..76bc28cf6 100644 --- a/docs/guide/pt/topics.webservice.txt +++ b/docs/guide/pt/topics.webservice.txt @@ -1,227 +1,227 @@ -Web Service -=========== - -[Web service](http://pt.wikipedia.org/wiki/Web_service) é um sistema -de software projetado para suportar interações máquina-máquina -interoperáveis através de uma rede. No contexto de aplicações Web -geralmente refere-se a um conjunto de APIs que podem ser acessadas -através da Internet e executadas em um sistema remoto que hospeda -o serviço solicitado. Por exemplo, um cliente baseado em -[Flex](http://www.adobe.com/products/flex/), poderá chamar uma função -implementada no lado do servidor rodando uma aplicação Web baseada em -PHP. Web Service se baseia em [SOAP](http://pt.wikipedia.org/wiki/SOAP) -como a camada principal da pilha de protocolo de comunicação. - -O Yii fornece as classes [CWebService] e [CWebServiceAction] para simplificar o trabalho -de implementação de Web Service em uma aplicação Web. As APIs são agrupadas -dentro de classes chamadas de *prestadores de serviço*. O Yii irá gerar para -cada classe uma especificação [WSDL](http://www.w3.org/TR/wsdl) que descreve -quais APIs estão disponíveis e como elas devem ser chamadas pelo cliente. -Quando uma API é chamada por um cliente, o Yii irá instanciar o prestador de -serviço correspondente e chamar a API requisitada para executar a requisição. - -> Note|Nota: A classe [CWebService] é depende da [Extensão PHP -SOAP](http://www.php.net/manual/en/ref.soap.php). Tenha certeza que -você possui ela habilitada antes de testar os exemplos disponíveis -nesta seção. - -Definindo um Prestador de Serviço -------------------------- - -Como mencionado acima, o prestador de serviços é uma classe que define os -métodos que podem ser chamados remotamente. O Yii se baseia em [comentários -de documentação](http://java.sun.com/j2se/javadoc/writingdoccomments/) e -[reflexão de classes](http://php.net/manual/en/book.reflection.php) para -identificar quais métodos podem ser chamados remotamente e quais são os seus -parâmetros e valores retornados. - -Vamos iniciar com um simples serviço de cotação de ações. Este serviço -permite uma requisição de um cliente para cotar uma ação específica. -Definimos o prestador de serviços como a seguir. Note que definimos a classe -prestadora de serviço `StockController` como extensão de [CController]. Isto -não é obrigatório. Explicaremos mais adiante por que fazemos dessa forma. - -~~~ -[php] -class StockController extends CController -{ - /** - * @param string símbolo da ação - * @return float preço da ação - * @soap - */ - public function getPrice($symbol) - { - $prices=array('IBM'=>100, 'GOOGLE'=>350); - return isset($prices[$symbol])?$prices[$symbol]:0; - //...retorna o preço da ação para o $symbol - } -} -~~~ - -Acima declaramos o método `getPrice` para ser a API do Web Service, marcando-o -com a tag `@soap` no comentário de documentação. Contamos com a documentação de -comentário para especificar o tipo de dados dos parâmetros de entrada e dos valores -de retorno. APIs adicionais podem ser declaradas de forma semelhante. - - -Declarando Ações do Web Service ----------------------------- - -Uma vez definido o prestador de serviço, precisamos fazer com que ele esteja -disponível para os clientes. Particularmente, precisamos criar uma ação de controle -para expor este serviço. Isto pode ser feito facilmente declarando uma ação -[CWebServiceAction] na classe de controle. Para exemplificar, colocamos ela na classe -`StockController`. - -~~~ -[php] -class StockController extends CController -{ - public function actions() - { - return array( - 'quote'=>array( - 'class'=>'CWebServiceAction', - ), - ); - } - - /** - * @param string símbolo da ação - * @return float preço da ação - * @soap - */ - public function getPrice($symbol) - { - //...retorna o preço da ação para o $symbol - } -} -~~~ - -Isto é tudo o que precisamos para criar um Web Service! -Se tentar acessar a ação através da URL `http://hostname/path/to/index.php?r=stock/quote`, -vamos ver um monte de conteúdo XML que é o WSDL para o Web Service que definimos . - -> Tip|Dica: Por padrão, [CWebServiceAction] assume que o controller corrente é -o prestador de serviço. É por isso que definimos o método `getPrice` dentro -da classe `StockController`. - -Utilizando um Web Service ---------------------- - -Para completar o exemplo, vamos criar um cliente que utiliza o Web Service -que acabamos de criar. O cliente de exemplo é escrito em PHP, porém, ele -poderia ser em outra linguagem como `Java`, `C#`, `Flex`, etc. - -~~~ -[php] -$client=new SoapClient('http://hostname/path/to/index.php?r=stock/quote'); -echo $client->getPrice('GOOGLE'); -~~~ - -Executando o script acima em modo Web ou console e veremos que `350` é o preço -para `GOOGLE`. - -Tipos de dados ----------- - -Quando declaramos métodos e propriedades de classes que são acessadas remotamente, -precisamos especificar o tipo de dados dos parâmetros de entrada e saída. A seguir, -os tipos de dados mais primitivos que podemos utilizar: - - - str/string: mapeado para `xsd:string`; - - int/integer: mapeado para `xsd:int`; - - float/double: mapeado para `xsd:float`; - - bool/boolean: mapeado para `xsd:boolean`; - - date: mapeado para `xsd:date`; - - time: mapeado para `xsd:time`; - - datetime: mapeado para `xsd:dateTime`; - - array: mapeado para `xsd:string`; - - object: mapeado para `xsd:struct`; - - mixed: mapeado para `xsd:anyType`. - - -Se um tipo não é um dos descritos acima, ele será considerado como um -tipo composto que consiste de propriedades. Um tipo composto é representado -em termos de uma classe e suas propriedades como variáveis públicas da classe, -marcadas com a tag `@soap` em seu comentário de documentação. - -Podemos utilizar também o tipo array adicionando `[]` no final do tipo primitivo -ou composto. Isto poderia especificar um array de um tipo específico. - -Abaixo um exemplo que define a Web API `getPosts` que retorna um array de -objetos `Post`. - -~~~ -[php] -class PostController extends CController -{ - /** - * @return Post[] uma lista de Post - * @soap - */ - public function getPosts() - { - return Post::model()->findAll(); - } -} - -class Post extends CActiveRecord -{ - /** - * @var integer ID do post - * @soap - */ - public $id; - /** - * @var string título do post - * @soap - */ - public $title; - - public static function model($className=__CLASS__) - { - return parent::model($className); - } -} -~~~ - -Mapeamento de Classes -------------- - -Para receber parâmetros de tipo composto de um cliente, um -aplicativo precisa declarar o mapeamento de tipos WSDL para as -classes PHP correspondentes. Isto é realizado configurando a propriedade -[classMap|CWebServiceAction::classMap] de [CWebServiceAction]. - -~~~ -[php] -class PostController extends CController -{ - public function actions() - { - return array( - 'service'=>array( - 'class'=>'CWebServiceAction', - 'classMap'=>array( - 'Post'=>'Post', // ou simplesmente 'Post' - ), - ), - ); - } - ...... -} -~~~ - -Interceptando Chamadas a Método Remoto -------------------------------------- - -Com a implementação da interface [IWebServiceProvider], um prestador de serviço -pode interceptar chamadas a método remoto. Em [IWebServiceProvider::beforeWebMethod], -o prestador de serviços pode recuperar o estado atual da instância de [CWebService] -e obter o nome do método que está sendo chamado atualmente através de -[CWebService::methodName]. Ele pode retornar false se o método remoto não pode ser -chamado por algum motivo (por exemplo, acesso não autorizado). - -
$Id: topics.webservice.txt 1808 2010-02-17 21:49:42Z qiang.xue $
+Web Service +=========== + +[Web service](http://pt.wikipedia.org/wiki/Web_service) é um sistema +de software projetado para suportar interações máquina-máquina +interoperáveis através de uma rede. No contexto de aplicações Web +geralmente refere-se a um conjunto de APIs que podem ser acessadas +através da Internet e executadas em um sistema remoto que hospeda +o serviço solicitado. Por exemplo, um cliente baseado em +[Flex](http://www.adobe.com/products/flex/), poderá chamar uma função +implementada no lado do servidor rodando uma aplicação Web baseada em +PHP. Web Service se baseia em [SOAP](http://pt.wikipedia.org/wiki/SOAP) +como a camada principal da pilha de protocolo de comunicação. + +O Yii fornece as classes [CWebService] e [CWebServiceAction] para simplificar o trabalho +de implementação de Web Service em uma aplicação Web. As APIs são agrupadas +dentro de classes chamadas de *prestadores de serviço*. O Yii irá gerar para +cada classe uma especificação [WSDL](http://www.w3.org/TR/wsdl) que descreve +quais APIs estão disponíveis e como elas devem ser chamadas pelo cliente. +Quando uma API é chamada por um cliente, o Yii irá instanciar o prestador de +serviço correspondente e chamar a API requisitada para executar a requisição. + +> Note|Nota: A classe [CWebService] é depende da [Extensão PHP +SOAP](http://www.php.net/manual/en/ref.soap.php). Tenha certeza que +você possui ela habilitada antes de testar os exemplos disponíveis +nesta seção. + +Definindo um Prestador de Serviço +------------------------- + +Como mencionado acima, o prestador de serviços é uma classe que define os +métodos que podem ser chamados remotamente. O Yii se baseia em [comentários +de documentação](http://java.sun.com/j2se/javadoc/writingdoccomments/) e +[reflexão de classes](http://php.net/manual/en/book.reflection.php) para +identificar quais métodos podem ser chamados remotamente e quais são os seus +parâmetros e valores retornados. + +Vamos iniciar com um simples serviço de cotação de ações. Este serviço +permite uma requisição de um cliente para cotar uma ação específica. +Definimos o prestador de serviços como a seguir. Note que definimos a classe +prestadora de serviço `StockController` como extensão de [CController]. Isto +não é obrigatório. Explicaremos mais adiante por que fazemos dessa forma. + +~~~ +[php] +class StockController extends CController +{ + /** + * @param string símbolo da ação + * @return float preço da ação + * @soap + */ + public function getPrice($symbol) + { + $prices=array('IBM'=>100, 'GOOGLE'=>350); + return isset($prices[$symbol])?$prices[$symbol]:0; + //...retorna o preço da ação para o $symbol + } +} +~~~ + +Acima declaramos o método `getPrice` para ser a API do Web Service, marcando-o +com a tag `@soap` no comentário de documentação. Contamos com a documentação de +comentário para especificar o tipo de dados dos parâmetros de entrada e dos valores +de retorno. APIs adicionais podem ser declaradas de forma semelhante. + + +Declarando Ações do Web Service +---------------------------- + +Uma vez definido o prestador de serviço, precisamos fazer com que ele esteja +disponível para os clientes. Particularmente, precisamos criar uma ação de controle +para expor este serviço. Isto pode ser feito facilmente declarando uma ação +[CWebServiceAction] na classe de controle. Para exemplificar, colocamos ela na classe +`StockController`. + +~~~ +[php] +class StockController extends CController +{ + public function actions() + { + return array( + 'quote'=>array( + 'class'=>'CWebServiceAction', + ), + ); + } + + /** + * @param string símbolo da ação + * @return float preço da ação + * @soap + */ + public function getPrice($symbol) + { + //...retorna o preço da ação para o $symbol + } +} +~~~ + +Isto é tudo o que precisamos para criar um Web Service! +Se tentar acessar a ação através da URL `http://hostname/path/to/index.php?r=stock/quote`, +vamos ver um monte de conteúdo XML que é o WSDL para o Web Service que definimos . + +> Tip|Dica: Por padrão, [CWebServiceAction] assume que o controller corrente é +o prestador de serviço. É por isso que definimos o método `getPrice` dentro +da classe `StockController`. + +Utilizando um Web Service +--------------------- + +Para completar o exemplo, vamos criar um cliente que utiliza o Web Service +que acabamos de criar. O cliente de exemplo é escrito em PHP, porém, ele +poderia ser em outra linguagem como `Java`, `C#`, `Flex`, etc. + +~~~ +[php] +$client=new SoapClient('http://hostname/path/to/index.php?r=stock/quote'); +echo $client->getPrice('GOOGLE'); +~~~ + +Executando o script acima em modo Web ou console e veremos que `350` é o preço +para `GOOGLE`. + +Tipos de dados +---------- + +Quando declaramos métodos e propriedades de classes que são acessadas remotamente, +precisamos especificar o tipo de dados dos parâmetros de entrada e saída. A seguir, +os tipos de dados mais primitivos que podemos utilizar: + + - str/string: mapeado para `xsd:string`; + - int/integer: mapeado para `xsd:int`; + - float/double: mapeado para `xsd:float`; + - bool/boolean: mapeado para `xsd:boolean`; + - date: mapeado para `xsd:date`; + - time: mapeado para `xsd:time`; + - datetime: mapeado para `xsd:dateTime`; + - array: mapeado para `xsd:string`; + - object: mapeado para `xsd:struct`; + - mixed: mapeado para `xsd:anyType`. + + +Se um tipo não é um dos descritos acima, ele será considerado como um +tipo composto que consiste de propriedades. Um tipo composto é representado +em termos de uma classe e suas propriedades como variáveis públicas da classe, +marcadas com a tag `@soap` em seu comentário de documentação. + +Podemos utilizar também o tipo array adicionando `[]` no final do tipo primitivo +ou composto. Isto poderia especificar um array de um tipo específico. + +Abaixo um exemplo que define a Web API `getPosts` que retorna um array de +objetos `Post`. + +~~~ +[php] +class PostController extends CController +{ + /** + * @return Post[] uma lista de Post + * @soap + */ + public function getPosts() + { + return Post::model()->findAll(); + } +} + +class Post extends CActiveRecord +{ + /** + * @var integer ID do post + * @soap + */ + public $id; + /** + * @var string título do post + * @soap + */ + public $title; + + public static function model($className=__CLASS__) + { + return parent::model($className); + } +} +~~~ + +Mapeamento de Classes +------------- + +Para receber parâmetros de tipo composto de um cliente, um +aplicativo precisa declarar o mapeamento de tipos WSDL para as +classes PHP correspondentes. Isto é realizado configurando a propriedade +[classMap|CWebServiceAction::classMap] de [CWebServiceAction]. + +~~~ +[php] +class PostController extends CController +{ + public function actions() + { + return array( + 'service'=>array( + 'class'=>'CWebServiceAction', + 'classMap'=>array( + 'Post'=>'Post', // ou simplesmente 'Post' + ), + ), + ); + } + ...... +} +~~~ + +Interceptando Chamadas a Método Remoto +------------------------------------- + +Com a implementação da interface [IWebServiceProvider], um prestador de serviço +pode interceptar chamadas a método remoto. Em [IWebServiceProvider::beforeWebMethod], +o prestador de serviços pode recuperar o estado atual da instância de [CWebService] +e obter o nome do método que está sendo chamado atualmente através de +[CWebService::methodName]. Ele pode retornar false se o método remoto não pode ser +chamado por algum motivo (por exemplo, acesso não autorizado). + +
$Id: topics.webservice.txt 1808 2010-02-17 21:49:42Z qiang.xue $
diff --git a/docs/guide/pt_br/basics.convention.txt b/docs/guide/pt_br/basics.convention.txt index 74b894de5..a128b9f4f 100644 --- a/docs/guide/pt_br/basics.convention.txt +++ b/docs/guide/pt_br/basics.convention.txt @@ -1,167 +1,167 @@ -Convenções -=========== - -O Yii favorece convenções sobre configurações. Siga as convenções e você -poderá criar aplicações sofisticadas sem ter que escrever ou gerenciar -configurações complexas. Evidentemente, o Yii ainda podem ser personalizados em quase -todos os aspectos, com configurações, quando necessário. - -Abaixo descrevemos convenções que são recomendadas para programar com o Yii. -Por conveniência, assumimos que `WebRoot` é o diretório onde está instalada uma aplicação desenvolvida com o Yii framework. - - -URL ---- - -Por padrão, o Yii reconhece URLs com o seguinte formato: - -~~~ -http://hostname/index.php?r=ControllerID/ActionID -~~~ - -A variável `r`, passada via GET, refere-se a -[rota](/doc/guide/basics.controller#route), que pode ser interpretada pelo Yii -como um controle e uma ação. Se o id da ação (`ActionID`) for omitido, o controle irá -utilizar a ação padrão (definida através da propriedade [CController::defaultAction]); e se -o id do controle (`ControllerID`) também for omitido (ou a variável `r` estiver ausente), a -aplicação irá utilizar o controle padrão (definido através da propriedade -[CWebApplication::defaultController]). - -Com a ajuda da classe [CUrlManager], é possível criar e reconhecer -URLs mais amigáveis, ao estilo SEO, tais como -`http://hostname/ControllerID/ActionID.html`. Esta funcionalidade é abordada em -detalhes em [Gerenciamento de URL](/doc/guide/topics.url). - -Código ----- - -O Yii recomenda que nomes de variáveis, funções e nomes de classe sejam escritos no formato Camel Case, -onde inicia-se cada palavra com letra maiúscula e junta-se todas, sem espaços entre elas. -Variáveis e nomes de funções devem ter a sua primeira palavra totalmente em letras minúsculas, -a fim de diferencia-los dos nomes das classes (por exemplo, `$basePath`, -`runController()`, `LinkPager`). Para as variáveis privadas membros de classe, -é recomendado prefixar seus nomes com um underscore (por exemplo, -`$_actionList`). - -Como não há suporte a namespaces antes do PHP 5.3.0, é recomendado -que as classes sejam denominadas de uma forma única, para evitar conflitos com nomes de -classes de terceiros. Por esta razão, todas as classes do Yii framework são -prefixadas com a letra "C". - -Existe uma regra especial para as classes de controle, onde deve-se adicionar -o sufixo `Controller` ao nome da classe. O ID do controle é, então, definido como o nome -da classe, com a primeira letra minúscula, e a palavra `Controller` removida. -Por exemplo, a classe `PageController` terá o ID `page`. Esta regra -torna a aplicação mais segura. Também deixa mais limpas as URLs relacionados aos -controles (por exemplo, `/index.php?r=page/index` em vez de -`/index.php?r=PageController/index`). - -Configuração -------------- - -A configuração é um vetor de pares chave-valor. Cada chave representa o -nome de uma propriedade do objeto a ser configurado, e cada valor, o -valor inicial da propriedade correspondente. Por exemplo, `array('name'=>'Minha -aplicação', 'basePath'=>'/protected')` inicializa as propriedades `name` e -`basePath` com os valores correspondentes no vetor. - -Qualquer propriedades "alterável" de um objeto pode ser configurada. Se não forem configuradas, -as propriedades assumirão seus valores padrão. Ao configurar uma propriedade, -vale a pena ler a documentação correspondente, para que o -valor inicial seja configurado corretamente. - -Arquivo ----- - -As convenções para nomenclatura e utilização de arquivos dependem seus tipos. - -Arquivos de classe devem ser nomeados de acordo com a classe pública que contém. Por -exemplo, a classe [CController] está no arquivo `CController.php`. Uma -classe pública é uma classe que pode ser utilizada por qualquer outra. Cada arquivo de -classe deve conter, no máximo, uma classe pública. Classes privadas (aquelas -que são utilizadas apenas por uma única classe pública) podem residir no mesmo arquivo com -a classe que a utiliza. - -Os arquivos das visões devem ser nomeados de acordo com o seus nomes. Por exemplo, a visão `index` -está no arquivo `index.php`. O arquivo de uma visão contém um script -com código HTML e PHP, utilizado, principalmente para apresentação de conteúdo. - -Arquivos de configuração podem ser nomeadas arbitrariamente. Um arquivo de configuração é um -script em PHP cuja única finalidade é a de retornar um vetor associativo -representando a configuração. - -Diretório ---------- - -O Yii assume um conjunto predefinido de diretórios utilizados para diversas finalidades. Cada um -deles pode ser personalizado, se necessário. - -- `WebRoot/protected`: este é o [diretório base -da aplicação](/doc/guide/basics.application#application-base-directory), onde estão todos os -scripts PHP que precisão estar seguros e os arquivos de dados. O Yii tem um apelido (alias) padrão -chamado `application`, associado a este caminho. Este diretório, e -tudo dentro dele, deve estar protegido para não ser acessado via web. Ele -pode ser alterado através da propriedade [CWebApplication::basePath]. - -- `WebRoot/protected/runtime`: este diretório armazena arquivos privados temporários -gerados durante a execução da aplicação. Este diretório deve ter -permissão de escrita para o processo do servidor Web. Ele pode ser alterado através da -propriedade [CApplication::runtimePath]. - -- `WebRoot/protected/extensions`: este diretório armazena todas as extensões -de terceiros. Ele pode ser alterado através da propriedade [CApplication::extensionPath]. - -- `WebRoot/protected/modules`: este diretório contém todos os -[módulos](/doc/guide/basics.module) da aplicação, cada um representado como um subdiretório. - -- `WebRoot/protected/controllers`: neste diretório estão os arquivos de classe -de todos os controles. Ele pode ser alterado através da propriedade [CWebApplication::controllerPath]. - -- `WebRoot/protected/views`: este diretório possui todos os arquivos das visões, -incluindo as visões dos controles, visões do layout e visões do sistema. Ele pode ser alterado -através da propriedade [CWebApplication::viewPath]. - -- `WebRoot/protected/views/ControllerID`: neste diretório estão os arquivos das visões -para um controle específico. Aqui, `ControllerID` é o ID -do controle. Ele pode ser alterado através da propriedade [CController::viewPath]. - -- `WebRoot/protected/views/layouts`: este diretório possui todos os arquivos de visão -do layout. Ele pode ser alterado através da propriedade [CWebApplication::layoutPath]. - -- `WebRoot/protected/views/system`: este diretório mantém todos os arquivos -de visões do sistema. Visões do sistema são templates utilizados para exibir exceções e -erros. Ele pode ser alterado através da propriedade [CWebApplication::systemViewPath]. - -- `WebRoot/assets`: este diretório mantém os assets publicados. Um -asset é um arquivo privado que pode ser publicado para se tornar acessível aos -usuários, via web. Este diretório deve ter permissão de escrita para o processo do servidor Web. Ele pode ser -alterado através da propriedade [CAssetManager::basePath]. - -- `WebRoot/themes`: este diretório armazena vários temas que podem ser -aplicados à aplicação. Cada subdiretório representa um único tema -cujo nome é o nome do tema. Ele pode ser alterado através da propriedade -[CThemeManager::basePath]. - -Banco de Dados --------------- - -A maioria das aplicações web utilizam algum tipo de banco de dados. Como boa prática, -propomos as seguintes convenções para a criação de nomes de tabelas e colunas. Note -que nenhuma delas é obrigatória para a utilização do Yii. - - - Nomes de tabelas e colunas devem utilizar apenas letras minúsculas. - - - As palavras de um nome devem ser separadas por underscores (ex.: `product_order`). - - - Para as tabelas, você pode utilizar nomes no singular ou no plural, mas nunca -ambos ao mesmo tempo. Para simplificar, recomendamos a utilização de nomes no singular. - - - Os nomes das tabelas devem ser prefixados com um token como, por exemplo, -`tbl_`. Isso é especialmente útil em casos onde as tabelas de uma aplicação estão -no mesmo banco de dados utilizado por tabelas de outra aplicação. Assim, os dois -conjuntos de tabelas podem ser lidos separadamente utilizando os prefixos dos nomes -das tabelas. - -
$Id: basics.convention.txt 2345 2010-08-28 12:51:08Z mdomba $
- -
$Id: basics.convention.txt 749 2009-02-26 02:11:31Z qiang.xue $
+Convenções +=========== + +O Yii favorece convenções sobre configurações. Siga as convenções e você +poderá criar aplicações sofisticadas sem ter que escrever ou gerenciar +configurações complexas. Evidentemente, o Yii ainda podem ser personalizados em quase +todos os aspectos, com configurações, quando necessário. + +Abaixo descrevemos convenções que são recomendadas para programar com o Yii. +Por conveniência, assumimos que `WebRoot` é o diretório onde está instalada uma aplicação desenvolvida com o Yii framework. + + +URL +--- + +Por padrão, o Yii reconhece URLs com o seguinte formato: + +~~~ +http://hostname/index.php?r=ControllerID/ActionID +~~~ + +A variável `r`, passada via GET, refere-se a +[rota](/doc/guide/basics.controller#route), que pode ser interpretada pelo Yii +como um controle e uma ação. Se o id da ação (`ActionID`) for omitido, o controle irá +utilizar a ação padrão (definida através da propriedade [CController::defaultAction]); e se +o id do controle (`ControllerID`) também for omitido (ou a variável `r` estiver ausente), a +aplicação irá utilizar o controle padrão (definido através da propriedade +[CWebApplication::defaultController]). + +Com a ajuda da classe [CUrlManager], é possível criar e reconhecer +URLs mais amigáveis, ao estilo SEO, tais como +`http://hostname/ControllerID/ActionID.html`. Esta funcionalidade é abordada em +detalhes em [Gerenciamento de URL](/doc/guide/topics.url). + +Código +---- + +O Yii recomenda que nomes de variáveis, funções e nomes de classe sejam escritos no formato Camel Case, +onde inicia-se cada palavra com letra maiúscula e junta-se todas, sem espaços entre elas. +Variáveis e nomes de funções devem ter a sua primeira palavra totalmente em letras minúsculas, +a fim de diferencia-los dos nomes das classes (por exemplo, `$basePath`, +`runController()`, `LinkPager`). Para as variáveis privadas membros de classe, +é recomendado prefixar seus nomes com um underscore (por exemplo, +`$_actionList`). + +Como não há suporte a namespaces antes do PHP 5.3.0, é recomendado +que as classes sejam denominadas de uma forma única, para evitar conflitos com nomes de +classes de terceiros. Por esta razão, todas as classes do Yii framework são +prefixadas com a letra "C". + +Existe uma regra especial para as classes de controle, onde deve-se adicionar +o sufixo `Controller` ao nome da classe. O ID do controle é, então, definido como o nome +da classe, com a primeira letra minúscula, e a palavra `Controller` removida. +Por exemplo, a classe `PageController` terá o ID `page`. Esta regra +torna a aplicação mais segura. Também deixa mais limpas as URLs relacionados aos +controles (por exemplo, `/index.php?r=page/index` em vez de +`/index.php?r=PageController/index`). + +Configuração +------------- + +A configuração é um vetor de pares chave-valor. Cada chave representa o +nome de uma propriedade do objeto a ser configurado, e cada valor, o +valor inicial da propriedade correspondente. Por exemplo, `array('name'=>'Minha +aplicação', 'basePath'=>'/protected')` inicializa as propriedades `name` e +`basePath` com os valores correspondentes no vetor. + +Qualquer propriedades "alterável" de um objeto pode ser configurada. Se não forem configuradas, +as propriedades assumirão seus valores padrão. Ao configurar uma propriedade, +vale a pena ler a documentação correspondente, para que o +valor inicial seja configurado corretamente. + +Arquivo +---- + +As convenções para nomenclatura e utilização de arquivos dependem seus tipos. + +Arquivos de classe devem ser nomeados de acordo com a classe pública que contém. Por +exemplo, a classe [CController] está no arquivo `CController.php`. Uma +classe pública é uma classe que pode ser utilizada por qualquer outra. Cada arquivo de +classe deve conter, no máximo, uma classe pública. Classes privadas (aquelas +que são utilizadas apenas por uma única classe pública) podem residir no mesmo arquivo com +a classe que a utiliza. + +Os arquivos das visões devem ser nomeados de acordo com o seus nomes. Por exemplo, a visão `index` +está no arquivo `index.php`. O arquivo de uma visão contém um script +com código HTML e PHP, utilizado, principalmente para apresentação de conteúdo. + +Arquivos de configuração podem ser nomeadas arbitrariamente. Um arquivo de configuração é um +script em PHP cuja única finalidade é a de retornar um vetor associativo +representando a configuração. + +Diretório +--------- + +O Yii assume um conjunto predefinido de diretórios utilizados para diversas finalidades. Cada um +deles pode ser personalizado, se necessário. + +- `WebRoot/protected`: este é o [diretório base +da aplicação](/doc/guide/basics.application#application-base-directory), onde estão todos os +scripts PHP que precisão estar seguros e os arquivos de dados. O Yii tem um apelido (alias) padrão +chamado `application`, associado a este caminho. Este diretório, e +tudo dentro dele, deve estar protegido para não ser acessado via web. Ele +pode ser alterado através da propriedade [CWebApplication::basePath]. + +- `WebRoot/protected/runtime`: este diretório armazena arquivos privados temporários +gerados durante a execução da aplicação. Este diretório deve ter +permissão de escrita para o processo do servidor Web. Ele pode ser alterado através da +propriedade [CApplication::runtimePath]. + +- `WebRoot/protected/extensions`: este diretório armazena todas as extensões +de terceiros. Ele pode ser alterado através da propriedade [CApplication::extensionPath]. + +- `WebRoot/protected/modules`: este diretório contém todos os +[módulos](/doc/guide/basics.module) da aplicação, cada um representado como um subdiretório. + +- `WebRoot/protected/controllers`: neste diretório estão os arquivos de classe +de todos os controles. Ele pode ser alterado através da propriedade [CWebApplication::controllerPath]. + +- `WebRoot/protected/views`: este diretório possui todos os arquivos das visões, +incluindo as visões dos controles, visões do layout e visões do sistema. Ele pode ser alterado +através da propriedade [CWebApplication::viewPath]. + +- `WebRoot/protected/views/ControllerID`: neste diretório estão os arquivos das visões +para um controle específico. Aqui, `ControllerID` é o ID +do controle. Ele pode ser alterado através da propriedade [CController::viewPath]. + +- `WebRoot/protected/views/layouts`: este diretório possui todos os arquivos de visão +do layout. Ele pode ser alterado através da propriedade [CWebApplication::layoutPath]. + +- `WebRoot/protected/views/system`: este diretório mantém todos os arquivos +de visões do sistema. Visões do sistema são templates utilizados para exibir exceções e +erros. Ele pode ser alterado através da propriedade [CWebApplication::systemViewPath]. + +- `WebRoot/assets`: este diretório mantém os assets publicados. Um +asset é um arquivo privado que pode ser publicado para se tornar acessível aos +usuários, via web. Este diretório deve ter permissão de escrita para o processo do servidor Web. Ele pode ser +alterado através da propriedade [CAssetManager::basePath]. + +- `WebRoot/themes`: este diretório armazena vários temas que podem ser +aplicados à aplicação. Cada subdiretório representa um único tema +cujo nome é o nome do tema. Ele pode ser alterado através da propriedade +[CThemeManager::basePath]. + +Banco de Dados +-------------- + +A maioria das aplicações web utilizam algum tipo de banco de dados. Como boa prática, +propomos as seguintes convenções para a criação de nomes de tabelas e colunas. Note +que nenhuma delas é obrigatória para a utilização do Yii. + + - Nomes de tabelas e colunas devem utilizar apenas letras minúsculas. + + - As palavras de um nome devem ser separadas por underscores (ex.: `product_order`). + + - Para as tabelas, você pode utilizar nomes no singular ou no plural, mas nunca +ambos ao mesmo tempo. Para simplificar, recomendamos a utilização de nomes no singular. + + - Os nomes das tabelas devem ser prefixados com um token como, por exemplo, +`tbl_`. Isso é especialmente útil em casos onde as tabelas de uma aplicação estão +no mesmo banco de dados utilizado por tabelas de outra aplicação. Assim, os dois +conjuntos de tabelas podem ser lidos separadamente utilizando os prefixos dos nomes +das tabelas. + +
$Id: basics.convention.txt 2345 2010-08-28 12:51:08Z mdomba $
+ +
$Id: basics.convention.txt 749 2009-02-26 02:11:31Z qiang.xue $
diff --git a/docs/guide/pt_br/topics.console.txt b/docs/guide/pt_br/topics.console.txt index e2657b87b..bcb723885 100644 --- a/docs/guide/pt_br/topics.console.txt +++ b/docs/guide/pt_br/topics.console.txt @@ -1,226 +1,226 @@ -Aplicativos de Console -==================== - -Aplicativos de console são utilizados para realizar algum trabalho offline necessário -por um aplicativo Web online como, geração de código, compilação do índice de busca, -envio de e-mail, etc. O Yii fornece um framework para escrever aplicativos de console -de uma maneira orientada a objetos. Ele permite que um aplicativo de console acesse os -recursos (por exemplo, conexões a banco de dados) que são utilizados por uma aplicação -Web online. - - -Visão Geral --------- - -O Yii representa cada tarefa de console como um [comando|CConsoleCommand]. -Um comando de console é uma classe que estende de [CConsoleCommand]. - -Quando utilizamos a ferramenta `yiic webapp` para criar o esqueleto inicial de uma -aplicação Yii, podemos encontrar dois arquivos no diretório `protected`: - -* `yiic`: este é um script executável utilizado no Linux/Unix; -* `yiic.bat`: este é um arquivo batch executável utilizado no Windows. - -Na janela de console, podemos informar os seguintes comandos: - -~~~ -cd protected -yiic help -~~~ - -Este comando irá mostrar uma lista dos comandos de console disponíveis. Por padrão, -os comandos de console disponíveis, incluem os fornecedidos pelo Yii Framework -(chamados de **comandos de sistema**) e aqueles desenvolvidos pelos usuários para -aplicações individuais (chamados **comandos de usuário**). - -Para descobrir como utilizar um determinado comando, podemos executar: - -~~~ -yiic help -~~~ - -E para executar um comando, podemos utilizar o seguinte formato: - -~~~ -yiic [parâmetros...] -~~~ - - -Criando Comandos ------------------ - -Comandos de console são armazenados como arquivos de classe dentro do diretório -especificado em [CConsoleApplication::commandPath]. Por padrão, o diretório -`protected/commands` é utilizado. - -Uma classe de comando de console deve estender [CConsoleCommand]. Seu nome -deve ter o formato `XyzCommand`, onde `Xyz` referencia o nome do comando com a primeira letra -em maiúsculo. Para exemplificar, o comando `sitemap` deve usar o nome de classe `SitemapCommand`. -Comandos de console são case-sensitive, ou seja, há diferença entre maiúsculas de minúsculas. - -> Tip|Dica: Através da propriedade [CConsoleApplication::commandMap], podemos ter classes de comandos -> com diferentes convenções de nomenclatura e localizadas em diferentes diretórios. - -Para criar um comando, precisamos sobrescrever o método [CConsoleCommand::run()] -ou desenvolver uma ou mais ações de comando (isto será explicado na próxima sessão). - -Quando executamos um comando de console, o método [CConsoleCommand::run()] será -invocado pela aplicação. Qualquer parâmetro de comando será passado -para o método, de acordo com a assinatura de método a seguir: - -~~~ -[php] -public function run($args) { ... } -~~~ - -onde `$args` refere-se aos parâmetros extras informados na linha de comando. - -Dentro de um comando de console, utilizamos `Yii::app()` para acessar a instância -da aplicação de console, através da qual podemos acessar recursos, tal como a conexão ao banco de dados -(ex: `Yii::app()->db`). Como é possível perceber, a utilização é similar ao modo como é -feita em aplicações Web. - -> Info|Informação: A partir da versão 1.1.1, também podemos criar comandos globais que são -compartilhados para **todas** as aplicações Yii no mesmo computador. Para isso, definimos -uma variável de ambiente com o nome `YII_CONSOLE_COMMANDS` que deverá apontar para um -diretório existente. Dentro deste diretório colocamos os arquivos de classe de comandos. - - -Ações em Comandos de Console ----------------------- - -> Note|Nota: Esta funcionalidade está disponível desde a versão 1.1.5. - -Um comando de console, muitas vezes precisa lidar com diferentes parâmetros de linha de comando, -alguns necessários, outros opcionais. Um comando de console também pode precisar fornecer vários -sub-comandos para lidar com diferentes sub-tarefas. Estes trabalhos podem ser simplificados -utilizando ações de comando de console. - -Uma ação de comando de console é um método da classe de comando de console. -O nome do método deve ter o formato `actionXyz`, onde `Xyz` refere-se ao nome -da ação, com a primeira letra em maicúsculo. Por exemplo, o método `actionIndex` -define uma ação chamada `index`. - -Para executar uma ação específica, podemos usar o seguinte formado de comando de console: - -~~~ -yiic --option1=value --option2=value2 ... -~~~ - -Os pares de opção-valor adicionais, serão passados como parâmetros de chamada para o método da ação. -O valor da opção `xyz` será passado como o parâmetro `$xyz` para o método da ação. -Por exemplo, se definir a seguinte classe de comando: - -~~~ -[php] -class SitemapCommand extends CConsoleCommand -{ - public function actionIndex($type, $limit=5) { ... } - public function actionInit() { ... } -} -~~~ - -Então, os seguintes comandos de console irão todos resultar na chamada `actionIndex('News', 5)`: - -~~~ -yiic sitemap index --type=News --limit=5 - -// $limit possui valor padrão -yiic sitemap index --type=News - -// $limit possui valor padrão -// como 'index' é uma ação padrão, podemos omitir o nome da ação -yiic sitemap --type=News - -// a ordem das opções não tem importância -yiic sitemap index --limit=5 --type=News -~~~ - -Se uma opção é informada sem valor (ex: `--type` ao invés de `--type=News`), o valor do parâmetro -correspondente a ação será assumido como um valor boleano `true`. - -> Note|Nota: Não são suportados formatos de opções alternativos como -> `--type News`, `-t News`. - -Um parâmetro pode ter um valor em array apenas declarando-o como um tipo sugerido de array: - -~~~ -[php] -public function actionIndex(array $types) { ... } -~~~ - -Para fornecer os valores ao array, devemos simplesmente repetir a mesma opção na linha de comando, se necessário: - -~~~ -yiic sitemap index --types=News --types=Article -~~~ - -O comando acima irá por fim chamar `actionIndex(array('News', 'Article'))` - -A partir da versão 1.1.6, o Yii também suporta o uso de parâmetros de ação anônimos e opções globais. - -Parâmetros anônimos referem-se aos parâmetros de linha de comando que não estão no formato de opções. -Por exemplo, no comando `yiic sitemap index --limit=5 News` nós temos um parâmetro anônimo cujo valor -é `News` enquanto o parâmetro nomeado `limit` contém o valor 5. - -Para usar parâmetros anônimos, a ação de comando deve ser declarada com um parâmetro nomeado `$args`, Para exemplificar, - -~~~ -[php] -public function actionIndex($limit=10, $args=array()) {...} -~~~ - -O array `$args` vai guardar todos os valores dos parâmetros anônimos disponíveis. - -Opções globais referem-se às opções de linha de comando que são compartilhadas por todas as ações de um comando. -Por exemplo, em um comando que prevê diversas ações, pode ser que cada ação precisa reconhecer uma -opção nomeada `$verbose`. Enquanto podemos declarar um parâmetro `$verbose` em cada método de ação, -uma forma melhor, é declará-la como uma **variável pública** na classe de comando, que transformará -`$verbose` em uma opção global. - -~~~ -[php] -class SitemapCommand extends CConsoleCommand -{ - public $verbose=false; - public function actionIndex($type) {...} -} -~~~ - -O comando abaixo nos permite executar um comando com a opção `verbose`: - -~~~ -yiic sitemap index --verbose=1 --type=News -~~~ - - -Customizando a Aplicação de Console --------------------------------- - -Por padrão, se uma aplicação é criada utilizando a ferramenta `yiic webapp`, a configuração -para a aplicação de console será criada em `protected/config/console.php`. Como um arquivo de configuração -de uma aplicação Web, este arquivo é um script PHP que retorna um array representando os valores iniciais -das propriedades para uma instância de aplicação de console. Como resultado, qualquer propriedade -pública de [CConsoleApplication], pode ser configurada neste arquivo. - -Como os comandos de console geralmente são criados para servir uma aplicação Web, -eles precisam acessar os recursos (como as conexões a banco de dados) que serão utilizado por essa aplicação. -Nós podemos fazê-lo no arquivo de configuração da aplicação de console, como a seguir: - -~~~ -[php] -return array( - ...... - 'components'=>array( - 'db'=>array( - ...... - ), - ), -); -~~~ - -Como podemos ver, o formato da configuração é muito semelhante ao que é utilizado na -configuração de um aplicativo Web. Isto porque [CConsoleApplication] e [CWebApplication] -compartilham a mesma classe base. - -
$Id: topics.console.txt 2867 2011-01-15 10:22:03Z haertl.mike $
+Aplicativos de Console +==================== + +Aplicativos de console são utilizados para realizar algum trabalho offline necessário +por um aplicativo Web online como, geração de código, compilação do índice de busca, +envio de e-mail, etc. O Yii fornece um framework para escrever aplicativos de console +de uma maneira orientada a objetos. Ele permite que um aplicativo de console acesse os +recursos (por exemplo, conexões a banco de dados) que são utilizados por uma aplicação +Web online. + + +Visão Geral +-------- + +O Yii representa cada tarefa de console como um [comando|CConsoleCommand]. +Um comando de console é uma classe que estende de [CConsoleCommand]. + +Quando utilizamos a ferramenta `yiic webapp` para criar o esqueleto inicial de uma +aplicação Yii, podemos encontrar dois arquivos no diretório `protected`: + +* `yiic`: este é um script executável utilizado no Linux/Unix; +* `yiic.bat`: este é um arquivo batch executável utilizado no Windows. + +Na janela de console, podemos informar os seguintes comandos: + +~~~ +cd protected +yiic help +~~~ + +Este comando irá mostrar uma lista dos comandos de console disponíveis. Por padrão, +os comandos de console disponíveis, incluem os fornecedidos pelo Yii Framework +(chamados de **comandos de sistema**) e aqueles desenvolvidos pelos usuários para +aplicações individuais (chamados **comandos de usuário**). + +Para descobrir como utilizar um determinado comando, podemos executar: + +~~~ +yiic help +~~~ + +E para executar um comando, podemos utilizar o seguinte formato: + +~~~ +yiic [parâmetros...] +~~~ + + +Criando Comandos +----------------- + +Comandos de console são armazenados como arquivos de classe dentro do diretório +especificado em [CConsoleApplication::commandPath]. Por padrão, o diretório +`protected/commands` é utilizado. + +Uma classe de comando de console deve estender [CConsoleCommand]. Seu nome +deve ter o formato `XyzCommand`, onde `Xyz` referencia o nome do comando com a primeira letra +em maiúsculo. Para exemplificar, o comando `sitemap` deve usar o nome de classe `SitemapCommand`. +Comandos de console são case-sensitive, ou seja, há diferença entre maiúsculas de minúsculas. + +> Tip|Dica: Através da propriedade [CConsoleApplication::commandMap], podemos ter classes de comandos +> com diferentes convenções de nomenclatura e localizadas em diferentes diretórios. + +Para criar um comando, precisamos sobrescrever o método [CConsoleCommand::run()] +ou desenvolver uma ou mais ações de comando (isto será explicado na próxima sessão). + +Quando executamos um comando de console, o método [CConsoleCommand::run()] será +invocado pela aplicação. Qualquer parâmetro de comando será passado +para o método, de acordo com a assinatura de método a seguir: + +~~~ +[php] +public function run($args) { ... } +~~~ + +onde `$args` refere-se aos parâmetros extras informados na linha de comando. + +Dentro de um comando de console, utilizamos `Yii::app()` para acessar a instância +da aplicação de console, através da qual podemos acessar recursos, tal como a conexão ao banco de dados +(ex: `Yii::app()->db`). Como é possível perceber, a utilização é similar ao modo como é +feita em aplicações Web. + +> Info|Informação: A partir da versão 1.1.1, também podemos criar comandos globais que são +compartilhados para **todas** as aplicações Yii no mesmo computador. Para isso, definimos +uma variável de ambiente com o nome `YII_CONSOLE_COMMANDS` que deverá apontar para um +diretório existente. Dentro deste diretório colocamos os arquivos de classe de comandos. + + +Ações em Comandos de Console +---------------------- + +> Note|Nota: Esta funcionalidade está disponível desde a versão 1.1.5. + +Um comando de console, muitas vezes precisa lidar com diferentes parâmetros de linha de comando, +alguns necessários, outros opcionais. Um comando de console também pode precisar fornecer vários +sub-comandos para lidar com diferentes sub-tarefas. Estes trabalhos podem ser simplificados +utilizando ações de comando de console. + +Uma ação de comando de console é um método da classe de comando de console. +O nome do método deve ter o formato `actionXyz`, onde `Xyz` refere-se ao nome +da ação, com a primeira letra em maicúsculo. Por exemplo, o método `actionIndex` +define uma ação chamada `index`. + +Para executar uma ação específica, podemos usar o seguinte formado de comando de console: + +~~~ +yiic --option1=value --option2=value2 ... +~~~ + +Os pares de opção-valor adicionais, serão passados como parâmetros de chamada para o método da ação. +O valor da opção `xyz` será passado como o parâmetro `$xyz` para o método da ação. +Por exemplo, se definir a seguinte classe de comando: + +~~~ +[php] +class SitemapCommand extends CConsoleCommand +{ + public function actionIndex($type, $limit=5) { ... } + public function actionInit() { ... } +} +~~~ + +Então, os seguintes comandos de console irão todos resultar na chamada `actionIndex('News', 5)`: + +~~~ +yiic sitemap index --type=News --limit=5 + +// $limit possui valor padrão +yiic sitemap index --type=News + +// $limit possui valor padrão +// como 'index' é uma ação padrão, podemos omitir o nome da ação +yiic sitemap --type=News + +// a ordem das opções não tem importância +yiic sitemap index --limit=5 --type=News +~~~ + +Se uma opção é informada sem valor (ex: `--type` ao invés de `--type=News`), o valor do parâmetro +correspondente a ação será assumido como um valor boleano `true`. + +> Note|Nota: Não são suportados formatos de opções alternativos como +> `--type News`, `-t News`. + +Um parâmetro pode ter um valor em array apenas declarando-o como um tipo sugerido de array: + +~~~ +[php] +public function actionIndex(array $types) { ... } +~~~ + +Para fornecer os valores ao array, devemos simplesmente repetir a mesma opção na linha de comando, se necessário: + +~~~ +yiic sitemap index --types=News --types=Article +~~~ + +O comando acima irá por fim chamar `actionIndex(array('News', 'Article'))` + +A partir da versão 1.1.6, o Yii também suporta o uso de parâmetros de ação anônimos e opções globais. + +Parâmetros anônimos referem-se aos parâmetros de linha de comando que não estão no formato de opções. +Por exemplo, no comando `yiic sitemap index --limit=5 News` nós temos um parâmetro anônimo cujo valor +é `News` enquanto o parâmetro nomeado `limit` contém o valor 5. + +Para usar parâmetros anônimos, a ação de comando deve ser declarada com um parâmetro nomeado `$args`, Para exemplificar, + +~~~ +[php] +public function actionIndex($limit=10, $args=array()) {...} +~~~ + +O array `$args` vai guardar todos os valores dos parâmetros anônimos disponíveis. + +Opções globais referem-se às opções de linha de comando que são compartilhadas por todas as ações de um comando. +Por exemplo, em um comando que prevê diversas ações, pode ser que cada ação precisa reconhecer uma +opção nomeada `$verbose`. Enquanto podemos declarar um parâmetro `$verbose` em cada método de ação, +uma forma melhor, é declará-la como uma **variável pública** na classe de comando, que transformará +`$verbose` em uma opção global. + +~~~ +[php] +class SitemapCommand extends CConsoleCommand +{ + public $verbose=false; + public function actionIndex($type) {...} +} +~~~ + +O comando abaixo nos permite executar um comando com a opção `verbose`: + +~~~ +yiic sitemap index --verbose=1 --type=News +~~~ + + +Customizando a Aplicação de Console +-------------------------------- + +Por padrão, se uma aplicação é criada utilizando a ferramenta `yiic webapp`, a configuração +para a aplicação de console será criada em `protected/config/console.php`. Como um arquivo de configuração +de uma aplicação Web, este arquivo é um script PHP que retorna um array representando os valores iniciais +das propriedades para uma instância de aplicação de console. Como resultado, qualquer propriedade +pública de [CConsoleApplication], pode ser configurada neste arquivo. + +Como os comandos de console geralmente são criados para servir uma aplicação Web, +eles precisam acessar os recursos (como as conexões a banco de dados) que serão utilizado por essa aplicação. +Nós podemos fazê-lo no arquivo de configuração da aplicação de console, como a seguir: + +~~~ +[php] +return array( + ...... + 'components'=>array( + 'db'=>array( + ...... + ), + ), +); +~~~ + +Como podemos ver, o formato da configuração é muito semelhante ao que é utilizado na +configuração de um aplicativo Web. Isto porque [CConsoleApplication] e [CWebApplication] +compartilham a mesma classe base. + +
$Id: topics.console.txt 2867 2011-01-15 10:22:03Z haertl.mike $
diff --git a/docs/guide/pt_br/topics.webservice.txt b/docs/guide/pt_br/topics.webservice.txt index f72f4cf62..76bc28cf6 100644 --- a/docs/guide/pt_br/topics.webservice.txt +++ b/docs/guide/pt_br/topics.webservice.txt @@ -1,227 +1,227 @@ -Web Service -=========== - -[Web service](http://pt.wikipedia.org/wiki/Web_service) é um sistema -de software projetado para suportar interações máquina-máquina -interoperáveis através de uma rede. No contexto de aplicações Web -geralmente refere-se a um conjunto de APIs que podem ser acessadas -através da Internet e executadas em um sistema remoto que hospeda -o serviço solicitado. Por exemplo, um cliente baseado em -[Flex](http://www.adobe.com/products/flex/), poderá chamar uma função -implementada no lado do servidor rodando uma aplicação Web baseada em -PHP. Web Service se baseia em [SOAP](http://pt.wikipedia.org/wiki/SOAP) -como a camada principal da pilha de protocolo de comunicação. - -O Yii fornece as classes [CWebService] e [CWebServiceAction] para simplificar o trabalho -de implementação de Web Service em uma aplicação Web. As APIs são agrupadas -dentro de classes chamadas de *prestadores de serviço*. O Yii irá gerar para -cada classe uma especificação [WSDL](http://www.w3.org/TR/wsdl) que descreve -quais APIs estão disponíveis e como elas devem ser chamadas pelo cliente. -Quando uma API é chamada por um cliente, o Yii irá instanciar o prestador de -serviço correspondente e chamar a API requisitada para executar a requisição. - -> Note|Nota: A classe [CWebService] é depende da [Extensão PHP -SOAP](http://www.php.net/manual/en/ref.soap.php). Tenha certeza que -você possui ela habilitada antes de testar os exemplos disponíveis -nesta seção. - -Definindo um Prestador de Serviço -------------------------- - -Como mencionado acima, o prestador de serviços é uma classe que define os -métodos que podem ser chamados remotamente. O Yii se baseia em [comentários -de documentação](http://java.sun.com/j2se/javadoc/writingdoccomments/) e -[reflexão de classes](http://php.net/manual/en/book.reflection.php) para -identificar quais métodos podem ser chamados remotamente e quais são os seus -parâmetros e valores retornados. - -Vamos iniciar com um simples serviço de cotação de ações. Este serviço -permite uma requisição de um cliente para cotar uma ação específica. -Definimos o prestador de serviços como a seguir. Note que definimos a classe -prestadora de serviço `StockController` como extensão de [CController]. Isto -não é obrigatório. Explicaremos mais adiante por que fazemos dessa forma. - -~~~ -[php] -class StockController extends CController -{ - /** - * @param string símbolo da ação - * @return float preço da ação - * @soap - */ - public function getPrice($symbol) - { - $prices=array('IBM'=>100, 'GOOGLE'=>350); - return isset($prices[$symbol])?$prices[$symbol]:0; - //...retorna o preço da ação para o $symbol - } -} -~~~ - -Acima declaramos o método `getPrice` para ser a API do Web Service, marcando-o -com a tag `@soap` no comentário de documentação. Contamos com a documentação de -comentário para especificar o tipo de dados dos parâmetros de entrada e dos valores -de retorno. APIs adicionais podem ser declaradas de forma semelhante. - - -Declarando Ações do Web Service ----------------------------- - -Uma vez definido o prestador de serviço, precisamos fazer com que ele esteja -disponível para os clientes. Particularmente, precisamos criar uma ação de controle -para expor este serviço. Isto pode ser feito facilmente declarando uma ação -[CWebServiceAction] na classe de controle. Para exemplificar, colocamos ela na classe -`StockController`. - -~~~ -[php] -class StockController extends CController -{ - public function actions() - { - return array( - 'quote'=>array( - 'class'=>'CWebServiceAction', - ), - ); - } - - /** - * @param string símbolo da ação - * @return float preço da ação - * @soap - */ - public function getPrice($symbol) - { - //...retorna o preço da ação para o $symbol - } -} -~~~ - -Isto é tudo o que precisamos para criar um Web Service! -Se tentar acessar a ação através da URL `http://hostname/path/to/index.php?r=stock/quote`, -vamos ver um monte de conteúdo XML que é o WSDL para o Web Service que definimos . - -> Tip|Dica: Por padrão, [CWebServiceAction] assume que o controller corrente é -o prestador de serviço. É por isso que definimos o método `getPrice` dentro -da classe `StockController`. - -Utilizando um Web Service ---------------------- - -Para completar o exemplo, vamos criar um cliente que utiliza o Web Service -que acabamos de criar. O cliente de exemplo é escrito em PHP, porém, ele -poderia ser em outra linguagem como `Java`, `C#`, `Flex`, etc. - -~~~ -[php] -$client=new SoapClient('http://hostname/path/to/index.php?r=stock/quote'); -echo $client->getPrice('GOOGLE'); -~~~ - -Executando o script acima em modo Web ou console e veremos que `350` é o preço -para `GOOGLE`. - -Tipos de dados ----------- - -Quando declaramos métodos e propriedades de classes que são acessadas remotamente, -precisamos especificar o tipo de dados dos parâmetros de entrada e saída. A seguir, -os tipos de dados mais primitivos que podemos utilizar: - - - str/string: mapeado para `xsd:string`; - - int/integer: mapeado para `xsd:int`; - - float/double: mapeado para `xsd:float`; - - bool/boolean: mapeado para `xsd:boolean`; - - date: mapeado para `xsd:date`; - - time: mapeado para `xsd:time`; - - datetime: mapeado para `xsd:dateTime`; - - array: mapeado para `xsd:string`; - - object: mapeado para `xsd:struct`; - - mixed: mapeado para `xsd:anyType`. - - -Se um tipo não é um dos descritos acima, ele será considerado como um -tipo composto que consiste de propriedades. Um tipo composto é representado -em termos de uma classe e suas propriedades como variáveis públicas da classe, -marcadas com a tag `@soap` em seu comentário de documentação. - -Podemos utilizar também o tipo array adicionando `[]` no final do tipo primitivo -ou composto. Isto poderia especificar um array de um tipo específico. - -Abaixo um exemplo que define a Web API `getPosts` que retorna um array de -objetos `Post`. - -~~~ -[php] -class PostController extends CController -{ - /** - * @return Post[] uma lista de Post - * @soap - */ - public function getPosts() - { - return Post::model()->findAll(); - } -} - -class Post extends CActiveRecord -{ - /** - * @var integer ID do post - * @soap - */ - public $id; - /** - * @var string título do post - * @soap - */ - public $title; - - public static function model($className=__CLASS__) - { - return parent::model($className); - } -} -~~~ - -Mapeamento de Classes -------------- - -Para receber parâmetros de tipo composto de um cliente, um -aplicativo precisa declarar o mapeamento de tipos WSDL para as -classes PHP correspondentes. Isto é realizado configurando a propriedade -[classMap|CWebServiceAction::classMap] de [CWebServiceAction]. - -~~~ -[php] -class PostController extends CController -{ - public function actions() - { - return array( - 'service'=>array( - 'class'=>'CWebServiceAction', - 'classMap'=>array( - 'Post'=>'Post', // ou simplesmente 'Post' - ), - ), - ); - } - ...... -} -~~~ - -Interceptando Chamadas a Método Remoto -------------------------------------- - -Com a implementação da interface [IWebServiceProvider], um prestador de serviço -pode interceptar chamadas a método remoto. Em [IWebServiceProvider::beforeWebMethod], -o prestador de serviços pode recuperar o estado atual da instância de [CWebService] -e obter o nome do método que está sendo chamado atualmente através de -[CWebService::methodName]. Ele pode retornar false se o método remoto não pode ser -chamado por algum motivo (por exemplo, acesso não autorizado). - -
$Id: topics.webservice.txt 1808 2010-02-17 21:49:42Z qiang.xue $
+Web Service +=========== + +[Web service](http://pt.wikipedia.org/wiki/Web_service) é um sistema +de software projetado para suportar interações máquina-máquina +interoperáveis através de uma rede. No contexto de aplicações Web +geralmente refere-se a um conjunto de APIs que podem ser acessadas +através da Internet e executadas em um sistema remoto que hospeda +o serviço solicitado. Por exemplo, um cliente baseado em +[Flex](http://www.adobe.com/products/flex/), poderá chamar uma função +implementada no lado do servidor rodando uma aplicação Web baseada em +PHP. Web Service se baseia em [SOAP](http://pt.wikipedia.org/wiki/SOAP) +como a camada principal da pilha de protocolo de comunicação. + +O Yii fornece as classes [CWebService] e [CWebServiceAction] para simplificar o trabalho +de implementação de Web Service em uma aplicação Web. As APIs são agrupadas +dentro de classes chamadas de *prestadores de serviço*. O Yii irá gerar para +cada classe uma especificação [WSDL](http://www.w3.org/TR/wsdl) que descreve +quais APIs estão disponíveis e como elas devem ser chamadas pelo cliente. +Quando uma API é chamada por um cliente, o Yii irá instanciar o prestador de +serviço correspondente e chamar a API requisitada para executar a requisição. + +> Note|Nota: A classe [CWebService] é depende da [Extensão PHP +SOAP](http://www.php.net/manual/en/ref.soap.php). Tenha certeza que +você possui ela habilitada antes de testar os exemplos disponíveis +nesta seção. + +Definindo um Prestador de Serviço +------------------------- + +Como mencionado acima, o prestador de serviços é uma classe que define os +métodos que podem ser chamados remotamente. O Yii se baseia em [comentários +de documentação](http://java.sun.com/j2se/javadoc/writingdoccomments/) e +[reflexão de classes](http://php.net/manual/en/book.reflection.php) para +identificar quais métodos podem ser chamados remotamente e quais são os seus +parâmetros e valores retornados. + +Vamos iniciar com um simples serviço de cotação de ações. Este serviço +permite uma requisição de um cliente para cotar uma ação específica. +Definimos o prestador de serviços como a seguir. Note que definimos a classe +prestadora de serviço `StockController` como extensão de [CController]. Isto +não é obrigatório. Explicaremos mais adiante por que fazemos dessa forma. + +~~~ +[php] +class StockController extends CController +{ + /** + * @param string símbolo da ação + * @return float preço da ação + * @soap + */ + public function getPrice($symbol) + { + $prices=array('IBM'=>100, 'GOOGLE'=>350); + return isset($prices[$symbol])?$prices[$symbol]:0; + //...retorna o preço da ação para o $symbol + } +} +~~~ + +Acima declaramos o método `getPrice` para ser a API do Web Service, marcando-o +com a tag `@soap` no comentário de documentação. Contamos com a documentação de +comentário para especificar o tipo de dados dos parâmetros de entrada e dos valores +de retorno. APIs adicionais podem ser declaradas de forma semelhante. + + +Declarando Ações do Web Service +---------------------------- + +Uma vez definido o prestador de serviço, precisamos fazer com que ele esteja +disponível para os clientes. Particularmente, precisamos criar uma ação de controle +para expor este serviço. Isto pode ser feito facilmente declarando uma ação +[CWebServiceAction] na classe de controle. Para exemplificar, colocamos ela na classe +`StockController`. + +~~~ +[php] +class StockController extends CController +{ + public function actions() + { + return array( + 'quote'=>array( + 'class'=>'CWebServiceAction', + ), + ); + } + + /** + * @param string símbolo da ação + * @return float preço da ação + * @soap + */ + public function getPrice($symbol) + { + //...retorna o preço da ação para o $symbol + } +} +~~~ + +Isto é tudo o que precisamos para criar um Web Service! +Se tentar acessar a ação através da URL `http://hostname/path/to/index.php?r=stock/quote`, +vamos ver um monte de conteúdo XML que é o WSDL para o Web Service que definimos . + +> Tip|Dica: Por padrão, [CWebServiceAction] assume que o controller corrente é +o prestador de serviço. É por isso que definimos o método `getPrice` dentro +da classe `StockController`. + +Utilizando um Web Service +--------------------- + +Para completar o exemplo, vamos criar um cliente que utiliza o Web Service +que acabamos de criar. O cliente de exemplo é escrito em PHP, porém, ele +poderia ser em outra linguagem como `Java`, `C#`, `Flex`, etc. + +~~~ +[php] +$client=new SoapClient('http://hostname/path/to/index.php?r=stock/quote'); +echo $client->getPrice('GOOGLE'); +~~~ + +Executando o script acima em modo Web ou console e veremos que `350` é o preço +para `GOOGLE`. + +Tipos de dados +---------- + +Quando declaramos métodos e propriedades de classes que são acessadas remotamente, +precisamos especificar o tipo de dados dos parâmetros de entrada e saída. A seguir, +os tipos de dados mais primitivos que podemos utilizar: + + - str/string: mapeado para `xsd:string`; + - int/integer: mapeado para `xsd:int`; + - float/double: mapeado para `xsd:float`; + - bool/boolean: mapeado para `xsd:boolean`; + - date: mapeado para `xsd:date`; + - time: mapeado para `xsd:time`; + - datetime: mapeado para `xsd:dateTime`; + - array: mapeado para `xsd:string`; + - object: mapeado para `xsd:struct`; + - mixed: mapeado para `xsd:anyType`. + + +Se um tipo não é um dos descritos acima, ele será considerado como um +tipo composto que consiste de propriedades. Um tipo composto é representado +em termos de uma classe e suas propriedades como variáveis públicas da classe, +marcadas com a tag `@soap` em seu comentário de documentação. + +Podemos utilizar também o tipo array adicionando `[]` no final do tipo primitivo +ou composto. Isto poderia especificar um array de um tipo específico. + +Abaixo um exemplo que define a Web API `getPosts` que retorna um array de +objetos `Post`. + +~~~ +[php] +class PostController extends CController +{ + /** + * @return Post[] uma lista de Post + * @soap + */ + public function getPosts() + { + return Post::model()->findAll(); + } +} + +class Post extends CActiveRecord +{ + /** + * @var integer ID do post + * @soap + */ + public $id; + /** + * @var string título do post + * @soap + */ + public $title; + + public static function model($className=__CLASS__) + { + return parent::model($className); + } +} +~~~ + +Mapeamento de Classes +------------- + +Para receber parâmetros de tipo composto de um cliente, um +aplicativo precisa declarar o mapeamento de tipos WSDL para as +classes PHP correspondentes. Isto é realizado configurando a propriedade +[classMap|CWebServiceAction::classMap] de [CWebServiceAction]. + +~~~ +[php] +class PostController extends CController +{ + public function actions() + { + return array( + 'service'=>array( + 'class'=>'CWebServiceAction', + 'classMap'=>array( + 'Post'=>'Post', // ou simplesmente 'Post' + ), + ), + ); + } + ...... +} +~~~ + +Interceptando Chamadas a Método Remoto +------------------------------------- + +Com a implementação da interface [IWebServiceProvider], um prestador de serviço +pode interceptar chamadas a método remoto. Em [IWebServiceProvider::beforeWebMethod], +o prestador de serviços pode recuperar o estado atual da instância de [CWebService] +e obter o nome do método que está sendo chamado atualmente através de +[CWebService::methodName]. Ele pode retornar false se o método remoto não pode ser +chamado por algum motivo (por exemplo, acesso não autorizado). + +
$Id: topics.webservice.txt 1808 2010-02-17 21:49:42Z qiang.xue $
diff --git a/docs/guide/quickstart.apache-nginx-config.txt b/docs/guide/quickstart.apache-nginx-config.txt index 11bae2476..4b6603573 100644 --- a/docs/guide/quickstart.apache-nginx-config.txt +++ b/docs/guide/quickstart.apache-nginx-config.txt @@ -1,82 +1,82 @@ -Apache and Nginx configurations -=================================== - -Apache ------- - -Yii is ready to work with a default Apache web server configuration. The `.htaccess` files in Yii framework and application folders restrict access to the restricted resources. To hide the bootstrap file (usually `index.php`) in your URLs you can add `mod_rewrite` instructions to the `.htaccess` file in your document root or to the virtual host configuration: - -~~~ -RewriteEngine on - -# prevent httpd from serving dotfiles (.htaccess, .svn, .git, etc.) -RedirectMatch 403 /\..*$ -# if a directory or a file exists, use it directly -RewriteCond %{REQUEST_FILENAME} !-f -RewriteCond %{REQUEST_FILENAME} !-d -# otherwise forward it to index.php -RewriteRule . index.php -~~~ - - -Nginx ------ - -You can use Yii with [Nginx](http://wiki.nginx.org/) and PHP with [FPM SAPI](http://php.net/install.fpm). -Here is a sample host configuration. It defines the bootstrap file and makes yii catch all requests to unexisting files, which allows us to have nice-looking URLs. - -~~~ -server { - set $host_path "/www/mysite"; - access_log /www/mysite/log/access.log main; - - server_name mysite; - root $host_path/htdocs; - set $yii_bootstrap "index.php"; - - charset utf-8; - - location / { - index index.html $yii_bootstrap; - try_files $uri $uri/ /$yii_bootstrap?$args; - } - - location ~ ^/(protected|framework|themes/\w+/views) { - deny all; - } - - #avoid processing of calls to unexisting static files by yii - location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { - try_files $uri =404; - } - - # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 - # - location ~ \.php { - fastcgi_split_path_info ^(.+\.php)(.*)$; - - #let yii catch the calls to unexising PHP files - set $fsn /$yii_bootstrap; - if (-f $document_root$fastcgi_script_name){ - set $fsn $fastcgi_script_name; - } - - fastcgi_pass 127.0.0.1:9000; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fsn; - - #PATH_INFO and PATH_TRANSLATED can be omitted, but RFC 3875 specifies them for CGI - fastcgi_param PATH_INFO $fastcgi_path_info; - fastcgi_param PATH_TRANSLATED $document_root$fsn; - } - - # prevent nginx from serving dotfiles (.htaccess, .svn, .git, etc.) - location ~ /\. { - deny all; - access_log off; - log_not_found off; - } -} -~~~ -Using this configuration you can set `cgi.fix_pathinfo=0` in php.ini to avoid many unnecessary system stat() calls. - +Apache and Nginx configurations +=================================== + +Apache +------ + +Yii is ready to work with a default Apache web server configuration. The `.htaccess` files in Yii framework and application folders restrict access to the restricted resources. To hide the bootstrap file (usually `index.php`) in your URLs you can add `mod_rewrite` instructions to the `.htaccess` file in your document root or to the virtual host configuration: + +~~~ +RewriteEngine on + +# prevent httpd from serving dotfiles (.htaccess, .svn, .git, etc.) +RedirectMatch 403 /\..*$ +# if a directory or a file exists, use it directly +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +# otherwise forward it to index.php +RewriteRule . index.php +~~~ + + +Nginx +----- + +You can use Yii with [Nginx](http://wiki.nginx.org/) and PHP with [FPM SAPI](http://php.net/install.fpm). +Here is a sample host configuration. It defines the bootstrap file and makes yii catch all requests to unexisting files, which allows us to have nice-looking URLs. + +~~~ +server { + set $host_path "/www/mysite"; + access_log /www/mysite/log/access.log main; + + server_name mysite; + root $host_path/htdocs; + set $yii_bootstrap "index.php"; + + charset utf-8; + + location / { + index index.html $yii_bootstrap; + try_files $uri $uri/ /$yii_bootstrap?$args; + } + + location ~ ^/(protected|framework|themes/\w+/views) { + deny all; + } + + #avoid processing of calls to unexisting static files by yii + location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { + try_files $uri =404; + } + + # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 + # + location ~ \.php { + fastcgi_split_path_info ^(.+\.php)(.*)$; + + #let yii catch the calls to unexising PHP files + set $fsn /$yii_bootstrap; + if (-f $document_root$fastcgi_script_name){ + set $fsn $fastcgi_script_name; + } + + fastcgi_pass 127.0.0.1:9000; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fsn; + + #PATH_INFO and PATH_TRANSLATED can be omitted, but RFC 3875 specifies them for CGI + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param PATH_TRANSLATED $document_root$fsn; + } + + # prevent nginx from serving dotfiles (.htaccess, .svn, .git, etc.) + location ~ /\. { + deny all; + access_log off; + log_not_found off; + } +} +~~~ +Using this configuration you can set `cgi.fix_pathinfo=0` in php.ini to avoid many unnecessary system stat() calls. + diff --git a/docs/guide/ru/basics.application.txt b/docs/guide/ru/basics.application.txt index c4c7c0a1d..c376d369f 100644 --- a/docs/guide/ru/basics.application.txt +++ b/docs/guide/ru/basics.application.txt @@ -1,209 +1,209 @@ -Приложение -========== - -Объект приложения (application) инкапсулирует контекст выполнения запроса. -Основная задача приложения — собрать информацию о запросе и передать -её соответствующему контроллеру для дальнейшей обработки. Также приложение -является централизованным хранилищем конфигурации приложения. Именно поэтому объект -приложения также называют `фронт-контроллером`. - -Объект приложения создаётся [входным скриптом](/doc/guide/basics.entry) как `одиночка (singleton)`. -Экземпляр приложения доступен из любой его точки посредством [Yii::app()|YiiBase::app]. - - -Конфигурация приложения ------------------------ -По умолчанию объект приложения — это экземпляр класса [CWebApplication], который -может быть настроен с использованием конфигурационного файла (или массива). -Необходимые значения свойств устанавливаются в момент создания экземпляра приложения. -Альтернативный путь настройки приложения — расширение класса [CWebApplication]. - -Конфигурация — это массив пар ключ-значение, где каждый ключ представляет собой -имя свойства экземпляра приложения, а значение — начальное значение соответствующего свойства. -Например, следующая конфигурация устанавливает значения свойств приложения -[name|CApplication::name] и [defaultController|CWebApplication::defaultController]: - -~~~ -[php] -array( - 'name'=>'Yii Framework', - 'defaultController'=>'site', -) -~~~ - -Стоит отметить, что приложение, как и большинство классов Yii, [является компонентом](/doc/guide/basics.component). -Это означает что: - -- Вы не можете присваивать значения не объявленным в классе свойствам. -- Приложение поддерживает свойства, объявленные через геттеры и сеттеры, то есть можно сконфигурировать свойство, заданное -[setImport|CModule::setImport] следующим образом: - -~~~ -[php] -array( - 'import'=>array( - 'application.components.*', - ), -) -~~~ - -Обычно конфигурация хранится в отдельном PHP-скрипте -(например, `protected/config/main.php`). Скрипт возвращает конфигурационный массив: - -~~~ -[php] -return array(…); -~~~ - -Чтобы воспользоваться конфигурацией, необходимо передать имя конфигурационного -файла в качестве аргумента конструктору приложения или методу -[Yii::createWebApplication()], как показано ниже. -Обычно это делается во [входном скрипте](/doc/guide/basics.entry): - -~~~ -[php] -$app=Yii::createWebApplication($configFile); -~~~ - -> Tip|Подсказка: Если конфигурация очень громоздкая, можно разделить ее на несколько файлов, -каждый из которых возвращает часть конфигурационного массива. Затем в основном конфигурационном -файле необходимо подключить эти файлы, используя `include()`, и соединить массивы-части в единый -конфигурационный массив. - - -Базовая директория приложения ------------------------------ -Базовой директорией приложения называется корневая директория, содержащая все основные, с точки -зрения безопасности, PHP-скрипты и данные. По умолчанию это поддиректория `protected`, находящаяся -в директории, содержащей входной скрипт. Изменить её местоположение можно, установив свойство -[basePath|CWebApplication::basePath] в [конфигурации приложения](/doc/guide/basics.application#application-configuration). - -Содержимое базовой директории должно быть закрыто от доступа из веб. -При использовании веб-сервера [Apache HTTP server](http://httpd.apache.org/) -это можно сделать путем добавления в базовую директорию файла `.htaccess` следующего содержания: - -~~~ -deny from all -~~~ - - -Компоненты приложения ---------------------- - -Функциональность объекта приложения может быть легко модифицирована и расширена благодаря компонентной архитектуре. -Приложение управляет набором компонентов, каждый из которых реализует набор определённых возможностей. -Например, приложение производит предварительную обработку запроса пользователя, используя компоненты [CUrlManager] и [CHttpRequest]. - -Изменяя значение свойства [components|CApplication::components], можно настроить классы и [значения свойств](/doc/guide/basics.component) -любого компонента, используемого приложением. Например, можно сконфигурировать компонент [CMemCache] так, чтобы -он использовал несколько memcache-серверов для кэширования: - -~~~ -[php] -array( - … - 'components'=>array( - … - 'cache'=>array( - 'class'=>'CMemCache', - 'servers'=>array( - array('host'=>'server1', 'port'=>11211, 'weight'=>60), - array('host'=>'server2', 'port'=>11211, 'weight'=>40), - ), - ), - ), -) -~~~ - -В данном примере мы добавили элемент `cache` к массиву `components`. -Элемент `cache` указывает, что классом компонента является [CMemCache], а также устанавливает его свойство -[servers|CMemcache::servers]. - -Для доступа к компоненту приложения используйте `Yii::app()->ComponentID`, где -`ComponentID` — это идентификатор компонента (например, `Yii::app()->cache`). - -Компонент может быть деактивирован путем установки параметра `enabled` в его конфигурации равным false. -При обращении к деактивированному компоненту будет возвращен null. - -> Tip|Подсказка: По умолчанию компоненты приложения создаются по требованию. -Это означает, что экземпляр компонента может быть не создан вообще в случае, -если это не требуется при обработке пользовательского запроса. В результате общая -производительность приложения может не пострадать, даже если в конфигурации -указано множество компонентов. - - -При необходимости обязательного создания экземпляров компонентов (например, [CLogRouter]) -вне зависимости от того, используются они или нет, укажите их идентификаторы в -значении конфигурационного свойства [preload|CApplication::preload]. - -Ключевые компоненты приложения ------------------------------- -Yii предопределяет набор компонентов ядра, которые предоставляют возможности, -необходимые для большинства веб-приложений. Например, компонент [request|CWebApplication::request] -используется для сбора информации о запросе пользователя и предоставляет -различную информацию, такую как URL и cookies. Задавая свойства компонентов, -можно изменять стандартное поведение Yii практически как угодно. - -Далее перечислены ключевые компоненты, предопределенные классом [CWebApplication]: - - - [assetManager|CWebApplication::assetManager]: [CAssetManager] — управляет публикацией файлов ресурсов (asset files); - - - [authManager|CWebApplication::authManager]: [CAuthManager] — контролирует доступ на основе ролей (RBAC); - - - [cache|CApplication::cache]: [CCache] — предоставляет возможности кэширования данных; учтите, что вы -должны указать используемый класс (например, [CMemCache], [CDbCache]), иначе при обращении к компоненту будет возвращен null; - - - [clientScript|CWebApplication::clientScript]: [CClientScript] — управляет клиентскими скриптами (JavaScript и CSS); - - - [coreMessages|CApplication::coreMessages]: [CPhpMessageSource] — предоставляет переводы системных сообщений Yii-фреймворка; - - - [db|CApplication::db]: [CDbConnection] — обслуживает соединение с базой данных; обратите внимание, что -для использования компонента необходимо установить свойство [connectionString|CDbConnection::connectionString]; - - - [errorHandler|CApplication::errorHandler]: [CErrorHandler] — обрабатывает не пойманные ошибки и исключения PHP; - - - [format|CApplication::format]: [CFormatter] — форматирует данные для их последующего отображения. - - - [messages|CApplication::messages]: [CPhpMessageSource] — предоставляет переводы сообщений, используемых в Yii-приложении; - - - [request|CWebApplication::request]: [CHttpRequest] — содержит информацию о пользовательском запросе; - - - [securityManager|CApplication::securityManager]: [CSecurityManager] — предоставляет функции, - связанные с безопасностью (например, хеширование, шифрование); - - - [session|CWebApplication::session]: [CHttpSession] — обеспечивает функциональность, связанную с сессиями; - - - [statePersister|CApplication::statePersister]: [CStatePersister] — предоставляет метод для сохранения -глобального состояния; - - - [urlManager|CWebApplication::urlManager]: [CUrlManager] — предоставляет функции парсинга и формирования URL; - - - [user|CWebApplication::user]: [CWebUser] — предоставляет идентификационную информацию текущего пользователя; - - - [themeManager|CWebApplication::themeManager]: [CThemeManager] — управляет темами оформления. - - -Жизненный цикл приложения -------------------------- -Жизненный цикл приложения при обработке пользовательского запроса выглядит следующим образом: - - 0. Предварительная инициализация приложения через [CApplication::preinit()]. - - 1. Инициализация обработчика ошибок. - - 2. Регистрация компонентов ядра. - - 3. Загрузка конфигурации приложения. - - 4. Инициализация приложения [CApplication::init()]: - - регистрация поведений приложения; - - загрузка статических компонентов приложения. - - 5. Вызов события [onBeginRequest|CApplication::onBeginRequest]. - - 6. Обработка запроса: - - сбор информации о запросе; - - создание контроллера; - - запуск контроллера. - +Приложение +========== + +Объект приложения (application) инкапсулирует контекст выполнения запроса. +Основная задача приложения — собрать информацию о запросе и передать +её соответствующему контроллеру для дальнейшей обработки. Также приложение +является централизованным хранилищем конфигурации приложения. Именно поэтому объект +приложения также называют `фронт-контроллером`. + +Объект приложения создаётся [входным скриптом](/doc/guide/basics.entry) как `одиночка (singleton)`. +Экземпляр приложения доступен из любой его точки посредством [Yii::app()|YiiBase::app]. + + +Конфигурация приложения +----------------------- +По умолчанию объект приложения — это экземпляр класса [CWebApplication], который +может быть настроен с использованием конфигурационного файла (или массива). +Необходимые значения свойств устанавливаются в момент создания экземпляра приложения. +Альтернативный путь настройки приложения — расширение класса [CWebApplication]. + +Конфигурация — это массив пар ключ-значение, где каждый ключ представляет собой +имя свойства экземпляра приложения, а значение — начальное значение соответствующего свойства. +Например, следующая конфигурация устанавливает значения свойств приложения +[name|CApplication::name] и [defaultController|CWebApplication::defaultController]: + +~~~ +[php] +array( + 'name'=>'Yii Framework', + 'defaultController'=>'site', +) +~~~ + +Стоит отметить, что приложение, как и большинство классов Yii, [является компонентом](/doc/guide/basics.component). +Это означает что: + +- Вы не можете присваивать значения не объявленным в классе свойствам. +- Приложение поддерживает свойства, объявленные через геттеры и сеттеры, то есть можно сконфигурировать свойство, заданное +[setImport|CModule::setImport] следующим образом: + +~~~ +[php] +array( + 'import'=>array( + 'application.components.*', + ), +) +~~~ + +Обычно конфигурация хранится в отдельном PHP-скрипте +(например, `protected/config/main.php`). Скрипт возвращает конфигурационный массив: + +~~~ +[php] +return array(…); +~~~ + +Чтобы воспользоваться конфигурацией, необходимо передать имя конфигурационного +файла в качестве аргумента конструктору приложения или методу +[Yii::createWebApplication()], как показано ниже. +Обычно это делается во [входном скрипте](/doc/guide/basics.entry): + +~~~ +[php] +$app=Yii::createWebApplication($configFile); +~~~ + +> Tip|Подсказка: Если конфигурация очень громоздкая, можно разделить ее на несколько файлов, +каждый из которых возвращает часть конфигурационного массива. Затем в основном конфигурационном +файле необходимо подключить эти файлы, используя `include()`, и соединить массивы-части в единый +конфигурационный массив. + + +Базовая директория приложения +----------------------------- +Базовой директорией приложения называется корневая директория, содержащая все основные, с точки +зрения безопасности, PHP-скрипты и данные. По умолчанию это поддиректория `protected`, находящаяся +в директории, содержащей входной скрипт. Изменить её местоположение можно, установив свойство +[basePath|CWebApplication::basePath] в [конфигурации приложения](/doc/guide/basics.application#application-configuration). + +Содержимое базовой директории должно быть закрыто от доступа из веб. +При использовании веб-сервера [Apache HTTP server](http://httpd.apache.org/) +это можно сделать путем добавления в базовую директорию файла `.htaccess` следующего содержания: + +~~~ +deny from all +~~~ + + +Компоненты приложения +--------------------- + +Функциональность объекта приложения может быть легко модифицирована и расширена благодаря компонентной архитектуре. +Приложение управляет набором компонентов, каждый из которых реализует набор определённых возможностей. +Например, приложение производит предварительную обработку запроса пользователя, используя компоненты [CUrlManager] и [CHttpRequest]. + +Изменяя значение свойства [components|CApplication::components], можно настроить классы и [значения свойств](/doc/guide/basics.component) +любого компонента, используемого приложением. Например, можно сконфигурировать компонент [CMemCache] так, чтобы +он использовал несколько memcache-серверов для кэширования: + +~~~ +[php] +array( + … + 'components'=>array( + … + 'cache'=>array( + 'class'=>'CMemCache', + 'servers'=>array( + array('host'=>'server1', 'port'=>11211, 'weight'=>60), + array('host'=>'server2', 'port'=>11211, 'weight'=>40), + ), + ), + ), +) +~~~ + +В данном примере мы добавили элемент `cache` к массиву `components`. +Элемент `cache` указывает, что классом компонента является [CMemCache], а также устанавливает его свойство +[servers|CMemcache::servers]. + +Для доступа к компоненту приложения используйте `Yii::app()->ComponentID`, где +`ComponentID` — это идентификатор компонента (например, `Yii::app()->cache`). + +Компонент может быть деактивирован путем установки параметра `enabled` в его конфигурации равным false. +При обращении к деактивированному компоненту будет возвращен null. + +> Tip|Подсказка: По умолчанию компоненты приложения создаются по требованию. +Это означает, что экземпляр компонента может быть не создан вообще в случае, +если это не требуется при обработке пользовательского запроса. В результате общая +производительность приложения может не пострадать, даже если в конфигурации +указано множество компонентов. + + +При необходимости обязательного создания экземпляров компонентов (например, [CLogRouter]) +вне зависимости от того, используются они или нет, укажите их идентификаторы в +значении конфигурационного свойства [preload|CApplication::preload]. + +Ключевые компоненты приложения +------------------------------ +Yii предопределяет набор компонентов ядра, которые предоставляют возможности, +необходимые для большинства веб-приложений. Например, компонент [request|CWebApplication::request] +используется для сбора информации о запросе пользователя и предоставляет +различную информацию, такую как URL и cookies. Задавая свойства компонентов, +можно изменять стандартное поведение Yii практически как угодно. + +Далее перечислены ключевые компоненты, предопределенные классом [CWebApplication]: + + - [assetManager|CWebApplication::assetManager]: [CAssetManager] — управляет публикацией файлов ресурсов (asset files); + + - [authManager|CWebApplication::authManager]: [CAuthManager] — контролирует доступ на основе ролей (RBAC); + + - [cache|CApplication::cache]: [CCache] — предоставляет возможности кэширования данных; учтите, что вы +должны указать используемый класс (например, [CMemCache], [CDbCache]), иначе при обращении к компоненту будет возвращен null; + + - [clientScript|CWebApplication::clientScript]: [CClientScript] — управляет клиентскими скриптами (JavaScript и CSS); + + - [coreMessages|CApplication::coreMessages]: [CPhpMessageSource] — предоставляет переводы системных сообщений Yii-фреймворка; + + - [db|CApplication::db]: [CDbConnection] — обслуживает соединение с базой данных; обратите внимание, что +для использования компонента необходимо установить свойство [connectionString|CDbConnection::connectionString]; + + - [errorHandler|CApplication::errorHandler]: [CErrorHandler] — обрабатывает не пойманные ошибки и исключения PHP; + + - [format|CApplication::format]: [CFormatter] — форматирует данные для их последующего отображения. + + - [messages|CApplication::messages]: [CPhpMessageSource] — предоставляет переводы сообщений, используемых в Yii-приложении; + + - [request|CWebApplication::request]: [CHttpRequest] — содержит информацию о пользовательском запросе; + + - [securityManager|CApplication::securityManager]: [CSecurityManager] — предоставляет функции, + связанные с безопасностью (например, хеширование, шифрование); + + - [session|CWebApplication::session]: [CHttpSession] — обеспечивает функциональность, связанную с сессиями; + + - [statePersister|CApplication::statePersister]: [CStatePersister] — предоставляет метод для сохранения +глобального состояния; + + - [urlManager|CWebApplication::urlManager]: [CUrlManager] — предоставляет функции парсинга и формирования URL; + + - [user|CWebApplication::user]: [CWebUser] — предоставляет идентификационную информацию текущего пользователя; + + - [themeManager|CWebApplication::themeManager]: [CThemeManager] — управляет темами оформления. + + +Жизненный цикл приложения +------------------------- +Жизненный цикл приложения при обработке пользовательского запроса выглядит следующим образом: + + 0. Предварительная инициализация приложения через [CApplication::preinit()]. + + 1. Инициализация обработчика ошибок. + + 2. Регистрация компонентов ядра. + + 3. Загрузка конфигурации приложения. + + 4. Инициализация приложения [CApplication::init()]: + - регистрация поведений приложения; + - загрузка статических компонентов приложения. + + 5. Вызов события [onBeginRequest|CApplication::onBeginRequest]. + + 6. Обработка запроса: + - сбор информации о запросе; + - создание контроллера; + - запуск контроллера. + 7. Вызов события [onEndRequest|CApplication::onEndRequest]. \ No newline at end of file diff --git a/docs/guide/ru/basics.component.txt b/docs/guide/ru/basics.component.txt index d016ae6a8..698ca7734 100644 --- a/docs/guide/ru/basics.component.txt +++ b/docs/guide/ru/basics.component.txt @@ -1,221 +1,221 @@ -Компонент -========= -Yii-приложения состоят из компонентов–объектов, созданных согласно спецификациям. -Компонент (component) — это экземпляр класса [CComponent] или производного от него. -Использование компонента, как правило, включает доступ к его свойствам, а также вызов и обработку его событий. -Базовый класс [CComponent] устанавливает правила, согласно которым определяются свойства и события. - -Объявление и использование свойства компонента ----------------------------------------------- -Свойство компонента схоже с открытой переменной-членом класса (public member variable). -Мы можем читать или устанавливать его значение. Например: - -~~~ -[php] -$width=$component->textWidth; // получаем значение свойства textWidth -$component->enableCaching=true; // устанавливаем значение свойства enableCaching -~~~ - -Существует два разных способа определения свойства компонента. Первым способом является обычное объявление -открытой переменной-члена класса компонента так, как показано ниже: - -~~~ -[php] -class Document extends CComponent -{ - public $textWidth; -} -~~~ - -Другим способом является использование геттеров и сеттеров. Это более гибкий подход потому как -помимо обычных свойств вы можете объявлять и свойства, доступные только для чтения или только -для записи. - -~~~ -[php] -class Document extends CComponent -{ - private $_textWidth; - protected $_completed=false; - - public function getTextWidth() - { - return $this->_textWidth; - } - - public function setTextWidth($value) - { - $this->_textWidth=$value; - } - - public function getTextHeight() - { - // вычисляет и возвращает высоту текста - } - - public function setCompleted($value) - { - $this->_completed=$value; - } -} -~~~ - -Компонент выше может быть использован следующим образом: - -~~~ -[php] -$document=new Document(); - -// мы можем как писать в, так и читать из textWidth -$document->textWidth=100; -echo $document->textWidth; - -// значение textHeight мы можем только получать -echo $document->textHeight; - -// значение completed мы можем только изменять -$document->completed=true; -~~~ - -При чтении свойства, которое не было объявлено публичным членом класса, Yii пытается -использовать методы-геттеры, т.е. для `textWidth` методом-геттером будет `getTextWidth`. Тоже самое -происходит и при изменении свойства, которое не было объявлено публичным членом класса. - -Если существует метод-геттер, но метода-сеттера при этом объявлено не было, то свойство компонента -можно использовать только для чтения, в противном случае будет вызвано исключение. Обратное верно и для -свойств, доступных только для изменения. - -Использование методов чтения и записи имеет дополнительное преимущество: при чтении или записи значения -свойства могут быть выполнены дополнительные действия (такие как проверка на корректность, -вызов события и др.). - ->Note|Примечание: Есть небольшая разница в определении свойства через методы и через простое -объявление переменной. В первом случае имя свойства не чувствительно к регистру, -во втором — чувствительно. - - -События компонента ------------------- -События компонента — это специальные свойства, в качестве значений которых выступают -методы (называемые обработчиками событий). Назначение метода событию приведет к тому, что метод будет вызван -автоматически при возникновении этого события. Поэтому поведение компонента может быть -изменено совершенно отлично от закладываемого при разработке. - -Событие компонента объявляется путём создания метода с именем, начинающимся на `on`. -Так же как и имена свойств, заданных через методы чтения и записи, имена событий -не чувствительны к регистру. Следующий код объявляет событие `onClicked`: - -~~~ -[php] -public function onClicked($event) -{ - $this->raiseEvent('onClicked', $event); -} -~~~ - -где `$event` — это экземпляр класса [CEvent] или производного от него, -представляющего параметр события. К событию можно подключить обработчик как показано ниже: - -~~~ -[php] -$component->onClicked=$callback; -~~~ - -где `$callback` — это корректный callback-вызов PHP (см. -PHP-функцию call_user_func). Это может быть либо глобальная функция, либо метод класса. -В последнем случае вызову должен передаваться массив: `array($object,'methodName')`. - -Обработчик события должен быть определён следующим образом: - -~~~ -[php] -function methodName($event) -{ - … -} -~~~ - -где `$event` — это параметр, описывающий событие (передаётся методом `raiseEvent()`). -Параметр `$event` — это экземпляр класса [CEvent] или его производного. -Как минимум, он содержит информацию о том, кто вызвал событие. - -Обработчик события может быть анонимной функцией, -требующей наличия версии PHP 5.3+. Например, - -~~~ -[php] -$component->onClicked=function($event) { - … -} -~~~ - -Если теперь использовать метод `onClicked()`, то в нём будет вызвано событие `onClicked`. -Назначенный ему обработчик будет запущен автоматически. - -Событию могут быть назначены несколько обработчиков. -При возникновении события обработчики будут вызваны в порядке их назначения. -Если в обработчике необходимо предотвратить вызов последующих обработчиков, -необходимо установить [$event->handled|CEvent::handled] в `true`. - - -Поведения компонента ---------------------- - -Для компонентов реализован шаблон проектирования [mixin](http://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%81%D1%8C_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)), -что позволяет присоединить к ним одно или несколько поведений. *Поведение* — -объект, чьи методы «наследуются» компонентом, к которому он присоединён. Под -«наследованием» здесь понимается наращивание функционала, а не наследование -в классическом смысле. К компоненту можно прикрепить несколько поведений и, -таким образом, получить аналог множественного наследования. - -Поведения классов должны реализовывать интерфейс [IBehavior]. Большинство поведений могут быть созданы путём расширения -базового класса [CBehavior]. В случае если поведение необходимо прикрепить к [модели](/doc/guide/basics.model), его можно -создать на основе класса [CModelBehavior] или класса [CActiveRecordBehavior], которые реализуют дополнительные, -специфические для моделей, возможности. - -Чтобы использовать поведение, его необходимо прикрепить к компоненту путём вызова метода поведения -[attach()|IBehavior::attach]. После этого мы можем вызывать методы поведения через компонент: - -~~~ -[php] -// $name уникально идентифицирует поведения в компоненте -$component->attachBehavior($name,$behavior); -// test() является методом $behavior -$component->test(); -~~~ - -К прикреплённому поведению можно обращаться как к обычному свойству компонента. -Например, если поведение с именем `tree` прикреплено к компоненту, мы можем получить -ссылку на объект поведения следующим образом: - -~~~ -[php] -$behavior=$component->tree; -// эквивалентно выражению: -// $behavior=$component->asa('tree'); -~~~ - -Поведение можно временно деактивировать, чтобы его методы и свойства были недоступны через компонент. -Например: - -~~~ -[php] -$component->disableBehavior($name); -// выражение ниже приведет к вызову исключения -$component->test(); -$component->enableBehavior($name); -// здесь все будет работать нормально -$component->test(); -~~~ - -В случае когда два поведения, прикреплённые к одному компоненту, имеют методы с одинаковыми именами, -преимущество будет иметь метод поведения, прикреплённого раньше. - -Использование поведений совместно с [событиями](/doc/guide/basics.component#component-event) даёт дополнительные возможности. -Поведение, прикреплённое к компоненту, может назначать некоторые свои методы в качестве обработчиков событий компонента. -В этом случае поведение получает возможность следить за ходом работы компонента и даже изменять его. - -Свойства поведения также доступны из компонента, к которому оно присоединено. -Свойства включают в себя как открытые поля класса поведения, так и его методы чтения/записи (getters/setters). -Например, если поведение имеет свойство с именем `xyz` и привязано к компоненту +Компонент +========= +Yii-приложения состоят из компонентов–объектов, созданных согласно спецификациям. +Компонент (component) — это экземпляр класса [CComponent] или производного от него. +Использование компонента, как правило, включает доступ к его свойствам, а также вызов и обработку его событий. +Базовый класс [CComponent] устанавливает правила, согласно которым определяются свойства и события. + +Объявление и использование свойства компонента +---------------------------------------------- +Свойство компонента схоже с открытой переменной-членом класса (public member variable). +Мы можем читать или устанавливать его значение. Например: + +~~~ +[php] +$width=$component->textWidth; // получаем значение свойства textWidth +$component->enableCaching=true; // устанавливаем значение свойства enableCaching +~~~ + +Существует два разных способа определения свойства компонента. Первым способом является обычное объявление +открытой переменной-члена класса компонента так, как показано ниже: + +~~~ +[php] +class Document extends CComponent +{ + public $textWidth; +} +~~~ + +Другим способом является использование геттеров и сеттеров. Это более гибкий подход потому как +помимо обычных свойств вы можете объявлять и свойства, доступные только для чтения или только +для записи. + +~~~ +[php] +class Document extends CComponent +{ + private $_textWidth; + protected $_completed=false; + + public function getTextWidth() + { + return $this->_textWidth; + } + + public function setTextWidth($value) + { + $this->_textWidth=$value; + } + + public function getTextHeight() + { + // вычисляет и возвращает высоту текста + } + + public function setCompleted($value) + { + $this->_completed=$value; + } +} +~~~ + +Компонент выше может быть использован следующим образом: + +~~~ +[php] +$document=new Document(); + +// мы можем как писать в, так и читать из textWidth +$document->textWidth=100; +echo $document->textWidth; + +// значение textHeight мы можем только получать +echo $document->textHeight; + +// значение completed мы можем только изменять +$document->completed=true; +~~~ + +При чтении свойства, которое не было объявлено публичным членом класса, Yii пытается +использовать методы-геттеры, т.е. для `textWidth` методом-геттером будет `getTextWidth`. Тоже самое +происходит и при изменении свойства, которое не было объявлено публичным членом класса. + +Если существует метод-геттер, но метода-сеттера при этом объявлено не было, то свойство компонента +можно использовать только для чтения, в противном случае будет вызвано исключение. Обратное верно и для +свойств, доступных только для изменения. + +Использование методов чтения и записи имеет дополнительное преимущество: при чтении или записи значения +свойства могут быть выполнены дополнительные действия (такие как проверка на корректность, +вызов события и др.). + +>Note|Примечание: Есть небольшая разница в определении свойства через методы и через простое +объявление переменной. В первом случае имя свойства не чувствительно к регистру, +во втором — чувствительно. + + +События компонента +------------------ +События компонента — это специальные свойства, в качестве значений которых выступают +методы (называемые обработчиками событий). Назначение метода событию приведет к тому, что метод будет вызван +автоматически при возникновении этого события. Поэтому поведение компонента может быть +изменено совершенно отлично от закладываемого при разработке. + +Событие компонента объявляется путём создания метода с именем, начинающимся на `on`. +Так же как и имена свойств, заданных через методы чтения и записи, имена событий +не чувствительны к регистру. Следующий код объявляет событие `onClicked`: + +~~~ +[php] +public function onClicked($event) +{ + $this->raiseEvent('onClicked', $event); +} +~~~ + +где `$event` — это экземпляр класса [CEvent] или производного от него, +представляющего параметр события. К событию можно подключить обработчик как показано ниже: + +~~~ +[php] +$component->onClicked=$callback; +~~~ + +где `$callback` — это корректный callback-вызов PHP (см. +PHP-функцию call_user_func). Это может быть либо глобальная функция, либо метод класса. +В последнем случае вызову должен передаваться массив: `array($object,'methodName')`. + +Обработчик события должен быть определён следующим образом: + +~~~ +[php] +function methodName($event) +{ + … +} +~~~ + +где `$event` — это параметр, описывающий событие (передаётся методом `raiseEvent()`). +Параметр `$event` — это экземпляр класса [CEvent] или его производного. +Как минимум, он содержит информацию о том, кто вызвал событие. + +Обработчик события может быть анонимной функцией, +требующей наличия версии PHP 5.3+. Например, + +~~~ +[php] +$component->onClicked=function($event) { + … +} +~~~ + +Если теперь использовать метод `onClicked()`, то в нём будет вызвано событие `onClicked`. +Назначенный ему обработчик будет запущен автоматически. + +Событию могут быть назначены несколько обработчиков. +При возникновении события обработчики будут вызваны в порядке их назначения. +Если в обработчике необходимо предотвратить вызов последующих обработчиков, +необходимо установить [$event->handled|CEvent::handled] в `true`. + + +Поведения компонента +--------------------- + +Для компонентов реализован шаблон проектирования [mixin](http://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%81%D1%8C_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)), +что позволяет присоединить к ним одно или несколько поведений. *Поведение* — +объект, чьи методы «наследуются» компонентом, к которому он присоединён. Под +«наследованием» здесь понимается наращивание функционала, а не наследование +в классическом смысле. К компоненту можно прикрепить несколько поведений и, +таким образом, получить аналог множественного наследования. + +Поведения классов должны реализовывать интерфейс [IBehavior]. Большинство поведений могут быть созданы путём расширения +базового класса [CBehavior]. В случае если поведение необходимо прикрепить к [модели](/doc/guide/basics.model), его можно +создать на основе класса [CModelBehavior] или класса [CActiveRecordBehavior], которые реализуют дополнительные, +специфические для моделей, возможности. + +Чтобы использовать поведение, его необходимо прикрепить к компоненту путём вызова метода поведения +[attach()|IBehavior::attach]. После этого мы можем вызывать методы поведения через компонент: + +~~~ +[php] +// $name уникально идентифицирует поведения в компоненте +$component->attachBehavior($name,$behavior); +// test() является методом $behavior +$component->test(); +~~~ + +К прикреплённому поведению можно обращаться как к обычному свойству компонента. +Например, если поведение с именем `tree` прикреплено к компоненту, мы можем получить +ссылку на объект поведения следующим образом: + +~~~ +[php] +$behavior=$component->tree; +// эквивалентно выражению: +// $behavior=$component->asa('tree'); +~~~ + +Поведение можно временно деактивировать, чтобы его методы и свойства были недоступны через компонент. +Например: + +~~~ +[php] +$component->disableBehavior($name); +// выражение ниже приведет к вызову исключения +$component->test(); +$component->enableBehavior($name); +// здесь все будет работать нормально +$component->test(); +~~~ + +В случае когда два поведения, прикреплённые к одному компоненту, имеют методы с одинаковыми именами, +преимущество будет иметь метод поведения, прикреплённого раньше. + +Использование поведений совместно с [событиями](/doc/guide/basics.component#component-event) даёт дополнительные возможности. +Поведение, прикреплённое к компоненту, может назначать некоторые свои методы в качестве обработчиков событий компонента. +В этом случае поведение получает возможность следить за ходом работы компонента и даже изменять его. + +Свойства поведения также доступны из компонента, к которому оно присоединено. +Свойства включают в себя как открытые поля класса поведения, так и его методы чтения/записи (getters/setters). +Например, если поведение имеет свойство с именем `xyz` и привязано к компоненту `$a`, то мы можем использовать выражение `$a->xyz` для доступа к этому свойству. \ No newline at end of file diff --git a/docs/guide/ru/basics.controller.txt b/docs/guide/ru/basics.controller.txt index 5e4c6032b..6b9cfc050 100644 --- a/docs/guide/ru/basics.controller.txt +++ b/docs/guide/ru/basics.controller.txt @@ -1,325 +1,325 @@ -Контроллер -========== -`Контроллер (controller)` — это экземпляр класса [CController] или унаследованного -от него класса. Он создается объектом приложения в случае, когда пользователь его -запрашивает. При запуске контроллер выполняет соответствующее действие, что обычно -подразумевает создание соответствующих моделей и отображение необходимых представлений. -В самом простом случае `действие` — это метод класса контроллера, название -которого начинается на `action`. - -У контроллера есть действие по умолчанию, которое выполняется в случае, когда -пользователь не указывает действие при запросе. По умолчанию это действие -называется `index`. Изменить его можно путём установки значения [CController::defaultAction]. - -Следующий код определяет контроллер `site` с действиями `index` (действие по -умолчанию) и `contact`: - -~~~ -[php] -class SiteController extends CController -{ - public function actionIndex() - { - // ... - } - - public function actionContact() - { - // ... - } -} -~~~ - - -Маршрут -------- -Контроллеры и действия опознаются по их идентификаторам. -Идентификатор контроллера — это запись формата `path/to/xyz`, соответствующая -файлу класса контроллера `protected/controllers/path/to/XyzController.php`, где `xyz` -следует заменить реальным названием класса (например, `post` соответствует -`protected/controllers/PostController.php`). Идентификатор действия — это название -метода без префикса `action`. Например, если класс контроллера содержит метод -`actionEdit`, то идентификатор соответствующего действия — `edit`. - -Пользователь обращается к контроллеру и действию посредством маршрута (route). -Маршрут формируется путём объединения идентификаторов контроллера и действия, -отделенных косой чертой. Например, маршрут `post/edit` указывает на действие -`edit` контроллера `PostController`, и по умолчанию URL `http://hostname/index.php?r=post/edit` -приведёт к вызову именно этих контроллера и действия. - -> Note|Примечание: По умолчанию маршруты чувствительны к регистру. ->Это возможно изменить путём установки свойства ->[CUrlManager::caseSensitive] равным false в конфигурации приложения. ->В режиме, не чувствительном к регистру, убедитесь, что названия директорий, ->содержащих файлы классов контроллеров, указаны в нижнем регистре, а также, ->что [controller map|CWebApplication::controllerMap] и [action map|CController::actions] ->используют ключи в нижнем регистре. - -Приложение может содержать [модули](/doc/guide/basics.module). Маршрут к действию -контроллера внутри модуля задаётся в формате `moduleID/controllerID/actionID`. -Более подробно это описано в [разделе о модулях](/doc/guide/basics.module). - -Создание экземпляра контроллера -------------------------------- -Экземпляр контроллера создаётся, когда [CWebApplication] обрабатывает входящий запрос. -Получив идентификатор контроллера, приложение использует следующие правила для -определения класса контроллера и его местоположения: - -- если установлено свойство [CWebApplication::catchAllRequest], контроллер будет создан -на основе этого свойства, а контроллер, запрошенный пользователем, будет проигнорирован. -Как правило, это используется для установки приложения в режим технического обслуживания -и отображения статической страницы с соответствующим сообщением; - -- если идентификатор контроллера обнаружен в [CWebApplication::controllerMap], то для -создания экземпляра контроллера будет использована соответствующая конфигурация контроллера; - -- если идентификатор контроллера соответствует формату `'path/to/xyz'`, то имя класса -контроллера определяется как `XyzController`, а соответствующий класс как -`protected/controllers/path/to/XyzController.php`. -Например, идентификатор контроллера `admin/user` будет соответствовать классу -контроллера — `UserController` и файлу `protected/controllers/admin/UserController.php`. -Если файл не существует, будет сгенерировано исключение [CHttpException] с кодом ошибки 404. - -При использовании [модулей](/doc/guide/basics.module) процесс, описанный -выше, будет выглядеть несколько иначе. В частности, приложение проверит, -соответствует ли идентификатор контроллеру внутри модуля. Если соответствует, то -сначала будет создан экземпляр модуля, а затем экземпляр контроллера. - - -Действие --------- -Как было упомянуто выше, действие — это метод, имя которого начинается на `action`. -Более продвинутый способ — создать класс действия и указать контроллеру создавать -экземпляр этого класса при необходимости. Такой подход позволяет использовать -действия повторно. - - -Для создания класса действия необходимо выполнить следующее: - -~~~ -[php] -class UpdateAction extends CAction -{ - public function run() - { - // некоторая логика действия - } -} -~~~ - -Чтобы контроллер знал об этом действии, необходимо переопределить метод -[actions()|CController::actions] в классе контроллера: - -~~~ -[php] -class PostController extends CController -{ - public function actions() - { - return array( - 'edit'=>'application.controllers.post.UpdateAction', - ); - } -} -~~~ - -В приведённом коде мы используем псевдоним маршрута `application.controllers.post.UpdateAction` -для указания файла класса действия `protected/controllers/post/UpdateAction.php`. -Создавая действия, основанные на классах, можно организовать приложение в модульном стиле. -Например, следующая структура директорий может быть использована для организации кода контроллеров: -~~~ -protected/ - controllers/ - PostController.php - UserController.php - post/ - CreateAction.php - ReadAction.php - UpdateAction.php - user/ - CreateAction.php - ListAction.php - ProfileAction.php - UpdateAction.php -~~~ - -### Привязка параметров действий - -Начиная с версии 1.1.4, в Yii появилась поддержка автоматической привязки -параметров к действиям контроллера. То есть можно задать именованные -параметры, в которые автоматически будут попадать соответствующие значения из `$_GET`. - -Для того чтобы показать, как это работает, предположим, что нам нужно -реализовать действие `create` контроллера `PostController`. Действие принимает -два параметра: - -* `category`: ID категории, в которой будет создаваться запись (целое число); -* `language`: строка, содержащая код языка, который будет использоваться в записи. - -Скорее всего, для получения параметров из `$_GET` в контроллере нам придётся написать следующий скучный код: - -~~~ -[php] -class PostController extends CController -{ - public function actionCreate() - { - if(isset($_GET['category'])) - $category=(int)$_GET['category']; - else - throw new CHttpException(404,'неверный запрос'); - - if(isset($_GET['language'])) - $language=$_GET['language']; - else - $language='en'; - - // … действительно полезная часть кода … - } -} -~~~ - -Используя параметры действий, мы можем получить более приятный код: - -~~~ -[php] -class PostController extends CController -{ - public function actionCreate($category, $language='en') - { - $category=(int)$category; - - // … действительно полезная часть кода … - } -} -~~~ - -Мы добавляем два параметра методу `actionCreate`. Имя каждого должно в точности -совпадать с одним из ключей в `$_GET`. Параметру `$language` задано значение -по умолчанию `en`, которое используется, если в запросе соответствующий параметр -отсутствует. Так как `$category` не имеет значения по умолчанию, в случае -отсутствия соответствующего параметра в запросе будет автоматически выброшено -исключение [CHttpException] (с кодом ошибки 400). - -Начиная с версии 1.1.5, Yii поддерживает указание массивов в качестве параметров действий. -Использовать их можно следующим образом: - -~~~ -[php] -class PostController extends CController -{ - public function actionCreate(array $categories) - { - // Yii приведёт $categories к массиву - } -} -~~~ - -Мы добавляем ключевое слово `array` перед параметром `$categories`. -В результате, если параметр `$_GET['categories']` является простой строкой, то он будет -приведён к массиву, содержащему исходную строку. - -> Note|Примечание: Если параметр объявлен без указания типа `array`, то он должен -> быть скалярным (т.е. не массивом). В этом случае передача массива через -> `$_GET` параметр приведёт к исключению HTTP. - - -Начиная с версии 1.1.7, автоматическая привязка параметров работает и с -действиями, оформленными в виде классов. Если метод `run()` в классе действия -описать с параметрами, то эти параметры наполняются соответствующими значениями -из HTTP-запроса: - -~~~ -[php] -class UpdateAction extends CAction -{ - public function run($id) - { - // $id будет заполнен значением из $_GET['id'] - } -} -~~~ - -Фильтры -------- -Фильтр — это часть кода, которая может выполняться до или после -выполнения действия контроллера в зависимости от конфигурации. Например, фильтр -контроля доступа может проверять, аутентифицирован ли пользователь перед тем, -как будет выполнено запрошенное действие. Фильтр, контролирующий производительность, -может быть использован для определения времени, затраченного на выполнение действия. - -Действие может иметь множество фильтров. Фильтры запускаются в том порядке, в котором -они указаны в списке фильтров, при этом фильтр может предотвратить выполнение -действия и следующих за ним фильтров. - -Фильтр может быть определён как метод класса контроллера. Имя метода должно начинаться на `filter`. -Например, метод `filterAccessControl` определяет фильтр `accessControl`. -Метод фильтра должен выглядеть так: - -~~~ -[php] -public function filterAccessControl($filterChain) -{ - // для выполнения последующих фильтров и выполнения действия вызовите метод $filterChain->run() -} -~~~ - -где `$filterChain` — экземпляр класса [CFilterChain], представляющего собой список -фильтров, ассоциированных с запрошенным действием. В коде фильтра можно вызвать -`$filterChain->run()` для того, чтобы продолжить выполнение последующих фильтров и действия. - -Фильтр также может быть экземпляром класса [CFilter] или его производного. -Следующий код определяет новый класс фильтра: - -~~~ -[php] -class PerformanceFilter extends CFilter -{ - protected function preFilter($filterChain) - { - // код, выполняемый до выполнения действия - return true; // false — для случая, когда действие не должно быть выполнено - } - - protected function postFilter($filterChain) - { - // код, выполняемый после выполнения действия - } -} -~~~ - -Для того чтобы применить фильтр к действию, необходимо переопределить метод -`CController::filters()`, возвращающий массив конфигураций фильтров. Например: - -~~~ -[php] -class PostController extends CController -{ - … - public function filters() - { - return array( - 'postOnly + edit, create', - array( - 'application.filters.PerformanceFilter - edit, create', - 'unit'=>'second', - ), - ); - } -} -~~~ - -Данный код определяет два фильтра: `postOnly` и `PerformanceFilter`. -Фильтр `postOnly` задан как метод (соответствующий метод уже определен в -[CController]), в то время как `PerformanceFilter` — фильтр на базе класса. -Псевдоним `application.filters.PerformanceFilter` указывает на файл класса фильтра — -`protected/filters/PerformanceFilter`. Для конфигурации `PerformanceFilter` -используется массив, что позволяет задать начальные значения свойств фильтра. -В данном случае свойство `unit` фильтра `PerformanceFilter` будет -инициализировано значением `'second'`. - -Используя операторы `'+'` и `'-'` можно указать, к каким действиям должен и -не должен быть применён фильтр. В приведённом примере `postOnly` будет -применён к действиям `edit` и `create`, а `PerformanceFilter` — ко всем действиям, -*кроме* `edit` и `create`. Если операторы `'+'` и `'-'` не указаны, фильтр будет +Контроллер +========== +`Контроллер (controller)` — это экземпляр класса [CController] или унаследованного +от него класса. Он создается объектом приложения в случае, когда пользователь его +запрашивает. При запуске контроллер выполняет соответствующее действие, что обычно +подразумевает создание соответствующих моделей и отображение необходимых представлений. +В самом простом случае `действие` — это метод класса контроллера, название +которого начинается на `action`. + +У контроллера есть действие по умолчанию, которое выполняется в случае, когда +пользователь не указывает действие при запросе. По умолчанию это действие +называется `index`. Изменить его можно путём установки значения [CController::defaultAction]. + +Следующий код определяет контроллер `site` с действиями `index` (действие по +умолчанию) и `contact`: + +~~~ +[php] +class SiteController extends CController +{ + public function actionIndex() + { + // ... + } + + public function actionContact() + { + // ... + } +} +~~~ + + +Маршрут +------- +Контроллеры и действия опознаются по их идентификаторам. +Идентификатор контроллера — это запись формата `path/to/xyz`, соответствующая +файлу класса контроллера `protected/controllers/path/to/XyzController.php`, где `xyz` +следует заменить реальным названием класса (например, `post` соответствует +`protected/controllers/PostController.php`). Идентификатор действия — это название +метода без префикса `action`. Например, если класс контроллера содержит метод +`actionEdit`, то идентификатор соответствующего действия — `edit`. + +Пользователь обращается к контроллеру и действию посредством маршрута (route). +Маршрут формируется путём объединения идентификаторов контроллера и действия, +отделенных косой чертой. Например, маршрут `post/edit` указывает на действие +`edit` контроллера `PostController`, и по умолчанию URL `http://hostname/index.php?r=post/edit` +приведёт к вызову именно этих контроллера и действия. + +> Note|Примечание: По умолчанию маршруты чувствительны к регистру. +>Это возможно изменить путём установки свойства +>[CUrlManager::caseSensitive] равным false в конфигурации приложения. +>В режиме, не чувствительном к регистру, убедитесь, что названия директорий, +>содержащих файлы классов контроллеров, указаны в нижнем регистре, а также, +>что [controller map|CWebApplication::controllerMap] и [action map|CController::actions] +>используют ключи в нижнем регистре. + +Приложение может содержать [модули](/doc/guide/basics.module). Маршрут к действию +контроллера внутри модуля задаётся в формате `moduleID/controllerID/actionID`. +Более подробно это описано в [разделе о модулях](/doc/guide/basics.module). + +Создание экземпляра контроллера +------------------------------- +Экземпляр контроллера создаётся, когда [CWebApplication] обрабатывает входящий запрос. +Получив идентификатор контроллера, приложение использует следующие правила для +определения класса контроллера и его местоположения: + +- если установлено свойство [CWebApplication::catchAllRequest], контроллер будет создан +на основе этого свойства, а контроллер, запрошенный пользователем, будет проигнорирован. +Как правило, это используется для установки приложения в режим технического обслуживания +и отображения статической страницы с соответствующим сообщением; + +- если идентификатор контроллера обнаружен в [CWebApplication::controllerMap], то для +создания экземпляра контроллера будет использована соответствующая конфигурация контроллера; + +- если идентификатор контроллера соответствует формату `'path/to/xyz'`, то имя класса +контроллера определяется как `XyzController`, а соответствующий класс как +`protected/controllers/path/to/XyzController.php`. +Например, идентификатор контроллера `admin/user` будет соответствовать классу +контроллера — `UserController` и файлу `protected/controllers/admin/UserController.php`. +Если файл не существует, будет сгенерировано исключение [CHttpException] с кодом ошибки 404. + +При использовании [модулей](/doc/guide/basics.module) процесс, описанный +выше, будет выглядеть несколько иначе. В частности, приложение проверит, +соответствует ли идентификатор контроллеру внутри модуля. Если соответствует, то +сначала будет создан экземпляр модуля, а затем экземпляр контроллера. + + +Действие +-------- +Как было упомянуто выше, действие — это метод, имя которого начинается на `action`. +Более продвинутый способ — создать класс действия и указать контроллеру создавать +экземпляр этого класса при необходимости. Такой подход позволяет использовать +действия повторно. + + +Для создания класса действия необходимо выполнить следующее: + +~~~ +[php] +class UpdateAction extends CAction +{ + public function run() + { + // некоторая логика действия + } +} +~~~ + +Чтобы контроллер знал об этом действии, необходимо переопределить метод +[actions()|CController::actions] в классе контроллера: + +~~~ +[php] +class PostController extends CController +{ + public function actions() + { + return array( + 'edit'=>'application.controllers.post.UpdateAction', + ); + } +} +~~~ + +В приведённом коде мы используем псевдоним маршрута `application.controllers.post.UpdateAction` +для указания файла класса действия `protected/controllers/post/UpdateAction.php`. +Создавая действия, основанные на классах, можно организовать приложение в модульном стиле. +Например, следующая структура директорий может быть использована для организации кода контроллеров: +~~~ +protected/ + controllers/ + PostController.php + UserController.php + post/ + CreateAction.php + ReadAction.php + UpdateAction.php + user/ + CreateAction.php + ListAction.php + ProfileAction.php + UpdateAction.php +~~~ + +### Привязка параметров действий + +Начиная с версии 1.1.4, в Yii появилась поддержка автоматической привязки +параметров к действиям контроллера. То есть можно задать именованные +параметры, в которые автоматически будут попадать соответствующие значения из `$_GET`. + +Для того чтобы показать, как это работает, предположим, что нам нужно +реализовать действие `create` контроллера `PostController`. Действие принимает +два параметра: + +* `category`: ID категории, в которой будет создаваться запись (целое число); +* `language`: строка, содержащая код языка, который будет использоваться в записи. + +Скорее всего, для получения параметров из `$_GET` в контроллере нам придётся написать следующий скучный код: + +~~~ +[php] +class PostController extends CController +{ + public function actionCreate() + { + if(isset($_GET['category'])) + $category=(int)$_GET['category']; + else + throw new CHttpException(404,'неверный запрос'); + + if(isset($_GET['language'])) + $language=$_GET['language']; + else + $language='en'; + + // … действительно полезная часть кода … + } +} +~~~ + +Используя параметры действий, мы можем получить более приятный код: + +~~~ +[php] +class PostController extends CController +{ + public function actionCreate($category, $language='en') + { + $category=(int)$category; + + // … действительно полезная часть кода … + } +} +~~~ + +Мы добавляем два параметра методу `actionCreate`. Имя каждого должно в точности +совпадать с одним из ключей в `$_GET`. Параметру `$language` задано значение +по умолчанию `en`, которое используется, если в запросе соответствующий параметр +отсутствует. Так как `$category` не имеет значения по умолчанию, в случае +отсутствия соответствующего параметра в запросе будет автоматически выброшено +исключение [CHttpException] (с кодом ошибки 400). + +Начиная с версии 1.1.5, Yii поддерживает указание массивов в качестве параметров действий. +Использовать их можно следующим образом: + +~~~ +[php] +class PostController extends CController +{ + public function actionCreate(array $categories) + { + // Yii приведёт $categories к массиву + } +} +~~~ + +Мы добавляем ключевое слово `array` перед параметром `$categories`. +В результате, если параметр `$_GET['categories']` является простой строкой, то он будет +приведён к массиву, содержащему исходную строку. + +> Note|Примечание: Если параметр объявлен без указания типа `array`, то он должен +> быть скалярным (т.е. не массивом). В этом случае передача массива через +> `$_GET` параметр приведёт к исключению HTTP. + + +Начиная с версии 1.1.7, автоматическая привязка параметров работает и с +действиями, оформленными в виде классов. Если метод `run()` в классе действия +описать с параметрами, то эти параметры наполняются соответствующими значениями +из HTTP-запроса: + +~~~ +[php] +class UpdateAction extends CAction +{ + public function run($id) + { + // $id будет заполнен значением из $_GET['id'] + } +} +~~~ + +Фильтры +------- +Фильтр — это часть кода, которая может выполняться до или после +выполнения действия контроллера в зависимости от конфигурации. Например, фильтр +контроля доступа может проверять, аутентифицирован ли пользователь перед тем, +как будет выполнено запрошенное действие. Фильтр, контролирующий производительность, +может быть использован для определения времени, затраченного на выполнение действия. + +Действие может иметь множество фильтров. Фильтры запускаются в том порядке, в котором +они указаны в списке фильтров, при этом фильтр может предотвратить выполнение +действия и следующих за ним фильтров. + +Фильтр может быть определён как метод класса контроллера. Имя метода должно начинаться на `filter`. +Например, метод `filterAccessControl` определяет фильтр `accessControl`. +Метод фильтра должен выглядеть так: + +~~~ +[php] +public function filterAccessControl($filterChain) +{ + // для выполнения последующих фильтров и выполнения действия вызовите метод $filterChain->run() +} +~~~ + +где `$filterChain` — экземпляр класса [CFilterChain], представляющего собой список +фильтров, ассоциированных с запрошенным действием. В коде фильтра можно вызвать +`$filterChain->run()` для того, чтобы продолжить выполнение последующих фильтров и действия. + +Фильтр также может быть экземпляром класса [CFilter] или его производного. +Следующий код определяет новый класс фильтра: + +~~~ +[php] +class PerformanceFilter extends CFilter +{ + protected function preFilter($filterChain) + { + // код, выполняемый до выполнения действия + return true; // false — для случая, когда действие не должно быть выполнено + } + + protected function postFilter($filterChain) + { + // код, выполняемый после выполнения действия + } +} +~~~ + +Для того чтобы применить фильтр к действию, необходимо переопределить метод +`CController::filters()`, возвращающий массив конфигураций фильтров. Например: + +~~~ +[php] +class PostController extends CController +{ + … + public function filters() + { + return array( + 'postOnly + edit, create', + array( + 'application.filters.PerformanceFilter - edit, create', + 'unit'=>'second', + ), + ); + } +} +~~~ + +Данный код определяет два фильтра: `postOnly` и `PerformanceFilter`. +Фильтр `postOnly` задан как метод (соответствующий метод уже определен в +[CController]), в то время как `PerformanceFilter` — фильтр на базе класса. +Псевдоним `application.filters.PerformanceFilter` указывает на файл класса фильтра — +`protected/filters/PerformanceFilter`. Для конфигурации `PerformanceFilter` +используется массив, что позволяет задать начальные значения свойств фильтра. +В данном случае свойство `unit` фильтра `PerformanceFilter` будет +инициализировано значением `'second'`. + +Используя операторы `'+'` и `'-'` можно указать, к каким действиям должен и +не должен быть применён фильтр. В приведённом примере `postOnly` будет +применён к действиям `edit` и `create`, а `PerformanceFilter` — ко всем действиям, +*кроме* `edit` и `create`. Если операторы `'+'` и `'-'` не указаны, фильтр будет применён ко всем действиям. \ No newline at end of file diff --git a/docs/guide/ru/basics.convention.txt b/docs/guide/ru/basics.convention.txt index abec97a1f..54e85780c 100644 --- a/docs/guide/ru/basics.convention.txt +++ b/docs/guide/ru/basics.convention.txt @@ -1,134 +1,134 @@ -Соглашения -========== - -Yii ставит соглашения выше конфигураций. Следуя соглашениям, вы сможете создавать серьёзные приложения -без необходимости написания и поддержки сложных конфигураций. Однако при необходимости Yii может -быть изменён с помощью конфигураций практически как угодно. - -Ниже представлены соглашения, рекомендуемые для программирования под Yii. -Для удобства примем, что `WebRoot` — это директория, в которую установлено приложение. - -URL ---- - -По умолчанию Yii понимает адреса URL следующего формата: - -~~~ -http://hostname/index.php?r=ControllerID/ActionID -~~~ - -GET-переменная `r` представляет [маршрут](/doc/guide/basics.controller#route), из которого Yii извлекает информацию о контроллере и действии. -Если `ActionID` не указан, контроллер будет использовать действие по умолчанию, определённое в свойстве [CController::defaultAction]. -Если же и `ControllerID` не указан (либо отсутствует переменная `r`), то будет использован -контроллер по умолчанию, определённый в свойстве [CWebApplication::defaultController]. - -Благодаря компоненту [CUrlManager] можно создавать и использовать SEO-дружественные адреса URL, такие как -`http://hostname/ControllerID/ActionID.html`. Эта возможность подробно описана в разделе [Красивые адреса URL](/doc/guide/topics.url). - -Код ---- - -Yii рекомендует именовать переменные, функции и классы, используя CamelCase, что подразумевает написание -каждого слова в имени с большой буквы и соединение их без пробелов. -Первое слово в имени переменных и функций должно быть написано в нижнем регистре, чтобы отличать их от имён -классов (например, `$basePath`, `runController()`, `LinkPager`). -Для полей класса с видимостью `private` рекомендуется -использовать знак подчеркивания в качестве префикса (например, `$_actionList`). - -Поскольку пространства имён не поддерживаются версиями PHP до 5.3.0, рекомендуется, чтобы имена классов были -уникальными во избежание конфликта имён с классами сторонних разработчиков. По этой причине все имена классов -фреймворка имеют префикс "C". - -Особое правило для имён классов контроллеров — они должны быть дополнены словом `Controller`. При этом идентификатором -контроллера будет имя класса с первой буквой в нижнем регистре и без слова `Controller`. -Например, для класса `PageController` идентификатором будет `page`. Данное правило делает приложение более защищённым. -Оно также делает адреса URL более понятными (к примеру, `/index.php?r=page/index` вместо -`/index.php?r=PageController/index`). - -Конфигурация ------------- - -Конфигурация — это массив пар ключ-значение, где каждый ключ представляет собой имя свойства конфигурируемого объекта, -а значение — начальное значение соответствующего свойства. -К примеру, `array('name'=>'My application', 'basePath'=>'./protected')` инициализирует свойства `name` и `basePath` -соответствующими значениями. - -Любые свойства объекта, которые доступны для записи, могут быть сконфигурированы. Если некоторые -свойства не сконфигурированы, для них будут использованы значения по умолчанию. -При конфигурировании свойств рекомендуется изучить соответствующий раздел документации, чтобы избежать задания некорректных значений. - -Файл ----- - -Соглашения для именования и использования файлов зависят от их типов. - -Файлы классов должны быть названы так же, как и открытые классы, содержащиеся в них. -Например, класс [CController] находится в файле `CController.php`. -Открытый класс — это класс, который может использоваться любыми другими классами. -Каждый файл классов должен содержать максимум один открытый класс. Приватные классы -(классы, которые могут быть использованы только одним открытым классом) должны -находиться в одном файле с открытым классом. - -Файлы представлений должны иметь такие же имена, как и содержащиеся в них представления. -К примеру, представление `index` находится в файле `index.php`. -Файл представления — это PHP-скрипт, содержащий HTML и PHP-код, в основном предназначенный для отображения -пользовательского интерфейса. - -Конфигурационные файлы могут именоваться произвольным образом. Файл конфигурации — -это PHP-скрипт, чьё единственное назначение — возвращать -ассоциативный массив, представляющий конфигурацию. - -Директория ----------- - -В Yii предопределён набор директорий для различных целей. Каждая из них может быть изменена при необходимости. - - - `WebRoot/protected`: это [базовая директория приложения](/doc/guide/basics.application#application-base-directory), -содержащая все наиболее важные с точки зрения безопасности PHP-скрипты и файлы данных. Псевдоним по умолчанию для этого пути — `application`. -Сама директория и её содержимое должны быть защищены от прямого доступа из веб. Директория может быть настроена через -[CWebApplication::basePath]. - - - `WebRoot/protected/runtime`: эта директория содержит приватные временные файлы, сгенерированные во время выполнения приложения. -Эта директория должна быть доступна для записи веб-сервером. Она может быть настроена через [CApplication::runtimePath]. - - - `WebRoot/protected/extensions`: эта директория содержит все сторонние расширения. Она может быть настроена через -[CApplication::extensionPath]. Псевдоним по умолчанию для этого пути — `ext`. - - - `WebRoot/protected/modules`: эта директория содержит все [модули](/doc/guide/basics.module) приложения, каждый из которых находится в отдельной поддиректории. Директория может быть настроена через [CWebApplication::modulePath]. - - - `WebRoot/protected/controllers`: эта директория содержит файлы всех классов контроллеров. Она может быть настроена через [CWebApplication::controllerPath]. - - - `WebRoot/protected/views`: эта директория содержит файлы всех представлений, включая представления контроллеров, макеты и системные -представления. Она может быть настроена через [CWebApplication::viewPath]. - - - `WebRoot/protected/views/ControllerID`: эта директория содержит файлы представлений для отдельного класса контроллера. -Здесь `ControllerID` является идентификатором контроллера. Директория может быть настроена через [CController::viewPath]. - - - `WebRoot/protected/views/layouts`: эта директория содержит файлы макетов. Она может быть настроена через -[CWebApplication::layoutPath]. - - - `WebRoot/protected/views/system`: эта директория содержит файлы системных представлений (используются для отображения сообщений об -ошибках и исключениях). Она может быть настроена через [CWebApplication::systemViewPath]. - - - `WebRoot/assets`: эта директория содержит файлы ресурсов (приватные файлы, которые могут быть опубликованы для доступа к ним из веб). -Директория должна быть доступна для записи процессами веб-сервера. Она может быть настроена через [CAssetManager::basePath]. - - - `WebRoot/themes`: эта директория содержит различные темы оформления, доступные в приложении. -Каждая поддиректория содержит отдельную тему с именем, совпадающим с названием поддиректории. Директория может быть настроена через [CThemeManager::basePath]. - -База данных ------------ - -Большинство приложений хранят данные в БД. Мы предлагаем соглашения -для именования таблиц и их полей. Стоит отметить, что Yii не требует строгого следования этим правилам. - - - Таблицы и поля именуются в нижнем регистре. - - - Слова в названиях разделяются символом подчёркивания (например, `product_order`). - - - В именах таблиц используется либо единственное число, либо множественное, но не - оба сразу. Мы рекомендуем использовать единственное число. - - - Имена таблиц могут содержать префикс. Например, `tbl_`. Это особенно полезно, -когда таблицы нашего приложения находятся в БД, используемой одновременно другими +Соглашения +========== + +Yii ставит соглашения выше конфигураций. Следуя соглашениям, вы сможете создавать серьёзные приложения +без необходимости написания и поддержки сложных конфигураций. Однако при необходимости Yii может +быть изменён с помощью конфигураций практически как угодно. + +Ниже представлены соглашения, рекомендуемые для программирования под Yii. +Для удобства примем, что `WebRoot` — это директория, в которую установлено приложение. + +URL +--- + +По умолчанию Yii понимает адреса URL следующего формата: + +~~~ +http://hostname/index.php?r=ControllerID/ActionID +~~~ + +GET-переменная `r` представляет [маршрут](/doc/guide/basics.controller#route), из которого Yii извлекает информацию о контроллере и действии. +Если `ActionID` не указан, контроллер будет использовать действие по умолчанию, определённое в свойстве [CController::defaultAction]. +Если же и `ControllerID` не указан (либо отсутствует переменная `r`), то будет использован +контроллер по умолчанию, определённый в свойстве [CWebApplication::defaultController]. + +Благодаря компоненту [CUrlManager] можно создавать и использовать SEO-дружественные адреса URL, такие как +`http://hostname/ControllerID/ActionID.html`. Эта возможность подробно описана в разделе [Красивые адреса URL](/doc/guide/topics.url). + +Код +--- + +Yii рекомендует именовать переменные, функции и классы, используя CamelCase, что подразумевает написание +каждого слова в имени с большой буквы и соединение их без пробелов. +Первое слово в имени переменных и функций должно быть написано в нижнем регистре, чтобы отличать их от имён +классов (например, `$basePath`, `runController()`, `LinkPager`). +Для полей класса с видимостью `private` рекомендуется +использовать знак подчеркивания в качестве префикса (например, `$_actionList`). + +Поскольку пространства имён не поддерживаются версиями PHP до 5.3.0, рекомендуется, чтобы имена классов были +уникальными во избежание конфликта имён с классами сторонних разработчиков. По этой причине все имена классов +фреймворка имеют префикс "C". + +Особое правило для имён классов контроллеров — они должны быть дополнены словом `Controller`. При этом идентификатором +контроллера будет имя класса с первой буквой в нижнем регистре и без слова `Controller`. +Например, для класса `PageController` идентификатором будет `page`. Данное правило делает приложение более защищённым. +Оно также делает адреса URL более понятными (к примеру, `/index.php?r=page/index` вместо +`/index.php?r=PageController/index`). + +Конфигурация +------------ + +Конфигурация — это массив пар ключ-значение, где каждый ключ представляет собой имя свойства конфигурируемого объекта, +а значение — начальное значение соответствующего свойства. +К примеру, `array('name'=>'My application', 'basePath'=>'./protected')` инициализирует свойства `name` и `basePath` +соответствующими значениями. + +Любые свойства объекта, которые доступны для записи, могут быть сконфигурированы. Если некоторые +свойства не сконфигурированы, для них будут использованы значения по умолчанию. +При конфигурировании свойств рекомендуется изучить соответствующий раздел документации, чтобы избежать задания некорректных значений. + +Файл +---- + +Соглашения для именования и использования файлов зависят от их типов. + +Файлы классов должны быть названы так же, как и открытые классы, содержащиеся в них. +Например, класс [CController] находится в файле `CController.php`. +Открытый класс — это класс, который может использоваться любыми другими классами. +Каждый файл классов должен содержать максимум один открытый класс. Приватные классы +(классы, которые могут быть использованы только одним открытым классом) должны +находиться в одном файле с открытым классом. + +Файлы представлений должны иметь такие же имена, как и содержащиеся в них представления. +К примеру, представление `index` находится в файле `index.php`. +Файл представления — это PHP-скрипт, содержащий HTML и PHP-код, в основном предназначенный для отображения +пользовательского интерфейса. + +Конфигурационные файлы могут именоваться произвольным образом. Файл конфигурации — +это PHP-скрипт, чьё единственное назначение — возвращать +ассоциативный массив, представляющий конфигурацию. + +Директория +---------- + +В Yii предопределён набор директорий для различных целей. Каждая из них может быть изменена при необходимости. + + - `WebRoot/protected`: это [базовая директория приложения](/doc/guide/basics.application#application-base-directory), +содержащая все наиболее важные с точки зрения безопасности PHP-скрипты и файлы данных. Псевдоним по умолчанию для этого пути — `application`. +Сама директория и её содержимое должны быть защищены от прямого доступа из веб. Директория может быть настроена через +[CWebApplication::basePath]. + + - `WebRoot/protected/runtime`: эта директория содержит приватные временные файлы, сгенерированные во время выполнения приложения. +Эта директория должна быть доступна для записи веб-сервером. Она может быть настроена через [CApplication::runtimePath]. + + - `WebRoot/protected/extensions`: эта директория содержит все сторонние расширения. Она может быть настроена через +[CApplication::extensionPath]. Псевдоним по умолчанию для этого пути — `ext`. + + - `WebRoot/protected/modules`: эта директория содержит все [модули](/doc/guide/basics.module) приложения, каждый из которых находится в отдельной поддиректории. Директория может быть настроена через [CWebApplication::modulePath]. + + - `WebRoot/protected/controllers`: эта директория содержит файлы всех классов контроллеров. Она может быть настроена через [CWebApplication::controllerPath]. + + - `WebRoot/protected/views`: эта директория содержит файлы всех представлений, включая представления контроллеров, макеты и системные +представления. Она может быть настроена через [CWebApplication::viewPath]. + + - `WebRoot/protected/views/ControllerID`: эта директория содержит файлы представлений для отдельного класса контроллера. +Здесь `ControllerID` является идентификатором контроллера. Директория может быть настроена через [CController::viewPath]. + + - `WebRoot/protected/views/layouts`: эта директория содержит файлы макетов. Она может быть настроена через +[CWebApplication::layoutPath]. + + - `WebRoot/protected/views/system`: эта директория содержит файлы системных представлений (используются для отображения сообщений об +ошибках и исключениях). Она может быть настроена через [CWebApplication::systemViewPath]. + + - `WebRoot/assets`: эта директория содержит файлы ресурсов (приватные файлы, которые могут быть опубликованы для доступа к ним из веб). +Директория должна быть доступна для записи процессами веб-сервера. Она может быть настроена через [CAssetManager::basePath]. + + - `WebRoot/themes`: эта директория содержит различные темы оформления, доступные в приложении. +Каждая поддиректория содержит отдельную тему с именем, совпадающим с названием поддиректории. Директория может быть настроена через [CThemeManager::basePath]. + +База данных +----------- + +Большинство приложений хранят данные в БД. Мы предлагаем соглашения +для именования таблиц и их полей. Стоит отметить, что Yii не требует строгого следования этим правилам. + + - Таблицы и поля именуются в нижнем регистре. + + - Слова в названиях разделяются символом подчёркивания (например, `product_order`). + + - В именах таблиц используется либо единственное число, либо множественное, но не + оба сразу. Мы рекомендуем использовать единственное число. + + - Имена таблиц могут содержать префикс. Например, `tbl_`. Это особенно полезно, +когда таблицы нашего приложения находятся в БД, используемой одновременно другими приложениями. \ No newline at end of file diff --git a/docs/guide/ru/basics.mvc.txt b/docs/guide/ru/basics.mvc.txt index 32f57f4c1..82c9323be 100644 --- a/docs/guide/ru/basics.mvc.txt +++ b/docs/guide/ru/basics.mvc.txt @@ -1,44 +1,44 @@ -Модель-Представление-Контроллер (MVC) -====================================== - -Yii использует шаблон проектирования Модель-Представление-Контроллер (MVC, Model-View-Controller), -который широко применяется в веб-программировании. - -MVC предназначен для разделения бизнес-логики и пользовательского интерфейса, чтобы разработчики могли легко изменять -отдельные части приложения, не затрагивая другие. -В архитектуре MVC модель предоставляет данные и правила бизнес-логики, представление отвечает за пользовательский -интерфейс (например, текст, поля ввода), а контроллер обеспечивает взаимодействие между моделью и представлением. - -Помимо этого, Yii использует фронт-контроллер, называемый приложением (application), -который инкапсулирует контекст обработки запроса. Приложение собирает информацию -о запросе и передает её для дальнейшей обработки соответствующему контроллеру. - -Следующая диаграмма отображает структуру приложения Yii: - -![Статическая структура приложения Yii](structure.png) - - -Типичная последовательность работы приложения Yii -------------------------------------------------- - -Следующая диаграмма описывает типичную последовательность процесса обработки пользовательского запроса приложением: - -![Типичная последовательность работы приложения Yii](flow.png) - - 1. Пользователь осуществляет запрос посредством URL `http://www.example.com/index.php?r=post/show&id=1`, -и веб-сервер обрабатывает его, запуская скрипт инициализации `index.php`. - 2. Скрипт инициализации создает экземпляр [приложения](/doc/guide/basics.application) и запускает его на выполнение. - 3. Приложение получает подробную информацию о запросе пользователя от [компонента приложения](/doc/guide/basics.application#application-component) `request`. - 4. Приложение определяет запрошенные [контроллер](/doc/guide/basics.controller) -и [действие](/doc/guide/basics.controller#action) при помощи компонента `urlManager`. -В данном примере контроллером будет `post`, относящийся к классу `PostController`, а действием — `show`, суть которого -определяется контроллером. - 5. Приложение создаёт экземпляр запрашиваемого контроллера для дальнейшей обработки запроса пользователя. Контроллер определяет -соответствие действия `show` методу `actionShow` в классе контроллера. Далее создаются и применяются фильтры -(например, access control, benchmarking), связанные с данным действием, и, если фильтры позволяют, действие выполняется. - 6. Действие считывает из базы данных [модель](/doc/guide/basics.model) `Post` с ID равным `1`. - 7. Действие подключает [представление](/doc/guide/basics.view) `show`, передавая в него модель `Post`. - 8. Представление получает и отображает атрибуты модели `Post`. - 9. Представление подключает некоторые [виджеты](/doc/guide/basics.view#widget). - 10. Сформированное представление вставляется в [макет страницы](/doc/guide/basics.view#layout). +Модель-Представление-Контроллер (MVC) +====================================== + +Yii использует шаблон проектирования Модель-Представление-Контроллер (MVC, Model-View-Controller), +который широко применяется в веб-программировании. + +MVC предназначен для разделения бизнес-логики и пользовательского интерфейса, чтобы разработчики могли легко изменять +отдельные части приложения, не затрагивая другие. +В архитектуре MVC модель предоставляет данные и правила бизнес-логики, представление отвечает за пользовательский +интерфейс (например, текст, поля ввода), а контроллер обеспечивает взаимодействие между моделью и представлением. + +Помимо этого, Yii использует фронт-контроллер, называемый приложением (application), +который инкапсулирует контекст обработки запроса. Приложение собирает информацию +о запросе и передает её для дальнейшей обработки соответствующему контроллеру. + +Следующая диаграмма отображает структуру приложения Yii: + +![Статическая структура приложения Yii](structure.png) + + +Типичная последовательность работы приложения Yii +------------------------------------------------- + +Следующая диаграмма описывает типичную последовательность процесса обработки пользовательского запроса приложением: + +![Типичная последовательность работы приложения Yii](flow.png) + + 1. Пользователь осуществляет запрос посредством URL `http://www.example.com/index.php?r=post/show&id=1`, +и веб-сервер обрабатывает его, запуская скрипт инициализации `index.php`. + 2. Скрипт инициализации создает экземпляр [приложения](/doc/guide/basics.application) и запускает его на выполнение. + 3. Приложение получает подробную информацию о запросе пользователя от [компонента приложения](/doc/guide/basics.application#application-component) `request`. + 4. Приложение определяет запрошенные [контроллер](/doc/guide/basics.controller) +и [действие](/doc/guide/basics.controller#action) при помощи компонента `urlManager`. +В данном примере контроллером будет `post`, относящийся к классу `PostController`, а действием — `show`, суть которого +определяется контроллером. + 5. Приложение создаёт экземпляр запрашиваемого контроллера для дальнейшей обработки запроса пользователя. Контроллер определяет +соответствие действия `show` методу `actionShow` в классе контроллера. Далее создаются и применяются фильтры +(например, access control, benchmarking), связанные с данным действием, и, если фильтры позволяют, действие выполняется. + 6. Действие считывает из базы данных [модель](/doc/guide/basics.model) `Post` с ID равным `1`. + 7. Действие подключает [представление](/doc/guide/basics.view) `show`, передавая в него модель `Post`. + 8. Представление получает и отображает атрибуты модели `Post`. + 9. Представление подключает некоторые [виджеты](/doc/guide/basics.view#widget). + 10. Сформированное представление вставляется в [макет страницы](/doc/guide/basics.view#layout). 11. Действие завершает формирование представления и выводит результат пользователю. \ No newline at end of file diff --git a/docs/guide/ru/basics.namespace.txt b/docs/guide/ru/basics.namespace.txt index 9ffb4973d..791c69e4f 100644 --- a/docs/guide/ru/basics.namespace.txt +++ b/docs/guide/ru/basics.namespace.txt @@ -1,270 +1,270 @@ -Псевдоним пути и пространство имён -================================== - -Псевдонимы пути широко используются в Yii. -Псевдоним ассоциируется с директорией или путём к файлу. -При его указании используется точечный синтаксис, схожий с широко используемым форматом пространств имён: - -~~~ -RootAlias.path.to.target -~~~ - -где `RootAlias` — псевдоним существующей директории. - - -При помощи [YiiBase::getPathOfAlias()] мы можем преобразовать псевдоним -в соответствующий ему путь. К примеру, `system.web.CController` будет -преобразован в `yii/framework/web/CController`. - -Также мы можем использовать [YiiBase::setPathOfAlias()] для определения новых -корневых псевдонимов. - - -Корневой псевдоним ------------------- - -Для удобства Yii предопределяет следующие системные псевдонимы: - - - `system`: соответствует директории фреймворка; - - `zii`: соответствует директории [библиотеки расширений Zii](/doc/guide/extension.use#zii-extensions); - - `application`: соответствует [базовой директории приложения](/doc/guide/basics.application#application-base-directory); - - `webroot`: соответствует директории, содержащей [входной скрипт](/doc/guide/basics.entry); - - `ext`: соответствует директории, содержащей все сторонние [расширения](/doc/guide/extension.overview). - - -Кроме того, если приложение использует [модули](/doc/guide/basics.module), то -у каждого модуля имеется совпадающий с его ID корневой псевдоним, указывающий на -корень модуля. К примеру, если приложение использует модуль `users`, то для него будет -определён корневой псевдоним `users`. - -Импорт классов --------------- - -Используя псевдонимы, очень удобно импортировать описания классов. -К примеру, для подключения класса [CController] можно вызвать: - -~~~ -[php] -Yii::import('system.web.CController'); -~~~ - -Использование метода [import|YiiBase::import] более эффективно, чем `include` и `require`, поскольку -описание импортируемого класса не будет включено до первого обращения (это реализовано через механизм -автозагрузки классов PHP). Импорт одного и того же пространства имён также происходит намного быстрее, -чем при использовании `include_once` и `require_once`. Стоит отметить, что при импорте директории субдиректории не -импортируются. - - -> Tip|Подсказка: Если мы ссылаемся на класс фреймворка, то нет необходимости импортировать или явно включать его. -Все системные классы Yii уже импортированы заранее. - -### Использование таблицы классов - -Начиная с версии 1.1.5, Yii позволяет предварительно импортировать пользовательские -классы через тот же механизм, что используется для классов ядра. Такие классы -могут использоваться где угодно в приложении без необходимости их предварительного -импорта или подключения. Данная возможность отлично подходит для фреймворка или библиотеки, -использующих Yii. - -Для импорта набора классов необходимо выполнить следующий код до вызова [CWebApplication::run()]: - -~~~ -[php] -Yii::$classMap=array( - 'ClassName1' => 'path/to/ClassName1.php', - 'ClassName2' => 'path/to/ClassName2.php', - ...... -); -~~~ - - -Импорт директорий ------------------ - -Можно использовать следующий синтаксис для того, чтобы импортировать целую директорию, а файлы классов, -содержащиеся в директории, будут подключены автоматически при необходимости. - -~~~ -[php] -Yii::import('system.web.*'); -~~~ - -Помимо [import|YiiBase::import], псевдонимы также используются во многих других местах, где есть ссылки на классы. -Например, псевдоним может быть передан методу [Yii::createComponent()] для создания экземпляра соответствующего -класса, даже если этот класс не был предварительно подключён. - -Пространство имён ------------------ - -Пространства имён служат для логической группировки -имён классов, чтобы их можно было отличить от других, даже если их имена совпадают. -Не путайте псевдоним пути с пространством имён. Псевдоним пути — всего лишь -удобный способ именования файлов и директорий. К пространствам имён он не имеет никакого -отношения. - -> Tip|Подсказка: Так как версии PHP до 5.3.0 не поддерживают пространства имён, вы не можете создать -экземпляры классов с одинаковыми именами, но различными описаниями. По этой причине все названия -классов Yii-фреймворка имеют префикс 'C' (означающий 'class'), чтобы их можно было отличить от -пользовательских классов. Для пользовательских классов рекомендуется использовать другие префиксы, -сохранив префикс 'C' зарезервированным для Yii-фреймворка. - -Классы в пространствах имён ---------------------------- - -Класс в пространстве имён — любой класс, описанный в неглобальном пространстве имён. -К примеру, класс `application\components\GoogleMap` описан в пространстве имён -`application\components`. Использование пространств имён требует PHP версии 5.3.0 или выше. - -Начиная с версии Yii 1.1.5, стало возможным использование класса из пространства имён -без его предварительного подключения. К примеру, мы можем создать новый экземпляр -`application\components\GoogleMap` без явного подключения соответствующего файла. -Это реализуется при помощи улучшенного загрузчика классов Yii. - -Для того чтобы автоматически подгрузить класс из пространства имён, пространство имён должно быть -названо в том же стиле, что и псевдонимы пути. Например, класс `application\components\GoogleMap` -должен храниться в файле, которому соответствует псевдоним `application.components.GoogleMap`. - -То есть для того, чтобы использовать пространство имён, начинающиеся, например, -с `\mynamespace` и классы которого располагаются в `/var/www/common/mynamespace/`, -единственное, что необходимо сделать — это объявить псевдоним пути: - -~~~ -[php] -Yii::setPathOfAlias('mynamespace', '/var/www/common/mynamespace/'); -~~~ - - -Контроллеры в пространствах имён --------------------------------- - -По умолчанию все контроллеры Yii берутся из глобального пространства имён. -Соответствующие классы расположены в `protected/controllers`. Вы можете изменить -данное поведение двумя способами: используя `controllerMap` и используя -`controllerNamespace`. Первый позволяет использовать контроллеры из разных -пространств имён. Второй легче настраивается, но задаёт одно пространство имён -для всех контроллеров. - -### Использование `controllerMap` - -Лучше всего менять данное свойство через файл конфигурации (`protected/config/main.php`): - -~~~ -[php] -// добавляем пространство имён "mynamespace" -Yii::setPathOfAlias('mynamespace', '/var/www/common/mynamespace/'); - -return array( - 'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..', - 'name'=>'My Web Application', - - 'controllerMap' => array( - 'test' => '\mynamespace\controllers\TestController', - ), -~~~ - -Когда пользователь пытается загрузить любой из контроллеров, для которого есть -запись в `controllerMap`, Yii сразу же подгружает указанный в ней класс. В -случае `test` Yii будет подгружать класс `\mynamespace\controllers\TestController`, -располагающийся в `/var/www/common/mynamespace/controllers/TestController.php`. - -Стоит отметить, что код контроллера должен быть в пространстве имён: - -~~~ -[php] -// задаём пространство имён -namespace mynamespace\controllers; - -// так как класс находится в пространстве имён, обращаться к глобальному -// пространству следует явно при помощи "\": -class TestController extends \CController -{ - public function actionIndex() - { - echo 'Это TestController из \mynamespace\controllers'; - } -} -~~~ - -### Использование `controllerNamespace` - -Так как приложение является модулем, то вы можете использовать `controllerNamespace` -так, как это описано далее в подразделе «Модули в пространствах имён». - -Модули в пространствах имён ---------------------------- - -Иногда удобно использовать пространство имён для целого модуля. К примеру, -если вам необходимо использовать пространство имён `\mynamespace\modules\testmodule`, -расположенное в `/var/www/common/mynamespace/modules/testmodule`, для модуля `testmodule` -сначала следует создать следующую структуру файлов: - -~~~ -/var/www/common/mynamespace/modules - testmodule - controllers - DefaultController.php - views - default - index.php - TestmoduleModule.php -~~~ - -Здесь `index.php` ничем не отличается от обычного модуля, а `TestmoduleModule.php` -и `DefaultController.php` находятся в пространстве имён. - -`TestmoduleModule.php`: - -~~~ -[php] -// задаём пространство имён: -namespace mynamespace\modules\testmodule; - -// так как класс находится в пространстве имён, обращаться к глобальному -// пространству следует явно при помощи "\": -class TestmoduleModule extends \CWebModule -{ - // задаём пространство имён для контроллеров (это же можно сделать через - // файл конфигурации) - public $controllerNamespace = '\mynamespace\modules\testmodule\controllers'; - - // обычный код модуля -} -~~~ - -`DefaultController.php`: - -~~~ -[php] -render('index'); - } -} -~~~ - -Теперь нам осталось только добавить модуль в наше приложение. Лучший способ сделать -это — использовать файл конфигурации `protected/config/main.php`: - -~~~ -[php] -// добавляем пространство имён "mynamespace" -Yii::setPathOfAlias('mynamespace', '/var/www/common/mynamespace/'); - -return array( - 'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..', - 'name'=>'My Web Application', - - 'modules'=>array( - 'testmodule' => array( - 'class' => '\mynamespace\modules\testmodule\TestModuleModule', - ), - ), +Псевдоним пути и пространство имён +================================== + +Псевдонимы пути широко используются в Yii. +Псевдоним ассоциируется с директорией или путём к файлу. +При его указании используется точечный синтаксис, схожий с широко используемым форматом пространств имён: + +~~~ +RootAlias.path.to.target +~~~ + +где `RootAlias` — псевдоним существующей директории. + + +При помощи [YiiBase::getPathOfAlias()] мы можем преобразовать псевдоним +в соответствующий ему путь. К примеру, `system.web.CController` будет +преобразован в `yii/framework/web/CController`. + +Также мы можем использовать [YiiBase::setPathOfAlias()] для определения новых +корневых псевдонимов. + + +Корневой псевдоним +------------------ + +Для удобства Yii предопределяет следующие системные псевдонимы: + + - `system`: соответствует директории фреймворка; + - `zii`: соответствует директории [библиотеки расширений Zii](/doc/guide/extension.use#zii-extensions); + - `application`: соответствует [базовой директории приложения](/doc/guide/basics.application#application-base-directory); + - `webroot`: соответствует директории, содержащей [входной скрипт](/doc/guide/basics.entry); + - `ext`: соответствует директории, содержащей все сторонние [расширения](/doc/guide/extension.overview). + + +Кроме того, если приложение использует [модули](/doc/guide/basics.module), то +у каждого модуля имеется совпадающий с его ID корневой псевдоним, указывающий на +корень модуля. К примеру, если приложение использует модуль `users`, то для него будет +определён корневой псевдоним `users`. + +Импорт классов +-------------- + +Используя псевдонимы, очень удобно импортировать описания классов. +К примеру, для подключения класса [CController] можно вызвать: + +~~~ +[php] +Yii::import('system.web.CController'); +~~~ + +Использование метода [import|YiiBase::import] более эффективно, чем `include` и `require`, поскольку +описание импортируемого класса не будет включено до первого обращения (это реализовано через механизм +автозагрузки классов PHP). Импорт одного и того же пространства имён также происходит намного быстрее, +чем при использовании `include_once` и `require_once`. Стоит отметить, что при импорте директории субдиректории не +импортируются. + + +> Tip|Подсказка: Если мы ссылаемся на класс фреймворка, то нет необходимости импортировать или явно включать его. +Все системные классы Yii уже импортированы заранее. + +### Использование таблицы классов + +Начиная с версии 1.1.5, Yii позволяет предварительно импортировать пользовательские +классы через тот же механизм, что используется для классов ядра. Такие классы +могут использоваться где угодно в приложении без необходимости их предварительного +импорта или подключения. Данная возможность отлично подходит для фреймворка или библиотеки, +использующих Yii. + +Для импорта набора классов необходимо выполнить следующий код до вызова [CWebApplication::run()]: + +~~~ +[php] +Yii::$classMap=array( + 'ClassName1' => 'path/to/ClassName1.php', + 'ClassName2' => 'path/to/ClassName2.php', + ...... +); +~~~ + + +Импорт директорий +----------------- + +Можно использовать следующий синтаксис для того, чтобы импортировать целую директорию, а файлы классов, +содержащиеся в директории, будут подключены автоматически при необходимости. + +~~~ +[php] +Yii::import('system.web.*'); +~~~ + +Помимо [import|YiiBase::import], псевдонимы также используются во многих других местах, где есть ссылки на классы. +Например, псевдоним может быть передан методу [Yii::createComponent()] для создания экземпляра соответствующего +класса, даже если этот класс не был предварительно подключён. + +Пространство имён +----------------- + +Пространства имён служат для логической группировки +имён классов, чтобы их можно было отличить от других, даже если их имена совпадают. +Не путайте псевдоним пути с пространством имён. Псевдоним пути — всего лишь +удобный способ именования файлов и директорий. К пространствам имён он не имеет никакого +отношения. + +> Tip|Подсказка: Так как версии PHP до 5.3.0 не поддерживают пространства имён, вы не можете создать +экземпляры классов с одинаковыми именами, но различными описаниями. По этой причине все названия +классов Yii-фреймворка имеют префикс 'C' (означающий 'class'), чтобы их можно было отличить от +пользовательских классов. Для пользовательских классов рекомендуется использовать другие префиксы, +сохранив префикс 'C' зарезервированным для Yii-фреймворка. + +Классы в пространствах имён +--------------------------- + +Класс в пространстве имён — любой класс, описанный в неглобальном пространстве имён. +К примеру, класс `application\components\GoogleMap` описан в пространстве имён +`application\components`. Использование пространств имён требует PHP версии 5.3.0 или выше. + +Начиная с версии Yii 1.1.5, стало возможным использование класса из пространства имён +без его предварительного подключения. К примеру, мы можем создать новый экземпляр +`application\components\GoogleMap` без явного подключения соответствующего файла. +Это реализуется при помощи улучшенного загрузчика классов Yii. + +Для того чтобы автоматически подгрузить класс из пространства имён, пространство имён должно быть +названо в том же стиле, что и псевдонимы пути. Например, класс `application\components\GoogleMap` +должен храниться в файле, которому соответствует псевдоним `application.components.GoogleMap`. + +То есть для того, чтобы использовать пространство имён, начинающиеся, например, +с `\mynamespace` и классы которого располагаются в `/var/www/common/mynamespace/`, +единственное, что необходимо сделать — это объявить псевдоним пути: + +~~~ +[php] +Yii::setPathOfAlias('mynamespace', '/var/www/common/mynamespace/'); +~~~ + + +Контроллеры в пространствах имён +-------------------------------- + +По умолчанию все контроллеры Yii берутся из глобального пространства имён. +Соответствующие классы расположены в `protected/controllers`. Вы можете изменить +данное поведение двумя способами: используя `controllerMap` и используя +`controllerNamespace`. Первый позволяет использовать контроллеры из разных +пространств имён. Второй легче настраивается, но задаёт одно пространство имён +для всех контроллеров. + +### Использование `controllerMap` + +Лучше всего менять данное свойство через файл конфигурации (`protected/config/main.php`): + +~~~ +[php] +// добавляем пространство имён "mynamespace" +Yii::setPathOfAlias('mynamespace', '/var/www/common/mynamespace/'); + +return array( + 'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..', + 'name'=>'My Web Application', + + 'controllerMap' => array( + 'test' => '\mynamespace\controllers\TestController', + ), +~~~ + +Когда пользователь пытается загрузить любой из контроллеров, для которого есть +запись в `controllerMap`, Yii сразу же подгружает указанный в ней класс. В +случае `test` Yii будет подгружать класс `\mynamespace\controllers\TestController`, +располагающийся в `/var/www/common/mynamespace/controllers/TestController.php`. + +Стоит отметить, что код контроллера должен быть в пространстве имён: + +~~~ +[php] +// задаём пространство имён +namespace mynamespace\controllers; + +// так как класс находится в пространстве имён, обращаться к глобальному +// пространству следует явно при помощи "\": +class TestController extends \CController +{ + public function actionIndex() + { + echo 'Это TestController из \mynamespace\controllers'; + } +} +~~~ + +### Использование `controllerNamespace` + +Так как приложение является модулем, то вы можете использовать `controllerNamespace` +так, как это описано далее в подразделе «Модули в пространствах имён». + +Модули в пространствах имён +--------------------------- + +Иногда удобно использовать пространство имён для целого модуля. К примеру, +если вам необходимо использовать пространство имён `\mynamespace\modules\testmodule`, +расположенное в `/var/www/common/mynamespace/modules/testmodule`, для модуля `testmodule` +сначала следует создать следующую структуру файлов: + +~~~ +/var/www/common/mynamespace/modules + testmodule + controllers + DefaultController.php + views + default + index.php + TestmoduleModule.php +~~~ + +Здесь `index.php` ничем не отличается от обычного модуля, а `TestmoduleModule.php` +и `DefaultController.php` находятся в пространстве имён. + +`TestmoduleModule.php`: + +~~~ +[php] +// задаём пространство имён: +namespace mynamespace\modules\testmodule; + +// так как класс находится в пространстве имён, обращаться к глобальному +// пространству следует явно при помощи "\": +class TestmoduleModule extends \CWebModule +{ + // задаём пространство имён для контроллеров (это же можно сделать через + // файл конфигурации) + public $controllerNamespace = '\mynamespace\modules\testmodule\controllers'; + + // обычный код модуля +} +~~~ + +`DefaultController.php`: + +~~~ +[php] +render('index'); + } +} +~~~ + +Теперь нам осталось только добавить модуль в наше приложение. Лучший способ сделать +это — использовать файл конфигурации `protected/config/main.php`: + +~~~ +[php] +// добавляем пространство имён "mynamespace" +Yii::setPathOfAlias('mynamespace', '/var/www/common/mynamespace/'); + +return array( + 'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..', + 'name'=>'My Web Application', + + 'modules'=>array( + 'testmodule' => array( + 'class' => '\mynamespace\modules\testmodule\TestModuleModule', + ), + ), ~~~ \ No newline at end of file diff --git a/docs/guide/ru/basics.view.txt b/docs/guide/ru/basics.view.txt index 9b9fc2ae3..ddfee8f7c 100644 --- a/docs/guide/ru/basics.view.txt +++ b/docs/guide/ru/basics.view.txt @@ -1,149 +1,149 @@ -Представление -============= -Представление — это PHP-скрипт, состоящий преимущественно из элементов -пользовательского интерфейса. Он может включать выражения PHP, однако рекомендуется, -чтобы эти выражения не изменяли данные и оставались относительно простыми. -Следуя концепции разделения логики и представления, большая часть кода логики -должна быть помещена в контроллер или модель, а не в скрипт представления. - -У представления есть имя, которое используется, чтобы идентифицировать файл -скрипта представления в процессе рендеринга. Имя представления должно совпадать -с названием файла представления. К примеру, для представления `edit` -соответствующий файл скрипта должен называться `edit.php`. Чтобы отобразить -представление, необходимо вызвать метод [CController::render()], указав имя -представления. При этом метод попытается найти соответствующий файл в директории -`protected/views/ControllerID`. - -Внутри скрипта представления экземпляр контроллера доступен через `$this`. -Таким образом, мы можем обратиться к свойству контроллера из кода представления: -`$this->propertyName`. - -Кроме того, мы можем использовать следующий способ для передачи данных представлению: - -~~~ -[php] -$this->render('edit', array( - 'var1'=>$value1, - 'var2'=>$value2, -)); -~~~ - -В приведённом коде метод [render()|CController::render] преобразует второй параметр — -массив — в переменные. Как результат, внутри представления будут доступны -локальные переменные `$var1` и `$var2`. - -Макет ----------- -Макет (layout) — это специальное представление для декорирования других представлений. -Макет обычно содержит части пользовательского интерфейса, общие для нескольких представлений. -Например, макет может содержать верхнюю и нижнюю части страницы, заключая между ними -содержание другого представления: - -~~~ -[php] -…здесь верхняя часть… - -…здесь нижняя… -~~~ - -Здесь `$content` хранит результат рендеринга представления. - -Макет применяется неявно при вызове метода [render()|CController::render]. -По умолчанию в качестве макета используется представление -`protected/views/layouts/main.php`. Его можно изменить путём установки значений -[CWebApplication::layout] или [CController::layout]. Для рендеринга представления -без применения макета необходимо вызвать [renderPartial()|CController::renderPartial]. - - -Виджет ------- - -Виджет (widget) — это экземпляр класса [CWidget] или унаследованного от него. -Это компонент, применяемый, в основном, с целью оформления. Виджеты обычно -встраиваются в представления для формирования некоторой сложной, но в то же время -самостоятельной части пользовательского интерфейса. К примеру, виджет календаря -может быть использован для рендеринга сложного интерфейса календаря. -Виджеты позволяют повторно использовать код пользовательского интерфейса. - -Для подключения виджета необходимо выполнить в коде: - -~~~ -[php] -beginWidget('path.to.WidgetClass'); ?> -…некое содержимое, которое может быть использовано виджетом… -endWidget(); ?> -~~~ - -или - -~~~ -[php] -widget('path.to.WidgetClass'); ?> -~~~ - -Последний вариант используется, когда виджет не имеет внутреннего содержимого. - -Изменить поведение виджета можно путём установки начальных значений его свойств при вызове -[CBaseController::beginWidget] или [CBaseController::widget]. -Например, при использовании виджета [CMaskedTextField] можно указать используемую маску, -передав массив начальных значений свойств как показано ниже, где ключи массива -являются именами свойств, а значения — начальными значениями соответствующих -им свойств виджета: - -~~~ -[php] -widget('CMaskedTextField',array( - 'mask'=>'99/99/9999' -)); -?> -~~~ - -Чтобы создать новый виджет, необходимо расширить класс [CWidget] и переопределить -его методы [init()|CWidget::init] и [run()|CWidget::run]: - -~~~ -[php] -class MyWidget extends CWidget -{ - public function init() - { - // этот метод будет вызван внутри CBaseController::beginWidget() - } - - public function run() - { - // этот метод будет вызван внутри CBaseController::endWidget() - } -} -~~~ - -Как и у контроллера, у виджета может быть собственное представление. -По умолчанию файлы представлений виджета находятся в поддиректории -`views` директории, содержащей файл класса виджета. Эти представления можно рендерить -при помощи вызова [CWidget::render()] точно так же, как и в случае с контроллером. -Единственное отличие состоит в том, что для представления виджета не используются макеты. -Также следует отметить, что `$this` в представлении указывает на экземпляр виджета, а не на экземпляр -контроллера. - -> Tip|Подсказка: свойство [CWidgetFactory::widgets] может быть использовано для -> настройки умолчаний для отдельных виджетов во всём приложении. Подробнее об -> этом можно прочитать в разделе -> «[Темы оформления, глобальная настройка виджетов](/doc/guide/topics.theming)». - -Системные представления ------------------------- -Системные представления относятся к представлениям, используемым Yii для -отображения ошибок и информации лога. Например, когда пользователь запрашивает -несуществующий контроллер или действие, Yii генерирует исключение, раскрывающее -суть ошибки. Такое исключение будет отображено с помощью системного представления. - -Именование системных представлений подчиняется некоторым правилам. -Имена типа `errorXXX` относятся к представлениям, служащим для -отображения исключений [CHttpException] с кодом ошибки `XXX`. -Например, если исключение [CHttpException] сгенерировано с кодом ошибки 404, -будет использовано представление `error404`. - -Yii предоставляет стандартный набор системных представлений, расположенных в -`framework/views`. Их можно изменить, создав файлы представлений с +Представление +============= +Представление — это PHP-скрипт, состоящий преимущественно из элементов +пользовательского интерфейса. Он может включать выражения PHP, однако рекомендуется, +чтобы эти выражения не изменяли данные и оставались относительно простыми. +Следуя концепции разделения логики и представления, большая часть кода логики +должна быть помещена в контроллер или модель, а не в скрипт представления. + +У представления есть имя, которое используется, чтобы идентифицировать файл +скрипта представления в процессе рендеринга. Имя представления должно совпадать +с названием файла представления. К примеру, для представления `edit` +соответствующий файл скрипта должен называться `edit.php`. Чтобы отобразить +представление, необходимо вызвать метод [CController::render()], указав имя +представления. При этом метод попытается найти соответствующий файл в директории +`protected/views/ControllerID`. + +Внутри скрипта представления экземпляр контроллера доступен через `$this`. +Таким образом, мы можем обратиться к свойству контроллера из кода представления: +`$this->propertyName`. + +Кроме того, мы можем использовать следующий способ для передачи данных представлению: + +~~~ +[php] +$this->render('edit', array( + 'var1'=>$value1, + 'var2'=>$value2, +)); +~~~ + +В приведённом коде метод [render()|CController::render] преобразует второй параметр — +массив — в переменные. Как результат, внутри представления будут доступны +локальные переменные `$var1` и `$var2`. + +Макет +---------- +Макет (layout) — это специальное представление для декорирования других представлений. +Макет обычно содержит части пользовательского интерфейса, общие для нескольких представлений. +Например, макет может содержать верхнюю и нижнюю части страницы, заключая между ними +содержание другого представления: + +~~~ +[php] +…здесь верхняя часть… + +…здесь нижняя… +~~~ + +Здесь `$content` хранит результат рендеринга представления. + +Макет применяется неявно при вызове метода [render()|CController::render]. +По умолчанию в качестве макета используется представление +`protected/views/layouts/main.php`. Его можно изменить путём установки значений +[CWebApplication::layout] или [CController::layout]. Для рендеринга представления +без применения макета необходимо вызвать [renderPartial()|CController::renderPartial]. + + +Виджет +------ + +Виджет (widget) — это экземпляр класса [CWidget] или унаследованного от него. +Это компонент, применяемый, в основном, с целью оформления. Виджеты обычно +встраиваются в представления для формирования некоторой сложной, но в то же время +самостоятельной части пользовательского интерфейса. К примеру, виджет календаря +может быть использован для рендеринга сложного интерфейса календаря. +Виджеты позволяют повторно использовать код пользовательского интерфейса. + +Для подключения виджета необходимо выполнить в коде: + +~~~ +[php] +beginWidget('path.to.WidgetClass'); ?> +…некое содержимое, которое может быть использовано виджетом… +endWidget(); ?> +~~~ + +или + +~~~ +[php] +widget('path.to.WidgetClass'); ?> +~~~ + +Последний вариант используется, когда виджет не имеет внутреннего содержимого. + +Изменить поведение виджета можно путём установки начальных значений его свойств при вызове +[CBaseController::beginWidget] или [CBaseController::widget]. +Например, при использовании виджета [CMaskedTextField] можно указать используемую маску, +передав массив начальных значений свойств как показано ниже, где ключи массива +являются именами свойств, а значения — начальными значениями соответствующих +им свойств виджета: + +~~~ +[php] +widget('CMaskedTextField',array( + 'mask'=>'99/99/9999' +)); +?> +~~~ + +Чтобы создать новый виджет, необходимо расширить класс [CWidget] и переопределить +его методы [init()|CWidget::init] и [run()|CWidget::run]: + +~~~ +[php] +class MyWidget extends CWidget +{ + public function init() + { + // этот метод будет вызван внутри CBaseController::beginWidget() + } + + public function run() + { + // этот метод будет вызван внутри CBaseController::endWidget() + } +} +~~~ + +Как и у контроллера, у виджета может быть собственное представление. +По умолчанию файлы представлений виджета находятся в поддиректории +`views` директории, содержащей файл класса виджета. Эти представления можно рендерить +при помощи вызова [CWidget::render()] точно так же, как и в случае с контроллером. +Единственное отличие состоит в том, что для представления виджета не используются макеты. +Также следует отметить, что `$this` в представлении указывает на экземпляр виджета, а не на экземпляр +контроллера. + +> Tip|Подсказка: свойство [CWidgetFactory::widgets] может быть использовано для +> настройки умолчаний для отдельных виджетов во всём приложении. Подробнее об +> этом можно прочитать в разделе +> «[Темы оформления, глобальная настройка виджетов](/doc/guide/topics.theming)». + +Системные представления +------------------------ +Системные представления относятся к представлениям, используемым Yii для +отображения ошибок и информации лога. Например, когда пользователь запрашивает +несуществующий контроллер или действие, Yii генерирует исключение, раскрывающее +суть ошибки. Такое исключение будет отображено с помощью системного представления. + +Именование системных представлений подчиняется некоторым правилам. +Имена типа `errorXXX` относятся к представлениям, служащим для +отображения исключений [CHttpException] с кодом ошибки `XXX`. +Например, если исключение [CHttpException] сгенерировано с кодом ошибки 404, +будет использовано представление `error404`. + +Yii предоставляет стандартный набор системных представлений, расположенных в +`framework/views`. Их можно изменить, создав файлы представлений с теми же названиями в директории `protected/views/system`. \ No newline at end of file diff --git a/docs/guide/ru/changes.txt b/docs/guide/ru/changes.txt index 7c6bf5c43..87c07874c 100644 --- a/docs/guide/ru/changes.txt +++ b/docs/guide/ru/changes.txt @@ -1,99 +1,99 @@ -Новые возможности -================= - -На этой странице кратко излагаются новые возможности, внесённые в каждом релизе Yii. - -Версия 1.1.14 -------------- - -* Добавлен [CPasswordHelper] -* Добавлен [CRedisCache] - - -Версия 1.1.11 -------------- - - * [Добавлена поддержка HTTP-кеширования](/doc/guide/caching.page#http-caching) - * [Добавлена поддержка кодов возврата в консольных приложениях](/doc/guide/topics.console#exit-codes) - * [Добавлена возможность исключения отдельных правил валидации](/doc/guide/form.model#declaring-validation-rules) - * [Добавлена поддержка генерации файлов для git и hg](/doc/guide/quickstart.first-app#creating-your-first-yii-application) - -Версия 1.1.8 -------------- - * [Добавлена возможность использовать свой класс правила URL](/doc/guide/topics.url#using-custom-url-rule-classes) - -Версия 1.1.7 ------------- - * [Добавлена поддержка URL в стиле REST](/doc/guide/topics.url#user-friendly-urls) - * [Добавлена поддержка кэширования запросов](/doc/guide/caching.data#query-caching) - * [Теперь возможно передавать параметры именованным группам условий связи](/doc/guide/database.arr#relational-query-with-named-scopes) - * [Добавлена возможность выполнения реляционных запросов без получения данных из связанных моделей](/doc/guide/database.arr#performing-relational-query-without-getting-related-models) - * [В AR добавлена поддержка связей HAS_MANY through и HAS_ONE through](/doc/guide/database.arr#relational-query-with-through) - * [В миграции добавлена поддержка транзакций](/doc/guide/database.migration#transactional-migrations) - * [Теперь возможно использовать привязку параметров с отдельными классами действий](/doc/guide/basics.controller#action-parameter-binding) - * Добавлена поддержка валидации на клиенте без AJAX с использованием [CActiveForm] - -Версия 1.1.6 ------------- - * [Добавлен конструктор запросов](/doc/guide/database.query-builder) - * [Добавлены миграции](/doc/guide/database.migration) - * [Лучшие практики MVC](/doc/guide/basics.best-practices) - * [Консольным командам добавлена поддержка анонимных параметров и глобальных опций](/doc/guide/topics.console) - -Версия 1.1.5 -------------- - - * [Добавлена поддержка действий и параметров действий в консольных командах](/doc/guide/topics.console) - * [Добавлена поддержка загрузки классов из пространств имён](/doc/guide/basics.namespace) - * [Добавлена поддержка темизации виджетов](/doc/guide/topics.theming#theming-widget-views) - -Версия 1.1.4 ------------- - - * [Добавлена поддержка автоматической привязки параметров действий контроллера](/doc/guide/basics.controller#action-parameters) - -Версия 1.1.3 -------------- - - * [Добавлена возможность настройки виджета через файл конфигурации приложения](/doc/guide/topics.theming#customizing-widgets-globally) - -Версия 1.1.2 -------------- - - * [Добавлен веб-кодогенератор Gii](/doc/guide/topics.gii) - -Версия 1.1.1 ------------- - - * Добавлен виджет CActiveForm, упрощающий написание кода формы и поддерживающий - прозрачную валидацию как на стороне клиента, так и на стороне сервера. - - * Произведён рефакторинг кода, генерируемого yiic. Приложение-каркас теперь - генерируется с поддержкой нескольких главных разметок, использован виджет меню, - добавлена возможность сортировать данные в административном интерфейсе, для - отображения форм используется CActiveForm. - - * [Добавлена поддержка глобальных консольных команд](/doc/guide/topics.console). - -Версия 1.1.0 ------------- - - * [Добавлена возможность использования модульного и функционального тестирования](/doc/guide/test.overview). - - * [Добавлена возможность использования скинов виджета](/doc/guide/topics.theming#skin). - - * [Добавлен гибкий инструмент для построения форм](/doc/guide/form.builder). - - * Улучшен способ объявления безопасных атрибутов модели: - - [Безопасное присваивание значений атрибутам](/doc/guide/form.model#securing-attribute-assignments). - - * Изменён алгоритм жадной загрузки по умолчанию для зависимых запросов AR так, - что все таблицы объединяются в одном SQL-запросе. - - * Изменён псевдоним таблицы по умолчанию на имя связи AR. - - * [Добавлена поддержка использования префикса таблиц](/doc/guide/database.dao#using-table-prefix). - - * Добавлен набор новых расширений — [библиотека Zii](http://code.google.com/p/zii/). - +Новые возможности +================= + +На этой странице кратко излагаются новые возможности, внесённые в каждом релизе Yii. + +Версия 1.1.14 +------------- + +* Добавлен [CPasswordHelper] +* Добавлен [CRedisCache] + + +Версия 1.1.11 +------------- + + * [Добавлена поддержка HTTP-кеширования](/doc/guide/caching.page#http-caching) + * [Добавлена поддержка кодов возврата в консольных приложениях](/doc/guide/topics.console#exit-codes) + * [Добавлена возможность исключения отдельных правил валидации](/doc/guide/form.model#declaring-validation-rules) + * [Добавлена поддержка генерации файлов для git и hg](/doc/guide/quickstart.first-app#creating-your-first-yii-application) + +Версия 1.1.8 +------------- + * [Добавлена возможность использовать свой класс правила URL](/doc/guide/topics.url#using-custom-url-rule-classes) + +Версия 1.1.7 +------------ + * [Добавлена поддержка URL в стиле REST](/doc/guide/topics.url#user-friendly-urls) + * [Добавлена поддержка кэширования запросов](/doc/guide/caching.data#query-caching) + * [Теперь возможно передавать параметры именованным группам условий связи](/doc/guide/database.arr#relational-query-with-named-scopes) + * [Добавлена возможность выполнения реляционных запросов без получения данных из связанных моделей](/doc/guide/database.arr#performing-relational-query-without-getting-related-models) + * [В AR добавлена поддержка связей HAS_MANY through и HAS_ONE through](/doc/guide/database.arr#relational-query-with-through) + * [В миграции добавлена поддержка транзакций](/doc/guide/database.migration#transactional-migrations) + * [Теперь возможно использовать привязку параметров с отдельными классами действий](/doc/guide/basics.controller#action-parameter-binding) + * Добавлена поддержка валидации на клиенте без AJAX с использованием [CActiveForm] + +Версия 1.1.6 +------------ + * [Добавлен конструктор запросов](/doc/guide/database.query-builder) + * [Добавлены миграции](/doc/guide/database.migration) + * [Лучшие практики MVC](/doc/guide/basics.best-practices) + * [Консольным командам добавлена поддержка анонимных параметров и глобальных опций](/doc/guide/topics.console) + +Версия 1.1.5 +------------- + + * [Добавлена поддержка действий и параметров действий в консольных командах](/doc/guide/topics.console) + * [Добавлена поддержка загрузки классов из пространств имён](/doc/guide/basics.namespace) + * [Добавлена поддержка темизации виджетов](/doc/guide/topics.theming#theming-widget-views) + +Версия 1.1.4 +------------ + + * [Добавлена поддержка автоматической привязки параметров действий контроллера](/doc/guide/basics.controller#action-parameters) + +Версия 1.1.3 +------------- + + * [Добавлена возможность настройки виджета через файл конфигурации приложения](/doc/guide/topics.theming#customizing-widgets-globally) + +Версия 1.1.2 +------------- + + * [Добавлен веб-кодогенератор Gii](/doc/guide/topics.gii) + +Версия 1.1.1 +------------ + + * Добавлен виджет CActiveForm, упрощающий написание кода формы и поддерживающий + прозрачную валидацию как на стороне клиента, так и на стороне сервера. + + * Произведён рефакторинг кода, генерируемого yiic. Приложение-каркас теперь + генерируется с поддержкой нескольких главных разметок, использован виджет меню, + добавлена возможность сортировать данные в административном интерфейсе, для + отображения форм используется CActiveForm. + + * [Добавлена поддержка глобальных консольных команд](/doc/guide/topics.console). + +Версия 1.1.0 +------------ + + * [Добавлена возможность использования модульного и функционального тестирования](/doc/guide/test.overview). + + * [Добавлена возможность использования скинов виджета](/doc/guide/topics.theming#skin). + + * [Добавлен гибкий инструмент для построения форм](/doc/guide/form.builder). + + * Улучшен способ объявления безопасных атрибутов модели: + - [Безопасное присваивание значений атрибутам](/doc/guide/form.model#securing-attribute-assignments). + + * Изменён алгоритм жадной загрузки по умолчанию для зависимых запросов AR так, + что все таблицы объединяются в одном SQL-запросе. + + * Изменён псевдоним таблицы по умолчанию на имя связи AR. + + * [Добавлена поддержка использования префикса таблиц](/doc/guide/database.dao#using-table-prefix). + + * Добавлен набор новых расширений — [библиотека Zii](http://code.google.com/p/zii/). + * Псевдоним для главной таблицы в AR запросе теперь всегда равен 't'. \ No newline at end of file diff --git a/docs/guide/ru/database.arr.txt b/docs/guide/ru/database.arr.txt index 53ca86eef..bea54919d 100644 --- a/docs/guide/ru/database.arr.txt +++ b/docs/guide/ru/database.arr.txt @@ -1,760 +1,760 @@ -Реляционная Active Record -========================= - -Мы уже рассмотрели использование Active Record (AR) для выбора данных из одной таблицы базы данных. -В этом разделе мы расскажем, как использовать AR для соединения нескольких связанных таблиц и получения -набора связанных данных. - -Перед использованием реляционной AR рекомендуется установить ограничения внешних ключей для таблиц базы данных. -Это позволит обеспечить непротиворечивость и целостность хранимых данных. - -Для наглядности примеров в этом разделе мы будем использовать схему базы данных, -представленную на следующей диаграмме сущность-связь (ER). - -![Диаграмма ER](er.png) - -> Info|Информация: Поддержка ограничений внешних ключей различается в разных СУБД. -> SQLite 3.6.19 и более ранние версии не поддерживает ограничений, но вы, тем не менее, можете их объявить -> при создании таблиц. Движок MySQL MyISAM не поддерживает внешние ключи. - -Установка связей между AR-классами ----------------- - -Перед тем как использовать AR для выполнения реляционных запросов, нам необходимо установить связи между AR-классами. - -Связь между двумя AR-классами напрямую зависит от связей между соответствующими таблицами базы данных. С точки -зрения БД, связь между таблицами A и В может быть трёх типов: один-ко-многим (например, `tbl_user` и `tbl_post`), один-к-одному -(например, `tbl_user` и `tbl_profile`) и многие-ко-многим (например, `tbl_category` и `tbl_post`). В AR существует четыре типа связей: - - - `BELONGS_TO`: если связь между А и В один-ко-многим, значит В принадлежит А (например, `Post` принадлежит `User`); - - - `HAS_MANY`: если связь между таблицами А и В один-ко-многим, значит у А есть много В (например, у `User` есть много `Post`); - - - `HAS_ONE`: это частный случай `HAS_MANY`, где А может иметь максимум одно В (например, у `User` есть только один `Profile`); - - - `MANY_MANY`: эта связь соответствует типу связи многие-ко-многим в БД. Поскольку многие СУБД не поддерживают непосредственно -этот тип связи, требуется ассоциативная таблица для преобразования связи многие-ко-многим в связи один-ко-многим. -В нашей схеме базы данных этой цели служит таблица `tbl_post_category`. В терминологии AR связь `MANY_MANY` можно описать как -комбинацию `BELONGS_TO` и `HAS_MANY`. Например, `Post` принадлежит многим `Category`, а у `Category` есть много `Post`. - -Существует пятый, специальный тип связи, который предназначен для статистических запросов над связанными записями -(запросы агрегирования) — называется он `STAT`. Более подробно с ним можно ознакомиться в разделе -[Статистический запрос](/doc/guide/database.arr#statistical-query). - -Установка связей производится внутри метода [relations()|CActiveRecord::relations] класса [CActiveRecord]. -Этот метод возвращает массив с конфигурацией связей. Каждый элемент массива представляет одну связь в следующем формате: - -~~~ -[php] -'VarName'=>array('RelationType', 'ClassName', 'ForeignKey', …дополнительные параметры) -~~~ - -где `VarName` — имя связи, `RelationType` указывает на один из четырёх типов связей, -`ClassName` — имя AR-класса, связанного с данным классом, а -`ForeignKey` обозначает один или несколько внешних ключей, используемых для связи. -Кроме того, можно указать ряд дополнительных параметров, о которых будет рассказано позже. - -В приведённом ниже коде показано, как установить связь между классами `User` и `Post`. - -~~~ -[php] -class Post extends CActiveRecord -{ - … - public function relations() - { - return array( - 'author'=>array(self::BELONGS_TO, 'User', 'author_id'), - 'categories'=>array(self::MANY_MANY, 'Category', - 'tbl_post_category(post_id, category_id)'), - ); - } -} - -class User extends CActiveRecord -{ - … - public function relations() - { - return array( - 'posts'=>array(self::HAS_MANY, 'Post', 'author_id'), - 'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'), - ); - } -} -~~~ - -> Info|Информация: Внешний ключ может быть составным, то есть состоять из двух и более столбцов таблицы. В этом случае -имена столбцов следует разделить запятыми и передать их либо в качестве строки, либо в виде массива `array('key1','key2')`. -Задать свою связь первичного ключа с внешним можно в виде массива `array('fk'=>'pk')`. Для составных -ключей это будет `array('fk_c1'=>'pk_c1','fk_c2'=>'pk_c2')`. Для типа связи `MANY_MANY` имя ассоциативной таблицы также должно быть -указано во внешнем ключе. Например, связи `categories` модели `Post` соответствует внешний ключ `tbl_post_category(post_id, category_id)`. -Для каждой добавленной связи неявно создаётся свойство класса. После выполнения реляционного запроса -соответствующее свойство будет содержать связанный экземпляр класса AR (или массив экземпляров для связей типа один-ко-многим и -многие-ко-многим). Например, если `$author` является экземпляром AR-класса `User`, то можно использовать `$author->posts` -для доступа к связанным экземплярам `Post`. - -Выполнение реляционного запроса -------------------------------- - -Самый простой способ выполнить реляционный запрос — использовать реляционное свойство AR-класса. -Если обращение к этому свойству производится впервые, то будет выполнен реляционный запрос, который соединит связанные таблицы -и оставит только данные, соответствующие первичному ключу текущего экземпляра AR. -Результат запроса будет сохранён в свойстве как экземпляр (или массив экземпляров) связанного класса. -Этот подход также известен как «отложенная загрузка» (lazy loading), при которой непосредственный запрос выполняется -только в момент первого обращения к связанным объектам. Ниже приведён пример использования этого подхода: - -~~~ -[php] -// получаем запись с ID=10 -$post=Post::model()->findByPk(10); -// Получаем автора записи. Здесь будет выполнен реляционный запрос. -$author=$post->author; -~~~ - -> Info|Информация: Если связанные данные не найдены, то соответствующее -свойство примет значение null для связей `BELONGS_TO` и `HAS_ONE` или будет являться пустым массивом -для `HAS_MANY` и `MANY_MANY`. -Стоит отметить, что связи `HAS_MANY` и `MANY_MANY` возвращают -массивы объектов, и обращаться к их свойствам необходимо в цикле, иначе -можно получить ошибку «Trying to get property of non-object». - -Способ отложенной загрузки удобен, но не всегда эффективен. Например, если нам потребуется -получить информацию об авторах `N` записей, то использование отложенной загрузки -потребует выполнения `N` дополнительных запросов к базе данных. -В данной ситуации разумно использовать метод «жадной загрузки» (eager loading). - -Этот метод заключается в загрузке всех связанных данных вместе с -основным экземпляром AR. Реализуется этот подход путем использования метода - [with()|CActiveRecord::with] вместе с методом [find|CActiveRecord::find] или - [findAll|CActiveRecord::findAll]. -Например: -~~~ -[php] -$posts=Post::model()->with('author')->findAll(); -~~~ - -Приведённый код вернёт массив экземпляров `Post`. В отличие от отложенной загрузки, свойство `author` каждой записи будет -заполнено связанным экземпляром `User` ещё до обращения к этому свойству. Таким образом, вместо выполнения отдельного запроса -для каждой записи, жадная загрузка получит все записи вместе с их авторами в одном запросе! - -В методе [with()|CActiveRecord::with] можно указать несколько связей, и жадная загрузка вернёт их за один раз. -Например, следующий код вернёт записи вместе с их авторами и категориями: - -~~~ -[php] -$posts=Post::model()->with('author','categories')->findAll(); -~~~ - -Кроме того, можно осуществлять вложенную жадную загрузку. Для этого вместо простого списка имён связей, мы передаем методу -[with()|CActiveRecord::with] имена связей, упорядоченных иерархически, как в следующем примере: - -~~~ -[php] -$posts=Post::model()->with( - 'author.profile', - 'author.posts', - 'categories')->findAll(); -~~~ - -Пример выше вернёт нам все записи вместе с их авторами и категориями, а также профиль каждого автора и все его записи. - -Жадная загрузка может быть выполнена путём указания свойства [CDbCriteria::with]: - -~~~ -[php] -$criteria=new CDbCriteria; -$criteria->with=array( - 'author.profile', - 'author.posts', - 'categories', -); -$posts=Post::model()->findAll($criteria); -~~~ - -или - -~~~ -[php] -$posts=Post::model()->findAll(array( - 'with'=>array( - 'author.profile', - 'author.posts', - 'categories', - ) -)); -~~~ - - -Реляционный запрос без получения связанных моделей --------------------------------------------------- - -Иногда требуется выполнить запрос с использованием связи, но при этом -не требуются данные из связанной модели. Допустим, есть пользователи (`User`), -которые публикуют множество записей (`Post`). Запись может быть опубликована, -а может быть черновиком. Этот факт определяется значением поля `published` модели `Post`. -Пусть нам необходимо получить всех пользователей, которые опубликовали хотя бы одну -запись, при этом сами записи нам не интересны. Сделать это можно следующим образом: - -~~~ -[php] -$users=User::model()->with(array( - 'posts'=>array( - // записи нам не нужны - 'select'=>false, - // но нужно выбрать только пользователей с опубликованными записями - 'joinType'=>'INNER JOIN', - 'condition'=>'posts.published=1', - ), -))->findAll(); -~~~ - - -Параметры реляционного запроса ------------------------------- - -Выше мы упоминали о том, что в реляционном запросе можно указать дополнительные параметры. -Эти параметры — пары имя-значение — используются для тонкой настройки реляционного запроса. -Список параметров представлен ниже. - - - `select`: список выбираемых полей для связанного AR-класса. По умолчанию значение параметра равно '*', -что соответствует всем полям таблицы. Для используемых столбцов должны быть разрешены конфликты имён. - - - `condition`: соответствует SQL оператору `WHERE`, по умолчанию значение параметра пустое. -Для используемых столбцов должны быть разрешены конфликты имён. - - - `params`: параметры для связывания в генерируемом SQL-выражении. Параметры передаются как массив пар имя-значение. - - - `on`: соответствует SQL оператору `ON`. Условие, указанное в этом параметре, -будет добавлено к основному условию соединения при помощи SQL оператора `AND`. Для используемых столбцов должны быть разрешены конфликты имён. -Данный параметр неприменим для связей типа `MANY_MANY`. - - - `order`: соответствует SQL оператору `ORDER BY`, по умолчанию значение параметра пустое. -Для используемых столбцов должны быть разрешены конфликты имён. - - - `with`: список дочерних связанных объектов, которые должны быть загружены с самим объектом. -Неправильное использование данной возможности может привести к бесконечному циклу. - - - `joinType`: тип соединения таблиц. По умолчанию значение параметра равно `LEFT -OUTER JOIN`; - - - `alias`: псевдоним таблицы, ассоциированной со связью. По умолчанию значение параметра -равняется null, что означает, что псевдоним соответствует имени связи. - - - `together`: параметр, устанавливающий необходимость принудительного соединения таблицы, ассоциированной с этой связью, -с другими таблицами. Этот параметр имеет смысл только для связей типов `HAS_MANY` и `MANY_MANY`. Если параметр не установлен или -равен false, тогда каждая связь `HAS_MANY` или `MANY_MANY` будет использовать отдельный SQL-запрос для связанных данных, -что может улучшить скорость выполнения запроса, т.к. уменьшается количество выбираемых данных. -Если параметр равен `true`, то зависимая таблица при выполнении запроса всегда будет -соединяться с основной, то есть будет выполнен один SQL-запрос даже в том случае, если -к основной таблице применяется постраничная разбивка. Если данный параметр не -задан, зависимая таблица будет соединена с основной только в случае, когда -к основной таблице не применяется постраничная разбивка. Более подробное описание -можно найти в разделе «производительность реляционного запроса». - - - `join`: дополнительный оператор `JOIN`. По умолчанию пуст. Этот параметр -доступен с версии 1.1.3. - - - `group`: соответствует SQL оператору `GROUP BY`, по умолчанию значение параметра пустое. -Для используемых столбцов должны быть разрешены конфликты имён. - - - `having`: соответствует SQL оператору `HAVING`, по умолчанию значение параметра пустое. -Для используемых столбцов должны быть разрешены конфликты имён. - - - `index`: имя столбца таблицы, значения которого должны быть использованы в -качестве ключей массива, хранящего связанные объекты. Без установки этого -параметра массив связанных объектов использует целочисленный индекс, -начинающийся с нуля. Параметр может быть установлен только для связей типа -`HAS_MANY` и `MANY_MANY`. - - - `scopes`: группы условий, которые необходимо применить. В случае одной группы -может задаваться в виде строки `'scopes'=>'scopeName'`. Если же групп несколько, то -их необходимо перечислить в массиве `'scopes'=>array('scopeName1','scopeName2')`. Этот параметр -доступен с версии 1.1.9. - -Кроме того, для отложенной загрузки некоторых типов связей доступен ряд дополнительных параметров: - - - `limit`: параметр для ограничения количества строк в выборке. Параметр неприменим для связей `BELONGS_TO`; - - - `offset`: параметр для указания начальной строки выборки. Параметр неприменим для связей `BELONGS_TO`. - - - `through`: имя связи модели, которое при получении данных будет - использоваться как мост. Параметр может быть установлен только для связей - `HAS_ONE` и `HAS_MANY`. Этот параметр доступен с версии 1.1.7, в которой можно применять его к `HAS_ONE` и `HAS_MANY`. - Начиная с версии 1.1.14 он может использоваться с `BELONGS_TO`. - -Ниже мы изменим определение связи `posts` в модели `User`, добавив несколько вышеприведенных параметров: - -~~~ -[php] -class User extends CActiveRecord -{ - public function relations() - { - return array( - 'posts'=>array(self::HAS_MANY, 'Post', 'author_id', - 'order'=>'posts.create_time DESC', - 'with'=>'categories'), - 'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'), - ); - } -} -~~~ - -Теперь при обращении к `$author->posts`, мы получим записи автора, отсортированные -в обратном порядке по времени их создания. Для каждой записи будут загружены -её категории. - -Устранение конфликта имён столбцов ----------------------------------- - -При совпадении имён столбцов в двух и более соединяемых таблицах, -приходится разрешать конфликт имён. Это делается при помощи добавления -псевдонима таблицы к имени столбца. - -В реляционном запросе псевдоним главной таблицы всегда равен `t`, -а псевдоним связанной таблицы по умолчанию равен имени связи. -В приведённом ниже коде псевдонимы таблиц для моделей `Post` и `Comment` будут соответственно -`t` и `comments`: - -~~~ -[php] -$posts=Post::model()->with('comments')->findAll(); -~~~ - -Допустим, что и в `Post`, и в `Comment` есть столбец `create_time`, в котором -хранится время создания записи или комментария, и нам необходимо получить записи -вместе с комментариями к ним, отсортированные сначала по времени создания -записи, а затем по времени написания комментария. Для этого нам понадобится -устранить конфликт столбцов `create_time` следующим образом: - -~~~ -[php] -$posts=Post::model()->with('comments')->findAll(array( - 'order'=>'t.create_time, comments.create_time' -)); -~~~ - -> Tip|Подсказка: Псевдоним таблицы связи по умолчанию равен названию самой связи. Имейте ввиду, -> что при использовании одной связи внутри другой будет использовано название последней из них. -> При этом название родительской связи не будет использовано в качестве префикса. Например, псевдонимом -> связи 'author.group' является 'group', а не 'author.group'. -> -> ~~~ -> [php] -> $posts=Post::model()->with('author', 'author.group')->findAll(array( -> 'order'=>'group.name, author.name, t.title' -> )); -> ~~~ -> -> Вы можете избежать конфликта псевдонимов таблиц задав свойство связи [alias|CActiveRelation::alias]. -> -> ~~~ -> [php] -> $comments=Comment::model()->with( -> 'author', -> 'post', -> 'post.author'=>array('alias'=>'p_author'))->findAll(array( -> 'order'=>'author.name, p_author.name, post.title' -> )); -> ~~~ - -Динамические параметры реляционного запроса -------------------------------------------- - -Мы можем использовать динамические параметры как для метода -[with()|CActiveRecord::with], так и для параметра `with`. Динамические параметры переопределяют существующие -параметры в соответствии с описанием метода [relations()|CActiveRecord::relations]. К примеру, если для модели `User`, приведённой выше, -мы хотим воспользоваться жадной загрузкой для получения записей автора в порядке возрастания (параметр `order` в определении связи -задает убывающий порядок), можно сделать это следующим образом: - -~~~ -[php] -User::model()->with(array( - 'posts'=>array('order'=>'posts.create_time ASC'), - 'profile', -))->findAll(); -~~~ - -Динамические параметры в реляционных запросах можно использовать вместе с -отложенной загрузкой. Для этого необходимо вызвать метод с тем же именем, что и -имя связи, и передать параметры в качестве аргументов. К примеру, следующий код -вернёт публикации пользователя, у которых `status` равен 1: - -~~~ -[php] -$user=User::model()->findByPk(1); -$posts=$user->posts(array('condition'=>'status=1')); -~~~ - -Производительность реляционного запроса ---------------------------------------- - -Как было сказано выше, жадная загрузка используется, главным образом, когда -требуется получить множество связанных объектов. В этом случае при соединении -всех таблиц генерируется сложный SQL-запрос. Такой запрос во многих случаях -является предпочтительным, т.к. упрощает фильтрацию по значению столбца связанной таблицы. -Тем не менее, в некоторых случаях такие запросы не являются эффективными. - -Рассмотрим пример, в котором нам необходимо найти последние добавленные записи вместе с их комментариями. -Предположив, что у каждой записи 10 комментариев, при использовании одного большого SQL-запроса -мы получим множество лишних данных, так как каждая запись будет повторно выбираться с каждым -её комментарием. Теперь попробуем по-другому: сначала выберем последние записи, а затем комментарии к ним. -В этом случае нам необходимо выполнить два SQL-запроса. Плюс в том, что полученные данные -не будут содержать дублирующую информацию. - -Какой же подход более эффективен? Однозначного ответа на этот вопрос нет. -Выполнение одного большого SQL-запроса может оказаться более эффективным, так как СУБД -не приходится лишний раз разбирать и выполнять дополнительные запросы. -С другой стороны, используя один SQL-запрос, мы получаем лишние данные, а значит нам требуется больше времени на их передачу и обработку. -По умолчанию Yii использует жадную загрузку, то есть генерирует один SQL-запрос -за исключением случая, когда к главной модели применяется `LIMIT`. Если выставить -опцию `together` в описании связи в `true`, то мы получим единственный SQL-запрос -даже если используется `LIMIT`. Если использовать `false`, то выборка из -некоторых таблиц будет производиться отдельными запросами. -Например, для того чтобы использовать отдельные SQL-запросы для выборки -последних записей и комментариев к ним, связь `comments` модели `Post` следует -описать следующим образом: - -~~~ -[php] -public function relations() -{ - return array( - 'comments' => array(self::HAS_MANY, 'Comment', 'post_id', 'together'=>false), - ); -} -~~~ - -Для жадной загрузки мы можем задать этот параметр динамически: - -~~~ -[php] -$posts = Post::model()->with(array('comments'=>array('together'=>false)))->findAll(); -~~~ - -Статистический запрос ---------------------- - -Помимо реляционных запросов, описанных выше, Yii также поддерживает так называемые статистические запросы (или запросы агрегирования). -Этот тип запросов используется для получения агрегированных данных, относящихся к связанным объектам (количество комментариев -к каждой записи, средний рейтинг для каждого наименования продукции и т.д.). -Статистические запросы могут быть использованы только для связей типа `HAS_MANY` (например, у записи есть много -комментариев) или `MANY_MANY` (например, запись принадлежит многим категориям, а категориия может относиться ко множеству записей). - -Выполнение статистического запроса аналогично выполнению реляционного запроса. Первым делом необходимо -объявить статистический запрос в методе [relations()|CActiveRecord::relations] класса [CActiveRecord]. - -~~~ -[php] -class Post extends CActiveRecord -{ - public function relations() - { - return array( - 'commentCount'=>array(self::STAT, 'Comment', 'post_id'), - 'categoryCount'=>array(self::STAT, 'Category', 'post_category(post_id, category_id)'), - ); - } -} -~~~ - -Выше мы объявили два статистических запроса: `commentCount` подсчитывает количество комментариев к записи, а `categoryCount` -считает количество категорий, к которым относится запись. Обратите внимание, что связь между `Post` и `Comment` — типа `HAS_MANY`, а -связь между `Post` и `Category` — типа `MANY_MANY` (с использованием преобразующей таблицы `post_category`). Как можно видеть, -способ объявления похож на объявление связей, описанных выше. Единственное различие состоит в том, что в данном случае тип связи -равен `STAT`. - -За счёт объявленных связей мы можем получить количество комментариев к записи, используя выражение `$post->commentCount`. -В момент первого обращения к данному свойству для получения соответствующего результата неявным образом выполняется SQL-запрос. -Как мы уже говорили, это называется подходом *отложенной загрузки*. Можно также использовать *жадный* вариант загрузки, если необходимо -получить количество комментариев к нескольким записям: - -~~~ -[php] -$posts=Post::model()->with('commentCount', 'categoryCount')->findAll(); -~~~ - -Выражение выше выполняет три SQL-запроса для получения всех записей вместе с количеством комментариев к ним и числом категорий. -В случае отложенной загрузки нам бы понадобилось выполнить `2*N+1` SQL-запросов для `N` записей. - -По умолчанию статистический запрос считает количество с использованием выражения `COUNT`. -Его можно уточнить путём указания дополнительных параметров в момент объявления в методе [relations()|CActiveRecord::relations]. -Доступные параметры перечислены ниже: - - - `select`: статистическое выражение, по умолчанию равно `COUNT(*)`, что соответствует количеству связанных объектов; - - - `defaultValue`: значение, которое присваивается в случае, если результат статистического запроса пуст. -Например, если запись не имеет ни одного комментария, то свойству `commentCount` будет присвоено это значение. По умолчанию значение -данного параметра равно 0; - - - `condition`: соответствует SQL оператору `WHERE`, по умолчанию значение параметра пустое; - - - `params`: параметры для связывания в генерируемом SQL-выражении. Параметры передаются - как массив пар имя-значение; - - - `order`: соответствует SQL оператору `ORDER BY`, по умолчанию значение параметра пустое; - - - `group`: соответствует SQL оператору `GROUP BY`, по умолчанию значение параметра пустое; - - - `having`: соответствует SQL оператору `HAVING`, по умолчанию значение параметра пустое. - -Реляционные запросы с именованными группами условий ---------------------------------------------------- - -В реляционном запросе [именованные группы условий](/doc/guide/database.ar#named-scopes) -могут быть использованы двумя способами. Их можно применить к основной модели и -к связанным моделям. - -Следующий код иллюстрирует случай их применения к основной модели: - -~~~ -[php] -$posts=Post::model()->published()->recently()->with('comments')->findAll(); -~~~ - -Данный код очень похож на нереляционный запрос. Единственное отличие состоит в том, что -присутствует вызов `with()` после вызовов групп условий. Данный запрос -вернёт недавно опубликованные записи вместе с комментариями к ним. - -В следующем примере показано, как применить группы условий к связанным моделям: - -~~~ -[php] -$posts=Post::model()->with('comments:recently:approved')->findAll(); -// или, начиная с версии 1.1.7 -$posts=Post::model()->with(array( - 'comments'=>array( - 'scopes'=>array('recently','approved') - ), -))->findAll(); -// или, начиная с версии 1.1.7 -$posts=Post::model()->findAll(array( - 'with'=>array( - 'comments'=>array( - 'scopes'=>array('recently','approved') - ), - ), -)); -~~~ - -Этот запрос вернёт все записи вместе с одобренными комментариями. Здесь `comments` -соответствует имени связи. `recently` и `approved` — именованные группы, описанные -в модели `Comment`. Имя связи и группы условий разделяются двоеточием. - -Вам может понадобится использовать вместо «жадной» выборки «отложенную» для -связи с группой условий. Синтаксис для этого такой: - -~~~ -[php] -// имя связи comments повторяется два раза -$approvedComments = $post->comments('comments:approved'); -~~~ - - -Именованные группы могут быть использованы при описании связей модели в -методе [CActiveRecord::relations()] в параметре `with`. В следующем примере -при обращении к `$user->posts` вместе с публикациями будут получены все -*одобренные* комментарии. - -~~~ -[php] -class User extends CActiveRecord -{ - public function relations() - { - return array( - 'posts'=>array(self::HAS_MANY, 'Post', 'author_id', - 'with'=>'comments:approved'), - ); - } -} -// или, начиная с версии 1.1.7 -class User extends CActiveRecord -{ - public function relations() - { - return array( - 'posts'=>array(self::HAS_MANY, 'Post', 'author_id', - 'with'=>array( - 'comments'=>array( - 'scopes'=>'approved' - ), - ), - ), - ); - } -} -~~~ - - -В версии 1.1.7 появилась возможность передавать параметры именованным группам условий -связи. К примеру, если в `Post` есть именованная группа условий `rated`, -принимающая минимальный рейтинг записи, использовать её в `User` можно следующим образом: - - -> Note|Примечание: до версии 1.1.7 именованные группы условий, применяемые к реляционным моделям, -> должны быть описаны в CActiveRecord::scopes. Поэтому они не могут быть параметризованы. - - -~~~ -[php] -$users=User::model()->findAll(array( - 'with'=>array( - 'posts'=>array( - 'scopes'=>array( - 'rated'=>5, - ), - ), - ), -)); - -class Post extends CActiveRecord -{ - ...... - - public function rated($rating) - { - $this->getDbCriteria()->mergeWith(array( - 'condition'=>'rating=:rating', - 'params'=>array(':rating'=>$rating), - )); - return $this; - } - - ...... -} -~~~ - -Реляционные запросы с through ------------------------------ - -При использовании `through` определение связи должно выглядеть следующим образом: - -~~~ -[php] -'comments'=>array(self::HAS_MANY,'Comment',array('key1'=>'key2'),'through'=>'posts'), -~~~ - -В коде выше, а именно в `array('key1'=>'key2')`: - - - `key1` — ключ, определённый в связи, на которую указывает `through` (в нашем случае `posts`). - - `key2` — ключ, определённый в модели, на которую указывает связь (в нашем случае `Comment`). - -`through` может использоваться с `HAS_ONE`, `BELONGS_TO` и `HAS_MANY`. - -### `HAS_MANY` through - -![HAS_MANY through ER](has_many_through.png) - -Пример использования `HAS_MANY` с `through` — получение пользователей, состоящих -в определённой группе, если они записаны в группу через роли. - -Более сложным примером является получение всех комментариев для всех пользователей -определённой группы. В этом случае необходимо использовать несколько связей -с `through` в одной модели: - -~~~ -[php] -class Group extends CActiveRecord -{ - ... - public function relations() - { - return array( - 'roles'=>array(self::HAS_MANY,'Role','group_id'), - 'users'=>array(self::HAS_MANY,'User',array('user_id'=>'id'),'through'=>'roles'), - 'comments'=>array(self::HAS_MANY,'Comment',array('id'=>'user_id'),'through'=>'users'), - ); - } -} -~~~ - -#### Примеры - -~~~ -[php] -// получаем все группы с соответствующими им пользователями -$groups=Group::model()->with('users')->findAll(); - -// получаем все группы с соответствующими им пользователями и ролями -$groups=Group::model()->with('roles','users')->findAll(); - -// получаем всех пользователей и роли для группы с ID, равным 1 -$group=Group::model()->findByPk(1); -$users=$group->users; -$roles=$group->roles; - -// получаем все комментарии для группы с ID, равным 1 -$group=Group::model()->findByPk(1); -$comments=$group->comments; -~~~ - - -### `HAS_ONE` through - -![HAS_ONE through ER](has_one_through.png) - -Пример использования `HAS_ONE` с `through` — получение адреса пользователя в -случае, если пользователь связан с адресом через профиль. Все задействованные -сущности (пользователь, профиль и адрес) имеют соответствующие им модели: - -~~~ -[php] -class User extends CActiveRecord -{ - ... - public function relations() - { - return array( - 'profile'=>array(self::HAS_ONE,'Profile','user_id'), - 'address'=>array(self::HAS_ONE,'Address',array('id'=>'profile_id'),'through'=>'profile'), - ); - } -} -~~~ - -#### Примеры - -~~~ -[php] -// получаем адрес пользователя с ID, равным 1 -$user=User::model()->findByPk(1); -$address=$user->address; -~~~ - - -### through с собой - -`through` можно использовать для модели, связанной с собой через мост. В нашем -случае это пользователь, обучающий других пользователей: - - -![through self ER](through_self.png) - - -Связи для данного случая определяются следующим образом: - -~~~ -[php] -class User extends CActiveRecord -{ - ... - public function relations() - { - return array( - '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'), - ); - } -} -~~~ - -#### Примеры - -~~~ -[php] -// получаем всех студентов учителя с ID, равным 1 -$teacher=User::model()->findByPk(1); -$students=$teacher->students; +Реляционная Active Record +========================= + +Мы уже рассмотрели использование Active Record (AR) для выбора данных из одной таблицы базы данных. +В этом разделе мы расскажем, как использовать AR для соединения нескольких связанных таблиц и получения +набора связанных данных. + +Перед использованием реляционной AR рекомендуется установить ограничения внешних ключей для таблиц базы данных. +Это позволит обеспечить непротиворечивость и целостность хранимых данных. + +Для наглядности примеров в этом разделе мы будем использовать схему базы данных, +представленную на следующей диаграмме сущность-связь (ER). + +![Диаграмма ER](er.png) + +> Info|Информация: Поддержка ограничений внешних ключей различается в разных СУБД. +> SQLite 3.6.19 и более ранние версии не поддерживает ограничений, но вы, тем не менее, можете их объявить +> при создании таблиц. Движок MySQL MyISAM не поддерживает внешние ключи. + +Установка связей между AR-классами +---------------- + +Перед тем как использовать AR для выполнения реляционных запросов, нам необходимо установить связи между AR-классами. + +Связь между двумя AR-классами напрямую зависит от связей между соответствующими таблицами базы данных. С точки +зрения БД, связь между таблицами A и В может быть трёх типов: один-ко-многим (например, `tbl_user` и `tbl_post`), один-к-одному +(например, `tbl_user` и `tbl_profile`) и многие-ко-многим (например, `tbl_category` и `tbl_post`). В AR существует четыре типа связей: + + - `BELONGS_TO`: если связь между А и В один-ко-многим, значит В принадлежит А (например, `Post` принадлежит `User`); + + - `HAS_MANY`: если связь между таблицами А и В один-ко-многим, значит у А есть много В (например, у `User` есть много `Post`); + + - `HAS_ONE`: это частный случай `HAS_MANY`, где А может иметь максимум одно В (например, у `User` есть только один `Profile`); + + - `MANY_MANY`: эта связь соответствует типу связи многие-ко-многим в БД. Поскольку многие СУБД не поддерживают непосредственно +этот тип связи, требуется ассоциативная таблица для преобразования связи многие-ко-многим в связи один-ко-многим. +В нашей схеме базы данных этой цели служит таблица `tbl_post_category`. В терминологии AR связь `MANY_MANY` можно описать как +комбинацию `BELONGS_TO` и `HAS_MANY`. Например, `Post` принадлежит многим `Category`, а у `Category` есть много `Post`. + +Существует пятый, специальный тип связи, который предназначен для статистических запросов над связанными записями +(запросы агрегирования) — называется он `STAT`. Более подробно с ним можно ознакомиться в разделе +[Статистический запрос](/doc/guide/database.arr#statistical-query). + +Установка связей производится внутри метода [relations()|CActiveRecord::relations] класса [CActiveRecord]. +Этот метод возвращает массив с конфигурацией связей. Каждый элемент массива представляет одну связь в следующем формате: + +~~~ +[php] +'VarName'=>array('RelationType', 'ClassName', 'ForeignKey', …дополнительные параметры) +~~~ + +где `VarName` — имя связи, `RelationType` указывает на один из четырёх типов связей, +`ClassName` — имя AR-класса, связанного с данным классом, а +`ForeignKey` обозначает один или несколько внешних ключей, используемых для связи. +Кроме того, можно указать ряд дополнительных параметров, о которых будет рассказано позже. + +В приведённом ниже коде показано, как установить связь между классами `User` и `Post`. + +~~~ +[php] +class Post extends CActiveRecord +{ + … + public function relations() + { + return array( + 'author'=>array(self::BELONGS_TO, 'User', 'author_id'), + 'categories'=>array(self::MANY_MANY, 'Category', + 'tbl_post_category(post_id, category_id)'), + ); + } +} + +class User extends CActiveRecord +{ + … + public function relations() + { + return array( + 'posts'=>array(self::HAS_MANY, 'Post', 'author_id'), + 'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'), + ); + } +} +~~~ + +> Info|Информация: Внешний ключ может быть составным, то есть состоять из двух и более столбцов таблицы. В этом случае +имена столбцов следует разделить запятыми и передать их либо в качестве строки, либо в виде массива `array('key1','key2')`. +Задать свою связь первичного ключа с внешним можно в виде массива `array('fk'=>'pk')`. Для составных +ключей это будет `array('fk_c1'=>'pk_c1','fk_c2'=>'pk_c2')`. Для типа связи `MANY_MANY` имя ассоциативной таблицы также должно быть +указано во внешнем ключе. Например, связи `categories` модели `Post` соответствует внешний ключ `tbl_post_category(post_id, category_id)`. +Для каждой добавленной связи неявно создаётся свойство класса. После выполнения реляционного запроса +соответствующее свойство будет содержать связанный экземпляр класса AR (или массив экземпляров для связей типа один-ко-многим и +многие-ко-многим). Например, если `$author` является экземпляром AR-класса `User`, то можно использовать `$author->posts` +для доступа к связанным экземплярам `Post`. + +Выполнение реляционного запроса +------------------------------- + +Самый простой способ выполнить реляционный запрос — использовать реляционное свойство AR-класса. +Если обращение к этому свойству производится впервые, то будет выполнен реляционный запрос, который соединит связанные таблицы +и оставит только данные, соответствующие первичному ключу текущего экземпляра AR. +Результат запроса будет сохранён в свойстве как экземпляр (или массив экземпляров) связанного класса. +Этот подход также известен как «отложенная загрузка» (lazy loading), при которой непосредственный запрос выполняется +только в момент первого обращения к связанным объектам. Ниже приведён пример использования этого подхода: + +~~~ +[php] +// получаем запись с ID=10 +$post=Post::model()->findByPk(10); +// Получаем автора записи. Здесь будет выполнен реляционный запрос. +$author=$post->author; +~~~ + +> Info|Информация: Если связанные данные не найдены, то соответствующее +свойство примет значение null для связей `BELONGS_TO` и `HAS_ONE` или будет являться пустым массивом +для `HAS_MANY` и `MANY_MANY`. +Стоит отметить, что связи `HAS_MANY` и `MANY_MANY` возвращают +массивы объектов, и обращаться к их свойствам необходимо в цикле, иначе +можно получить ошибку «Trying to get property of non-object». + +Способ отложенной загрузки удобен, но не всегда эффективен. Например, если нам потребуется +получить информацию об авторах `N` записей, то использование отложенной загрузки +потребует выполнения `N` дополнительных запросов к базе данных. +В данной ситуации разумно использовать метод «жадной загрузки» (eager loading). + +Этот метод заключается в загрузке всех связанных данных вместе с +основным экземпляром AR. Реализуется этот подход путем использования метода + [with()|CActiveRecord::with] вместе с методом [find|CActiveRecord::find] или + [findAll|CActiveRecord::findAll]. +Например: +~~~ +[php] +$posts=Post::model()->with('author')->findAll(); +~~~ + +Приведённый код вернёт массив экземпляров `Post`. В отличие от отложенной загрузки, свойство `author` каждой записи будет +заполнено связанным экземпляром `User` ещё до обращения к этому свойству. Таким образом, вместо выполнения отдельного запроса +для каждой записи, жадная загрузка получит все записи вместе с их авторами в одном запросе! + +В методе [with()|CActiveRecord::with] можно указать несколько связей, и жадная загрузка вернёт их за один раз. +Например, следующий код вернёт записи вместе с их авторами и категориями: + +~~~ +[php] +$posts=Post::model()->with('author','categories')->findAll(); +~~~ + +Кроме того, можно осуществлять вложенную жадную загрузку. Для этого вместо простого списка имён связей, мы передаем методу +[with()|CActiveRecord::with] имена связей, упорядоченных иерархически, как в следующем примере: + +~~~ +[php] +$posts=Post::model()->with( + 'author.profile', + 'author.posts', + 'categories')->findAll(); +~~~ + +Пример выше вернёт нам все записи вместе с их авторами и категориями, а также профиль каждого автора и все его записи. + +Жадная загрузка может быть выполнена путём указания свойства [CDbCriteria::with]: + +~~~ +[php] +$criteria=new CDbCriteria; +$criteria->with=array( + 'author.profile', + 'author.posts', + 'categories', +); +$posts=Post::model()->findAll($criteria); +~~~ + +или + +~~~ +[php] +$posts=Post::model()->findAll(array( + 'with'=>array( + 'author.profile', + 'author.posts', + 'categories', + ) +)); +~~~ + + +Реляционный запрос без получения связанных моделей +-------------------------------------------------- + +Иногда требуется выполнить запрос с использованием связи, но при этом +не требуются данные из связанной модели. Допустим, есть пользователи (`User`), +которые публикуют множество записей (`Post`). Запись может быть опубликована, +а может быть черновиком. Этот факт определяется значением поля `published` модели `Post`. +Пусть нам необходимо получить всех пользователей, которые опубликовали хотя бы одну +запись, при этом сами записи нам не интересны. Сделать это можно следующим образом: + +~~~ +[php] +$users=User::model()->with(array( + 'posts'=>array( + // записи нам не нужны + 'select'=>false, + // но нужно выбрать только пользователей с опубликованными записями + 'joinType'=>'INNER JOIN', + 'condition'=>'posts.published=1', + ), +))->findAll(); +~~~ + + +Параметры реляционного запроса +------------------------------ + +Выше мы упоминали о том, что в реляционном запросе можно указать дополнительные параметры. +Эти параметры — пары имя-значение — используются для тонкой настройки реляционного запроса. +Список параметров представлен ниже. + + - `select`: список выбираемых полей для связанного AR-класса. По умолчанию значение параметра равно '*', +что соответствует всем полям таблицы. Для используемых столбцов должны быть разрешены конфликты имён. + + - `condition`: соответствует SQL оператору `WHERE`, по умолчанию значение параметра пустое. +Для используемых столбцов должны быть разрешены конфликты имён. + + - `params`: параметры для связывания в генерируемом SQL-выражении. Параметры передаются как массив пар имя-значение. + + - `on`: соответствует SQL оператору `ON`. Условие, указанное в этом параметре, +будет добавлено к основному условию соединения при помощи SQL оператора `AND`. Для используемых столбцов должны быть разрешены конфликты имён. +Данный параметр неприменим для связей типа `MANY_MANY`. + + - `order`: соответствует SQL оператору `ORDER BY`, по умолчанию значение параметра пустое. +Для используемых столбцов должны быть разрешены конфликты имён. + + - `with`: список дочерних связанных объектов, которые должны быть загружены с самим объектом. +Неправильное использование данной возможности может привести к бесконечному циклу. + + - `joinType`: тип соединения таблиц. По умолчанию значение параметра равно `LEFT +OUTER JOIN`; + + - `alias`: псевдоним таблицы, ассоциированной со связью. По умолчанию значение параметра +равняется null, что означает, что псевдоним соответствует имени связи. + + - `together`: параметр, устанавливающий необходимость принудительного соединения таблицы, ассоциированной с этой связью, +с другими таблицами. Этот параметр имеет смысл только для связей типов `HAS_MANY` и `MANY_MANY`. Если параметр не установлен или +равен false, тогда каждая связь `HAS_MANY` или `MANY_MANY` будет использовать отдельный SQL-запрос для связанных данных, +что может улучшить скорость выполнения запроса, т.к. уменьшается количество выбираемых данных. +Если параметр равен `true`, то зависимая таблица при выполнении запроса всегда будет +соединяться с основной, то есть будет выполнен один SQL-запрос даже в том случае, если +к основной таблице применяется постраничная разбивка. Если данный параметр не +задан, зависимая таблица будет соединена с основной только в случае, когда +к основной таблице не применяется постраничная разбивка. Более подробное описание +можно найти в разделе «производительность реляционного запроса». + + - `join`: дополнительный оператор `JOIN`. По умолчанию пуст. Этот параметр +доступен с версии 1.1.3. + + - `group`: соответствует SQL оператору `GROUP BY`, по умолчанию значение параметра пустое. +Для используемых столбцов должны быть разрешены конфликты имён. + + - `having`: соответствует SQL оператору `HAVING`, по умолчанию значение параметра пустое. +Для используемых столбцов должны быть разрешены конфликты имён. + + - `index`: имя столбца таблицы, значения которого должны быть использованы в +качестве ключей массива, хранящего связанные объекты. Без установки этого +параметра массив связанных объектов использует целочисленный индекс, +начинающийся с нуля. Параметр может быть установлен только для связей типа +`HAS_MANY` и `MANY_MANY`. + + - `scopes`: группы условий, которые необходимо применить. В случае одной группы +может задаваться в виде строки `'scopes'=>'scopeName'`. Если же групп несколько, то +их необходимо перечислить в массиве `'scopes'=>array('scopeName1','scopeName2')`. Этот параметр +доступен с версии 1.1.9. + +Кроме того, для отложенной загрузки некоторых типов связей доступен ряд дополнительных параметров: + + - `limit`: параметр для ограничения количества строк в выборке. Параметр неприменим для связей `BELONGS_TO`; + + - `offset`: параметр для указания начальной строки выборки. Параметр неприменим для связей `BELONGS_TO`. + + - `through`: имя связи модели, которое при получении данных будет + использоваться как мост. Параметр может быть установлен только для связей + `HAS_ONE` и `HAS_MANY`. Этот параметр доступен с версии 1.1.7, в которой можно применять его к `HAS_ONE` и `HAS_MANY`. + Начиная с версии 1.1.14 он может использоваться с `BELONGS_TO`. + +Ниже мы изменим определение связи `posts` в модели `User`, добавив несколько вышеприведенных параметров: + +~~~ +[php] +class User extends CActiveRecord +{ + public function relations() + { + return array( + 'posts'=>array(self::HAS_MANY, 'Post', 'author_id', + 'order'=>'posts.create_time DESC', + 'with'=>'categories'), + 'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'), + ); + } +} +~~~ + +Теперь при обращении к `$author->posts`, мы получим записи автора, отсортированные +в обратном порядке по времени их создания. Для каждой записи будут загружены +её категории. + +Устранение конфликта имён столбцов +---------------------------------- + +При совпадении имён столбцов в двух и более соединяемых таблицах, +приходится разрешать конфликт имён. Это делается при помощи добавления +псевдонима таблицы к имени столбца. + +В реляционном запросе псевдоним главной таблицы всегда равен `t`, +а псевдоним связанной таблицы по умолчанию равен имени связи. +В приведённом ниже коде псевдонимы таблиц для моделей `Post` и `Comment` будут соответственно +`t` и `comments`: + +~~~ +[php] +$posts=Post::model()->with('comments')->findAll(); +~~~ + +Допустим, что и в `Post`, и в `Comment` есть столбец `create_time`, в котором +хранится время создания записи или комментария, и нам необходимо получить записи +вместе с комментариями к ним, отсортированные сначала по времени создания +записи, а затем по времени написания комментария. Для этого нам понадобится +устранить конфликт столбцов `create_time` следующим образом: + +~~~ +[php] +$posts=Post::model()->with('comments')->findAll(array( + 'order'=>'t.create_time, comments.create_time' +)); +~~~ + +> Tip|Подсказка: Псевдоним таблицы связи по умолчанию равен названию самой связи. Имейте ввиду, +> что при использовании одной связи внутри другой будет использовано название последней из них. +> При этом название родительской связи не будет использовано в качестве префикса. Например, псевдонимом +> связи 'author.group' является 'group', а не 'author.group'. +> +> ~~~ +> [php] +> $posts=Post::model()->with('author', 'author.group')->findAll(array( +> 'order'=>'group.name, author.name, t.title' +> )); +> ~~~ +> +> Вы можете избежать конфликта псевдонимов таблиц задав свойство связи [alias|CActiveRelation::alias]. +> +> ~~~ +> [php] +> $comments=Comment::model()->with( +> 'author', +> 'post', +> 'post.author'=>array('alias'=>'p_author'))->findAll(array( +> 'order'=>'author.name, p_author.name, post.title' +> )); +> ~~~ + +Динамические параметры реляционного запроса +------------------------------------------- + +Мы можем использовать динамические параметры как для метода +[with()|CActiveRecord::with], так и для параметра `with`. Динамические параметры переопределяют существующие +параметры в соответствии с описанием метода [relations()|CActiveRecord::relations]. К примеру, если для модели `User`, приведённой выше, +мы хотим воспользоваться жадной загрузкой для получения записей автора в порядке возрастания (параметр `order` в определении связи +задает убывающий порядок), можно сделать это следующим образом: + +~~~ +[php] +User::model()->with(array( + 'posts'=>array('order'=>'posts.create_time ASC'), + 'profile', +))->findAll(); +~~~ + +Динамические параметры в реляционных запросах можно использовать вместе с +отложенной загрузкой. Для этого необходимо вызвать метод с тем же именем, что и +имя связи, и передать параметры в качестве аргументов. К примеру, следующий код +вернёт публикации пользователя, у которых `status` равен 1: + +~~~ +[php] +$user=User::model()->findByPk(1); +$posts=$user->posts(array('condition'=>'status=1')); +~~~ + +Производительность реляционного запроса +--------------------------------------- + +Как было сказано выше, жадная загрузка используется, главным образом, когда +требуется получить множество связанных объектов. В этом случае при соединении +всех таблиц генерируется сложный SQL-запрос. Такой запрос во многих случаях +является предпочтительным, т.к. упрощает фильтрацию по значению столбца связанной таблицы. +Тем не менее, в некоторых случаях такие запросы не являются эффективными. + +Рассмотрим пример, в котором нам необходимо найти последние добавленные записи вместе с их комментариями. +Предположив, что у каждой записи 10 комментариев, при использовании одного большого SQL-запроса +мы получим множество лишних данных, так как каждая запись будет повторно выбираться с каждым +её комментарием. Теперь попробуем по-другому: сначала выберем последние записи, а затем комментарии к ним. +В этом случае нам необходимо выполнить два SQL-запроса. Плюс в том, что полученные данные +не будут содержать дублирующую информацию. + +Какой же подход более эффективен? Однозначного ответа на этот вопрос нет. +Выполнение одного большого SQL-запроса может оказаться более эффективным, так как СУБД +не приходится лишний раз разбирать и выполнять дополнительные запросы. +С другой стороны, используя один SQL-запрос, мы получаем лишние данные, а значит нам требуется больше времени на их передачу и обработку. +По умолчанию Yii использует жадную загрузку, то есть генерирует один SQL-запрос +за исключением случая, когда к главной модели применяется `LIMIT`. Если выставить +опцию `together` в описании связи в `true`, то мы получим единственный SQL-запрос +даже если используется `LIMIT`. Если использовать `false`, то выборка из +некоторых таблиц будет производиться отдельными запросами. +Например, для того чтобы использовать отдельные SQL-запросы для выборки +последних записей и комментариев к ним, связь `comments` модели `Post` следует +описать следующим образом: + +~~~ +[php] +public function relations() +{ + return array( + 'comments' => array(self::HAS_MANY, 'Comment', 'post_id', 'together'=>false), + ); +} +~~~ + +Для жадной загрузки мы можем задать этот параметр динамически: + +~~~ +[php] +$posts = Post::model()->with(array('comments'=>array('together'=>false)))->findAll(); +~~~ + +Статистический запрос +--------------------- + +Помимо реляционных запросов, описанных выше, Yii также поддерживает так называемые статистические запросы (или запросы агрегирования). +Этот тип запросов используется для получения агрегированных данных, относящихся к связанным объектам (количество комментариев +к каждой записи, средний рейтинг для каждого наименования продукции и т.д.). +Статистические запросы могут быть использованы только для связей типа `HAS_MANY` (например, у записи есть много +комментариев) или `MANY_MANY` (например, запись принадлежит многим категориям, а категориия может относиться ко множеству записей). + +Выполнение статистического запроса аналогично выполнению реляционного запроса. Первым делом необходимо +объявить статистический запрос в методе [relations()|CActiveRecord::relations] класса [CActiveRecord]. + +~~~ +[php] +class Post extends CActiveRecord +{ + public function relations() + { + return array( + 'commentCount'=>array(self::STAT, 'Comment', 'post_id'), + 'categoryCount'=>array(self::STAT, 'Category', 'post_category(post_id, category_id)'), + ); + } +} +~~~ + +Выше мы объявили два статистических запроса: `commentCount` подсчитывает количество комментариев к записи, а `categoryCount` +считает количество категорий, к которым относится запись. Обратите внимание, что связь между `Post` и `Comment` — типа `HAS_MANY`, а +связь между `Post` и `Category` — типа `MANY_MANY` (с использованием преобразующей таблицы `post_category`). Как можно видеть, +способ объявления похож на объявление связей, описанных выше. Единственное различие состоит в том, что в данном случае тип связи +равен `STAT`. + +За счёт объявленных связей мы можем получить количество комментариев к записи, используя выражение `$post->commentCount`. +В момент первого обращения к данному свойству для получения соответствующего результата неявным образом выполняется SQL-запрос. +Как мы уже говорили, это называется подходом *отложенной загрузки*. Можно также использовать *жадный* вариант загрузки, если необходимо +получить количество комментариев к нескольким записям: + +~~~ +[php] +$posts=Post::model()->with('commentCount', 'categoryCount')->findAll(); +~~~ + +Выражение выше выполняет три SQL-запроса для получения всех записей вместе с количеством комментариев к ним и числом категорий. +В случае отложенной загрузки нам бы понадобилось выполнить `2*N+1` SQL-запросов для `N` записей. + +По умолчанию статистический запрос считает количество с использованием выражения `COUNT`. +Его можно уточнить путём указания дополнительных параметров в момент объявления в методе [relations()|CActiveRecord::relations]. +Доступные параметры перечислены ниже: + + - `select`: статистическое выражение, по умолчанию равно `COUNT(*)`, что соответствует количеству связанных объектов; + + - `defaultValue`: значение, которое присваивается в случае, если результат статистического запроса пуст. +Например, если запись не имеет ни одного комментария, то свойству `commentCount` будет присвоено это значение. По умолчанию значение +данного параметра равно 0; + + - `condition`: соответствует SQL оператору `WHERE`, по умолчанию значение параметра пустое; + + - `params`: параметры для связывания в генерируемом SQL-выражении. Параметры передаются + как массив пар имя-значение; + + - `order`: соответствует SQL оператору `ORDER BY`, по умолчанию значение параметра пустое; + + - `group`: соответствует SQL оператору `GROUP BY`, по умолчанию значение параметра пустое; + + - `having`: соответствует SQL оператору `HAVING`, по умолчанию значение параметра пустое. + +Реляционные запросы с именованными группами условий +--------------------------------------------------- + +В реляционном запросе [именованные группы условий](/doc/guide/database.ar#named-scopes) +могут быть использованы двумя способами. Их можно применить к основной модели и +к связанным моделям. + +Следующий код иллюстрирует случай их применения к основной модели: + +~~~ +[php] +$posts=Post::model()->published()->recently()->with('comments')->findAll(); +~~~ + +Данный код очень похож на нереляционный запрос. Единственное отличие состоит в том, что +присутствует вызов `with()` после вызовов групп условий. Данный запрос +вернёт недавно опубликованные записи вместе с комментариями к ним. + +В следующем примере показано, как применить группы условий к связанным моделям: + +~~~ +[php] +$posts=Post::model()->with('comments:recently:approved')->findAll(); +// или, начиная с версии 1.1.7 +$posts=Post::model()->with(array( + 'comments'=>array( + 'scopes'=>array('recently','approved') + ), +))->findAll(); +// или, начиная с версии 1.1.7 +$posts=Post::model()->findAll(array( + 'with'=>array( + 'comments'=>array( + 'scopes'=>array('recently','approved') + ), + ), +)); +~~~ + +Этот запрос вернёт все записи вместе с одобренными комментариями. Здесь `comments` +соответствует имени связи. `recently` и `approved` — именованные группы, описанные +в модели `Comment`. Имя связи и группы условий разделяются двоеточием. + +Вам может понадобится использовать вместо «жадной» выборки «отложенную» для +связи с группой условий. Синтаксис для этого такой: + +~~~ +[php] +// имя связи comments повторяется два раза +$approvedComments = $post->comments('comments:approved'); +~~~ + + +Именованные группы могут быть использованы при описании связей модели в +методе [CActiveRecord::relations()] в параметре `with`. В следующем примере +при обращении к `$user->posts` вместе с публикациями будут получены все +*одобренные* комментарии. + +~~~ +[php] +class User extends CActiveRecord +{ + public function relations() + { + return array( + 'posts'=>array(self::HAS_MANY, 'Post', 'author_id', + 'with'=>'comments:approved'), + ); + } +} +// или, начиная с версии 1.1.7 +class User extends CActiveRecord +{ + public function relations() + { + return array( + 'posts'=>array(self::HAS_MANY, 'Post', 'author_id', + 'with'=>array( + 'comments'=>array( + 'scopes'=>'approved' + ), + ), + ), + ); + } +} +~~~ + + +В версии 1.1.7 появилась возможность передавать параметры именованным группам условий +связи. К примеру, если в `Post` есть именованная группа условий `rated`, +принимающая минимальный рейтинг записи, использовать её в `User` можно следующим образом: + + +> Note|Примечание: до версии 1.1.7 именованные группы условий, применяемые к реляционным моделям, +> должны быть описаны в CActiveRecord::scopes. Поэтому они не могут быть параметризованы. + + +~~~ +[php] +$users=User::model()->findAll(array( + 'with'=>array( + 'posts'=>array( + 'scopes'=>array( + 'rated'=>5, + ), + ), + ), +)); + +class Post extends CActiveRecord +{ + ...... + + public function rated($rating) + { + $this->getDbCriteria()->mergeWith(array( + 'condition'=>'rating=:rating', + 'params'=>array(':rating'=>$rating), + )); + return $this; + } + + ...... +} +~~~ + +Реляционные запросы с through +----------------------------- + +При использовании `through` определение связи должно выглядеть следующим образом: + +~~~ +[php] +'comments'=>array(self::HAS_MANY,'Comment',array('key1'=>'key2'),'through'=>'posts'), +~~~ + +В коде выше, а именно в `array('key1'=>'key2')`: + + - `key1` — ключ, определённый в связи, на которую указывает `through` (в нашем случае `posts`). + - `key2` — ключ, определённый в модели, на которую указывает связь (в нашем случае `Comment`). + +`through` может использоваться с `HAS_ONE`, `BELONGS_TO` и `HAS_MANY`. + +### `HAS_MANY` through + +![HAS_MANY through ER](has_many_through.png) + +Пример использования `HAS_MANY` с `through` — получение пользователей, состоящих +в определённой группе, если они записаны в группу через роли. + +Более сложным примером является получение всех комментариев для всех пользователей +определённой группы. В этом случае необходимо использовать несколько связей +с `through` в одной модели: + +~~~ +[php] +class Group extends CActiveRecord +{ + ... + public function relations() + { + return array( + 'roles'=>array(self::HAS_MANY,'Role','group_id'), + 'users'=>array(self::HAS_MANY,'User',array('user_id'=>'id'),'through'=>'roles'), + 'comments'=>array(self::HAS_MANY,'Comment',array('id'=>'user_id'),'through'=>'users'), + ); + } +} +~~~ + +#### Примеры + +~~~ +[php] +// получаем все группы с соответствующими им пользователями +$groups=Group::model()->with('users')->findAll(); + +// получаем все группы с соответствующими им пользователями и ролями +$groups=Group::model()->with('roles','users')->findAll(); + +// получаем всех пользователей и роли для группы с ID, равным 1 +$group=Group::model()->findByPk(1); +$users=$group->users; +$roles=$group->roles; + +// получаем все комментарии для группы с ID, равным 1 +$group=Group::model()->findByPk(1); +$comments=$group->comments; +~~~ + + +### `HAS_ONE` through + +![HAS_ONE through ER](has_one_through.png) + +Пример использования `HAS_ONE` с `through` — получение адреса пользователя в +случае, если пользователь связан с адресом через профиль. Все задействованные +сущности (пользователь, профиль и адрес) имеют соответствующие им модели: + +~~~ +[php] +class User extends CActiveRecord +{ + ... + public function relations() + { + return array( + 'profile'=>array(self::HAS_ONE,'Profile','user_id'), + 'address'=>array(self::HAS_ONE,'Address',array('id'=>'profile_id'),'through'=>'profile'), + ); + } +} +~~~ + +#### Примеры + +~~~ +[php] +// получаем адрес пользователя с ID, равным 1 +$user=User::model()->findByPk(1); +$address=$user->address; +~~~ + + +### through с собой + +`through` можно использовать для модели, связанной с собой через мост. В нашем +случае это пользователь, обучающий других пользователей: + + +![through self ER](through_self.png) + + +Связи для данного случая определяются следующим образом: + +~~~ +[php] +class User extends CActiveRecord +{ + ... + public function relations() + { + return array( + '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'), + ); + } +} +~~~ + +#### Примеры + +~~~ +[php] +// получаем всех студентов учителя с ID, равным 1 +$teacher=User::model()->findByPk(1); +$students=$teacher->students; ~~~ \ 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 fc71e4173..d3263a687 100644 --- a/docs/guide/ru/database.query-builder.txt +++ b/docs/guide/ru/database.query-builder.txt @@ -1,923 +1,923 @@ -Конструктор запросов -==================== - -Конструктор запросов Yii предоставляет объектно-ориентированный способ -написания SQL-запросов. Он позволяет разработчику использовать методы и свойства -класса для того, чтобы указать отдельные части SQL-запроса. Затем конструктор -собирает отдельные части в единый SQL-запрос, который может быть выполнен вызовом -методов DAO, как описано в «[Объекты доступа к данным (DAO)](/doc/guide/database.dao)». -Следующий код показывает типичное использование конструктора запросов для создания -SQL-запроса SELECT: - -~~~ -[php] -$user = Yii::app()->db->createCommand() - ->select('id, username, profile') - ->from('tbl_user u') - ->join('tbl_profile p', 'u.id=p.user_id') - ->where('id=:id', array(':id'=>$id)) - ->queryRow(); -~~~ - - -Конструктор запросов лучше всего использовать в том случае, когда необходимо -собрать SQL-запрос, следуя некоторой условной логике приложения. Основными достоинствами -конструктора запросов являются: - -* Возможность собрать сложный SQL-запрос программно. - -* Автоматическое экранирование имён таблиц и полей для избежания конфликтов -с ключевыми словами SQL и специальными символами. - -* Экранирование значений параметров и, где это возможно, использование привязки -параметров, помогающей избежать SQL-инъекций. - -* Слой абстракции, упрощающий переход на другие СУБД. - - -Использовать конструктор запросов не обязательно. Если ваши запросы простые, легче -и быстрее использовать именно SQL. - -> Note|Примечание: Конструктор запросов не может быть использован для -> изменения существующего запроса, заданного при помощи SQL. -> К примеру, не будет работать следующий код: -> -> ~~~ -> [php] -> $command = Yii::app()->db->createCommand('SELECT * FROM tbl_user'); -> // следующая строка НЕ добавит WHERE к SQL -> $command->where('id=:id', array(':id'=>$id)); -> ~~~ -> -> Не стоит использовать для одного запроса и SQL, и конструктор запросов. - - -Подготовка конструктора запросов --------------------------------- - -Конструктор запросов реализован в классе [CDbCommand] — главном классе для -работы с базой данных, описанном в разделе «[Объекты доступа к данным (DAO)](/doc/guide/database.dao)». - -Для того чтобы начать его использовать, необходимо создать новый экземпляр -[CDbCommand]: - -~~~ -[php] -$command = Yii::app()->db->createCommand(); -~~~ - -Здесь мы используем `Yii::app()->db` для получения соединения с базой данных и -затем вызываем [CDbConnection::createCommand()] для создания экземпляра команды. - -Следует отметить, что теперь мы не передаём методу `createCommand()` готовое SQL-выражение, как это делалось -[в случае с DAO](/doc/guide/database.dao). -Вместо этого мы соберём отдельные части запроса при помощи методов конструктора, которые описаны далее. - - -Запросы на получение данных ------------------------- - -Запросы на получение данных соответствуют SQL-запросам SELECT. В конструкторе -есть ряд методов для сборки отдельных частей SELECT запроса. Так как все -эти методы возвращают экземпляр [CDbCommand], мы можем использовать их -цепочкой, как показано в примере в начале этого раздела. - -* [select()|CDbCommand::select() ]: часть запроса после SELECT. -* [selectDistinct()|CDbCommand::selectDistinct]: часть запроса после SELECT. Добавляет DISTINCT. -* [from()|CDbCommand::from() ]: часть запроса после FROM. -* [where()|CDbCommand::where() ]: часть запроса после WHERE. -* [join()|CDbCommand::join() ]: добавляет к запросу INNER JOIN. -* [leftJoin()|CDbCommand::leftJoin]: добавляет к запросу LEFT OUTER JOIN. -* [rightJoin()|CDbCommand::rightJoin]: добавляет к запросу RIGHT OUTER JOIN. -* [crossJoin()|CDbCommand::crossJoin]: добавляет к запросу CROSS JOIN. -* [naturalJoin()|CDbCommand::naturalJoin]: добавляет к запросу NATURAL JOIN. -* [group()|CDbCommand::group() ]: часть запроса после GROUP BY. -* [having()|CDbCommand::having() ]: часть запроса после HAVING. -* [order()|CDbCommand::order() ]: часть запроса после ORDER BY. -* [limit()|CDbCommand::limit() ]: часть запроса после LIMIT. -* [offset()|CDbCommand::offset() ]: часть запроса после OFFSET. -* [union()|CDbCommand::union() ]: часть запроса после UNION. - - -Рассмотрим использование перечисленных методов. Для простоты предположим, что -запросы делаются к MySQL. Для других СУБД способ экранирования названий таблиц, полей и значений, -используемый в примерах, может отличаться. - - -### select() - -~~~ -[php] -function select($columns='*') -~~~ - -Метод [select()|CDbCommand::select() ] задаёт часть запроса после `SELECT`. Параметр -`$columns` определяет выбираемые поля и может быть либо списком имён выбираемых -полей, разделённых запятой, либо массивом имён полей. Имена могут содержать -префиксы таблиц и псевдонимы полей. Метод автоматически экранирует имена, если -в них нет скобок (что означает использование выражения). - -Несколько примеров: - -~~~ -[php] -// SELECT * -select() -// SELECT `id`, `username` -select('id, username') -// SELECT `tbl_user`.`id`, `username` AS `name` -select('tbl_user.id, username as name') -// SELECT `id`, `username` -select(array('id', 'username')) -// SELECT `id`, count(*) as num -select(array('id', 'count(*) as num')) -~~~ - - -### selectDistinct() - -~~~ -[php] -function selectDistinct($columns) -~~~ - -Метод [selectDistinct()|CDbCommand::selectDistinct] делает то же, что и метод -[select()|CDbCommand::select() ], но добавляет к выражению `DISTINCT`. К примеру, -`selectDistinct('id, username')` сгенерирует следующий SQL: - -~~~ -SELECT DISTINCT `id`, `username` -~~~ - - -### from() - -~~~ -[php] -function from($tables) -~~~ - -Метод [from()|CDbCommand::from() ] задаёт часть запроса после `FROM`. Параметр -`$tables` определяет, из каких таблиц производится выборка, и может быть либо -списком имён таблиц, разделённых запятыми, либо массивом имён таблиц. Имена могут -содержать префиксы схемы (такие, как `public.tbl_user`) и псевдонимы таблиц -(такие, как `tbl_user u`). Метод автоматически экранирует имена, если в них нет -скобок (что означает использование подзапроса или выражения). - -Примеры: - -~~~ -[php] -// FROM `tbl_user` -from('tbl_user') -// FROM `tbl_user` `u`, `public`.`tbl_profile` `p` -from('tbl_user u, public.tbl_profile p') -// FROM `tbl_user`, `tbl_profile` -from(array('tbl_user', 'tbl_profile')) -// FROM `tbl_user`, (select * from tbl_profile) p -from(array('tbl_user', '(select * from tbl_profile) p')) -~~~ - - -### where() - -~~~ -[php] -function where($conditions, $params=array()) -~~~ - -Метод [where()|CDbCommand::where() ] задаёт часть запроса после `WHERE`. Параметр -`$conditions` определяет условия запроса, а `$params` — параметры, которые -подставляются в запрос. Значение параметра `$conditions` может быть как строкой -(например, `id=1`), так и массивом следующего вида: - -~~~ -[php] -array(operator, operand1, operand2, ...) -~~~ - -где `operator` может быть одним из следующих: - -* `and`: операнды соединяются при помощи `AND`. К примеру, `array('and', 'id=1', 'id=2')` -сгенерирует `id=1 AND id=2`. Если операнд является массивом, то он будет преобразован -в строку с использованием описанных здесь правил. К примеру, -`array('and', 'type=1', array('or', 'id=1', 'id=2'))` сгенерирует -`type=1 AND (id=1 OR id=2)`. Данный метод ничего НЕ экранирует. - -* `or`: то же, что и `and`, но для `OR`. - -* `in`: первый операнд должнен быть столбцом или выражением, второй — массивом, -содержащим список значений, в которые должно входить значение поля или выражения. -К примеру, `array('in', 'id', array(1,2,3))` сгенерирует `id IN (1,2,3)`. -Метод экранирует имя столбца и значения в списке. - -* `not in`: то же, что и `in`, но вместо `IN` используется `NOT IN`. - -* `like`: первый операнд должен быть именем поля или выражением, второй — строкой -или массивом, содержащим список значений, на которые должно быть похоже значение -поля или выражения. К примеру, `array('like', 'name', '%tester%')` сгенерирует -`name LIKE '%tester%'`. Когда список значений является массивом, генерируется несколько -`LIKE`, соединённых при помощи `AND`. Например, -`array('like', 'name', array('%test%', '%sample%'))` сгенерирует -`name LIKE '%test%' AND name LIKE '%sample%'`. Метод экранирует имена полей и -значения в списке. - -* `not like`: то же, что и `like`, но вместо `LIKE` генерируется `NOT LIKE`. - -* `or like`: то же, что и `like` но для соединения `LIKE` используется `OR`. - -* `or not like`: то же, что и `not like` но для соединения `NOT LIKE` используется `OR`. - - -Несколько примеров использования `where`: - -~~~ -[php] -// WHERE id=1 or id=2 -where('id=1 or id=2') -// WHERE id=:id1 or id=:id2 -where('id=:id1 or id=:id2', array(':id1'=>1, ':id2'=>2)) -// WHERE id=1 OR id=2 -where(array('or', 'id=1', 'id=2')) -// WHERE id=1 AND (type=2 OR type=3) -where(array('and', 'id=1', array('or', 'type=2', 'type=3'))) -// WHERE `id` IN (1, 2) -where(array('in', 'id', array(1, 2)) -// WHERE `id` NOT IN (1, 2) -where(array('not in', 'id', array(1,2))) -// WHERE `name` LIKE '%Qiang%' -where(array('like', 'name', '%Qiang%')) -// WHERE `name` LIKE '%Qiang' AND `name` LIKE '%Xue' -where(array('like', 'name', array('%Qiang', '%Xue'))) -// WHERE `name` LIKE '%Qiang' OR `name` LIKE '%Xue' -where(array('or like', 'name', array('%Qiang', '%Xue'))) -// WHERE `name` NOT LIKE '%Qiang%' -where(array('not like', 'name', '%Qiang%')) -// WHERE `name` NOT LIKE '%Qiang%' OR `name` NOT LIKE '%Xue%' -where(array('or not like', 'name', array('%Qiang%', '%Xue%'))) -~~~ - -Стоит отметить, что в случае, когда оператор содержит `like`, необходимо явно -задавать спецсимволы (вроде `%` и `_`). Если паттерн вводится пользователем, то -необходимо использовать приведённый ниже код для экранирования спецсимволов и -предотвращения интерпретации их как спецсимволов: - -~~~ -[php] -$keyword=$_GET['q']; -// экранирует символы % и _ -$keyword=strtr($keyword, array('%'=>'\%', '_'=>'\_')); -$command->where(array('like', 'title', '%'.$keyword.'%')); -~~~ - -### order() - -~~~ -[php] -function order($columns) -~~~ - -Метод [order()|CDbCommand::order() ] задаёт часть запроса после `ORDER BY`. Параметр -`$columns` определяет, по каким полям будет производиться сортировка. Поля могут -быть указаны как в виде строки, содержащей список полей и направлений -(`ASC` или `DESC`), разделённых запятыми, так и массив полей и направлений. -Имена полей могут содержать префиксы таблиц. Метод автоматически экранирует -имена полей, если они не содержат скобок (что означает использование выражения). - -Несколько примеров: - -~~~ -[php] -// ORDER BY `name`, `id` DESC -order('name, id desc') -// ORDER BY `tbl_profile`.`name`, `id` DESC -order(array('tbl_profile.name', 'id desc')) -~~~ - - -### limit() и offset() - -~~~ -[php] -function limit($limit, $offset=null) -function offset($offset) -~~~ - -Методы [limit()|CDbCommand::limit() ] и [offset()|CDbCommand::offset() ] задают -части запроса, следующие после `LIMIT` и `OFFSET`. Стоит отметить, что не все -СУБД поддерживают именно синтаксис `LIMIT` и `OFFSET`. Если он не поддерживается, -то конструктор запросов переписывает весь SQL-запрос для достижения схожего -эффекта. - -Несколько примеров: - -~~~ -[php] -// LIMIT 10 -limit(10) -// LIMIT 10 OFFSET 20 -limit(10, 20) -// OFFSET 20 -offset(20) -~~~ - - -### join() и его варианты - -~~~ -[php] -function join($table, $conditions, $params=array()) -function leftJoin($table, $conditions, $params=array()) -function rightJoin($table, $conditions, $params=array()) -function crossJoin($table) -function naturalJoin($table) -~~~ - -Метод [join()|CDbCommand::join() ] и его варианты задают порядок и параметры -соединения таблиц с использованием `INNER JOIN`, `LEFT OUTER JOIN`, -`RIGHT OUTER JOIN`, `CROSS JOIN` и `NATURAL JOIN`. Параметр `$table` определяет -таблицу, с которой производится соединение. Имя таблицы может содержать -префикс схемы или псевдоним. Метод экранирует имя таблицы, если оно не -содержит скобок, что означает использование подзапроса или выражения. Параметр -`$conditions` задаёт условие соединения. Синтаксис такой же, как и у -[where()|CDbCommand::where() ]. Через `$params` указываются параметры, подставляемые -в запрос. - -Стоит отметить, что этот метод отличается от остальных тем, что каждый следующий -его вызов добавляет часть запроса к предыдущим. - -Несколько примеров: - -~~~ -[php] -// JOIN `tbl_profile` ON user_id=id -join('tbl_profile', 'user_id=id') -// LEFT JOIN `pub`.`tbl_profile` `p` ON p.user_id=id AND type=1 -leftJoin('pub.tbl_profile p', 'p.user_id=id AND type=:type', array(':type'=>1)) -~~~ - - -### group() - -~~~ -[php] -function group($columns) -~~~ - -Метод [group()|CDbCommand::group() ] задаёт часть запроса после `GROUP BY`. -Параметр `$columns` определяет поля, по которым будет осуществляться группировка, -и может быть либо строкой разделённых запятыми полей, либо массивом полей. -Имена полей могут содержать префиксы. Метод автоматически экранирует имена полей, -если они не содержат скобок (что означает использование выражений). - -Несколько примеров: - -~~~ -[php] -// GROUP BY `name`, `id` -group('name, id') -// GROUP BY `tbl_profile`.`name`, `id` -group(array('tbl_profile.name', 'id')) -~~~ - - -### having() - -~~~ -[php] -function having($conditions, $params=array()) -~~~ - -Метод [having()|CDbCommand::having() ] задаёт часть запроса после `HAVING`. Используется -точно так же, как и [where()|CDbCommand::where() ]. - -Несколько примеров: - -~~~ -[php] -// HAVING id=1 or id=2 -having('id=1 or id=2') -// HAVING id=1 OR id=2 -having(array('or', 'id=1', 'id=2')) -~~~ - - -### union() - -~~~ -[php] -function union($sql) -~~~ - -Метод [union()|CDbCommand::union() ] задаёт часть запроса после `UNION`. Он добавляет -`$sql` к сгенерированному запросу, используя `UNION`. Несколько вызовов `union()` -добавят несколько частей запроса. - -Несколько примеров: - -~~~ -[php] -// UNION (select * from tbl_profile) -union('select * from tbl_profile') -~~~ - - -### Выполнение запросов - -После вызова приведённых выше методов для построения запроса, выполнить его можно, -используя методы DAO, как описано в разделе «[Объекты доступа к данным (DAO)](/doc/guide/database.dao)». -Например, мы можем использовать метод [CDbCommand::queryRow()] для получения строки или [CDbCommand::queryAll()] -для получения набора строк. - -Пример: - -~~~ -[php] -$users = Yii::app()->db->createCommand() - ->select('*') - ->from('tbl_user') - ->queryAll(); -~~~ - - -### Получение SQL - -Кроме выполнения запросов, которые мы создали при помощи конструктора, можно -также получить их SQL. Сделать это можно при помощи [CDbCommand::getText()]. - -~~~ -[php] -$sql = Yii::app()->db->createCommand() - ->select('*') - ->from('tbl_user') - ->text; -~~~ - -Если у запроса есть параметры, получить их можно при помощи свойства -[CDbCommand::params]. - - -### Альтернативный синтаксис построения запросов - -Иногда использование цепочек вызовов может быть неоптимальным решением. Конструктор -запросов Yii позволяет создать запрос путём задания полей объекта. Для каждого -метода конструктора запросов есть соответствующее поле с таким же именем. -Присвоение значения полю эквивалентно вызову соответствующего метода. К примеру, -приведённые ниже строки эквивалентны, если `$command` — объект [CDbCommand]: - -~~~ -[php] -$command->select(array('id', 'username')); -$command->select = array('id', 'username'); -~~~ - -Более того, метод [CDbConnection::createCommand()] может принимать массив в -качестве аргумента. Пары имя-значение из массива будут использованы для инициализации -полей созданного экземпляра [CDbCommand]. Таким образом, для построения запроса можно -использовать следующий код: - -~~~ -[php] -$row = Yii::app()->db->createCommand(array( - 'select' => array('id', 'username'), - 'from' => 'tbl_user', - 'where' => 'id=:id', - 'params' => array(':id'=>1), -))->queryRow(); -~~~ - - -### Построение нескольких запросов - -Для построения нескольких запросов экземпляр [CDbCommand] может быть -использован несколько раз. Перед тем как построить новый запрос, необходимо -вызвать метод [CDbCommand::reset()] для очистки предыдушего запроса. Пример: - -~~~ -[php] -$command = Yii::app()->db->createCommand(); -$users = $command->select('*')->from('tbl_users')->queryAll(); -$command->reset(); // очищаем предыдущий запрос -$posts = $command->select('*')->from('tbl_posts')->queryAll(); -~~~ - - -Построение запросов для изменения данных ----------------------------------------- - -К запросам для изменения данных относятся SQL-запросы для вставки, обновления и -удаления данных из базы. В конструкторе запросов есть соответствующие методы -`insert`, `update` и `delete`. В отличие от запросов получения данных, описанных -выше, данные методы строят полный SQL-запрос и тут же выполняют его. - -* [insert()|CDbCommand::insert]: вставляет строку в таблицу; -* [update()|CDbCommand::update]: обновляет данные в таблице; -* [delete()|CDbCommand::delete]: удаляет данные из таблицы. - - -### insert() - -~~~ -[php] -function insert($table, $columns) -~~~ - -Метод [insert()|CDbCommand::insert] строит и выполняет SQL-запрос `INSERT`. Параметр -`$table` указывает, в какую таблицу производится вставка, а `$columns` является -массивом пар имя-значение полей для вставки. Метод экранирует имя таблицы и использует -параметры для вставляемых значений. - -Пример: - -~~~ -[php] -// строим и выполняем следующий SQL: -// INSERT INTO `tbl_user` (`name`, `email`) VALUES (:name, :email) -$command->insert('tbl_user', array( - 'name'=>'Tester', - 'email'=>'tester@example.com', -)); -~~~ - - -### update() - -~~~ -[php] -function update($table, $columns, $conditions='', $params=array()) -~~~ - -Метод [update()|CDbCommand::update] строит и выполняет SQL-запрос `UPDATE`. Параметр -`$table` указывает обновляемую таблицу; `$columns` является массивом пар имя-значение, -задающим значения обновляемых полей; `$conditions` и `$params` эквивалентны аналогичным -параметрам в [where()|CDbCommand::where() ] и определяют часть запроса `UPDATE` после -`WHERE`. Метод экранирует имя таблицы и использует параметры для обновляемых значений. - -Пример: - -~~~ -[php] -// строим и выполняем следующий SQL: -// UPDATE `tbl_user` SET `name`=:name WHERE id=:id -$command->update('tbl_user', array( - 'name'=>'Tester', -), 'id=:id', array(':id'=>1)); -~~~ - - -### delete - -~~~ -[php] -function delete($table, $conditions='', $params=array()) -~~~ - -Метод [delete()|CDbCommand::delete] строит и выполняет SQL-запрос `DELETE`. Параметр -`$table` указывает таблицу, из которой удаляются записи; `$conditions` и `$params` -эквивалентны аналогичным параметрам в [where()|CDbCommand::where() ], которые определяют -часть запроса `DELETE` после `WHERE`. Метод экранирует имя таблицы. - -Пример: - -~~~ -[php] -// строим и выполняем следующий SQL: -// DELETE FROM `tbl_user` WHERE id=:id -$command->delete('tbl_user', 'id=:id', array(':id'=>1)); -~~~ - -Построение запросов изменения схемы ------------------------------------ - -Кроме обычных запросов для получения данных и работы с ними, конструктор -может собирать и выполнять SQL-запросы для изменения схемы базы данных. -Поддерживаются следующие запросы: - -* [createTable()|CDbCommand::createTable]: создание таблицы; -* [renameTable()|CDbCommand::renameTable]: переименование таблицы; -* [dropTable()|CDbCommand::dropTable]: удаление таблицы; -* [truncateTable()|CDbCommand::truncateTable]: очистка таблицы; -* [addColumn()|CDbCommand::addColumn]: добавление нового поля в таблицу; -* [renameColumn()|CDbCommand::renameColumn]: переименование поля таблицы; -* [alterColumn()|CDbCommand::alterColumn]: изменение поля таблицы; -* [addForeignKey()|CDbCommand::addForeignKey]: добавление внешнего ключа (доступно с версии 1.1.6) -* [dropForeignKey()|CDbCommand::dropForeignKey]: удаление внешнего ключа (доступно с версии 1.1.6) -* [dropColumn()|CDbCommand::dropColumn]: удаление поля таблицы; -* [createIndex()|CDbCommand::createIndex]: создание индекса; -* [dropIndex()|CDbCommand::dropIndex]: удаление индекса. - -> Info|Информация: Несмотря на то что в разных СУБД запросы для измения схемы -различаются, конструктор запросов предоставляет единый интерфейс для их создания. -Это упрощает задачу мигрирования с одной СУБД на другую. - - -### Абстрактные типы данных - -Конструктор запросов вводит ряд абстрактных типов данных, которые можно -использовать для описания полей таблицы. В отличие от реальных типов данных, -которые отличаются в разных СУБД, абстрактные типы не зависят от СУБД. -При использовании их для описания типов полей конструктор запросов конвертирует -абстрактные типы в соответствующие им реальные. - -Конструктор запросов поддерживает следующие абстрактные типы: - -* `pk`: обычный первичный ключ. Для MySQL конвертируется в `int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY`; -* `string`: строка. Для MySQL конвертируется в `varchar(255)`; -* `text`: текстовый тип (длинная строка). Для MySQL конвертируется в `text`; -* `integer`: целое. Для MySQL конвертируется в `int(11)`; -* `float`: число с плавающей точкой. Для MySQL конвертируется в `float`; -* `decimal`: десятичное число. Для MySQL конвертируется в `decimal`; -* `datetime`: дата и время. Для MySQL конвертируется в `datetime`; -* `timestamp`: метка времени. Для MySQL конвертируется в `timestamp`; -* `time`: время. Для MySQL конвертируется в `time`; -* `date`: дата. Для MySQL конвертируется в `date`; -* `binary`: бинарный. Для MySQL конвертируется в `blob`; -* `boolean`: булевый. Для MySQL конвертируется в `tinyint(1)`; -* `money`: деньги/валюта. Для MySQL конвертируется в `decimal(19,4)`. Доступен с версии 1.1.8. - - -###createTable() - -~~~ -[php] -function createTable($table, $columns, $options=null) -~~~ - -Метод [createTable()|CDbCommand::createTable] строит и выполняет SQL-запрос для -создания таблицы. Параметр `$table` задаёт имя создаваемой таблицы. Параметр -`$columns` определяет поля новой таблицы. Они должны быть указаны в виде пар -имя-определение (т.е. `'username'=>'string'`). Параметр `$options` задаёт -дополнительный фрагмент SQL, который будет добавлен к генерируемому SQL. -Конструктор запроса экранирует имя таблицы и имена полей. - -Для указания определения поля можно использовать один из абстрактных типов данных, описанных выше. -Конструктор конвертирует абстрактный тип данных в соответствующий реальный тип данных в соответствии с используемой СУБД. -Например, `string` в случае MySQL преобразуется в `varchar(255)`. - -Определение поля также может содержать неабстрактный тип данных и спецификаций. -Они будут подставлены в результирующий SQL-запрос без каких-либо изменений. К примеру, -`point` не является абстрактным типом данных, и при использовании в определении -поля он будет включён в итоговый SQL без изменений. `string NOT NULL` будет -конвертирован в `varchar(255) NOT NULL` (т.е. конвертируются только абстрактный -тип `string`). - -Пример создания таблицы: - -~~~ -[php] -// CREATE TABLE `tbl_user` ( -// `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, -// `username` varchar(255) NOT NULL, -// `location` point -// ) ENGINE=InnoDB -createTable('tbl_user', array( - 'id' => 'pk', - 'username' => 'string NOT NULL', - 'location' => 'point', -), 'ENGINE=InnoDB') -~~~ - - -###renameTable() - -~~~ -[php] -function renameTable($table, $newName) -~~~ - -Метод [renameTable()|CDbCommand::renameTable] строит и выполняет SQL-запрос для -переименования таблицы. Параметр `$table` задаёт имя изменяемой таблицы. -Параметр `$newName` определяет новое имя таблицы. Конструктор запроса -экранирует имена таблицы. - -Пример переименования таблицы: - -~~~ -[php] -// RENAME TABLE `tbl_users` TO `tbl_user` -renameTable('tbl_users', 'tbl_user') -~~~ - - -###dropTable() - -~~~ -[php] -function dropTable($table) -~~~ - -Метод [dropTable()|CDbCommand::dropTable] строит и выполняет SQL-запрос для удаления -таблицы. Параметр `$table` определяет имя удаляемой таблицы. Конструктор запроса -экранирует имя таблицы. - -Пример удаления таблицы: - -~~~ -[php] -// DROP TABLE `tbl_user` -dropTable('tbl_user') -~~~ - -###truncateTable() - -~~~ -[php] -function truncateTable($table) -~~~ - -Метод [truncateTable()|CDbCommand::truncateTable] строит и выполняет SQL-запрос для -очистки всех данных таблицы. Параметр `$table` определяет имя очищаемой таблицы. -Конструктор запроса экранирует имя таблицы. - -Пример очистки таблицы: - -~~~ -[php] -// TRUNCATE TABLE `tbl_user` -truncateTable('tbl_user') -~~~ - - -###addColumn() - -~~~ -[php] -function addColumn($table, $column, $type) -~~~ - -Метод [addColumn()|CDbCommand::addColumn] строит и выполняет SQL-запрос для -добавления нового поля таблицы. Параметр `$table` задаёт имя таблицы, к которой -будет добавлено новое поле. Параметр `$column` — имя нового поля. `$type` -задаёт тип поля. Определение поля может содержать абстрактный тип данных, -как уже было описано в подразделе «createTable». Конструктор запроса -экранирует имя таблицы и имя поля. - -Пример добавления поля: - -~~~ -[php] -// ALTER TABLE `tbl_user` ADD `email` varchar(255) NOT NULL -addColumn('tbl_user', 'email', 'string NOT NULL') -~~~ - - -###dropColumn() - -~~~ -[php] -function dropColumn($table, $column) -~~~ - -Метод [dropColumn()|CDbCommand::dropColumn] строит и выполняет SQL-запрос для -удаления поля таблицы. Параметр `$table` задаёт имя таблицы, из которой удаляется -поле. Параметр `$column` определяет имя удаляемого поля. Конструктор запроса -экранирует имя таблицы и имя поля. - -Пример удаления поля таблицы: - -~~~ -[php] -// ALTER TABLE `tbl_user` DROP COLUMN `location` -dropColumn('tbl_user', 'location') -~~~ - - -###renameColumn() - -~~~ -[php] -function renameColumn($table, $name, $newName) -~~~ - -Метод [renameColumn()|CDbCommand::renameColumn] строит и выполняет SQL-запрос для -переименования поля таблицы. Параметр `$table` задаёт имя таблицы, поле которой -будет переименовано. Параметр `$name` определяет имя изменяемого поля. -`$newName` задаёт новое имя поля. Конструктор запроса экранирует имя таблицы и имена полей. - -Пример переименования поля таблицы: - -~~~ -[php] -// ALTER TABLE `tbl_users` CHANGE `name` `username` varchar(255) NOT NULL -renameColumn('tbl_user', 'name', 'username') -~~~ - - -###alterColumn() - -~~~ -[php] -function alterColumn($table, $column, $type) -~~~ - -Метод [alterColumn()|CDbCommand::alterColumn] строит и выполняет SQL-запрос для -изменения поля таблицы. Параметр `$table` задаёт имя таблицы, поле которой -будет изменено. Параметр `$column` определяет имя изменяемого поля. `$type` -задаёт новое определение поля, которое может содержать абстрактный тип данных, -как было описано в подразделе «createTable». Конструктор запросов экранирует имя -таблицы и имя поля. - -Пример изменения поля таблицы: - -~~~ -[php] -// ALTER TABLE `tbl_user` CHANGE `username` `username` varchar(255) NOT NULL -alterColumn('tbl_user', 'username', 'string NOT NULL') -~~~ - - - - -###addForeignKey() - -~~~ -[php] -function addForeignKey($name, $table, $columns, - $refTable, $refColumns, $delete=null, $update=null) -~~~ - -Метод [addForeignKey()|CDbCommand::addForeignKey] строит и выполняет SQL-запрос для -добавления внешнего ключа в таблицу. Параметр `$name` задаёт имя внешнего ключа. -Параметры `$table` и `$columns` определяют имя таблицы и имя поля внешнего -ключа. Если указаны несколько полей, то они должны быть разделены запятыми. -Параметры `$refTable` и `$refColumns` определяют имя таблицы и имя поля, на которое -ссылается внешний ключ. Параметры `$delete` и `$update` задают SQL-опции -`ON DELETE` и `ON UPDATE` соответственно. Большинство СУБД поддерживают -следующие опции: `RESTRICT`, `CASCADE`, `NO ACTION`, `SET DEFAULT` и `SET NULL`. -Конструктор запросов экранирует имя таблицы, имя индекса и имена полей. - -Пример добавления внешнего ключа: - -~~~ -[php] -// ALTER TABLE `tbl_profile` ADD CONSTRAINT `fk_profile_user_id` -// FOREIGN KEY (`user_id`) REFERENCES `tbl_user` (`id`) -// ON DELETE CASCADE ON UPDATE CASCADE -addForeignKey('fk_profile_user_id', 'tbl_profile', 'user_id', - 'tbl_user', 'id', 'CASCADE', 'CASCADE') -~~~ - - -###dropForeignKey() - -~~~ -[php] -function dropForeignKey($name, $table) -~~~ - -Метод [dropForeignKey()|CDbCommand::dropForeignKey] строит и выполняет SQL-запрос -для удаления внешнего ключа. Параметр `$name` задаёт имя внешнего ключа, -который требуется удалить. Параметр `$table` — имя таблицы, из которой -удаляется ключ. Конструктор запроса экранирует имя таблицы и имя ключа. - -Пример удаления внешнего ключа: - -~~~ -[php] -// ALTER TABLE `tbl_profile` DROP FOREIGN KEY `fk_profile_user_id` -dropForeignKey('fk_profile_user_id', 'tbl_profile') -~~~ - - -###createIndex() - -~~~ -[php] -function createIndex($name, $table, $column, $unique=false) -~~~ - -Метод [createIndex()|CDbCommand::createIndex] строит и выполняет SQL-запрос -для создания индекса. Параметр `$name` задаёт имя индекса, который будет создан. -Параметр `$table` — имя таблицы, в которой создаётся индекс. Параметр `$column` -— имя индексируемого поля. Параметр `$unique` определяет, будет ли -индекс уникальным. Если индекс состоит из нескольких полей, то они разделяются -запятыми. Конструктор запросов экранирует имя таблицы, имя индекса и имена полей. - -Пример создания индекса: - -~~~ -[php] -// CREATE INDEX `idx_username` ON `tbl_user` (`username`) -createIndex('idx_username', 'tbl_user', 'username') -~~~ - - -###dropIndex() - -~~~ -[php] -function dropIndex($name, $table) -~~~ - -Метод [dropIndex()|CDbCommand::dropIndex] строит и выполняет SQL-запрос для -удаления индекса. Параметр `$name` задаёт имя удаляемого индекса. -Параметр `$table` — имя таблицы, из которой удаляется индекс. Конструктор -запроса экранирует имя таблицы и имя индекса. - -Пример удаления индекса: - -~~~ -[php] -// DROP INDEX `idx_username` ON `tbl_user` -dropIndex('idx_username', 'tbl_user') +Конструктор запросов +==================== + +Конструктор запросов Yii предоставляет объектно-ориентированный способ +написания SQL-запросов. Он позволяет разработчику использовать методы и свойства +класса для того, чтобы указать отдельные части SQL-запроса. Затем конструктор +собирает отдельные части в единый SQL-запрос, который может быть выполнен вызовом +методов DAO, как описано в «[Объекты доступа к данным (DAO)](/doc/guide/database.dao)». +Следующий код показывает типичное использование конструктора запросов для создания +SQL-запроса SELECT: + +~~~ +[php] +$user = Yii::app()->db->createCommand() + ->select('id, username, profile') + ->from('tbl_user u') + ->join('tbl_profile p', 'u.id=p.user_id') + ->where('id=:id', array(':id'=>$id)) + ->queryRow(); +~~~ + + +Конструктор запросов лучше всего использовать в том случае, когда необходимо +собрать SQL-запрос, следуя некоторой условной логике приложения. Основными достоинствами +конструктора запросов являются: + +* Возможность собрать сложный SQL-запрос программно. + +* Автоматическое экранирование имён таблиц и полей для избежания конфликтов +с ключевыми словами SQL и специальными символами. + +* Экранирование значений параметров и, где это возможно, использование привязки +параметров, помогающей избежать SQL-инъекций. + +* Слой абстракции, упрощающий переход на другие СУБД. + + +Использовать конструктор запросов не обязательно. Если ваши запросы простые, легче +и быстрее использовать именно SQL. + +> Note|Примечание: Конструктор запросов не может быть использован для +> изменения существующего запроса, заданного при помощи SQL. +> К примеру, не будет работать следующий код: +> +> ~~~ +> [php] +> $command = Yii::app()->db->createCommand('SELECT * FROM tbl_user'); +> // следующая строка НЕ добавит WHERE к SQL +> $command->where('id=:id', array(':id'=>$id)); +> ~~~ +> +> Не стоит использовать для одного запроса и SQL, и конструктор запросов. + + +Подготовка конструктора запросов +-------------------------------- + +Конструктор запросов реализован в классе [CDbCommand] — главном классе для +работы с базой данных, описанном в разделе «[Объекты доступа к данным (DAO)](/doc/guide/database.dao)». + +Для того чтобы начать его использовать, необходимо создать новый экземпляр +[CDbCommand]: + +~~~ +[php] +$command = Yii::app()->db->createCommand(); +~~~ + +Здесь мы используем `Yii::app()->db` для получения соединения с базой данных и +затем вызываем [CDbConnection::createCommand()] для создания экземпляра команды. + +Следует отметить, что теперь мы не передаём методу `createCommand()` готовое SQL-выражение, как это делалось +[в случае с DAO](/doc/guide/database.dao). +Вместо этого мы соберём отдельные части запроса при помощи методов конструктора, которые описаны далее. + + +Запросы на получение данных +------------------------ + +Запросы на получение данных соответствуют SQL-запросам SELECT. В конструкторе +есть ряд методов для сборки отдельных частей SELECT запроса. Так как все +эти методы возвращают экземпляр [CDbCommand], мы можем использовать их +цепочкой, как показано в примере в начале этого раздела. + +* [select()|CDbCommand::select() ]: часть запроса после SELECT. +* [selectDistinct()|CDbCommand::selectDistinct]: часть запроса после SELECT. Добавляет DISTINCT. +* [from()|CDbCommand::from() ]: часть запроса после FROM. +* [where()|CDbCommand::where() ]: часть запроса после WHERE. +* [join()|CDbCommand::join() ]: добавляет к запросу INNER JOIN. +* [leftJoin()|CDbCommand::leftJoin]: добавляет к запросу LEFT OUTER JOIN. +* [rightJoin()|CDbCommand::rightJoin]: добавляет к запросу RIGHT OUTER JOIN. +* [crossJoin()|CDbCommand::crossJoin]: добавляет к запросу CROSS JOIN. +* [naturalJoin()|CDbCommand::naturalJoin]: добавляет к запросу NATURAL JOIN. +* [group()|CDbCommand::group() ]: часть запроса после GROUP BY. +* [having()|CDbCommand::having() ]: часть запроса после HAVING. +* [order()|CDbCommand::order() ]: часть запроса после ORDER BY. +* [limit()|CDbCommand::limit() ]: часть запроса после LIMIT. +* [offset()|CDbCommand::offset() ]: часть запроса после OFFSET. +* [union()|CDbCommand::union() ]: часть запроса после UNION. + + +Рассмотрим использование перечисленных методов. Для простоты предположим, что +запросы делаются к MySQL. Для других СУБД способ экранирования названий таблиц, полей и значений, +используемый в примерах, может отличаться. + + +### select() + +~~~ +[php] +function select($columns='*') +~~~ + +Метод [select()|CDbCommand::select() ] задаёт часть запроса после `SELECT`. Параметр +`$columns` определяет выбираемые поля и может быть либо списком имён выбираемых +полей, разделённых запятой, либо массивом имён полей. Имена могут содержать +префиксы таблиц и псевдонимы полей. Метод автоматически экранирует имена, если +в них нет скобок (что означает использование выражения). + +Несколько примеров: + +~~~ +[php] +// SELECT * +select() +// SELECT `id`, `username` +select('id, username') +// SELECT `tbl_user`.`id`, `username` AS `name` +select('tbl_user.id, username as name') +// SELECT `id`, `username` +select(array('id', 'username')) +// SELECT `id`, count(*) as num +select(array('id', 'count(*) as num')) +~~~ + + +### selectDistinct() + +~~~ +[php] +function selectDistinct($columns) +~~~ + +Метод [selectDistinct()|CDbCommand::selectDistinct] делает то же, что и метод +[select()|CDbCommand::select() ], но добавляет к выражению `DISTINCT`. К примеру, +`selectDistinct('id, username')` сгенерирует следующий SQL: + +~~~ +SELECT DISTINCT `id`, `username` +~~~ + + +### from() + +~~~ +[php] +function from($tables) +~~~ + +Метод [from()|CDbCommand::from() ] задаёт часть запроса после `FROM`. Параметр +`$tables` определяет, из каких таблиц производится выборка, и может быть либо +списком имён таблиц, разделённых запятыми, либо массивом имён таблиц. Имена могут +содержать префиксы схемы (такие, как `public.tbl_user`) и псевдонимы таблиц +(такие, как `tbl_user u`). Метод автоматически экранирует имена, если в них нет +скобок (что означает использование подзапроса или выражения). + +Примеры: + +~~~ +[php] +// FROM `tbl_user` +from('tbl_user') +// FROM `tbl_user` `u`, `public`.`tbl_profile` `p` +from('tbl_user u, public.tbl_profile p') +// FROM `tbl_user`, `tbl_profile` +from(array('tbl_user', 'tbl_profile')) +// FROM `tbl_user`, (select * from tbl_profile) p +from(array('tbl_user', '(select * from tbl_profile) p')) +~~~ + + +### where() + +~~~ +[php] +function where($conditions, $params=array()) +~~~ + +Метод [where()|CDbCommand::where() ] задаёт часть запроса после `WHERE`. Параметр +`$conditions` определяет условия запроса, а `$params` — параметры, которые +подставляются в запрос. Значение параметра `$conditions` может быть как строкой +(например, `id=1`), так и массивом следующего вида: + +~~~ +[php] +array(operator, operand1, operand2, ...) +~~~ + +где `operator` может быть одним из следующих: + +* `and`: операнды соединяются при помощи `AND`. К примеру, `array('and', 'id=1', 'id=2')` +сгенерирует `id=1 AND id=2`. Если операнд является массивом, то он будет преобразован +в строку с использованием описанных здесь правил. К примеру, +`array('and', 'type=1', array('or', 'id=1', 'id=2'))` сгенерирует +`type=1 AND (id=1 OR id=2)`. Данный метод ничего НЕ экранирует. + +* `or`: то же, что и `and`, но для `OR`. + +* `in`: первый операнд должнен быть столбцом или выражением, второй — массивом, +содержащим список значений, в которые должно входить значение поля или выражения. +К примеру, `array('in', 'id', array(1,2,3))` сгенерирует `id IN (1,2,3)`. +Метод экранирует имя столбца и значения в списке. + +* `not in`: то же, что и `in`, но вместо `IN` используется `NOT IN`. + +* `like`: первый операнд должен быть именем поля или выражением, второй — строкой +или массивом, содержащим список значений, на которые должно быть похоже значение +поля или выражения. К примеру, `array('like', 'name', '%tester%')` сгенерирует +`name LIKE '%tester%'`. Когда список значений является массивом, генерируется несколько +`LIKE`, соединённых при помощи `AND`. Например, +`array('like', 'name', array('%test%', '%sample%'))` сгенерирует +`name LIKE '%test%' AND name LIKE '%sample%'`. Метод экранирует имена полей и +значения в списке. + +* `not like`: то же, что и `like`, но вместо `LIKE` генерируется `NOT LIKE`. + +* `or like`: то же, что и `like` но для соединения `LIKE` используется `OR`. + +* `or not like`: то же, что и `not like` но для соединения `NOT LIKE` используется `OR`. + + +Несколько примеров использования `where`: + +~~~ +[php] +// WHERE id=1 or id=2 +where('id=1 or id=2') +// WHERE id=:id1 or id=:id2 +where('id=:id1 or id=:id2', array(':id1'=>1, ':id2'=>2)) +// WHERE id=1 OR id=2 +where(array('or', 'id=1', 'id=2')) +// WHERE id=1 AND (type=2 OR type=3) +where(array('and', 'id=1', array('or', 'type=2', 'type=3'))) +// WHERE `id` IN (1, 2) +where(array('in', 'id', array(1, 2)) +// WHERE `id` NOT IN (1, 2) +where(array('not in', 'id', array(1,2))) +// WHERE `name` LIKE '%Qiang%' +where(array('like', 'name', '%Qiang%')) +// WHERE `name` LIKE '%Qiang' AND `name` LIKE '%Xue' +where(array('like', 'name', array('%Qiang', '%Xue'))) +// WHERE `name` LIKE '%Qiang' OR `name` LIKE '%Xue' +where(array('or like', 'name', array('%Qiang', '%Xue'))) +// WHERE `name` NOT LIKE '%Qiang%' +where(array('not like', 'name', '%Qiang%')) +// WHERE `name` NOT LIKE '%Qiang%' OR `name` NOT LIKE '%Xue%' +where(array('or not like', 'name', array('%Qiang%', '%Xue%'))) +~~~ + +Стоит отметить, что в случае, когда оператор содержит `like`, необходимо явно +задавать спецсимволы (вроде `%` и `_`). Если паттерн вводится пользователем, то +необходимо использовать приведённый ниже код для экранирования спецсимволов и +предотвращения интерпретации их как спецсимволов: + +~~~ +[php] +$keyword=$_GET['q']; +// экранирует символы % и _ +$keyword=strtr($keyword, array('%'=>'\%', '_'=>'\_')); +$command->where(array('like', 'title', '%'.$keyword.'%')); +~~~ + +### order() + +~~~ +[php] +function order($columns) +~~~ + +Метод [order()|CDbCommand::order() ] задаёт часть запроса после `ORDER BY`. Параметр +`$columns` определяет, по каким полям будет производиться сортировка. Поля могут +быть указаны как в виде строки, содержащей список полей и направлений +(`ASC` или `DESC`), разделённых запятыми, так и массив полей и направлений. +Имена полей могут содержать префиксы таблиц. Метод автоматически экранирует +имена полей, если они не содержат скобок (что означает использование выражения). + +Несколько примеров: + +~~~ +[php] +// ORDER BY `name`, `id` DESC +order('name, id desc') +// ORDER BY `tbl_profile`.`name`, `id` DESC +order(array('tbl_profile.name', 'id desc')) +~~~ + + +### limit() и offset() + +~~~ +[php] +function limit($limit, $offset=null) +function offset($offset) +~~~ + +Методы [limit()|CDbCommand::limit() ] и [offset()|CDbCommand::offset() ] задают +части запроса, следующие после `LIMIT` и `OFFSET`. Стоит отметить, что не все +СУБД поддерживают именно синтаксис `LIMIT` и `OFFSET`. Если он не поддерживается, +то конструктор запросов переписывает весь SQL-запрос для достижения схожего +эффекта. + +Несколько примеров: + +~~~ +[php] +// LIMIT 10 +limit(10) +// LIMIT 10 OFFSET 20 +limit(10, 20) +// OFFSET 20 +offset(20) +~~~ + + +### join() и его варианты + +~~~ +[php] +function join($table, $conditions, $params=array()) +function leftJoin($table, $conditions, $params=array()) +function rightJoin($table, $conditions, $params=array()) +function crossJoin($table) +function naturalJoin($table) +~~~ + +Метод [join()|CDbCommand::join() ] и его варианты задают порядок и параметры +соединения таблиц с использованием `INNER JOIN`, `LEFT OUTER JOIN`, +`RIGHT OUTER JOIN`, `CROSS JOIN` и `NATURAL JOIN`. Параметр `$table` определяет +таблицу, с которой производится соединение. Имя таблицы может содержать +префикс схемы или псевдоним. Метод экранирует имя таблицы, если оно не +содержит скобок, что означает использование подзапроса или выражения. Параметр +`$conditions` задаёт условие соединения. Синтаксис такой же, как и у +[where()|CDbCommand::where() ]. Через `$params` указываются параметры, подставляемые +в запрос. + +Стоит отметить, что этот метод отличается от остальных тем, что каждый следующий +его вызов добавляет часть запроса к предыдущим. + +Несколько примеров: + +~~~ +[php] +// JOIN `tbl_profile` ON user_id=id +join('tbl_profile', 'user_id=id') +// LEFT JOIN `pub`.`tbl_profile` `p` ON p.user_id=id AND type=1 +leftJoin('pub.tbl_profile p', 'p.user_id=id AND type=:type', array(':type'=>1)) +~~~ + + +### group() + +~~~ +[php] +function group($columns) +~~~ + +Метод [group()|CDbCommand::group() ] задаёт часть запроса после `GROUP BY`. +Параметр `$columns` определяет поля, по которым будет осуществляться группировка, +и может быть либо строкой разделённых запятыми полей, либо массивом полей. +Имена полей могут содержать префиксы. Метод автоматически экранирует имена полей, +если они не содержат скобок (что означает использование выражений). + +Несколько примеров: + +~~~ +[php] +// GROUP BY `name`, `id` +group('name, id') +// GROUP BY `tbl_profile`.`name`, `id` +group(array('tbl_profile.name', 'id')) +~~~ + + +### having() + +~~~ +[php] +function having($conditions, $params=array()) +~~~ + +Метод [having()|CDbCommand::having() ] задаёт часть запроса после `HAVING`. Используется +точно так же, как и [where()|CDbCommand::where() ]. + +Несколько примеров: + +~~~ +[php] +// HAVING id=1 or id=2 +having('id=1 or id=2') +// HAVING id=1 OR id=2 +having(array('or', 'id=1', 'id=2')) +~~~ + + +### union() + +~~~ +[php] +function union($sql) +~~~ + +Метод [union()|CDbCommand::union() ] задаёт часть запроса после `UNION`. Он добавляет +`$sql` к сгенерированному запросу, используя `UNION`. Несколько вызовов `union()` +добавят несколько частей запроса. + +Несколько примеров: + +~~~ +[php] +// UNION (select * from tbl_profile) +union('select * from tbl_profile') +~~~ + + +### Выполнение запросов + +После вызова приведённых выше методов для построения запроса, выполнить его можно, +используя методы DAO, как описано в разделе «[Объекты доступа к данным (DAO)](/doc/guide/database.dao)». +Например, мы можем использовать метод [CDbCommand::queryRow()] для получения строки или [CDbCommand::queryAll()] +для получения набора строк. + +Пример: + +~~~ +[php] +$users = Yii::app()->db->createCommand() + ->select('*') + ->from('tbl_user') + ->queryAll(); +~~~ + + +### Получение SQL + +Кроме выполнения запросов, которые мы создали при помощи конструктора, можно +также получить их SQL. Сделать это можно при помощи [CDbCommand::getText()]. + +~~~ +[php] +$sql = Yii::app()->db->createCommand() + ->select('*') + ->from('tbl_user') + ->text; +~~~ + +Если у запроса есть параметры, получить их можно при помощи свойства +[CDbCommand::params]. + + +### Альтернативный синтаксис построения запросов + +Иногда использование цепочек вызовов может быть неоптимальным решением. Конструктор +запросов Yii позволяет создать запрос путём задания полей объекта. Для каждого +метода конструктора запросов есть соответствующее поле с таким же именем. +Присвоение значения полю эквивалентно вызову соответствующего метода. К примеру, +приведённые ниже строки эквивалентны, если `$command` — объект [CDbCommand]: + +~~~ +[php] +$command->select(array('id', 'username')); +$command->select = array('id', 'username'); +~~~ + +Более того, метод [CDbConnection::createCommand()] может принимать массив в +качестве аргумента. Пары имя-значение из массива будут использованы для инициализации +полей созданного экземпляра [CDbCommand]. Таким образом, для построения запроса можно +использовать следующий код: + +~~~ +[php] +$row = Yii::app()->db->createCommand(array( + 'select' => array('id', 'username'), + 'from' => 'tbl_user', + 'where' => 'id=:id', + 'params' => array(':id'=>1), +))->queryRow(); +~~~ + + +### Построение нескольких запросов + +Для построения нескольких запросов экземпляр [CDbCommand] может быть +использован несколько раз. Перед тем как построить новый запрос, необходимо +вызвать метод [CDbCommand::reset()] для очистки предыдушего запроса. Пример: + +~~~ +[php] +$command = Yii::app()->db->createCommand(); +$users = $command->select('*')->from('tbl_users')->queryAll(); +$command->reset(); // очищаем предыдущий запрос +$posts = $command->select('*')->from('tbl_posts')->queryAll(); +~~~ + + +Построение запросов для изменения данных +---------------------------------------- + +К запросам для изменения данных относятся SQL-запросы для вставки, обновления и +удаления данных из базы. В конструкторе запросов есть соответствующие методы +`insert`, `update` и `delete`. В отличие от запросов получения данных, описанных +выше, данные методы строят полный SQL-запрос и тут же выполняют его. + +* [insert()|CDbCommand::insert]: вставляет строку в таблицу; +* [update()|CDbCommand::update]: обновляет данные в таблице; +* [delete()|CDbCommand::delete]: удаляет данные из таблицы. + + +### insert() + +~~~ +[php] +function insert($table, $columns) +~~~ + +Метод [insert()|CDbCommand::insert] строит и выполняет SQL-запрос `INSERT`. Параметр +`$table` указывает, в какую таблицу производится вставка, а `$columns` является +массивом пар имя-значение полей для вставки. Метод экранирует имя таблицы и использует +параметры для вставляемых значений. + +Пример: + +~~~ +[php] +// строим и выполняем следующий SQL: +// INSERT INTO `tbl_user` (`name`, `email`) VALUES (:name, :email) +$command->insert('tbl_user', array( + 'name'=>'Tester', + 'email'=>'tester@example.com', +)); +~~~ + + +### update() + +~~~ +[php] +function update($table, $columns, $conditions='', $params=array()) +~~~ + +Метод [update()|CDbCommand::update] строит и выполняет SQL-запрос `UPDATE`. Параметр +`$table` указывает обновляемую таблицу; `$columns` является массивом пар имя-значение, +задающим значения обновляемых полей; `$conditions` и `$params` эквивалентны аналогичным +параметрам в [where()|CDbCommand::where() ] и определяют часть запроса `UPDATE` после +`WHERE`. Метод экранирует имя таблицы и использует параметры для обновляемых значений. + +Пример: + +~~~ +[php] +// строим и выполняем следующий SQL: +// UPDATE `tbl_user` SET `name`=:name WHERE id=:id +$command->update('tbl_user', array( + 'name'=>'Tester', +), 'id=:id', array(':id'=>1)); +~~~ + + +### delete + +~~~ +[php] +function delete($table, $conditions='', $params=array()) +~~~ + +Метод [delete()|CDbCommand::delete] строит и выполняет SQL-запрос `DELETE`. Параметр +`$table` указывает таблицу, из которой удаляются записи; `$conditions` и `$params` +эквивалентны аналогичным параметрам в [where()|CDbCommand::where() ], которые определяют +часть запроса `DELETE` после `WHERE`. Метод экранирует имя таблицы. + +Пример: + +~~~ +[php] +// строим и выполняем следующий SQL: +// DELETE FROM `tbl_user` WHERE id=:id +$command->delete('tbl_user', 'id=:id', array(':id'=>1)); +~~~ + +Построение запросов изменения схемы +----------------------------------- + +Кроме обычных запросов для получения данных и работы с ними, конструктор +может собирать и выполнять SQL-запросы для изменения схемы базы данных. +Поддерживаются следующие запросы: + +* [createTable()|CDbCommand::createTable]: создание таблицы; +* [renameTable()|CDbCommand::renameTable]: переименование таблицы; +* [dropTable()|CDbCommand::dropTable]: удаление таблицы; +* [truncateTable()|CDbCommand::truncateTable]: очистка таблицы; +* [addColumn()|CDbCommand::addColumn]: добавление нового поля в таблицу; +* [renameColumn()|CDbCommand::renameColumn]: переименование поля таблицы; +* [alterColumn()|CDbCommand::alterColumn]: изменение поля таблицы; +* [addForeignKey()|CDbCommand::addForeignKey]: добавление внешнего ключа (доступно с версии 1.1.6) +* [dropForeignKey()|CDbCommand::dropForeignKey]: удаление внешнего ключа (доступно с версии 1.1.6) +* [dropColumn()|CDbCommand::dropColumn]: удаление поля таблицы; +* [createIndex()|CDbCommand::createIndex]: создание индекса; +* [dropIndex()|CDbCommand::dropIndex]: удаление индекса. + +> Info|Информация: Несмотря на то что в разных СУБД запросы для измения схемы +различаются, конструктор запросов предоставляет единый интерфейс для их создания. +Это упрощает задачу мигрирования с одной СУБД на другую. + + +### Абстрактные типы данных + +Конструктор запросов вводит ряд абстрактных типов данных, которые можно +использовать для описания полей таблицы. В отличие от реальных типов данных, +которые отличаются в разных СУБД, абстрактные типы не зависят от СУБД. +При использовании их для описания типов полей конструктор запросов конвертирует +абстрактные типы в соответствующие им реальные. + +Конструктор запросов поддерживает следующие абстрактные типы: + +* `pk`: обычный первичный ключ. Для MySQL конвертируется в `int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY`; +* `string`: строка. Для MySQL конвертируется в `varchar(255)`; +* `text`: текстовый тип (длинная строка). Для MySQL конвертируется в `text`; +* `integer`: целое. Для MySQL конвертируется в `int(11)`; +* `float`: число с плавающей точкой. Для MySQL конвертируется в `float`; +* `decimal`: десятичное число. Для MySQL конвертируется в `decimal`; +* `datetime`: дата и время. Для MySQL конвертируется в `datetime`; +* `timestamp`: метка времени. Для MySQL конвертируется в `timestamp`; +* `time`: время. Для MySQL конвертируется в `time`; +* `date`: дата. Для MySQL конвертируется в `date`; +* `binary`: бинарный. Для MySQL конвертируется в `blob`; +* `boolean`: булевый. Для MySQL конвертируется в `tinyint(1)`; +* `money`: деньги/валюта. Для MySQL конвертируется в `decimal(19,4)`. Доступен с версии 1.1.8. + + +###createTable() + +~~~ +[php] +function createTable($table, $columns, $options=null) +~~~ + +Метод [createTable()|CDbCommand::createTable] строит и выполняет SQL-запрос для +создания таблицы. Параметр `$table` задаёт имя создаваемой таблицы. Параметр +`$columns` определяет поля новой таблицы. Они должны быть указаны в виде пар +имя-определение (т.е. `'username'=>'string'`). Параметр `$options` задаёт +дополнительный фрагмент SQL, который будет добавлен к генерируемому SQL. +Конструктор запроса экранирует имя таблицы и имена полей. + +Для указания определения поля можно использовать один из абстрактных типов данных, описанных выше. +Конструктор конвертирует абстрактный тип данных в соответствующий реальный тип данных в соответствии с используемой СУБД. +Например, `string` в случае MySQL преобразуется в `varchar(255)`. + +Определение поля также может содержать неабстрактный тип данных и спецификаций. +Они будут подставлены в результирующий SQL-запрос без каких-либо изменений. К примеру, +`point` не является абстрактным типом данных, и при использовании в определении +поля он будет включён в итоговый SQL без изменений. `string NOT NULL` будет +конвертирован в `varchar(255) NOT NULL` (т.е. конвертируются только абстрактный +тип `string`). + +Пример создания таблицы: + +~~~ +[php] +// CREATE TABLE `tbl_user` ( +// `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, +// `username` varchar(255) NOT NULL, +// `location` point +// ) ENGINE=InnoDB +createTable('tbl_user', array( + 'id' => 'pk', + 'username' => 'string NOT NULL', + 'location' => 'point', +), 'ENGINE=InnoDB') +~~~ + + +###renameTable() + +~~~ +[php] +function renameTable($table, $newName) +~~~ + +Метод [renameTable()|CDbCommand::renameTable] строит и выполняет SQL-запрос для +переименования таблицы. Параметр `$table` задаёт имя изменяемой таблицы. +Параметр `$newName` определяет новое имя таблицы. Конструктор запроса +экранирует имена таблицы. + +Пример переименования таблицы: + +~~~ +[php] +// RENAME TABLE `tbl_users` TO `tbl_user` +renameTable('tbl_users', 'tbl_user') +~~~ + + +###dropTable() + +~~~ +[php] +function dropTable($table) +~~~ + +Метод [dropTable()|CDbCommand::dropTable] строит и выполняет SQL-запрос для удаления +таблицы. Параметр `$table` определяет имя удаляемой таблицы. Конструктор запроса +экранирует имя таблицы. + +Пример удаления таблицы: + +~~~ +[php] +// DROP TABLE `tbl_user` +dropTable('tbl_user') +~~~ + +###truncateTable() + +~~~ +[php] +function truncateTable($table) +~~~ + +Метод [truncateTable()|CDbCommand::truncateTable] строит и выполняет SQL-запрос для +очистки всех данных таблицы. Параметр `$table` определяет имя очищаемой таблицы. +Конструктор запроса экранирует имя таблицы. + +Пример очистки таблицы: + +~~~ +[php] +// TRUNCATE TABLE `tbl_user` +truncateTable('tbl_user') +~~~ + + +###addColumn() + +~~~ +[php] +function addColumn($table, $column, $type) +~~~ + +Метод [addColumn()|CDbCommand::addColumn] строит и выполняет SQL-запрос для +добавления нового поля таблицы. Параметр `$table` задаёт имя таблицы, к которой +будет добавлено новое поле. Параметр `$column` — имя нового поля. `$type` +задаёт тип поля. Определение поля может содержать абстрактный тип данных, +как уже было описано в подразделе «createTable». Конструктор запроса +экранирует имя таблицы и имя поля. + +Пример добавления поля: + +~~~ +[php] +// ALTER TABLE `tbl_user` ADD `email` varchar(255) NOT NULL +addColumn('tbl_user', 'email', 'string NOT NULL') +~~~ + + +###dropColumn() + +~~~ +[php] +function dropColumn($table, $column) +~~~ + +Метод [dropColumn()|CDbCommand::dropColumn] строит и выполняет SQL-запрос для +удаления поля таблицы. Параметр `$table` задаёт имя таблицы, из которой удаляется +поле. Параметр `$column` определяет имя удаляемого поля. Конструктор запроса +экранирует имя таблицы и имя поля. + +Пример удаления поля таблицы: + +~~~ +[php] +// ALTER TABLE `tbl_user` DROP COLUMN `location` +dropColumn('tbl_user', 'location') +~~~ + + +###renameColumn() + +~~~ +[php] +function renameColumn($table, $name, $newName) +~~~ + +Метод [renameColumn()|CDbCommand::renameColumn] строит и выполняет SQL-запрос для +переименования поля таблицы. Параметр `$table` задаёт имя таблицы, поле которой +будет переименовано. Параметр `$name` определяет имя изменяемого поля. +`$newName` задаёт новое имя поля. Конструктор запроса экранирует имя таблицы и имена полей. + +Пример переименования поля таблицы: + +~~~ +[php] +// ALTER TABLE `tbl_users` CHANGE `name` `username` varchar(255) NOT NULL +renameColumn('tbl_user', 'name', 'username') +~~~ + + +###alterColumn() + +~~~ +[php] +function alterColumn($table, $column, $type) +~~~ + +Метод [alterColumn()|CDbCommand::alterColumn] строит и выполняет SQL-запрос для +изменения поля таблицы. Параметр `$table` задаёт имя таблицы, поле которой +будет изменено. Параметр `$column` определяет имя изменяемого поля. `$type` +задаёт новое определение поля, которое может содержать абстрактный тип данных, +как было описано в подразделе «createTable». Конструктор запросов экранирует имя +таблицы и имя поля. + +Пример изменения поля таблицы: + +~~~ +[php] +// ALTER TABLE `tbl_user` CHANGE `username` `username` varchar(255) NOT NULL +alterColumn('tbl_user', 'username', 'string NOT NULL') +~~~ + + + + +###addForeignKey() + +~~~ +[php] +function addForeignKey($name, $table, $columns, + $refTable, $refColumns, $delete=null, $update=null) +~~~ + +Метод [addForeignKey()|CDbCommand::addForeignKey] строит и выполняет SQL-запрос для +добавления внешнего ключа в таблицу. Параметр `$name` задаёт имя внешнего ключа. +Параметры `$table` и `$columns` определяют имя таблицы и имя поля внешнего +ключа. Если указаны несколько полей, то они должны быть разделены запятыми. +Параметры `$refTable` и `$refColumns` определяют имя таблицы и имя поля, на которое +ссылается внешний ключ. Параметры `$delete` и `$update` задают SQL-опции +`ON DELETE` и `ON UPDATE` соответственно. Большинство СУБД поддерживают +следующие опции: `RESTRICT`, `CASCADE`, `NO ACTION`, `SET DEFAULT` и `SET NULL`. +Конструктор запросов экранирует имя таблицы, имя индекса и имена полей. + +Пример добавления внешнего ключа: + +~~~ +[php] +// ALTER TABLE `tbl_profile` ADD CONSTRAINT `fk_profile_user_id` +// FOREIGN KEY (`user_id`) REFERENCES `tbl_user` (`id`) +// ON DELETE CASCADE ON UPDATE CASCADE +addForeignKey('fk_profile_user_id', 'tbl_profile', 'user_id', + 'tbl_user', 'id', 'CASCADE', 'CASCADE') +~~~ + + +###dropForeignKey() + +~~~ +[php] +function dropForeignKey($name, $table) +~~~ + +Метод [dropForeignKey()|CDbCommand::dropForeignKey] строит и выполняет SQL-запрос +для удаления внешнего ключа. Параметр `$name` задаёт имя внешнего ключа, +который требуется удалить. Параметр `$table` — имя таблицы, из которой +удаляется ключ. Конструктор запроса экранирует имя таблицы и имя ключа. + +Пример удаления внешнего ключа: + +~~~ +[php] +// ALTER TABLE `tbl_profile` DROP FOREIGN KEY `fk_profile_user_id` +dropForeignKey('fk_profile_user_id', 'tbl_profile') +~~~ + + +###createIndex() + +~~~ +[php] +function createIndex($name, $table, $column, $unique=false) +~~~ + +Метод [createIndex()|CDbCommand::createIndex] строит и выполняет SQL-запрос +для создания индекса. Параметр `$name` задаёт имя индекса, который будет создан. +Параметр `$table` — имя таблицы, в которой создаётся индекс. Параметр `$column` +— имя индексируемого поля. Параметр `$unique` определяет, будет ли +индекс уникальным. Если индекс состоит из нескольких полей, то они разделяются +запятыми. Конструктор запросов экранирует имя таблицы, имя индекса и имена полей. + +Пример создания индекса: + +~~~ +[php] +// CREATE INDEX `idx_username` ON `tbl_user` (`username`) +createIndex('idx_username', 'tbl_user', 'username') +~~~ + + +###dropIndex() + +~~~ +[php] +function dropIndex($name, $table) +~~~ + +Метод [dropIndex()|CDbCommand::dropIndex] строит и выполняет SQL-запрос для +удаления индекса. Параметр `$name` задаёт имя удаляемого индекса. +Параметр `$table` — имя таблицы, из которой удаляется индекс. Конструктор +запроса экранирует имя таблицы и имя индекса. + +Пример удаления индекса: + +~~~ +[php] +// DROP INDEX `idx_username` ON `tbl_user` +dropIndex('idx_username', 'tbl_user') ~~~ \ No newline at end of file diff --git a/docs/guide/ru/index.txt b/docs/guide/ru/index.txt index 88597b35c..9e073f2f5 100644 --- a/docs/guide/ru/index.txt +++ b/docs/guide/ru/index.txt @@ -1,16 +1,16 @@ -Полное руководство по Yii -========================= - -Данное руководство выпущено в соответствии с [положениями о документации Yii](http://www.yiiframework.com/doc/terms/). - -Переводчики ------------ -- Константин Мирин, Konstantin Mirin ([programmersnotes.info](http://programmersnotes.info/)) -- Александр Макаров, Sam Dark ([rmcreative.ru](http://rmcreative.ru/)) -- Алексей Лукьяненко, Caveman ([caveman.ru](http://caveman.ru/)) -- Евгений Халецкий, xenon -- Александр Овчинников, multif -- Сергей Кузнецов, cr0t ([summercode.ru](http://summercode.ru/)) -- Александр Кожевников, Bethrezen ([bethrezen.ru](http://bethrezen.ru/)) - +Полное руководство по Yii +========================= + +Данное руководство выпущено в соответствии с [положениями о документации Yii](http://www.yiiframework.com/doc/terms/). + +Переводчики +----------- +- Константин Мирин, Konstantin Mirin ([programmersnotes.info](http://programmersnotes.info/)) +- Александр Макаров, Sam Dark ([rmcreative.ru](http://rmcreative.ru/)) +- Алексей Лукьяненко, Caveman ([caveman.ru](http://caveman.ru/)) +- Евгений Халецкий, xenon +- Александр Овчинников, multif +- Сергей Кузнецов, cr0t ([summercode.ru](http://summercode.ru/)) +- Александр Кожевников, Bethrezen ([bethrezen.ru](http://bethrezen.ru/)) + © 2008—2013, Yii Software LLC. \ No newline at end of file diff --git a/docs/guide/ru/quickstart.apache-nginx-config.txt b/docs/guide/ru/quickstart.apache-nginx-config.txt index ccc9711da..dbbc25535 100644 --- a/docs/guide/ru/quickstart.apache-nginx-config.txt +++ b/docs/guide/ru/quickstart.apache-nginx-config.txt @@ -1,88 +1,88 @@ -Конфигурация веб-серверов Apache и Nginx -======================================== - -Apache ------- - -Yii готов к работе с настроенным по умолчанию Apache. Файлы -.htaccess во фреймворке и директориях приложения ограничивают доступ к -некоторым ресурсам. Для сокрытия файла точки входа (обычно это `index.php`) в -URL можно добавить инструкцию для модуля `mod_rewrite` в файл `.htaccess` -в корневой директории приложения или в настройках виртуальных хостов: - -~~~ -RewriteEngine on - -# не позволять httpd отдавать файлы, начинающиеся с точки (.htaccess, .svn, .git и прочие) -RedirectMatch 403 /\..*$ -# если директория или файл существуют, использовать их напрямую -RewriteCond %{REQUEST_FILENAME} !-f -RewriteCond %{REQUEST_FILENAME} !-d -# иначе отправлять запрос на файл index.php -RewriteRule . index.php -~~~ - - -Nginx ------ - -Yii можно использовать с веб-сервером [Nginx](http://nginx.org/ru/) и PHP с -помощью [FPM SAPI](http://php.net/install.fpm). Ниже приведён пример простой -конфигурации хоста. Он определяет файл точки входа и заставляет Yii -перехватывать все запросы к несуществующим файлам, что позволяет создавать -человекопонятные URL-адреса. - -~~~ -server { - set $host_path "/www/mysite"; - access_log /www/mysite/log/access.log main; - - server_name mysite; - root $host_path/htdocs; - set $yii_bootstrap "index.php"; - - charset utf-8; - - location / { - index index.html $yii_bootstrap; - try_files $uri $uri/ /$yii_bootstrap?$args; - } - - location ~ ^/(protected|framework|themes/\w+/views) { - deny all; - } - - # отключаем обработку запросов фреймворком к несуществующим статичным файлам - location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { - try_files $uri =404; - } - - # передаем PHP-скрипт серверу FastCGI, прослушивающему адрес 127.0.0.1:9000 - location ~ \.php { - fastcgi_split_path_info ^(.+\.php)(.*)$; - - # позволяем yii перехватывать запросы к несуществующим PHP-файлам - set $fsn /$yii_bootstrap; - if (-f $document_root$fastcgi_script_name){ - set $fsn $fastcgi_script_name; - } - - fastcgi_pass 127.0.0.1:9000; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fsn; - - # PATH_INFO и PATH_TRANSLATED могут быть опущены, но стандарт RFC 3875 определяет для CGI - fastcgi_param PATH_INFO $fastcgi_path_info; - fastcgi_param PATH_TRANSLATED $document_root$fsn; - } - - # не позволять nginx отдавать файлы, начинающиеся с точки (.htaccess, .svn, .git и прочие) - location ~ /\. { - deny all; - access_log off; - log_not_found off; - } -} -~~~ -Используя данную конфигурацию, можно в файле php.ini установить опцию +Конфигурация веб-серверов Apache и Nginx +======================================== + +Apache +------ + +Yii готов к работе с настроенным по умолчанию Apache. Файлы +.htaccess во фреймворке и директориях приложения ограничивают доступ к +некоторым ресурсам. Для сокрытия файла точки входа (обычно это `index.php`) в +URL можно добавить инструкцию для модуля `mod_rewrite` в файл `.htaccess` +в корневой директории приложения или в настройках виртуальных хостов: + +~~~ +RewriteEngine on + +# не позволять httpd отдавать файлы, начинающиеся с точки (.htaccess, .svn, .git и прочие) +RedirectMatch 403 /\..*$ +# если директория или файл существуют, использовать их напрямую +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +# иначе отправлять запрос на файл index.php +RewriteRule . index.php +~~~ + + +Nginx +----- + +Yii можно использовать с веб-сервером [Nginx](http://nginx.org/ru/) и PHP с +помощью [FPM SAPI](http://php.net/install.fpm). Ниже приведён пример простой +конфигурации хоста. Он определяет файл точки входа и заставляет Yii +перехватывать все запросы к несуществующим файлам, что позволяет создавать +человекопонятные URL-адреса. + +~~~ +server { + set $host_path "/www/mysite"; + access_log /www/mysite/log/access.log main; + + server_name mysite; + root $host_path/htdocs; + set $yii_bootstrap "index.php"; + + charset utf-8; + + location / { + index index.html $yii_bootstrap; + try_files $uri $uri/ /$yii_bootstrap?$args; + } + + location ~ ^/(protected|framework|themes/\w+/views) { + deny all; + } + + # отключаем обработку запросов фреймворком к несуществующим статичным файлам + location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { + try_files $uri =404; + } + + # передаем PHP-скрипт серверу FastCGI, прослушивающему адрес 127.0.0.1:9000 + location ~ \.php { + fastcgi_split_path_info ^(.+\.php)(.*)$; + + # позволяем yii перехватывать запросы к несуществующим PHP-файлам + set $fsn /$yii_bootstrap; + if (-f $document_root$fastcgi_script_name){ + set $fsn $fastcgi_script_name; + } + + fastcgi_pass 127.0.0.1:9000; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fsn; + + # PATH_INFO и PATH_TRANSLATED могут быть опущены, но стандарт RFC 3875 определяет для CGI + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param PATH_TRANSLATED $document_root$fsn; + } + + # не позволять nginx отдавать файлы, начинающиеся с точки (.htaccess, .svn, .git и прочие) + location ~ /\. { + deny all; + access_log off; + log_not_found off; + } +} +~~~ +Используя данную конфигурацию, можно в файле php.ini установить опцию cgi.fix_pathinfo=0 во избежание множества нежелательных системных вызовов stat(). \ No newline at end of file diff --git a/docs/guide/ru/quickstart.first-app-yiic.txt b/docs/guide/ru/quickstart.first-app-yiic.txt index e2248949b..ac577c627 100644 --- a/docs/guide/ru/quickstart.first-app-yiic.txt +++ b/docs/guide/ru/quickstart.first-app-yiic.txt @@ -1,89 +1,89 @@ -Генерация кода при помощи консоли (устаревшее) -============================================== - -> Note|Примечание: Генераторы кода `yiic shell` считаются устаревшими, начиная с -> версии 1.1.2. Пожалуйста, используйте более мощные расширяемые веб-генераторы -> [Gii](/doc/guide/topics.gii). - -Откроем консоль и выполним следующие команды: - -~~~ -% cd WebRoot/testdrive -% protected/yiic shell -Yii Interactive Tool v1.1 -Please type 'help' for help. Type 'exit' to quit. ->> model User tbl_user - generate models/User.php - generate fixtures/tbl_user.php - generate unit/UserTest.php - -The following model classes are successfully generated: - User - -If you have a 'db' database connection, you can test these models now with: - $model=User::model()->find(); - print_r($model); - ->> crud User - generate UserController.php - generate UserTest.php - mkdir D:/testdrive/protected/views/user - generate create.php - generate update.php - generate index.php - generate view.php - generate admin.php - generate _form.php - generate _view.php - -Crud 'user' has been successfully created. You may access it via: -http://hostname/path/to/index.php?r=user -~~~ - -В примере выше мы использовали команду `shell` утилиты `yiic` для взаимодействия с -созданным каркасом приложения. В командной строке мы вводим две команды: `model User tbl_user` и `crud User`. -Команда `model` автоматически создает класс модели `User`, основываясь на структуре таблицы `tbl_user`, -а команда `crud` генерирует класс контроллера и файлы представлений, которые обеспечивают -выполнение соответствующих операций CRUD. - -> Note|Примечание: Даже если проверка соответствия требованиям показывает, что расширение PDO и драйвер PDO, соответствующий используемой базе -> данных, включены, могут возникать ошибки типа «…could not find driver». В этом случае необходимо запустить утилиту `yiic` следующим образом: -> -> ~~~ -> % php -c path/to/php.ini protected/yiic.php shell -> ~~~ -> -> где `path/to/php.ini` — путь до файла PHP ini - - -Давайте порадуемся нашим трудам, перейдя по следующему URL: - -~~~ -http://hostname/testdrive/index.php?r=user -~~~ - -Мы увидим страницу со списком пользователей из таблицы `tbl_user`. Поскольку наша таблица пустая, то записей в ней не будет. -Кликнем по кнопке `Create User` и, если мы еще не авторизованы, отобразится страница авторизации. -Затем загрузится форма добавления нового пользователя. Заполним её и нажмем кнопку `Create`. -Если при заполнении формы были допущены ошибки, мы увидим аккуратное сообщение об ошибке. - -Вернувшись назад к списку пользователей, мы должны увидеть только что созданного пользователя. -Повторите описанную операцию и добавьте ещё несколько пользователей. Обратите внимание, что при значительном -количестве пользователей для их отображения на одной странице список будет автоматически разбиваться на страницы. -Выполнив вход в качестве администратора (`admin/admin`), -можно увидеть страницу управления пользователями по адресу: - -~~~ -http://hostname/testdrive/index.php?r=user/admin -~~~ - -Появится аккуратная таблица пользователей. Кликнув на название одного из полей заголовка таблицы, -можно упорядочить записи по значениям соответствующего столбца. -Для просмотра, редактирования или удаления записей можно воспользоваться кнопками в соответствующих строках таблицы. -Также можно переходить на разные страницы, фильтровать результаты и производить поиск по ним. - -Всё это не требует написания ни одной строчки кода! - -![Страница управления пользователями](first-app6.png) - +Генерация кода при помощи консоли (устаревшее) +============================================== + +> Note|Примечание: Генераторы кода `yiic shell` считаются устаревшими, начиная с +> версии 1.1.2. Пожалуйста, используйте более мощные расширяемые веб-генераторы +> [Gii](/doc/guide/topics.gii). + +Откроем консоль и выполним следующие команды: + +~~~ +% cd WebRoot/testdrive +% protected/yiic shell +Yii Interactive Tool v1.1 +Please type 'help' for help. Type 'exit' to quit. +>> model User tbl_user + generate models/User.php + generate fixtures/tbl_user.php + generate unit/UserTest.php + +The following model classes are successfully generated: + User + +If you have a 'db' database connection, you can test these models now with: + $model=User::model()->find(); + print_r($model); + +>> crud User + generate UserController.php + generate UserTest.php + mkdir D:/testdrive/protected/views/user + generate create.php + generate update.php + generate index.php + generate view.php + generate admin.php + generate _form.php + generate _view.php + +Crud 'user' has been successfully created. You may access it via: +http://hostname/path/to/index.php?r=user +~~~ + +В примере выше мы использовали команду `shell` утилиты `yiic` для взаимодействия с +созданным каркасом приложения. В командной строке мы вводим две команды: `model User tbl_user` и `crud User`. +Команда `model` автоматически создает класс модели `User`, основываясь на структуре таблицы `tbl_user`, +а команда `crud` генерирует класс контроллера и файлы представлений, которые обеспечивают +выполнение соответствующих операций CRUD. + +> Note|Примечание: Даже если проверка соответствия требованиям показывает, что расширение PDO и драйвер PDO, соответствующий используемой базе +> данных, включены, могут возникать ошибки типа «…could not find driver». В этом случае необходимо запустить утилиту `yiic` следующим образом: +> +> ~~~ +> % php -c path/to/php.ini protected/yiic.php shell +> ~~~ +> +> где `path/to/php.ini` — путь до файла PHP ini + + +Давайте порадуемся нашим трудам, перейдя по следующему URL: + +~~~ +http://hostname/testdrive/index.php?r=user +~~~ + +Мы увидим страницу со списком пользователей из таблицы `tbl_user`. Поскольку наша таблица пустая, то записей в ней не будет. +Кликнем по кнопке `Create User` и, если мы еще не авторизованы, отобразится страница авторизации. +Затем загрузится форма добавления нового пользователя. Заполним её и нажмем кнопку `Create`. +Если при заполнении формы были допущены ошибки, мы увидим аккуратное сообщение об ошибке. + +Вернувшись назад к списку пользователей, мы должны увидеть только что созданного пользователя. +Повторите описанную операцию и добавьте ещё несколько пользователей. Обратите внимание, что при значительном +количестве пользователей для их отображения на одной странице список будет автоматически разбиваться на страницы. +Выполнив вход в качестве администратора (`admin/admin`), +можно увидеть страницу управления пользователями по адресу: + +~~~ +http://hostname/testdrive/index.php?r=user/admin +~~~ + +Появится аккуратная таблица пользователей. Кликнув на название одного из полей заголовка таблицы, +можно упорядочить записи по значениям соответствующего столбца. +Для просмотра, редактирования или удаления записей можно воспользоваться кнопками в соответствующих строках таблицы. +Также можно переходить на разные страницы, фильтровать результаты и производить поиск по ним. + +Всё это не требует написания ни одной строчки кода! + +![Страница управления пользователями](first-app6.png) + ![Страница добавления нового пользователя](first-app7.png) \ No newline at end of file diff --git a/docs/guide/ru/quickstart.first-app.txt b/docs/guide/ru/quickstart.first-app.txt index 9a9ff40ef..3c5431124 100644 --- a/docs/guide/ru/quickstart.first-app.txt +++ b/docs/guide/ru/quickstart.first-app.txt @@ -1,250 +1,250 @@ -Создание первого приложения -=========================== - -В этом разделе мы расскажем, как создать наше первое приложение. -Для создания нового приложения мы будем использовать `yiic` (консольную утилиту), для -генерации кода — `Gii` (мощный веб кодогенератор). Будем считать для удобства, -что `YiiRoot` — это директория, куда установлен Yii, а `WebRoot` — корневая -директория веб-сервера. - -Запускаем `yiic` в консоли со следующими параметрами: -~~~ -% YiiRoot/framework/yiic webapp WebRoot/testdrive -~~~ -> Note|Примечание: При использовании `yiic` на Mac OS, -> Linux или Unix вам может понадобиться изменить права доступа -> для файла `yiic`, чтобы сделать его исполняемым. -> Альтернативный вариант запуска утилиты представлен ниже: -> -> ~~~ -> % cd WebRoot -> % php YiiRoot/framework/yiic.php webapp testdrive -> ~~~ - -В результате в директории `WebRoot/testdrive` будет создан каркас приложения. - -Созданное приложение — хорошая отправная точка для добавления необходимого функционала, -так как оно уже содержит все необходимые директории и файлы. -Не написав ни единой строчки кода, мы уже можем протестировать наше первое Yii-приложение, перейдя в браузере по следующему URL: - -~~~ -http://hostname/testdrive/index.php -~~~ - -Приложение содержит четыре страницы: главную, страницу «о проекте», страницу обратной связи и страницу авторизации. -Страница обратной связи содержит форму для отправки вопросов и предложений, а страница авторизации -позволяет пользователю аутентифицироваться и получить доступ к закрытой части сайта (см. рисунки ниже). - -![Главная страница](first-app1.png) - -![Страница обратной связи](first-app2.png) - -![Страница обратной связи с ошибками ввода](first-app3.png) - -![Страница обратной связи с успешно отправленной формой](first-app4.png) - -![Страница авторизации](first-app5.png) - - -Наше приложение имеет следующую структуру директорий. -Подробное описание этой структуры можно найти в [соглашениях](/doc/guide/basics.convention#directory). - -~~~ -testdrive/ - index.php скрипт инициализации приложения - index-test.php скрипт инициализации функциональных тестов - assets/ содержит файлы ресурсов - css/ содержит CSS-файлы - images/ содержит картинки - themes/ содержит темы оформления приложения - protected/ содержит защищённые файлы приложения - yiic скрипт yiic - yiic.bat скрипт yiic для Windows - yiic.php PHP-скрипт yiic - commands/ содержит команды 'yiic' - shell/ содержит команды 'yiic shell' - components/ содержит компоненты для повторного использования - Controller.php класс базового контроллера - UserIdentity.php класс 'UserIdentity' для аутентификации - config/ содержит конфигурационные файлы - console.php файл конфигурации консоли - main.php файл конфигурации веб-приложения - test.php файл конфигурации функциональных тестов - controllers/ содержит файлы классов контроллеров - SiteController.php класс контроллера по умолчанию - data/ содержит пример базы данных - schema.mysql.sql схема БД для MySQL - schema.sqlite.sql схема БД для SQLite - testdrive.db файл БД для SQLite - extensions/ содержит сторонние расширения - messages/ содержит переведённые сообщения - models/ содержит файлы классов моделей - LoginForm.php модель формы для действия 'login' - ContactForm.php модель формы для действия 'contact' - runtime/ содержит временные файлы - tests/ содержит тесты - views/ содержит файлы представлений контроллеров и файлы макетов (layout) - layouts/ содержит файлы представлений макетов - main.php общая для всех страниц разметка - column1.php разметка для страниц с одной колонкой - column2.php разметка для страниц с двумя колонками - site/ содержит файлы представлений для контроллера 'site' - pages/ статические страницы - about.php страница «о проекте» - contact.php файл представления для действия 'contact' - error.php файл представления для действия 'error' (отображение ошибок) - index.php файл представления для действия 'index' - login.php файл представления для действия 'login' -~~~ - -Описанный выше генератор может создать файлы, необходимые при работе с -системой контроля версий Git. Приведённая далее команда создаст все необходимые -`.gitignore` (содержимое `assets` и `runtime` не должно оказаться в репозитории) -и `.gitkeep` (важные директории включаем в репозиторий даже если они пустые): - -~~~ -% YiiRoot/framework/yiic webapp WebRoot/testdrive git -~~~ - -Ещё одна поддерживаемая система контроля версий — Mercurial. Если вы пользуетесь -ей, передайте третьим параметром `hg`. Данная возможность доступна с версии 1.1.11. - - -Соединение с базой данных ----------------------- -Большинство веб-приложений используют базы данных, и наше приложение не исключение. Для использования базы данных -необходимо объяснить приложению, как к ней подключиться. -Это делается в конфигурационном файле `WebRoot/testdrive/protected/config/main.php`. -Например, так: - -~~~ -[php] -return array( - … - 'components'=>array( - … - 'db'=>array( - 'connectionString'=>'sqlite:protected/data/testdrive.db', - ), - ), - … -); -~~~ - -В приведённом выше коде указано, что приложение должно подключиться к базе данных SQLite -`WebRoot/testdrive/protected/data/testdrive.db` как только это понадобится. Отметим, что -база данных SQLite уже включена в сгенерированное приложение. В этой базе имеется только -одна таблица `tbl_user`: - -~~~ -[sql] -CREATE TABLE tbl_user ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - username VARCHAR(128) NOT NULL, - password VARCHAR(128) NOT NULL, - email VARCHAR(128) NOT NULL -); -~~~ - -Если вы хотите использовать базу данных MySQL, то вы можете воспользоваться файлом -`WebRoot/testdrive/protected/data/schema.mysql.sql` для её создания. - -> Note|Примечание: Для работы с базой данных Yii требуется расширение PHP PDO -и соответствующий драйвер PDO. Для тестового приложения необходимо подключить -расширения `php_pdo` и `php_pdo_sqlite`. - -Реализация операций CRUD ------------------------- - -А теперь самое интересное. Мы бы хотели добавить операции CRUD -(создание, чтение, обновление и удаление) для только что созданной таблицы `tbl_user` — -это часто необходимо при разработке реальных приложений. Вместо ручного написания кода мы -воспользуемся веб кодогенератором `Gii`. - -> Info|Информация: Gii доступен, начиная с версии 1.1.2. Ранее для тех же целей -> использовался уже упомянутый `yiic`. Подробнее `yiic` описан в разделе -«[генерация CRUD при помощи yiic shell](/doc/guide/quickstart.first-app-yiic)». - - -### Настройка Gii - -Для того чтобы использовать Gii, нужно отредактировать -[файл конфигурации приложения](/doc/guide/basics.application#application-configuration) -`WebRoot/testdrive/protected/config/main.php`: - -~~~ -[php] -return array( - … - 'import'=>array( - 'application.models.*', - 'application.components.*', - ), - - 'modules'=>array( - 'gii'=>array( - 'class'=>'system.gii.GiiModule', - 'password'=>'задайте свой пароль', - ), - ), -); -~~~ - -После этого перейдите по URL `http://hostname/testdrive/index.php?r=gii` и -введите указанный в конфигурации пароль. - -### Генерация модели User - -После входа зайдите в раздел `Model Generator`: - -![Model Generator](gii-model.png) - -В поле `Table Name` введите `tbl_user`. В поле `Model Class` — `User`. -Затем нажмите на кнопку `Preview`. Вы увидите новый файл, который будет -сгенерирован. После нажатия кнопки `Generate` в `protected/models` будет создан -файл `User.php`. Как будет описано далее в руководстве, класс модели `User` -позволяет работать с данными в таблице `tbl_user` в стиле ООП. - -### Генерация CRUD - -После генерации класса модели мы сгенерируем код, реализующий для неё операции CRUD. -Выбираем `Crud Generator`: - -![CRUD Generator](gii-crud.png) - -В поле `Model Class` вводим `User`. В поле `Controller ID` — `user` (в нижнем регистре). -Теперь нажимаем `Preview` и затем `Generate`. Генерация кода CRUD завершена. - -### Доступ к страницам CRUD - -Давайте порадуемся нашим трудам, перейдя по следующему URL: - -~~~ -http://hostname/testdrive/index.php?r=user -~~~ - -Мы увидим страницу со списком пользователей из таблицы `tbl_user`. Поскольку наша таблица пуста, то записей в ней не будет. -Кликнем по кнопке `Create User` и, если мы еще не авторизованы, отобразится страница авторизации. -В случае успешной авторизации загрузится форма добавления нового пользователя. Заполним её и нажмем кнопку `Create`. -Если при заполнении формы были допущены ошибки, мы увидим красивое сообщение об ошибке. - -Вернувшись назад к списку пользователей, мы должны увидеть только что созданного пользователя. -Повторите описанную операцию и добавьте ещё несколько пользователей. Обратите внимание, что при значительном -количестве пользователей для их отображения на одной странице список будет автоматически разбиваться на страницы. -Выполнив вход в качестве администратора (`admin/admin`), -можно увидеть страницу управления пользователями по адресу: - -~~~ -http://hostname/testdrive/index.php?r=user/admin -~~~ - -Появится наглядная таблица пользователей. Кликнув на название одного из полей заголовка таблицы, -можно упорядочить записи по значениям соответствующего столбца. -Для просмотра, редактирования или удаления записей можно воспользоваться кнопками в соответствующих строках таблицы. -Также можно переходить на разные страницы, фильтровать результаты и производить поиск по ним. - -Всё это не требует написания ни одной строчки кода! - -![Страница управления пользователями](first-app6.png) - +Создание первого приложения +=========================== + +В этом разделе мы расскажем, как создать наше первое приложение. +Для создания нового приложения мы будем использовать `yiic` (консольную утилиту), для +генерации кода — `Gii` (мощный веб кодогенератор). Будем считать для удобства, +что `YiiRoot` — это директория, куда установлен Yii, а `WebRoot` — корневая +директория веб-сервера. + +Запускаем `yiic` в консоли со следующими параметрами: +~~~ +% YiiRoot/framework/yiic webapp WebRoot/testdrive +~~~ +> Note|Примечание: При использовании `yiic` на Mac OS, +> Linux или Unix вам может понадобиться изменить права доступа +> для файла `yiic`, чтобы сделать его исполняемым. +> Альтернативный вариант запуска утилиты представлен ниже: +> +> ~~~ +> % cd WebRoot +> % php YiiRoot/framework/yiic.php webapp testdrive +> ~~~ + +В результате в директории `WebRoot/testdrive` будет создан каркас приложения. + +Созданное приложение — хорошая отправная точка для добавления необходимого функционала, +так как оно уже содержит все необходимые директории и файлы. +Не написав ни единой строчки кода, мы уже можем протестировать наше первое Yii-приложение, перейдя в браузере по следующему URL: + +~~~ +http://hostname/testdrive/index.php +~~~ + +Приложение содержит четыре страницы: главную, страницу «о проекте», страницу обратной связи и страницу авторизации. +Страница обратной связи содержит форму для отправки вопросов и предложений, а страница авторизации +позволяет пользователю аутентифицироваться и получить доступ к закрытой части сайта (см. рисунки ниже). + +![Главная страница](first-app1.png) + +![Страница обратной связи](first-app2.png) + +![Страница обратной связи с ошибками ввода](first-app3.png) + +![Страница обратной связи с успешно отправленной формой](first-app4.png) + +![Страница авторизации](first-app5.png) + + +Наше приложение имеет следующую структуру директорий. +Подробное описание этой структуры можно найти в [соглашениях](/doc/guide/basics.convention#directory). + +~~~ +testdrive/ + index.php скрипт инициализации приложения + index-test.php скрипт инициализации функциональных тестов + assets/ содержит файлы ресурсов + css/ содержит CSS-файлы + images/ содержит картинки + themes/ содержит темы оформления приложения + protected/ содержит защищённые файлы приложения + yiic скрипт yiic + yiic.bat скрипт yiic для Windows + yiic.php PHP-скрипт yiic + commands/ содержит команды 'yiic' + shell/ содержит команды 'yiic shell' + components/ содержит компоненты для повторного использования + Controller.php класс базового контроллера + UserIdentity.php класс 'UserIdentity' для аутентификации + config/ содержит конфигурационные файлы + console.php файл конфигурации консоли + main.php файл конфигурации веб-приложения + test.php файл конфигурации функциональных тестов + controllers/ содержит файлы классов контроллеров + SiteController.php класс контроллера по умолчанию + data/ содержит пример базы данных + schema.mysql.sql схема БД для MySQL + schema.sqlite.sql схема БД для SQLite + testdrive.db файл БД для SQLite + extensions/ содержит сторонние расширения + messages/ содержит переведённые сообщения + models/ содержит файлы классов моделей + LoginForm.php модель формы для действия 'login' + ContactForm.php модель формы для действия 'contact' + runtime/ содержит временные файлы + tests/ содержит тесты + views/ содержит файлы представлений контроллеров и файлы макетов (layout) + layouts/ содержит файлы представлений макетов + main.php общая для всех страниц разметка + column1.php разметка для страниц с одной колонкой + column2.php разметка для страниц с двумя колонками + site/ содержит файлы представлений для контроллера 'site' + pages/ статические страницы + about.php страница «о проекте» + contact.php файл представления для действия 'contact' + error.php файл представления для действия 'error' (отображение ошибок) + index.php файл представления для действия 'index' + login.php файл представления для действия 'login' +~~~ + +Описанный выше генератор может создать файлы, необходимые при работе с +системой контроля версий Git. Приведённая далее команда создаст все необходимые +`.gitignore` (содержимое `assets` и `runtime` не должно оказаться в репозитории) +и `.gitkeep` (важные директории включаем в репозиторий даже если они пустые): + +~~~ +% YiiRoot/framework/yiic webapp WebRoot/testdrive git +~~~ + +Ещё одна поддерживаемая система контроля версий — Mercurial. Если вы пользуетесь +ей, передайте третьим параметром `hg`. Данная возможность доступна с версии 1.1.11. + + +Соединение с базой данных +---------------------- +Большинство веб-приложений используют базы данных, и наше приложение не исключение. Для использования базы данных +необходимо объяснить приложению, как к ней подключиться. +Это делается в конфигурационном файле `WebRoot/testdrive/protected/config/main.php`. +Например, так: + +~~~ +[php] +return array( + … + 'components'=>array( + … + 'db'=>array( + 'connectionString'=>'sqlite:protected/data/testdrive.db', + ), + ), + … +); +~~~ + +В приведённом выше коде указано, что приложение должно подключиться к базе данных SQLite +`WebRoot/testdrive/protected/data/testdrive.db` как только это понадобится. Отметим, что +база данных SQLite уже включена в сгенерированное приложение. В этой базе имеется только +одна таблица `tbl_user`: + +~~~ +[sql] +CREATE TABLE tbl_user ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + username VARCHAR(128) NOT NULL, + password VARCHAR(128) NOT NULL, + email VARCHAR(128) NOT NULL +); +~~~ + +Если вы хотите использовать базу данных MySQL, то вы можете воспользоваться файлом +`WebRoot/testdrive/protected/data/schema.mysql.sql` для её создания. + +> Note|Примечание: Для работы с базой данных Yii требуется расширение PHP PDO +и соответствующий драйвер PDO. Для тестового приложения необходимо подключить +расширения `php_pdo` и `php_pdo_sqlite`. + +Реализация операций CRUD +------------------------ + +А теперь самое интересное. Мы бы хотели добавить операции CRUD +(создание, чтение, обновление и удаление) для только что созданной таблицы `tbl_user` — +это часто необходимо при разработке реальных приложений. Вместо ручного написания кода мы +воспользуемся веб кодогенератором `Gii`. + +> Info|Информация: Gii доступен, начиная с версии 1.1.2. Ранее для тех же целей +> использовался уже упомянутый `yiic`. Подробнее `yiic` описан в разделе +«[генерация CRUD при помощи yiic shell](/doc/guide/quickstart.first-app-yiic)». + + +### Настройка Gii + +Для того чтобы использовать Gii, нужно отредактировать +[файл конфигурации приложения](/doc/guide/basics.application#application-configuration) +`WebRoot/testdrive/protected/config/main.php`: + +~~~ +[php] +return array( + … + 'import'=>array( + 'application.models.*', + 'application.components.*', + ), + + 'modules'=>array( + 'gii'=>array( + 'class'=>'system.gii.GiiModule', + 'password'=>'задайте свой пароль', + ), + ), +); +~~~ + +После этого перейдите по URL `http://hostname/testdrive/index.php?r=gii` и +введите указанный в конфигурации пароль. + +### Генерация модели User + +После входа зайдите в раздел `Model Generator`: + +![Model Generator](gii-model.png) + +В поле `Table Name` введите `tbl_user`. В поле `Model Class` — `User`. +Затем нажмите на кнопку `Preview`. Вы увидите новый файл, который будет +сгенерирован. После нажатия кнопки `Generate` в `protected/models` будет создан +файл `User.php`. Как будет описано далее в руководстве, класс модели `User` +позволяет работать с данными в таблице `tbl_user` в стиле ООП. + +### Генерация CRUD + +После генерации класса модели мы сгенерируем код, реализующий для неё операции CRUD. +Выбираем `Crud Generator`: + +![CRUD Generator](gii-crud.png) + +В поле `Model Class` вводим `User`. В поле `Controller ID` — `user` (в нижнем регистре). +Теперь нажимаем `Preview` и затем `Generate`. Генерация кода CRUD завершена. + +### Доступ к страницам CRUD + +Давайте порадуемся нашим трудам, перейдя по следующему URL: + +~~~ +http://hostname/testdrive/index.php?r=user +~~~ + +Мы увидим страницу со списком пользователей из таблицы `tbl_user`. Поскольку наша таблица пуста, то записей в ней не будет. +Кликнем по кнопке `Create User` и, если мы еще не авторизованы, отобразится страница авторизации. +В случае успешной авторизации загрузится форма добавления нового пользователя. Заполним её и нажмем кнопку `Create`. +Если при заполнении формы были допущены ошибки, мы увидим красивое сообщение об ошибке. + +Вернувшись назад к списку пользователей, мы должны увидеть только что созданного пользователя. +Повторите описанную операцию и добавьте ещё несколько пользователей. Обратите внимание, что при значительном +количестве пользователей для их отображения на одной странице список будет автоматически разбиваться на страницы. +Выполнив вход в качестве администратора (`admin/admin`), +можно увидеть страницу управления пользователями по адресу: + +~~~ +http://hostname/testdrive/index.php?r=user/admin +~~~ + +Появится наглядная таблица пользователей. Кликнув на название одного из полей заголовка таблицы, +можно упорядочить записи по значениям соответствующего столбца. +Для просмотра, редактирования или удаления записей можно воспользоваться кнопками в соответствующих строках таблицы. +Также можно переходить на разные страницы, фильтровать результаты и производить поиск по ним. + +Всё это не требует написания ни одной строчки кода! + +![Страница управления пользователями](first-app6.png) + ![Страница добавления нового пользователя](first-app7.png) \ No newline at end of file diff --git a/docs/guide/ru/test.overview.txt b/docs/guide/ru/test.overview.txt index 63cda901d..1fc4e76c8 100644 --- a/docs/guide/ru/test.overview.txt +++ b/docs/guide/ru/test.overview.txt @@ -1,133 +1,133 @@ -Тестирование -============ - -Тестирование — важная составляющая процесса разработки ПО. Вне зависимости от того, осознаём -ли мы это или нет, мы проводим тестирование на протяжении всего процесса разработки приложения. -К примеру, при написании PHP-класса мы используем `echo` или `die` для того, чтобы проверить -корректность выполнения метода. При создании страницы, содержащей сложные HTML-формы, мы вводим -некоторые тестовые данные, чтобы проверить её работу. Более опытные разработчики напишут код, -автоматизирующий этот процесс и дающий возможность выполнить все тесты автоматически за один раз. -Этот процесс называется *автоматизированное тестирование* и является главной темой данного раздела. - -В Yii поддерживается *модульное тестирование* и *функциональное тестирование*. - -Модульный тест проверяет, что единица кода работает так, как должна. В ООП такой единицей является класс. -Поэтому модульный тест должен проверить, что каждый открытый метод класса работает должным образом. -То есть, имея входные тестовые данные, тест проверяет, что метод возвращает ожидаемый результат. -Модульные тесты обычно пишутся теми же, кто разрабатывает сам класс. - -Функциональный тест проверяет, что некоторая возможность (например, управление записями блога) -работает как надо. Функциональный тест, по сравнению с модульным, относится к более высокому уровню -так как чаще всего производится проверка работы нескольких классов. Функциональные тесты -обычно разрабатываются теми, кто очень хорошо знает требования к системе (это могут быть как -разработчики, так и инженеры QA). - - -Разработка через тестирование ------------------------------ - -Ниже приведён цикл [разработки через тестирование (TDD)](http://ru.wikipedia.org/wiki/Test-Driven_Development): - - 1. Создаём новый тест, описывающий функциональность планируемой возможности. Тест должен при первом запуске - завершиться неудачей, так как сама возможность ещё не реализована. - 2. Запускаем все тесты. Проверяем, что все они завершились неудачно. - 3. Пишем код, который проходит тесты. - 4. Запускаем все тесты. Проверяем, что все они завершились удачно. - 5. Рефакторим написанный код и проверяем, что он всё ещё проходит тесты. - -Повторяем шаги 1 — 5 для новой функциональности. - - -Настройка тестового окружения ------------------------------ - -Тестирование в Yii требует установленного [PHPUnit](http://www.phpunit.de/) 3.5+ и -[Selenium Remote Control](http://seleniumhq.org/projects/remote-control/) 1.0+. -Как устанавливать PHPUnit и Selenium Remote Control вы можете прочитать в их -документации. - -При использовании консольной команды `yiic webapp` для создания нового приложения -генерируются следующие директории, позволяющие писать и выполнять тесты: - -~~~ -testdrive/ - protected/ файлы приложения - tests/ тесты - fixtures/ фикстуры БД - functional/ функциональные тесты - unit/ модульные тесты - report/ отчёты по покрытию кода тестами - bootstrap.php загрузчик - phpunit.xml конфигурация для PHPUnit - WebTestCase.php базовый класс для функциональных тестов страниц -~~~ - -Как показано выше, код тестов главным образом находится в трёх директориях: -`fixtures`, `functional` и `unit`. Директория `report` используется для хранения -генерируемых отчётов о покрытии кода тестами. - -Для того, чтобы запустить тесты (как модульные, так и функциональные), необходимо -выполнить следующие команды в консоли: - -~~~ -% cd testdrive/protected/tests -% phpunit functional/PostTest.php // запускает отдельный тест -% phpunit --verbose functional // запускает все тесты в директории 'functional' -% phpunit --coverage-html ./report unit -~~~ - -Последняя команда выполнит все тесты в директории `unit` и создаст отчёт о покрытии кода в -директории `report`. Стоит отметить, что для отчётов требуется установленное -[расширение xdebug](http://www.xdebug.org/). - - -Загрузчик тестов ----------------- - -Давайте посмотрим, что может находиться в файле `bootstrap.php`. Мы уделяем ему большое внимание, -так как он играет такую же роль, как [входной скрипт](/doc/guide/basics.entry) и является -стартовой точкой при запуске набора тестов. - -~~~ -[php] -$yiit='path/to/yii/framework/yiit.php'; -$config=dirname(__FILE__).'/../config/test.php'; -require_once($yiit); -require_once(dirname(__FILE__).'/WebTestCase.php'); -Yii::createWebApplication($config); -~~~ - -В приведённом коде мы сначала подключаем файл `yiit.php`, который инициализирует -некоторые глобальные константы и подключает необходимые базовые классы тестов. -Затем мы создаём экземпляр приложения, используя файл конфигурации `test.php`. -Конфигурация в нём наследуется от `main.php` и добавляет компонент -`fixture` (класс [CDbFixtureManager]). Фикстуры будут детально описаны в -следующем разделе. - -~~~ -[php] -return CMap::mergeArray( - require(dirname(__FILE__).'/main.php'), - array( - 'components'=>array( - 'fixture'=>array( - 'class'=>'system.test.CDbFixtureManager', - ), - /* раскомментируйте, если вам нужно подключение к тестовой БД - 'db'=>array( - 'connectionString'=>'DSN для БД', - ), - */ - ), - ) -); -~~~ - -При запуске тестов, включающих работу с базой данных, необходимо -переопределить БД для того, чтобы не испортить реальные данные или данные, -используемые при разработке. Для этого необходимо раскомментировать конфигурацию `db` -выше и указать DSN тестовой базы в свойстве `connectionString`. - -При помощи такого входного скрипта при запуске тестов мы получаем экземпляр приложения, -максимально приближённый к реальному. Главное отличие в том, что в нём есть поддержка +Тестирование +============ + +Тестирование — важная составляющая процесса разработки ПО. Вне зависимости от того, осознаём +ли мы это или нет, мы проводим тестирование на протяжении всего процесса разработки приложения. +К примеру, при написании PHP-класса мы используем `echo` или `die` для того, чтобы проверить +корректность выполнения метода. При создании страницы, содержащей сложные HTML-формы, мы вводим +некоторые тестовые данные, чтобы проверить её работу. Более опытные разработчики напишут код, +автоматизирующий этот процесс и дающий возможность выполнить все тесты автоматически за один раз. +Этот процесс называется *автоматизированное тестирование* и является главной темой данного раздела. + +В Yii поддерживается *модульное тестирование* и *функциональное тестирование*. + +Модульный тест проверяет, что единица кода работает так, как должна. В ООП такой единицей является класс. +Поэтому модульный тест должен проверить, что каждый открытый метод класса работает должным образом. +То есть, имея входные тестовые данные, тест проверяет, что метод возвращает ожидаемый результат. +Модульные тесты обычно пишутся теми же, кто разрабатывает сам класс. + +Функциональный тест проверяет, что некоторая возможность (например, управление записями блога) +работает как надо. Функциональный тест, по сравнению с модульным, относится к более высокому уровню +так как чаще всего производится проверка работы нескольких классов. Функциональные тесты +обычно разрабатываются теми, кто очень хорошо знает требования к системе (это могут быть как +разработчики, так и инженеры QA). + + +Разработка через тестирование +----------------------------- + +Ниже приведён цикл [разработки через тестирование (TDD)](http://ru.wikipedia.org/wiki/Test-Driven_Development): + + 1. Создаём новый тест, описывающий функциональность планируемой возможности. Тест должен при первом запуске + завершиться неудачей, так как сама возможность ещё не реализована. + 2. Запускаем все тесты. Проверяем, что все они завершились неудачно. + 3. Пишем код, который проходит тесты. + 4. Запускаем все тесты. Проверяем, что все они завершились удачно. + 5. Рефакторим написанный код и проверяем, что он всё ещё проходит тесты. + +Повторяем шаги 1 — 5 для новой функциональности. + + +Настройка тестового окружения +----------------------------- + +Тестирование в Yii требует установленного [PHPUnit](http://www.phpunit.de/) 3.5+ и +[Selenium Remote Control](http://seleniumhq.org/projects/remote-control/) 1.0+. +Как устанавливать PHPUnit и Selenium Remote Control вы можете прочитать в их +документации. + +При использовании консольной команды `yiic webapp` для создания нового приложения +генерируются следующие директории, позволяющие писать и выполнять тесты: + +~~~ +testdrive/ + protected/ файлы приложения + tests/ тесты + fixtures/ фикстуры БД + functional/ функциональные тесты + unit/ модульные тесты + report/ отчёты по покрытию кода тестами + bootstrap.php загрузчик + phpunit.xml конфигурация для PHPUnit + WebTestCase.php базовый класс для функциональных тестов страниц +~~~ + +Как показано выше, код тестов главным образом находится в трёх директориях: +`fixtures`, `functional` и `unit`. Директория `report` используется для хранения +генерируемых отчётов о покрытии кода тестами. + +Для того, чтобы запустить тесты (как модульные, так и функциональные), необходимо +выполнить следующие команды в консоли: + +~~~ +% cd testdrive/protected/tests +% phpunit functional/PostTest.php // запускает отдельный тест +% phpunit --verbose functional // запускает все тесты в директории 'functional' +% phpunit --coverage-html ./report unit +~~~ + +Последняя команда выполнит все тесты в директории `unit` и создаст отчёт о покрытии кода в +директории `report`. Стоит отметить, что для отчётов требуется установленное +[расширение xdebug](http://www.xdebug.org/). + + +Загрузчик тестов +---------------- + +Давайте посмотрим, что может находиться в файле `bootstrap.php`. Мы уделяем ему большое внимание, +так как он играет такую же роль, как [входной скрипт](/doc/guide/basics.entry) и является +стартовой точкой при запуске набора тестов. + +~~~ +[php] +$yiit='path/to/yii/framework/yiit.php'; +$config=dirname(__FILE__).'/../config/test.php'; +require_once($yiit); +require_once(dirname(__FILE__).'/WebTestCase.php'); +Yii::createWebApplication($config); +~~~ + +В приведённом коде мы сначала подключаем файл `yiit.php`, который инициализирует +некоторые глобальные константы и подключает необходимые базовые классы тестов. +Затем мы создаём экземпляр приложения, используя файл конфигурации `test.php`. +Конфигурация в нём наследуется от `main.php` и добавляет компонент +`fixture` (класс [CDbFixtureManager]). Фикстуры будут детально описаны в +следующем разделе. + +~~~ +[php] +return CMap::mergeArray( + require(dirname(__FILE__).'/main.php'), + array( + 'components'=>array( + 'fixture'=>array( + 'class'=>'system.test.CDbFixtureManager', + ), + /* раскомментируйте, если вам нужно подключение к тестовой БД + 'db'=>array( + 'connectionString'=>'DSN для БД', + ), + */ + ), + ) +); +~~~ + +При запуске тестов, включающих работу с базой данных, необходимо +переопределить БД для того, чтобы не испортить реальные данные или данные, +используемые при разработке. Для этого необходимо раскомментировать конфигурацию `db` +выше и указать DSN тестовой базы в свойстве `connectionString`. + +При помощи такого входного скрипта при запуске тестов мы получаем экземпляр приложения, +максимально приближённый к реальному. Главное отличие в том, что в нём есть поддержка фикстур и используется тестовая БД. \ No newline at end of file diff --git a/docs/guide/ru/topics.gii.txt b/docs/guide/ru/topics.gii.txt index 5020f4577..ddcfc4fd0 100644 --- a/docs/guide/ru/topics.gii.txt +++ b/docs/guide/ru/topics.gii.txt @@ -1,368 +1,368 @@ -Автоматическая генерация кода -============================= - -Начиная с версии 1.1.2, в состав Yii входит веб-инструмент для генерации кода, -называемый *Gii*. Он заменяет существовавший до этого консольный генератор -`yiic shell`. В данном разделе описано, как использовать Gii и как расширить его -для ускорения разработки. - -Использование Gii ------------------ - -Gii является модулем и должен быть использован в составе существующего приложения Yii. -Для использования Gii необходимо отредактировать файл конфигурации приложения -следующим образом: - -~~~ -[php] -return array( - … - 'modules'=>array( - 'gii'=>array( - 'class'=>'system.gii.GiiModule', - 'password'=>'задайте свой пароль', - // 'ipFilters'=>array(…список IP…), - // 'newFileMode'=>0666, - // 'newDirMode'=>0777, - ), - ), -); -~~~ - -Выше мы объявили модуль с именем `gii` и классом [GiiModule]. Также мы задали -пароль, который будет использоваться для доступа к Gii. - -По умолчанию, в целях безопасности, Gii доступен только для localhost. -Если необходимо дать доступ к нему с других компьютеров, нужно задать -свойство [GiiModule::ipFilters] как показано в коде выше. - -Так как Gii будет генерировать и сохранять новые файлы с кодом в существующее -приложение, необходимо убедиться в том, что процесс веб-сервера имеет на это права. -Показанные выше свойства [GiiModule::newFileMode] и [GiiModule::newDirMode] -содержат права, с которыми будут создаваться файлы и директории. - -> Note|Примечание: Gii является инструментом разработчика. Поэтому он должен быть -> установлен исключительно на компьютере или сервере разработчика. Так как -> он может генерировать новые скрипты PHP, необходимо уделить особое внимание -> безопасности (пароль, IP фильтры). - -Теперь можно запустить Gii по URL `http://hostname/path/to/index.php?r=gii`, где -`http://hostname/path/to/index.php` — URL вашего приложения. - -Если существующее приложение использует формат URL `path` -(см. [красивые адреса URL](/doc/guide/topics.url)), мы можем запустить Gii по URL -`http://hostname/path/to/index.php/gii`. Может понадобиться добавить следующие правила -URL перед уже существующими: - -~~~ -[php] -'components'=>array( - … - 'urlManager'=>array( - 'urlFormat'=>'path', - 'rules'=>array( - 'gii'=>'gii', - 'gii/'=>'gii/', - 'gii//'=>'gii//', - …существующие правила… - ), - ), -) -~~~ - -В составе Gii есть готовый набор генераторов кода. Каждый генератор отвечает за -свой тип кода. К примеру, генератор контроллера создаёт класс контроллера -вместе с несколькими шаблонами отображения; генератор модели создаёт класс ActiveRecord -для определённой таблицы БД. - -Последовательность работы с генератором следующая: - -1. Зайти на страницу генератора; -2. Заполнить поля, которые задают параметры генерируемого кода. К примеру, -для генерации модуля необходимо указать его ID; -3. Нажать кнопку `Preview` для предварительной оценки генерируемого кода. -Вы увидите таблицу файлов, которые будут сгенерированы и сможете просмотреть их -код; -4. Нажать кнопку `Generate` для создания файлов; -5. Просмотреть журнал генерации кода. - -> Note|Примечание: после генерации модели стоит проверить и скорректировать метод `rules` так как структура базы данных -часто не содержит достаточно данных о требованиях валидации. - -Расширение Gii --------------- - -Несмотря на то, что включённые в состав Gii генераторы создают достаточно -функциональный код, часто требуется его немного изменить или создать новый -генератор по своему вкусу и потребностям. К примеру, нам может понадобиться -изменить стиль генерируемого кода или добавить поддержку нескольких языков. -Всё это может быть легко реализовано через Gii. - -Gii можно расширять двумя способами: изменяя существующие шаблоны кодогенераторов -и создавая свои генераторы. - -###Структура кодогенератора - -Генератор кода размещается в директории, чьё имя является именем генератора. -Директория обычно содержит: - -~~~ -model/ корневая директория генератора модели - ModelCode.php модель, используемая для генерации кода - ModelGenerator.php контроллер кодогенератора - views/ отображения генератора - index.php шаблон по умолчанию - templates/ шаблоны кода - default/ набор шаблонов 'default' - model.php шаблон для генерации класса модели -~~~ - -###Путь поиска генераторов - -Gii ищет генераторы в списке директорий, указанных в свойстве -[GiiModule::generatorPaths]. В том случае, если необходимо добавить -свои генераторы, следует настроить приложение следующим образом: - -~~~ -[php] -return array( - 'modules'=>array( - 'gii'=>array( - 'class'=>'system.gii.GiiModule', - 'generatorPaths'=>array( - 'common.gii', // псевдоним пути - ), - ), - ), -); -~~~ - -Приведённые выше настройки заставляют Gii искать генераторы в директории -с псевдонимом `common.gii` в дополнение к стандартным `system.gii.generators` и `application.gii`. - -Возможно иметь несколько одноимённых генераторов, если у них разные пути -поиска. В этом случае будет использоваться генератор, путь поиска которого -указан выше в [GiiModule::generatorPaths]. - - -###Изменение шаблонов кода - -Изменение шаблонов кода — самый простой и самый распространённый путь расширения -Gii. Мы будем использовать примеры для того, чтобы описать, как изменить шаблоны -кода. Допустим, нам необходимо изменить код, создаваемый генератором модели. - -Сначала мы создаём директорию `protected/gii/model/templates/compact`. Здесь `model` -означает, что мы собираемся *перекрыть* генератор модели по умолчанию. -А `templates/compact` — что мы добавляем новый набор шаблонов кода `compact`. - -После этого мы добавляем в настройки приложения в свойство [GiiModule::generatorPaths] -значение `application.gii`, как показано в предыдущем подразделе. - -Теперь открываем страницу генератора модели. Щёлкаем на поле `Code Template`. -Вы должны увидеть выпадающий список, содержащий нашу только что созданную директорию -шаблонов `compact`. Тем не менее, если мы выберем этот шаблон, будет выведена -ошибка. Происходит это потому, что в наборе `compact` ещё нет самих шаблонов кода. - -Скопируем файл `framework/gii/generators/model/templates/default/model.php` в -`protected/gii/model/templates/compact`. Если попробовать сгенерировать код -с набором `compact` ещё раз, генерация должна пройти успешно. Тем не менее, -генерируемый код ничем не отличается от кода, получаемого из набора `default`. - -Время сделать некоторые изменения. -Откроем файл `protected/gii/model/templates/compact/model.php`. Данный файл будет -использован как шаблон отображения, что означает, что он может содержать -выражения и код PHP. Изменим шаблон таким образом, что метод `attributeLabels()` -генерируемого кода будет использовать `Yii::t()` для перевода заголовков полей: - -~~~ -[php] -public function attributeLabels() -{ - return array( -$label): ?> - Yii::t('application', '$label'),\n"; ?> - - ); -} -~~~ - -В каждом шаблоне кода у нас есть доступ к некоторым предопределённым переменным, -таким как, например, `$labels`. Эти переменные задаются соответствующим генератором -кода. Разные генераторы могут предоставлять шаблонам различные наборы переменных. -Стоит внимательно изучить описание шаблонов кода по умолчанию. - - -###Создание новых генераторов - -В этом подразделе мы покажем, как реализовать новый генератор, который сможет -создавать новые классы виджетов. - -Сначала создадим директорию `protected/gii/widget`. В ней создадим следующие файлы: - -* `WidgetGenerator.php`: содержит класс контроллера `WidgetGenerator`, который -является входной точкой генератора виджетов. -* `WidgetCode.php`: содержит класс модели `WidgetCode`, который отвечает за -логику генерации кода. -* `views/index.php`: отображение, содержащее форму ввода генератора. -* `templates/default/widget.php`: шаблон кода по умолчанию для генерации класса -виджета. - - -#### Реализация `WidgetGenerator.php` - -Файл `WidgetGenerator.php` предельно простой. Он содержит лишь следующий код: - -~~~ -[php] -class WidgetGenerator extends CCodeGenerator -{ - public $codeModel='application.gii.widget.WidgetCode'; -} -~~~ - -Здесь мы описываем, что генератор будет использовать класс модели, чей псевдоним -пути `application.gii.widget.WidgetCode`. Класс `WidgetGenerator` наследуется -от [CCodeGenerator], реализующего большое количество функций, включая -действия контроллера, необходимые для координации процесса генерации кода. - -#### Реализация `WidgetCode.php` - -Файл `WidgetCode.php` содержит класс модели `WidgetCode`, в котором реализована -логика генерации класса виджета на основе полученных от пользователя параметров. -В данном примере будем считать, что единственное, что вводит пользователь — -имя класса виджета. `WidgetCode` выглядит следующим образом: - -~~~ -[php] -class WidgetCode extends CCodeModel -{ - public $className; - - public function rules() - { - return array_merge(parent::rules(), array( - array('className', 'required'), - array('className', 'match', 'pattern'=>'/^\w+$/'), - )); - } - - public function attributeLabels() - { - return array_merge(parent::attributeLabels(), array( - 'className'=>'Widget Class Name', - )); - } - - public function prepare() - { - $path=Yii::getPathOfAlias('application.components.' . $this->className) . '.php'; - $code=$this->render($this->templatepath.'/widget.php'); - - $this->files[]=new CCodeFile($path, $code); - } -} -~~~ - -Класс `WidgetCode` наследуется от [CCodeModel]. Как и в обычном классе модели, в -данном классе мы реализуем методы `rules()` и `attributeLabels()` для валидации -ввода и генерации подписей полей соответственно. Стоит отметить, что так как -базовый класс [CCodeModel] уже описывает некоторое количество правил валидации -и названий подписей, то мы должны объединить их с нашими правилами и подписями. - -Метод `prepare()` подготавливает код к генерации. Главная задача метода — подготовить -список объектов [CCodeFile], каждый из которых представляет будущий файл с кодом. -В нашем примере необходимо создать всего один объект [CCodeFile], представляющий -класс виджета, который будет сгенерирован в директории `protected/components`. -Для непосредственной генерации кода используется метод [CCodeFile::render]. -Данный метод содержит PHP-шаблон кода и возвращает сгенерированный код. - - -#### Реализация `views/index.php` - -После реализации контроллера (`WidgetGenerator`) и модели (`WidgetCode`) -самое время заняться отображением `views/index.php`: - -~~~ -[php] -

Генератор виджета

- -beginWidget('CCodeForm', array('model'=>$model)); ?> - -
- labelEx($model,'className'); ?> - textField($model,'className',array('size'=>65)); ?> -
- Класс виджета должен содержать только буквы. -
- error($model,'className'); ?> -
- -endWidget(); ?> -~~~ - -В данном коде мы отображаем форму, используя виджет [CCodeForm]. В этой форме мы -показываем поле для ввода атрибута `className` модели `WidgetCode`. - -При создании формы мы можем использовать две замечательные возможности [CCodeForm]. -Одна — подсказки для полей. Вторая — запоминание введённых значений. - -Если вы использовали один из стандартных генераторов кода, вы могли заметить красивые -всплывающие подсказки, появляющиеся рядом с полем при получении им фокуса. Использовать -данную возможность очень легко: достаточно после поля вставить `div` с CSS классом `tooltip`. - -Для некоторых полей полезно запомнить последнее верное значение и тем самым позволив -пользователю не вводить значения повторно каждый раз, когда он использует генератор. -Примером может служить поле ввода базового класса контроллера в стандартном генераторе. -Такие поля изначально отображаются как подсвеченный статичный текст. При щелчке -они превращаются в поля ввода. - -Для того, чтобы сделать поле запоминаемым, необходимо сделать две вещи. - -Во-первых, нужно описать правило валидации `sticky` для соответствующего атрибута -модели. К примеру, для стандартного генератора контроллера используется приведённое -ниже правило для запоминания атрибутов `baseClass` и `actions`: - -~~~ -[php] -public function rules() -{ - return array_merge(parent::rules(), array( - … - array('baseClass, actions', 'sticky'), - )); -} -~~~ - -Во-вторых, в отображении необходимо добавить CSS класс `sticky` контейнеру `div` -поля ввода: - -~~~ -[php] -
- …поле ввода… -
-~~~ - -#### Реализация `templates/default/widget.php` - -Наконец, мы создаём шаблон кода `templates/default/widget.php`. Как было описано -ранее, он используется как PHP-шаблон отображения. В шаблоне кода мы всегда -можем обратиться к переменной `$this`, которая содержит экземпляр модели кода. -В нашем примере `$this` содержит объект `WidgetModel`. Таким образом, мы можем -получить введённый пользователем класс виджета через `$this->className`. - -~~~ -[php] - - -class className; ?> extends CWidget -{ - public function run() - { - - } -} -~~~ - -На этом реализация генератора кода завершена. Обратиться к нему можно по +Автоматическая генерация кода +============================= + +Начиная с версии 1.1.2, в состав Yii входит веб-инструмент для генерации кода, +называемый *Gii*. Он заменяет существовавший до этого консольный генератор +`yiic shell`. В данном разделе описано, как использовать Gii и как расширить его +для ускорения разработки. + +Использование Gii +----------------- + +Gii является модулем и должен быть использован в составе существующего приложения Yii. +Для использования Gii необходимо отредактировать файл конфигурации приложения +следующим образом: + +~~~ +[php] +return array( + … + 'modules'=>array( + 'gii'=>array( + 'class'=>'system.gii.GiiModule', + 'password'=>'задайте свой пароль', + // 'ipFilters'=>array(…список IP…), + // 'newFileMode'=>0666, + // 'newDirMode'=>0777, + ), + ), +); +~~~ + +Выше мы объявили модуль с именем `gii` и классом [GiiModule]. Также мы задали +пароль, который будет использоваться для доступа к Gii. + +По умолчанию, в целях безопасности, Gii доступен только для localhost. +Если необходимо дать доступ к нему с других компьютеров, нужно задать +свойство [GiiModule::ipFilters] как показано в коде выше. + +Так как Gii будет генерировать и сохранять новые файлы с кодом в существующее +приложение, необходимо убедиться в том, что процесс веб-сервера имеет на это права. +Показанные выше свойства [GiiModule::newFileMode] и [GiiModule::newDirMode] +содержат права, с которыми будут создаваться файлы и директории. + +> Note|Примечание: Gii является инструментом разработчика. Поэтому он должен быть +> установлен исключительно на компьютере или сервере разработчика. Так как +> он может генерировать новые скрипты PHP, необходимо уделить особое внимание +> безопасности (пароль, IP фильтры). + +Теперь можно запустить Gii по URL `http://hostname/path/to/index.php?r=gii`, где +`http://hostname/path/to/index.php` — URL вашего приложения. + +Если существующее приложение использует формат URL `path` +(см. [красивые адреса URL](/doc/guide/topics.url)), мы можем запустить Gii по URL +`http://hostname/path/to/index.php/gii`. Может понадобиться добавить следующие правила +URL перед уже существующими: + +~~~ +[php] +'components'=>array( + … + 'urlManager'=>array( + 'urlFormat'=>'path', + 'rules'=>array( + 'gii'=>'gii', + 'gii/'=>'gii/', + 'gii//'=>'gii//', + …существующие правила… + ), + ), +) +~~~ + +В составе Gii есть готовый набор генераторов кода. Каждый генератор отвечает за +свой тип кода. К примеру, генератор контроллера создаёт класс контроллера +вместе с несколькими шаблонами отображения; генератор модели создаёт класс ActiveRecord +для определённой таблицы БД. + +Последовательность работы с генератором следующая: + +1. Зайти на страницу генератора; +2. Заполнить поля, которые задают параметры генерируемого кода. К примеру, +для генерации модуля необходимо указать его ID; +3. Нажать кнопку `Preview` для предварительной оценки генерируемого кода. +Вы увидите таблицу файлов, которые будут сгенерированы и сможете просмотреть их +код; +4. Нажать кнопку `Generate` для создания файлов; +5. Просмотреть журнал генерации кода. + +> Note|Примечание: после генерации модели стоит проверить и скорректировать метод `rules` так как структура базы данных +часто не содержит достаточно данных о требованиях валидации. + +Расширение Gii +-------------- + +Несмотря на то, что включённые в состав Gii генераторы создают достаточно +функциональный код, часто требуется его немного изменить или создать новый +генератор по своему вкусу и потребностям. К примеру, нам может понадобиться +изменить стиль генерируемого кода или добавить поддержку нескольких языков. +Всё это может быть легко реализовано через Gii. + +Gii можно расширять двумя способами: изменяя существующие шаблоны кодогенераторов +и создавая свои генераторы. + +###Структура кодогенератора + +Генератор кода размещается в директории, чьё имя является именем генератора. +Директория обычно содержит: + +~~~ +model/ корневая директория генератора модели + ModelCode.php модель, используемая для генерации кода + ModelGenerator.php контроллер кодогенератора + views/ отображения генератора + index.php шаблон по умолчанию + templates/ шаблоны кода + default/ набор шаблонов 'default' + model.php шаблон для генерации класса модели +~~~ + +###Путь поиска генераторов + +Gii ищет генераторы в списке директорий, указанных в свойстве +[GiiModule::generatorPaths]. В том случае, если необходимо добавить +свои генераторы, следует настроить приложение следующим образом: + +~~~ +[php] +return array( + 'modules'=>array( + 'gii'=>array( + 'class'=>'system.gii.GiiModule', + 'generatorPaths'=>array( + 'common.gii', // псевдоним пути + ), + ), + ), +); +~~~ + +Приведённые выше настройки заставляют Gii искать генераторы в директории +с псевдонимом `common.gii` в дополнение к стандартным `system.gii.generators` и `application.gii`. + +Возможно иметь несколько одноимённых генераторов, если у них разные пути +поиска. В этом случае будет использоваться генератор, путь поиска которого +указан выше в [GiiModule::generatorPaths]. + + +###Изменение шаблонов кода + +Изменение шаблонов кода — самый простой и самый распространённый путь расширения +Gii. Мы будем использовать примеры для того, чтобы описать, как изменить шаблоны +кода. Допустим, нам необходимо изменить код, создаваемый генератором модели. + +Сначала мы создаём директорию `protected/gii/model/templates/compact`. Здесь `model` +означает, что мы собираемся *перекрыть* генератор модели по умолчанию. +А `templates/compact` — что мы добавляем новый набор шаблонов кода `compact`. + +После этого мы добавляем в настройки приложения в свойство [GiiModule::generatorPaths] +значение `application.gii`, как показано в предыдущем подразделе. + +Теперь открываем страницу генератора модели. Щёлкаем на поле `Code Template`. +Вы должны увидеть выпадающий список, содержащий нашу только что созданную директорию +шаблонов `compact`. Тем не менее, если мы выберем этот шаблон, будет выведена +ошибка. Происходит это потому, что в наборе `compact` ещё нет самих шаблонов кода. + +Скопируем файл `framework/gii/generators/model/templates/default/model.php` в +`protected/gii/model/templates/compact`. Если попробовать сгенерировать код +с набором `compact` ещё раз, генерация должна пройти успешно. Тем не менее, +генерируемый код ничем не отличается от кода, получаемого из набора `default`. + +Время сделать некоторые изменения. +Откроем файл `protected/gii/model/templates/compact/model.php`. Данный файл будет +использован как шаблон отображения, что означает, что он может содержать +выражения и код PHP. Изменим шаблон таким образом, что метод `attributeLabels()` +генерируемого кода будет использовать `Yii::t()` для перевода заголовков полей: + +~~~ +[php] +public function attributeLabels() +{ + return array( +$label): ?> + Yii::t('application', '$label'),\n"; ?> + + ); +} +~~~ + +В каждом шаблоне кода у нас есть доступ к некоторым предопределённым переменным, +таким как, например, `$labels`. Эти переменные задаются соответствующим генератором +кода. Разные генераторы могут предоставлять шаблонам различные наборы переменных. +Стоит внимательно изучить описание шаблонов кода по умолчанию. + + +###Создание новых генераторов + +В этом подразделе мы покажем, как реализовать новый генератор, который сможет +создавать новые классы виджетов. + +Сначала создадим директорию `protected/gii/widget`. В ней создадим следующие файлы: + +* `WidgetGenerator.php`: содержит класс контроллера `WidgetGenerator`, который +является входной точкой генератора виджетов. +* `WidgetCode.php`: содержит класс модели `WidgetCode`, который отвечает за +логику генерации кода. +* `views/index.php`: отображение, содержащее форму ввода генератора. +* `templates/default/widget.php`: шаблон кода по умолчанию для генерации класса +виджета. + + +#### Реализация `WidgetGenerator.php` + +Файл `WidgetGenerator.php` предельно простой. Он содержит лишь следующий код: + +~~~ +[php] +class WidgetGenerator extends CCodeGenerator +{ + public $codeModel='application.gii.widget.WidgetCode'; +} +~~~ + +Здесь мы описываем, что генератор будет использовать класс модели, чей псевдоним +пути `application.gii.widget.WidgetCode`. Класс `WidgetGenerator` наследуется +от [CCodeGenerator], реализующего большое количество функций, включая +действия контроллера, необходимые для координации процесса генерации кода. + +#### Реализация `WidgetCode.php` + +Файл `WidgetCode.php` содержит класс модели `WidgetCode`, в котором реализована +логика генерации класса виджета на основе полученных от пользователя параметров. +В данном примере будем считать, что единственное, что вводит пользователь — +имя класса виджета. `WidgetCode` выглядит следующим образом: + +~~~ +[php] +class WidgetCode extends CCodeModel +{ + public $className; + + public function rules() + { + return array_merge(parent::rules(), array( + array('className', 'required'), + array('className', 'match', 'pattern'=>'/^\w+$/'), + )); + } + + public function attributeLabels() + { + return array_merge(parent::attributeLabels(), array( + 'className'=>'Widget Class Name', + )); + } + + public function prepare() + { + $path=Yii::getPathOfAlias('application.components.' . $this->className) . '.php'; + $code=$this->render($this->templatepath.'/widget.php'); + + $this->files[]=new CCodeFile($path, $code); + } +} +~~~ + +Класс `WidgetCode` наследуется от [CCodeModel]. Как и в обычном классе модели, в +данном классе мы реализуем методы `rules()` и `attributeLabels()` для валидации +ввода и генерации подписей полей соответственно. Стоит отметить, что так как +базовый класс [CCodeModel] уже описывает некоторое количество правил валидации +и названий подписей, то мы должны объединить их с нашими правилами и подписями. + +Метод `prepare()` подготавливает код к генерации. Главная задача метода — подготовить +список объектов [CCodeFile], каждый из которых представляет будущий файл с кодом. +В нашем примере необходимо создать всего один объект [CCodeFile], представляющий +класс виджета, который будет сгенерирован в директории `protected/components`. +Для непосредственной генерации кода используется метод [CCodeFile::render]. +Данный метод содержит PHP-шаблон кода и возвращает сгенерированный код. + + +#### Реализация `views/index.php` + +После реализации контроллера (`WidgetGenerator`) и модели (`WidgetCode`) +самое время заняться отображением `views/index.php`: + +~~~ +[php] +

Генератор виджета

+ +beginWidget('CCodeForm', array('model'=>$model)); ?> + +
+ labelEx($model,'className'); ?> + textField($model,'className',array('size'=>65)); ?> +
+ Класс виджета должен содержать только буквы. +
+ error($model,'className'); ?> +
+ +endWidget(); ?> +~~~ + +В данном коде мы отображаем форму, используя виджет [CCodeForm]. В этой форме мы +показываем поле для ввода атрибута `className` модели `WidgetCode`. + +При создании формы мы можем использовать две замечательные возможности [CCodeForm]. +Одна — подсказки для полей. Вторая — запоминание введённых значений. + +Если вы использовали один из стандартных генераторов кода, вы могли заметить красивые +всплывающие подсказки, появляющиеся рядом с полем при получении им фокуса. Использовать +данную возможность очень легко: достаточно после поля вставить `div` с CSS классом `tooltip`. + +Для некоторых полей полезно запомнить последнее верное значение и тем самым позволив +пользователю не вводить значения повторно каждый раз, когда он использует генератор. +Примером может служить поле ввода базового класса контроллера в стандартном генераторе. +Такие поля изначально отображаются как подсвеченный статичный текст. При щелчке +они превращаются в поля ввода. + +Для того, чтобы сделать поле запоминаемым, необходимо сделать две вещи. + +Во-первых, нужно описать правило валидации `sticky` для соответствующего атрибута +модели. К примеру, для стандартного генератора контроллера используется приведённое +ниже правило для запоминания атрибутов `baseClass` и `actions`: + +~~~ +[php] +public function rules() +{ + return array_merge(parent::rules(), array( + … + array('baseClass, actions', 'sticky'), + )); +} +~~~ + +Во-вторых, в отображении необходимо добавить CSS класс `sticky` контейнеру `div` +поля ввода: + +~~~ +[php] +
+ …поле ввода… +
+~~~ + +#### Реализация `templates/default/widget.php` + +Наконец, мы создаём шаблон кода `templates/default/widget.php`. Как было описано +ранее, он используется как PHP-шаблон отображения. В шаблоне кода мы всегда +можем обратиться к переменной `$this`, которая содержит экземпляр модели кода. +В нашем примере `$this` содержит объект `WidgetModel`. Таким образом, мы можем +получить введённый пользователем класс виджета через `$this->className`. + +~~~ +[php] + + +class className; ?> extends CWidget +{ + public function run() + { + + } +} +~~~ + +На этом реализация генератора кода завершена. Обратиться к нему можно по URL `http://hostname/path/to/index.php?r=gii/widget`. \ No newline at end of file diff --git a/docs/guide/ru/topics.i18n.txt b/docs/guide/ru/topics.i18n.txt index 1f392523d..61208ecd1 100644 --- a/docs/guide/ru/topics.i18n.txt +++ b/docs/guide/ru/topics.i18n.txt @@ -1,336 +1,336 @@ -Интернационализация -=================== - -Интернационализация (сокращённо I18N) — процесс создания приложения, которое может -работать на различных языках и с различными региональными особенностями без каких-либо -дополнительных изменений. Для веб-приложений это особенно важно, так как пользователь -может быть из любой точки мира. - -Yii поддерживает интернационализацию на нескольких уровнях: - - - Предоставляет региональные данные для всех возможных языков и их вариаций. - - Сервис для перевода сообщений и файлов. - - Форматирование дат и чисел в зависимости от региональных настроек. - -В последующих разделах мы рассмотрим перечисленные возможности более подробно. - -Региональные настройки и язык ------------------------------ - -Региональные настройки — это набор параметров, задающих язык пользователя, -страну и любые другие специальные настройки интерфейса. Обычно настройки определяются идентификатором, -состоящем из языка и региона. К примеру, идентификатор `en_US` соответствует английскому языку и настройкам для США. -В Yii все идентификаторы региональных настроек названы единообразно: `LanguageID` (язык) или `LanguageID_RegionID` (язык_регион) -в нижнем регистре. Например: `ru`, `en_us`. - -Региональные настройки хранятся в объекте класса [CLocale], который можно использовать для получения -зависимой от них информации, такой как символы и форматы валют и чисел, форматы дат и времени, название -месяцев и дней недели. Так как информация о языке уже содержится в идентификаторе, -в [CLocale] она не дублируется. По этой причине мы часто применяем в одном контексте термины «язык», «региональные настройки» и «локаль». - -Имея идентификатор языка, мы можем получить соответствующий ему объект [CLocale]: -`CLocale::getInstance($localeID)` или `CApplication::getLocale($localeID)`. - -> Info|Информация: в состав Yii включены региональные данные практически по -всем языкам и регионам. Данные получены из -[Common Locale Data Repository](http://unicode.org/cldr/) (CLDR). Доступны не -все данные, предоставляемые CLDR, так как там содержится большое количество -редко используемой информации. Пользователи также могут -предоставлять свои собственные специальные региональные данные. Для этого -настройте свойство [CApplication::localeDataPath], чтобы оно указывало на -директорию, содержащую специальные региональные данные. Обратитесь к файлам -региональных данных в директории `framework/i18n/data` для того, чтобы создать -файлы специальных региональных данных. - -В приложении Yii мы различаем [язык приложения|CApplication::language] и [исходный язык приложения|CApplication::sourceLanguage]. -Язык приложения — это язык (локаль) пользователя, который работает с приложением. Исходный язык приложения — язык, который используется -в исходном коде приложения. Интернационализация требуется только в том случае, если эти два языка различаются. - -> Tip|Подсказка: Лучше всего в качестве исходного языка оставить английский. - Вам будет несложно найти переводчиков с английского на любой другой язык, чего - нельзя сказать о переводчиках с любого другого языка. - -Вы можете установить [язык приложения|CApplication::language] в -[настройках приложения](/doc/guide/basics.application#application-configuration) или -изменить его непосредственно перед использованием возможностей интернационализации. - -> Tip|Подсказка: Иногда нам требуется считать язык пользователя из браузера. -Мы можем получить идентификатор локали используя [CHttpRequest::preferredLanguage]. - -Перевод -------- - -Самой востребованой возможностью интернационализации, скорее всего, является -перевод. Он включает в себя перевод сообщений и строк в представлениях. Первый используется -для перевода отдельных сообщений, второй — для перевода файлов целиком. - -В процессе перевода участвуют объект перевода, исходный язык и конечный язык. -В Yii исходный язык по умолчанию приравнивается к [исходному языку приложения|CApplication::sourceLanguage], а -язык — к [языку приложения|CApplication::language]. Если оба языка совпадают — перевод не производится. - -### Перевод сообщений - -Перевод сообщений осуществляется при помощи метода [Yii::t()|YiiBase::t]. Метод переводит -данное сообщение с [исходного языка приложения|CApplication::sourceLanguage] на [текущий язык приложения|CApplication::language]. - -При переводе необходимо указать категорию сообщения, так как -оно может иметь различные переводы в разных категориях (контекстах). -Категория с именем `yii` является зарезервированной для использования ядром фреймворка. - -Сообщения могут содержать параметры, которые при вызове [Yii::t()|YiiBase::t] заменяются -соответствующими им значениями. К примеру, следующий вызов метода заменит -`{alias}` на значение соответствующей переменной: - -~~~ -[php] -Yii::t('app', 'Path alias "{alias}" is redefined.', - array('{alias}'=>$alias)) -~~~ - -> Note|Примечание: переводимые сообщения не должны содержать переменных, изменяющих -строку (`"Invalid {$message} content."`). Если есть необходимость подставлять в строку -какие-либо значения — используйте параметры. - -Переведённые сообщения хранятся в репозитории, называемом *источник сообщений*. Источник -сообщений представляет собой экземпляр класса [CMessageSource] или его наследника. При выполнении -[Yii::t()|YiiBase::t] производится поиск сообщения в источнике сообщений и, если оно найдено — -возвращается его переведённая версия. - -Yii поддерживает несколько типов источников сообщений, перечисленных ниже. Также вы можете -создать свой источник, унаследовав его от [CMessageSource]. - - - [CPhpMessageSource]: переводы сообщений хранятся как пары ключ-значение -в массиве PHP. Исходное сообщение при этом является ключом, а переведённое — -значением. Каждый массив содержит переводы для определённой категории сообщений -и находится в отдельном файле, имя которого совпадает с названием категории. -Файлы с переводом для одного и того же языка хранятся в одной директории, -имеющей такое же имя, как и идентификатор языка. Директории для всех языков -располагаются в директории, указанной в [basePath|CPhpMessageSource::basePath]; - - - [CGettextMessageSource]: переводы сообщений хранятся в формате [GNU -Gettext](http://www.gnu.org/software/gettext/); - - - [CDbMessageSource]: переводы сообщений хранятся в базе данных. Подробнее см. [CDbMessageSource]. - -Источник сообщений загружается как [компонент приложения](/doc/guide/basics.application#application-component). -Сообщения, которые используются в приложении, хранятся в компоненте [messages|CApplication::messages]. -По умолчанию тип данного источника сообщений — [CPhpMessageSource]. Путь, по которому хранятся файлы перевода — `protected/messages`. - -Таким образом, для того, чтобы использовать механизм перевода сообщений, необходимо следующее: - - 1. Вызвать [Yii::t()|YiiBase::t] в нужных местах; - - 2. Создать файлы перевода `protected/messages/IdЯзыка/ИмяКатегории.php`. Каждый такой файл -просто возвращает массив переведённых сообщений. Обратите внимание, что при этом используется [CPhpMessageSource]; - - 3. В файле конфигурации установите значения [CApplication::sourceLanguage] и [CApplication::language]. - -> Tip|Подсказка: Если в качестве источника сообщений используется [CPhpMessageSource], для работы с сообщениями -может использоваться утилита `yiic`. При помощи команды `message` возможно выбрать из исходного кода все сообщения, -для которых необходим перевод и, при необходимости, объединить их с уже существующим переводом. Подробное -описание команды `message` можно получить набрав в консоли `yiic help message`. - -При использовании [CPhpMessageSource], сообщения для расширений, таких, как виджет или модуль, могут быть использованы особым образом. -То есть, если сообщение принадлежит расширению с именем класса `Xyz`, то категория сообщений может быть указана в формате -`Xyz.имяКатегории`. Соответствующий ей файл сообщений будет -`ПутьДоРасширения/messages/IDЯзыка/имяКатегории.php`, где `ПутьДоРасширения` — директория, -в которой находится класс расширения. При использовании `Yii::t()` для перевода -сообщения расширений должен использоваться следующий формат: - -~~~ -[php] -Yii::t('Xyz.имяКатегории', 'сообщение для перевода') -~~~ - -Yii поддерживает [формат выбора|CChoiceFormat], известный также как множественные -формы. Формат выбора предназначен -для выбора перевода в зависимости от заданного числа. К примеру, в английском языке -слово 'book' может быть единственного или множественного числа в зависимости от количества книг. В других -языках слово может не иметь специальной формы (как в китайском) или может подчиняться более сложным правилам -для множественного числа (как в русском). Формат выбора решает данную проблему простым, но в то же время -эффективным способом. - -Для использования формата выбора перевод должен содержать последовательность пар выражение-сообщение, разделённых -символом `|`: - -~~~ -[php] -'expr1#message1|expr2#message2|expr3#message3' -~~~ - -где `exprN` — выражение PHP, возвращающее логическое значение. Если выражение равно true — -используется соответствующий ему перевод и дальнейшие выражения не вычисляются. Выражение -может содержать специальную переменную `n` (не `$n`!), которая содержит число, переданное -первым параметром. Допустим, если мы используем перевод - -~~~ -[php] -'n==1#one book|n>1#many books' -~~~ - -и передаём число 2 параметром [Yii::t()|YiiBase::t], то получим `many books`: - -~~~ -[php] -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); -~~~ - -Если проверяется соответствие определённому числу, можно использовать сокращённую запись, -которая будет рассматриваться как `n==Number`: - -~~~ -[php] -'1#one book|n>1#many books' -~~~ - -### Формат для множественных форм - -С версии 1.1.6 доступен ещё один механизм, выполняющий перевод множественных -форм слова по правилам CLDR. Он позволяет использовать более простую запись -правил, что особенно актуально для языков со сложными правилами для множественных -форм слова. - -Правило для множественных форм английского языка, приведённое выше, можно -записать так: - -~~~ -[php] -Yii::t('test', 'cucumber|cucumbers', 1); -Yii::t('test', 'cucumber|cucumbers', 2); -Yii::t('test', 'cucumber|cucumbers', 0); -~~~ - -что даст на выходе: - -~~~ -cucumber -cucumbers -cucumbers -~~~ - -Если требуется включить в сообщение число, можно использовать следующий код: - -~~~ -[php] -echo Yii::t('test', '{n} cucumber|{n} cucumbers', 1); -~~~ - -Здесь `{n}` — специальный токен, который будет заменён на переданное число. -В данном случае будет напечатано `1 cucumber`. - -Можно передать дополнительные параметры: - -~~~ -[php] -Yii::t('test', '{username} has a cucumber|{username} has {n} cucumbers', -array(5, '{username}' => 'samdark')); -~~~ - -и даже заменить число чем-нибудь ещё: - -~~~ -[php] -function convertNumber($number) -{ - // число прописью - return $number; -} - -Yii::t('test', '{n} cucumber|{n} cucumbers', -array(5, '{n}' => convertNumber(5))); -~~~ - -Количество множественных форм будет варьироваться от языка к языку. К примеру: - -~~~ -[php] -Yii::t('app', '{n} cucumber|{n} cucumbers', 62); -Yii::t('app', '{n} cucumber|{n} cucumbers', 1.5); -Yii::t('app', '{n} cucumber|{n} cucumbers', 1); -Yii::t('app', '{n} cucumber|{n} cucumbers', 7); -~~~ - -при переводе на русский будет содержать четыре множественных формы вместо двух: - -~~~ -[php] -'{n} cucumber|{n} cucumbers' => '{n} огурец|{n} огурца|{n} огурцов|{n} огурца', -~~~ - -На выходе: - -~~~ -62 огурца -1.5 огурца -1 огурец -7 огурцов -~~~ - - - - -> Info|Информация: число и порядок выражений можно узнать в разделе - [Language Plural Rules](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) - на сайте CLDR. - -### Перевод файлов - -Перевод файлов осуществляется вызовом [CApplication::findLocalizedFile()]. Параметром -передаётся путь к файлу, который необходимо перевести. Метод ищет одноимённый файл в -подпапке `LocaleID`. Если файл найден — возвращается его путь, иначе — путь к исходному файлу. - -Перевод файла используется в основном при отображении представлений. При вызове одного из методов -отображения контроллера или виджета, файлы представления будут переведены автоматически. К примеру, если - [язык приложения|CApplication::language] установлен как `zh_cn`, а [исходный язык|CApplication::sourceLanguage] как `en_us` - — при отображении представления `edit` будет произведён поиск представления -`protected/views/ControllerID/zh_cn/edit.php`. Если оно найдено — будет использована переведённая версия, если нет — исходная, -расположенная в `protected/views/ControllerID/edit.php`. - -Перевод файлом можно использовать и для других целей. Например, для того, чтобы -отобразить переведённое изображение или загрузить локализованную версию файла. - -Форматирование даты и времени ------------------------------ - -Дата и время в разных странах и регионах часто форматируются по-разному. Задача форматирования -даты и времени таким образом сводится к генерации строки, подходящей для данной страны и региона. -Для этого в Yii используется [CDateFormatter]. - -Каждый экземпляр [CDateFormatter] соответствует некому языку приложения. Чтобы получить -форматтер для выбранного языка, мы можем просто обратиться к свойству приложения -[dateFormatter|CApplication::dateFormatter]. - -Класс [CDateFormatter] содержит два метода, предназначенных для форматирования UNIX -timestamp: - - - [format|CDateFormatter::format]: форматирует переданную в формате UNIX timestamp дату -согласно шаблону даты-времени (например, `$dateFormatter->format('dd.MM.yyyy', $timestamp)`); - - - [formatDateTime|CDateFormatter::formatDateTime]: форматирует переданную в формате UNIX timestamp дату -согласно шаблону, заданному для выбранного языка (например формат даты `short`, формат времени `long`). - -Форматирование чисел --------------------- - -Также, как дата и время, числа могут писаться по-разному в разных странах и регионах. -Форматирование чисел включает в себя форматирование десятичных дробей, -валют и чисел с процентами. Для выполнения данных задач в Yii используется класс [CNumberFormatter]. - -Для того, чтобы воспользоваться форматтером чисел, соответствующим выбранному языку, мы можем обратиться к -свойству приложения [numberFormatter|CApplication::numberFormatter]. - -Для форматирования целых чисел и дробей в классе [CNumberFormatter] есть следующие методы: - - - [format|CNumberFormatter::format]: форматирует число в соответствии с заданным форматом -(например, `$numberFormatter->format('#,##0.00',$number)`); - - - [formatDecimal|CNumberFormatter::formatDecimal]: форматирует число в соответствии с форматом, - заданным для текущего языка приложения; - - - [formatCurrency|CNumberFormatter::formatCurrency]: форматирует число и код валюты в соответствии с - форматом, заданным для текущего языка приложения; - - - [formatPercentage|CNumberFormatter::formatPercentage]: форматирует число в соответствии с форматом форматирования процентов, +Интернационализация +=================== + +Интернационализация (сокращённо I18N) — процесс создания приложения, которое может +работать на различных языках и с различными региональными особенностями без каких-либо +дополнительных изменений. Для веб-приложений это особенно важно, так как пользователь +может быть из любой точки мира. + +Yii поддерживает интернационализацию на нескольких уровнях: + + - Предоставляет региональные данные для всех возможных языков и их вариаций. + - Сервис для перевода сообщений и файлов. + - Форматирование дат и чисел в зависимости от региональных настроек. + +В последующих разделах мы рассмотрим перечисленные возможности более подробно. + +Региональные настройки и язык +----------------------------- + +Региональные настройки — это набор параметров, задающих язык пользователя, +страну и любые другие специальные настройки интерфейса. Обычно настройки определяются идентификатором, +состоящем из языка и региона. К примеру, идентификатор `en_US` соответствует английскому языку и настройкам для США. +В Yii все идентификаторы региональных настроек названы единообразно: `LanguageID` (язык) или `LanguageID_RegionID` (язык_регион) +в нижнем регистре. Например: `ru`, `en_us`. + +Региональные настройки хранятся в объекте класса [CLocale], который можно использовать для получения +зависимой от них информации, такой как символы и форматы валют и чисел, форматы дат и времени, название +месяцев и дней недели. Так как информация о языке уже содержится в идентификаторе, +в [CLocale] она не дублируется. По этой причине мы часто применяем в одном контексте термины «язык», «региональные настройки» и «локаль». + +Имея идентификатор языка, мы можем получить соответствующий ему объект [CLocale]: +`CLocale::getInstance($localeID)` или `CApplication::getLocale($localeID)`. + +> Info|Информация: в состав Yii включены региональные данные практически по +всем языкам и регионам. Данные получены из +[Common Locale Data Repository](http://unicode.org/cldr/) (CLDR). Доступны не +все данные, предоставляемые CLDR, так как там содержится большое количество +редко используемой информации. Пользователи также могут +предоставлять свои собственные специальные региональные данные. Для этого +настройте свойство [CApplication::localeDataPath], чтобы оно указывало на +директорию, содержащую специальные региональные данные. Обратитесь к файлам +региональных данных в директории `framework/i18n/data` для того, чтобы создать +файлы специальных региональных данных. + +В приложении Yii мы различаем [язык приложения|CApplication::language] и [исходный язык приложения|CApplication::sourceLanguage]. +Язык приложения — это язык (локаль) пользователя, который работает с приложением. Исходный язык приложения — язык, который используется +в исходном коде приложения. Интернационализация требуется только в том случае, если эти два языка различаются. + +> Tip|Подсказка: Лучше всего в качестве исходного языка оставить английский. + Вам будет несложно найти переводчиков с английского на любой другой язык, чего + нельзя сказать о переводчиках с любого другого языка. + +Вы можете установить [язык приложения|CApplication::language] в +[настройках приложения](/doc/guide/basics.application#application-configuration) или +изменить его непосредственно перед использованием возможностей интернационализации. + +> Tip|Подсказка: Иногда нам требуется считать язык пользователя из браузера. +Мы можем получить идентификатор локали используя [CHttpRequest::preferredLanguage]. + +Перевод +------- + +Самой востребованой возможностью интернационализации, скорее всего, является +перевод. Он включает в себя перевод сообщений и строк в представлениях. Первый используется +для перевода отдельных сообщений, второй — для перевода файлов целиком. + +В процессе перевода участвуют объект перевода, исходный язык и конечный язык. +В Yii исходный язык по умолчанию приравнивается к [исходному языку приложения|CApplication::sourceLanguage], а +язык — к [языку приложения|CApplication::language]. Если оба языка совпадают — перевод не производится. + +### Перевод сообщений + +Перевод сообщений осуществляется при помощи метода [Yii::t()|YiiBase::t]. Метод переводит +данное сообщение с [исходного языка приложения|CApplication::sourceLanguage] на [текущий язык приложения|CApplication::language]. + +При переводе необходимо указать категорию сообщения, так как +оно может иметь различные переводы в разных категориях (контекстах). +Категория с именем `yii` является зарезервированной для использования ядром фреймворка. + +Сообщения могут содержать параметры, которые при вызове [Yii::t()|YiiBase::t] заменяются +соответствующими им значениями. К примеру, следующий вызов метода заменит +`{alias}` на значение соответствующей переменной: + +~~~ +[php] +Yii::t('app', 'Path alias "{alias}" is redefined.', + array('{alias}'=>$alias)) +~~~ + +> Note|Примечание: переводимые сообщения не должны содержать переменных, изменяющих +строку (`"Invalid {$message} content."`). Если есть необходимость подставлять в строку +какие-либо значения — используйте параметры. + +Переведённые сообщения хранятся в репозитории, называемом *источник сообщений*. Источник +сообщений представляет собой экземпляр класса [CMessageSource] или его наследника. При выполнении +[Yii::t()|YiiBase::t] производится поиск сообщения в источнике сообщений и, если оно найдено — +возвращается его переведённая версия. + +Yii поддерживает несколько типов источников сообщений, перечисленных ниже. Также вы можете +создать свой источник, унаследовав его от [CMessageSource]. + + - [CPhpMessageSource]: переводы сообщений хранятся как пары ключ-значение +в массиве PHP. Исходное сообщение при этом является ключом, а переведённое — +значением. Каждый массив содержит переводы для определённой категории сообщений +и находится в отдельном файле, имя которого совпадает с названием категории. +Файлы с переводом для одного и того же языка хранятся в одной директории, +имеющей такое же имя, как и идентификатор языка. Директории для всех языков +располагаются в директории, указанной в [basePath|CPhpMessageSource::basePath]; + + - [CGettextMessageSource]: переводы сообщений хранятся в формате [GNU +Gettext](http://www.gnu.org/software/gettext/); + + - [CDbMessageSource]: переводы сообщений хранятся в базе данных. Подробнее см. [CDbMessageSource]. + +Источник сообщений загружается как [компонент приложения](/doc/guide/basics.application#application-component). +Сообщения, которые используются в приложении, хранятся в компоненте [messages|CApplication::messages]. +По умолчанию тип данного источника сообщений — [CPhpMessageSource]. Путь, по которому хранятся файлы перевода — `protected/messages`. + +Таким образом, для того, чтобы использовать механизм перевода сообщений, необходимо следующее: + + 1. Вызвать [Yii::t()|YiiBase::t] в нужных местах; + + 2. Создать файлы перевода `protected/messages/IdЯзыка/ИмяКатегории.php`. Каждый такой файл +просто возвращает массив переведённых сообщений. Обратите внимание, что при этом используется [CPhpMessageSource]; + + 3. В файле конфигурации установите значения [CApplication::sourceLanguage] и [CApplication::language]. + +> Tip|Подсказка: Если в качестве источника сообщений используется [CPhpMessageSource], для работы с сообщениями +может использоваться утилита `yiic`. При помощи команды `message` возможно выбрать из исходного кода все сообщения, +для которых необходим перевод и, при необходимости, объединить их с уже существующим переводом. Подробное +описание команды `message` можно получить набрав в консоли `yiic help message`. + +При использовании [CPhpMessageSource], сообщения для расширений, таких, как виджет или модуль, могут быть использованы особым образом. +То есть, если сообщение принадлежит расширению с именем класса `Xyz`, то категория сообщений может быть указана в формате +`Xyz.имяКатегории`. Соответствующий ей файл сообщений будет +`ПутьДоРасширения/messages/IDЯзыка/имяКатегории.php`, где `ПутьДоРасширения` — директория, +в которой находится класс расширения. При использовании `Yii::t()` для перевода +сообщения расширений должен использоваться следующий формат: + +~~~ +[php] +Yii::t('Xyz.имяКатегории', 'сообщение для перевода') +~~~ + +Yii поддерживает [формат выбора|CChoiceFormat], известный также как множественные +формы. Формат выбора предназначен +для выбора перевода в зависимости от заданного числа. К примеру, в английском языке +слово 'book' может быть единственного или множественного числа в зависимости от количества книг. В других +языках слово может не иметь специальной формы (как в китайском) или может подчиняться более сложным правилам +для множественного числа (как в русском). Формат выбора решает данную проблему простым, но в то же время +эффективным способом. + +Для использования формата выбора перевод должен содержать последовательность пар выражение-сообщение, разделённых +символом `|`: + +~~~ +[php] +'expr1#message1|expr2#message2|expr3#message3' +~~~ + +где `exprN` — выражение PHP, возвращающее логическое значение. Если выражение равно true — +используется соответствующий ему перевод и дальнейшие выражения не вычисляются. Выражение +может содержать специальную переменную `n` (не `$n`!), которая содержит число, переданное +первым параметром. Допустим, если мы используем перевод + +~~~ +[php] +'n==1#one book|n>1#many books' +~~~ + +и передаём число 2 параметром [Yii::t()|YiiBase::t], то получим `many books`: + +~~~ +[php] +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); +~~~ + +Если проверяется соответствие определённому числу, можно использовать сокращённую запись, +которая будет рассматриваться как `n==Number`: + +~~~ +[php] +'1#one book|n>1#many books' +~~~ + +### Формат для множественных форм + +С версии 1.1.6 доступен ещё один механизм, выполняющий перевод множественных +форм слова по правилам CLDR. Он позволяет использовать более простую запись +правил, что особенно актуально для языков со сложными правилами для множественных +форм слова. + +Правило для множественных форм английского языка, приведённое выше, можно +записать так: + +~~~ +[php] +Yii::t('test', 'cucumber|cucumbers', 1); +Yii::t('test', 'cucumber|cucumbers', 2); +Yii::t('test', 'cucumber|cucumbers', 0); +~~~ + +что даст на выходе: + +~~~ +cucumber +cucumbers +cucumbers +~~~ + +Если требуется включить в сообщение число, можно использовать следующий код: + +~~~ +[php] +echo Yii::t('test', '{n} cucumber|{n} cucumbers', 1); +~~~ + +Здесь `{n}` — специальный токен, который будет заменён на переданное число. +В данном случае будет напечатано `1 cucumber`. + +Можно передать дополнительные параметры: + +~~~ +[php] +Yii::t('test', '{username} has a cucumber|{username} has {n} cucumbers', +array(5, '{username}' => 'samdark')); +~~~ + +и даже заменить число чем-нибудь ещё: + +~~~ +[php] +function convertNumber($number) +{ + // число прописью + return $number; +} + +Yii::t('test', '{n} cucumber|{n} cucumbers', +array(5, '{n}' => convertNumber(5))); +~~~ + +Количество множественных форм будет варьироваться от языка к языку. К примеру: + +~~~ +[php] +Yii::t('app', '{n} cucumber|{n} cucumbers', 62); +Yii::t('app', '{n} cucumber|{n} cucumbers', 1.5); +Yii::t('app', '{n} cucumber|{n} cucumbers', 1); +Yii::t('app', '{n} cucumber|{n} cucumbers', 7); +~~~ + +при переводе на русский будет содержать четыре множественных формы вместо двух: + +~~~ +[php] +'{n} cucumber|{n} cucumbers' => '{n} огурец|{n} огурца|{n} огурцов|{n} огурца', +~~~ + +На выходе: + +~~~ +62 огурца +1.5 огурца +1 огурец +7 огурцов +~~~ + + + + +> Info|Информация: число и порядок выражений можно узнать в разделе + [Language Plural Rules](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) + на сайте CLDR. + +### Перевод файлов + +Перевод файлов осуществляется вызовом [CApplication::findLocalizedFile()]. Параметром +передаётся путь к файлу, который необходимо перевести. Метод ищет одноимённый файл в +подпапке `LocaleID`. Если файл найден — возвращается его путь, иначе — путь к исходному файлу. + +Перевод файла используется в основном при отображении представлений. При вызове одного из методов +отображения контроллера или виджета, файлы представления будут переведены автоматически. К примеру, если + [язык приложения|CApplication::language] установлен как `zh_cn`, а [исходный язык|CApplication::sourceLanguage] как `en_us` + — при отображении представления `edit` будет произведён поиск представления +`protected/views/ControllerID/zh_cn/edit.php`. Если оно найдено — будет использована переведённая версия, если нет — исходная, +расположенная в `protected/views/ControllerID/edit.php`. + +Перевод файлом можно использовать и для других целей. Например, для того, чтобы +отобразить переведённое изображение или загрузить локализованную версию файла. + +Форматирование даты и времени +----------------------------- + +Дата и время в разных странах и регионах часто форматируются по-разному. Задача форматирования +даты и времени таким образом сводится к генерации строки, подходящей для данной страны и региона. +Для этого в Yii используется [CDateFormatter]. + +Каждый экземпляр [CDateFormatter] соответствует некому языку приложения. Чтобы получить +форматтер для выбранного языка, мы можем просто обратиться к свойству приложения +[dateFormatter|CApplication::dateFormatter]. + +Класс [CDateFormatter] содержит два метода, предназначенных для форматирования UNIX +timestamp: + + - [format|CDateFormatter::format]: форматирует переданную в формате UNIX timestamp дату +согласно шаблону даты-времени (например, `$dateFormatter->format('dd.MM.yyyy', $timestamp)`); + + - [formatDateTime|CDateFormatter::formatDateTime]: форматирует переданную в формате UNIX timestamp дату +согласно шаблону, заданному для выбранного языка (например формат даты `short`, формат времени `long`). + +Форматирование чисел +-------------------- + +Также, как дата и время, числа могут писаться по-разному в разных странах и регионах. +Форматирование чисел включает в себя форматирование десятичных дробей, +валют и чисел с процентами. Для выполнения данных задач в Yii используется класс [CNumberFormatter]. + +Для того, чтобы воспользоваться форматтером чисел, соответствующим выбранному языку, мы можем обратиться к +свойству приложения [numberFormatter|CApplication::numberFormatter]. + +Для форматирования целых чисел и дробей в классе [CNumberFormatter] есть следующие методы: + + - [format|CNumberFormatter::format]: форматирует число в соответствии с заданным форматом +(например, `$numberFormatter->format('#,##0.00',$number)`); + + - [formatDecimal|CNumberFormatter::formatDecimal]: форматирует число в соответствии с форматом, + заданным для текущего языка приложения; + + - [formatCurrency|CNumberFormatter::formatCurrency]: форматирует число и код валюты в соответствии с + форматом, заданным для текущего языка приложения; + + - [formatPercentage|CNumberFormatter::formatPercentage]: форматирует число в соответствии с форматом форматирования процентов, заданным для текущего языка приложения. \ No newline at end of file diff --git a/docs/guide/ru/topics.performance.txt b/docs/guide/ru/topics.performance.txt index 25ca8d1c4..8eb3b14f1 100644 --- a/docs/guide/ru/topics.performance.txt +++ b/docs/guide/ru/topics.performance.txt @@ -1,190 +1,190 @@ -Улучшение производительности -============================ - -Производительность веб-приложения зависит от многих факторов. Главные из них — -обращение к базе данных, файловой системе и пропускная способность сети. -В Yii, для уменьшения падения производительности из-за самого фреймворка, учтён -каждый из этих факторов. Несмотря на это, многие части приложения можно улучшить -для получения более высокой производительности. - -Включение расширения APC ------------------------- - -Включение [расширения PHP APC](http://php.net/manual/en/book.apc.php) — -возможно, самый простой способ улучшить общую производительность приложения. -Расширение оптимизирует и кэширует промежуточный код PHP и выигрывает время, -затрачиваемое на интерпретацию скриптов PHP при каждом запросе. - -Отключение режима отладки -------------------------- - -Отключение режима отладки — ещё один лёгкий способ увеличить производительность. -Приложение Yii работает в режиме отладки если константа `YII_DEBUG` определена -как true. Режим отладки полезен при разработке, но не лучшим образом влияет на -производительность из-за использования большего числа компонентов. К примеру, -при журналировании ошибок, с каждым сообщением может записываться дополнительная -информация. - -Использование `yiilite.php` ---------------------------- - -Если используется [расширение PHP APC](http://php.net/manual/en/book.apc.php), -мы можем заменить `yii.php` другим загрузчиком — `yiilite.php`. Это даст приложению -ещё больший прирост производительности. - -Файл `yiilite.php` поставляется вместе с каждой версией Yii и представляет -собой собранные вместе часто используемые классы. Все комментарии и выражения трассировки -вырезаются, поэтому использование `yiilite.php` уменьшает количество подключаемых файлов -и выполняемого кода. - -Стоит заметить, что использование `yiilite.php` без APC может отрицательно -повлиять на производительность, так как `yiilite.php` включает в себя классы, -которые могут не требоваться при каждом запросе и отнимать некоторое время на -парсинг. Также было отмечено, что на некоторых конфигурациях сервера `yiilite.php` -медленнее даже при использовании APC. Лучший способ принятия решения об -использовании `yiilite.php` — провести тесты на прилагающемся демонстрационном -приложении `hello world`. - -Использование кэширования -------------------------- - -Как уже было описано в разделе «[кэширование](/doc/guide/caching.overview)», Yii -предоставляет несколько решений, которые могут значительно увеличить -производительность приложения. Если генерация каких-либо данных занимает много -времени, мы можем использовать [кэширование данных](/doc/guide/caching.data) для -того, чтобы делать это не так часто. Если часть страницы остаётся неизменной, мы -можем использовать [кэширование фрагментов](/doc/guide/caching.fragment). Если вся -страница не меняется, можно использовать [кэширование страниц](/doc/guide/caching.page). - -Если используется [Active Record](/doc/guide/database.ar), можно включить -кэширование структуры базы данных. Это можно сделать, установив в настройках -свойству [CDbConnection::schemaCachingDuration] значение, большее 0. - -Кроме описанных настроек приложения можно использовать кэширование на уровне -сервера. Описанное выше -[кэширование APC](/doc/guide/topics.performance#enabling-apc-extension) относится -как раз к ним. Существуют и другие решения, такие как -[Zend Optimizer](http://www.zend.com/en/products/guard/zend-optimizer), [eAccelerator](http://eaccelerator.net/) -и [Squid](http://www.squid-cache.org/). - -Оптимизация базы данных ------------------------ - -Получение данных из базы часто является узким местом производительности -приложения. Несмотря на то, что кэширование может смягчить потери, оно не решает -проблему полностью. Когда в базе содержатся огромные объёмы данных, и нужно -обновить кэш, получение данных может быть чрезмерно растратным при неверном -составлении схемы данных или запросов. - -Будьте осмотрительны при выборе индексов. Их использование может значительно -ускорить `SELECT`-запросы, но замедляет запросы `INSERT`, `UPDATE` и `DELETE`. - -Для сложных запросов рекомендуется создать view в базе данных вместо использования -запросов из кода PHP, которые СУБД разбирает каждый раз. - -Не злоупотребляйте [Active Record](/doc/guide/database.ar). Хоть [Active -Record](/doc/guide/database.ar) и является удобной проекцией данных в стиле ООП, -но производительность при её использовании, из-за использования объектов для -представления каждой строки результата, падает. Для приложений, интенсивно -работающих с данными, рекомендуется использовать [DAO](/doc/guide/database.dao) -или API для работы с СУБД на ещё более низком уровне. - -Последний по счёту, но не по значению совет: используйте `LIMIT` в -`SELECT`-запросах. Так вы сможете избежать получение избыточных данных из базы и -расхода требующейся для их хранения памяти, выделенной PHP. - -Минимизация файлов скриптов ---------------------------- - -Сложные страницы часто включают большое количество внешних файлов JavaScript и -CSS. Так как каждый файл равен дополнительному запросу к серверу, мы должны -уменьшить число файлов путём их слияния. Также не лишним будет уменьшить размер -каждого из них для уменьшения времени передачи по сети. Существует немало -инструментов для выполнения этих двух задач. - -Для страницы, генерируемой Yii, не исключено, что некоторые скрипты подключаются -компонентами, код которых изменять не хочется (например, компоненты ядра Yii). -Как минимизировать такие скрипты показано далее. - -Для начала опишем, какие файлы минимизировать. Зададим свойство -[scriptMap|CClientScript::scriptMap] компонента -[clientScript|CWebApplication::clientScript]. Это можно сделать как в настройках -приложения, так и в коде. К примеру: - -~~~ -[php] -$cs=Yii::app()->clientScript; -$cs->scriptMap=array( - 'jquery.js'=>'/js/all.js', - 'jquery.ajaxqueue.js'=>'/js/all.js', - 'jquery.metadata.js'=>'/js/all.js', - … -); -~~~ - -Приведённый код сделает файлы JavaScript доступными по URL `/js/all.js`. -Если какой-либо из этих файлов требуется для каких-либо компонент, Yii -подключит URL (один раз) вместо того, чтобы подключать отдельные файлы. - -Нам понадобится использовать какой-либо инструмент для слияния (и, возможно, сжатия) -JavaScript в один файл и записать результат в `js/all.js`. - -То же относится и к файлам CSS. - -Увеличить скорость загрузки страницы можно также при помощи -[Google AJAX Libraries API](http://code.google.com/apis/ajaxlibs/). К примеру, -мы можем подключить `jquery.js` с серверов Google вместо того, чтобы использовать -свой сервер. Для того, чтобы это сделать нужно настроить `scriptMap` следующим -образом: - -~~~ -[php] -$cs=Yii::app()->clientScript; -$cs->scriptMap=array( - 'jquery.js'=>false, - 'jquery.ajaxqueue.js'=>false, - 'jquery.metadata.js'=>false, - … -); -~~~ - -Устанавливая значения в false мы запрещаем Yii генерировать код для включения -соответствующих файлов. Вместо этого подключим их с серверов Google: - -~~~ -[php] - - - - -… - -~~~ - -Использование символьных ссылок для ресурсов --------------------------------------------- - -Если ваш проект интенсивно использует ресурсы, то вы можете увеличить его производительность -посредством [символьных ссылок](http://ru.wikipedia.org/wiki/Символьная_ссылка) -вместо стандартного копирования файлов. Для того, чтобы включить их вам нужно задать -свойство [linkAssets|CAssetManager::linkAssets] компонента приложения `assetManager` -используя конфигурационный файл `protected/config/main.php`: - -~~~ -[php] -return array( - // ... - 'components' => array( - // ... - 'assetManager' => array( - 'linkAssets' => true, - ), - ), -); -~~~ - -Имейте ввиду, что [это потребует дополнительных настроек|CAssetManager::linkAssets]. +Улучшение производительности +============================ + +Производительность веб-приложения зависит от многих факторов. Главные из них — +обращение к базе данных, файловой системе и пропускная способность сети. +В Yii, для уменьшения падения производительности из-за самого фреймворка, учтён +каждый из этих факторов. Несмотря на это, многие части приложения можно улучшить +для получения более высокой производительности. + +Включение расширения APC +------------------------ + +Включение [расширения PHP APC](http://php.net/manual/en/book.apc.php) — +возможно, самый простой способ улучшить общую производительность приложения. +Расширение оптимизирует и кэширует промежуточный код PHP и выигрывает время, +затрачиваемое на интерпретацию скриптов PHP при каждом запросе. + +Отключение режима отладки +------------------------- + +Отключение режима отладки — ещё один лёгкий способ увеличить производительность. +Приложение Yii работает в режиме отладки если константа `YII_DEBUG` определена +как true. Режим отладки полезен при разработке, но не лучшим образом влияет на +производительность из-за использования большего числа компонентов. К примеру, +при журналировании ошибок, с каждым сообщением может записываться дополнительная +информация. + +Использование `yiilite.php` +--------------------------- + +Если используется [расширение PHP APC](http://php.net/manual/en/book.apc.php), +мы можем заменить `yii.php` другим загрузчиком — `yiilite.php`. Это даст приложению +ещё больший прирост производительности. + +Файл `yiilite.php` поставляется вместе с каждой версией Yii и представляет +собой собранные вместе часто используемые классы. Все комментарии и выражения трассировки +вырезаются, поэтому использование `yiilite.php` уменьшает количество подключаемых файлов +и выполняемого кода. + +Стоит заметить, что использование `yiilite.php` без APC может отрицательно +повлиять на производительность, так как `yiilite.php` включает в себя классы, +которые могут не требоваться при каждом запросе и отнимать некоторое время на +парсинг. Также было отмечено, что на некоторых конфигурациях сервера `yiilite.php` +медленнее даже при использовании APC. Лучший способ принятия решения об +использовании `yiilite.php` — провести тесты на прилагающемся демонстрационном +приложении `hello world`. + +Использование кэширования +------------------------- + +Как уже было описано в разделе «[кэширование](/doc/guide/caching.overview)», Yii +предоставляет несколько решений, которые могут значительно увеличить +производительность приложения. Если генерация каких-либо данных занимает много +времени, мы можем использовать [кэширование данных](/doc/guide/caching.data) для +того, чтобы делать это не так часто. Если часть страницы остаётся неизменной, мы +можем использовать [кэширование фрагментов](/doc/guide/caching.fragment). Если вся +страница не меняется, можно использовать [кэширование страниц](/doc/guide/caching.page). + +Если используется [Active Record](/doc/guide/database.ar), можно включить +кэширование структуры базы данных. Это можно сделать, установив в настройках +свойству [CDbConnection::schemaCachingDuration] значение, большее 0. + +Кроме описанных настроек приложения можно использовать кэширование на уровне +сервера. Описанное выше +[кэширование APC](/doc/guide/topics.performance#enabling-apc-extension) относится +как раз к ним. Существуют и другие решения, такие как +[Zend Optimizer](http://www.zend.com/en/products/guard/zend-optimizer), [eAccelerator](http://eaccelerator.net/) +и [Squid](http://www.squid-cache.org/). + +Оптимизация базы данных +----------------------- + +Получение данных из базы часто является узким местом производительности +приложения. Несмотря на то, что кэширование может смягчить потери, оно не решает +проблему полностью. Когда в базе содержатся огромные объёмы данных, и нужно +обновить кэш, получение данных может быть чрезмерно растратным при неверном +составлении схемы данных или запросов. + +Будьте осмотрительны при выборе индексов. Их использование может значительно +ускорить `SELECT`-запросы, но замедляет запросы `INSERT`, `UPDATE` и `DELETE`. + +Для сложных запросов рекомендуется создать view в базе данных вместо использования +запросов из кода PHP, которые СУБД разбирает каждый раз. + +Не злоупотребляйте [Active Record](/doc/guide/database.ar). Хоть [Active +Record](/doc/guide/database.ar) и является удобной проекцией данных в стиле ООП, +но производительность при её использовании, из-за использования объектов для +представления каждой строки результата, падает. Для приложений, интенсивно +работающих с данными, рекомендуется использовать [DAO](/doc/guide/database.dao) +или API для работы с СУБД на ещё более низком уровне. + +Последний по счёту, но не по значению совет: используйте `LIMIT` в +`SELECT`-запросах. Так вы сможете избежать получение избыточных данных из базы и +расхода требующейся для их хранения памяти, выделенной PHP. + +Минимизация файлов скриптов +--------------------------- + +Сложные страницы часто включают большое количество внешних файлов JavaScript и +CSS. Так как каждый файл равен дополнительному запросу к серверу, мы должны +уменьшить число файлов путём их слияния. Также не лишним будет уменьшить размер +каждого из них для уменьшения времени передачи по сети. Существует немало +инструментов для выполнения этих двух задач. + +Для страницы, генерируемой Yii, не исключено, что некоторые скрипты подключаются +компонентами, код которых изменять не хочется (например, компоненты ядра Yii). +Как минимизировать такие скрипты показано далее. + +Для начала опишем, какие файлы минимизировать. Зададим свойство +[scriptMap|CClientScript::scriptMap] компонента +[clientScript|CWebApplication::clientScript]. Это можно сделать как в настройках +приложения, так и в коде. К примеру: + +~~~ +[php] +$cs=Yii::app()->clientScript; +$cs->scriptMap=array( + 'jquery.js'=>'/js/all.js', + 'jquery.ajaxqueue.js'=>'/js/all.js', + 'jquery.metadata.js'=>'/js/all.js', + … +); +~~~ + +Приведённый код сделает файлы JavaScript доступными по URL `/js/all.js`. +Если какой-либо из этих файлов требуется для каких-либо компонент, Yii +подключит URL (один раз) вместо того, чтобы подключать отдельные файлы. + +Нам понадобится использовать какой-либо инструмент для слияния (и, возможно, сжатия) +JavaScript в один файл и записать результат в `js/all.js`. + +То же относится и к файлам CSS. + +Увеличить скорость загрузки страницы можно также при помощи +[Google AJAX Libraries API](http://code.google.com/apis/ajaxlibs/). К примеру, +мы можем подключить `jquery.js` с серверов Google вместо того, чтобы использовать +свой сервер. Для того, чтобы это сделать нужно настроить `scriptMap` следующим +образом: + +~~~ +[php] +$cs=Yii::app()->clientScript; +$cs->scriptMap=array( + 'jquery.js'=>false, + 'jquery.ajaxqueue.js'=>false, + 'jquery.metadata.js'=>false, + … +); +~~~ + +Устанавливая значения в false мы запрещаем Yii генерировать код для включения +соответствующих файлов. Вместо этого подключим их с серверов Google: + +~~~ +[php] + + + + +… + +~~~ + +Использование символьных ссылок для ресурсов +-------------------------------------------- + +Если ваш проект интенсивно использует ресурсы, то вы можете увеличить его производительность +посредством [символьных ссылок](http://ru.wikipedia.org/wiki/Символьная_ссылка) +вместо стандартного копирования файлов. Для того, чтобы включить их вам нужно задать +свойство [linkAssets|CAssetManager::linkAssets] компонента приложения `assetManager` +используя конфигурационный файл `protected/config/main.php`: + +~~~ +[php] +return array( + // ... + 'components' => array( + // ... + 'assetManager' => array( + 'linkAssets' => true, + ), + ), +); +~~~ + +Имейте ввиду, что [это потребует дополнительных настроек|CAssetManager::linkAssets]. diff --git a/docs/guide/ru/topics.prado.txt b/docs/guide/ru/topics.prado.txt index 23df6cbb6..b1176ad56 100644 --- a/docs/guide/ru/topics.prado.txt +++ b/docs/guide/ru/topics.prado.txt @@ -1,182 +1,182 @@ -Альтернативный язык шаблонов -============================ - -Yii позволяет разработчику использовать свой любимый язык шаблонов (например, -Prado или Smarty) для описания представлений контроллера или виджета. Для этого -требуется написать и установить свой компонент [viewRenderer|CWebApplication::viewRenderer]. -Обработчик представления перехватывает вызовы [CBaseController::renderFile], -компилирует файл представления с альтернативным синтаксисом и отдаёт результат -компиляции. - -> Info|Информация: Не рекомендуется использовать альтернативный синтаксис шаблонов -для описания представлений компонентов, выкладываемых в открытый доступ. Это -приведёт к требованию использовать тот же синтаксис, что использован в представлении -компонента. - -Далее мы покажем, как использовать [CPradoViewRenderer] — обработчик представлений, -позволяющий разработчику использовать синтаксис шаблонов, используемый в фреймворке -[Prado](http://www.pradosoft.com/). Если вы хотите реализовать свои обработчики -представлений, обязательно изучите [CPradoViewRenderer]. - -Использование `CPradoViewRenderer` ----------------------------------- - -Для использования [CPradoViewRenderer] необходимо настроить приложение -следующим образом: - -~~~ -[php] -return array( - 'components'=>array( - …, - 'viewRenderer'=>array( - 'class'=>'CPradoViewRenderer', - ), - ), -); -~~~ - -По умолчанию [CPradoViewRenderer] будет компилировать исходные файлы представлений и -сохранять получаемые файлы PHP в директорию [runtime](/doc/guide/basics.convention#directory). -PHP-файлы изменяются только в том случае, если изменено исходное представление. -Поэтому, использование [CPradoViewRenderer] влечёт за собой очень незначительное -падение производительности. - -> Tip|Подсказка: Несмотря на то, что [CPradoViewRenderer] добавляет новый синтаксис -для более быстрого и удобного описания представлений, вы можете использовать код -PHP также, как и в обычных представлениях. - -Ниже будут описаны конструкции, поддерживаемые [CPradoViewRenderer]. - -### Сокращённые PHP-тэги - -Сокращённые PHP-тэги — хороший способ сократить код, используемый в представлении. -Выражение `<%= expression %>` преобразуется в ``. -`<% statement %>` — в ``. К примеру: - -~~~ -[php] -<%= CHtml::textField($name,'value'); %> -<% foreach($models as $model): %> -~~~ - -преобразуется в - -~~~ -[php] - - -~~~ - -### Компонентные тэги - -Компонентные тэги используются для того, чтобы вставить в представление -[виджет](/doc/guide/basics.view#widget). Синтаксис следующий: - -~~~ -[php] - - // содержимое виджета - - -// виджет без содержимого - -~~~ - -Здесь `WidgetClass` определяет имя класса виджета или -[псевдоним пути](/doc/guide/basics.namespace). Начальные значения свойств могут -быть как строками, заключенными в кавычки, так и выражениями PHP, заключёнными в -фигурные скобки. К примеру: - -~~~ -[php] - -~~~ - -преобразуется в - -~~~ -[php] -widget('CCaptcha', array( - 'captchaAction'=>'captcha', - 'showRefreshButton'=>false)); ?> -~~~ - -> Note|Примечание: Значение `showRefreshButton` задано как `{false}` -вместо `"false"` так как последнее означает строку, а не логическое значение. - -### Кэширующие тэги - -Кэширующие тэги — краткий способ использования -[кэширования фрагментов](/doc/guide/caching.fragment). Синтаксис следующий: - -~~~ -[php] - - // содержимое, которое необходимо кэшировать - -~~~ - -Здесь `fragmentID` — уникальный идентификатор кэшируемого объекта. Пары имя-значение -используются для настройки кэширования фрагментов. К примеру: - -~~~ -[php] - - // информация из профиля пользователя - -~~~ - -будет преобразовано в - -~~~ -[php] -beginCache('profile', array('duration'=>3600))): ?> - // информация из профиля пользователя -endCache(); endif; ?> -~~~ - -### Захватывающие тэги - -Как и кэширующие тэги, захватывающие тэги — компактный способ использования -[CBaseController::beginClip] и [CBaseController::endClip]. Синтаксис следующий: - -~~~ -[php] - - // содержимое для захвата - -~~~ - -Здесь `clipID` — уникальный идентификатор захваченного содержимого. -Захватывающие тэги преобразуются следующим образом: - -~~~ -[php] -beginClip('clipID'); ?> - // содержимое для захвата -endClip(); ?> -~~~ - -### Тэги комментариев - -Тэги комментариев используются для написания комментариев, доступных исключительно -разработчикам. Данные тэги будут удалены непосредственно перед отображением -представления. Синтаксис следующий: - -~~~ -[php] - -~~~ - -Одновременное использование шаблонов разного формата ----------------------------------------------------- - -Начиная с версии 1.1.2 возможно использовать одновременно как альтернативный, так и -обычный PHP синтаксис шаблонов. Для этого необходимо задать свойству обработчика -шаблонов [CViewRenderer::fileExtension] значение, отличное от `.php`. -К примеру, если оно будет выставлено в `.tpl`, то все шаблоны с расширением `.tpl` будут -обрабатываться выбранным обработчиком представлений. Шаблоны с расширением `.php`, +Альтернативный язык шаблонов +============================ + +Yii позволяет разработчику использовать свой любимый язык шаблонов (например, +Prado или Smarty) для описания представлений контроллера или виджета. Для этого +требуется написать и установить свой компонент [viewRenderer|CWebApplication::viewRenderer]. +Обработчик представления перехватывает вызовы [CBaseController::renderFile], +компилирует файл представления с альтернативным синтаксисом и отдаёт результат +компиляции. + +> Info|Информация: Не рекомендуется использовать альтернативный синтаксис шаблонов +для описания представлений компонентов, выкладываемых в открытый доступ. Это +приведёт к требованию использовать тот же синтаксис, что использован в представлении +компонента. + +Далее мы покажем, как использовать [CPradoViewRenderer] — обработчик представлений, +позволяющий разработчику использовать синтаксис шаблонов, используемый в фреймворке +[Prado](http://www.pradosoft.com/). Если вы хотите реализовать свои обработчики +представлений, обязательно изучите [CPradoViewRenderer]. + +Использование `CPradoViewRenderer` +---------------------------------- + +Для использования [CPradoViewRenderer] необходимо настроить приложение +следующим образом: + +~~~ +[php] +return array( + 'components'=>array( + …, + 'viewRenderer'=>array( + 'class'=>'CPradoViewRenderer', + ), + ), +); +~~~ + +По умолчанию [CPradoViewRenderer] будет компилировать исходные файлы представлений и +сохранять получаемые файлы PHP в директорию [runtime](/doc/guide/basics.convention#directory). +PHP-файлы изменяются только в том случае, если изменено исходное представление. +Поэтому, использование [CPradoViewRenderer] влечёт за собой очень незначительное +падение производительности. + +> Tip|Подсказка: Несмотря на то, что [CPradoViewRenderer] добавляет новый синтаксис +для более быстрого и удобного описания представлений, вы можете использовать код +PHP также, как и в обычных представлениях. + +Ниже будут описаны конструкции, поддерживаемые [CPradoViewRenderer]. + +### Сокращённые PHP-тэги + +Сокращённые PHP-тэги — хороший способ сократить код, используемый в представлении. +Выражение `<%= expression %>` преобразуется в ``. +`<% statement %>` — в ``. К примеру: + +~~~ +[php] +<%= CHtml::textField($name,'value'); %> +<% foreach($models as $model): %> +~~~ + +преобразуется в + +~~~ +[php] + + +~~~ + +### Компонентные тэги + +Компонентные тэги используются для того, чтобы вставить в представление +[виджет](/doc/guide/basics.view#widget). Синтаксис следующий: + +~~~ +[php] + + // содержимое виджета + + +// виджет без содержимого + +~~~ + +Здесь `WidgetClass` определяет имя класса виджета или +[псевдоним пути](/doc/guide/basics.namespace). Начальные значения свойств могут +быть как строками, заключенными в кавычки, так и выражениями PHP, заключёнными в +фигурные скобки. К примеру: + +~~~ +[php] + +~~~ + +преобразуется в + +~~~ +[php] +widget('CCaptcha', array( + 'captchaAction'=>'captcha', + 'showRefreshButton'=>false)); ?> +~~~ + +> Note|Примечание: Значение `showRefreshButton` задано как `{false}` +вместо `"false"` так как последнее означает строку, а не логическое значение. + +### Кэширующие тэги + +Кэширующие тэги — краткий способ использования +[кэширования фрагментов](/doc/guide/caching.fragment). Синтаксис следующий: + +~~~ +[php] + + // содержимое, которое необходимо кэшировать + +~~~ + +Здесь `fragmentID` — уникальный идентификатор кэшируемого объекта. Пары имя-значение +используются для настройки кэширования фрагментов. К примеру: + +~~~ +[php] + + // информация из профиля пользователя + +~~~ + +будет преобразовано в + +~~~ +[php] +beginCache('profile', array('duration'=>3600))): ?> + // информация из профиля пользователя +endCache(); endif; ?> +~~~ + +### Захватывающие тэги + +Как и кэширующие тэги, захватывающие тэги — компактный способ использования +[CBaseController::beginClip] и [CBaseController::endClip]. Синтаксис следующий: + +~~~ +[php] + + // содержимое для захвата + +~~~ + +Здесь `clipID` — уникальный идентификатор захваченного содержимого. +Захватывающие тэги преобразуются следующим образом: + +~~~ +[php] +beginClip('clipID'); ?> + // содержимое для захвата +endClip(); ?> +~~~ + +### Тэги комментариев + +Тэги комментариев используются для написания комментариев, доступных исключительно +разработчикам. Данные тэги будут удалены непосредственно перед отображением +представления. Синтаксис следующий: + +~~~ +[php] + +~~~ + +Одновременное использование шаблонов разного формата +---------------------------------------------------- + +Начиная с версии 1.1.2 возможно использовать одновременно как альтернативный, так и +обычный PHP синтаксис шаблонов. Для этого необходимо задать свойству обработчика +шаблонов [CViewRenderer::fileExtension] значение, отличное от `.php`. +К примеру, если оно будет выставлено в `.tpl`, то все шаблоны с расширением `.tpl` будут +обрабатываться выбранным обработчиком представлений. Шаблоны с расширением `.php`, как и ранее, будут использовать стандартный синтаксис PHP. \ No newline at end of file diff --git a/docs/guide/ru/topics.security.txt b/docs/guide/ru/topics.security.txt index 5761370d3..7cc5826b4 100644 --- a/docs/guide/ru/topics.security.txt +++ b/docs/guide/ru/topics.security.txt @@ -1,122 +1,122 @@ -Безопасность -============ - -Предотвращение межсайтового скриптинга --------------------------------------- -Межсайтововый скриптинг (также известный как XSS) — злонамеренный сбор информации -пользователя через страницы веб-приложения. Чаще всего, производящий атаку, используя -уязвимости приложения, включает в текст страницы JavaScript, VBScript, ActiveX, -HTML или Flash. Делается это для получения информации других пользователей приложения -и последующего её использования в нехороших целях. К примеру, плохо написанный форум -может отображать сообщения пользователей без какой-либо проверки. Атакующий может -вставить JavaScript-код в сообщение. Все, кто прочитает это сообщение, выполнит -код на своём компьютере. - -Чтобы не допустить XSS-атак, нужно всегда проверять то, что ввёл пользователь, -прежде чем это отображать. Конечно, чтобы не допустить ввода скриптов, можно -кодировать все HTML-сущности. В некоторых ситуациях такое поведение нежелательно, -так как ввод HTML становится недоступен. - -Yii включает в себя библиотеку [HTMLPurifier](http://htmlpurifier.org/) -и предоставляет разработчику полезный компонент [CHtmlPurifier], который -может отфильтровать весь вредоносный код при помощи тщательно проверенного -белого листа. Также компонент делает код совместимым со стандартами. - -[CHtmlPurifier] может быть использован и как [виджет](/doc/guide/basics.view#widget), -и как [фильтр](/doc/guide/basics.controller#filter). При использовании в качестве -виджета [CHtmlPurifier] обрабатывает заключённое в него содержимое: - -~~~ -[php] -beginWidget('CHtmlPurifier'); ?> -…этот текст будет подвергнут деликатной санобработке… -endWidget(); ?> -~~~ - - -Предотвращение подделки межсайтовых запросов --------------------------------------------- - -Подделка межсайтового запроса (CSRF) — атака, при которой сайт атакующего -заставляет браузер пользователя выполнить какое-либо действие на другом сайте. -К примеру, на сайте атакующего есть страница, содержащая тэг `img` с атрибутом `src`, -указывающим на сайт банка: `http://bank.example/перевод?сумма=10000&кому=кулхацкеру`. -Если в браузере пользователя установлен cookie, позволяющий запомнить -его на сайте, посещение такой страницы вызовет перевод 10000 тугриков нехорошему -кулхацкеру. В CSRF, в отличие от межсайтового скриптинга, основанного на доверии -пользователя к некоторому сайту, используется доверие сайта определённому пользователю. - -Для того, чтобы не допустить CSRF, важно придерживаться простого правила: -`GET` — только для получения данных. Ничего менять при GET-запросах нельзя. -Для `POST` необходимо использовать случайное значение, которое можно проверить -на сервере и убедиться, что запрос идёт оттуда, откуда нужно. - -В Yii реализована защита от CSRF-атаки, проводимой через `POST`. Защита основана -на хранении случайного значения в cookie и сравнения его со значением в `POST`. - -По умолчанию, защита от CSRF отключена. Для её включения необходимо настроить -компонент [CHttpRequest] в -[файле конфигурации](/doc/guide/basics.application#application-configuration): - -~~~ -[php] -return array( - 'components'=>array( - 'request'=>array( - 'enableCsrfValidation'=>true, - ), - ), -); -~~~ - -Для отображения формы следует использовать [CHtml::form] вместо написания HTML-тэга. -Данный метод позволяет автоматически включить случайное значение, используемое для -проверки на CSRF, как скрытое поле формы. - - -Предотвращение атак через cookie --------------------------------- -Защита cookie очень важна, так как именно в них чаще всего хранится ID -сессии. Если злоумышленник получит ID сессии, он получит и всю информацию, которая -в ней хранится. - -Есть несколько способов предотвращения атак через cookie: - -* Использовать SSL для создания защищённого соединения и передавать cookie только - через него. Атакующий не сможет расшифровать содержимое передаваемых cookie. -* Вовремя объявлять сессию устаревшей, включая все cookie и маркеры сессии, - для того, чтобы снизить возможность атаки. -* Предотвратить XSS, тем самым исключив захват cookie. -* Проверять данные cookie и определять, изменены ли они. - -В Yii реализована проверка на изменения через подсчёт хэша HMAC от значений -cookie. - -По умолчанию проверка cookie отключена. Для её включения необходимо в -[конфигурации приложения](/doc/guide/basics.application#application-configuration) -настроить компонент [CHttpRequest] следующим образом: - -~~~ -[php] -return array( - 'components'=>array( - 'request'=>array( - 'enableCookieValidation'=>true, - ), - ), -); -~~~ - -При использовании проверки cookie, обращаться к ним необходимо через коллекцию -[cookies|CHttpRequest::cookies], а не напрямую через `$_COOKIES`: - -~~~ -[php] -// Получаем cookie с заданным именем -$cookie=Yii::app()->request->cookies[$name]; -$value=$cookie->value; -… -// Отсылаем cookie -$cookie=new CHttpCookie($name,$value); -Yii::app()->request->cookies[$name]=$cookie; +Безопасность +============ + +Предотвращение межсайтового скриптинга +-------------------------------------- +Межсайтововый скриптинг (также известный как XSS) — злонамеренный сбор информации +пользователя через страницы веб-приложения. Чаще всего, производящий атаку, используя +уязвимости приложения, включает в текст страницы JavaScript, VBScript, ActiveX, +HTML или Flash. Делается это для получения информации других пользователей приложения +и последующего её использования в нехороших целях. К примеру, плохо написанный форум +может отображать сообщения пользователей без какой-либо проверки. Атакующий может +вставить JavaScript-код в сообщение. Все, кто прочитает это сообщение, выполнит +код на своём компьютере. + +Чтобы не допустить XSS-атак, нужно всегда проверять то, что ввёл пользователь, +прежде чем это отображать. Конечно, чтобы не допустить ввода скриптов, можно +кодировать все HTML-сущности. В некоторых ситуациях такое поведение нежелательно, +так как ввод HTML становится недоступен. + +Yii включает в себя библиотеку [HTMLPurifier](http://htmlpurifier.org/) +и предоставляет разработчику полезный компонент [CHtmlPurifier], который +может отфильтровать весь вредоносный код при помощи тщательно проверенного +белого листа. Также компонент делает код совместимым со стандартами. + +[CHtmlPurifier] может быть использован и как [виджет](/doc/guide/basics.view#widget), +и как [фильтр](/doc/guide/basics.controller#filter). При использовании в качестве +виджета [CHtmlPurifier] обрабатывает заключённое в него содержимое: + +~~~ +[php] +beginWidget('CHtmlPurifier'); ?> +…этот текст будет подвергнут деликатной санобработке… +endWidget(); ?> +~~~ + + +Предотвращение подделки межсайтовых запросов +-------------------------------------------- + +Подделка межсайтового запроса (CSRF) — атака, при которой сайт атакующего +заставляет браузер пользователя выполнить какое-либо действие на другом сайте. +К примеру, на сайте атакующего есть страница, содержащая тэг `img` с атрибутом `src`, +указывающим на сайт банка: `http://bank.example/перевод?сумма=10000&кому=кулхацкеру`. +Если в браузере пользователя установлен cookie, позволяющий запомнить +его на сайте, посещение такой страницы вызовет перевод 10000 тугриков нехорошему +кулхацкеру. В CSRF, в отличие от межсайтового скриптинга, основанного на доверии +пользователя к некоторому сайту, используется доверие сайта определённому пользователю. + +Для того, чтобы не допустить CSRF, важно придерживаться простого правила: +`GET` — только для получения данных. Ничего менять при GET-запросах нельзя. +Для `POST` необходимо использовать случайное значение, которое можно проверить +на сервере и убедиться, что запрос идёт оттуда, откуда нужно. + +В Yii реализована защита от CSRF-атаки, проводимой через `POST`. Защита основана +на хранении случайного значения в cookie и сравнения его со значением в `POST`. + +По умолчанию, защита от CSRF отключена. Для её включения необходимо настроить +компонент [CHttpRequest] в +[файле конфигурации](/doc/guide/basics.application#application-configuration): + +~~~ +[php] +return array( + 'components'=>array( + 'request'=>array( + 'enableCsrfValidation'=>true, + ), + ), +); +~~~ + +Для отображения формы следует использовать [CHtml::form] вместо написания HTML-тэга. +Данный метод позволяет автоматически включить случайное значение, используемое для +проверки на CSRF, как скрытое поле формы. + + +Предотвращение атак через cookie +-------------------------------- +Защита cookie очень важна, так как именно в них чаще всего хранится ID +сессии. Если злоумышленник получит ID сессии, он получит и всю информацию, которая +в ней хранится. + +Есть несколько способов предотвращения атак через cookie: + +* Использовать SSL для создания защищённого соединения и передавать cookie только + через него. Атакующий не сможет расшифровать содержимое передаваемых cookie. +* Вовремя объявлять сессию устаревшей, включая все cookie и маркеры сессии, + для того, чтобы снизить возможность атаки. +* Предотвратить XSS, тем самым исключив захват cookie. +* Проверять данные cookie и определять, изменены ли они. + +В Yii реализована проверка на изменения через подсчёт хэша HMAC от значений +cookie. + +По умолчанию проверка cookie отключена. Для её включения необходимо в +[конфигурации приложения](/doc/guide/basics.application#application-configuration) +настроить компонент [CHttpRequest] следующим образом: + +~~~ +[php] +return array( + 'components'=>array( + 'request'=>array( + 'enableCookieValidation'=>true, + ), + ), +); +~~~ + +При использовании проверки cookie, обращаться к ним необходимо через коллекцию +[cookies|CHttpRequest::cookies], а не напрямую через `$_COOKIES`: + +~~~ +[php] +// Получаем cookie с заданным именем +$cookie=Yii::app()->request->cookies[$name]; +$value=$cookie->value; +… +// Отсылаем cookie +$cookie=new CHttpCookie($name,$value); +Yii::app()->request->cookies[$name]=$cookie; ~~~ \ No newline at end of file diff --git a/docs/guide/ru/topics.webservice.txt b/docs/guide/ru/topics.webservice.txt index fbe473988..694e31b48 100644 --- a/docs/guide/ru/topics.webservice.txt +++ b/docs/guide/ru/topics.webservice.txt @@ -1,211 +1,211 @@ -Веб-сервисы -=========== - -[Веб-сервис](http://ru.wikipedia.org/wiki/Веб_сервис) — программная система, -разработанная для обеспечения взаимодействия между несколькими компьютерами через -сеть. В веб-приложении это обычно набор API, который можно использовать через -интернет для выполнения действий на удалённом сервере, обслуживающем веб-сервис. -К примеру, клиент, основанный на [Flex](http://www.adobe.com/products/flex/), может -вызывать функции, реализованные на сервере в PHP-приложении. В качестве базового -уровня протокола используется [SOAP](http://en.wikipedia.org/wiki/SOAP). - -Для того, чтобы упростить задачу создания веб-сервиса, в Yii включены -[CWebService] и [CWebServiceAction]. API сгруппированы по классам, которые называются -*провайдерами*. Для каждого класса Yii генерирует [WSDL](http://www.w3.org/TR/wsdl), -описывающий функционал предоставляемого API и правила его использования клиентом. -При обработке вызова клиента, Yii создаёт соответствующий ему экземпляр провайдера, -вызывает метод API и отвечает на запрос. - -> Note|Примечание: Для работы [CWebService] требуется -> [расширение PHP SOAP](http://php.net/manual/en/ref.soap.php). Убедитесь, что -> оно включено, прежде, чем пробовать примеры, описанные далее. - -Создание провайдера -------------------- - -Как уже было описано, провайдер — это класс, реализующий методы, которые могут -быть вызваны удалённо. Для того, чтобы определить, какие методы могут быть -вызваны удалённо и какое значение возвращать, Yii использует -[специальные комментарии](http://java.sun.com/j2se/javadoc/writingdoccomments/) и -[reflection](http://php.net/manual/en/book.reflection.php). - -Попробуем реализовать простой сервис, отдающий информацию о котировках акций -определённой компании. Для этого нам потребуется реализовать провайдер, как показано -ниже. Стоит отметить, что наследуем класс провайдера `StockController` -от [CController]. Наследование не является обязательным. - -~~~ -[php] -class StockController extends CController -{ - /** - * @param string индекс предприятия - * @return float цена - * @soap - */ - public function getPrice($symbol) - { - $prices=array('IBM'=>100, 'GOOGLE'=>350); - return isset($prices[$symbol])?$prices[$symbol]:0; - //…возвращаем цену для компании с индексом $symbol - } -} -~~~ - -Выше мы описали, что метод `getPrice` является частью API веб-сервиса, пометив его -в комментарии тэгом `@soap`. Там же мы описали типы параметров и возвращаемого -значения. Дополнительные методы API могут быть описаны точно таким же образом. - -Реализация действия веб-сервиса -------------------------------- - -После создания провайдера необходимо сделать его доступным для клиентов. -Для этого необходимо описать действие контроллера [CWebServiceAction]. -В нашем примере мы используем `StockController`: - -~~~ -[php] -class StockController extends CController -{ - public function actions() - { - return array( - 'quote'=>array( - 'class'=>'CWebServiceAction', - ), - ); - } - - /** - * @param string индекс предприятия - * @return float цена - * @soap - */ - public function getPrice($symbol) - { - //…возвращаем цену для компании с индексом $symbol - } -} -~~~ - -Это всё, что требуется для создания веб-сервиса. Теперь при обращении к URL -`http://hostname/path/to/index.php?r=stock/quote`, мы получим объёмистый -XML, на самом деле являющийся WSDL описанного нами веб-сервиса. - -> Tip|Подсказка: По умолчанию, при использовании [CWebServiceAction] -подразумевается, что текущий контроллер является провайдером. Именно поэтому мы -определили метод `getPrice` в классе `StockController`. - -Использование веб-сервиса -------------------------- - -Для того, чтобы наш пример был полным, создадим клиент, использующий веб-сервис, -который мы только что создали. В примере клиент будет написан на PHP, но для его -реализации можно использовать и другие языки, такие как `Java`, `C#`, `Flex` и т.д. - -~~~ -[php] -$client=new SoapClient('http://hostname/path/to/index.php?r=stock/quote'); -echo $client->getPrice('GOOGLE'); -~~~ - -Запустив данный скрипт через браузер или в консоли, вы должны получить `350`, -что соответствует цене акций `GOOGLE`. - -Типы данных ------------ - -При описании методов и свойств класса, которые должны быть доступны через -веб-сервис, нам необходимо определить типы параметров и возвращаемых значений. -Для этого могут быть использованы следующие типы: - - - str/string: соответствует `xsd:string`; - - int/integer: соответствует `xsd:int`; - - float/double: соответствует `xsd:float`; - - bool/boolean: соответствует `xsd:boolean`; - - date: соответствует `xsd:date`; - - time: соответствует `xsd:time`; - - datetime: соответствует `xsd:dateTime`; - - array: соответствует `xsd:string`; - - object: соответствует `xsd:struct`; - - mixed: соответствует `xsd:anyType`. - -Если тип не является одним из приведённых выше, он воспринимается как -составной тип, состоящий из свойств. Этот тип соответствует классу, а его -свойства — public-переменным класса, отмеченных в комментариях `@soap`. - -Также можно использовать массивы. Для этого необходимо дописать `[]` в конец -примитивного или составного типа. Таким образом мы получим массив с элементами -заданного типа. - -Ниже приведён пример определения метода API `getPosts`, возвращающего массив -объектов класса `Post`. - -~~~ -[php] -class PostController extends CController -{ - /** - * @return Post[] список записей - * @soap - */ - public function getPosts() - { - return Post::model()->findAll(); - } -} - -class Post extends CActiveRecord -{ - /** - * @var integer ID записи - * @soap - */ - public $id; - /** - * @var string заголовок записи - * @soap - */ - public $title; - - public static function model($className=__CLASS__) - { - return parent::model($className); - } -} -~~~ - -Сопоставление классов ---------------------- - -Для получения от клиента параметров составного типа, в приложении должны быть -заданы соответствия типов WSDL классам PHP. Для этого необходимо настроить -свойство [classMap|CWebServiceAction::classMap] класса [CWebServiceAction]. - -~~~ -[php] -class PostController extends CController -{ - public function actions() - { - return array( - 'service'=>array( - 'class'=>'CWebServiceAction', - 'classMap'=>array( - 'Post'=>'Post', // или просто 'Post' - ), - ), - ); - } - … -} -~~~ - -Перехват удалённого вызова метода ---------------------------------- - -Если реализован интерфейс [IWebServiceProvider], провайдер может перехватывать -удалённые вызовы методов. Используя [IWebServiceProvider::beforeWebMethod] можно получить -текущий экземпляр [CWebService]. Через [CWebService::methodName] — название -вызываемого метода. Если метод по каким либо причинам (например, отсутствие прав +Веб-сервисы +=========== + +[Веб-сервис](http://ru.wikipedia.org/wiki/Веб_сервис) — программная система, +разработанная для обеспечения взаимодействия между несколькими компьютерами через +сеть. В веб-приложении это обычно набор API, который можно использовать через +интернет для выполнения действий на удалённом сервере, обслуживающем веб-сервис. +К примеру, клиент, основанный на [Flex](http://www.adobe.com/products/flex/), может +вызывать функции, реализованные на сервере в PHP-приложении. В качестве базового +уровня протокола используется [SOAP](http://en.wikipedia.org/wiki/SOAP). + +Для того, чтобы упростить задачу создания веб-сервиса, в Yii включены +[CWebService] и [CWebServiceAction]. API сгруппированы по классам, которые называются +*провайдерами*. Для каждого класса Yii генерирует [WSDL](http://www.w3.org/TR/wsdl), +описывающий функционал предоставляемого API и правила его использования клиентом. +При обработке вызова клиента, Yii создаёт соответствующий ему экземпляр провайдера, +вызывает метод API и отвечает на запрос. + +> Note|Примечание: Для работы [CWebService] требуется +> [расширение PHP SOAP](http://php.net/manual/en/ref.soap.php). Убедитесь, что +> оно включено, прежде, чем пробовать примеры, описанные далее. + +Создание провайдера +------------------- + +Как уже было описано, провайдер — это класс, реализующий методы, которые могут +быть вызваны удалённо. Для того, чтобы определить, какие методы могут быть +вызваны удалённо и какое значение возвращать, Yii использует +[специальные комментарии](http://java.sun.com/j2se/javadoc/writingdoccomments/) и +[reflection](http://php.net/manual/en/book.reflection.php). + +Попробуем реализовать простой сервис, отдающий информацию о котировках акций +определённой компании. Для этого нам потребуется реализовать провайдер, как показано +ниже. Стоит отметить, что наследуем класс провайдера `StockController` +от [CController]. Наследование не является обязательным. + +~~~ +[php] +class StockController extends CController +{ + /** + * @param string индекс предприятия + * @return float цена + * @soap + */ + public function getPrice($symbol) + { + $prices=array('IBM'=>100, 'GOOGLE'=>350); + return isset($prices[$symbol])?$prices[$symbol]:0; + //…возвращаем цену для компании с индексом $symbol + } +} +~~~ + +Выше мы описали, что метод `getPrice` является частью API веб-сервиса, пометив его +в комментарии тэгом `@soap`. Там же мы описали типы параметров и возвращаемого +значения. Дополнительные методы API могут быть описаны точно таким же образом. + +Реализация действия веб-сервиса +------------------------------- + +После создания провайдера необходимо сделать его доступным для клиентов. +Для этого необходимо описать действие контроллера [CWebServiceAction]. +В нашем примере мы используем `StockController`: + +~~~ +[php] +class StockController extends CController +{ + public function actions() + { + return array( + 'quote'=>array( + 'class'=>'CWebServiceAction', + ), + ); + } + + /** + * @param string индекс предприятия + * @return float цена + * @soap + */ + public function getPrice($symbol) + { + //…возвращаем цену для компании с индексом $symbol + } +} +~~~ + +Это всё, что требуется для создания веб-сервиса. Теперь при обращении к URL +`http://hostname/path/to/index.php?r=stock/quote`, мы получим объёмистый +XML, на самом деле являющийся WSDL описанного нами веб-сервиса. + +> Tip|Подсказка: По умолчанию, при использовании [CWebServiceAction] +подразумевается, что текущий контроллер является провайдером. Именно поэтому мы +определили метод `getPrice` в классе `StockController`. + +Использование веб-сервиса +------------------------- + +Для того, чтобы наш пример был полным, создадим клиент, использующий веб-сервис, +который мы только что создали. В примере клиент будет написан на PHP, но для его +реализации можно использовать и другие языки, такие как `Java`, `C#`, `Flex` и т.д. + +~~~ +[php] +$client=new SoapClient('http://hostname/path/to/index.php?r=stock/quote'); +echo $client->getPrice('GOOGLE'); +~~~ + +Запустив данный скрипт через браузер или в консоли, вы должны получить `350`, +что соответствует цене акций `GOOGLE`. + +Типы данных +----------- + +При описании методов и свойств класса, которые должны быть доступны через +веб-сервис, нам необходимо определить типы параметров и возвращаемых значений. +Для этого могут быть использованы следующие типы: + + - str/string: соответствует `xsd:string`; + - int/integer: соответствует `xsd:int`; + - float/double: соответствует `xsd:float`; + - bool/boolean: соответствует `xsd:boolean`; + - date: соответствует `xsd:date`; + - time: соответствует `xsd:time`; + - datetime: соответствует `xsd:dateTime`; + - array: соответствует `xsd:string`; + - object: соответствует `xsd:struct`; + - mixed: соответствует `xsd:anyType`. + +Если тип не является одним из приведённых выше, он воспринимается как +составной тип, состоящий из свойств. Этот тип соответствует классу, а его +свойства — public-переменным класса, отмеченных в комментариях `@soap`. + +Также можно использовать массивы. Для этого необходимо дописать `[]` в конец +примитивного или составного типа. Таким образом мы получим массив с элементами +заданного типа. + +Ниже приведён пример определения метода API `getPosts`, возвращающего массив +объектов класса `Post`. + +~~~ +[php] +class PostController extends CController +{ + /** + * @return Post[] список записей + * @soap + */ + public function getPosts() + { + return Post::model()->findAll(); + } +} + +class Post extends CActiveRecord +{ + /** + * @var integer ID записи + * @soap + */ + public $id; + /** + * @var string заголовок записи + * @soap + */ + public $title; + + public static function model($className=__CLASS__) + { + return parent::model($className); + } +} +~~~ + +Сопоставление классов +--------------------- + +Для получения от клиента параметров составного типа, в приложении должны быть +заданы соответствия типов WSDL классам PHP. Для этого необходимо настроить +свойство [classMap|CWebServiceAction::classMap] класса [CWebServiceAction]. + +~~~ +[php] +class PostController extends CController +{ + public function actions() + { + return array( + 'service'=>array( + 'class'=>'CWebServiceAction', + 'classMap'=>array( + 'Post'=>'Post', // или просто 'Post' + ), + ), + ); + } + … +} +~~~ + +Перехват удалённого вызова метода +--------------------------------- + +Если реализован интерфейс [IWebServiceProvider], провайдер может перехватывать +удалённые вызовы методов. Используя [IWebServiceProvider::beforeWebMethod] можно получить +текущий экземпляр [CWebService]. Через [CWebService::methodName] — название +вызываемого метода. Если метод по каким либо причинам (например, отсутствие прав на его выполнение) не должен быть вызван, необходимо вернуть false. \ No newline at end of file diff --git a/docs/guide/sv/quickstart.apache-nginx-config.txt b/docs/guide/sv/quickstart.apache-nginx-config.txt index 15f00129b..e41f5ffd0 100644 --- a/docs/guide/sv/quickstart.apache-nginx-config.txt +++ b/docs/guide/sv/quickstart.apache-nginx-config.txt @@ -1,84 +1,84 @@ -Apache- och Nginx-konfigurationer -================================= - -Apache ------- - -Yii är klar att arbeta med en standardkonfiguration för Apache webbserver. -Filerna `.htaccess` i kataloger som innehåller Yii Framework och applikationer -förhindrar tillgång till de skyddade resurserna. Startskriptet (vanligen `index.php`) -kan gömmas i URL:er genom tillägg av `mod_rewrite`-instruktioner i `.htaccess`-filen -placerad i dokumentrotkatalogen, alternativt läggas till i konfiguration för virtuell host: - -~~~ -RewriteEngine on - -# om en katalog eller fil existerar, använd den -RewriteCond %{REQUEST_FILENAME} !-f -RewriteCond %{REQUEST_FILENAME} !-d -# i annat fall omdirigera till index.php -RewriteRule . index.php -~~~ - - -Nginx ------ - -Yii kan användas med [Nginx](http://wiki.nginx.org/) samt PHP med [FPM SAPI](http://php.net/install.fpm). -Här följer ett exempel på host-konfiguration. Det definierar startskript och ser till att Yii fångar upp alla -request till ej existerande filer, vilket tillåter användning av estetiskt tilltalande URL:er. - -~~~ -server { - set $host_path "/www/mysite"; - access_log /www/mysite/log/access.log main; - - server_name mysite; - root $host_path/htdocs; - set $yii_bootstrap "index.php"; - - charset utf-8; - - location / { - index index.html $yii_bootstrap; - try_files $uri $uri/ $yii_bootstrap?$args; - } - - location ~ ^/(protected|framework|themes/\w+/views) { - deny all; - } - - # undvik bearbetning av anrop till ej existerande statiska filer, av Yii - location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { - try_files $uri =404; - } - - # dirigera PHP-skript till FastCGI-servern på 127.0.0.1:9000 - # - location ~ \.php { - fastcgi_split_path_info ^(.+\.php)(.*)$; - - #let yii catch the calls to unexising PHP files - set $fsn /$yii_bootstrap; - if (-f $document_root$fastcgi_script_name){ - set $fsn $fastcgi_script_name; - } - - fastcgi_pass 127.0.0.1:9000; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fsn; - - #PATH_INFO and PATH_TRANSLATED can be omitted, but RFC 3875 specifies them for CGI - fastcgi_param PATH_INFO $fastcgi_path_info; - fastcgi_param PATH_TRANSLATED $document_root$fsn; - } - - location ~ /\.ht { - deny all; - } -} -~~~ -Med denna konfiguration kan man sätta cgi.fix_pathinfo=0 i php.ini och därmed undvika många onödiga -stat() systemanrop. - +Apache- och Nginx-konfigurationer +================================= + +Apache +------ + +Yii är klar att arbeta med en standardkonfiguration för Apache webbserver. +Filerna `.htaccess` i kataloger som innehåller Yii Framework och applikationer +förhindrar tillgång till de skyddade resurserna. Startskriptet (vanligen `index.php`) +kan gömmas i URL:er genom tillägg av `mod_rewrite`-instruktioner i `.htaccess`-filen +placerad i dokumentrotkatalogen, alternativt läggas till i konfiguration för virtuell host: + +~~~ +RewriteEngine on + +# om en katalog eller fil existerar, använd den +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +# i annat fall omdirigera till index.php +RewriteRule . index.php +~~~ + + +Nginx +----- + +Yii kan användas med [Nginx](http://wiki.nginx.org/) samt PHP med [FPM SAPI](http://php.net/install.fpm). +Här följer ett exempel på host-konfiguration. Det definierar startskript och ser till att Yii fångar upp alla +request till ej existerande filer, vilket tillåter användning av estetiskt tilltalande URL:er. + +~~~ +server { + set $host_path "/www/mysite"; + access_log /www/mysite/log/access.log main; + + server_name mysite; + root $host_path/htdocs; + set $yii_bootstrap "index.php"; + + charset utf-8; + + location / { + index index.html $yii_bootstrap; + try_files $uri $uri/ $yii_bootstrap?$args; + } + + location ~ ^/(protected|framework|themes/\w+/views) { + deny all; + } + + # undvik bearbetning av anrop till ej existerande statiska filer, av Yii + location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { + try_files $uri =404; + } + + # dirigera PHP-skript till FastCGI-servern på 127.0.0.1:9000 + # + location ~ \.php { + fastcgi_split_path_info ^(.+\.php)(.*)$; + + #let yii catch the calls to unexising PHP files + set $fsn /$yii_bootstrap; + if (-f $document_root$fastcgi_script_name){ + set $fsn $fastcgi_script_name; + } + + fastcgi_pass 127.0.0.1:9000; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fsn; + + #PATH_INFO and PATH_TRANSLATED can be omitted, but RFC 3875 specifies them for CGI + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param PATH_TRANSLATED $document_root$fsn; + } + + location ~ /\.ht { + deny all; + } +} +~~~ +Med denna konfiguration kan man sätta cgi.fix_pathinfo=0 i php.ini och därmed undvika många onödiga +stat() systemanrop. +
$Id: quickstart.apache-nginx-config.txt 3512 2011-12-27 16:50:03Z haertl.mike $
\ No newline at end of file diff --git a/docs/guide/uk/basics.application.txt b/docs/guide/uk/basics.application.txt index ab223248b..346cef7a1 100644 --- a/docs/guide/uk/basics.application.txt +++ b/docs/guide/uk/basics.application.txt @@ -1,208 +1,208 @@ -Додаток -======= - -Обʼєкт додатку (application) інкапсулює контекст виконання запиту. Основне завдання додатка — -зібрати основну інформацію про запит та передати його відповідному контролеру для подальшої обробки. -Також додаток є централізованим сховищем зберігання конфігурації додатку. -Саме тому додаток також називають `фронт-контролером`. - -Обʼєкт додатка створюється [вхідним скриптом](/doc/guide/basics.entry) як `одинак (singleton)`. -Екземпляр додатка доступний з будь-якої його точки за допомогою [Yii::app()|yiibase::app]. - - -Конфігурація додатку --------------------- -За замовчуванням, обʼєкт додатку — це екземпляр класу [CWebApplication], -який може бути настроєний з використанням конфігураційного файлу (або масиву). -Необхідні значення властивостей встановлюються у момент створення екземпляра додатку. -Альтернативний шлях налаштування додатку — розширення класу [CWebApplication]. - -Конфігурація — це масив пар ключ-значення, де кожний ключ являє собою імʼя властивості -екземпляра додатку, а значення - початкове значення відповідної властивості. -Наприклад, наступна конфігурація встановлює значення властивостей додатка -[name|CApplication::name] та [defaultController|CWebApplication::defaultController]: - -~~~ -[php] -array( - 'name'=>'Yii Framework', - 'defaultController'=>'site', -) -~~~ - -Варто відзначити, що додаток, як і більшість класів Yii, [є компонентом](/doc/guide/basics.component). -Це означає що: - -- Ви не можете присвоювати значення не оголошеним в класі властивостям. -- Додаток підтримує властивості, оголошені через геттер і сеттери, тобто можна конфігурувати властивість, задану -[setImport|CModule::setImport] наступним чином: - -~~~ -[php] -array( - 'import'=>array( - 'application.components.*', - ), -) -~~~ - -Зазвичай конфігурація зберігається в окремому PHP-скрипті -(наприклад, `protected/config/main.php`). Скрипт повертає конфігураційний масив так: - -~~~ -[php] -return array(…); -~~~ - -Щоб скористатися конфігурацією, необхідно передати імʼя конфігураційного файлу як параметр -конструктору додатка або класу [Yii::createWebApplication()], як показано нижче. -Зазвичай це робиться в [вхідному скрипті](/doc/guide/basics.entry): - -~~~ -[php] -$app=Yii::createWebApplication($configFile); -~~~ - -> Tip|Підказка: Якщо конфігурація дуже громіздка, можна розділити її на кілька файлів, -кожний з яких повертає лише частину конфігураційного масиву. Потім, в основному конфігураційному -файлі, необхідно підключити ці файли, використовуючи `include()`, і злити масиви-частини в єдиний -конфігураційний масив. - - -Базова директорія додатка -------------------------- -Базовою директорією додатку називається коренева директорія, що містить усі основні, з точки -зору безпеки, PHP-скрипти й дані. За замовчуванням, це піддиректорія з назвою `protected` -у директорії, що містить вхідний скрипт. Змінити місце розташування можна змінивши властивість -[basepath|CWebApplication::basePath] в [конфігурації додатку](#application-configuration). - -Вміст базової директорії має бути закритим для веб-доступу. -При використанні веб-сервера [Apache HTTP server](http://httpd.apache.org/), -це можна зробити, додавши в базову директорію файл `.htaccess` наступного змісту: - -~~~ -deny from all -~~~ - - -Компоненти додатку ------------------- -Функціональність обʼєкта додатку може бути легко модифікована й розширена завдяки компонентній архітектурі. -Додаток управляє набором компонентів, кожний з яких має специфічні можливості. -Наприклад, додаток робить попередню обробку запиту користувача, -використовуючи компоненти [CUrlManager] і [CHttpRequest]. - -Змінюючи значення властивості [components|CApplication::components], -можна настроїти класи й значення властивостей(/doc/guide/basics.component) -будь-якого компоненту, що використовує додаток. Наприклад, можна зконфігурувати компонент [CMemCache] так, щоб -він використовував декілька memcache-серверів для кешування: - -~~~ -[php] -array( - … - 'components'=>array( - … - 'cache'=>array( - 'class'=>'CMemCache', - 'servers'=>array( - array('host'=>'server1', 'port'=>11211, 'weight'=>60), - array('host'=>'server2', 'port'=>11211, 'weight'=>40), - ), - ), - ), -) -~~~ - -У даному прикладі ми додали елемент `cache` до масиву `components`. -Елемент `cache` вказує, що класом компоненту є `CMemCache`, -а також встановлює його властивість [servers|CMemcache::servers]. - -Для доступу до компонента додатку використовуйте `Yii::app()->ComponentId`, де -`ComponentId` — це ідентифікатор компоненту (наприклад, `Yii::app()->cache`). - -Компонент може бути деактивований шляхом установки параметра `enabled` в його конфігурації рівним `false`. -При звертанні до деактивованого компоненту буде повернуто null. - -> Tip|Підказка: За замовчуванням, компоненти додатка створюються за вимогою. -Це означає, що екземпляр компоненту може бути не створений взагалі, у випадку, -якщо це не потрібно при обробці користувальницького запиту. Як результат — загальна -продуктивність додатку не постраждає, навіть якщо конфігурація -додатка включає безліч компонентів. - -При необхідності обовʼязкового створення екземплярів компонентів (наприклад, [CLogRouter]) -незалежно від того, використовуються вони чи ні, вкажіть їх ідентифікатори в якості -конфігураційної властивості [preload|CApplication::preload]. - -Ключові компоненти додатку --------------------------- -Yii визначає набір компонентів ядра, що надають можливості, -необхідні для більшості веб-додатків. Наприклад, компонент [request|CWebApplication::request] -використовується для збору інформації про запит користувача та надає різноманітну інформацію, -таку як URL, cookies. Задаючи властивості ключових компонентів, -можна змінювати стандартну поведінку Yii практично як завгодно. - -Далі перераховані ключові компоненти, визначені класом [CWebApplication]: - - - [assetManager|CWebApplication::assetmanager]: [CAssetManager] — керує публікацією файлів ресурсів (asset files); - - - [authManager|CWebApplication::authmanager]: [CAuthManager] — контролює доступ на основі ролей (RBAC); - - - [cache|CApplication::cache]: [CCache] — надає можливості кешування даних; врахуйте, що ви -повинні вказати використовуваний клас (наприклад, [CMemCache], [CDbCache]), інакше при звертанні до компонента -буде повернуто null; - - - [clientScript|CWebApplication::clientscript]: [CClientScript] —керує клієнтськими скриптами (javascripts і CSS); - - - [coreMessages|CApplication::coreMessages]: [CPhpMessageSource] — надає переклади системних повідомлень Yii-Фреймворка; - - - [db|CApplication::db]: [CDbConnection] — обслуговує зʼєднання з базою даних; зверніть увагу, що -для використання компонента необхідно встановити властивість [connectionString|CDbConnection::connectionString]; - - - [errorHandler|CApplication::errorHandler]: [Cerrorhandler] — оброблює не піймані помилки й виключення PHP; - - - [format|CApplication::format]: [CFormatter] - форматує дані для їх подальшого відображення; - - - [messages|CApplication::messages]: [CPhpMessageSource] — надає переклади повідомлень, використаних в Yii-додатку; - - - [request|CWebApplication::request]: [CHttpRequest] — надає інформацію, що відноситься до запиту користувача; - - - [securityManager|CApplication::securityManager]: [CSecurityManager] — забезпечує функціональність, - повʼязану з безпекою (наприклад, хешування, шифрування); - - - [session|CWebApplication::session]: [CHttpSession] — забезпечує функціональність, повʼязану із сесіями; - - - [statePersister|CApplication::statePersister]: [CStatePersister] — надає метод для збереження -глобального стану; - - - [urlManager|CWebApplication::urlManager]: [CUrlManager] — надає функції парсинга й формування URL; - - - [user|CWebApplication::user]: [CWebUser] — надає ідентифікаційну інформацію поточного користувача; - - - [themeManager|CWebApplication::themeManager]: [CThemeManager] — управляє темами оформлення. - - -Життєвий цикл додатку ---------------------- -Життєвий цикл додатку при обробці користувальницького запиту: - - 0. Попередня ініціалізація додатку через [CApplication::preinit()]. - - 1. Ініціалізація обробника помилок. - - 2. Реєстрація компонентів ядра. - - 3. Завантаження конфігурації додатку. - - 4. Ініціалізація додатку [CApplication::init()]: - - реєстрація поведінок додатку; - - завантаження статичних компонентів додатку; - - 5. Виклик події [onBeginRequest|CApplication::onBeginRequest]. - - 6. Обробка запиту: - - збір інформації про запит; - - створення контролера; - - запуск контролера; - +Додаток +======= + +Обʼєкт додатку (application) інкапсулює контекст виконання запиту. Основне завдання додатка — +зібрати основну інформацію про запит та передати його відповідному контролеру для подальшої обробки. +Також додаток є централізованим сховищем зберігання конфігурації додатку. +Саме тому додаток також називають `фронт-контролером`. + +Обʼєкт додатка створюється [вхідним скриптом](/doc/guide/basics.entry) як `одинак (singleton)`. +Екземпляр додатка доступний з будь-якої його точки за допомогою [Yii::app()|yiibase::app]. + + +Конфігурація додатку +-------------------- +За замовчуванням, обʼєкт додатку — це екземпляр класу [CWebApplication], +який може бути настроєний з використанням конфігураційного файлу (або масиву). +Необхідні значення властивостей встановлюються у момент створення екземпляра додатку. +Альтернативний шлях налаштування додатку — розширення класу [CWebApplication]. + +Конфігурація — це масив пар ключ-значення, де кожний ключ являє собою імʼя властивості +екземпляра додатку, а значення - початкове значення відповідної властивості. +Наприклад, наступна конфігурація встановлює значення властивостей додатка +[name|CApplication::name] та [defaultController|CWebApplication::defaultController]: + +~~~ +[php] +array( + 'name'=>'Yii Framework', + 'defaultController'=>'site', +) +~~~ + +Варто відзначити, що додаток, як і більшість класів Yii, [є компонентом](/doc/guide/basics.component). +Це означає що: + +- Ви не можете присвоювати значення не оголошеним в класі властивостям. +- Додаток підтримує властивості, оголошені через геттер і сеттери, тобто можна конфігурувати властивість, задану +[setImport|CModule::setImport] наступним чином: + +~~~ +[php] +array( + 'import'=>array( + 'application.components.*', + ), +) +~~~ + +Зазвичай конфігурація зберігається в окремому PHP-скрипті +(наприклад, `protected/config/main.php`). Скрипт повертає конфігураційний масив так: + +~~~ +[php] +return array(…); +~~~ + +Щоб скористатися конфігурацією, необхідно передати імʼя конфігураційного файлу як параметр +конструктору додатка або класу [Yii::createWebApplication()], як показано нижче. +Зазвичай це робиться в [вхідному скрипті](/doc/guide/basics.entry): + +~~~ +[php] +$app=Yii::createWebApplication($configFile); +~~~ + +> Tip|Підказка: Якщо конфігурація дуже громіздка, можна розділити її на кілька файлів, +кожний з яких повертає лише частину конфігураційного масиву. Потім, в основному конфігураційному +файлі, необхідно підключити ці файли, використовуючи `include()`, і злити масиви-частини в єдиний +конфігураційний масив. + + +Базова директорія додатка +------------------------- +Базовою директорією додатку називається коренева директорія, що містить усі основні, з точки +зору безпеки, PHP-скрипти й дані. За замовчуванням, це піддиректорія з назвою `protected` +у директорії, що містить вхідний скрипт. Змінити місце розташування можна змінивши властивість +[basepath|CWebApplication::basePath] в [конфігурації додатку](#application-configuration). + +Вміст базової директорії має бути закритим для веб-доступу. +При використанні веб-сервера [Apache HTTP server](http://httpd.apache.org/), +це можна зробити, додавши в базову директорію файл `.htaccess` наступного змісту: + +~~~ +deny from all +~~~ + + +Компоненти додатку +------------------ +Функціональність обʼєкта додатку може бути легко модифікована й розширена завдяки компонентній архітектурі. +Додаток управляє набором компонентів, кожний з яких має специфічні можливості. +Наприклад, додаток робить попередню обробку запиту користувача, +використовуючи компоненти [CUrlManager] і [CHttpRequest]. + +Змінюючи значення властивості [components|CApplication::components], +можна настроїти класи й значення властивостей(/doc/guide/basics.component) +будь-якого компоненту, що використовує додаток. Наприклад, можна зконфігурувати компонент [CMemCache] так, щоб +він використовував декілька memcache-серверів для кешування: + +~~~ +[php] +array( + … + 'components'=>array( + … + 'cache'=>array( + 'class'=>'CMemCache', + 'servers'=>array( + array('host'=>'server1', 'port'=>11211, 'weight'=>60), + array('host'=>'server2', 'port'=>11211, 'weight'=>40), + ), + ), + ), +) +~~~ + +У даному прикладі ми додали елемент `cache` до масиву `components`. +Елемент `cache` вказує, що класом компоненту є `CMemCache`, +а також встановлює його властивість [servers|CMemcache::servers]. + +Для доступу до компонента додатку використовуйте `Yii::app()->ComponentId`, де +`ComponentId` — це ідентифікатор компоненту (наприклад, `Yii::app()->cache`). + +Компонент може бути деактивований шляхом установки параметра `enabled` в його конфігурації рівним `false`. +При звертанні до деактивованого компоненту буде повернуто null. + +> Tip|Підказка: За замовчуванням, компоненти додатка створюються за вимогою. +Це означає, що екземпляр компоненту може бути не створений взагалі, у випадку, +якщо це не потрібно при обробці користувальницького запиту. Як результат — загальна +продуктивність додатку не постраждає, навіть якщо конфігурація +додатка включає безліч компонентів. + +При необхідності обовʼязкового створення екземплярів компонентів (наприклад, [CLogRouter]) +незалежно від того, використовуються вони чи ні, вкажіть їх ідентифікатори в якості +конфігураційної властивості [preload|CApplication::preload]. + +Ключові компоненти додатку +-------------------------- +Yii визначає набір компонентів ядра, що надають можливості, +необхідні для більшості веб-додатків. Наприклад, компонент [request|CWebApplication::request] +використовується для збору інформації про запит користувача та надає різноманітну інформацію, +таку як URL, cookies. Задаючи властивості ключових компонентів, +можна змінювати стандартну поведінку Yii практично як завгодно. + +Далі перераховані ключові компоненти, визначені класом [CWebApplication]: + + - [assetManager|CWebApplication::assetmanager]: [CAssetManager] — керує публікацією файлів ресурсів (asset files); + + - [authManager|CWebApplication::authmanager]: [CAuthManager] — контролює доступ на основі ролей (RBAC); + + - [cache|CApplication::cache]: [CCache] — надає можливості кешування даних; врахуйте, що ви +повинні вказати використовуваний клас (наприклад, [CMemCache], [CDbCache]), інакше при звертанні до компонента +буде повернуто null; + + - [clientScript|CWebApplication::clientscript]: [CClientScript] —керує клієнтськими скриптами (javascripts і CSS); + + - [coreMessages|CApplication::coreMessages]: [CPhpMessageSource] — надає переклади системних повідомлень Yii-Фреймворка; + + - [db|CApplication::db]: [CDbConnection] — обслуговує зʼєднання з базою даних; зверніть увагу, що +для використання компонента необхідно встановити властивість [connectionString|CDbConnection::connectionString]; + + - [errorHandler|CApplication::errorHandler]: [Cerrorhandler] — оброблює не піймані помилки й виключення PHP; + + - [format|CApplication::format]: [CFormatter] - форматує дані для їх подальшого відображення; + + - [messages|CApplication::messages]: [CPhpMessageSource] — надає переклади повідомлень, використаних в Yii-додатку; + + - [request|CWebApplication::request]: [CHttpRequest] — надає інформацію, що відноситься до запиту користувача; + + - [securityManager|CApplication::securityManager]: [CSecurityManager] — забезпечує функціональність, + повʼязану з безпекою (наприклад, хешування, шифрування); + + - [session|CWebApplication::session]: [CHttpSession] — забезпечує функціональність, повʼязану із сесіями; + + - [statePersister|CApplication::statePersister]: [CStatePersister] — надає метод для збереження +глобального стану; + + - [urlManager|CWebApplication::urlManager]: [CUrlManager] — надає функції парсинга й формування URL; + + - [user|CWebApplication::user]: [CWebUser] — надає ідентифікаційну інформацію поточного користувача; + + - [themeManager|CWebApplication::themeManager]: [CThemeManager] — управляє темами оформлення. + + +Життєвий цикл додатку +--------------------- +Життєвий цикл додатку при обробці користувальницького запиту: + + 0. Попередня ініціалізація додатку через [CApplication::preinit()]. + + 1. Ініціалізація обробника помилок. + + 2. Реєстрація компонентів ядра. + + 3. Завантаження конфігурації додатку. + + 4. Ініціалізація додатку [CApplication::init()]: + - реєстрація поведінок додатку; + - завантаження статичних компонентів додатку; + + 5. Виклик події [onBeginRequest|CApplication::onBeginRequest]. + + 6. Обробка запиту: + - збір інформації про запит; + - створення контролера; + - запуск контролера; + 7. Виклик події [onEndRequest|CApplication::onEndRequest]. \ No newline at end of file diff --git a/docs/guide/uk/basics.component.txt b/docs/guide/uk/basics.component.txt index c8fdc8887..8e9922cc8 100644 --- a/docs/guide/uk/basics.component.txt +++ b/docs/guide/uk/basics.component.txt @@ -1,225 +1,225 @@ -Компонент -========= -Yii-додатки складаються з компонентів-обʼєктів, створених згідно специфікацій. -Компонент (component) — це екземпляр класу [CComponent] або похідного від нього. -Використання компоненту в основному включає доступ до його властивостей, а також виклик і обробку його подій. -Базовий клас [CComponent] встановлює те, як визначаються властивості та події. - -Визначення і використання властивостей компонента -------------------------------------------------- - -Властивість компонента схожа на публічну змінну-член класу (public member variable). -Ми можемо читати або встановлювати його значення. Наприклад: - -~~~ -[php] -$width=$component->textWidth; // отримуємо значення властивості textWidth -$component->enableCaching=true; // встановлюємо значення властивості enableCaching -~~~ - -Існують два різні способи визначення властивостей компонента. -Перший шлях це просто оголосити публічну змінну у класі компонента, -наприклад: - -~~~ -[php] -class Document extends CComponent -{ - public $textWidth; -} -~~~ - -Інший спосіб полягає у використанні методів отримання (getter) та встановлення (setter). -Цей спосіб більш гнучкий, оскільки додатково до звичайних властивостей -ви можете оголосити властивість тільки для читання або тільки для запису. - -~~~ -[php] -class Document extends CComponent -{ - private $_textWidth; - protected $_completed=false; - - public function getTextWidth() - { - return $this->_textWidth; - } - - public function setTextWidth($value) - { - $this->_textWidth=$value; - } - - public function getTextHeight() - { - // розраховує та повертає висоту тексту - } - - public function setCompleted($value) - { - $this->_completed=$value; - } -} -~~~ - -Компонент вище, можна використовувати наступним чином: - -~~~ -[php] -$document=new Document(); - -// ми можемо писати та читати textWidth -$document->textWidth=100; -echo $document->textWidth; - -// ми можемо тільки читати textHeight -echo $document->textHeight; - -// ми можемо тільки писати completed -$document->completed=true; -~~~ - -При спробі прочитати властивість компонента і властивість не визначена, -як відкритий член класу (public class member), -Yii намагатиметься використовувати метод отримання (getter), наприклад -для `textWidth` метод отримання буде `getTextWidth`. -Те ж саме відбувається при спробі запису властивості, яка не визначена як відкритий член класу. - -Якщо існує метод-отримувач (getter), але відсутній метод-встановлювач (setter) — -властивість компонента стає доступною тільки для читання та генерує виключення при спробі -запису в неї. Якщо ж це навпаки, властивість доступна тільки для запису. - -Використання методів отримання та встановлення для визначення властивості має -користь у тому, що додаткові дії (такі як перевірка, виклик події) -можуть бути виконані при читанні і запису властивості. - ->Note|Примітка: Є невелика різниця у визначенні властивості через методи та через просте -оголошення змінної. У першому випадку імʼя властивості нечутливе до регістру, -у другому — чутливо. - - -Події компонента ----------------- -Події компонента — це спеціальні властивості, у якості значень яких виступають -методи (обробник подій). Прикріплення методу до події приведе до того, що метод буде викликаний -автоматично при виникненні події. Тому поведінка компонента може бути -задана зовсім відмінною від тієї, що закладається при розробці. - -Подія компоненту задається шляхом створення методу з іменем, що починаються на `on`. -Подібно іменам властивостей, заданих через методи читання й запису, імена подій -не чутливі до регістру. Наступний код задає подію `onClicked`: - -~~~ -[php] -public function onClicked($event) -{ - $this->raiseEvent('onClicked', $event); -} -~~~ - -де `$event` — це екземпляр класу [CEvent] або похідного від нього, -що представляє параметр події. До події можна підключити обробник, як показано нижче: - -~~~ -[php] -$component->onClicked=$callback; -~~~ - -де `$callback` — це коректний callback-виклик PHP (див. PHP-функцію `call_user_func`). -Це може бути або глобальна функція, або метод класу. -В останньому випадку виклику повинен передаватись масив: `array($object,'methodName')`. - -Обробник події повинен бути визначений наступним чином: - -~~~ -[php] -function methodName($event) -{ - … -} -~~~ - -де `$event` — це параметр, що описує подію (відбувається із виклику `raiseEvent()`). -Параметр `$event` — це екземпляр класу [CEvent] або його похідного. -Як мінімум, він містить інформацію про те, хто викликав подію. - -Обробник події може бути анонімною функцією, яка потребує наявності версії PHP 5.3+. -Наприклад: - -~~~ -[php] -$component->onClicked=function($event) { - … -} -~~~ - -Якщо тепер викликати метод `onClicked()`, подію `onClicked` буде викликано -(всередині `onClicked()`), і прикріплений обробник події буде запущений автоматично. - -До події може бути прикріплено кілька обробників. -При виникненні події обробники будуть викликані в тому порядку, -у якому вони були прикріплені до події. -Якщо всередині обробника необхідно запобігти виклику наступних обробників, -необхідно встановити [$event->handled|CEvent::handled] в `true`. - - -Поведінки компонента --------------------- - -Для компонента була додана підтримка шаблону проектування [домішок (mixin)](http://ru.wikipedia.org/wiki/Mixin), -що дозволяє прикріпити до нього одну або кілька поведінок. -Поведінка — це обʼєкт, чиї методи «успадковуються» компонентом, до якого прикріплені. -Під «успадкуванням» мається на увазі нарощування функціоналу, а не успадкування у класичному розумінні. -До компонента можна прикріпити кілька поведінок і, таким чином, одержати множинне спадкування. - -Поведінка класів повинна реалізовувати інтерфейс [IBehavior]. Більшість поведінок можуть бути створені шляхом розширення -базового класу [CBehavior]. У випадку, якщо поведінку необхідно прикріпити до [моделі](/doc/guide/basics.model), її можна -створити на основі класу [CModelBehavior] або класу [CActiveRecordBehavior], який реалізує додаткові, -специфічні для моделі, можливості. - -Щоб скористатися поведінкою, її необхідно прикріпити до компонента шляхом виклику методу -[attach()|IBehavior::attach]. Далі ми викликаємо метод поведінки через компонент: - -~~~ -[php] -// $name унікально ідентифікує поведінки в компоненті -$component->attachBehavior($name,$behavior); -// test() є методом $behavior -$component->test(); -~~~ - -До прикріпленої поведінки можна звертатися, як до звичайної властивості компоненту. -Наприклад, якщо поведінка з іменем `tree` прикріплена до компонента, ми можемо одержати -посилання на цей обʼєкт поведінки таким чином: - -~~~ -[php] -$behavior=$component->tree; -// еквівалентно виразу: -// $behavior=$component->asa('tree'); -~~~ - -Поведінку можна тимчасово деактивувати таким чином, щоб її методи були недоступні через компонент. -Наприклад: - -~~~ -[php] -$component->disableBehavior($name); -// вираз нижче приведе до виклику виключення -$component->test(); -$component->enableBehavior($name); -// тут все буде працювати нормально -$component->test(); -~~~ - -У випадку, коли дві поведінки, прикріплені до одного компоненту, мають методи з однаковими іменами, -перевагу буде мати метод поведінки, яка була прикріплена першою. - -Використання поведінок разом із [подіями](#component-event) дає додаткові можливості. -Поведінка, прикріплена до компонента, може привласнювати деякі свої методи подіям компоненту. -В цьому випадку, поведінка отримує можливість стежити або змінювати нормальний хід виконання компоненту. - -Властивості поведінки також можуть бути доступні із компонента, до якого вона привʼязана. -Властивості містять у собі як відкриті, так і визначені через геттери та/або сеттери поведінки. -Наприклад, поведінка має властивість із іменем `xyz` і привʼязана до компонента -`$a`. Тоді ми можемо використовувати вираз `$a->xyz` для доступу до властивості. +Компонент +========= +Yii-додатки складаються з компонентів-обʼєктів, створених згідно специфікацій. +Компонент (component) — це екземпляр класу [CComponent] або похідного від нього. +Використання компоненту в основному включає доступ до його властивостей, а також виклик і обробку його подій. +Базовий клас [CComponent] встановлює те, як визначаються властивості та події. + +Визначення і використання властивостей компонента +------------------------------------------------- + +Властивість компонента схожа на публічну змінну-член класу (public member variable). +Ми можемо читати або встановлювати його значення. Наприклад: + +~~~ +[php] +$width=$component->textWidth; // отримуємо значення властивості textWidth +$component->enableCaching=true; // встановлюємо значення властивості enableCaching +~~~ + +Існують два різні способи визначення властивостей компонента. +Перший шлях це просто оголосити публічну змінну у класі компонента, +наприклад: + +~~~ +[php] +class Document extends CComponent +{ + public $textWidth; +} +~~~ + +Інший спосіб полягає у використанні методів отримання (getter) та встановлення (setter). +Цей спосіб більш гнучкий, оскільки додатково до звичайних властивостей +ви можете оголосити властивість тільки для читання або тільки для запису. + +~~~ +[php] +class Document extends CComponent +{ + private $_textWidth; + protected $_completed=false; + + public function getTextWidth() + { + return $this->_textWidth; + } + + public function setTextWidth($value) + { + $this->_textWidth=$value; + } + + public function getTextHeight() + { + // розраховує та повертає висоту тексту + } + + public function setCompleted($value) + { + $this->_completed=$value; + } +} +~~~ + +Компонент вище, можна використовувати наступним чином: + +~~~ +[php] +$document=new Document(); + +// ми можемо писати та читати textWidth +$document->textWidth=100; +echo $document->textWidth; + +// ми можемо тільки читати textHeight +echo $document->textHeight; + +// ми можемо тільки писати completed +$document->completed=true; +~~~ + +При спробі прочитати властивість компонента і властивість не визначена, +як відкритий член класу (public class member), +Yii намагатиметься використовувати метод отримання (getter), наприклад +для `textWidth` метод отримання буде `getTextWidth`. +Те ж саме відбувається при спробі запису властивості, яка не визначена як відкритий член класу. + +Якщо існує метод-отримувач (getter), але відсутній метод-встановлювач (setter) — +властивість компонента стає доступною тільки для читання та генерує виключення при спробі +запису в неї. Якщо ж це навпаки, властивість доступна тільки для запису. + +Використання методів отримання та встановлення для визначення властивості має +користь у тому, що додаткові дії (такі як перевірка, виклик події) +можуть бути виконані при читанні і запису властивості. + +>Note|Примітка: Є невелика різниця у визначенні властивості через методи та через просте +оголошення змінної. У першому випадку імʼя властивості нечутливе до регістру, +у другому — чутливо. + + +Події компонента +---------------- +Події компонента — це спеціальні властивості, у якості значень яких виступають +методи (обробник подій). Прикріплення методу до події приведе до того, що метод буде викликаний +автоматично при виникненні події. Тому поведінка компонента може бути +задана зовсім відмінною від тієї, що закладається при розробці. + +Подія компоненту задається шляхом створення методу з іменем, що починаються на `on`. +Подібно іменам властивостей, заданих через методи читання й запису, імена подій +не чутливі до регістру. Наступний код задає подію `onClicked`: + +~~~ +[php] +public function onClicked($event) +{ + $this->raiseEvent('onClicked', $event); +} +~~~ + +де `$event` — це екземпляр класу [CEvent] або похідного від нього, +що представляє параметр події. До події можна підключити обробник, як показано нижче: + +~~~ +[php] +$component->onClicked=$callback; +~~~ + +де `$callback` — це коректний callback-виклик PHP (див. PHP-функцію `call_user_func`). +Це може бути або глобальна функція, або метод класу. +В останньому випадку виклику повинен передаватись масив: `array($object,'methodName')`. + +Обробник події повинен бути визначений наступним чином: + +~~~ +[php] +function methodName($event) +{ + … +} +~~~ + +де `$event` — це параметр, що описує подію (відбувається із виклику `raiseEvent()`). +Параметр `$event` — це екземпляр класу [CEvent] або його похідного. +Як мінімум, він містить інформацію про те, хто викликав подію. + +Обробник події може бути анонімною функцією, яка потребує наявності версії PHP 5.3+. +Наприклад: + +~~~ +[php] +$component->onClicked=function($event) { + … +} +~~~ + +Якщо тепер викликати метод `onClicked()`, подію `onClicked` буде викликано +(всередині `onClicked()`), і прикріплений обробник події буде запущений автоматично. + +До події може бути прикріплено кілька обробників. +При виникненні події обробники будуть викликані в тому порядку, +у якому вони були прикріплені до події. +Якщо всередині обробника необхідно запобігти виклику наступних обробників, +необхідно встановити [$event->handled|CEvent::handled] в `true`. + + +Поведінки компонента +-------------------- + +Для компонента була додана підтримка шаблону проектування [домішок (mixin)](http://ru.wikipedia.org/wiki/Mixin), +що дозволяє прикріпити до нього одну або кілька поведінок. +Поведінка — це обʼєкт, чиї методи «успадковуються» компонентом, до якого прикріплені. +Під «успадкуванням» мається на увазі нарощування функціоналу, а не успадкування у класичному розумінні. +До компонента можна прикріпити кілька поведінок і, таким чином, одержати множинне спадкування. + +Поведінка класів повинна реалізовувати інтерфейс [IBehavior]. Більшість поведінок можуть бути створені шляхом розширення +базового класу [CBehavior]. У випадку, якщо поведінку необхідно прикріпити до [моделі](/doc/guide/basics.model), її можна +створити на основі класу [CModelBehavior] або класу [CActiveRecordBehavior], який реалізує додаткові, +специфічні для моделі, можливості. + +Щоб скористатися поведінкою, її необхідно прикріпити до компонента шляхом виклику методу +[attach()|IBehavior::attach]. Далі ми викликаємо метод поведінки через компонент: + +~~~ +[php] +// $name унікально ідентифікує поведінки в компоненті +$component->attachBehavior($name,$behavior); +// test() є методом $behavior +$component->test(); +~~~ + +До прикріпленої поведінки можна звертатися, як до звичайної властивості компоненту. +Наприклад, якщо поведінка з іменем `tree` прикріплена до компонента, ми можемо одержати +посилання на цей обʼєкт поведінки таким чином: + +~~~ +[php] +$behavior=$component->tree; +// еквівалентно виразу: +// $behavior=$component->asa('tree'); +~~~ + +Поведінку можна тимчасово деактивувати таким чином, щоб її методи були недоступні через компонент. +Наприклад: + +~~~ +[php] +$component->disableBehavior($name); +// вираз нижче приведе до виклику виключення +$component->test(); +$component->enableBehavior($name); +// тут все буде працювати нормально +$component->test(); +~~~ + +У випадку, коли дві поведінки, прикріплені до одного компоненту, мають методи з однаковими іменами, +перевагу буде мати метод поведінки, яка була прикріплена першою. + +Використання поведінок разом із [подіями](#component-event) дає додаткові можливості. +Поведінка, прикріплена до компонента, може привласнювати деякі свої методи подіям компоненту. +В цьому випадку, поведінка отримує можливість стежити або змінювати нормальний хід виконання компоненту. + +Властивості поведінки також можуть бути доступні із компонента, до якого вона привʼязана. +Властивості містять у собі як відкриті, так і визначені через геттери та/або сеттери поведінки. +Наприклад, поведінка має властивість із іменем `xyz` і привʼязана до компонента +`$a`. Тоді ми можемо використовувати вираз `$a->xyz` для доступу до властивості. diff --git a/docs/guide/uk/basics.controller.txt b/docs/guide/uk/basics.controller.txt index f949262ac..705373697 100644 --- a/docs/guide/uk/basics.controller.txt +++ b/docs/guide/uk/basics.controller.txt @@ -1,321 +1,321 @@ -Контролер -========= -`Контролер (controller)` — це екземпляр класу [CController] або успадкованого від нього класу. -Він створюється обʼєктом додатку тоді, коли користувач робить відповідний запит. -При запуску контролер виконує відповідну дію, що зазвичай передбачає створення відповідних моделей -і рендеринг необхідних представлень. -У найпростішому випадку `дія` — це метод класу контролера, назва якого починається на `action`. - -У контролера є дія за замовчуванням, яка виконується у випадку, коли користувач не вказує дію при запиті. -За замовчуванням ця дія називається `index`. -Змінити її можна шляхом встановлення значення [CController::defaultAction]. - -Наступний код визначає контролер `site` з діями `index` (за замовчуванням) та `contact`: - -~~~ -[php] -class SiteController extends CController -{ - public function actionIndex() - { - // ... - } - - public function actionContact() - { - // ... - } -} -~~~ - - -Маршрут -------- -Контролери та дії розпізнаються по їхнім ідентифікаторам. -Ідентифікатор контролера — це запис формату `path/to/xyz`, що відповідає -файлу класу контролера `protected/controllers/path/to/XyzController.php`, де `xyz` -слід замінити реальною назвою класу (наприклад, `post` відповідає -`protected/controllers/PostController.php`). Ідентифікатор дії — це назва -методу без префікса `action`. Наприклад, якщо клас контролера містить метод -`actionEdit`, то ідентифікатор відповідної дії — `edit`. - -Користувач звертається до контролера та дії за допомогою маршруту (route). -Маршрут формується шляхом обʼєднання ідентифікаторів контролера та дії, відокремлених символом `/`. -Наприклад, маршрут `post/edit` вказує на дію `edit` контролеру `PostController` і, -за замовчуванням, URL `http://hostname/index.php?r=post/edit` приведе до виклику саме цих контролера та дії. - -> Note|Примітка: За замовчуванням маршрути чутливі до регістру. ->Це можливо змінити шляхом встановлення властивості ->[CUrlManager::caseSensitive] у конфігурації додатка рівною `false`. ->У режимі нечутливому до регістру переконайтеся, що назви директорій, ->які містять файли класів контролерів написані в нижньому регістрі, а також ->що [controller map|CWebApplication::controllerMap] та [action map|CController::actions] ->використовують ключі в нижньому регістрі. - -Додаток може містити [модулі](/doc/guide/basics.module). Маршрут до дії -контролера всередині модуля задається у форматі `moduleID/controllerID/actionID`. -Більш детальніше це описано у [розділі про модулі](/doc/guide/basics.module). - -Створення екземпляра контролера -------------------------------- -Екземпляр контролера створюється, коли [CWebApplication] обробляє вхідний запит. -Одержавши ідентифікатор контролера, додаток використовує наступні правила для -визначення класу контролера та його місцерозташування: - -- якщо встановлена властивість [CWebApplication::catchAllRequest], контролер буде створений -на основі цієї властивості, а контролер, який запитує користувач, буде проігноровано. -Як правило, це використовується для встановлення додатка в режим технічного обслуговування -та відображення статичної сторінки з відповідним повідомленням; - -- якщо ідентифікатор контролера виявлений у [CWebApplication::controllerMap], то для -створення екземпляру контролера буде використана відповідна конфігурація контролера; - -- якщо ідентифікатор контролера відповідає формату `'path/to/xyz'`, то імʼя класу -контролера визначається як `XyzController`, а відповідний клас як -`protected/controllers/path/to/XyzController.php`. -Наприклад, ідентифікатор контролера `admin/user` відповідатиме класу -контролера — `Usercontroller` та файл `protected/controllers/admin/UserController.php`. -Якщо файл класу не існує, буде викликане виключення [CHttpException] з кодом помилки 404. - -У випадку використання [модулів](/doc/guide/basics.module), процес, описаний вище, буде виглядати дещо інакше. -Зокрема, додаток перевірить, чи відповідає ідентифікатор контролеру всередині модуля. -Якщо відповідає — спочатку буде створений екземпляр модуля, потім екземпляр контролера. - - -Дія --------- -Як було згадано вище, дія — це метод, імʼя якого починається на `action`. -Ще один спосіб — створити клас дії та вказати контролеру створювати -екземпляр цього класу при необхідності. Такий підхід дозволяє використовувати -дії повторно. - - -Для створення класу дії необхідно виконати наступне: - -~~~ -[php] -class UpdateAction extends CAction -{ - public function run() - { - // деяка логіка дії - } -} -~~~ - -Щоб контролер знав про цю дію, необхідно перевизначити метод -[actions()|CController::actions] у класі контролера: - -~~~ -[php] -class PostController extends CController -{ - public function actions() - { - return array( - 'edit'=>'application.controllers.post.UpdateAction', - ); - } -} -~~~ - -У наведеному коді ми використовуємо псевдонім маршруту `application.controllers.post.UpdateAction` -щоб вказати на файл класу дії `protected/controllers/post/UpdateAction.php`. -Створюючи дії, засновані на класах, можна організувати додаток у модульному стилі. -Наприклад структура, директорій, приведена нижче, може бути використана для розташування коду контролерів: -~~~ -protected/ - controllers/ - PostController.php - UserController.php - post/ - CreateAction.php - ReadAction.php - UpdateAction.php - user/ - CreateAction.php - ListAction.php - ProfileAction.php - UpdateAction.php -~~~ - -### Привʼязка параметрів дій - -Починаючи з версії 1.1.4, в Yii зʼявилася підтримка автоматичної привʼязки -параметрів для дії контролера. Тобто можна задати іменовані параметри, -в які автоматично будуть потрапляти відповідні значення із `$_GET`. - -Для того, щоб показати, як це працює, припустимо, що нам потрібно -реалізувати дію `create` контролера `PostController`. Дія вимагає двох параметрів: - -* `category`: ID категорії, у якій буде створюватись запис. Ціле число; -* `language`: рядок, який містить код мови, яка буде використовуватися у запису. - -Скоріш за все у результаті для отримання параметрів із `$_GET` у нас вийде -наведений нижче нудний код: - -~~~ -[php] -class PostController extends CController -{ - public function actionCreate() - { - if(isset($_GET['category'])) - $category=(int)$_GET['category']; - else - throw new CHttpException(404,'Невірний запит'); - - if(isset($_GET['language'])) - $language=$_GET['language']; - else - $language='en'; - - // … дійсно корисна частина коду … - } -} -~~~ - -Тепер, використовуючи параметри дій, ми можемо отримати більш охайний код: - -~~~ -[php] -class PostController extends CController -{ - public function actionCreate($category, $language='en') - { - $category=(int)$category; - - // … дійсно корисна частина коду … - } -} -~~~ - -Ми додаємо два параметри методу `actionCreate`. Імʼя кожного повинно в точності -співпадати з одним із ключів у `$_GET`. Параметру `$language` задано значення -за замовчуванням `en`, яке використовується тоді, коли у запиті відповідний параметр -відсутній. Так як `$category` не має значення за замовчуванням, у випадку -відсутності відповідного параметру у запиті буде автоматично викликане виключення -[CHttpException] (із кодом помилки 400). - -Починаючи із версії 1.1.5, Yii підтримує масиви як параметри дій. -Використовувати їх можна наступним чином: - -~~~ -[php] -class PostController extends CController -{ - public function actionCreate(array $categories) - { - // Yii приведе $categories до масиву - } -} -~~~ - -Ми додаємо ключове слово `array` перед параметром `$categories`. -Після цього, якщо параметр `$_GET['categories']` є простим рядком, то він буде -приведений до масиву, який міститиме вихідний рядок. - -> Note|Примітка: Якщо параметр оголошений без вказівки типу `array`, то він повинен -> бути скалярним (тобто не масивом). У цьому випадку передача масива через -> `$_GET`-параметр приведе до виключення HTTP. - - -Починаючи із версії 1.1.7, автоматична привʼязка параметрів працює і з -діями, оформленими як класи. Якщо метод `run()` у класі дії -описати з параметрами, то ці параметри заповнюються відповідними значеннями -із HTTP-запиту: - -~~~ -[php] -class UpdateAction extends CAction -{ - public function run($id) - { - // $id буде заповнений значенням із $_GET['id'] - } -} -~~~ - -Фільтри ---------------- -Фільтр (filter) – це частина коду, що може виконуватися до або після -виконання дії контролера залежно від конфігурації. Наприклад, фільтр -контролю доступу може перевіряти, чи аутентифікований користувач перед тим, -як буде виконана запитана дія. Фільтр, що контролює продуктивність додатку, -може бути використаний для визначення часу, витраченого на виконання дії. - -Дія може мати безліч фільтрів. Фільтри запускаються в такому порядку, як -вони зазначені в списку фільтрів, при цьому фільтр може запобігти виконанню -дії і наступних за ним фільтрів. - -Фільтр може бути визначений як метод класу контролера. Імʼя методу повинне починатися на `filter`. -Наприклад, метод `filterAccessControl` визначає фільтр `accessControl`. -Метод фільтру оформлюється у такий спосіб: - -~~~ -[php] -public function filterAccessControl($filterchain) -{ - // для виконання наступних фільтрів і виконання дії викличте метод $filterchain->run() -} -~~~ - -де `$filterChain` — екземпляр класу [CFilterChain], який являє собою список фільтрів, -асоційованих із запитаною дією. У коді фільтра можна викликати -`$filterChain->run()` для того, щоб продовжити виконання наступних фільтрів і дії. - -Фільтр також може бути екземпляром класу [CFilter] або похідного від нього. -Наступний код визначає новий клас фільтра: - -~~~ -[php] -class PerformanceFilter extends CFilter -{ - protected function preFilter($filterChain) - { - // код, виконуваний до виконання дії - return true; // false — для випадку, коли дія не повинна бути виконана - } - - protected function postFilter($filterChain) - { - // код, виконуваний після виконання дії - } -} -~~~ - -Для того, щоб застосувати фільтр до дії, необхідно перевизначити метод -`CController::filters()`, який повертає масив конфігурацій фільтрів. Наприклад: - -~~~ -[php] -class PostController extends CController -{ - … - public function filters() - { - return array( - 'postOnly + edit, create', - array( - 'application.filters.PerformanceFilter - edit, create', - 'unit'=>'second', - ), - ); - } -} -~~~ - -Даний код визначає два фільтри: `postOnly` і `PerformanceFilter`. -Фільтр `postOnly` заданий як метод (відповідний метод уже визначений в -[CController]), в той час як `PerformanceFilter` — фільтр на базі класу. -Псевдонім `application.filters.PerformanceFilter` вказує на файл класу фільтра — -`protected/filters/PerformanceFilter`. Для конфігурації `PerformanceFilter` -використаний масив, тому можливо ініціалізувати значення властивості фільтру. -У цьому випадку властивість `unit` фільтра `PerformanceFilter` буде -ініціалізовано значенням `'second'`. - -Використовуючи оператори `'+'` і `'-'` можна вказати, до яких дій повинен чи -не повинен бути застосованим фільтр. У наведеному прикладі `postOnly` повинен бути -застосований до дій `edit` та `create`, а `PerformanceFilter` — до всіх дій, -окрім `edit` і `create`. Якщо оператори `'+'` і `'-'` не зазначені, фільтр буде +Контролер +========= +`Контролер (controller)` — це екземпляр класу [CController] або успадкованого від нього класу. +Він створюється обʼєктом додатку тоді, коли користувач робить відповідний запит. +При запуску контролер виконує відповідну дію, що зазвичай передбачає створення відповідних моделей +і рендеринг необхідних представлень. +У найпростішому випадку `дія` — це метод класу контролера, назва якого починається на `action`. + +У контролера є дія за замовчуванням, яка виконується у випадку, коли користувач не вказує дію при запиті. +За замовчуванням ця дія називається `index`. +Змінити її можна шляхом встановлення значення [CController::defaultAction]. + +Наступний код визначає контролер `site` з діями `index` (за замовчуванням) та `contact`: + +~~~ +[php] +class SiteController extends CController +{ + public function actionIndex() + { + // ... + } + + public function actionContact() + { + // ... + } +} +~~~ + + +Маршрут +------- +Контролери та дії розпізнаються по їхнім ідентифікаторам. +Ідентифікатор контролера — це запис формату `path/to/xyz`, що відповідає +файлу класу контролера `protected/controllers/path/to/XyzController.php`, де `xyz` +слід замінити реальною назвою класу (наприклад, `post` відповідає +`protected/controllers/PostController.php`). Ідентифікатор дії — це назва +методу без префікса `action`. Наприклад, якщо клас контролера містить метод +`actionEdit`, то ідентифікатор відповідної дії — `edit`. + +Користувач звертається до контролера та дії за допомогою маршруту (route). +Маршрут формується шляхом обʼєднання ідентифікаторів контролера та дії, відокремлених символом `/`. +Наприклад, маршрут `post/edit` вказує на дію `edit` контролеру `PostController` і, +за замовчуванням, URL `http://hostname/index.php?r=post/edit` приведе до виклику саме цих контролера та дії. + +> Note|Примітка: За замовчуванням маршрути чутливі до регістру. +>Це можливо змінити шляхом встановлення властивості +>[CUrlManager::caseSensitive] у конфігурації додатка рівною `false`. +>У режимі нечутливому до регістру переконайтеся, що назви директорій, +>які містять файли класів контролерів написані в нижньому регістрі, а також +>що [controller map|CWebApplication::controllerMap] та [action map|CController::actions] +>використовують ключі в нижньому регістрі. + +Додаток може містити [модулі](/doc/guide/basics.module). Маршрут до дії +контролера всередині модуля задається у форматі `moduleID/controllerID/actionID`. +Більш детальніше це описано у [розділі про модулі](/doc/guide/basics.module). + +Створення екземпляра контролера +------------------------------- +Екземпляр контролера створюється, коли [CWebApplication] обробляє вхідний запит. +Одержавши ідентифікатор контролера, додаток використовує наступні правила для +визначення класу контролера та його місцерозташування: + +- якщо встановлена властивість [CWebApplication::catchAllRequest], контролер буде створений +на основі цієї властивості, а контролер, який запитує користувач, буде проігноровано. +Як правило, це використовується для встановлення додатка в режим технічного обслуговування +та відображення статичної сторінки з відповідним повідомленням; + +- якщо ідентифікатор контролера виявлений у [CWebApplication::controllerMap], то для +створення екземпляру контролера буде використана відповідна конфігурація контролера; + +- якщо ідентифікатор контролера відповідає формату `'path/to/xyz'`, то імʼя класу +контролера визначається як `XyzController`, а відповідний клас як +`protected/controllers/path/to/XyzController.php`. +Наприклад, ідентифікатор контролера `admin/user` відповідатиме класу +контролера — `Usercontroller` та файл `protected/controllers/admin/UserController.php`. +Якщо файл класу не існує, буде викликане виключення [CHttpException] з кодом помилки 404. + +У випадку використання [модулів](/doc/guide/basics.module), процес, описаний вище, буде виглядати дещо інакше. +Зокрема, додаток перевірить, чи відповідає ідентифікатор контролеру всередині модуля. +Якщо відповідає — спочатку буде створений екземпляр модуля, потім екземпляр контролера. + + +Дія +-------- +Як було згадано вище, дія — це метод, імʼя якого починається на `action`. +Ще один спосіб — створити клас дії та вказати контролеру створювати +екземпляр цього класу при необхідності. Такий підхід дозволяє використовувати +дії повторно. + + +Для створення класу дії необхідно виконати наступне: + +~~~ +[php] +class UpdateAction extends CAction +{ + public function run() + { + // деяка логіка дії + } +} +~~~ + +Щоб контролер знав про цю дію, необхідно перевизначити метод +[actions()|CController::actions] у класі контролера: + +~~~ +[php] +class PostController extends CController +{ + public function actions() + { + return array( + 'edit'=>'application.controllers.post.UpdateAction', + ); + } +} +~~~ + +У наведеному коді ми використовуємо псевдонім маршруту `application.controllers.post.UpdateAction` +щоб вказати на файл класу дії `protected/controllers/post/UpdateAction.php`. +Створюючи дії, засновані на класах, можна організувати додаток у модульному стилі. +Наприклад структура, директорій, приведена нижче, може бути використана для розташування коду контролерів: +~~~ +protected/ + controllers/ + PostController.php + UserController.php + post/ + CreateAction.php + ReadAction.php + UpdateAction.php + user/ + CreateAction.php + ListAction.php + ProfileAction.php + UpdateAction.php +~~~ + +### Привʼязка параметрів дій + +Починаючи з версії 1.1.4, в Yii зʼявилася підтримка автоматичної привʼязки +параметрів для дії контролера. Тобто можна задати іменовані параметри, +в які автоматично будуть потрапляти відповідні значення із `$_GET`. + +Для того, щоб показати, як це працює, припустимо, що нам потрібно +реалізувати дію `create` контролера `PostController`. Дія вимагає двох параметрів: + +* `category`: ID категорії, у якій буде створюватись запис. Ціле число; +* `language`: рядок, який містить код мови, яка буде використовуватися у запису. + +Скоріш за все у результаті для отримання параметрів із `$_GET` у нас вийде +наведений нижче нудний код: + +~~~ +[php] +class PostController extends CController +{ + public function actionCreate() + { + if(isset($_GET['category'])) + $category=(int)$_GET['category']; + else + throw new CHttpException(404,'Невірний запит'); + + if(isset($_GET['language'])) + $language=$_GET['language']; + else + $language='en'; + + // … дійсно корисна частина коду … + } +} +~~~ + +Тепер, використовуючи параметри дій, ми можемо отримати більш охайний код: + +~~~ +[php] +class PostController extends CController +{ + public function actionCreate($category, $language='en') + { + $category=(int)$category; + + // … дійсно корисна частина коду … + } +} +~~~ + +Ми додаємо два параметри методу `actionCreate`. Імʼя кожного повинно в точності +співпадати з одним із ключів у `$_GET`. Параметру `$language` задано значення +за замовчуванням `en`, яке використовується тоді, коли у запиті відповідний параметр +відсутній. Так як `$category` не має значення за замовчуванням, у випадку +відсутності відповідного параметру у запиті буде автоматично викликане виключення +[CHttpException] (із кодом помилки 400). + +Починаючи із версії 1.1.5, Yii підтримує масиви як параметри дій. +Використовувати їх можна наступним чином: + +~~~ +[php] +class PostController extends CController +{ + public function actionCreate(array $categories) + { + // Yii приведе $categories до масиву + } +} +~~~ + +Ми додаємо ключове слово `array` перед параметром `$categories`. +Після цього, якщо параметр `$_GET['categories']` є простим рядком, то він буде +приведений до масиву, який міститиме вихідний рядок. + +> Note|Примітка: Якщо параметр оголошений без вказівки типу `array`, то він повинен +> бути скалярним (тобто не масивом). У цьому випадку передача масива через +> `$_GET`-параметр приведе до виключення HTTP. + + +Починаючи із версії 1.1.7, автоматична привʼязка параметрів працює і з +діями, оформленими як класи. Якщо метод `run()` у класі дії +описати з параметрами, то ці параметри заповнюються відповідними значеннями +із HTTP-запиту: + +~~~ +[php] +class UpdateAction extends CAction +{ + public function run($id) + { + // $id буде заповнений значенням із $_GET['id'] + } +} +~~~ + +Фільтри +--------------- +Фільтр (filter) – це частина коду, що може виконуватися до або після +виконання дії контролера залежно від конфігурації. Наприклад, фільтр +контролю доступу може перевіряти, чи аутентифікований користувач перед тим, +як буде виконана запитана дія. Фільтр, що контролює продуктивність додатку, +може бути використаний для визначення часу, витраченого на виконання дії. + +Дія може мати безліч фільтрів. Фільтри запускаються в такому порядку, як +вони зазначені в списку фільтрів, при цьому фільтр може запобігти виконанню +дії і наступних за ним фільтрів. + +Фільтр може бути визначений як метод класу контролера. Імʼя методу повинне починатися на `filter`. +Наприклад, метод `filterAccessControl` визначає фільтр `accessControl`. +Метод фільтру оформлюється у такий спосіб: + +~~~ +[php] +public function filterAccessControl($filterchain) +{ + // для виконання наступних фільтрів і виконання дії викличте метод $filterchain->run() +} +~~~ + +де `$filterChain` — екземпляр класу [CFilterChain], який являє собою список фільтрів, +асоційованих із запитаною дією. У коді фільтра можна викликати +`$filterChain->run()` для того, щоб продовжити виконання наступних фільтрів і дії. + +Фільтр також може бути екземпляром класу [CFilter] або похідного від нього. +Наступний код визначає новий клас фільтра: + +~~~ +[php] +class PerformanceFilter extends CFilter +{ + protected function preFilter($filterChain) + { + // код, виконуваний до виконання дії + return true; // false — для випадку, коли дія не повинна бути виконана + } + + protected function postFilter($filterChain) + { + // код, виконуваний після виконання дії + } +} +~~~ + +Для того, щоб застосувати фільтр до дії, необхідно перевизначити метод +`CController::filters()`, який повертає масив конфігурацій фільтрів. Наприклад: + +~~~ +[php] +class PostController extends CController +{ + … + public function filters() + { + return array( + 'postOnly + edit, create', + array( + 'application.filters.PerformanceFilter - edit, create', + 'unit'=>'second', + ), + ); + } +} +~~~ + +Даний код визначає два фільтри: `postOnly` і `PerformanceFilter`. +Фільтр `postOnly` заданий як метод (відповідний метод уже визначений в +[CController]), в той час як `PerformanceFilter` — фільтр на базі класу. +Псевдонім `application.filters.PerformanceFilter` вказує на файл класу фільтра — +`protected/filters/PerformanceFilter`. Для конфігурації `PerformanceFilter` +використаний масив, тому можливо ініціалізувати значення властивості фільтру. +У цьому випадку властивість `unit` фільтра `PerformanceFilter` буде +ініціалізовано значенням `'second'`. + +Використовуючи оператори `'+'` і `'-'` можна вказати, до яких дій повинен чи +не повинен бути застосованим фільтр. У наведеному прикладі `postOnly` повинен бути +застосований до дій `edit` та `create`, а `PerformanceFilter` — до всіх дій, +окрім `edit` і `create`. Якщо оператори `'+'` і `'-'` не зазначені, фільтр буде застосований до всіх дій. \ No newline at end of file diff --git a/docs/guide/uk/basics.convention.txt b/docs/guide/uk/basics.convention.txt index 4e5745fee..73c778cbb 100644 --- a/docs/guide/uk/basics.convention.txt +++ b/docs/guide/uk/basics.convention.txt @@ -1,141 +1,141 @@ -Угоди -===== - -Yii ставить угоди вище за конфігурації. Дотримуючись угод, ви зможете створювати серйозні додатки -без необхідності написання та підтримки складних конфігурацій. Звичайно ж, при необхідності Yii може -бути змінений за допомогою конфігурацій практично як завгодно. - -Нижче представлені угоди, рекомендовані для програмування під Yii. -Для зручності приймемо, що `WebRoot` — це директорія, у яку встановлений додаток. - -URL ---- - -За замовчуванням, Yii сприймає адреси URL наступного формату: - -~~~ -http://hostname/index.php?r=ControllerId/ActionId -~~~ - -GET-змінна `r` представляє [маршрут](/doc/guide/basics.controller#route), -з якого Yii отримує інформацію про контролер і дію. -Якщо `ActionId` не зазначений, контролер буде використовувати дію за замовчуванням -(визначену в [CController::defaultAction]). -Якщо ж `ControllerId` також не зазначений (або змінна `r` відсутня), буде використаний -контролер за замовчуванням (визначений у властивості [CWebApplication::defaultController]). - -Використовуючи [CUrlManager] можна створювати й застосовувати більш SEO-дружні адреси URL, такі як -`http://hostname/ControllerId/ActionId.html`. Ця можливість докладно описана в розділі -[Красиві адреси URL](/doc/guide/topics.url). - -Код ---- - -Yii рекомендує іменувати змінні, функції та класи, використовуючи ГорбатийРегістр (Camel Case), що має на увазі написання -кожного слова в імені з великої букви та зʼєднання їх без пробілів. -Перше слово в імені змінних і функцій повинне бути написане в нижньому регістрі, щоб відрізняти їх від імен -класів (наприклад, `$basepath`, `runController()`, `LinkPager`). -Для імен властивостей класу рекомендується використовувати знак підкреслення як префікса (наприклад, `$_actionList`). - -Оскільки простори імен не підтримуються версіями PHP до 5.3.0, рекомендується, щоб імена класів були -унікальними для уникнення конфлікту імен із класами сторонніх виробників. Із цієї причини всі імена класів -фреймворка мають префікс "C". - -Особливе правило для імен класів контролерів — вони повинні бути доповнені словом `Controller`. При цьому ідентифікатором -контролера буде імʼя класу з першою буквою в нижньому регістрі та без слова `Controller`. -Наприклад, для класу `Pagecontroller` ідентифікатором буде `page`. Дане правило робить додаток більш захищеним. -Воно також робить адреси URL більш зрозумілими ( приміром, `/index.php?r=page/index` замість -`/index.php?r=PageController/index`). - -Конфігурація ------------- - -Конфігурація — це масив пар ключ-значення, де кожний ключ являє собою імʼя властивості обʼєкта, що конфігурується, -а значення — початкове значення відповіднї властивості. -Приміром, `array('name'=>'My application', 'basePath'=>'./protected')` ініціалізує властивості `name` і `basePath` -відповідними значеннями. - -Будь-які властивості обʼєкта, що доступні для запису, можуть бути зконфігуровані. Якщо деякі -властивості не зконфігуровані, для них будуть використані значення за замовчуванням. -При конфігуруванні властивостей рекомендується вивчити відповідний розділ документації, -щоб уникнути завдання некоректних значень. - -Файл ----- - -Угоди для іменування й використання файлів залежать від їх типів. - -Файли класів повинні бути названі так само, як і публічні класи, що містяться в них. -Наприклад, клас [CController] знаходиться в файлі `CController.php`. -Загальний клас — це клас, який може бути використаний будь-якими іншими класами. -Кожний файл класів повинен містити максимум один загальний клас. Приватні класи -(класи, які можуть бути використані тільки одним загальним класом) повинні перебувати в одному -файлі із загальним класом. - -Файли представлень повинні мати такі ж імена, як і відображення, що містяться в них. -Приміром, відображення `index` знаходиться в файлі `index.php`. -Файл відображення — це PHP-скрипт, що містить HTML і PHP-код, в основному для задання відображення -користувальницького інтерфейсу. - -Конфігураційні файли можуть іменуватися довільним чином. Файл конфігурації — -це PHP-скрипт, чиє єдине призначення — повертати асоціативний масив, що представляє конфігурацію. - -Директорія ----------- - -В Yii визначений набір директорій для різних цілей. Кожна з них може бути змінена при необхідності. - - - `WebRoot/protected`: це [базова директорія додатку](/doc/guide/basics.application#application-base-directory), -яка містить всі найбільш важливі з точки зору безпеки PHP-скрипти та файли даних. Псевдонім за замовчуванням для цього шляху -— `application`. Ця директорія і її вміст повинні бути захищені від прямого доступу з веб. -Директорія може бути налаштована через [CWebApplication::basePath]; - - - `WebRoot/protected/runtime`: ця директорія містить приватні тимчасові файли, згенеровані під час виконання додатку. -Ця директорія повинна бути доступна для запису веб-сервером. Вона може бути налаштована через [Capplication::runtimePath]; - - - `WebRoot/protected/extensions`: ця директорія містить всі сторонні розширення. Вона може бути налаштована через -[CApplication::extensionPath]. Псевдонім за замовчуванням для цього шляху - `ext`; - - - `WebRoot/protected/modules`: ця директорія містить усі [модулі](/doc/guide/basics.module) додатку, -кожний з яких представлений в окремій піддиректорії. Директорія може бути налаштована через [CWebApplication::modulePath]; - - - `WebRoot/protected/controllers`: ця директорія містить файли всіх класів контролерів. -Вона може бути налаштована через [CWebApplication::controllerPath]; - - - `WebRoot/protected/views`: ця директорія містить файли всіх представлень, включаючи відображення контролерів, -макети та системні представлення. Вона може бути налаштована через [CWebApplication::viewPath]; - - - `WebRoot/protected/views/ControllerId`: ця директорія містить файли представлень для окремого класу контролера. -Тут `ControllerId` є ідентифікатором контролера. Директорія може бути налаштована через [CController::viewPath]; - - - `WebRoot/protected/views/layouts`: ця директорія містить файли макетів. Вона може бути налаштована через -[CWebApplication::layoutpath]; - - - `WebRoot/protected/views/system`: ця директорія містить файли системних представлень -(використовуються для виводу повідомлень про помилки і виключення). -Вона може бути налаштована через [CWebApplication::systemViewPath]; - - - `WebRoot/assets`: ця директорія містить файли ресурсів (приватні файли, які можуть бути -опубліковані для доступу до них із веб). Директорія повинна бути доступна для запису процесами веб-серверу. -Вона може бути налаштована через [CAssetManager::basePath]; - - - `WebRoot/themes`: ця директорія містить різні теми оформлення, які доступні додатку. -Кожна піддиректорія містить окрему тему з іменем, що збігаються з назвою піддиректорії. -Директорія може бути налаштована через [CThemeManager::basePath]. - -База даних ------------ - -Більшість додатків зберігають дані в БД. Ми пропонуємо угоди для таблиць та атрибутів БД. -Варто відзначити, що Yii не вимагає суворої відповідності їм. - - - Таблиці й атрибути іменуються в нижньому регістрі. - - - Слова в назві розділяються підкресленням (наприклад, `product_order`). - - - В іменах таблиць використовується або однина, або множина, але не обидва відразу. -Ми рекомендуємо використовувати однину. - - - Імена таблиць можуть містити префікс. Наприклад, `tbl_`. Це особливо корисно -коли таблиці нашого додатку містяться в БД, що використовується одночасно іншими +Угоди +===== + +Yii ставить угоди вище за конфігурації. Дотримуючись угод, ви зможете створювати серйозні додатки +без необхідності написання та підтримки складних конфігурацій. Звичайно ж, при необхідності Yii може +бути змінений за допомогою конфігурацій практично як завгодно. + +Нижче представлені угоди, рекомендовані для програмування під Yii. +Для зручності приймемо, що `WebRoot` — це директорія, у яку встановлений додаток. + +URL +--- + +За замовчуванням, Yii сприймає адреси URL наступного формату: + +~~~ +http://hostname/index.php?r=ControllerId/ActionId +~~~ + +GET-змінна `r` представляє [маршрут](/doc/guide/basics.controller#route), +з якого Yii отримує інформацію про контролер і дію. +Якщо `ActionId` не зазначений, контролер буде використовувати дію за замовчуванням +(визначену в [CController::defaultAction]). +Якщо ж `ControllerId` також не зазначений (або змінна `r` відсутня), буде використаний +контролер за замовчуванням (визначений у властивості [CWebApplication::defaultController]). + +Використовуючи [CUrlManager] можна створювати й застосовувати більш SEO-дружні адреси URL, такі як +`http://hostname/ControllerId/ActionId.html`. Ця можливість докладно описана в розділі +[Красиві адреси URL](/doc/guide/topics.url). + +Код +--- + +Yii рекомендує іменувати змінні, функції та класи, використовуючи ГорбатийРегістр (Camel Case), що має на увазі написання +кожного слова в імені з великої букви та зʼєднання їх без пробілів. +Перше слово в імені змінних і функцій повинне бути написане в нижньому регістрі, щоб відрізняти їх від імен +класів (наприклад, `$basepath`, `runController()`, `LinkPager`). +Для імен властивостей класу рекомендується використовувати знак підкреслення як префікса (наприклад, `$_actionList`). + +Оскільки простори імен не підтримуються версіями PHP до 5.3.0, рекомендується, щоб імена класів були +унікальними для уникнення конфлікту імен із класами сторонніх виробників. Із цієї причини всі імена класів +фреймворка мають префікс "C". + +Особливе правило для імен класів контролерів — вони повинні бути доповнені словом `Controller`. При цьому ідентифікатором +контролера буде імʼя класу з першою буквою в нижньому регістрі та без слова `Controller`. +Наприклад, для класу `Pagecontroller` ідентифікатором буде `page`. Дане правило робить додаток більш захищеним. +Воно також робить адреси URL більш зрозумілими ( приміром, `/index.php?r=page/index` замість +`/index.php?r=PageController/index`). + +Конфігурація +------------ + +Конфігурація — це масив пар ключ-значення, де кожний ключ являє собою імʼя властивості обʼєкта, що конфігурується, +а значення — початкове значення відповіднї властивості. +Приміром, `array('name'=>'My application', 'basePath'=>'./protected')` ініціалізує властивості `name` і `basePath` +відповідними значеннями. + +Будь-які властивості обʼєкта, що доступні для запису, можуть бути зконфігуровані. Якщо деякі +властивості не зконфігуровані, для них будуть використані значення за замовчуванням. +При конфігуруванні властивостей рекомендується вивчити відповідний розділ документації, +щоб уникнути завдання некоректних значень. + +Файл +---- + +Угоди для іменування й використання файлів залежать від їх типів. + +Файли класів повинні бути названі так само, як і публічні класи, що містяться в них. +Наприклад, клас [CController] знаходиться в файлі `CController.php`. +Загальний клас — це клас, який може бути використаний будь-якими іншими класами. +Кожний файл класів повинен містити максимум один загальний клас. Приватні класи +(класи, які можуть бути використані тільки одним загальним класом) повинні перебувати в одному +файлі із загальним класом. + +Файли представлень повинні мати такі ж імена, як і відображення, що містяться в них. +Приміром, відображення `index` знаходиться в файлі `index.php`. +Файл відображення — це PHP-скрипт, що містить HTML і PHP-код, в основному для задання відображення +користувальницького інтерфейсу. + +Конфігураційні файли можуть іменуватися довільним чином. Файл конфігурації — +це PHP-скрипт, чиє єдине призначення — повертати асоціативний масив, що представляє конфігурацію. + +Директорія +---------- + +В Yii визначений набір директорій для різних цілей. Кожна з них може бути змінена при необхідності. + + - `WebRoot/protected`: це [базова директорія додатку](/doc/guide/basics.application#application-base-directory), +яка містить всі найбільш важливі з точки зору безпеки PHP-скрипти та файли даних. Псевдонім за замовчуванням для цього шляху +— `application`. Ця директорія і її вміст повинні бути захищені від прямого доступу з веб. +Директорія може бути налаштована через [CWebApplication::basePath]; + + - `WebRoot/protected/runtime`: ця директорія містить приватні тимчасові файли, згенеровані під час виконання додатку. +Ця директорія повинна бути доступна для запису веб-сервером. Вона може бути налаштована через [Capplication::runtimePath]; + + - `WebRoot/protected/extensions`: ця директорія містить всі сторонні розширення. Вона може бути налаштована через +[CApplication::extensionPath]. Псевдонім за замовчуванням для цього шляху - `ext`; + + - `WebRoot/protected/modules`: ця директорія містить усі [модулі](/doc/guide/basics.module) додатку, +кожний з яких представлений в окремій піддиректорії. Директорія може бути налаштована через [CWebApplication::modulePath]; + + - `WebRoot/protected/controllers`: ця директорія містить файли всіх класів контролерів. +Вона може бути налаштована через [CWebApplication::controllerPath]; + + - `WebRoot/protected/views`: ця директорія містить файли всіх представлень, включаючи відображення контролерів, +макети та системні представлення. Вона може бути налаштована через [CWebApplication::viewPath]; + + - `WebRoot/protected/views/ControllerId`: ця директорія містить файли представлень для окремого класу контролера. +Тут `ControllerId` є ідентифікатором контролера. Директорія може бути налаштована через [CController::viewPath]; + + - `WebRoot/protected/views/layouts`: ця директорія містить файли макетів. Вона може бути налаштована через +[CWebApplication::layoutpath]; + + - `WebRoot/protected/views/system`: ця директорія містить файли системних представлень +(використовуються для виводу повідомлень про помилки і виключення). +Вона може бути налаштована через [CWebApplication::systemViewPath]; + + - `WebRoot/assets`: ця директорія містить файли ресурсів (приватні файли, які можуть бути +опубліковані для доступу до них із веб). Директорія повинна бути доступна для запису процесами веб-серверу. +Вона може бути налаштована через [CAssetManager::basePath]; + + - `WebRoot/themes`: ця директорія містить різні теми оформлення, які доступні додатку. +Кожна піддиректорія містить окрему тему з іменем, що збігаються з назвою піддиректорії. +Директорія може бути налаштована через [CThemeManager::basePath]. + +База даних +----------- + +Більшість додатків зберігають дані в БД. Ми пропонуємо угоди для таблиць та атрибутів БД. +Варто відзначити, що Yii не вимагає суворої відповідності їм. + + - Таблиці й атрибути іменуються в нижньому регістрі. + + - Слова в назві розділяються підкресленням (наприклад, `product_order`). + + - В іменах таблиць використовується або однина, або множина, але не обидва відразу. +Ми рекомендуємо використовувати однину. + + - Імена таблиць можуть містити префікс. Наприклад, `tbl_`. Це особливо корисно +коли таблиці нашого додатку містяться в БД, що використовується одночасно іншими додатками. \ No newline at end of file diff --git a/docs/guide/uk/basics.mvc.txt b/docs/guide/uk/basics.mvc.txt index db083e207..eb376596a 100644 --- a/docs/guide/uk/basics.mvc.txt +++ b/docs/guide/uk/basics.mvc.txt @@ -1,45 +1,45 @@ -Модель-Представлення-Контролер (MVC) -====================================== - -Yii використовує шаблон проектування Модель-Представлення-Контролер (MVC, Model-View-Controller), -який широко використовується у веб-програмуванні. - -MVC спрямований на відділення бізнес-логіки від користувальницького інтерфейсу, -щоб розробники могли легко змінювати окремі частини додатку, не задіваючи інші. -В архітектурі MVC модель надає дані і правила бізнес-логіки, представлення відповідає за користувальницький -інтерфейс (наприклад, текст, поля вводу), а контролер забезпечує взаємодію між моделлю та представленням. - -Окрім цього, Yii також використовує фронт-контролер, який називається додатком (application), -який інкапсулює контекст обробки запиту. Додаток збирає інформацію -про запит та передає його для подальшої обробки відповідному контролеру. - -Наступна діаграма відобража структуру додатку Yii: - -![Статична структура додатку Yii](structure.png) - - -Типова послідовність роботи додатку Yii -------------------------------------------------- - -Наступна діаграма описує типову послідовність процесу обробки користувальницького запиту додатком: - -![Типова послідовність роботи додатку Yii](flow.png) - - 1. Користувач здійснює запит за допомогою URL `http://www.example.com/index.php?r=post/show&id=1`, -а веб-сервер обробляє його, запускаючи виконання скрипта ініціалізації `index.php`; - 2. Скрипт ініціалізації створює екземпляр [додатку](/doc/guide/basics.application) та запускає його на виконання; - 3. Додаток отримує детальну інформацію про запит користувача -від [компонента додатку](/doc/guide/basics.application#application-component) `request`; - 4. Додаток визначає запитані [контролер](/doc/guide/basics.controller) -та [дію](/doc/guide/basics.controller#action) за допомогою компоненту `urlManager`. -У даному прикладі контролером буде `post`, який відноситься до класу `PostController`, -а дією — `show`, суть якого визначається контролером; - 5. Додаток створює екземпляр запитуваного контролеру для подальшої обробки запиту користувача. Контролер визначає -відповідність дії `show` методу `actionShow` у класі контролера. Далі створюються та застосовуються фільтри -(наприклад, access control, benchmarking), повʼязані з даною дією, та, якщо фільтри дозволять, дія виконається; - 6. Дія зчитує із бази даних [модель](/doc/guide/basics.model) `Post` з ID рівним `1`; - 7. Дія формує [представлення](/doc/guide/basics.view) `show` із даними моделі `Post`; - 8. Представлення отримує та відображає атрибути моделі `Post`; - 9. Представлення виконує деякі [віджети](/doc/guide/basics.view#widget); - 10. Сформоване представлення додається у [макет сторінки](/doc/guide/basics.view#layout); +Модель-Представлення-Контролер (MVC) +====================================== + +Yii використовує шаблон проектування Модель-Представлення-Контролер (MVC, Model-View-Controller), +який широко використовується у веб-програмуванні. + +MVC спрямований на відділення бізнес-логіки від користувальницького інтерфейсу, +щоб розробники могли легко змінювати окремі частини додатку, не задіваючи інші. +В архітектурі MVC модель надає дані і правила бізнес-логіки, представлення відповідає за користувальницький +інтерфейс (наприклад, текст, поля вводу), а контролер забезпечує взаємодію між моделлю та представленням. + +Окрім цього, Yii також використовує фронт-контролер, який називається додатком (application), +який інкапсулює контекст обробки запиту. Додаток збирає інформацію +про запит та передає його для подальшої обробки відповідному контролеру. + +Наступна діаграма відобража структуру додатку Yii: + +![Статична структура додатку Yii](structure.png) + + +Типова послідовність роботи додатку Yii +------------------------------------------------- + +Наступна діаграма описує типову послідовність процесу обробки користувальницького запиту додатком: + +![Типова послідовність роботи додатку Yii](flow.png) + + 1. Користувач здійснює запит за допомогою URL `http://www.example.com/index.php?r=post/show&id=1`, +а веб-сервер обробляє його, запускаючи виконання скрипта ініціалізації `index.php`; + 2. Скрипт ініціалізації створює екземпляр [додатку](/doc/guide/basics.application) та запускає його на виконання; + 3. Додаток отримує детальну інформацію про запит користувача +від [компонента додатку](/doc/guide/basics.application#application-component) `request`; + 4. Додаток визначає запитані [контролер](/doc/guide/basics.controller) +та [дію](/doc/guide/basics.controller#action) за допомогою компоненту `urlManager`. +У даному прикладі контролером буде `post`, який відноситься до класу `PostController`, +а дією — `show`, суть якого визначається контролером; + 5. Додаток створює екземпляр запитуваного контролеру для подальшої обробки запиту користувача. Контролер визначає +відповідність дії `show` методу `actionShow` у класі контролера. Далі створюються та застосовуються фільтри +(наприклад, access control, benchmarking), повʼязані з даною дією, та, якщо фільтри дозволять, дія виконається; + 6. Дія зчитує із бази даних [модель](/doc/guide/basics.model) `Post` з ID рівним `1`; + 7. Дія формує [представлення](/doc/guide/basics.view) `show` із даними моделі `Post`; + 8. Представлення отримує та відображає атрибути моделі `Post`; + 9. Представлення виконує деякі [віджети](/doc/guide/basics.view#widget); + 10. Сформоване представлення додається у [макет сторінки](/doc/guide/basics.view#layout); 11. Дія завершує формування представлення та виводить результат користувачу. \ No newline at end of file diff --git a/docs/guide/uk/basics.namespace.txt b/docs/guide/uk/basics.namespace.txt index e80a5df85..21b0beac9 100644 --- a/docs/guide/uk/basics.namespace.txt +++ b/docs/guide/uk/basics.namespace.txt @@ -1,268 +1,268 @@ -Псевдонім маршруту та простір імен -================================== - -Псевдоніми шляху широко використовуються в Yii. -Псевдонім асоціюється із директорією або шляхом до файлу. -При його вказуванні використовується точковий синтаксис, -подібний із широко використовуваним форматом просторів імен: - -~~~ -RootAlias.path.to.target -~~~ - -де `RootAlias` — псевдонім існуючої директорії. - - -За допомого [YiiBase::getPathOfAlias()] ми можемо перетворити псевдонім -у відповідний йому шлях. наприклад, `system.web.CController` буде -перетворений у `yii/framework/web/CController`. - -Також, ми можемо використовувати [YiiBase::setPathOfAlias()] для визначення нових -кореневих псевдонімів. - - -Кореневий псевдонім -------------------- - -Для зручності, наступні системні псевдоніми вже визначені: - - - `system`: відповідає директорії фреймворка; - - `zii`: відповідає директорії [бібліотеки розширень Zii](/doc/guide/extension.use#zii-extensions); - - `application`: відповідає [базовій директорії додатку](/doc/guide/basics.application#application-base-directory); - - `webroot`: відповідає директорії, яка містить [вхідний скрипт](/doc/guide/basics.entry). - - `ext`: відповідає директорії, яка містить всі сторонні [розширення](/doc/guide/extension.overview). - - -Крім того, якщо додаток використовує [модулі](/doc/guide/basics.module), то -у кожного модуля є співпадаючий з його ID кореневий псевдонім, який вказує на -корінь модуля. Наприклад, якщо додаток використовує модуль з ID `users`, то буде -визначений кореневий псевдонім `users`. - -Імпорт класів -------------- - -Використовуючи псевдоніми, дуже зручно імпортувати описи класів. -Наприклад, для підключення класа [CController] можна викликати: - -~~~ -[php] -Yii::import('system.web.CController'); -~~~ - -Використання методу [import|YiiBase::import] більш ефективно, ніж `include` та `require`, оскільки -опис імпортуємого класу не буде включено до першого звертання (реалізовано через механізм -автозавантаження класів PHP). Імпорт одного і того ж простору імен також відбувається набагато швидше, -ніж при використанні `include_once` та `require_once`. -Зверніть увагу, що імпорт каталогу не імпортує будь-який із його підкаталогів. - -> Tip|Підказка: Якщо ми посилаємося на клас фреймворку, то немає необхідності імпортувати або включати їх. -Всі системні класи Yii уже імпортовані заздалегідь. - -### Використання таблиці класів - -Починаючи із версії 1.1.5, Yii дозволяє попередньо імпортувати класи -через той же механізм, що використовується для класів ядра. Такі класи -можуть використовуватися де завгодно у додатку без необхідності іх попереднього -імпорту або підключення. Дана можливість відмінно підходить для фреймворку або бібліотеки, -які використовує Yii. - -Для імпорту набору класів, виконайте наступний код до виклику [CWebApplication::run()]: - -~~~ -[php] -Yii::$classMap=array( - 'ClassName1' => 'path/to/ClassName1.php', - 'ClassName2' => 'path/to/ClassName2.php', - ...... -); -~~~ - - -Імпорт директорій ------------------ - -Можна використовувати наступний синтаксис для того, щоб імпортувати цілу директорію, а файли класів, -які містяться у директорії, будуть підключені автоматично при необхідності. - -~~~ -[php] -Yii::import('system.web.*'); -~~~ - -Окрім [import|YiiBase::import], псевдоніми також використовуються у багатьох інших місцях, -де є посилання на класи. Наприклад, псевдонім може бути переданий методу [Yii::createComponent()] -для створення екземпляру відповідного класу, навіть якщо цей клас не був попередньо включений. - -Простір імен ------------- - -Простори служать для логічного групування імен класів, -щоб їх можна було відрізнити від інших, навіть якщо їх імена співпадають. -Не плутайте псевдонім шляху із простором імен. Псевдонім шляху — всього навсього -зручний спосіб іменування файлів та директорій. До простору імен він не має ніякого -відношення. - -> Tip|Підказка: Так як версії PHP до 5.3.0 не підтримують простори імен, ви не можете створити -екземпляри класів з однаковими іменами, аде різними описами. По цій причині всі назви -класів Yii-фреймворка мають префікс 'C' (який означає 'class'), щоб їх можна було відрізнити від -користувальницьких класів. Для користувальницьких класів рекомендується використовувати інші префікси, -зберігши префікс 'C' зарезервованим для Yii-фреймворка. - -Класи у просторах імен ----------------------- - -Клас у просторі імен — будь-який клас, описаний у неглобальному просторі імен. -Наприклад, клас `application\components\GoogleMap` описаний у просторі імен -`application\components`. Використання простору імен вимагає PHP 5.3.0 і вище. - -Починаючи із версії 1.1.5 стало можливим використання класу з простору імен -без його попереднього підключення. Наприклад, ми можемо створити новий екземпляр -`application\components\GoogleMap` без явного підключення відповідного файлу. -Це реалізується за допомогою покращеного завантажувача класів Yii. - -Для того, щоб автоматично довантажити клас із простору імен, простір імен повинен бути -названий у тому ж стилі, що і псевдоніми шляхів. Наприклад, клас `application\components\GoogleMap` -повинен зберігатися у файлі, якому відповідає псевдонім `application.components.GoogleMap`. - -Таким чином, для використання користувальницьких просторів імен, -що починаються, наприклад із `\mynamespace`, де класи розташовані у `/var/www/common/mynamespace/`, -єдине, що ви повинні зробити це визначити шлях псевдоніму, як наприклад: - -~~~ -[php] -Yii::setPathOfAlias('mynamespace', '/var/www/common/mynamespace/'); -~~~ - -Контролери у просторах імен ---------------------------- - -За замовчуванням Yii використовує контролери із глобального простору імен. -Ці класи знаходяться у `protected/controllers`. -Ви можете змінити дану поведінку двома способами: -використовуючи `controllerMap` та використовуючи `controllerNamespace`. -Перший дозволяє використовувати контролери із різних просторів імен. -Другий легше налаштовується, але задає один простір імен для всіх контролерів. - -### Використання `controllerMap` - -Краще всього змінювати дану властивість через файл конфігурації (`protected/config/main.php`): - -~~~ -[php] -// додаємо простір імен "mynamespace" -Yii::setPathOfAlias('mynamespace', '/var/www/common/mynamespace/'); - -return array( - 'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..', - 'name'=>'My Web Application', - - 'controllerMap' => array( - 'test' => '\mynamespace\controllers\TestController', - ), -~~~ - -Коли користувач намагається завантажити будь-який із контролерів, визначених у `controllerMap`, -Yii завантажує вказані класи, минаючи звичайний метод завантаження контролера. -У випадку із `test`, Yii буде завантажувати класс із простору імен -`\mynamespace\controllers\TestController` розташованного у -`/var/www/common/mynamespace/controllers/TestController.php`. - -Зверніть увагу, що код контролера повинен бути належним чином призначений до простору імен: - -~~~ -[php] -// визначаємо простір імен: -namespace mynamespace\controllers; - -// відколи клас тепер знаходиться у просторі імен, звертання до глобального простору імен -// повинно відбуватися явно із використанням "\": -class TestController extends \CController -{ - public function actionIndex() - { - echo 'Це TestController із \mynamespace\controllers'; - } -} -~~~ - -### Використання `controllerNamespace` - -Так як додаток є модулем, то ви можете використовувати `controllerNamespace` -так, як це описано далі у підрозділі "Модулі у просторах імен". - -Модулі у просторах імен ------------------------ - -Іноді корисно визначити простір імен для цілого модуля. -Наприклад, якщо ви хочете помістити `testmodule` у простір імен `\mynamespace\modules\testmodule`, який вказує на -`/var/www/common/mynamespace/modules/testmodule`, вам потрібно спочатку створити наступну файлову структуру: - -~~~ -/var/www/common/mynamespace/modules - testmodule - controllers - DefaultController.php - views - default - index.php - TestmoduleModule.php -~~~ - -Представлення `index.php` таке саме, як і у звичайного модуля. -`TestmoduleModule.php` та `DefaultController.php` знаходяться у просторі імен. - -`TestmoduleModule.php`: - -~~~ -[php] -// визначаємо простір імен: -namespace mynamespace\modules\testmodule; - -// відколи клас тепер знаходиться у просторі імен, звертання до глобального простору імен -// повинно відбуватися явно із використанням "\": -class TestmoduleModule extends \CWebModule -{ - // налаштування неглобального простору імен контролерів (також можна зробити за допомогою конфігурації) - public $controllerNamespace = '\mynamespace\modules\testmodule\controllers'; - - // звичайний код модуля -} -~~~ - -`DefaultController.php`: - -~~~ -[php] -render('index'); - } -} -~~~ - -Тепер залишилося тільки додати наш модуль до додатку. -Найкращий спосіб зробити це, щоб вказати його у файлі конфігурації додатку (`protected/config/main.php`): - -~~~ -[php] -// додаємо простір імен "mynamespace" -Yii::setPathOfAlias('mynamespace', '/var/www/common/mynamespace/'); - -return array( - 'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..', - 'name'=>'My Web Application', - - 'modules'=>array( - 'testmodule' => array( - 'class' => '\mynamespace\modules\testmodule\TestModuleModule', - ), - ), +Псевдонім маршруту та простір імен +================================== + +Псевдоніми шляху широко використовуються в Yii. +Псевдонім асоціюється із директорією або шляхом до файлу. +При його вказуванні використовується точковий синтаксис, +подібний із широко використовуваним форматом просторів імен: + +~~~ +RootAlias.path.to.target +~~~ + +де `RootAlias` — псевдонім існуючої директорії. + + +За допомого [YiiBase::getPathOfAlias()] ми можемо перетворити псевдонім +у відповідний йому шлях. наприклад, `system.web.CController` буде +перетворений у `yii/framework/web/CController`. + +Також, ми можемо використовувати [YiiBase::setPathOfAlias()] для визначення нових +кореневих псевдонімів. + + +Кореневий псевдонім +------------------- + +Для зручності, наступні системні псевдоніми вже визначені: + + - `system`: відповідає директорії фреймворка; + - `zii`: відповідає директорії [бібліотеки розширень Zii](/doc/guide/extension.use#zii-extensions); + - `application`: відповідає [базовій директорії додатку](/doc/guide/basics.application#application-base-directory); + - `webroot`: відповідає директорії, яка містить [вхідний скрипт](/doc/guide/basics.entry). + - `ext`: відповідає директорії, яка містить всі сторонні [розширення](/doc/guide/extension.overview). + + +Крім того, якщо додаток використовує [модулі](/doc/guide/basics.module), то +у кожного модуля є співпадаючий з його ID кореневий псевдонім, який вказує на +корінь модуля. Наприклад, якщо додаток використовує модуль з ID `users`, то буде +визначений кореневий псевдонім `users`. + +Імпорт класів +------------- + +Використовуючи псевдоніми, дуже зручно імпортувати описи класів. +Наприклад, для підключення класа [CController] можна викликати: + +~~~ +[php] +Yii::import('system.web.CController'); +~~~ + +Використання методу [import|YiiBase::import] більш ефективно, ніж `include` та `require`, оскільки +опис імпортуємого класу не буде включено до першого звертання (реалізовано через механізм +автозавантаження класів PHP). Імпорт одного і того ж простору імен також відбувається набагато швидше, +ніж при використанні `include_once` та `require_once`. +Зверніть увагу, що імпорт каталогу не імпортує будь-який із його підкаталогів. + +> Tip|Підказка: Якщо ми посилаємося на клас фреймворку, то немає необхідності імпортувати або включати їх. +Всі системні класи Yii уже імпортовані заздалегідь. + +### Використання таблиці класів + +Починаючи із версії 1.1.5, Yii дозволяє попередньо імпортувати класи +через той же механізм, що використовується для класів ядра. Такі класи +можуть використовуватися де завгодно у додатку без необхідності іх попереднього +імпорту або підключення. Дана можливість відмінно підходить для фреймворку або бібліотеки, +які використовує Yii. + +Для імпорту набору класів, виконайте наступний код до виклику [CWebApplication::run()]: + +~~~ +[php] +Yii::$classMap=array( + 'ClassName1' => 'path/to/ClassName1.php', + 'ClassName2' => 'path/to/ClassName2.php', + ...... +); +~~~ + + +Імпорт директорій +----------------- + +Можна використовувати наступний синтаксис для того, щоб імпортувати цілу директорію, а файли класів, +які містяться у директорії, будуть підключені автоматично при необхідності. + +~~~ +[php] +Yii::import('system.web.*'); +~~~ + +Окрім [import|YiiBase::import], псевдоніми також використовуються у багатьох інших місцях, +де є посилання на класи. Наприклад, псевдонім може бути переданий методу [Yii::createComponent()] +для створення екземпляру відповідного класу, навіть якщо цей клас не був попередньо включений. + +Простір імен +------------ + +Простори служать для логічного групування імен класів, +щоб їх можна було відрізнити від інших, навіть якщо їх імена співпадають. +Не плутайте псевдонім шляху із простором імен. Псевдонім шляху — всього навсього +зручний спосіб іменування файлів та директорій. До простору імен він не має ніякого +відношення. + +> Tip|Підказка: Так як версії PHP до 5.3.0 не підтримують простори імен, ви не можете створити +екземпляри класів з однаковими іменами, аде різними описами. По цій причині всі назви +класів Yii-фреймворка мають префікс 'C' (який означає 'class'), щоб їх можна було відрізнити від +користувальницьких класів. Для користувальницьких класів рекомендується використовувати інші префікси, +зберігши префікс 'C' зарезервованим для Yii-фреймворка. + +Класи у просторах імен +---------------------- + +Клас у просторі імен — будь-який клас, описаний у неглобальному просторі імен. +Наприклад, клас `application\components\GoogleMap` описаний у просторі імен +`application\components`. Використання простору імен вимагає PHP 5.3.0 і вище. + +Починаючи із версії 1.1.5 стало можливим використання класу з простору імен +без його попереднього підключення. Наприклад, ми можемо створити новий екземпляр +`application\components\GoogleMap` без явного підключення відповідного файлу. +Це реалізується за допомогою покращеного завантажувача класів Yii. + +Для того, щоб автоматично довантажити клас із простору імен, простір імен повинен бути +названий у тому ж стилі, що і псевдоніми шляхів. Наприклад, клас `application\components\GoogleMap` +повинен зберігатися у файлі, якому відповідає псевдонім `application.components.GoogleMap`. + +Таким чином, для використання користувальницьких просторів імен, +що починаються, наприклад із `\mynamespace`, де класи розташовані у `/var/www/common/mynamespace/`, +єдине, що ви повинні зробити це визначити шлях псевдоніму, як наприклад: + +~~~ +[php] +Yii::setPathOfAlias('mynamespace', '/var/www/common/mynamespace/'); +~~~ + +Контролери у просторах імен +--------------------------- + +За замовчуванням Yii використовує контролери із глобального простору імен. +Ці класи знаходяться у `protected/controllers`. +Ви можете змінити дану поведінку двома способами: +використовуючи `controllerMap` та використовуючи `controllerNamespace`. +Перший дозволяє використовувати контролери із різних просторів імен. +Другий легше налаштовується, але задає один простір імен для всіх контролерів. + +### Використання `controllerMap` + +Краще всього змінювати дану властивість через файл конфігурації (`protected/config/main.php`): + +~~~ +[php] +// додаємо простір імен "mynamespace" +Yii::setPathOfAlias('mynamespace', '/var/www/common/mynamespace/'); + +return array( + 'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..', + 'name'=>'My Web Application', + + 'controllerMap' => array( + 'test' => '\mynamespace\controllers\TestController', + ), +~~~ + +Коли користувач намагається завантажити будь-який із контролерів, визначених у `controllerMap`, +Yii завантажує вказані класи, минаючи звичайний метод завантаження контролера. +У випадку із `test`, Yii буде завантажувати класс із простору імен +`\mynamespace\controllers\TestController` розташованного у +`/var/www/common/mynamespace/controllers/TestController.php`. + +Зверніть увагу, що код контролера повинен бути належним чином призначений до простору імен: + +~~~ +[php] +// визначаємо простір імен: +namespace mynamespace\controllers; + +// відколи клас тепер знаходиться у просторі імен, звертання до глобального простору імен +// повинно відбуватися явно із використанням "\": +class TestController extends \CController +{ + public function actionIndex() + { + echo 'Це TestController із \mynamespace\controllers'; + } +} +~~~ + +### Використання `controllerNamespace` + +Так як додаток є модулем, то ви можете використовувати `controllerNamespace` +так, як це описано далі у підрозділі "Модулі у просторах імен". + +Модулі у просторах імен +----------------------- + +Іноді корисно визначити простір імен для цілого модуля. +Наприклад, якщо ви хочете помістити `testmodule` у простір імен `\mynamespace\modules\testmodule`, який вказує на +`/var/www/common/mynamespace/modules/testmodule`, вам потрібно спочатку створити наступну файлову структуру: + +~~~ +/var/www/common/mynamespace/modules + testmodule + controllers + DefaultController.php + views + default + index.php + TestmoduleModule.php +~~~ + +Представлення `index.php` таке саме, як і у звичайного модуля. +`TestmoduleModule.php` та `DefaultController.php` знаходяться у просторі імен. + +`TestmoduleModule.php`: + +~~~ +[php] +// визначаємо простір імен: +namespace mynamespace\modules\testmodule; + +// відколи клас тепер знаходиться у просторі імен, звертання до глобального простору імен +// повинно відбуватися явно із використанням "\": +class TestmoduleModule extends \CWebModule +{ + // налаштування неглобального простору імен контролерів (також можна зробити за допомогою конфігурації) + public $controllerNamespace = '\mynamespace\modules\testmodule\controllers'; + + // звичайний код модуля +} +~~~ + +`DefaultController.php`: + +~~~ +[php] +render('index'); + } +} +~~~ + +Тепер залишилося тільки додати наш модуль до додатку. +Найкращий спосіб зробити це, щоб вказати його у файлі конфігурації додатку (`protected/config/main.php`): + +~~~ +[php] +// додаємо простір імен "mynamespace" +Yii::setPathOfAlias('mynamespace', '/var/www/common/mynamespace/'); + +return array( + 'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..', + 'name'=>'My Web Application', + + 'modules'=>array( + 'testmodule' => array( + 'class' => '\mynamespace\modules\testmodule\TestModuleModule', + ), + ), ~~~ \ No newline at end of file diff --git a/docs/guide/uk/basics.view.txt b/docs/guide/uk/basics.view.txt index 4523e7403..7188139b9 100644 --- a/docs/guide/uk/basics.view.txt +++ b/docs/guide/uk/basics.view.txt @@ -1,146 +1,146 @@ -Представлення -============= -Представлення — це PHP-скрипт, який складається переважно з елементів -користувальницького інтерфейсу. Він може охоплювати вирази PHP, проте рекомендується, -щоб ці вирази не змінювали дані і залишалися відносно простими. -Згідно концепції розділення логіки та представлення, більша частина коду логіки -повинна бути розміщена у контролері або моделі, а не у скрипті представлення. - -Представлення має іʼмя, яке використовуєтся, щоб ідентифікувати файл -скрипта представлення у процесі рендеринга. Іʼмя представлення повинно співпадати -із назвою файла представлення. Наприклад, для представлення `edit` -відповідний файл скрипта повинен називатися `edit.php`. Щоб відобразити -представлення необхідно викликати метод [CController::render()], вказавши імʼя представлення. -При цьому метод спробує виявити відповідний файл у директорії `protected/views/ControllerID`. - -Всередині скрипта представлення екземпляр контролера доступний через `$this`. -Таким чином, ми можемо звернутися до властивостей контролера із кода представлення: -`$this->propertyName`. - -Крім того, ми можемо використовувати наступний спосіб для передачі даних представленню: - -~~~ -[php] -$this->render('edit', array( - 'var1'=>$value1, - 'var2'=>$value2, -)); -~~~ - -У наведеному коді метод [render()|CController::render] витягне другий параметр — -масив — у змінні. Як результат, у коді скрипта представлення можна -звертатися до локальних змінних `$var1` та `$var2`. - -Макет ----------- -Макет (layout) — це спеціальне представлення для декорування інших представлень. -Макет звичайно містить частини користувальницького інтерфейсу, загальні для інших представлень. -Наприклад, макет може містити верхню та нижню частини сторінки, укладаючи між ними -зміст іншого представлення: - -~~~ -[php] -…тут верхня частина… - -…тут нижня… -~~~ - -У цьому місці `$content` зберігає результати рендеринга представлення. - -Макет застосовується неявно при виклиці метода [render()|CController::render]. -За замовчуванням, у якості макета використовується представлення -`protected/views/layouts/main.php`. Це можна змінити шляхом встановлення значень -[CWebApplication::layout] або [CController::layout]. Для рендерингу представлення -без застосування макету необхідно викликати [renderPartial()|CController::renderPartial]. - - -Віджет ------- - -Віджет (widget) — це екземпляр класу [CWidget] або успадкований від нього. -Це компонент, який застосовується в основному з метою оформлення. Віджети звичайно -вбудовують у скрипт представлення для генерації деякої складної, але в той же час, -самостійної частини користувальницького інтерфейсу. Наприклад, віджет календаря -може бути використаний для рендерингу складного інтерфейса календаря. -Віджети дозволяють повторно використовувати код користувальницького інтерфейсу. - -Для використання віджету необхідно виконати у коді: - -~~~ -[php] -beginWidget('path.to.WidgetClass'); ?> -…деякий вміст, який може бути використаний віджетом… -endWidget(); ?> -~~~ - -або - -~~~ -[php] -widget('path.to.WidgetClass'); ?> -~~~ - -В останньому варіанті для використання віджету не потрібно додаткового вмісту. - -У випадку, якщо поведінку віджета необхідно змінити, можна виконати це шляхом -встановлення початкових значень його властивостей при виклиці -[CBaseController::beginWidget] або [CBaseController::widget]. -Наприклад, при використанні віджету [CMaskedTextField] можна вказувати використовувану маску, -передавши масив початкових значень властивостей, як показано нижче, де ключі масиву -є іменами властивостей, а значення — початковими значеннями відповідних їм властивостей віджета: - -~~~ -[php] -widget('CMaskedTextField',array( - 'mask'=>'99/99/9999' -)); -?> -~~~ - -Щоб створити новий віджет необхідно розширити клас [CWidget] и перевизначити -його методи [init()|CWidget::init] та [run()|CWidget::run]: - -~~~ -[php] -class MyWidget extends CWidget -{ - public function init() - { - // цей метод буде викликаний методом CController::beginWidget() - } - - public function run() - { - // цей метод буде викликаний методом CController::endWidget() - } -} -~~~ - -Як і у контролера, у віджета може бути власне представлення. -За замовчуванням, файли представлень віджета знаходяться у піддиректорії -`views` директорії, яка містить файл класу віджета. Ці представлення можна рендерити -за допомогою виклику [CWidget::render()], точно так, як і у випадку із контролером. -Єдина різниця полягає у тому, що для представлення віджета не використовуються макети. -Також, `$this` в представленні вказує на екземпляр віджета, а не на екземпляр контролера. - -> Tip|Підказка: властивість [CWidgetFactory::widgets] може бути використана для налаштування -замовчувань для окремих віджетів у всьому додатку. Детальніше про це можна прочитати у розділі -[«Теми оформлення» (Глобальне налаштування віджетів)](/doc/guide/topics.theming#customizing-widgets-globally) - -Системні представлення ----------------------- -Системні представлення відносяться до представлень, які використовуються Yii для -відображення помилок та інформації логів. Наприклад, коли користувач запитує -неіснуючий контролер або дію, Yii згенерує виключення, яке розкриває -суть помилки. Таке виключення буде відображено за допомогою системного представлення. - -Іменування системних представлень підпорядковується деяким правилам. -Імена типу `errorXXX` відносяться до представлень, які служать для -відображення [CHttpException] з кодом помилки `XXX`. -Наприклад, якщо виключення [CHttpException] згенеровано із кодом помилки 404, -буде використане представлення `error404`. - -Yii надає стандартний набір системних представлень, розташованих у -`framework/views`. Їх можна змінити, створивши файли представлень з -тими ж назвами у директорії `protected/views/system`. +Представлення +============= +Представлення — це PHP-скрипт, який складається переважно з елементів +користувальницького інтерфейсу. Він може охоплювати вирази PHP, проте рекомендується, +щоб ці вирази не змінювали дані і залишалися відносно простими. +Згідно концепції розділення логіки та представлення, більша частина коду логіки +повинна бути розміщена у контролері або моделі, а не у скрипті представлення. + +Представлення має іʼмя, яке використовуєтся, щоб ідентифікувати файл +скрипта представлення у процесі рендеринга. Іʼмя представлення повинно співпадати +із назвою файла представлення. Наприклад, для представлення `edit` +відповідний файл скрипта повинен називатися `edit.php`. Щоб відобразити +представлення необхідно викликати метод [CController::render()], вказавши імʼя представлення. +При цьому метод спробує виявити відповідний файл у директорії `protected/views/ControllerID`. + +Всередині скрипта представлення екземпляр контролера доступний через `$this`. +Таким чином, ми можемо звернутися до властивостей контролера із кода представлення: +`$this->propertyName`. + +Крім того, ми можемо використовувати наступний спосіб для передачі даних представленню: + +~~~ +[php] +$this->render('edit', array( + 'var1'=>$value1, + 'var2'=>$value2, +)); +~~~ + +У наведеному коді метод [render()|CController::render] витягне другий параметр — +масив — у змінні. Як результат, у коді скрипта представлення можна +звертатися до локальних змінних `$var1` та `$var2`. + +Макет +---------- +Макет (layout) — це спеціальне представлення для декорування інших представлень. +Макет звичайно містить частини користувальницького інтерфейсу, загальні для інших представлень. +Наприклад, макет може містити верхню та нижню частини сторінки, укладаючи між ними +зміст іншого представлення: + +~~~ +[php] +…тут верхня частина… + +…тут нижня… +~~~ + +У цьому місці `$content` зберігає результати рендеринга представлення. + +Макет застосовується неявно при виклиці метода [render()|CController::render]. +За замовчуванням, у якості макета використовується представлення +`protected/views/layouts/main.php`. Це можна змінити шляхом встановлення значень +[CWebApplication::layout] або [CController::layout]. Для рендерингу представлення +без застосування макету необхідно викликати [renderPartial()|CController::renderPartial]. + + +Віджет +------ + +Віджет (widget) — це екземпляр класу [CWidget] або успадкований від нього. +Це компонент, який застосовується в основному з метою оформлення. Віджети звичайно +вбудовують у скрипт представлення для генерації деякої складної, але в той же час, +самостійної частини користувальницького інтерфейсу. Наприклад, віджет календаря +може бути використаний для рендерингу складного інтерфейса календаря. +Віджети дозволяють повторно використовувати код користувальницького інтерфейсу. + +Для використання віджету необхідно виконати у коді: + +~~~ +[php] +beginWidget('path.to.WidgetClass'); ?> +…деякий вміст, який може бути використаний віджетом… +endWidget(); ?> +~~~ + +або + +~~~ +[php] +widget('path.to.WidgetClass'); ?> +~~~ + +В останньому варіанті для використання віджету не потрібно додаткового вмісту. + +У випадку, якщо поведінку віджета необхідно змінити, можна виконати це шляхом +встановлення початкових значень його властивостей при виклиці +[CBaseController::beginWidget] або [CBaseController::widget]. +Наприклад, при використанні віджету [CMaskedTextField] можна вказувати використовувану маску, +передавши масив початкових значень властивостей, як показано нижче, де ключі масиву +є іменами властивостей, а значення — початковими значеннями відповідних їм властивостей віджета: + +~~~ +[php] +widget('CMaskedTextField',array( + 'mask'=>'99/99/9999' +)); +?> +~~~ + +Щоб створити новий віджет необхідно розширити клас [CWidget] и перевизначити +його методи [init()|CWidget::init] та [run()|CWidget::run]: + +~~~ +[php] +class MyWidget extends CWidget +{ + public function init() + { + // цей метод буде викликаний методом CController::beginWidget() + } + + public function run() + { + // цей метод буде викликаний методом CController::endWidget() + } +} +~~~ + +Як і у контролера, у віджета може бути власне представлення. +За замовчуванням, файли представлень віджета знаходяться у піддиректорії +`views` директорії, яка містить файл класу віджета. Ці представлення можна рендерити +за допомогою виклику [CWidget::render()], точно так, як і у випадку із контролером. +Єдина різниця полягає у тому, що для представлення віджета не використовуються макети. +Також, `$this` в представленні вказує на екземпляр віджета, а не на екземпляр контролера. + +> Tip|Підказка: властивість [CWidgetFactory::widgets] може бути використана для налаштування +замовчувань для окремих віджетів у всьому додатку. Детальніше про це можна прочитати у розділі +[«Теми оформлення» (Глобальне налаштування віджетів)](/doc/guide/topics.theming#customizing-widgets-globally) + +Системні представлення +---------------------- +Системні представлення відносяться до представлень, які використовуються Yii для +відображення помилок та інформації логів. Наприклад, коли користувач запитує +неіснуючий контролер або дію, Yii згенерує виключення, яке розкриває +суть помилки. Таке виключення буде відображено за допомогою системного представлення. + +Іменування системних представлень підпорядковується деяким правилам. +Імена типу `errorXXX` відносяться до представлень, які служать для +відображення [CHttpException] з кодом помилки `XXX`. +Наприклад, якщо виключення [CHttpException] згенеровано із кодом помилки 404, +буде використане представлення `error404`. + +Yii надає стандартний набір системних представлень, розташованих у +`framework/views`. Їх можна змінити, створивши файли представлень з +тими ж назвами у директорії `protected/views/system`. diff --git a/docs/guide/uk/changes.txt b/docs/guide/uk/changes.txt index 7cd224489..24f106c28 100644 --- a/docs/guide/uk/changes.txt +++ b/docs/guide/uk/changes.txt @@ -1,97 +1,97 @@ -Нові можливості -=============== - -На цій сторінці коротко викладаються нові можливості, внесені у кожному релізі Yii. - -Версія 1.1.14 -------------- - -* Доданий [CPasswordHelper] -* Доданий [CRedisCache] - -Версія 1.1.11 -------------- - * [Додана підтримка кешування на рівні HTTP](/doc/guide/caching.page#http-caching) - * [Додано код виходу для консольних додатків](/doc/guide/topics.console#exit-codes) - * [Додано внесення правил валідації моделі до чорних списків](/doc/guide/form.model#declaring-validation-rules) - * [Додана підтримка git та hg](/doc/guide/quickstart.first-app#creating-your-first-yii-application) - -Версія 1.1.8 ------------- - * [Додана можливість використовувати свій клас правила URL](/doc/guide/topics.url#using-custom-url-rule-classes) - -Версія 1.1.7 ------------- - * [Додана підтримка URL у стилі REST](/doc/guide/topics.url#user-friendly-urls) - * [Додана підтримка кешування запитів](/doc/guide/caching.data#query-caching) - * [Тепер можливо передати параметри именованій групі умов відношення](/doc/guide/database.arr#relational-query-with-named-scopes) - * [Додана можливість виконання реляційних запитів без отримання даних із звʼязаних моделей](/doc/guide/database.arr#performing-relational-query-without-getting-related-models) - * [У AR додана підтримка відношень HAS_MANY through та HAS_ONE through](/doc/guide/database.arr#relational-query-with-through) - * [У міграції додана підтримка транзакцій](/doc/guide/database.migration#transactional-migrations) - * [Тепер можливо використовувати привʼязку параметрів із окремими класами дій](/doc/guide/basics.controller#action-parameter-binding) - * [Додана підтримка валідації на клієнті без AJAX з використанням [CActiveForm]](CActiveForm) - -Версія 1.1.6 ------------- - * [Додано конструктор запитів](/doc/guide/database.query-builder) - * [Додані міграції](/doc/guide/database.migration) - * [Кращі практики MVC](/doc/guide/basics.best-practices) - * [Консольним командам додана підтримка анонімних параметрів та глобальних опцій](/doc/guide/topics.console) - -Версія 1.1.5 ------------- - - * [Додана підтримка дій та параметрів дій у консольних командах](/doc/guide/topics.console) - * [Додана підтримка завантаження класів із простору імен](/doc/guide/basics.namespace) - * [Додана підтримка темізації віджетів](/doc/guide/topics.theming#theming-widget-views) - -Версія 1.1.4 ------------- - - * [Додана підтримка автоматичної привʼязки параметрів дій контролера](/doc/guide/basics.controller#action-parameters) - -Версія 1.1.3 ------------- - - * [Додана можливість налаштування віджету через файл конфігурації додатку](/doc/guide/topics.theming#customizing-widgets-globally) - -Версія 1.1.2 ------------- - - * [Доданий веб-кодогенератор Gii](/doc/guide/topics.gii) - -Версія 1.1.1 ------------- - - * Доданий віджет CActiveForm, який спрощує написання коду форми та підтримуючий - прозору валідацію як на стороні клієнта, так і на сторіне серверу. - - * Проведений рефакторинг коду, який генерує yiic. Додаток-каркас відтепер - генерирується із підтримкою декількох головних розміток, використаний віджет меню, - додана можливість сортувати дані в адміністративному інтерфейсі, для - відображення форм використовується CActiveForm. - - * [Додана підтримка глобальних консольних команд](/doc/guide/topics.console). - -Версія 1.1.0 ------------- - - * [Додана можливість використання модульного та функціонального тестування](/doc/guide/test.overview). - - * [Додана можливість використання скінів віджету](/doc/guide/topics.theming#skin). - - * [Доданий гнучкий інструмент для побудови форм](/doc/guide/form.builder). - - * Покращений спосіб оголошення безпечних атрибутів моделі: - - [Безпечне присвоєння значень атрибутам](/doc/guide/form.model#securing-attribute-assignments). - - * Змінений алгоритм жадного завантаження за замовчуванням для залежних запитів AR так, - що всі таблиці обʼєднуються в одному SQL-запиті. - - * Змінений псевдонім таблиці за замовчуванням на імʼя відношень AR. - - * [Додана підтримка використання префікса таблиць](/doc/guide/database.dao#using-table-prefix). - - * Доданий набір нових розширень — [бібліотека Zii](http://code.google.com/p/zii/). - - * Псевдонім для головної таблиці в AR запиті тепер назавжди рівен 't'. +Нові можливості +=============== + +На цій сторінці коротко викладаються нові можливості, внесені у кожному релізі Yii. + +Версія 1.1.14 +------------- + +* Доданий [CPasswordHelper] +* Доданий [CRedisCache] + +Версія 1.1.11 +------------- + * [Додана підтримка кешування на рівні HTTP](/doc/guide/caching.page#http-caching) + * [Додано код виходу для консольних додатків](/doc/guide/topics.console#exit-codes) + * [Додано внесення правил валідації моделі до чорних списків](/doc/guide/form.model#declaring-validation-rules) + * [Додана підтримка git та hg](/doc/guide/quickstart.first-app#creating-your-first-yii-application) + +Версія 1.1.8 +------------ + * [Додана можливість використовувати свій клас правила URL](/doc/guide/topics.url#using-custom-url-rule-classes) + +Версія 1.1.7 +------------ + * [Додана підтримка URL у стилі REST](/doc/guide/topics.url#user-friendly-urls) + * [Додана підтримка кешування запитів](/doc/guide/caching.data#query-caching) + * [Тепер можливо передати параметри именованій групі умов відношення](/doc/guide/database.arr#relational-query-with-named-scopes) + * [Додана можливість виконання реляційних запитів без отримання даних із звʼязаних моделей](/doc/guide/database.arr#performing-relational-query-without-getting-related-models) + * [У AR додана підтримка відношень HAS_MANY through та HAS_ONE through](/doc/guide/database.arr#relational-query-with-through) + * [У міграції додана підтримка транзакцій](/doc/guide/database.migration#transactional-migrations) + * [Тепер можливо використовувати привʼязку параметрів із окремими класами дій](/doc/guide/basics.controller#action-parameter-binding) + * [Додана підтримка валідації на клієнті без AJAX з використанням [CActiveForm]](CActiveForm) + +Версія 1.1.6 +------------ + * [Додано конструктор запитів](/doc/guide/database.query-builder) + * [Додані міграції](/doc/guide/database.migration) + * [Кращі практики MVC](/doc/guide/basics.best-practices) + * [Консольним командам додана підтримка анонімних параметрів та глобальних опцій](/doc/guide/topics.console) + +Версія 1.1.5 +------------ + + * [Додана підтримка дій та параметрів дій у консольних командах](/doc/guide/topics.console) + * [Додана підтримка завантаження класів із простору імен](/doc/guide/basics.namespace) + * [Додана підтримка темізації віджетів](/doc/guide/topics.theming#theming-widget-views) + +Версія 1.1.4 +------------ + + * [Додана підтримка автоматичної привʼязки параметрів дій контролера](/doc/guide/basics.controller#action-parameters) + +Версія 1.1.3 +------------ + + * [Додана можливість налаштування віджету через файл конфігурації додатку](/doc/guide/topics.theming#customizing-widgets-globally) + +Версія 1.1.2 +------------ + + * [Доданий веб-кодогенератор Gii](/doc/guide/topics.gii) + +Версія 1.1.1 +------------ + + * Доданий віджет CActiveForm, який спрощує написання коду форми та підтримуючий + прозору валідацію як на стороні клієнта, так і на сторіне серверу. + + * Проведений рефакторинг коду, який генерує yiic. Додаток-каркас відтепер + генерирується із підтримкою декількох головних розміток, використаний віджет меню, + додана можливість сортувати дані в адміністративному інтерфейсі, для + відображення форм використовується CActiveForm. + + * [Додана підтримка глобальних консольних команд](/doc/guide/topics.console). + +Версія 1.1.0 +------------ + + * [Додана можливість використання модульного та функціонального тестування](/doc/guide/test.overview). + + * [Додана можливість використання скінів віджету](/doc/guide/topics.theming#skin). + + * [Доданий гнучкий інструмент для побудови форм](/doc/guide/form.builder). + + * Покращений спосіб оголошення безпечних атрибутів моделі: + - [Безпечне присвоєння значень атрибутам](/doc/guide/form.model#securing-attribute-assignments). + + * Змінений алгоритм жадного завантаження за замовчуванням для залежних запитів AR так, + що всі таблиці обʼєднуються в одному SQL-запиті. + + * Змінений псевдонім таблиці за замовчуванням на імʼя відношень AR. + + * [Додана підтримка використання префікса таблиць](/doc/guide/database.dao#using-table-prefix). + + * Доданий набір нових розширень — [бібліотека Zii](http://code.google.com/p/zii/). + + * Псевдонім для головної таблиці в AR запиті тепер назавжди рівен 't'. diff --git a/docs/guide/uk/database.arr.txt b/docs/guide/uk/database.arr.txt index 42f831cf5..c1cb7a9c3 100644 --- a/docs/guide/uk/database.arr.txt +++ b/docs/guide/uk/database.arr.txt @@ -1,767 +1,767 @@ -Реляційна Active Record -======================= - -Ми вже розглянули використання Active Record (AR) для вибору даних з однієї таблиці бази даних. -У цьому розділі ми розповімо, як використовувати AR для обʼєднання декількох -звʼязаних таблиць і отримати обʼєднаний набір даних. - -Для використання реляційної AR рекомендується щоб усі звʼязки відношення -первинний-зовнішній ключ були чітко визначені для таблиць, що обʼєднуються. -Це допомагає підтримувати звʼязність та цілісність даних. - -Для наочності прикладів у даному розділі ми будемо використовувати схему бази даних, -представлену на цій діаграмі сутність-відношення (ER). - -![Діаграма ER](er.png) - -> Info|Інформація: Підтримка обмежень по зовнішньому ключу різна у різних СУБД. -SQLite 3.6.19 та більш ранні версії не підтримують обмежень, але ви, тим не менш, -можете їх оголосити при створенні таблиць. Движок MySQL MyISAM не підтримує зовнішні ключі. - -Оголошення відношення ---------------------- - -Перед тим, як використовувати AR для реляційних запитів, нам необхідно пояснити AR, -як AR-класи звʼязані один з одним. - -Відношення між двома AR-класами безпосередньо залежить від відносин між відповідними таблицями бази даних. -З точки зору БД, відношення між таблицями A і В може бути трьох типів: -один-до-багатьох (наприклад, `tbl_user` і `tbl_post`), -один-до-одного (наприклад, `tbl_user` і `tbl_profile`) -і багато-до-багатьох (наприклад, `tbl_category` і `tbl_post`). -У AR існує чотири типи відношень: - - - `BELONGS_TO`: якщо відношення між А і В один-до-багатьох, значить В належить А (наприклад, `Post` належить `User`); - - - `HAS_MANY`: якщо відношення між таблицями А і В один-до-багатьох, значить у А є багато В (наприклад, у `User` є багато `Post`); - - - `HAS_ONE`: це окремий випадок `HAS_MANY`, де А може мати максимум одне В (наприклад, у `User` є тільки один `Profile`); - - - `MANY_MANY`: це відношення відповідає типу відношення багато-до-багатьох в БД. -Оскільки багато СУБД не підтримують безпосередньо тип відношення багато-до-багатьох, -потрібно асоційована таблиця для перетворення відношення багато-до-багатьох у відносини один-до-багатьох. -У нашій схемі бази даних, для цієї мети є таблиця `tbl_post_category`. -У термінології AR ставлення `MANY_MANY` можна описати як комбінацію `BELONGS_TO` та `HAS_MANY`. -Наприклад, `Post` належить багатьом `Category`, а у `Category` є багато `Post`. - -Існує пʼятий спеціальний тип, який виконує агреговані запити на звʼязаних записах - він називається `STAT`. -Зверніться, будь ласка, до розділу [Статистичний запит](/doc/guide/database.arr#statistical-query) за деталями. - -Оголошуючи відношення в AR, ми перевизначаємо метод [relations()|CActiveRecord::relations] класу [CActiveRecord]. -Цей метод повертає масив з конфігурацією відношень. Кожен елемент масиву представляє один звʼязок у наступному форматі: - -~~~ -[php] -'VarName'=>array('RelationType', 'ClassName', 'ForeignKey', …додаткові параметри) -~~~ - -де `VarName` - імʼя відношення, `RelationType` вказує на один із чотирьох типів відношення, -`ClassName` - імʼя AR-класу, звʼязаного з даним AR-класом, -а `ForeignKey` вказує один або кілька зовнішніх ключів, які використовуються для звʼязку. -Крім того, можна вказати ряд додаткових параметрів, про які розповімо трохи пізніше. - -У коді нижче показано, як оголосити відношення між класами `User` і `Post`. - -~~~ -[php] -class Post extends CActiveRecord -{ - … - public function relations() - { - return array( - 'author'=>array(self::BELONGS_TO, 'User', 'author_id'), - 'categories'=>array(self::MANY_MANY, 'Category', - 'tbl_post_category(post_id, category_id)'), - ); - } -} - -class User extends CActiveRecord -{ - … - public function relations() - { - return array( - 'posts'=>array(self::HAS_MANY, 'Post', 'author_id'), - 'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'), - ); - } -} -~~~ - -> Info|Інформація: Зовнішній ключ може бути складеним, тобто складатися з двох і більше стовпців. -У даному випадку імена стовпців слід розділити комами і передати їх або у якості рядка, -або у вигляді масиву `array('key1','key2')`. Якщо потрібно вказати свій звʼязок первинного ключа із зовнішнім, -задати її можна у вигляді `array('fk'=>'pk')`. Для складених ключів це буде `array('fk_c1'=>'pk_c1','fk_c2'=>'pk_c2')`. -Для типу відношення `MANY_MANY` імʼя асоціативної таблиці також повинно бути зазначено в зовнішньому ключі. -Наприклад, відношення `categories` у моделі `Post` позначено зовнішнім ключем `tbl_post_category(post_id, category_id)`. -При оголошенні відношення в AR-класі для кожного відношення у клас неявно додається властивість. -Після виконання реляційного запиту відповідна властивість буде заповнена звʼязаним(-и) екземпляром(-ами) AR. -Наприклад, якщо `$author` представляє AR-екземпляр `User`, -то можна використовувати `$author->posts` для доступу до звʼязаних екземплярів `Post`. - -Виконання реляційного запиту ----------------------------- - -Найпростіший спосіб виконати реляційний запит — зчитати реляційну властивість AR-класу. -Якщо раніше до цієї властивості ніхто не звертався, то буде ініційований реляційний запит, -який зʼєднає звʼязані таблиці і залишить тільки дані, що відповідають первинному ключу поточного екземпляра AR. -Результат запиту буде збережено у властивості як екземпляр(-и) звʼязаного класу. Цей підхід також відомий, -як «відкладене завантаження» (lazy loading), тобто реляційний запит здійснюється -тільки у момент першого звернення до звʼязаних обʼєктів. Приклад нижче показує використання цього підходу: - -~~~ -[php] -// отримуємо запис з ID=10 -$post=Post::model()->findByPk(10); -// отримуємо автора запису. Тут буде виконаний реляційний запит. -$author=$post->author; -~~~ - -> Info|Інформація: Якщо для відношення не існує звʼязаного екземпляра, -то відповідна властивість буде null для відношень `BELONGS_TO` та `HAS_ONE` -або порожнім масивом для `HAS_MANY` та `MANY_MANY`. -Варто зазначити, що відношення `HAS_MANY` та `MANY_MANY` повертають масиви обʼєктів -і звертатися до їх властивостей необхідно в циклі, -інакше можна отримати помилку «Trying to get property of non-object». - -Спосіб відкладеного завантаження зручний, але не завжди ефективний. -Наприклад, якщо ми захочемо отримати інформацію про автора `N` записів, -використання відкладеного завантаження вимагатиме виконання `N` запитів для обʼєднання. -У даній ситуації, нам допоможе метод «жадібного завантаження» (eager loading). - -Цей підхід полягає у завантаженні всіх звʼязаних екземплярів AR одночасно з основним екземпляром AR. -Реалізується цей підхід шляхом використання в AR методу [with()|CActiveRecord::with] -у звʼязці із методом [find|CActiveRecord::find] або [findAll|CActiveRecord::findAll]. Наприклад: - -~~~ -[php] -$posts=Post::model()->with('author')->findAll(); -~~~ - -Код вище поверне масив екземплярів `Post`. -На відміну від відкладеного завантаження, властивість `author` у кожного запису -заповнено звʼязаним екземпляром `User` ще до звернення до цієї властивості. -Таким чином, замість виконання окремого запиту для кожного запису, -жадібне завантаження отримає всі записи разом із їх авторами у одному запиті! - -У методі [with()|CActiveRecord::with] можна вказати безліч імен відношень -і жадібне завантаження поверне їх за один раз. -Наприклад, наступний код поверне записи разом з їх авторами і категоріями: - -~~~ -[php] -$posts=Post::model()->with('author','categories')->findAll(); -~~~ - -Крім того, можна здійснювати вкладене жадібне завантаження. -Для цього замість простого списку імен відношень, -ми передаємо методу [with()|CActiveRecord::with] імена відношень, -упорядкованих ієрархічно, як у прикладі нижче: - -~~~ -[php] -$posts=Post::model()->with( - 'author.profile', - 'author.posts', - 'categories')->findAll(); -~~~ - -Приклад вище поверне нам всі записи з їх авторами і категоріями, а також профіль кожного автора і всі його записи. - -Жадібне завантаження може бути виконане шляхом зазначення властивості [CDbCriteria::with]: - -~~~ -[php] -$criteria=new CDbCriteria; -$criteria->with=array( - 'author.profile', - 'author.posts', - 'categories', -); -$posts=Post::model()->findAll($criteria); -~~~ - -або - -~~~ -[php] -$posts=Post::model()->findAll(array( - 'with'=>array( - 'author.profile', - 'author.posts', - 'categories', - ) -)); -~~~ - -Реляційний запит без отримання звʼязаних моделей ------------------------------------------------- - -Іноді потрібно виконати запит із використанням відношень, але, при цьому, не потрібні дані із звʼязаної моделі. -Припустимо, є користувачі (`User`), які публікують безліч записів (`Post`). -Запис може бути опублікований, а може бути чернеткою. -Цей факт визначається полем моделі `published`. Нам необхідно отримати всіх користувачів, -які опублікували хоча-б один запис. При цьому самі записи нам не цікаві. Зробити це можна так: - -~~~ -[php] -$users=User::model()->with(array( - 'posts'=>array( - // записи нам не потрібні - 'select'=>false, - // але потрібно вибрати тільки користувачів з опублікованими записами - 'joinType'=>'INNER JOIN', - 'condition'=>'posts.published=1', - ), -))->findAll(); -~~~ - - -Параметри реляційного запиту ----------------------------- - -Вище ми згадували про те, що в реляційному запиті можна вказати додаткові параметри. -Ці параметри — пари імʼя-значення — використовуються для тонкого налаштування реляційного запиту. -Перелік параметрів представлений нижче. - - - `select`: список обраних полів для звʼязаного AR-класу. За замовчуванням значення параметра дорівнює '*', -тобто вибираються всі поля таблиці. Для використовуваних стовпців повинні бути вирішені конфлікти імен; - - - `condition`: відповідає оператору `WHERE`, за замовчуванням значення параметра порожнє. -Для використовуваних стовпців повинні бути вирішені конфлікти імен; - - - `params`: параметри для звʼязування у генерованому SQL-виразу. Параметри передаються як масив пар імʼя-значення; - - - `on`: відповідає оператору `ON`. Умова, що визначене в цьому параметрі, -буде додано до умови обʼєднання з використанням оператора `AND`. -Для використовуваних стовпців повинні бути вирішені конфлікти імен. -Даний параметр непридатний для відношень типу `MANY_MANY`; - - - `order`: відповідає оператору `ORDER BY`, за замовчуванням значення параметра порожнє. -Для використовуваних стовпців повинні бути вирішені конфлікти імен; - - - `with`: перелік дочірніх звʼязаних обʼєктів, які повинні бути завантажені із самим обʼєктом. -Неправильне використання даної можливості може призвести до нескінченного циклу; - - - `joinType`: тип обʼєднання для відношень. За замовчуванням значення параметра дорівнює `LEFT OUTER JOIN`; - - - `alias`: псевдонім таблиці, асоційованої з відношенням. За замовчуванням значення параметра дорівнює null, -що означає, що псевдонім відповідає імені відношення; - - - `together`: параметр, який встановлює необхідність примусового обʼєднання таблиці, -асоційованої з цим відношенням, з іншими таблицями. -Цей параметр має сенс тільки для відношень типів `HAS_MANY` та `MANY_MANY`. -Якщо параметр не встановлений або дорівнює `false`, тоді кожне відношення `HAS_MANY` або `MANY_MANY` -буде використовувати окремий SQL запит для звʼязаних даних, що може поліпшити швидкість виконання запиту, -так як зменшується кількість даних, що вибирається. Якщо цей параметр дорівнює `true`, -залежна таблиця при запиті буде завжди обʼєднуватися з основною, тобто буде зроблено один запит навіть у тому випадку, -якщо до основної таблиці застосовується посторінкова розбивка. -Якщо цей параметр не заданий, залежна таблиця буде обʼєднана з основною тільки у разі, -коли не до основної таблиці не застосовується посторінкова розбивка. -Більш докладний опис можна знайти в розділі «продуктивність реляційного запиту»; - - - `join`: додатковий оператор `JOIN`. За замовчуванням порожній. Цей параметр доступний з версії 1.1.3; - - - `group`: відповідає оператору `GROUP BY`, за замовчуванням значення параметра порожнє. -Для використовуваних стовпців повинні бути вирішені конфлікти імен; - - - `having`: відповідає оператору `HAVING`, за замовчуванням значення параметра порожнє. -Для використовуваних стовпців повинні бути вирішені конфлікти імен; - - - `index`: імʼя стовпця, значення якого повинні бути використані в якості ключів масиву, -що зберігає звʼязані обʼєкти. Без установки цього параметра, масив звʼязаних обʼєктів використовує цілочисельний індекс, -що починається з нуля. Параметр може бути встановлений тільки для відношень `HAS_MANY` та `MANY_MANY`; - - - `scopes`: групи умов, які необхідно застосувати. -У випадку однієї групи може використовуватися як `'scopes'=>'scopeName'`. -Якщо ж груп кілька, то використовується як `'scopes'=>array('scopeName1','scopeName2')`. -Цей параметр доступний з версії 1.1.9. - -Крім того, для відкладеного завантаження деяких типів відношень доступний ряд додаткових параметрів: - - - `limit`: параметр для обмеження кількості рядків у вибірці. Параметр не можна застосувати для відношень `BELONGS_TO`; - - - `offset`: параметр для зазначення початкового рядка вибірки. Параметр не можна застосувати для відношень `BELONGS_TO`; - - - `through`: імʼя відношення моделі, яке при отриманні даних буде використовуватися як міст. -Параметр може бути встановлений тільки для відношень `HAS_ONE` та `HAS_MANY`. -Цей параметр доступний з версії 1.1.7, у якій можна застосовувати його до `HAS_ONE` та `HAS_MANY`. -Починаючи з версії 1.1.14, він може використовуватися із `BELONGS_TO`. - -Нижче ми змінимо визначення відношення `posts` у моделі `User`, додавши кілька вищенаведених параметрів: - -~~~ -[php] -class User extends CActiveRecord -{ - public function relations() - { - return array( - 'posts'=>array(self::HAS_MANY, 'Post', 'author_id', - 'order'=>'posts.create_time DESC', - 'with'=>'categories'), - 'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'), - ); - } -} -~~~ - -Тепер при зверненні до `$author->posts`, ми отримаємо записи автора, -відсортовані в зворотньому порядку за часом їх створення. Для кожного запису будуть завантажені її категорії. - -Усунення конфлікту імен стовпців --------------------------------- - -При співпаданні імен стовпців у двох і більше зʼєднуваних таблиць, доводиться вирішувати конфлікт імен. -Це робиться за допомогою додавання псевдоніма таблиці до імені стовпця. - -У реляційному запиті псевдонім головної таблиці завжди дорівнює `t`, а імʼя псевдоніма таблиці, -що відноситься до неї, за замовчуванням відповідає імені відношення. -Наприклад, у коді нижче псевдоніми для `Post` та `Comment` відповідно `t` і `comments`: - -~~~ -[php] -$posts=Post::model()->with('comments')->findAll(); -~~~ - -Припустимо, що і в `Post` і в `Comment` є стовпець `create_time`, -у якому зберігається час створення запису або коментаря, і нам необхідно отримати записи разом з коментарями до них, -відсортовані спочатку за часом створення запису, а потім за часом написання коментаря. -Для цього нам знадобиться усунути конфлікт стовпців `create_time` наступним чином: - -~~~ -[php] -$posts=Post::model()->with('comments')->findAll(array( - 'order'=>'t.create_time, comments.create_time' -)); -~~~ - -> Tip|Підказка: Псевдонім таблиці звʼязку за замовчуванням дорівнює назві самого звʼязку. -> Майте на увазі, що при використанні одного звʼязку всередині іншого буде використано назву -> останнього із них. При цьому назва батьківського звʼязку не буде використана у якості префікса. -> Наприклад, псевдонімом звʼязку 'author.group' є 'group', а не 'author.group'. -> -> ~~~ -> [php] -> $posts=Post::model()->with('author', 'author.group')->findAll(array( -> 'order'=>'group.name, author.name, t.title' -> )); -> ~~~ -> -> Ви можете уникнути конфлікту псевдонімів таблиць задавши властивість звʼязку [alias|CActiveRelation::alias]. -> -> ~~~ -> [php] -> $comments=Comment::model()->with( -> 'author', -> 'post', -> 'post.author'=>array('alias'=>'p_author'))->findAll(array( -> 'order'=>'author.name, p_author.name, post.title' -> )); -> ~~~ - -Динамічні параметри реляційного запиту --------------------------------------- - -Ми можемо використовувати динамічні параметри як для методу [with()|CActiveRecord::with], так і для параметра `with`. -Динамічні параметри перевизначають існуючі параметри відповідно до опису метода [relations()|CActiveRecord::relations]. -Наприклад, якщо для моделі `User`, наведеної вище, ми хочемо скористатися жадібним завантаженням -для отримання записів автора у порядку зростання (параметр `order` у визначенні відношення задає регресний порядок), -можна зробити це таким чином: - -~~~ -[php] -User::model()->with(array( - 'posts'=>array('order'=>'posts.create_time ASC'), - 'profile', -))->findAll(); -~~~ - -Динамічні параметри в реляційних запитах можна використовувати разом з відкладеним завантаженням. -Для цього необхідно викликати метод з тим же імʼям, що й імʼя звʼязку, і передати параметри як його аргумент. -Наприклад, наступний код поверне публікації користувача, у яких `status` дорівнює 1: - -~~~ -[php] -$user=User::model()->findByPk(1); -$posts=$user->posts(array('condition'=>'status=1')); -~~~ - -Продуктивність реляційного запиту ---------------------------------- - -Як було описано вище, жадібне завантаження використовується, головним чином, -коли потрібно отримати безліч звʼязаних обʼєктів. -У цьому випадку зʼєднанням усіх таблиць генерується великий складний SQL-запит. -Такий запит у багатьох випадках є кращим, так як спрощує фільтрацію за значенням стовпця звʼязаної таблиці. -Тим не менш, в деяких випадках такі запити не є ефективними. - -Розглянемо приклад, у якому нам потрібно знайти нові записи разом з їх коментарями. -Враховуючи, що у кожному записі 10 коментарів, -при використанні одного великого SQL-запиту ми отримаємо безліч зайвих -даних так як кожен запис буде повторно вибиратися з кожним її коментарем. -Тепер спробуємо по-іншому: спочатку виберемо останні записи, а потім коментарі до них. -У даному випадку нам необхідно виконати два SQL запити. Плюс в тому, що в отриманих даних не буде нічого зайвого. - -То який підхід більш ефективний? Абсолютно вірної відповіді на це питання немає. -Виконання одного великого SQL запиту може бути більш ефективним так як СУБД не доводиться -зайвий раз розбирати і виконувати додаткові запити. З іншого боку, використовуючи один SQL запит, -ми отримуємо більше зайвих даних і відповідно нам потрібно більше часу на їх передачу і обробку. -За замовчуванням Yii використовує "жадібне" завантаження, тобто генерує один SQL запит, крім того випадку, -коли до головної моделі застосовується `LIMIT`. Якщо виставити опцію `together` в описі відношення в `true`, -то ми отримаємо єдиний SQL запит навіть якщо використовується `LIMIT`. Якщо використовувати `false`, -то вибірка з деяких таблиць проводитиметься окремими запитами. -Приміром, для того, щоб використовувати окремі SQL запити для вибірки останніх записів і коментарів до них, -відношення `comments` моделі `Post` слід описати таким чином: - -~~~ -[php] -public function relations() -{ - return array( - 'comments' => array(self::HAS_MANY, 'Comment', 'post_id', 'together'=>false), - ); -} -~~~ - -Для жадібного завантаження ми можемо задати цю опцію динамічно: - -~~~ -[php] -$posts = Post::model()->with(array('comments'=>array('together'=>false)))->findAll(); -~~~ - -Статистичний запит ------------------- - -Крім реляційних запитів, описаних вище, Yii також підтримує так звані статистичні запити (або запити агрегування). -Цей тип запитів використовується для отримання агрегованих даних, що відносяться до звʼязаних обʼєктів, -наприклад кількість коментарів до кожного запису, середній рейтинг для кожного найменування продукції і т.д. -Статистичні запити можуть бути використані тільки для обʼєктів, -звʼязаних відношеннями `HAS_MANY` (наприклад, у запису є багато коментарів) -або `MANY_MANY` (наприклад, запис належить багатьом категоріям, -а категорія може відноситися ставитися до безлічі записів). - -Виконання статистичного запиту аналогічно виконання реляційного запиту відповідно до опису вище. -Передусім необхідно оголосити статистичний запит у методі [relations()|CActiveRecord::relations] класа [CActiveRecord]. - -~~~ -[php] -class Post extends CActiveRecord -{ - public function relations() - { - return array( - 'commentCount'=>array(self::STAT, 'Comment', 'post_id'), - 'categoryCount'=>array(self::STAT, 'Category', 'post_category(post_id, category_id)'), - ); - } -} -~~~ - -Вище ми оголосили два статистичних запита: `commentCount` підраховує кількість коментарів до запису, -а `categoryCount` рахує кількість категорій, до яких відноситься запис. -Зверніть увагу, що відношення між `Post` і `Comment` - типу `HAS_MANY`, -а відношення між `Post` і `Category` - типу `MANY_MANY` (з використанням перетворюючої таблиці `post_category`). -Як можна бачити, порядок оголошення дуже схожий з оголошенням відношень, описаних вище. -Єдина відмінність полягає у тому, що у даному випадку тип відношення дорівнює `STAT`. - -За рахунок оголошених відношень ми можемо отримати кількість коментарів для запису, -використовуючи вираз `$post->commentCount`. -У момент першого звернення до даної властивості для отримання відповідного результату -неявним чином виконується SQL-вираз. Як ми вже говорили, це називається підходом *відкладеного завантаження*. -Можна також використовувати *жадібний* варіант завантаження, -якщо необхідно отримати кількість коментарів до кількох записів: - -~~~ -[php] -$posts=Post::model()->with('commentCount', 'categoryCount')->findAll(); -~~~ - -Вираз вище виконує три SQL-запити для отримання всіх записів разом із значеннями кількості -коментарів до них і кількості категорій. -У разі відкладеного завантаження нам би знадобилося виконати `2*N+1` SQL-запитів для `N` записів. - -За замовчуванням статистичний запит рахує кількість з використанням виразу `COUNT`. -Його можна уточнити шляхом зазначення додаткових параметрів у момент оголошення в методі -[relations()|CActiveRecord::relations]. Доступні параметри перераховані нижче: - - - `select`: статистичний вираз, за замовчуванням дорівнює `COUNT(*)`, що відповідає кількості дочірніх обʼєктів; - - - `defaultValue`: значення, яке надається у випадку, якщо результат статистичного запиту для запису відʼємний. -Наприклад, якщо запис не має жодного коментаря, то властивості `commentCount` буде присвоєно це значення. -За замовчуванням значення даного параметра дорівнює 0; - - - `condition`: відповідає оператору `WHERE`, за замовчуванням значення параметра порожнє; - - - `params`: параметри для звʼязування у генерованому SQL-виразі. Параметри передаються як масив пар імʼя-значення; - - - `order`: відповідає оператору `ORDER BY`, за замовчуванням значення параметра порожнє; - - - `group`: відповідає оператору `GROUP BY`, за замовчуванням значення параметра порожнє; - - - `having`: відповідає оператору `HAVING`, за замовчуванням значення параметра порожнє. - -Реляційні запити з іменованими групами умов -------------------------------------------- - -У реляційному запиті [іменовані групи умов](/doc/guide/database.ar#named-scopes) можуть бути використані двома способами. -Їх можна застосувати до основної моделі і до звʼязаних моделей. - -Наступний код показує роботу з основною моделлю: - -~~~ -[php] -$posts=Post::model()->published()->recently()->with('comments')->findAll(); -~~~ - -Даний код дуже схожий на нереляційні запити. Єдина відмінність у тому, -що у нас присутній виклик `with()` після викликів груп умов. -Даний запит поверне недавно опубліковані записи разом з коментарями до них. - -У наступному прикладі показано, як застосувати групи умов до звʼязаних моделей: - -~~~ -[php] -$posts=Post::model()->with('comments:recently:approved')->findAll(); -// або, починаючи з версії 1.1.7 -$posts=Post::model()->with(array( - 'comments'=>array( - 'scopes'=>array('recently','approved') - ), -))->findAll(); -// або, починаючи з версії 1.1.7 -$posts=Post::model()->findAll(array( - 'with'=>array( - 'comments'=>array( - 'scopes'=>array('recently','approved') - ), - ), -)); -~~~ - -Цей запит поверне всі записи разом із ухваленими коментарями. Тут `comments` відноситься до імені відношення. -`recently` та `approved` - іменовані групи, описані у моделі `Comment`. -Імʼя відношення і групи параметрів розділяються двокрапкою. - -Вам може знадобитися використовувати замість жадібної вибірки відкладену для -звʼязку із групою умов. Синтаксис для цього такий: - -~~~ -[php] -// імʼя звʼязку comments повторюється два рази -$approvedComments = $post->comments('comments:approved'); -~~~ - -Іменовані групи можуть бути використані при описі відношень моделі у -методі [CActiveRecord::relations()] у параметрі `with`. -У наступному прикладі при зверненні до `$user->posts` разом з публікаціями будуть отримані всі *ухвалені* коментарі. - -~~~ -[php] -class User extends CActiveRecord -{ - public function relations() - { - return array( - 'posts'=>array(self::HAS_MANY, 'Post', 'author_id', - 'with'=>'comments:approved'), - ); - } -} -// або, починаючи з версії 1.1.7 -class User extends CActiveRecord -{ - public function relations() - { - return array( - 'posts'=>array(self::HAS_MANY, 'Post', 'author_id', - 'with'=>array( - 'comments'=>array( - 'scopes'=>'approved' - ), - ), - ), - ); - } -} -~~~ - - -З версії 1.1.7 стало можливо передати параметри іменованим групам умов відношення. -Приміром, якщо у `Post` є іменована група умов `rated`, яка приймає мінімальний рейтинг запису, -використовувати її у `User` можна так: - -> Note|Примітка: до 1.1.7 іменовані групи умов, що застосовуються до реляційних моделей, -повинні бути описані в CActiveRecord::scopes. Тому вони не можуть бути параметризовані. - -~~~ -[php] -$users=User::model()->findAll(array( - 'with'=>array( - 'posts'=>array( - 'scopes'=>array( - 'rated'=>5, - ), - ), - ), -)); - -class Post extends CActiveRecord -{ - ...... - - public function rated($rating) - { - $this->getDbCriteria()->mergeWith(array( - 'condition'=>'rating=:rating', - 'params'=>array(':rating'=>$rating), - )); - return $this; - } - - ...... -} -~~~ - -Реляційні запити з through --------------------------- - -При використанні `through` визначення відношення має виглядати наступним чином: - -~~~ -[php] -'comments'=>array(self::HAS_MANY,'Comment',array('key1'=>'key2'),'through'=>'posts'), -~~~ - -У коді вище, а саме у `array('key1'=>'key2')`: - - - `key1` — ключ, визначений у відношенні, на яке вказує `through` (у нашому випадку `posts`). - - `key2` — ключ, визначений в моделі, на яку вказує відношення (у нашому випадку `Comment`). - -`through` може використовуватися з `HAS_ONE`, `BELONGS_TO` та `HAS_MANY`. - -### `HAS_MANY` through - -![HAS_MANY through ER](has_many_through.png) - -Приклад використання `HAS_MANY` з `through` - отримання користувачів, які перебувають у певній групі, -якщо вони записані до групи через ролі. - -Більш складним прикладом є отримання усіх коментарів для всіх користувачів певної групи. -У цьому випадку необхідно використовувати декілька відношень з `through` в одній моделі: - -~~~ -[php] -class Group extends CActiveRecord -{ - ... - public function relations() - { - return array( - 'roles'=>array(self::HAS_MANY,'Role','group_id'), - 'users'=>array(self::HAS_MANY,'User',array('user_id'=>'id'),'through'=>'roles'), - 'comments'=>array(self::HAS_MANY,'Comment',array('id'=>'user_id'),'through'=>'users'), - ); - } -} -~~~ - -#### Приклади - -~~~ -[php] -// отримуємо всі групи з відповідними їм користувачами -$groups=Group::model()->with('users')->findAll(); - -// отримуємо всі групи з відповідними їм користувачами і ролями -$groups=Group::model()->with('roles','users')->findAll(); - -// отримуємо всіх користувачів і ролі для групи з ID, рівним 1 -$group=Group::model()->findByPk(1); -$users=$group->users; -$roles=$group->roles; - -// отримуємо всі коментарі для групи з ID, рівним 1 -$group=Group::model()->findByPk(1); -$comments=$group->comments; -~~~ - - -### `HAS_ONE` through - -![HAS_ONE through ER](has_one_through.png) - -Приклад використання `HAS_ONE` з `through` - отримання адреси користувача у випадку, -якщо користувач звʼязаний з адресою через профіль. -Всі задіяні сутності (користувач, профіль і адреса) мають відповідні їм моделі: - -~~~ -[php] -class User extends CActiveRecord -{ - ... - public function relations() - { - return array( - 'profile'=>array(self::HAS_ONE,'Profile','user_id'), - 'address'=>array(self::HAS_ONE,'Address',array('id'=>'profile_id'),'through'=>'profile'), - ); - } -} -~~~ - -#### Приклади - -~~~ -[php] -// отримуємо адресу користувача з ID, рівним 1 -$user=User::model()->findByPk(1); -$address=$user->address; -~~~ - -### through із собою - -`through` можна використовувати для моделі, звʼязаної із собою через міст. У нашому випадку це користувач, -який навчає інших користувачів: - -![through self ER](through_self.png) - -Відношення для даного випадку визначаються таким чином: - -~~~ -[php] -class User extends CActiveRecord -{ - ... - public function relations() - { - return array( - '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' - ), - ); - } -} -~~~ - -#### Приклади - -~~~ -[php] -// отримуємо всіх студентів вчителя з ID, рівним 1 -$teacher=User::model()->findByPk(1); -$students=$teacher->students; -~~~ +Реляційна Active Record +======================= + +Ми вже розглянули використання Active Record (AR) для вибору даних з однієї таблиці бази даних. +У цьому розділі ми розповімо, як використовувати AR для обʼєднання декількох +звʼязаних таблиць і отримати обʼєднаний набір даних. + +Для використання реляційної AR рекомендується щоб усі звʼязки відношення +первинний-зовнішній ключ були чітко визначені для таблиць, що обʼєднуються. +Це допомагає підтримувати звʼязність та цілісність даних. + +Для наочності прикладів у даному розділі ми будемо використовувати схему бази даних, +представлену на цій діаграмі сутність-відношення (ER). + +![Діаграма ER](er.png) + +> Info|Інформація: Підтримка обмежень по зовнішньому ключу різна у різних СУБД. +SQLite 3.6.19 та більш ранні версії не підтримують обмежень, але ви, тим не менш, +можете їх оголосити при створенні таблиць. Движок MySQL MyISAM не підтримує зовнішні ключі. + +Оголошення відношення +--------------------- + +Перед тим, як використовувати AR для реляційних запитів, нам необхідно пояснити AR, +як AR-класи звʼязані один з одним. + +Відношення між двома AR-класами безпосередньо залежить від відносин між відповідними таблицями бази даних. +З точки зору БД, відношення між таблицями A і В може бути трьох типів: +один-до-багатьох (наприклад, `tbl_user` і `tbl_post`), +один-до-одного (наприклад, `tbl_user` і `tbl_profile`) +і багато-до-багатьох (наприклад, `tbl_category` і `tbl_post`). +У AR існує чотири типи відношень: + + - `BELONGS_TO`: якщо відношення між А і В один-до-багатьох, значить В належить А (наприклад, `Post` належить `User`); + + - `HAS_MANY`: якщо відношення між таблицями А і В один-до-багатьох, значить у А є багато В (наприклад, у `User` є багато `Post`); + + - `HAS_ONE`: це окремий випадок `HAS_MANY`, де А може мати максимум одне В (наприклад, у `User` є тільки один `Profile`); + + - `MANY_MANY`: це відношення відповідає типу відношення багато-до-багатьох в БД. +Оскільки багато СУБД не підтримують безпосередньо тип відношення багато-до-багатьох, +потрібно асоційована таблиця для перетворення відношення багато-до-багатьох у відносини один-до-багатьох. +У нашій схемі бази даних, для цієї мети є таблиця `tbl_post_category`. +У термінології AR ставлення `MANY_MANY` можна описати як комбінацію `BELONGS_TO` та `HAS_MANY`. +Наприклад, `Post` належить багатьом `Category`, а у `Category` є багато `Post`. + +Існує пʼятий спеціальний тип, який виконує агреговані запити на звʼязаних записах - він називається `STAT`. +Зверніться, будь ласка, до розділу [Статистичний запит](/doc/guide/database.arr#statistical-query) за деталями. + +Оголошуючи відношення в AR, ми перевизначаємо метод [relations()|CActiveRecord::relations] класу [CActiveRecord]. +Цей метод повертає масив з конфігурацією відношень. Кожен елемент масиву представляє один звʼязок у наступному форматі: + +~~~ +[php] +'VarName'=>array('RelationType', 'ClassName', 'ForeignKey', …додаткові параметри) +~~~ + +де `VarName` - імʼя відношення, `RelationType` вказує на один із чотирьох типів відношення, +`ClassName` - імʼя AR-класу, звʼязаного з даним AR-класом, +а `ForeignKey` вказує один або кілька зовнішніх ключів, які використовуються для звʼязку. +Крім того, можна вказати ряд додаткових параметрів, про які розповімо трохи пізніше. + +У коді нижче показано, як оголосити відношення між класами `User` і `Post`. + +~~~ +[php] +class Post extends CActiveRecord +{ + … + public function relations() + { + return array( + 'author'=>array(self::BELONGS_TO, 'User', 'author_id'), + 'categories'=>array(self::MANY_MANY, 'Category', + 'tbl_post_category(post_id, category_id)'), + ); + } +} + +class User extends CActiveRecord +{ + … + public function relations() + { + return array( + 'posts'=>array(self::HAS_MANY, 'Post', 'author_id'), + 'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'), + ); + } +} +~~~ + +> Info|Інформація: Зовнішній ключ може бути складеним, тобто складатися з двох і більше стовпців. +У даному випадку імена стовпців слід розділити комами і передати їх або у якості рядка, +або у вигляді масиву `array('key1','key2')`. Якщо потрібно вказати свій звʼязок первинного ключа із зовнішнім, +задати її можна у вигляді `array('fk'=>'pk')`. Для складених ключів це буде `array('fk_c1'=>'pk_c1','fk_c2'=>'pk_c2')`. +Для типу відношення `MANY_MANY` імʼя асоціативної таблиці також повинно бути зазначено в зовнішньому ключі. +Наприклад, відношення `categories` у моделі `Post` позначено зовнішнім ключем `tbl_post_category(post_id, category_id)`. +При оголошенні відношення в AR-класі для кожного відношення у клас неявно додається властивість. +Після виконання реляційного запиту відповідна властивість буде заповнена звʼязаним(-и) екземпляром(-ами) AR. +Наприклад, якщо `$author` представляє AR-екземпляр `User`, +то можна використовувати `$author->posts` для доступу до звʼязаних екземплярів `Post`. + +Виконання реляційного запиту +---------------------------- + +Найпростіший спосіб виконати реляційний запит — зчитати реляційну властивість AR-класу. +Якщо раніше до цієї властивості ніхто не звертався, то буде ініційований реляційний запит, +який зʼєднає звʼязані таблиці і залишить тільки дані, що відповідають первинному ключу поточного екземпляра AR. +Результат запиту буде збережено у властивості як екземпляр(-и) звʼязаного класу. Цей підхід також відомий, +як «відкладене завантаження» (lazy loading), тобто реляційний запит здійснюється +тільки у момент першого звернення до звʼязаних обʼєктів. Приклад нижче показує використання цього підходу: + +~~~ +[php] +// отримуємо запис з ID=10 +$post=Post::model()->findByPk(10); +// отримуємо автора запису. Тут буде виконаний реляційний запит. +$author=$post->author; +~~~ + +> Info|Інформація: Якщо для відношення не існує звʼязаного екземпляра, +то відповідна властивість буде null для відношень `BELONGS_TO` та `HAS_ONE` +або порожнім масивом для `HAS_MANY` та `MANY_MANY`. +Варто зазначити, що відношення `HAS_MANY` та `MANY_MANY` повертають масиви обʼєктів +і звертатися до їх властивостей необхідно в циклі, +інакше можна отримати помилку «Trying to get property of non-object». + +Спосіб відкладеного завантаження зручний, але не завжди ефективний. +Наприклад, якщо ми захочемо отримати інформацію про автора `N` записів, +використання відкладеного завантаження вимагатиме виконання `N` запитів для обʼєднання. +У даній ситуації, нам допоможе метод «жадібного завантаження» (eager loading). + +Цей підхід полягає у завантаженні всіх звʼязаних екземплярів AR одночасно з основним екземпляром AR. +Реалізується цей підхід шляхом використання в AR методу [with()|CActiveRecord::with] +у звʼязці із методом [find|CActiveRecord::find] або [findAll|CActiveRecord::findAll]. Наприклад: + +~~~ +[php] +$posts=Post::model()->with('author')->findAll(); +~~~ + +Код вище поверне масив екземплярів `Post`. +На відміну від відкладеного завантаження, властивість `author` у кожного запису +заповнено звʼязаним екземпляром `User` ще до звернення до цієї властивості. +Таким чином, замість виконання окремого запиту для кожного запису, +жадібне завантаження отримає всі записи разом із їх авторами у одному запиті! + +У методі [with()|CActiveRecord::with] можна вказати безліч імен відношень +і жадібне завантаження поверне їх за один раз. +Наприклад, наступний код поверне записи разом з їх авторами і категоріями: + +~~~ +[php] +$posts=Post::model()->with('author','categories')->findAll(); +~~~ + +Крім того, можна здійснювати вкладене жадібне завантаження. +Для цього замість простого списку імен відношень, +ми передаємо методу [with()|CActiveRecord::with] імена відношень, +упорядкованих ієрархічно, як у прикладі нижче: + +~~~ +[php] +$posts=Post::model()->with( + 'author.profile', + 'author.posts', + 'categories')->findAll(); +~~~ + +Приклад вище поверне нам всі записи з їх авторами і категоріями, а також профіль кожного автора і всі його записи. + +Жадібне завантаження може бути виконане шляхом зазначення властивості [CDbCriteria::with]: + +~~~ +[php] +$criteria=new CDbCriteria; +$criteria->with=array( + 'author.profile', + 'author.posts', + 'categories', +); +$posts=Post::model()->findAll($criteria); +~~~ + +або + +~~~ +[php] +$posts=Post::model()->findAll(array( + 'with'=>array( + 'author.profile', + 'author.posts', + 'categories', + ) +)); +~~~ + +Реляційний запит без отримання звʼязаних моделей +------------------------------------------------ + +Іноді потрібно виконати запит із використанням відношень, але, при цьому, не потрібні дані із звʼязаної моделі. +Припустимо, є користувачі (`User`), які публікують безліч записів (`Post`). +Запис може бути опублікований, а може бути чернеткою. +Цей факт визначається полем моделі `published`. Нам необхідно отримати всіх користувачів, +які опублікували хоча-б один запис. При цьому самі записи нам не цікаві. Зробити це можна так: + +~~~ +[php] +$users=User::model()->with(array( + 'posts'=>array( + // записи нам не потрібні + 'select'=>false, + // але потрібно вибрати тільки користувачів з опублікованими записами + 'joinType'=>'INNER JOIN', + 'condition'=>'posts.published=1', + ), +))->findAll(); +~~~ + + +Параметри реляційного запиту +---------------------------- + +Вище ми згадували про те, що в реляційному запиті можна вказати додаткові параметри. +Ці параметри — пари імʼя-значення — використовуються для тонкого налаштування реляційного запиту. +Перелік параметрів представлений нижче. + + - `select`: список обраних полів для звʼязаного AR-класу. За замовчуванням значення параметра дорівнює '*', +тобто вибираються всі поля таблиці. Для використовуваних стовпців повинні бути вирішені конфлікти імен; + + - `condition`: відповідає оператору `WHERE`, за замовчуванням значення параметра порожнє. +Для використовуваних стовпців повинні бути вирішені конфлікти імен; + + - `params`: параметри для звʼязування у генерованому SQL-виразу. Параметри передаються як масив пар імʼя-значення; + + - `on`: відповідає оператору `ON`. Умова, що визначене в цьому параметрі, +буде додано до умови обʼєднання з використанням оператора `AND`. +Для використовуваних стовпців повинні бути вирішені конфлікти імен. +Даний параметр непридатний для відношень типу `MANY_MANY`; + + - `order`: відповідає оператору `ORDER BY`, за замовчуванням значення параметра порожнє. +Для використовуваних стовпців повинні бути вирішені конфлікти імен; + + - `with`: перелік дочірніх звʼязаних обʼєктів, які повинні бути завантажені із самим обʼєктом. +Неправильне використання даної можливості може призвести до нескінченного циклу; + + - `joinType`: тип обʼєднання для відношень. За замовчуванням значення параметра дорівнює `LEFT OUTER JOIN`; + + - `alias`: псевдонім таблиці, асоційованої з відношенням. За замовчуванням значення параметра дорівнює null, +що означає, що псевдонім відповідає імені відношення; + + - `together`: параметр, який встановлює необхідність примусового обʼєднання таблиці, +асоційованої з цим відношенням, з іншими таблицями. +Цей параметр має сенс тільки для відношень типів `HAS_MANY` та `MANY_MANY`. +Якщо параметр не встановлений або дорівнює `false`, тоді кожне відношення `HAS_MANY` або `MANY_MANY` +буде використовувати окремий SQL запит для звʼязаних даних, що може поліпшити швидкість виконання запиту, +так як зменшується кількість даних, що вибирається. Якщо цей параметр дорівнює `true`, +залежна таблиця при запиті буде завжди обʼєднуватися з основною, тобто буде зроблено один запит навіть у тому випадку, +якщо до основної таблиці застосовується посторінкова розбивка. +Якщо цей параметр не заданий, залежна таблиця буде обʼєднана з основною тільки у разі, +коли не до основної таблиці не застосовується посторінкова розбивка. +Більш докладний опис можна знайти в розділі «продуктивність реляційного запиту»; + + - `join`: додатковий оператор `JOIN`. За замовчуванням порожній. Цей параметр доступний з версії 1.1.3; + + - `group`: відповідає оператору `GROUP BY`, за замовчуванням значення параметра порожнє. +Для використовуваних стовпців повинні бути вирішені конфлікти імен; + + - `having`: відповідає оператору `HAVING`, за замовчуванням значення параметра порожнє. +Для використовуваних стовпців повинні бути вирішені конфлікти імен; + + - `index`: імʼя стовпця, значення якого повинні бути використані в якості ключів масиву, +що зберігає звʼязані обʼєкти. Без установки цього параметра, масив звʼязаних обʼєктів використовує цілочисельний індекс, +що починається з нуля. Параметр може бути встановлений тільки для відношень `HAS_MANY` та `MANY_MANY`; + + - `scopes`: групи умов, які необхідно застосувати. +У випадку однієї групи може використовуватися як `'scopes'=>'scopeName'`. +Якщо ж груп кілька, то використовується як `'scopes'=>array('scopeName1','scopeName2')`. +Цей параметр доступний з версії 1.1.9. + +Крім того, для відкладеного завантаження деяких типів відношень доступний ряд додаткових параметрів: + + - `limit`: параметр для обмеження кількості рядків у вибірці. Параметр не можна застосувати для відношень `BELONGS_TO`; + + - `offset`: параметр для зазначення початкового рядка вибірки. Параметр не можна застосувати для відношень `BELONGS_TO`; + + - `through`: імʼя відношення моделі, яке при отриманні даних буде використовуватися як міст. +Параметр може бути встановлений тільки для відношень `HAS_ONE` та `HAS_MANY`. +Цей параметр доступний з версії 1.1.7, у якій можна застосовувати його до `HAS_ONE` та `HAS_MANY`. +Починаючи з версії 1.1.14, він може використовуватися із `BELONGS_TO`. + +Нижче ми змінимо визначення відношення `posts` у моделі `User`, додавши кілька вищенаведених параметрів: + +~~~ +[php] +class User extends CActiveRecord +{ + public function relations() + { + return array( + 'posts'=>array(self::HAS_MANY, 'Post', 'author_id', + 'order'=>'posts.create_time DESC', + 'with'=>'categories'), + 'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'), + ); + } +} +~~~ + +Тепер при зверненні до `$author->posts`, ми отримаємо записи автора, +відсортовані в зворотньому порядку за часом їх створення. Для кожного запису будуть завантажені її категорії. + +Усунення конфлікту імен стовпців +-------------------------------- + +При співпаданні імен стовпців у двох і більше зʼєднуваних таблиць, доводиться вирішувати конфлікт імен. +Це робиться за допомогою додавання псевдоніма таблиці до імені стовпця. + +У реляційному запиті псевдонім головної таблиці завжди дорівнює `t`, а імʼя псевдоніма таблиці, +що відноситься до неї, за замовчуванням відповідає імені відношення. +Наприклад, у коді нижче псевдоніми для `Post` та `Comment` відповідно `t` і `comments`: + +~~~ +[php] +$posts=Post::model()->with('comments')->findAll(); +~~~ + +Припустимо, що і в `Post` і в `Comment` є стовпець `create_time`, +у якому зберігається час створення запису або коментаря, і нам необхідно отримати записи разом з коментарями до них, +відсортовані спочатку за часом створення запису, а потім за часом написання коментаря. +Для цього нам знадобиться усунути конфлікт стовпців `create_time` наступним чином: + +~~~ +[php] +$posts=Post::model()->with('comments')->findAll(array( + 'order'=>'t.create_time, comments.create_time' +)); +~~~ + +> Tip|Підказка: Псевдонім таблиці звʼязку за замовчуванням дорівнює назві самого звʼязку. +> Майте на увазі, що при використанні одного звʼязку всередині іншого буде використано назву +> останнього із них. При цьому назва батьківського звʼязку не буде використана у якості префікса. +> Наприклад, псевдонімом звʼязку 'author.group' є 'group', а не 'author.group'. +> +> ~~~ +> [php] +> $posts=Post::model()->with('author', 'author.group')->findAll(array( +> 'order'=>'group.name, author.name, t.title' +> )); +> ~~~ +> +> Ви можете уникнути конфлікту псевдонімів таблиць задавши властивість звʼязку [alias|CActiveRelation::alias]. +> +> ~~~ +> [php] +> $comments=Comment::model()->with( +> 'author', +> 'post', +> 'post.author'=>array('alias'=>'p_author'))->findAll(array( +> 'order'=>'author.name, p_author.name, post.title' +> )); +> ~~~ + +Динамічні параметри реляційного запиту +-------------------------------------- + +Ми можемо використовувати динамічні параметри як для методу [with()|CActiveRecord::with], так і для параметра `with`. +Динамічні параметри перевизначають існуючі параметри відповідно до опису метода [relations()|CActiveRecord::relations]. +Наприклад, якщо для моделі `User`, наведеної вище, ми хочемо скористатися жадібним завантаженням +для отримання записів автора у порядку зростання (параметр `order` у визначенні відношення задає регресний порядок), +можна зробити це таким чином: + +~~~ +[php] +User::model()->with(array( + 'posts'=>array('order'=>'posts.create_time ASC'), + 'profile', +))->findAll(); +~~~ + +Динамічні параметри в реляційних запитах можна використовувати разом з відкладеним завантаженням. +Для цього необхідно викликати метод з тим же імʼям, що й імʼя звʼязку, і передати параметри як його аргумент. +Наприклад, наступний код поверне публікації користувача, у яких `status` дорівнює 1: + +~~~ +[php] +$user=User::model()->findByPk(1); +$posts=$user->posts(array('condition'=>'status=1')); +~~~ + +Продуктивність реляційного запиту +--------------------------------- + +Як було описано вище, жадібне завантаження використовується, головним чином, +коли потрібно отримати безліч звʼязаних обʼєктів. +У цьому випадку зʼєднанням усіх таблиць генерується великий складний SQL-запит. +Такий запит у багатьох випадках є кращим, так як спрощує фільтрацію за значенням стовпця звʼязаної таблиці. +Тим не менш, в деяких випадках такі запити не є ефективними. + +Розглянемо приклад, у якому нам потрібно знайти нові записи разом з їх коментарями. +Враховуючи, що у кожному записі 10 коментарів, +при використанні одного великого SQL-запиту ми отримаємо безліч зайвих +даних так як кожен запис буде повторно вибиратися з кожним її коментарем. +Тепер спробуємо по-іншому: спочатку виберемо останні записи, а потім коментарі до них. +У даному випадку нам необхідно виконати два SQL запити. Плюс в тому, що в отриманих даних не буде нічого зайвого. + +То який підхід більш ефективний? Абсолютно вірної відповіді на це питання немає. +Виконання одного великого SQL запиту може бути більш ефективним так як СУБД не доводиться +зайвий раз розбирати і виконувати додаткові запити. З іншого боку, використовуючи один SQL запит, +ми отримуємо більше зайвих даних і відповідно нам потрібно більше часу на їх передачу і обробку. +За замовчуванням Yii використовує "жадібне" завантаження, тобто генерує один SQL запит, крім того випадку, +коли до головної моделі застосовується `LIMIT`. Якщо виставити опцію `together` в описі відношення в `true`, +то ми отримаємо єдиний SQL запит навіть якщо використовується `LIMIT`. Якщо використовувати `false`, +то вибірка з деяких таблиць проводитиметься окремими запитами. +Приміром, для того, щоб використовувати окремі SQL запити для вибірки останніх записів і коментарів до них, +відношення `comments` моделі `Post` слід описати таким чином: + +~~~ +[php] +public function relations() +{ + return array( + 'comments' => array(self::HAS_MANY, 'Comment', 'post_id', 'together'=>false), + ); +} +~~~ + +Для жадібного завантаження ми можемо задати цю опцію динамічно: + +~~~ +[php] +$posts = Post::model()->with(array('comments'=>array('together'=>false)))->findAll(); +~~~ + +Статистичний запит +------------------ + +Крім реляційних запитів, описаних вище, Yii також підтримує так звані статистичні запити (або запити агрегування). +Цей тип запитів використовується для отримання агрегованих даних, що відносяться до звʼязаних обʼєктів, +наприклад кількість коментарів до кожного запису, середній рейтинг для кожного найменування продукції і т.д. +Статистичні запити можуть бути використані тільки для обʼєктів, +звʼязаних відношеннями `HAS_MANY` (наприклад, у запису є багато коментарів) +або `MANY_MANY` (наприклад, запис належить багатьом категоріям, +а категорія може відноситися ставитися до безлічі записів). + +Виконання статистичного запиту аналогічно виконання реляційного запиту відповідно до опису вище. +Передусім необхідно оголосити статистичний запит у методі [relations()|CActiveRecord::relations] класа [CActiveRecord]. + +~~~ +[php] +class Post extends CActiveRecord +{ + public function relations() + { + return array( + 'commentCount'=>array(self::STAT, 'Comment', 'post_id'), + 'categoryCount'=>array(self::STAT, 'Category', 'post_category(post_id, category_id)'), + ); + } +} +~~~ + +Вище ми оголосили два статистичних запита: `commentCount` підраховує кількість коментарів до запису, +а `categoryCount` рахує кількість категорій, до яких відноситься запис. +Зверніть увагу, що відношення між `Post` і `Comment` - типу `HAS_MANY`, +а відношення між `Post` і `Category` - типу `MANY_MANY` (з використанням перетворюючої таблиці `post_category`). +Як можна бачити, порядок оголошення дуже схожий з оголошенням відношень, описаних вище. +Єдина відмінність полягає у тому, що у даному випадку тип відношення дорівнює `STAT`. + +За рахунок оголошених відношень ми можемо отримати кількість коментарів для запису, +використовуючи вираз `$post->commentCount`. +У момент першого звернення до даної властивості для отримання відповідного результату +неявним чином виконується SQL-вираз. Як ми вже говорили, це називається підходом *відкладеного завантаження*. +Можна також використовувати *жадібний* варіант завантаження, +якщо необхідно отримати кількість коментарів до кількох записів: + +~~~ +[php] +$posts=Post::model()->with('commentCount', 'categoryCount')->findAll(); +~~~ + +Вираз вище виконує три SQL-запити для отримання всіх записів разом із значеннями кількості +коментарів до них і кількості категорій. +У разі відкладеного завантаження нам би знадобилося виконати `2*N+1` SQL-запитів для `N` записів. + +За замовчуванням статистичний запит рахує кількість з використанням виразу `COUNT`. +Його можна уточнити шляхом зазначення додаткових параметрів у момент оголошення в методі +[relations()|CActiveRecord::relations]. Доступні параметри перераховані нижче: + + - `select`: статистичний вираз, за замовчуванням дорівнює `COUNT(*)`, що відповідає кількості дочірніх обʼєктів; + + - `defaultValue`: значення, яке надається у випадку, якщо результат статистичного запиту для запису відʼємний. +Наприклад, якщо запис не має жодного коментаря, то властивості `commentCount` буде присвоєно це значення. +За замовчуванням значення даного параметра дорівнює 0; + + - `condition`: відповідає оператору `WHERE`, за замовчуванням значення параметра порожнє; + + - `params`: параметри для звʼязування у генерованому SQL-виразі. Параметри передаються як масив пар імʼя-значення; + + - `order`: відповідає оператору `ORDER BY`, за замовчуванням значення параметра порожнє; + + - `group`: відповідає оператору `GROUP BY`, за замовчуванням значення параметра порожнє; + + - `having`: відповідає оператору `HAVING`, за замовчуванням значення параметра порожнє. + +Реляційні запити з іменованими групами умов +------------------------------------------- + +У реляційному запиті [іменовані групи умов](/doc/guide/database.ar#named-scopes) можуть бути використані двома способами. +Їх можна застосувати до основної моделі і до звʼязаних моделей. + +Наступний код показує роботу з основною моделлю: + +~~~ +[php] +$posts=Post::model()->published()->recently()->with('comments')->findAll(); +~~~ + +Даний код дуже схожий на нереляційні запити. Єдина відмінність у тому, +що у нас присутній виклик `with()` після викликів груп умов. +Даний запит поверне недавно опубліковані записи разом з коментарями до них. + +У наступному прикладі показано, як застосувати групи умов до звʼязаних моделей: + +~~~ +[php] +$posts=Post::model()->with('comments:recently:approved')->findAll(); +// або, починаючи з версії 1.1.7 +$posts=Post::model()->with(array( + 'comments'=>array( + 'scopes'=>array('recently','approved') + ), +))->findAll(); +// або, починаючи з версії 1.1.7 +$posts=Post::model()->findAll(array( + 'with'=>array( + 'comments'=>array( + 'scopes'=>array('recently','approved') + ), + ), +)); +~~~ + +Цей запит поверне всі записи разом із ухваленими коментарями. Тут `comments` відноситься до імені відношення. +`recently` та `approved` - іменовані групи, описані у моделі `Comment`. +Імʼя відношення і групи параметрів розділяються двокрапкою. + +Вам може знадобитися використовувати замість жадібної вибірки відкладену для +звʼязку із групою умов. Синтаксис для цього такий: + +~~~ +[php] +// імʼя звʼязку comments повторюється два рази +$approvedComments = $post->comments('comments:approved'); +~~~ + +Іменовані групи можуть бути використані при описі відношень моделі у +методі [CActiveRecord::relations()] у параметрі `with`. +У наступному прикладі при зверненні до `$user->posts` разом з публікаціями будуть отримані всі *ухвалені* коментарі. + +~~~ +[php] +class User extends CActiveRecord +{ + public function relations() + { + return array( + 'posts'=>array(self::HAS_MANY, 'Post', 'author_id', + 'with'=>'comments:approved'), + ); + } +} +// або, починаючи з версії 1.1.7 +class User extends CActiveRecord +{ + public function relations() + { + return array( + 'posts'=>array(self::HAS_MANY, 'Post', 'author_id', + 'with'=>array( + 'comments'=>array( + 'scopes'=>'approved' + ), + ), + ), + ); + } +} +~~~ + + +З версії 1.1.7 стало можливо передати параметри іменованим групам умов відношення. +Приміром, якщо у `Post` є іменована група умов `rated`, яка приймає мінімальний рейтинг запису, +використовувати її у `User` можна так: + +> Note|Примітка: до 1.1.7 іменовані групи умов, що застосовуються до реляційних моделей, +повинні бути описані в CActiveRecord::scopes. Тому вони не можуть бути параметризовані. + +~~~ +[php] +$users=User::model()->findAll(array( + 'with'=>array( + 'posts'=>array( + 'scopes'=>array( + 'rated'=>5, + ), + ), + ), +)); + +class Post extends CActiveRecord +{ + ...... + + public function rated($rating) + { + $this->getDbCriteria()->mergeWith(array( + 'condition'=>'rating=:rating', + 'params'=>array(':rating'=>$rating), + )); + return $this; + } + + ...... +} +~~~ + +Реляційні запити з through +-------------------------- + +При використанні `through` визначення відношення має виглядати наступним чином: + +~~~ +[php] +'comments'=>array(self::HAS_MANY,'Comment',array('key1'=>'key2'),'through'=>'posts'), +~~~ + +У коді вище, а саме у `array('key1'=>'key2')`: + + - `key1` — ключ, визначений у відношенні, на яке вказує `through` (у нашому випадку `posts`). + - `key2` — ключ, визначений в моделі, на яку вказує відношення (у нашому випадку `Comment`). + +`through` може використовуватися з `HAS_ONE`, `BELONGS_TO` та `HAS_MANY`. + +### `HAS_MANY` through + +![HAS_MANY through ER](has_many_through.png) + +Приклад використання `HAS_MANY` з `through` - отримання користувачів, які перебувають у певній групі, +якщо вони записані до групи через ролі. + +Більш складним прикладом є отримання усіх коментарів для всіх користувачів певної групи. +У цьому випадку необхідно використовувати декілька відношень з `through` в одній моделі: + +~~~ +[php] +class Group extends CActiveRecord +{ + ... + public function relations() + { + return array( + 'roles'=>array(self::HAS_MANY,'Role','group_id'), + 'users'=>array(self::HAS_MANY,'User',array('user_id'=>'id'),'through'=>'roles'), + 'comments'=>array(self::HAS_MANY,'Comment',array('id'=>'user_id'),'through'=>'users'), + ); + } +} +~~~ + +#### Приклади + +~~~ +[php] +// отримуємо всі групи з відповідними їм користувачами +$groups=Group::model()->with('users')->findAll(); + +// отримуємо всі групи з відповідними їм користувачами і ролями +$groups=Group::model()->with('roles','users')->findAll(); + +// отримуємо всіх користувачів і ролі для групи з ID, рівним 1 +$group=Group::model()->findByPk(1); +$users=$group->users; +$roles=$group->roles; + +// отримуємо всі коментарі для групи з ID, рівним 1 +$group=Group::model()->findByPk(1); +$comments=$group->comments; +~~~ + + +### `HAS_ONE` through + +![HAS_ONE through ER](has_one_through.png) + +Приклад використання `HAS_ONE` з `through` - отримання адреси користувача у випадку, +якщо користувач звʼязаний з адресою через профіль. +Всі задіяні сутності (користувач, профіль і адреса) мають відповідні їм моделі: + +~~~ +[php] +class User extends CActiveRecord +{ + ... + public function relations() + { + return array( + 'profile'=>array(self::HAS_ONE,'Profile','user_id'), + 'address'=>array(self::HAS_ONE,'Address',array('id'=>'profile_id'),'through'=>'profile'), + ); + } +} +~~~ + +#### Приклади + +~~~ +[php] +// отримуємо адресу користувача з ID, рівним 1 +$user=User::model()->findByPk(1); +$address=$user->address; +~~~ + +### through із собою + +`through` можна використовувати для моделі, звʼязаної із собою через міст. У нашому випадку це користувач, +який навчає інших користувачів: + +![through self ER](through_self.png) + +Відношення для даного випадку визначаються таким чином: + +~~~ +[php] +class User extends CActiveRecord +{ + ... + public function relations() + { + return array( + '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' + ), + ); + } +} +~~~ + +#### Приклади + +~~~ +[php] +// отримуємо всіх студентів вчителя з ID, рівним 1 +$teacher=User::model()->findByPk(1); +$students=$teacher->students; +~~~ diff --git a/docs/guide/uk/index.txt b/docs/guide/uk/index.txt index e84e73f59..100447cce 100644 --- a/docs/guide/uk/index.txt +++ b/docs/guide/uk/index.txt @@ -1,12 +1,12 @@ -Повний посібник по Yii -====================== - -Даний посібник випущено відповідно до [положень про документацію Yii](http://www.yiiframework.com/doc/terms/). - -Перекладачі ------------ -- Олександр Бордун, Borales ([yiiframework.com.ua](http://yiiframework.com.ua/)) -- Мирослав Демчунь ([chandler.org.ua](http://chandler.org.ua/)) -- Віталій Степаненко, vitaliy.step - -© 2008—2013, Yii Software LLC. +Повний посібник по Yii +====================== + +Даний посібник випущено відповідно до [положень про документацію Yii](http://www.yiiframework.com/doc/terms/). + +Перекладачі +----------- +- Олександр Бордун, Borales ([yiiframework.com.ua](http://yiiframework.com.ua/)) +- Мирослав Демчунь ([chandler.org.ua](http://chandler.org.ua/)) +- Віталій Степаненко, vitaliy.step + +© 2008—2013, Yii Software LLC. diff --git a/docs/guide/uk/quickstart.apache-nginx-config.txt b/docs/guide/uk/quickstart.apache-nginx-config.txt index a2a3dd6e4..39dc909d9 100644 --- a/docs/guide/uk/quickstart.apache-nginx-config.txt +++ b/docs/guide/uk/quickstart.apache-nginx-config.txt @@ -1,89 +1,89 @@ -Конфігурація веб-серверів Apache та Nginx -========================================= - -Apache ------- - -Yii готовий до роботи із налаштуваннями Apache за замовчуванням. Файли -`.htaccess` у фреймворку та директоріях додатку обмежують доступ до -деяких ресурсів. Для приховання файла точки входу (звичайно це `index.php`) в -URL можна додати інструкцію для модуля `mod_rewrite` у файл `.htaccess` -в кореневій директорії додатку чи у налаштуваннях віртуальних хостів: - -~~~ -RewriteEngine on - -# не дозволяти httpd віддавати файли, що починаються із крапки (.htaccess, .svn, .git та інші) -RedirectMatch 403 /\..*$ -# якщо директорія або файл існують, використовувати їх напряму -RewriteCond %{REQUEST_FILENAME} !-f -RewriteCond %{REQUEST_FILENAME} !-d -# інакше відправляти запит на файл index.php -RewriteRule . index.php -~~~ - - -Nginx ------ - -Yii можна використовувати із веб-сервером [Nginx](http://wiki.nginx.org/) та PHP за -допомогою [FPM SAPI](http://php.net/install.fpm). Нижче наведений приклад простої -конфігурації хоста. Він визначає файл точки входу та змушує Yii -перехоплювати всі запити до неіснуючих файлів, що дозволяє створювати -людинозрозумілі URL-адреси. - -~~~ -server { - set $host_path "/www/mysite"; - access_log /www/mysite/log/access.log main; - - server_name mysite; - root $host_path/htdocs; - set $yii_bootstrap "index.php"; - - charset utf-8; - - location / { - index index.html $yii_bootstrap; - try_files $uri $uri/ /$yii_bootstrap?$args; - } - - location ~ ^/(protected|framework|themes/\w+/views) { - deny all; - } - - # відключаємо обробку запитів фреймворком до неіснуючих статичних файлів - location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { - try_files $uri =404; - } - - # передаємо PHP-скрипт серверу FastCGI, який прослуховує адресу 127.0.0.1:9000 - # - location ~ \.php { - fastcgi_split_path_info ^(.+\.php)(.*)$; - - # дозволяємо yii перехоплювати запити до неіснуючих PHP-файлів - set $fsn /$yii_bootstrap; - if (-f $document_root$fastcgi_script_name){ - set $fsn $fastcgi_script_name; - } - - fastcgi_pass 127.0.0.1:9000; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fsn; - - #PATH_INFO та PATH_TRANSLATED можуть бути опущені, але стандарт RFC 3875 визначає для CGI - fastcgi_param PATH_INFO $fastcgi_path_info; - fastcgi_param PATH_TRANSLATED $document_root$fsn; - } - - # не дозволяти nginx віддавати файли, що починаються із крапки (.htaccess, .svn, .git та інші) - location ~ /\. { - deny all; - access_log off; - log_not_found off; - } -} -~~~ -Використовуючи дану конфігурацію, можна у файлі `php.ini` встановити опцію +Конфігурація веб-серверів Apache та Nginx +========================================= + +Apache +------ + +Yii готовий до роботи із налаштуваннями Apache за замовчуванням. Файли +`.htaccess` у фреймворку та директоріях додатку обмежують доступ до +деяких ресурсів. Для приховання файла точки входу (звичайно це `index.php`) в +URL можна додати інструкцію для модуля `mod_rewrite` у файл `.htaccess` +в кореневій директорії додатку чи у налаштуваннях віртуальних хостів: + +~~~ +RewriteEngine on + +# не дозволяти httpd віддавати файли, що починаються із крапки (.htaccess, .svn, .git та інші) +RedirectMatch 403 /\..*$ +# якщо директорія або файл існують, використовувати їх напряму +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +# інакше відправляти запит на файл index.php +RewriteRule . index.php +~~~ + + +Nginx +----- + +Yii можна використовувати із веб-сервером [Nginx](http://wiki.nginx.org/) та PHP за +допомогою [FPM SAPI](http://php.net/install.fpm). Нижче наведений приклад простої +конфігурації хоста. Він визначає файл точки входу та змушує Yii +перехоплювати всі запити до неіснуючих файлів, що дозволяє створювати +людинозрозумілі URL-адреси. + +~~~ +server { + set $host_path "/www/mysite"; + access_log /www/mysite/log/access.log main; + + server_name mysite; + root $host_path/htdocs; + set $yii_bootstrap "index.php"; + + charset utf-8; + + location / { + index index.html $yii_bootstrap; + try_files $uri $uri/ /$yii_bootstrap?$args; + } + + location ~ ^/(protected|framework|themes/\w+/views) { + deny all; + } + + # відключаємо обробку запитів фреймворком до неіснуючих статичних файлів + location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { + try_files $uri =404; + } + + # передаємо PHP-скрипт серверу FastCGI, який прослуховує адресу 127.0.0.1:9000 + # + location ~ \.php { + fastcgi_split_path_info ^(.+\.php)(.*)$; + + # дозволяємо yii перехоплювати запити до неіснуючих PHP-файлів + set $fsn /$yii_bootstrap; + if (-f $document_root$fastcgi_script_name){ + set $fsn $fastcgi_script_name; + } + + fastcgi_pass 127.0.0.1:9000; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fsn; + + #PATH_INFO та PATH_TRANSLATED можуть бути опущені, але стандарт RFC 3875 визначає для CGI + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param PATH_TRANSLATED $document_root$fsn; + } + + # не дозволяти nginx віддавати файли, що починаються із крапки (.htaccess, .svn, .git та інші) + location ~ /\. { + deny all; + access_log off; + log_not_found off; + } +} +~~~ +Використовуючи дану конфігурацію, можна у файлі `php.ini` встановити опцію `cgi.fix_pathinfo=0` щоб уникнути безлічі небажаних системних викликів `stat()`. \ No newline at end of file diff --git a/docs/guide/uk/quickstart.first-app.txt b/docs/guide/uk/quickstart.first-app.txt index 6b25997db..9446de731 100644 --- a/docs/guide/uk/quickstart.first-app.txt +++ b/docs/guide/uk/quickstart.first-app.txt @@ -1,251 +1,251 @@ -Створення першого додатку -========================= - -У цьому розділі ми розкажемо як створити наш перший додаток. -Для створення нового додатку будемо використовувати `yiic` (консольну утиліту), для -генерації коду — `Gii` (потужний веб кодогенератор). Будемо вважати для зручності, -що `YiiRoot` — це директорія, в яку встановлено Yii, а `WebRoot` — коренева -директорія вашего веб-серверу. - -Запускаємо `yiic` у консолі з наступними параметрами: -~~~ -% YiiRoot/framework/yiic webapp WebRoot/testdrive -~~~ -> Note|Примітка: При використанні `yiic` на Mac OS, -> Linux або Unix вам може знадобитися змінити права доступу -> для файла `yiic`, щоб зробити його виконуваним. -> Альтернативний варіант запуску утиліти представлений нижче: -> -> ~~~ -> % cd WebRoot -> % php YiiRoot/framework/yiic.php webapp testdrive -> ~~~ - -В результаті у директорії `WebRoot/testdrive` буде створений каркас додатку. - -Створений додаток — хороша відправна точка для додавання необхідного функціоналу, -так як воно вже містить всі необхідні директорії та файли. -Не написавши жодного рядка коду, ми вже можемо протестувати -наш перший Yii-додаток, перейшовши в браузері за наступним URL: - -~~~ -http://hostname/testdrive/index.php -~~~ - -Додаток містить чотири сторінки: головну, сторінку «про проект», -сторінку зворотнього звʼязку та сторінку авторизації. -Сторінка зворотнього звʼязку містить форму для відправки питань та побажань, а сторінка авторизації -дозволяє користувачу аутентифікуватися та отримати доступ до закритої частини сайту (див. малюнки нижче). - -![Головна сторінка](first-app1.png) - -![Сторінка зворотнього звʼязку](first-app2.png) - -![Сторінка зворотнього звʼязку з помилками вводу](first-app3.png) - -![Сторінка зворотнього звʼязку з успішно відправленою формою](first-app4.png) - -![Сторінка авторизації](first-app5.png) - - -Наш додаток має наступну структуру папок. -Детальний опис цієї структури можна знайти в [угодах](/doc/guide/basics.convention#directory). - -~~~ -testdrive/ - index.php скрипт ініціалізації додатку - index-test.php скрипт ініціалізації функціональних тестів - assets/ містить файли ресурсів - css/ містить CSS-файли - images/ містить зображення - themes/ містить теми оформлення додатку - protected/ містить захищені файли додатку - yiic скрипт yiic - yiic.bat скрипт yiic для Windows - yiic.php PHP-скрипт yiic - commands/ містить команди 'yiic' - shell/ містить команди 'yiic shell' - components/ містить компоненти для повторного використання - Controller.php класс базового контролеру - UserIdentity.php класс 'UserIdentity' для аутентифікації - config/ містить конфігураційні файли - console.php файл конфігурації консолі - main.php файл конфігурації веб-додатку - test.php файл конфігурації функціональних тестів - controllers/ містить файли класів контролерів - SiteController.php класс контролеру за замовчуванням - data/ містить приклад бази даних - schema.mysql.sql схема БД для MySQL - schema.sqlite.sql схема БД для SQLite - testdrive.db файл БД для SQLite - extensions/ містить сторонні розширення - messages/ містить перекладені повідомлення - models/ містить файли класів моделей - LoginForm.php модель форми для дії 'login' - ContactForm.php модель форми для дії 'contact' - runtime/ містить тимчасові файли - tests/ містить тести - views/ містить файли представлень контролерів та файли макетів (layout) - layouts/ містить файли представлень макетів - main.php загальна для всіх сторінок розмітка - column1.php розмітка для сторінок з одною колонкою - column2.php розмітка для сторінок з двома колонками - site/ містить файли представлень для контролеру 'site' - pages/ статичні сторінки - about.php сторінка «про проект» - contact.php файл представлення для дії 'contact' - error.php файл представлення для дії 'error' (відображення помилок) - index.php файл представлення для дії 'index' - login.php файл представлення для дії 'login' -~~~ - -Описаний вище генератор додатку, також підтримує створення необхідних файлів для системи контролю версій Git. -Наступна команда створить необхідні файли `.gitignore` (наприклад, зміст директорій `assets` та `runtime` не повинен відслідковуватися) -та `.gitkeep` (примусово встановлює відстеження початково порожніх, але важливих каталогів): - -~~~ -% YiiRoot/framework/yiic webapp WebRoot/testdrive git -~~~ - -Інша система VCS, що підтримується, це Mercurial: передайте значення `hg` у якості третього параметра, якщо ви використовуєте цю VCS. -Ця функція доступна із версії 1.1.11. - -Зʼєднання з базою даних ------------------------ -Більшість веб-додатків використовують бази даних, і наш додаток не виключення. -Для використання бази даних необхідно пояснити додатку, як до неї підключитися. -Це робиться у конфігураційному файлі `WebRoot/testdrive/protected/config/main.php`. -Наприклад, так: - -~~~ -[php] -return array( - … - 'components'=>array( - … - 'db'=>array( - 'connectionString'=>'sqlite:protected/data/testdrive.db', - ), - ), - … -); -~~~ - -У наведеному вище коді вказано, що додаток повинен підключитися до бази даних SQLite -`WebRoot/testdrive/protected/data/testdrive.db` як тільки це знадобиться. Відмітимо, що -база даних SQLite вже вімкнена до згенерованого додатку. У цій базі є тільки -одна таблиця `tbl_user`: - -~~~ -[sql] -CREATE TABLE tbl_user ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - username VARCHAR(128) NOT NULL, - password VARCHAR(128) NOT NULL, - email VARCHAR(128) NOT NULL -); -~~~ - -Якщо ви бажаєте використовувати базу даних MySQL, ви можете використовувати файл -`WebRoot/testdrive/protected/data/schema.mysql.sql` для її створення. - -> Note|Примітка: Для роботи з базою даних Yii потрібне розширення PHP PDO -і відповідний драйвер PDO. Для тестового додатку необхідно підключити -розширення `php_pdo` та `php_pdo_sqlite`. - -Реалізація операцій CRUD ------------------------- - -А тепер найвеселіше. Ми б хотіли додати операції CRUD -(створення, читання, оновлення та видалениня) для щойно створеної таблиці `tbl_user` — -це часто необхідно при створенні реальних додатків. Замість написання коду ми -скористаємося веб-кодогенератором `Gii`. - -> Info|Інформація: Gii доступний починаючи із версії 1.1.2. Раніше для тих же цілей -> використовувався вже згаданий `yiic`. Детальніше `yiic` описаний у розділі -«[генерація CRUD за допомогою yiic shell](/doc/guide/quickstart.first-app-yiic)». - - -### Налаштування Gii - -Для того, щоб використовувати Gii, необхідно відредагувати -[файл конфігурації додатку](/doc/guide/basics.application#application-configuration) -`WebRoot/testdrive/protected/config/main.php`: - -~~~ -[php] -return array( - … - 'import'=>array( - 'application.models.*', - 'application.components.*', - ), - - 'modules'=>array( - 'gii'=>array( - 'class'=>'system.gii.GiiModule', - 'password'=>'впишіть свій пароль', - ), - ), -); -~~~ - -Після цього перейдіть по URL `http://hostname/testdrive/index.php?r=gii` та -введіть вказаний у конфігурації пароль. - -### Генерація моделі User - -Після входу перейдіть у розділ `Model Generator`: - -![Model Generator](gii-model.png) - -У полі `Table Name` введіть `tbl_user`. У полі `Model Class` — `User`. -Потім натисніть на кнопку `Preview`. Буде показаний новий файл, який буде -згенерировано. Після натискання кнопки `Generate` у `protected/models` буде створений -файл `User.php`. Як буде описано далі у керівництві, клас моделі `User` -дозволяє працювати з даними у таблиці `tbl_user` у стилі ООП. - -### Генерація CRUD - -Після генерації класів моделі ми згенеруємо код, який реалізує для неї операції CRUD. -Обираємо `Crud Generator`: - -![CRUD Generator](gii-crud.png) - -У полі `Model Class` вводимо `User`. У полі `Controller ID` — `user` (у нижньому регістрі). -Тепер натискаємо `Preview` і потім `Generate`. Генерація коду CRUD завершена. - -### Доступ до сторінок CRUD - -Давайте порадіємо нашим зусиллям, та перейдемо по URL: - -~~~ -http://hostname/testdrive/index.php?r=user -~~~ - -Ми бачимо сторінку із спиком користувачів із таблиці `tbl_user`. Оскільки наша таблиця пуста, то записів у ній не буде. -Кликнемо по кнопці `Create User` і, якщо ми ще не авторизовані, відобразиться сторінка авторизації. -Потім завантажиться форма додавання нового користувача. Заповнимо її та натиснемо кнопку `Create`. -Якщо при заповненні форми були допущені помилки, ми побачимо охайне повідомлення про помилку. - -Повертаючись до списку користувачів, ми повинні побачити у списку щойно створенного користувача. -Додамо ще кілька користувачів. Зверніть увагу, що при значній -кількості користувачів для їх відображення на одній сторінці список буде автоматично розбиватися на сторінки. -Авторизувавшись у якості адміністратора (`admin/admin`), -можні побачити сторінку управління користувачами за адресою: - -~~~ -http://hostname/testdrive/index.php?r=user/admin -~~~ - -Зʼявиться охайна таблиця користувачів. Можна натиснути на заголовок -таблиці, щоб впорядкувати записи по значенням відповідного стовпця. -Для перегляду, редагування або видалення відповідного рядка можна -скористатися кнопками. Також можна переходити на різні сторінки, фільтрувати -результати та виконувати пошук по ним. - -Все це не вимагає написання коду! - -![Сторінка управління користувачами](first-app6.png) - +Створення першого додатку +========================= + +У цьому розділі ми розкажемо як створити наш перший додаток. +Для створення нового додатку будемо використовувати `yiic` (консольну утиліту), для +генерації коду — `Gii` (потужний веб кодогенератор). Будемо вважати для зручності, +що `YiiRoot` — це директорія, в яку встановлено Yii, а `WebRoot` — коренева +директорія вашего веб-серверу. + +Запускаємо `yiic` у консолі з наступними параметрами: +~~~ +% YiiRoot/framework/yiic webapp WebRoot/testdrive +~~~ +> Note|Примітка: При використанні `yiic` на Mac OS, +> Linux або Unix вам може знадобитися змінити права доступу +> для файла `yiic`, щоб зробити його виконуваним. +> Альтернативний варіант запуску утиліти представлений нижче: +> +> ~~~ +> % cd WebRoot +> % php YiiRoot/framework/yiic.php webapp testdrive +> ~~~ + +В результаті у директорії `WebRoot/testdrive` буде створений каркас додатку. + +Створений додаток — хороша відправна точка для додавання необхідного функціоналу, +так як воно вже містить всі необхідні директорії та файли. +Не написавши жодного рядка коду, ми вже можемо протестувати +наш перший Yii-додаток, перейшовши в браузері за наступним URL: + +~~~ +http://hostname/testdrive/index.php +~~~ + +Додаток містить чотири сторінки: головну, сторінку «про проект», +сторінку зворотнього звʼязку та сторінку авторизації. +Сторінка зворотнього звʼязку містить форму для відправки питань та побажань, а сторінка авторизації +дозволяє користувачу аутентифікуватися та отримати доступ до закритої частини сайту (див. малюнки нижче). + +![Головна сторінка](first-app1.png) + +![Сторінка зворотнього звʼязку](first-app2.png) + +![Сторінка зворотнього звʼязку з помилками вводу](first-app3.png) + +![Сторінка зворотнього звʼязку з успішно відправленою формою](first-app4.png) + +![Сторінка авторизації](first-app5.png) + + +Наш додаток має наступну структуру папок. +Детальний опис цієї структури можна знайти в [угодах](/doc/guide/basics.convention#directory). + +~~~ +testdrive/ + index.php скрипт ініціалізації додатку + index-test.php скрипт ініціалізації функціональних тестів + assets/ містить файли ресурсів + css/ містить CSS-файли + images/ містить зображення + themes/ містить теми оформлення додатку + protected/ містить захищені файли додатку + yiic скрипт yiic + yiic.bat скрипт yiic для Windows + yiic.php PHP-скрипт yiic + commands/ містить команди 'yiic' + shell/ містить команди 'yiic shell' + components/ містить компоненти для повторного використання + Controller.php класс базового контролеру + UserIdentity.php класс 'UserIdentity' для аутентифікації + config/ містить конфігураційні файли + console.php файл конфігурації консолі + main.php файл конфігурації веб-додатку + test.php файл конфігурації функціональних тестів + controllers/ містить файли класів контролерів + SiteController.php класс контролеру за замовчуванням + data/ містить приклад бази даних + schema.mysql.sql схема БД для MySQL + schema.sqlite.sql схема БД для SQLite + testdrive.db файл БД для SQLite + extensions/ містить сторонні розширення + messages/ містить перекладені повідомлення + models/ містить файли класів моделей + LoginForm.php модель форми для дії 'login' + ContactForm.php модель форми для дії 'contact' + runtime/ містить тимчасові файли + tests/ містить тести + views/ містить файли представлень контролерів та файли макетів (layout) + layouts/ містить файли представлень макетів + main.php загальна для всіх сторінок розмітка + column1.php розмітка для сторінок з одною колонкою + column2.php розмітка для сторінок з двома колонками + site/ містить файли представлень для контролеру 'site' + pages/ статичні сторінки + about.php сторінка «про проект» + contact.php файл представлення для дії 'contact' + error.php файл представлення для дії 'error' (відображення помилок) + index.php файл представлення для дії 'index' + login.php файл представлення для дії 'login' +~~~ + +Описаний вище генератор додатку, також підтримує створення необхідних файлів для системи контролю версій Git. +Наступна команда створить необхідні файли `.gitignore` (наприклад, зміст директорій `assets` та `runtime` не повинен відслідковуватися) +та `.gitkeep` (примусово встановлює відстеження початково порожніх, але важливих каталогів): + +~~~ +% YiiRoot/framework/yiic webapp WebRoot/testdrive git +~~~ + +Інша система VCS, що підтримується, це Mercurial: передайте значення `hg` у якості третього параметра, якщо ви використовуєте цю VCS. +Ця функція доступна із версії 1.1.11. + +Зʼєднання з базою даних +----------------------- +Більшість веб-додатків використовують бази даних, і наш додаток не виключення. +Для використання бази даних необхідно пояснити додатку, як до неї підключитися. +Це робиться у конфігураційному файлі `WebRoot/testdrive/protected/config/main.php`. +Наприклад, так: + +~~~ +[php] +return array( + … + 'components'=>array( + … + 'db'=>array( + 'connectionString'=>'sqlite:protected/data/testdrive.db', + ), + ), + … +); +~~~ + +У наведеному вище коді вказано, що додаток повинен підключитися до бази даних SQLite +`WebRoot/testdrive/protected/data/testdrive.db` як тільки це знадобиться. Відмітимо, що +база даних SQLite вже вімкнена до згенерованого додатку. У цій базі є тільки +одна таблиця `tbl_user`: + +~~~ +[sql] +CREATE TABLE tbl_user ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + username VARCHAR(128) NOT NULL, + password VARCHAR(128) NOT NULL, + email VARCHAR(128) NOT NULL +); +~~~ + +Якщо ви бажаєте використовувати базу даних MySQL, ви можете використовувати файл +`WebRoot/testdrive/protected/data/schema.mysql.sql` для її створення. + +> Note|Примітка: Для роботи з базою даних Yii потрібне розширення PHP PDO +і відповідний драйвер PDO. Для тестового додатку необхідно підключити +розширення `php_pdo` та `php_pdo_sqlite`. + +Реалізація операцій CRUD +------------------------ + +А тепер найвеселіше. Ми б хотіли додати операції CRUD +(створення, читання, оновлення та видалениня) для щойно створеної таблиці `tbl_user` — +це часто необхідно при створенні реальних додатків. Замість написання коду ми +скористаємося веб-кодогенератором `Gii`. + +> Info|Інформація: Gii доступний починаючи із версії 1.1.2. Раніше для тих же цілей +> використовувався вже згаданий `yiic`. Детальніше `yiic` описаний у розділі +«[генерація CRUD за допомогою yiic shell](/doc/guide/quickstart.first-app-yiic)». + + +### Налаштування Gii + +Для того, щоб використовувати Gii, необхідно відредагувати +[файл конфігурації додатку](/doc/guide/basics.application#application-configuration) +`WebRoot/testdrive/protected/config/main.php`: + +~~~ +[php] +return array( + … + 'import'=>array( + 'application.models.*', + 'application.components.*', + ), + + 'modules'=>array( + 'gii'=>array( + 'class'=>'system.gii.GiiModule', + 'password'=>'впишіть свій пароль', + ), + ), +); +~~~ + +Після цього перейдіть по URL `http://hostname/testdrive/index.php?r=gii` та +введіть вказаний у конфігурації пароль. + +### Генерація моделі User + +Після входу перейдіть у розділ `Model Generator`: + +![Model Generator](gii-model.png) + +У полі `Table Name` введіть `tbl_user`. У полі `Model Class` — `User`. +Потім натисніть на кнопку `Preview`. Буде показаний новий файл, який буде +згенерировано. Після натискання кнопки `Generate` у `protected/models` буде створений +файл `User.php`. Як буде описано далі у керівництві, клас моделі `User` +дозволяє працювати з даними у таблиці `tbl_user` у стилі ООП. + +### Генерація CRUD + +Після генерації класів моделі ми згенеруємо код, який реалізує для неї операції CRUD. +Обираємо `Crud Generator`: + +![CRUD Generator](gii-crud.png) + +У полі `Model Class` вводимо `User`. У полі `Controller ID` — `user` (у нижньому регістрі). +Тепер натискаємо `Preview` і потім `Generate`. Генерація коду CRUD завершена. + +### Доступ до сторінок CRUD + +Давайте порадіємо нашим зусиллям, та перейдемо по URL: + +~~~ +http://hostname/testdrive/index.php?r=user +~~~ + +Ми бачимо сторінку із спиком користувачів із таблиці `tbl_user`. Оскільки наша таблиця пуста, то записів у ній не буде. +Кликнемо по кнопці `Create User` і, якщо ми ще не авторизовані, відобразиться сторінка авторизації. +Потім завантажиться форма додавання нового користувача. Заповнимо її та натиснемо кнопку `Create`. +Якщо при заповненні форми були допущені помилки, ми побачимо охайне повідомлення про помилку. + +Повертаючись до списку користувачів, ми повинні побачити у списку щойно створенного користувача. +Додамо ще кілька користувачів. Зверніть увагу, що при значній +кількості користувачів для їх відображення на одній сторінці список буде автоматично розбиватися на сторінки. +Авторизувавшись у якості адміністратора (`admin/admin`), +можні побачити сторінку управління користувачами за адресою: + +~~~ +http://hostname/testdrive/index.php?r=user/admin +~~~ + +Зʼявиться охайна таблиця користувачів. Можна натиснути на заголовок +таблиці, щоб впорядкувати записи по значенням відповідного стовпця. +Для перегляду, редагування або видалення відповідного рядка можна +скористатися кнопками. Також можна переходити на різні сторінки, фільтрувати +результати та виконувати пошук по ним. + +Все це не вимагає написання коду! + +![Сторінка управління користувачами](first-app6.png) + ![Сторінка додавання нового користувача](first-app7.png) \ No newline at end of file diff --git a/docs/guide/uk/topics.performance.txt b/docs/guide/uk/topics.performance.txt index b7ef788b2..59651c039 100644 --- a/docs/guide/uk/topics.performance.txt +++ b/docs/guide/uk/topics.performance.txt @@ -1,121 +1,121 @@ -Покращення продуктивності -============================ - -Продуктивність веб-додатків залежить від багатьох факторів. Головний з них — запити до бази даних, файлової системи і пропускної властивості мережі. В Yii, для зменшення втрат продуктивності через самий фреймворк, взятий до уваги кожен з цих факторів. Не дивлячись на це, багато частин додатку можна покращити, для одержання більшої продуктивності. - -Включення розширення APC ------------------------- - -Включення [розширення PHP APC](http://php.net/manual/en/book.apc.php) — можливо, самий простий спосіб, щоб покращити загальну продуктивність додатку. Розширення оптимізує і кешує проміжний код -PHP і виграє час, який витрачається на інтерпретацію PHP при кожному запиті. - -Відключення режиму налагодження -------------------------- - -Відключення режиму налагодження, це ще один легкий спосіб збільшити продуктивність. Додаток Yii працює в режимі налагодження, якщо константа `YII_DEBUG` визначена як true. Режим налагодження корисний при розробці, але не найкращим чином впливає на продуктивність, через використання більшого числа компонентів. Для прикладу, при журналюванні помилок, з кожним повідомленням може записуватися додаткова інформація. - -Використання `yiilite.php` ---------------------------- -Якщо використовується [розширення PHP APC](http://php.net/manual/en/book.apc.php), ми можемо замінити `yii.php`, іншим завантажувачем — `yiilite.php`. Це дасть додатку ще більший приріст продуктивності. - -Файл `yiilite.php` є в кожній версії Yii і являє собою, забрані разом, часто використовувані класи. Всі коментарі і вирази трасування вирізаються, через це використання `yiilite.php` зменшує кількість файлів які підключаються і виконуваного коду. - -Варто відмітити, що використання `yiilite.php` без APC, може негативно відобразитися на продуктивності, так як `yiilite.php` включає в себе класи які можуть бути задіяні в кожному запиті і забираються деякий час на парсинг. Також було відмічено, що на деяких конфігураціях сервера `yiilite.php` повільніше, навіть при використанні APC. Найкращий спосіб для прийняття рішення — провести тест на включеному демонстраційному додатку `hello world`. - -Використання кешування ----------------------- - -Як було описано в розділі«[кешування](/doc/guide/caching.overview)», Yii надає декілька рішень які зможуть значно збільшити продуктивність додатку. Якщо генерація деяких даних займає багато часу, ми можемо використовувати [кешування данних](/doc/guide/caching.data), щоб робити це не настільки часто. Якщо вся сторінка не міняється, можна використати [кешування сторінок](/doc/guide/caching.page). - -Якщо використовується [Active Record](/doc/guide/database.ar), можна використати кешування структури бази данних. Це можна зробити, встановивши в налаштуваннях [CdbConnection::schemaCachingDuration] значення більше 0. - -Окрім описаних налаштувань додатку можна використовувати кешування на рівні сервера. Описане вище [кэширование APC](/doc/guide/topics.performance#enabling-apc-extension) відноситься якраз до них. Є і інші рішення, такі як [Zend Optimizer](http://www.zend.com/en/products/guard/zend-optimizer), [eAccelerator](http://eaccelerator.net/) -і [Squid](http://www.squid-cache.org/). - -Оптимізація бази даних ----------------------- - -Отримання даних з бази, часто є вузьким місцем продуктивності додатку. Не дивлячись на те, що кешування може помʼякшити втрати, воно не вирішує проблему повністю. Коли в базі зберігаються великі обсяги даних і потрібно поновити кеш, отримання даних може бути дуже витратним, при невірному складанні схеми, чи даних запиту. - -Будьте уважні при виборі індексів. Їх використання може значно пришвидшити `SELECT`-запити, але значно уповільнити `INSERT`, `UPDATE` та `DELETE`. - -Для складних запитів, рекомендується створити view в базі данних, замість виконання запитів з коду PHP, які СУБД розбирає кожен раз. - -Не зловживайте [Active Record](/doc/guide/database.ar). Хоча [Active Record](/doc/guide/database.ar) і являється зручною проекцію даних в вигляді ООП, але продуктивність при її використанні, через використання обʼєктів для кожного рядка результату, падає. -Для додатків, що інтенсивно працюють з даними, рекомендується використання [DAO](/doc/guide/database.dao). Остання за рахунком, але не за значенням порада, використовуйте `LIMIT` в `SELECT`-запитах. Так ви зможете запобігти отриманню надлишкових даних з бази і витрати потрібної памяті, виділеної для PHP. - -Мінімізація файлів скриптів ---------------------------- - -Складні сторінки часто включають велику кількість зовнішніх файлів JavaScript і CSS. Так як кожний файл дорівнює додатковому запиту до сервера, ми повинні зменшити число файлів шляхом їх злиття. -Також зайвим не буде зменшити розмір кожного з них, для зменшення часу витраченого для передачі по мережі. Існує не мало інструментів для виконування цих задач. - -Для сторінки, яка генерується Yii, не виключено, що деякі скрипти підключаються компонентами, код котрих змінювати не хочеться (наприклад компоненти ядра Yii). Як мінімізувати такі скрипти, показано далі. - -Для початку опишемо котрі скрипти потрібно мінімізувати. Задамо властивість [scriptMap|CClientScript::scriptMap] компонента [clientScript|CWebApplication::clientScript]. Це можна зробити як в налаштуваннях додатку, так і в коді. Наприклад. - -~~~ -[php] -$cs=Yii::app()->clientScript; -$cs->scriptMap=array( - 'jquery.js'=>'/js/all.js', - 'jquery.ajaxqueue.js'=>'/js/all.js', - 'jquery.metadata.js'=>'/js/all.js', - … -); -~~~ -Наведений код, зробить файли JavaScript доступними за URL `/js/all.js`. -Якщо який-небудь з цих файлів потрібен для яких-небудь компонентів, Yii підключить URL (один раз), замість того, щоб підключати файли окремо. Нам потрібно використовувати який-небудь інструмент для злиття (і можливо, стиснення) JavaScript в один файл і записати результат в `js/all.js`. - -Збільшити швидкість завантаження сторінки можна також за допомогою [Google AJAX Libraries API](http://code.google.com/apis/ajaxlibs/). Наприклад, ми можемо підключити `jquery.js` з серверів Google, замість того, щоб використовувати свій сервер. Для того щоб це зробити, потрібно налаштувати `scriptMap` наступним чином. - -~~~ -[php] -$cs=Yii::app()->clientScript; -$cs->scriptMap=array( - 'jquery.js'=>false, - 'jquery.ajaxqueue.js'=>false, - 'jquery.metadata.js'=>false, - … -); -~~~ -Встановивши значення в false, ми забороняємо генерувати Yii для включення відповідних файлів. Замість того, підключимо їх з сервера Google. - -~~~ -[php] - - - - -… - -~~~ - -Створення символічних посилань ресурсів ---------------------------------------- - -Якщо ваш проект використовує ресурси у великій кількості, -то ви можете поліпшити його продуктивність за допомогою -[символічних посилань](http://en.wikipedia.org/wiki/Symbolic_link) замість копії файлів. -Для того, щоб увімкнути його необхідно налаштувати властивість -[linkAssets|CAssetManager::linkAssets] компонента додатку `assetManager` -за допомогою файла конфігурації `protected/config/main.php`: - -~~~ -[php] -return array( - // ... - 'components' => array( - // ... - 'assetManager' => array( - 'linkAssets' => true, - ), - ), -); -~~~ - -Слід зазначити, що це [може вимагати додаткового налаштування|CAssetManager::linkAssets]. +Покращення продуктивності +============================ + +Продуктивність веб-додатків залежить від багатьох факторів. Головний з них — запити до бази даних, файлової системи і пропускної властивості мережі. В Yii, для зменшення втрат продуктивності через самий фреймворк, взятий до уваги кожен з цих факторів. Не дивлячись на це, багато частин додатку можна покращити, для одержання більшої продуктивності. + +Включення розширення APC +------------------------ + +Включення [розширення PHP APC](http://php.net/manual/en/book.apc.php) — можливо, самий простий спосіб, щоб покращити загальну продуктивність додатку. Розширення оптимізує і кешує проміжний код +PHP і виграє час, який витрачається на інтерпретацію PHP при кожному запиті. + +Відключення режиму налагодження +------------------------- + +Відключення режиму налагодження, це ще один легкий спосіб збільшити продуктивність. Додаток Yii працює в режимі налагодження, якщо константа `YII_DEBUG` визначена як true. Режим налагодження корисний при розробці, але не найкращим чином впливає на продуктивність, через використання більшого числа компонентів. Для прикладу, при журналюванні помилок, з кожним повідомленням може записуватися додаткова інформація. + +Використання `yiilite.php` +--------------------------- +Якщо використовується [розширення PHP APC](http://php.net/manual/en/book.apc.php), ми можемо замінити `yii.php`, іншим завантажувачем — `yiilite.php`. Це дасть додатку ще більший приріст продуктивності. + +Файл `yiilite.php` є в кожній версії Yii і являє собою, забрані разом, часто використовувані класи. Всі коментарі і вирази трасування вирізаються, через це використання `yiilite.php` зменшує кількість файлів які підключаються і виконуваного коду. + +Варто відмітити, що використання `yiilite.php` без APC, може негативно відобразитися на продуктивності, так як `yiilite.php` включає в себе класи які можуть бути задіяні в кожному запиті і забираються деякий час на парсинг. Також було відмічено, що на деяких конфігураціях сервера `yiilite.php` повільніше, навіть при використанні APC. Найкращий спосіб для прийняття рішення — провести тест на включеному демонстраційному додатку `hello world`. + +Використання кешування +---------------------- + +Як було описано в розділі«[кешування](/doc/guide/caching.overview)», Yii надає декілька рішень які зможуть значно збільшити продуктивність додатку. Якщо генерація деяких даних займає багато часу, ми можемо використовувати [кешування данних](/doc/guide/caching.data), щоб робити це не настільки часто. Якщо вся сторінка не міняється, можна використати [кешування сторінок](/doc/guide/caching.page). + +Якщо використовується [Active Record](/doc/guide/database.ar), можна використати кешування структури бази данних. Це можна зробити, встановивши в налаштуваннях [CdbConnection::schemaCachingDuration] значення більше 0. + +Окрім описаних налаштувань додатку можна використовувати кешування на рівні сервера. Описане вище [кэширование APC](/doc/guide/topics.performance#enabling-apc-extension) відноситься якраз до них. Є і інші рішення, такі як [Zend Optimizer](http://www.zend.com/en/products/guard/zend-optimizer), [eAccelerator](http://eaccelerator.net/) +і [Squid](http://www.squid-cache.org/). + +Оптимізація бази даних +---------------------- + +Отримання даних з бази, часто є вузьким місцем продуктивності додатку. Не дивлячись на те, що кешування може помʼякшити втрати, воно не вирішує проблему повністю. Коли в базі зберігаються великі обсяги даних і потрібно поновити кеш, отримання даних може бути дуже витратним, при невірному складанні схеми, чи даних запиту. + +Будьте уважні при виборі індексів. Їх використання може значно пришвидшити `SELECT`-запити, але значно уповільнити `INSERT`, `UPDATE` та `DELETE`. + +Для складних запитів, рекомендується створити view в базі данних, замість виконання запитів з коду PHP, які СУБД розбирає кожен раз. + +Не зловживайте [Active Record](/doc/guide/database.ar). Хоча [Active Record](/doc/guide/database.ar) і являється зручною проекцію даних в вигляді ООП, але продуктивність при її використанні, через використання обʼєктів для кожного рядка результату, падає. +Для додатків, що інтенсивно працюють з даними, рекомендується використання [DAO](/doc/guide/database.dao). Остання за рахунком, але не за значенням порада, використовуйте `LIMIT` в `SELECT`-запитах. Так ви зможете запобігти отриманню надлишкових даних з бази і витрати потрібної памяті, виділеної для PHP. + +Мінімізація файлів скриптів +--------------------------- + +Складні сторінки часто включають велику кількість зовнішніх файлів JavaScript і CSS. Так як кожний файл дорівнює додатковому запиту до сервера, ми повинні зменшити число файлів шляхом їх злиття. +Також зайвим не буде зменшити розмір кожного з них, для зменшення часу витраченого для передачі по мережі. Існує не мало інструментів для виконування цих задач. + +Для сторінки, яка генерується Yii, не виключено, що деякі скрипти підключаються компонентами, код котрих змінювати не хочеться (наприклад компоненти ядра Yii). Як мінімізувати такі скрипти, показано далі. + +Для початку опишемо котрі скрипти потрібно мінімізувати. Задамо властивість [scriptMap|CClientScript::scriptMap] компонента [clientScript|CWebApplication::clientScript]. Це можна зробити як в налаштуваннях додатку, так і в коді. Наприклад. + +~~~ +[php] +$cs=Yii::app()->clientScript; +$cs->scriptMap=array( + 'jquery.js'=>'/js/all.js', + 'jquery.ajaxqueue.js'=>'/js/all.js', + 'jquery.metadata.js'=>'/js/all.js', + … +); +~~~ +Наведений код, зробить файли JavaScript доступними за URL `/js/all.js`. +Якщо який-небудь з цих файлів потрібен для яких-небудь компонентів, Yii підключить URL (один раз), замість того, щоб підключати файли окремо. Нам потрібно використовувати який-небудь інструмент для злиття (і можливо, стиснення) JavaScript в один файл і записати результат в `js/all.js`. + +Збільшити швидкість завантаження сторінки можна також за допомогою [Google AJAX Libraries API](http://code.google.com/apis/ajaxlibs/). Наприклад, ми можемо підключити `jquery.js` з серверів Google, замість того, щоб використовувати свій сервер. Для того щоб це зробити, потрібно налаштувати `scriptMap` наступним чином. + +~~~ +[php] +$cs=Yii::app()->clientScript; +$cs->scriptMap=array( + 'jquery.js'=>false, + 'jquery.ajaxqueue.js'=>false, + 'jquery.metadata.js'=>false, + … +); +~~~ +Встановивши значення в false, ми забороняємо генерувати Yii для включення відповідних файлів. Замість того, підключимо їх з сервера Google. + +~~~ +[php] + + + + +… + +~~~ + +Створення символічних посилань ресурсів +--------------------------------------- + +Якщо ваш проект використовує ресурси у великій кількості, +то ви можете поліпшити його продуктивність за допомогою +[символічних посилань](http://en.wikipedia.org/wiki/Symbolic_link) замість копії файлів. +Для того, щоб увімкнути його необхідно налаштувати властивість +[linkAssets|CAssetManager::linkAssets] компонента додатку `assetManager` +за допомогою файла конфігурації `protected/config/main.php`: + +~~~ +[php] +return array( + // ... + 'components' => array( + // ... + 'assetManager' => array( + 'linkAssets' => true, + ), + ), +); +~~~ + +Слід зазначити, що це [може вимагати додаткового налаштування|CAssetManager::linkAssets]. diff --git a/framework/.htaccess b/framework/.htaccess index e01983226..8d2f25636 100644 --- a/framework/.htaccess +++ b/framework/.htaccess @@ -1 +1 @@ -deny from all +deny from all diff --git a/framework/cli/views/webapp/hg-hgignore b/framework/cli/views/webapp/hg-hgignore index 88003d6a9..167b1ae6d 100644 --- a/framework/cli/views/webapp/hg-hgignore +++ b/framework/cli/views/webapp/hg-hgignore @@ -1,7 +1,7 @@ -syntax: glob - -syntax: regexp -# ignore all except .hgkeep -^assets/(?!.*\.hgkeep$).+ -^protected/runtime/(?!.*\.hgkeep$).+ -^protected/tests/report/(?!.*\.hgkeep$).+ +syntax: glob + +syntax: regexp +# ignore all except .hgkeep +^assets/(?!.*\.hgkeep$).+ +^protected/runtime/(?!.*\.hgkeep$).+ +^protected/tests/report/(?!.*\.hgkeep$).+ diff --git a/framework/cli/views/webapp/protected/.htaccess b/framework/cli/views/webapp/protected/.htaccess index e01983226..8d2f25636 100644 --- a/framework/cli/views/webapp/protected/.htaccess +++ b/framework/cli/views/webapp/protected/.htaccess @@ -1 +1 @@ -deny from all +deny from all diff --git a/framework/cli/views/webapp/themes/classic/views/.htaccess b/framework/cli/views/webapp/themes/classic/views/.htaccess index e01983226..8d2f25636 100644 --- a/framework/cli/views/webapp/themes/classic/views/.htaccess +++ b/framework/cli/views/webapp/themes/classic/views/.htaccess @@ -1 +1 @@ -deny from all +deny from all diff --git a/framework/console/CConsoleCommandBehavior.php b/framework/console/CConsoleCommandBehavior.php index 0b18b2abf..2e65ffc10 100644 --- a/framework/console/CConsoleCommandBehavior.php +++ b/framework/console/CConsoleCommandBehavior.php @@ -1,53 +1,53 @@ - - * @link http://www.yiiframework.com/ - * @copyright 2008-2013 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -/** - * CConsoleCommandBehavior is a base class for behaviors that are attached to a console command component. - * - * @property CConsoleCommand $owner The owner model that this behavior is attached to. - * - * @author Evgeny Blinov - * @package system.console - * @since 1.1.11 - */ -class CConsoleCommandBehavior extends CBehavior -{ - /** - * Declares events and the corresponding event handler methods. - * The default implementation returns 'onAfterConstruct', 'onBeforeValidate' and 'onAfterValidate' events and handlers. - * If you override this method, make sure you merge the parent result to the return value. - * @return array events (array keys) and the corresponding event handler methods (array values). - * @see CBehavior::events - */ - public function events() - { - return array( - 'onBeforeAction' => 'beforeAction', - 'onAfterAction' => 'afterAction' - ); - } - /** - * Responds to {@link CConsoleCommand::onBeforeAction} event. - * Override this method and make it public if you want to handle the corresponding event of the {@link CBehavior::owner owner}. - * @param CConsoleCommandEvent $event event parameter - */ - protected function beforeAction($event) - { - } - - /** - * Responds to {@link CConsoleCommand::onAfterAction} event. - * Override this method and make it public if you want to handle the corresponding event of the {@link CBehavior::owner owner}. - * @param CConsoleCommandEvent $event event parameter - */ - protected function afterAction($event) - { - } + + * @link http://www.yiiframework.com/ + * @copyright 2008-2013 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CConsoleCommandBehavior is a base class for behaviors that are attached to a console command component. + * + * @property CConsoleCommand $owner The owner model that this behavior is attached to. + * + * @author Evgeny Blinov + * @package system.console + * @since 1.1.11 + */ +class CConsoleCommandBehavior extends CBehavior +{ + /** + * Declares events and the corresponding event handler methods. + * The default implementation returns 'onAfterConstruct', 'onBeforeValidate' and 'onAfterValidate' events and handlers. + * If you override this method, make sure you merge the parent result to the return value. + * @return array events (array keys) and the corresponding event handler methods (array values). + * @see CBehavior::events + */ + public function events() + { + return array( + 'onBeforeAction' => 'beforeAction', + 'onAfterAction' => 'afterAction' + ); + } + /** + * Responds to {@link CConsoleCommand::onBeforeAction} event. + * Override this method and make it public if you want to handle the corresponding event of the {@link CBehavior::owner owner}. + * @param CConsoleCommandEvent $event event parameter + */ + protected function beforeAction($event) + { + } + + /** + * Responds to {@link CConsoleCommand::onAfterAction} event. + * Override this method and make it public if you want to handle the corresponding event of the {@link CBehavior::owner owner}. + * @param CConsoleCommandEvent $event event parameter + */ + protected function afterAction($event) + { + } } \ No newline at end of file diff --git a/framework/gii/assets/js/fancybox/jquery.fancybox-1.3.1.css b/framework/gii/assets/js/fancybox/jquery.fancybox-1.3.1.css index 28bbb1327..fd71d7470 100644 --- a/framework/gii/assets/js/fancybox/jquery.fancybox-1.3.1.css +++ b/framework/gii/assets/js/fancybox/jquery.fancybox-1.3.1.css @@ -1,363 +1,363 @@ -/* - * FancyBox - jQuery Plugin - * Simple and fancy lightbox alternative - * - * Examples and documentation at: http://fancybox.net - * - * Copyright (c) 2008 - 2010 Janis Skarnelis - * - * Version: 1.3.1 (05/03/2010) - * Requires: jQuery v1.3+ - * - * Dual licensed under the MIT and GPL licenses: - * http://www.opensource.org/licenses/mit-license.php - * http://www.gnu.org/licenses/gpl.html - */ - -#fancybox-loading { - position: fixed; - top: 50%; - left: 50%; - height: 40px; - width: 40px; - margin-top: -20px; - margin-left: -20px; - cursor: pointer; - overflow: hidden; - z-index: 1104; - display: none; -} - -* html #fancybox-loading { /* IE6 */ - position: absolute; - margin-top: 0; -} - -#fancybox-loading div { - position: absolute; - top: 0; - left: 0; - width: 40px; - height: 480px; - background-image: url('fancybox.png'); -} - -#fancybox-overlay { - position: fixed; - top: 0; - left: 0; - bottom: 0; - right: 0; - background: #000; - z-index: 1100; - display: none; -} - -* html #fancybox-overlay { /* IE6 */ - position: absolute; - width: 100%; -} - -#fancybox-tmp { - padding: 0; - margin: 0; - border: 0; - overflow: auto; - display: none; -} - -#fancybox-wrap { - position: absolute; - top: 0; - left: 0; - margin: 0; - padding: 20px; - z-index: 1101; - display: none; -} - -#fancybox-outer { - position: relative; - width: 100%; - height: 100%; - background: #FFF; -} - -#fancybox-inner { - position: absolute; - top: 0; - left: 0; - width: 1px; - height: 1px; - padding: 0; - margin: 0; - outline: none; - overflow: hidden; -} - -#fancybox-hide-sel-frame { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: transparent; -} - -#fancybox-close { - position: absolute; - top: -15px; - right: -15px; - width: 30px; - height: 30px; - background-image: url('fancybox.png'); - background-position: -40px 0px; - cursor: pointer; - z-index: 1103; - display: none; -} - -#fancybox_error { - color: #444; - font: normal 12px/20px Arial; - padding: 7px; - margin: 0; -} - -#fancybox-content { - height: auto; - width: auto; - padding: 0; - margin: 0; -} - -#fancybox-img { - width: 100%; - height: 100%; - padding: 0; - margin: 0; - border: none; - outline: none; - line-height: 0; - vertical-align: top; - -ms-interpolation-mode: bicubic; -} - -#fancybox-frame { - position: relative; - width: 100%; - height: 100%; - border: none; - display: block; -} - -#fancybox-title { - position: absolute; - bottom: 0; - left: 0; - font-family: Arial; - font-size: 12px; - z-index: 1102; -} - -.fancybox-title-inside { - padding: 10px 0; - text-align: center; - color: #333; -} - -.fancybox-title-outside { - padding-top: 5px; - color: #FFF; - text-align: center; - font-weight: bold; -} - -.fancybox-title-over { - color: #FFF; - text-align: left; -} - -#fancybox-title-over { - padding: 10px; - background-image: url('fancy_title_over.png'); - display: block; -} - -#fancybox-title-wrap { - display: inline-block; -} - -#fancybox-title-wrap span { - height: 32px; - float: left; -} - -#fancybox-title-left { - padding-left: 15px; - background-image: url('fancybox.png'); - background-position: -40px -90px; - background-repeat: no-repeat; -} - -#fancybox-title-main { - font-weight: bold; - line-height: 29px; - background-image: url('fancybox-x.png'); - background-position: 0px -40px; - color: #FFF; -} - -#fancybox-title-right { - padding-left: 15px; - background-image: url('fancybox.png'); - background-position: -55px -90px; - background-repeat: no-repeat; -} - -#fancybox-left, #fancybox-right { - position: absolute; - bottom: 0px; - height: 100%; - width: 35%; - cursor: pointer; - outline: none; - background-image: url('blank.gif'); - z-index: 1102; - display: none; -} - -#fancybox-left { - left: 0px; -} - -#fancybox-right { - right: 0px; -} - -#fancybox-left-ico, #fancybox-right-ico { - position: absolute; - top: 50%; - left: -9999px; - width: 30px; - height: 30px; - margin-top: -15px; - cursor: pointer; - z-index: 1102; - display: block; -} - -#fancybox-left-ico { - background-image: url('fancybox.png'); - background-position: -40px -30px; -} - -#fancybox-right-ico { - background-image: url('fancybox.png'); - background-position: -40px -60px; -} - -#fancybox-left:hover, #fancybox-right:hover { - visibility: visible; /* IE6 */ -} - -#fancybox-left:hover span { - left: 20px; -} - -#fancybox-right:hover span { - left: auto; - right: 20px; -} - -.fancy-bg { - position: absolute; - padding: 0; - margin: 0; - border: 0; - width: 20px; - height: 20px; - z-index: 1001; -} - -#fancy-bg-n { - top: -20px; - left: 0; - width: 100%; - background-image: url('fancybox-x.png'); -} - -#fancy-bg-ne { - top: -20px; - right: -20px; - background-image: url('fancybox.png'); - background-position: -40px -162px; -} - -#fancy-bg-e { - top: 0; - right: -20px; - height: 100%; - background-image: url('fancybox-y.png'); - background-position: -20px 0px; -} - -#fancy-bg-se { - bottom: -20px; - right: -20px; - background-image: url('fancybox.png'); - background-position: -40px -182px; -} - -#fancy-bg-s { - bottom: -20px; - left: 0; - width: 100%; - background-image: url('fancybox-x.png'); - background-position: 0px -20px; -} - -#fancy-bg-sw { - bottom: -20px; - left: -20px; - background-image: url('fancybox.png'); - background-position: -40px -142px; -} - -#fancy-bg-w { - top: 0; - left: -20px; - height: 100%; - background-image: url('fancybox-y.png'); -} - -#fancy-bg-nw { - top: -20px; - left: -20px; - background-image: url('fancybox.png'); - background-position: -40px -122px; -} - -/* IE */ - -#fancybox-loading.fancybox-ie div { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_loading.png', sizingMethod='scale'); } -.fancybox-ie #fancybox-close { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_close.png', sizingMethod='scale'); } - -.fancybox-ie #fancybox-title-over { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_over.png', sizingMethod='scale'); zoom: 1; } -.fancybox-ie #fancybox-title-left { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_left.png', sizingMethod='scale'); } -.fancybox-ie #fancybox-title-main { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_main.png', sizingMethod='scale'); } -.fancybox-ie #fancybox-title-right { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_right.png', sizingMethod='scale'); } - -.fancybox-ie #fancybox-left-ico { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_nav_left.png', sizingMethod='scale'); } -.fancybox-ie #fancybox-right-ico { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_nav_right.png', sizingMethod='scale'); } - -.fancybox-ie .fancy-bg { background: transparent !important; } - -.fancybox-ie #fancy-bg-n { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_n.png', sizingMethod='scale'); } -.fancybox-ie #fancy-bg-ne { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_ne.png', sizingMethod='scale'); } -.fancybox-ie #fancy-bg-e { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_e.png', sizingMethod='scale'); } -.fancybox-ie #fancy-bg-se { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_se.png', sizingMethod='scale'); } -.fancybox-ie #fancy-bg-s { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_s.png', sizingMethod='scale'); } -.fancybox-ie #fancy-bg-sw { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_sw.png', sizingMethod='scale'); } -.fancybox-ie #fancy-bg-w { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_w.png', sizingMethod='scale'); } -.fancybox-ie #fancy-bg-nw { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_nw.png', sizingMethod='scale'); } +/* + * FancyBox - jQuery Plugin + * Simple and fancy lightbox alternative + * + * Examples and documentation at: http://fancybox.net + * + * Copyright (c) 2008 - 2010 Janis Skarnelis + * + * Version: 1.3.1 (05/03/2010) + * Requires: jQuery v1.3+ + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + */ + +#fancybox-loading { + position: fixed; + top: 50%; + left: 50%; + height: 40px; + width: 40px; + margin-top: -20px; + margin-left: -20px; + cursor: pointer; + overflow: hidden; + z-index: 1104; + display: none; +} + +* html #fancybox-loading { /* IE6 */ + position: absolute; + margin-top: 0; +} + +#fancybox-loading div { + position: absolute; + top: 0; + left: 0; + width: 40px; + height: 480px; + background-image: url('fancybox.png'); +} + +#fancybox-overlay { + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: #000; + z-index: 1100; + display: none; +} + +* html #fancybox-overlay { /* IE6 */ + position: absolute; + width: 100%; +} + +#fancybox-tmp { + padding: 0; + margin: 0; + border: 0; + overflow: auto; + display: none; +} + +#fancybox-wrap { + position: absolute; + top: 0; + left: 0; + margin: 0; + padding: 20px; + z-index: 1101; + display: none; +} + +#fancybox-outer { + position: relative; + width: 100%; + height: 100%; + background: #FFF; +} + +#fancybox-inner { + position: absolute; + top: 0; + left: 0; + width: 1px; + height: 1px; + padding: 0; + margin: 0; + outline: none; + overflow: hidden; +} + +#fancybox-hide-sel-frame { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: transparent; +} + +#fancybox-close { + position: absolute; + top: -15px; + right: -15px; + width: 30px; + height: 30px; + background-image: url('fancybox.png'); + background-position: -40px 0px; + cursor: pointer; + z-index: 1103; + display: none; +} + +#fancybox_error { + color: #444; + font: normal 12px/20px Arial; + padding: 7px; + margin: 0; +} + +#fancybox-content { + height: auto; + width: auto; + padding: 0; + margin: 0; +} + +#fancybox-img { + width: 100%; + height: 100%; + padding: 0; + margin: 0; + border: none; + outline: none; + line-height: 0; + vertical-align: top; + -ms-interpolation-mode: bicubic; +} + +#fancybox-frame { + position: relative; + width: 100%; + height: 100%; + border: none; + display: block; +} + +#fancybox-title { + position: absolute; + bottom: 0; + left: 0; + font-family: Arial; + font-size: 12px; + z-index: 1102; +} + +.fancybox-title-inside { + padding: 10px 0; + text-align: center; + color: #333; +} + +.fancybox-title-outside { + padding-top: 5px; + color: #FFF; + text-align: center; + font-weight: bold; +} + +.fancybox-title-over { + color: #FFF; + text-align: left; +} + +#fancybox-title-over { + padding: 10px; + background-image: url('fancy_title_over.png'); + display: block; +} + +#fancybox-title-wrap { + display: inline-block; +} + +#fancybox-title-wrap span { + height: 32px; + float: left; +} + +#fancybox-title-left { + padding-left: 15px; + background-image: url('fancybox.png'); + background-position: -40px -90px; + background-repeat: no-repeat; +} + +#fancybox-title-main { + font-weight: bold; + line-height: 29px; + background-image: url('fancybox-x.png'); + background-position: 0px -40px; + color: #FFF; +} + +#fancybox-title-right { + padding-left: 15px; + background-image: url('fancybox.png'); + background-position: -55px -90px; + background-repeat: no-repeat; +} + +#fancybox-left, #fancybox-right { + position: absolute; + bottom: 0px; + height: 100%; + width: 35%; + cursor: pointer; + outline: none; + background-image: url('blank.gif'); + z-index: 1102; + display: none; +} + +#fancybox-left { + left: 0px; +} + +#fancybox-right { + right: 0px; +} + +#fancybox-left-ico, #fancybox-right-ico { + position: absolute; + top: 50%; + left: -9999px; + width: 30px; + height: 30px; + margin-top: -15px; + cursor: pointer; + z-index: 1102; + display: block; +} + +#fancybox-left-ico { + background-image: url('fancybox.png'); + background-position: -40px -30px; +} + +#fancybox-right-ico { + background-image: url('fancybox.png'); + background-position: -40px -60px; +} + +#fancybox-left:hover, #fancybox-right:hover { + visibility: visible; /* IE6 */ +} + +#fancybox-left:hover span { + left: 20px; +} + +#fancybox-right:hover span { + left: auto; + right: 20px; +} + +.fancy-bg { + position: absolute; + padding: 0; + margin: 0; + border: 0; + width: 20px; + height: 20px; + z-index: 1001; +} + +#fancy-bg-n { + top: -20px; + left: 0; + width: 100%; + background-image: url('fancybox-x.png'); +} + +#fancy-bg-ne { + top: -20px; + right: -20px; + background-image: url('fancybox.png'); + background-position: -40px -162px; +} + +#fancy-bg-e { + top: 0; + right: -20px; + height: 100%; + background-image: url('fancybox-y.png'); + background-position: -20px 0px; +} + +#fancy-bg-se { + bottom: -20px; + right: -20px; + background-image: url('fancybox.png'); + background-position: -40px -182px; +} + +#fancy-bg-s { + bottom: -20px; + left: 0; + width: 100%; + background-image: url('fancybox-x.png'); + background-position: 0px -20px; +} + +#fancy-bg-sw { + bottom: -20px; + left: -20px; + background-image: url('fancybox.png'); + background-position: -40px -142px; +} + +#fancy-bg-w { + top: 0; + left: -20px; + height: 100%; + background-image: url('fancybox-y.png'); +} + +#fancy-bg-nw { + top: -20px; + left: -20px; + background-image: url('fancybox.png'); + background-position: -40px -122px; +} + +/* IE */ + +#fancybox-loading.fancybox-ie div { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_loading.png', sizingMethod='scale'); } +.fancybox-ie #fancybox-close { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_close.png', sizingMethod='scale'); } + +.fancybox-ie #fancybox-title-over { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_over.png', sizingMethod='scale'); zoom: 1; } +.fancybox-ie #fancybox-title-left { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_left.png', sizingMethod='scale'); } +.fancybox-ie #fancybox-title-main { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_main.png', sizingMethod='scale'); } +.fancybox-ie #fancybox-title-right { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_right.png', sizingMethod='scale'); } + +.fancybox-ie #fancybox-left-ico { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_nav_left.png', sizingMethod='scale'); } +.fancybox-ie #fancybox-right-ico { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_nav_right.png', sizingMethod='scale'); } + +.fancybox-ie .fancy-bg { background: transparent !important; } + +.fancybox-ie #fancy-bg-n { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_n.png', sizingMethod='scale'); } +.fancybox-ie #fancy-bg-ne { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_ne.png', sizingMethod='scale'); } +.fancybox-ie #fancy-bg-e { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_e.png', sizingMethod='scale'); } +.fancybox-ie #fancy-bg-se { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_se.png', sizingMethod='scale'); } +.fancybox-ie #fancy-bg-s { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_s.png', sizingMethod='scale'); } +.fancybox-ie #fancy-bg-sw { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_sw.png', sizingMethod='scale'); } +.fancybox-ie #fancy-bg-w { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_w.png', sizingMethod='scale'); } +.fancybox-ie #fancy-bg-nw { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_nw.png', sizingMethod='scale'); } diff --git a/framework/gii/components/Pear/Text/Diff/Engine/shell.php b/framework/gii/components/Pear/Text/Diff/Engine/shell.php index 00ead96f4..f1aaa98f0 100644 --- a/framework/gii/components/Pear/Text/Diff/Engine/shell.php +++ b/framework/gii/components/Pear/Text/Diff/Engine/shell.php @@ -1,162 +1,162 @@ - - * @package Text_Diff - * @since 0.3.0 - */ -class Text_Diff_Engine_shell { - - /** - * Path to the diff executable - * - * @var string - */ - var $_diffCommand = 'diff'; - - /** - * Returns the array of differences. - * - * @param array $from_lines lines of text from old file - * @param array $to_lines lines of text from new file - * - * @return array all changes made (array with Text_Diff_Op_* objects) - */ - function diff($from_lines, $to_lines) - { - array_walk($from_lines, array('Text_Diff', 'trimNewlines')); - array_walk($to_lines, array('Text_Diff', 'trimNewlines')); - - $temp_dir = Text_Diff::_getTempDir(); - - // Execute gnu diff or similar to get a standard diff file. - $from_file = tempnam($temp_dir, 'Text_Diff'); - $to_file = tempnam($temp_dir, 'Text_Diff'); - $fp = fopen($from_file, 'w'); - fwrite($fp, implode("\n", $from_lines)); - fclose($fp); - $fp = fopen($to_file, 'w'); - fwrite($fp, implode("\n", $to_lines)); - fclose($fp); - $diff = shell_exec($this->_diffCommand . ' ' . $from_file . ' ' . $to_file); - unlink($from_file); - unlink($to_file); - - if (is_null($diff)) { - // No changes were made - return array(new Text_Diff_Op_copy($from_lines)); - } - - $from_line_no = 1; - $to_line_no = 1; - $edits = array(); - - // Get changed lines by parsing something like: - // 0a1,2 - // 1,2c4,6 - // 1,5d6 - preg_match_all('#^(\d+)(?:,(\d+))?([adc])(\d+)(?:,(\d+))?$#m', $diff, - $matches, PREG_SET_ORDER); - - foreach ($matches as $match) { - if (!isset($match[5])) { - // This paren is not set every time (see regex). - $match[5] = false; - } - - if ($match[3] == 'a') { - $from_line_no--; - } - - if ($match[3] == 'd') { - $to_line_no--; - } - - if ($from_line_no < $match[1] || $to_line_no < $match[4]) { - // copied lines - assert('$match[1] - $from_line_no == $match[4] - $to_line_no'); - array_push($edits, - new Text_Diff_Op_copy( - $this->_getLines($from_lines, $from_line_no, $match[1] - 1), - $this->_getLines($to_lines, $to_line_no, $match[4] - 1))); - } - - switch ($match[3]) { - case 'd': - // deleted lines - array_push($edits, - new Text_Diff_Op_delete( - $this->_getLines($from_lines, $from_line_no, $match[2]))); - $to_line_no++; - break; - - case 'c': - // changed lines - array_push($edits, - new Text_Diff_Op_change( - $this->_getLines($from_lines, $from_line_no, $match[2]), - $this->_getLines($to_lines, $to_line_no, $match[5]))); - break; - - case 'a': - // added lines - array_push($edits, - new Text_Diff_Op_add( - $this->_getLines($to_lines, $to_line_no, $match[5]))); - $from_line_no++; - break; - } - } - - if (!empty($from_lines)) { - // Some lines might still be pending. Add them as copied - array_push($edits, - new Text_Diff_Op_copy( - $this->_getLines($from_lines, $from_line_no, - $from_line_no + count($from_lines) - 1), - $this->_getLines($to_lines, $to_line_no, - $to_line_no + count($to_lines) - 1))); - } - - return $edits; - } - - /** - * Get lines from either the old or new text - * - * @access private - * - * @param array &$text_lines Either $from_lines or $to_lines - * @param integer &$line_no Current line number - * @param integer $end Optional end line, when we want to chop more than one line. - * @return array The chopped lines - */ - function _getLines(&$text_lines, &$line_no, $end = false) - { - if (!empty($end)) { - $lines = array(); - // We can shift even more - while ($line_no <= $end) { - array_push($lines, array_shift($text_lines)); - $line_no++; - } - } else { - $lines = array(array_shift($text_lines)); - $line_no++; - } - - return $lines; - } - -} + + * @package Text_Diff + * @since 0.3.0 + */ +class Text_Diff_Engine_shell { + + /** + * Path to the diff executable + * + * @var string + */ + var $_diffCommand = 'diff'; + + /** + * Returns the array of differences. + * + * @param array $from_lines lines of text from old file + * @param array $to_lines lines of text from new file + * + * @return array all changes made (array with Text_Diff_Op_* objects) + */ + function diff($from_lines, $to_lines) + { + array_walk($from_lines, array('Text_Diff', 'trimNewlines')); + array_walk($to_lines, array('Text_Diff', 'trimNewlines')); + + $temp_dir = Text_Diff::_getTempDir(); + + // Execute gnu diff or similar to get a standard diff file. + $from_file = tempnam($temp_dir, 'Text_Diff'); + $to_file = tempnam($temp_dir, 'Text_Diff'); + $fp = fopen($from_file, 'w'); + fwrite($fp, implode("\n", $from_lines)); + fclose($fp); + $fp = fopen($to_file, 'w'); + fwrite($fp, implode("\n", $to_lines)); + fclose($fp); + $diff = shell_exec($this->_diffCommand . ' ' . $from_file . ' ' . $to_file); + unlink($from_file); + unlink($to_file); + + if (is_null($diff)) { + // No changes were made + return array(new Text_Diff_Op_copy($from_lines)); + } + + $from_line_no = 1; + $to_line_no = 1; + $edits = array(); + + // Get changed lines by parsing something like: + // 0a1,2 + // 1,2c4,6 + // 1,5d6 + preg_match_all('#^(\d+)(?:,(\d+))?([adc])(\d+)(?:,(\d+))?$#m', $diff, + $matches, PREG_SET_ORDER); + + foreach ($matches as $match) { + if (!isset($match[5])) { + // This paren is not set every time (see regex). + $match[5] = false; + } + + if ($match[3] == 'a') { + $from_line_no--; + } + + if ($match[3] == 'd') { + $to_line_no--; + } + + if ($from_line_no < $match[1] || $to_line_no < $match[4]) { + // copied lines + assert('$match[1] - $from_line_no == $match[4] - $to_line_no'); + array_push($edits, + new Text_Diff_Op_copy( + $this->_getLines($from_lines, $from_line_no, $match[1] - 1), + $this->_getLines($to_lines, $to_line_no, $match[4] - 1))); + } + + switch ($match[3]) { + case 'd': + // deleted lines + array_push($edits, + new Text_Diff_Op_delete( + $this->_getLines($from_lines, $from_line_no, $match[2]))); + $to_line_no++; + break; + + case 'c': + // changed lines + array_push($edits, + new Text_Diff_Op_change( + $this->_getLines($from_lines, $from_line_no, $match[2]), + $this->_getLines($to_lines, $to_line_no, $match[5]))); + break; + + case 'a': + // added lines + array_push($edits, + new Text_Diff_Op_add( + $this->_getLines($to_lines, $to_line_no, $match[5]))); + $from_line_no++; + break; + } + } + + if (!empty($from_lines)) { + // Some lines might still be pending. Add them as copied + array_push($edits, + new Text_Diff_Op_copy( + $this->_getLines($from_lines, $from_line_no, + $from_line_no + count($from_lines) - 1), + $this->_getLines($to_lines, $to_line_no, + $to_line_no + count($to_lines) - 1))); + } + + return $edits; + } + + /** + * Get lines from either the old or new text + * + * @access private + * + * @param array &$text_lines Either $from_lines or $to_lines + * @param integer &$line_no Current line number + * @param integer $end Optional end line, when we want to chop more than one line. + * @return array The chopped lines + */ + function _getLines(&$text_lines, &$line_no, $end = false) + { + if (!empty($end)) { + $lines = array(); + // We can shift even more + while ($line_no <= $end) { + array_push($lines, array_shift($text_lines)); + $line_no++; + } + } else { + $lines = array(array_shift($text_lines)); + $line_no++; + } + + return $lines; + } + +} diff --git a/framework/gii/components/Pear/Text/Diff/Engine/string.php b/framework/gii/components/Pear/Text/Diff/Engine/string.php index d314afa6f..4b29daafe 100644 --- a/framework/gii/components/Pear/Text/Diff/Engine/string.php +++ b/framework/gii/components/Pear/Text/Diff/Engine/string.php @@ -1,237 +1,237 @@ - - * $patch = file_get_contents('example.patch'); - * $diff = new Text_Diff('string', array($patch)); - * $renderer = new Text_Diff_Renderer_inline(); - * echo $renderer->render($diff); - * - * - * $Horde: framework/Text_Diff/Diff/Engine/string.php,v 1.5.2.5 2008/09/10 08:31:58 jan Exp $ - * - * Copyright 2005 rjan Persson - * Copyright 2005-2008 The Horde Project (http://www.horde.org/) - * - * See the enclosed file COPYING for license information (LGPL). If you did - * not receive this file, see http://opensource.org/licenses/lgpl-license.php. - * - * @author rjan Persson - * @package Text_Diff - * @since 0.2.0 - */ -class Text_Diff_Engine_string { - - /** - * Parses a unified or context diff. - * - * First param contains the whole diff and the second can be used to force - * a specific diff type. If the second parameter is 'autodetect', the - * diff will be examined to find out which type of diff this is. - * - * @param string $diff The diff content. - * @param string $mode The diff mode of the content in $diff. One of - * 'context', 'unified', or 'autodetect'. - * - * @return array List of all diff operations. - */ - function diff($diff, $mode = 'autodetect') - { - if ($mode != 'autodetect' && $mode != 'context' && $mode != 'unified') { - return PEAR::raiseError('Type of diff is unsupported'); - } - - if ($mode == 'autodetect') { - $context = strpos($diff, '***'); - $unified = strpos($diff, '---'); - if ($context === $unified) { - return PEAR::raiseError('Type of diff could not be detected'); - } elseif ($context === false || $unified === false) { - $mode = $context !== false ? 'context' : 'unified'; - } else { - $mode = $context < $unified ? 'context' : 'unified'; - } - } - - // Split by new line and remove the diff header, if there is one. - $diff = explode("\n", $diff); - if (($mode == 'context' && strpos($diff[0], '***') === 0) || - ($mode == 'unified' && strpos($diff[0], '---') === 0)) { - array_shift($diff); - array_shift($diff); - } - - if ($mode == 'context') { - return $this->parseContextDiff($diff); - } else { - return $this->parseUnifiedDiff($diff); - } - } - - /** - * Parses an array containing the unified diff. - * - * @param array $diff Array of lines. - * - * @return array List of all diff operations. - */ - function parseUnifiedDiff($diff) - { - $edits = array(); - $end = count($diff) - 1; - for ($i = 0; $i < $end;) { - $diff1 = array(); - switch (substr($diff[$i], 0, 1)) { - case ' ': - do { - $diff1[] = substr($diff[$i], 1); - } while (++$i < $end && substr($diff[$i], 0, 1) == ' '); - $edits[] = new Text_Diff_Op_copy($diff1); - break; - - case '+': - // get all new lines - do { - $diff1[] = substr($diff[$i], 1); - } while (++$i < $end && substr($diff[$i], 0, 1) == '+'); - $edits[] = new Text_Diff_Op_add($diff1); - break; - - case '-': - // get changed or removed lines - $diff2 = array(); - do { - $diff1[] = substr($diff[$i], 1); - } while (++$i < $end && substr($diff[$i], 0, 1) == '-'); - - while ($i < $end && substr($diff[$i], 0, 1) == '+') { - $diff2[] = substr($diff[$i++], 1); - } - if (count($diff2) == 0) { - $edits[] = new Text_Diff_Op_delete($diff1); - } else { - $edits[] = new Text_Diff_Op_change($diff1, $diff2); - } - break; - - default: - $i++; - break; - } - } - - return $edits; - } - - /** - * Parses an array containing the context diff. - * - * @param array $diff Array of lines. - * - * @return array List of all diff operations. - */ - function parseContextDiff(&$diff) - { - $edits = array(); - $i = $max_i = $j = $max_j = 0; - $end = count($diff) - 1; - while ($i < $end && $j < $end) { - while ($i >= $max_i && $j >= $max_j) { - // Find the boundaries of the diff output of the two files - for ($i = $j; - $i < $end && substr($diff[$i], 0, 3) == '***'; - $i++); - for ($max_i = $i; - $max_i < $end && substr($diff[$max_i], 0, 3) != '---'; - $max_i++); - for ($j = $max_i; - $j < $end && substr($diff[$j], 0, 3) == '---'; - $j++); - for ($max_j = $j; - $max_j < $end && substr($diff[$max_j], 0, 3) != '***'; - $max_j++); - } - - // find what hasn't been changed - $array = array(); - while ($i < $max_i && - $j < $max_j && - strcmp($diff[$i], $diff[$j]) == 0) { - $array[] = substr($diff[$i], 2); - $i++; - $j++; - } - - while ($i < $max_i && ($max_j-$j) <= 1) { - if ($diff[$i] != '' && substr($diff[$i], 0, 1) != ' ') { - break; - } - $array[] = substr($diff[$i++], 2); - } - - while ($j < $max_j && ($max_i-$i) <= 1) { - if ($diff[$j] != '' && substr($diff[$j], 0, 1) != ' ') { - break; - } - $array[] = substr($diff[$j++], 2); - } - if (count($array) > 0) { - $edits[] = new Text_Diff_Op_copy($array); - } - - if ($i < $max_i) { - $diff1 = array(); - switch (substr($diff[$i], 0, 1)) { - case '!': - $diff2 = array(); - do { - $diff1[] = substr($diff[$i], 2); - if ($j < $max_j && substr($diff[$j], 0, 1) == '!') { - $diff2[] = substr($diff[$j++], 2); - } - } while (++$i < $max_i && substr($diff[$i], 0, 1) == '!'); - $edits[] = new Text_Diff_Op_change($diff1, $diff2); - break; - - case '+': - do { - $diff1[] = substr($diff[$i], 2); - } while (++$i < $max_i && substr($diff[$i], 0, 1) == '+'); - $edits[] = new Text_Diff_Op_add($diff1); - break; - - case '-': - do { - $diff1[] = substr($diff[$i], 2); - } while (++$i < $max_i && substr($diff[$i], 0, 1) == '-'); - $edits[] = new Text_Diff_Op_delete($diff1); - break; - } - } - - if ($j < $max_j) { - $diff2 = array(); - switch (substr($diff[$j], 0, 1)) { - case '+': - do { - $diff2[] = substr($diff[$j++], 2); - } while ($j < $max_j && substr($diff[$j], 0, 1) == '+'); - $edits[] = new Text_Diff_Op_add($diff2); - break; - - case '-': - do { - $diff2[] = substr($diff[$j++], 2); - } while ($j < $max_j && substr($diff[$j], 0, 1) == '-'); - $edits[] = new Text_Diff_Op_delete($diff2); - break; - } - } - } - - return $edits; - } - -} + + * $patch = file_get_contents('example.patch'); + * $diff = new Text_Diff('string', array($patch)); + * $renderer = new Text_Diff_Renderer_inline(); + * echo $renderer->render($diff); + * + * + * $Horde: framework/Text_Diff/Diff/Engine/string.php,v 1.5.2.5 2008/09/10 08:31:58 jan Exp $ + * + * Copyright 2005 rjan Persson + * Copyright 2005-2008 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @author rjan Persson + * @package Text_Diff + * @since 0.2.0 + */ +class Text_Diff_Engine_string { + + /** + * Parses a unified or context diff. + * + * First param contains the whole diff and the second can be used to force + * a specific diff type. If the second parameter is 'autodetect', the + * diff will be examined to find out which type of diff this is. + * + * @param string $diff The diff content. + * @param string $mode The diff mode of the content in $diff. One of + * 'context', 'unified', or 'autodetect'. + * + * @return array List of all diff operations. + */ + function diff($diff, $mode = 'autodetect') + { + if ($mode != 'autodetect' && $mode != 'context' && $mode != 'unified') { + return PEAR::raiseError('Type of diff is unsupported'); + } + + if ($mode == 'autodetect') { + $context = strpos($diff, '***'); + $unified = strpos($diff, '---'); + if ($context === $unified) { + return PEAR::raiseError('Type of diff could not be detected'); + } elseif ($context === false || $unified === false) { + $mode = $context !== false ? 'context' : 'unified'; + } else { + $mode = $context < $unified ? 'context' : 'unified'; + } + } + + // Split by new line and remove the diff header, if there is one. + $diff = explode("\n", $diff); + if (($mode == 'context' && strpos($diff[0], '***') === 0) || + ($mode == 'unified' && strpos($diff[0], '---') === 0)) { + array_shift($diff); + array_shift($diff); + } + + if ($mode == 'context') { + return $this->parseContextDiff($diff); + } else { + return $this->parseUnifiedDiff($diff); + } + } + + /** + * Parses an array containing the unified diff. + * + * @param array $diff Array of lines. + * + * @return array List of all diff operations. + */ + function parseUnifiedDiff($diff) + { + $edits = array(); + $end = count($diff) - 1; + for ($i = 0; $i < $end;) { + $diff1 = array(); + switch (substr($diff[$i], 0, 1)) { + case ' ': + do { + $diff1[] = substr($diff[$i], 1); + } while (++$i < $end && substr($diff[$i], 0, 1) == ' '); + $edits[] = new Text_Diff_Op_copy($diff1); + break; + + case '+': + // get all new lines + do { + $diff1[] = substr($diff[$i], 1); + } while (++$i < $end && substr($diff[$i], 0, 1) == '+'); + $edits[] = new Text_Diff_Op_add($diff1); + break; + + case '-': + // get changed or removed lines + $diff2 = array(); + do { + $diff1[] = substr($diff[$i], 1); + } while (++$i < $end && substr($diff[$i], 0, 1) == '-'); + + while ($i < $end && substr($diff[$i], 0, 1) == '+') { + $diff2[] = substr($diff[$i++], 1); + } + if (count($diff2) == 0) { + $edits[] = new Text_Diff_Op_delete($diff1); + } else { + $edits[] = new Text_Diff_Op_change($diff1, $diff2); + } + break; + + default: + $i++; + break; + } + } + + return $edits; + } + + /** + * Parses an array containing the context diff. + * + * @param array $diff Array of lines. + * + * @return array List of all diff operations. + */ + function parseContextDiff(&$diff) + { + $edits = array(); + $i = $max_i = $j = $max_j = 0; + $end = count($diff) - 1; + while ($i < $end && $j < $end) { + while ($i >= $max_i && $j >= $max_j) { + // Find the boundaries of the diff output of the two files + for ($i = $j; + $i < $end && substr($diff[$i], 0, 3) == '***'; + $i++); + for ($max_i = $i; + $max_i < $end && substr($diff[$max_i], 0, 3) != '---'; + $max_i++); + for ($j = $max_i; + $j < $end && substr($diff[$j], 0, 3) == '---'; + $j++); + for ($max_j = $j; + $max_j < $end && substr($diff[$max_j], 0, 3) != '***'; + $max_j++); + } + + // find what hasn't been changed + $array = array(); + while ($i < $max_i && + $j < $max_j && + strcmp($diff[$i], $diff[$j]) == 0) { + $array[] = substr($diff[$i], 2); + $i++; + $j++; + } + + while ($i < $max_i && ($max_j-$j) <= 1) { + if ($diff[$i] != '' && substr($diff[$i], 0, 1) != ' ') { + break; + } + $array[] = substr($diff[$i++], 2); + } + + while ($j < $max_j && ($max_i-$i) <= 1) { + if ($diff[$j] != '' && substr($diff[$j], 0, 1) != ' ') { + break; + } + $array[] = substr($diff[$j++], 2); + } + if (count($array) > 0) { + $edits[] = new Text_Diff_Op_copy($array); + } + + if ($i < $max_i) { + $diff1 = array(); + switch (substr($diff[$i], 0, 1)) { + case '!': + $diff2 = array(); + do { + $diff1[] = substr($diff[$i], 2); + if ($j < $max_j && substr($diff[$j], 0, 1) == '!') { + $diff2[] = substr($diff[$j++], 2); + } + } while (++$i < $max_i && substr($diff[$i], 0, 1) == '!'); + $edits[] = new Text_Diff_Op_change($diff1, $diff2); + break; + + case '+': + do { + $diff1[] = substr($diff[$i], 2); + } while (++$i < $max_i && substr($diff[$i], 0, 1) == '+'); + $edits[] = new Text_Diff_Op_add($diff1); + break; + + case '-': + do { + $diff1[] = substr($diff[$i], 2); + } while (++$i < $max_i && substr($diff[$i], 0, 1) == '-'); + $edits[] = new Text_Diff_Op_delete($diff1); + break; + } + } + + if ($j < $max_j) { + $diff2 = array(); + switch (substr($diff[$j], 0, 1)) { + case '+': + do { + $diff2[] = substr($diff[$j++], 2); + } while ($j < $max_j && substr($diff[$j], 0, 1) == '+'); + $edits[] = new Text_Diff_Op_add($diff2); + break; + + case '-': + do { + $diff2[] = substr($diff[$j++], 2); + } while ($j < $max_j && substr($diff[$j], 0, 1) == '-'); + $edits[] = new Text_Diff_Op_delete($diff2); + break; + } + } + } + + return $edits; + } + +} diff --git a/framework/gii/components/Pear/Text/Diff/Mapped.php b/framework/gii/components/Pear/Text/Diff/Mapped.php index 908e4de61..7f671844e 100644 --- a/framework/gii/components/Pear/Text/Diff/Mapped.php +++ b/framework/gii/components/Pear/Text/Diff/Mapped.php @@ -1,55 +1,55 @@ - - */ -class Text_Diff_Mapped extends Text_Diff { - - /** - * Computes a diff between sequences of strings. - * - * 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. - * @param array $to_lines An array of strings. - * @param array $mapped_from_lines This array should have the same size - * number of elements as $from_lines. The - * elements in $mapped_from_lines and - * $mapped_to_lines are what is actually - * compared when computing the diff. - * @param array $mapped_to_lines This array should have the same number - * of elements as $to_lines. - */ - function Text_Diff_Mapped($from_lines, $to_lines, - $mapped_from_lines, $mapped_to_lines) - { - assert(count($from_lines) == count($mapped_from_lines)); - assert(count($to_lines) == count($mapped_to_lines)); - - parent::Text_Diff($mapped_from_lines, $mapped_to_lines); - - $xi = $yi = 0; - for ($i = 0; $i < count($this->_edits); $i++) { - $orig = &$this->_edits[$i]->orig; - if (is_array($orig)) { - $orig = array_slice($from_lines, $xi, count($orig)); - $xi += count($orig); - } - - $final = &$this->_edits[$i]->final; - if (is_array($final)) { - $final = array_slice($to_lines, $yi, count($final)); - $yi += count($final); - } - } - } - -} + + */ +class Text_Diff_Mapped extends Text_Diff { + + /** + * Computes a diff between sequences of strings. + * + * 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. + * @param array $to_lines An array of strings. + * @param array $mapped_from_lines This array should have the same size + * number of elements as $from_lines. The + * elements in $mapped_from_lines and + * $mapped_to_lines are what is actually + * compared when computing the diff. + * @param array $mapped_to_lines This array should have the same number + * of elements as $to_lines. + */ + function Text_Diff_Mapped($from_lines, $to_lines, + $mapped_from_lines, $mapped_to_lines) + { + assert(count($from_lines) == count($mapped_from_lines)); + assert(count($to_lines) == count($mapped_to_lines)); + + parent::Text_Diff($mapped_from_lines, $mapped_to_lines); + + $xi = $yi = 0; + for ($i = 0; $i < count($this->_edits); $i++) { + $orig = &$this->_edits[$i]->orig; + if (is_array($orig)) { + $orig = array_slice($from_lines, $xi, count($orig)); + $xi += count($orig); + } + + $final = &$this->_edits[$i]->final; + if (is_array($final)) { + $final = array_slice($to_lines, $yi, count($final)); + $yi += count($final); + } + } + } + +} diff --git a/framework/gii/components/Pear/Text/Diff/Renderer/context.php b/framework/gii/components/Pear/Text/Diff/Renderer/context.php index 262ecefcb..79775006a 100644 --- a/framework/gii/components/Pear/Text/Diff/Renderer/context.php +++ b/framework/gii/components/Pear/Text/Diff/Renderer/context.php @@ -1,77 +1,77 @@ -_second_block = "--- $ybeg ----\n"; - return "***************\n*** $xbeg ****"; - } - - function _endBlock() - { - return $this->_second_block; - } - - function _context($lines) - { - $this->_second_block .= $this->_lines($lines, ' '); - return $this->_lines($lines, ' '); - } - - function _added($lines) - { - $this->_second_block .= $this->_lines($lines, '+ '); - return ''; - } - - function _deleted($lines) - { - return $this->_lines($lines, '- '); - } - - function _changed($orig, $final) - { - $this->_second_block .= $this->_lines($final, '! '); - return $this->_lines($orig, '! '); - } - -} +_second_block = "--- $ybeg ----\n"; + return "***************\n*** $xbeg ****"; + } + + function _endBlock() + { + return $this->_second_block; + } + + function _context($lines) + { + $this->_second_block .= $this->_lines($lines, ' '); + return $this->_lines($lines, ' '); + } + + function _added($lines) + { + $this->_second_block .= $this->_lines($lines, '+ '); + return ''; + } + + function _deleted($lines) + { + return $this->_lines($lines, '- '); + } + + function _changed($orig, $final) + { + $this->_second_block .= $this->_lines($final, '! '); + return $this->_lines($orig, '! '); + } + +} diff --git a/framework/gii/components/Pear/Text/Diff/Renderer/inline.php b/framework/gii/components/Pear/Text/Diff/Renderer/inline.php index ae4bf9883..7f4e5ef2c 100644 --- a/framework/gii/components/Pear/Text/Diff/Renderer/inline.php +++ b/framework/gii/components/Pear/Text/Diff/Renderer/inline.php @@ -1,170 +1,170 @@ -'; - - /** - * Suffix for inserted text. - */ - var $_ins_suffix = ''; - - /** - * Prefix for deleted text. - */ - var $_del_prefix = ''; - - /** - * Suffix for deleted text. - */ - var $_del_suffix = ''; - - /** - * Header for each change block. - */ - var $_block_header = ''; - - /** - * What are we currently splitting on? Used to recurse to show word-level - * changes. - */ - var $_split_level = 'lines'; - - function _blockHeader($xbeg, $xlen, $ybeg, $ylen) - { - return $this->_block_header; - } - - function _startBlock($header) - { - return $header; - } - - function _lines($lines, $prefix = ' ', $encode = true) - { - if ($encode) { - array_walk($lines, array(&$this, '_encode')); - } - - if ($this->_split_level == 'words') { - return implode('', $lines); - } else { - return implode("\n", $lines) . "\n"; - } - } - - function _added($lines) - { - array_walk($lines, array(&$this, '_encode')); - $lines[0] = $this->_ins_prefix . $lines[0]; - $lines[count($lines) - 1] .= $this->_ins_suffix; - return $this->_lines($lines, ' ', false); - } - - function _deleted($lines, $words = false) - { - array_walk($lines, array(&$this, '_encode')); - $lines[0] = $this->_del_prefix . $lines[0]; - $lines[count($lines) - 1] .= $this->_del_suffix; - return $this->_lines($lines, ' ', false); - } - - function _changed($orig, $final) - { - /* If we've already split on words, don't try to do so again - just - * display. */ - if ($this->_split_level == 'words') { - $prefix = ''; - while ($orig[0] !== false && $final[0] !== false && - substr($orig[0], 0, 1) == ' ' && - substr($final[0], 0, 1) == ' ') { - $prefix .= substr($orig[0], 0, 1); - $orig[0] = substr($orig[0], 1); - $final[0] = substr($final[0], 1); - } - return $prefix . $this->_deleted($orig) . $this->_added($final); - } - - $text1 = implode("\n", $orig); - $text2 = implode("\n", $final); - - /* Non-printing newline marker. */ - $nl = "\0"; - - /* We want to split on word boundaries, but we need to - * preserve whitespace as well. Therefore we split on words, - * but include all blocks of whitespace in the wordlist. */ - $diff = new Text_Diff($this->_splitOnWords($text1, $nl), - $this->_splitOnWords($text2, $nl)); - - /* Get the diff in inline format. */ - $renderer = new Text_Diff_Renderer_inline(array_merge($this->getParams(), - array('split_level' => 'words'))); - - /* Run the diff and get the output. */ - return str_replace($nl, "\n", $renderer->render($diff)) . "\n"; - } - - function _splitOnWords($string, $newlineEscape = "\n") - { - // Ignore \0; otherwise the while loop will never finish. - $string = str_replace("\0", '', $string); - - $words = array(); - $length = strlen($string); - $pos = 0; - - while ($pos < $length) { - // Eat a word with any preceding whitespace. - $spaces = strspn(substr($string, $pos), " \n"); - $nextpos = strcspn(substr($string, $pos + $spaces), " \n"); - $words[] = str_replace("\n", $newlineEscape, substr($string, $pos, $spaces + $nextpos)); - $pos += $spaces + $nextpos; - } - - return $words; - } - - function _encode(&$string) - { - $string = htmlspecialchars($string); - } - -} +'; + + /** + * Suffix for inserted text. + */ + var $_ins_suffix = ''; + + /** + * Prefix for deleted text. + */ + var $_del_prefix = ''; + + /** + * Suffix for deleted text. + */ + var $_del_suffix = ''; + + /** + * Header for each change block. + */ + var $_block_header = ''; + + /** + * What are we currently splitting on? Used to recurse to show word-level + * changes. + */ + var $_split_level = 'lines'; + + function _blockHeader($xbeg, $xlen, $ybeg, $ylen) + { + return $this->_block_header; + } + + function _startBlock($header) + { + return $header; + } + + function _lines($lines, $prefix = ' ', $encode = true) + { + if ($encode) { + array_walk($lines, array(&$this, '_encode')); + } + + if ($this->_split_level == 'words') { + return implode('', $lines); + } else { + return implode("\n", $lines) . "\n"; + } + } + + function _added($lines) + { + array_walk($lines, array(&$this, '_encode')); + $lines[0] = $this->_ins_prefix . $lines[0]; + $lines[count($lines) - 1] .= $this->_ins_suffix; + return $this->_lines($lines, ' ', false); + } + + function _deleted($lines, $words = false) + { + array_walk($lines, array(&$this, '_encode')); + $lines[0] = $this->_del_prefix . $lines[0]; + $lines[count($lines) - 1] .= $this->_del_suffix; + return $this->_lines($lines, ' ', false); + } + + function _changed($orig, $final) + { + /* If we've already split on words, don't try to do so again - just + * display. */ + if ($this->_split_level == 'words') { + $prefix = ''; + while ($orig[0] !== false && $final[0] !== false && + substr($orig[0], 0, 1) == ' ' && + substr($final[0], 0, 1) == ' ') { + $prefix .= substr($orig[0], 0, 1); + $orig[0] = substr($orig[0], 1); + $final[0] = substr($final[0], 1); + } + return $prefix . $this->_deleted($orig) . $this->_added($final); + } + + $text1 = implode("\n", $orig); + $text2 = implode("\n", $final); + + /* Non-printing newline marker. */ + $nl = "\0"; + + /* We want to split on word boundaries, but we need to + * preserve whitespace as well. Therefore we split on words, + * but include all blocks of whitespace in the wordlist. */ + $diff = new Text_Diff($this->_splitOnWords($text1, $nl), + $this->_splitOnWords($text2, $nl)); + + /* Get the diff in inline format. */ + $renderer = new Text_Diff_Renderer_inline(array_merge($this->getParams(), + array('split_level' => 'words'))); + + /* Run the diff and get the output. */ + return str_replace($nl, "\n", $renderer->render($diff)) . "\n"; + } + + function _splitOnWords($string, $newlineEscape = "\n") + { + // Ignore \0; otherwise the while loop will never finish. + $string = str_replace("\0", '', $string); + + $words = array(); + $length = strlen($string); + $pos = 0; + + while ($pos < $length) { + // Eat a word with any preceding whitespace. + $spaces = strspn(substr($string, $pos), " \n"); + $nextpos = strcspn(substr($string, $pos + $spaces), " \n"); + $words[] = str_replace("\n", $newlineEscape, substr($string, $pos, $spaces + $nextpos)); + $pos += $spaces + $nextpos; + } + + return $words; + } + + function _encode(&$string) + { + $string = htmlspecialchars($string); + } + +} diff --git a/framework/gii/components/Pear/Text/Diff/Renderer/unified.php b/framework/gii/components/Pear/Text/Diff/Renderer/unified.php index 90f053879..943d519ac 100644 --- a/framework/gii/components/Pear/Text/Diff/Renderer/unified.php +++ b/framework/gii/components/Pear/Text/Diff/Renderer/unified.php @@ -1,67 +1,67 @@ -_lines($lines, ' '); - } - - function _added($lines) - { - return $this->_lines($lines, '+'); - } - - function _deleted($lines) - { - return $this->_lines($lines, '-'); - } - - function _changed($orig, $final) - { - return $this->_deleted($orig) . $this->_added($final); - } - -} +_lines($lines, ' '); + } + + function _added($lines) + { + return $this->_lines($lines, '+'); + } + + function _deleted($lines) + { + return $this->_lines($lines, '-'); + } + + function _changed($orig, $final) + { + return $this->_deleted($orig) . $this->_added($final); + } + +} diff --git a/framework/gii/components/Pear/Text/Diff/ThreeWay.php b/framework/gii/components/Pear/Text/Diff/ThreeWay.php index 0ed96fdf2..4e4b939d0 100644 --- a/framework/gii/components/Pear/Text/Diff/ThreeWay.php +++ b/framework/gii/components/Pear/Text/Diff/ThreeWay.php @@ -1,276 +1,276 @@ - - */ -class Text_Diff_ThreeWay extends Text_Diff { - - /** - * Conflict counter. - * - * @var integer - */ - var $_conflictingBlocks = 0; - - /** - * Computes diff between 3 sequences of strings. - * - * @param array $orig The original lines to use. - * @param array $final1 The first version to compare to. - * @param array $final2 The second version to compare to. - */ - function Text_Diff_ThreeWay($orig, $final1, $final2) - { - if (extension_loaded('xdiff')) { - $engine = new Text_Diff_Engine_xdiff(); - } else { - $engine = new Text_Diff_Engine_native(); - } - - $this->_edits = $this->_diff3($engine->diff($orig, $final1), - $engine->diff($orig, $final2)); - } - - /** - */ - function mergedOutput($label1 = false, $label2 = false) - { - $lines = array(); - foreach ($this->_edits as $edit) { - if ($edit->isConflict()) { - /* FIXME: this should probably be moved somewhere else. */ - $lines = array_merge($lines, - array('<<<<<<<' . ($label1 ? ' ' . $label1 : '')), - $edit->final1, - array("======="), - $edit->final2, - array('>>>>>>>' . ($label2 ? ' ' . $label2 : ''))); - $this->_conflictingBlocks++; - } else { - $lines = array_merge($lines, $edit->merged()); - } - } - - return $lines; - } - - /** - * @access private - */ - function _diff3($edits1, $edits2) - { - $edits = array(); - $bb = new Text_Diff_ThreeWay_BlockBuilder(); - - $e1 = current($edits1); - $e2 = current($edits2); - while ($e1 || $e2) { - if ($e1 && $e2 && is_a($e1, 'Text_Diff_Op_copy') && is_a($e2, 'Text_Diff_Op_copy')) { - /* We have copy blocks from both diffs. This is the (only) - * time we want to emit a diff3 copy block. Flush current - * diff3 diff block, if any. */ - if ($edit = $bb->finish()) { - $edits[] = $edit; - } - - $ncopy = min($e1->norig(), $e2->norig()); - assert($ncopy > 0); - $edits[] = new Text_Diff_ThreeWay_Op_copy(array_slice($e1->orig, 0, $ncopy)); - - if ($e1->norig() > $ncopy) { - array_splice($e1->orig, 0, $ncopy); - array_splice($e1->final, 0, $ncopy); - } else { - $e1 = next($edits1); - } - - if ($e2->norig() > $ncopy) { - array_splice($e2->orig, 0, $ncopy); - array_splice($e2->final, 0, $ncopy); - } else { - $e2 = next($edits2); - } - } else { - if ($e1 && $e2) { - if ($e1->orig && $e2->orig) { - $norig = min($e1->norig(), $e2->norig()); - $orig = array_splice($e1->orig, 0, $norig); - array_splice($e2->orig, 0, $norig); - $bb->input($orig); - } - - if (is_a($e1, 'Text_Diff_Op_copy')) { - $bb->out1(array_splice($e1->final, 0, $norig)); - } - - if (is_a($e2, 'Text_Diff_Op_copy')) { - $bb->out2(array_splice($e2->final, 0, $norig)); - } - } - - if ($e1 && ! $e1->orig) { - $bb->out1($e1->final); - $e1 = next($edits1); - } - if ($e2 && ! $e2->orig) { - $bb->out2($e2->final); - $e2 = next($edits2); - } - } - } - - if ($edit = $bb->finish()) { - $edits[] = $edit; - } - - return $edits; - } - -} - -/** - * @package Text_Diff - * @author Geoffrey T. Dairiki - * - * @access private - */ -class Text_Diff_ThreeWay_Op { - - function Text_Diff_ThreeWay_Op($orig = false, $final1 = false, $final2 = false) - { - $this->orig = $orig ? $orig : array(); - $this->final1 = $final1 ? $final1 : array(); - $this->final2 = $final2 ? $final2 : array(); - } - - function merged() - { - if (!isset($this->_merged)) { - if ($this->final1 === $this->final2) { - $this->_merged = &$this->final1; - } elseif ($this->final1 === $this->orig) { - $this->_merged = &$this->final2; - } elseif ($this->final2 === $this->orig) { - $this->_merged = &$this->final1; - } else { - $this->_merged = false; - } - } - - return $this->_merged; - } - - function isConflict() - { - return $this->merged() === false; - } - -} - -/** - * @package Text_Diff - * @author Geoffrey T. Dairiki - * - * @access private - */ -class Text_Diff_ThreeWay_Op_copy extends Text_Diff_ThreeWay_Op { - - function Text_Diff_ThreeWay_Op_Copy($lines = false) - { - $this->orig = $lines ? $lines : array(); - $this->final1 = &$this->orig; - $this->final2 = &$this->orig; - } - - function merged() - { - return $this->orig; - } - - function isConflict() - { - return false; - } - -} - -/** - * @package Text_Diff - * @author Geoffrey T. Dairiki - * - * @access private - */ -class Text_Diff_ThreeWay_BlockBuilder { - - function Text_Diff_ThreeWay_BlockBuilder() - { - $this->_init(); - } - - function input($lines) - { - if ($lines) { - $this->_append($this->orig, $lines); - } - } - - function out1($lines) - { - if ($lines) { - $this->_append($this->final1, $lines); - } - } - - function out2($lines) - { - if ($lines) { - $this->_append($this->final2, $lines); - } - } - - function isEmpty() - { - return !$this->orig && !$this->final1 && !$this->final2; - } - - function finish() - { - if ($this->isEmpty()) { - return false; - } else { - $edit = new Text_Diff_ThreeWay_Op($this->orig, $this->final1, $this->final2); - $this->_init(); - return $edit; - } - } - - function _init() - { - $this->orig = $this->final1 = $this->final2 = array(); - } - - function _append(&$array, $lines) - { - array_splice($array, sizeof($array), 0, $lines); - } - -} + + */ +class Text_Diff_ThreeWay extends Text_Diff { + + /** + * Conflict counter. + * + * @var integer + */ + var $_conflictingBlocks = 0; + + /** + * Computes diff between 3 sequences of strings. + * + * @param array $orig The original lines to use. + * @param array $final1 The first version to compare to. + * @param array $final2 The second version to compare to. + */ + function Text_Diff_ThreeWay($orig, $final1, $final2) + { + if (extension_loaded('xdiff')) { + $engine = new Text_Diff_Engine_xdiff(); + } else { + $engine = new Text_Diff_Engine_native(); + } + + $this->_edits = $this->_diff3($engine->diff($orig, $final1), + $engine->diff($orig, $final2)); + } + + /** + */ + function mergedOutput($label1 = false, $label2 = false) + { + $lines = array(); + foreach ($this->_edits as $edit) { + if ($edit->isConflict()) { + /* FIXME: this should probably be moved somewhere else. */ + $lines = array_merge($lines, + array('<<<<<<<' . ($label1 ? ' ' . $label1 : '')), + $edit->final1, + array("======="), + $edit->final2, + array('>>>>>>>' . ($label2 ? ' ' . $label2 : ''))); + $this->_conflictingBlocks++; + } else { + $lines = array_merge($lines, $edit->merged()); + } + } + + return $lines; + } + + /** + * @access private + */ + function _diff3($edits1, $edits2) + { + $edits = array(); + $bb = new Text_Diff_ThreeWay_BlockBuilder(); + + $e1 = current($edits1); + $e2 = current($edits2); + while ($e1 || $e2) { + if ($e1 && $e2 && is_a($e1, 'Text_Diff_Op_copy') && is_a($e2, 'Text_Diff_Op_copy')) { + /* We have copy blocks from both diffs. This is the (only) + * time we want to emit a diff3 copy block. Flush current + * diff3 diff block, if any. */ + if ($edit = $bb->finish()) { + $edits[] = $edit; + } + + $ncopy = min($e1->norig(), $e2->norig()); + assert($ncopy > 0); + $edits[] = new Text_Diff_ThreeWay_Op_copy(array_slice($e1->orig, 0, $ncopy)); + + if ($e1->norig() > $ncopy) { + array_splice($e1->orig, 0, $ncopy); + array_splice($e1->final, 0, $ncopy); + } else { + $e1 = next($edits1); + } + + if ($e2->norig() > $ncopy) { + array_splice($e2->orig, 0, $ncopy); + array_splice($e2->final, 0, $ncopy); + } else { + $e2 = next($edits2); + } + } else { + if ($e1 && $e2) { + if ($e1->orig && $e2->orig) { + $norig = min($e1->norig(), $e2->norig()); + $orig = array_splice($e1->orig, 0, $norig); + array_splice($e2->orig, 0, $norig); + $bb->input($orig); + } + + if (is_a($e1, 'Text_Diff_Op_copy')) { + $bb->out1(array_splice($e1->final, 0, $norig)); + } + + if (is_a($e2, 'Text_Diff_Op_copy')) { + $bb->out2(array_splice($e2->final, 0, $norig)); + } + } + + if ($e1 && ! $e1->orig) { + $bb->out1($e1->final); + $e1 = next($edits1); + } + if ($e2 && ! $e2->orig) { + $bb->out2($e2->final); + $e2 = next($edits2); + } + } + } + + if ($edit = $bb->finish()) { + $edits[] = $edit; + } + + return $edits; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki + * + * @access private + */ +class Text_Diff_ThreeWay_Op { + + function Text_Diff_ThreeWay_Op($orig = false, $final1 = false, $final2 = false) + { + $this->orig = $orig ? $orig : array(); + $this->final1 = $final1 ? $final1 : array(); + $this->final2 = $final2 ? $final2 : array(); + } + + function merged() + { + if (!isset($this->_merged)) { + if ($this->final1 === $this->final2) { + $this->_merged = &$this->final1; + } elseif ($this->final1 === $this->orig) { + $this->_merged = &$this->final2; + } elseif ($this->final2 === $this->orig) { + $this->_merged = &$this->final1; + } else { + $this->_merged = false; + } + } + + return $this->_merged; + } + + function isConflict() + { + return $this->merged() === false; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki + * + * @access private + */ +class Text_Diff_ThreeWay_Op_copy extends Text_Diff_ThreeWay_Op { + + function Text_Diff_ThreeWay_Op_Copy($lines = false) + { + $this->orig = $lines ? $lines : array(); + $this->final1 = &$this->orig; + $this->final2 = &$this->orig; + } + + function merged() + { + return $this->orig; + } + + function isConflict() + { + return false; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki + * + * @access private + */ +class Text_Diff_ThreeWay_BlockBuilder { + + function Text_Diff_ThreeWay_BlockBuilder() + { + $this->_init(); + } + + function input($lines) + { + if ($lines) { + $this->_append($this->orig, $lines); + } + } + + function out1($lines) + { + if ($lines) { + $this->_append($this->final1, $lines); + } + } + + function out2($lines) + { + if ($lines) { + $this->_append($this->final2, $lines); + } + } + + function isEmpty() + { + return !$this->orig && !$this->final1 && !$this->final2; + } + + function finish() + { + if ($this->isEmpty()) { + return false; + } else { + $edit = new Text_Diff_ThreeWay_Op($this->orig, $this->final1, $this->final2); + $this->_init(); + return $edit; + } + } + + function _init() + { + $this->orig = $this->final1 = $this->final2 = array(); + } + + function _append(&$array, $lines) + { + array_splice($array, sizeof($array), 0, $lines); + } + +} diff --git a/framework/gii/components/Pear/Text/Diff3.php b/framework/gii/components/Pear/Text/Diff3.php index 525c2b8a8..2c2837070 100644 --- a/framework/gii/components/Pear/Text/Diff3.php +++ b/framework/gii/components/Pear/Text/Diff3.php @@ -1,276 +1,276 @@ - - */ -class Text_Diff3 extends Text_Diff { - - /** - * Conflict counter. - * - * @var integer - */ - var $_conflictingBlocks = 0; - - /** - * Computes diff between 3 sequences of strings. - * - * @param array $orig The original lines to use. - * @param array $final1 The first version to compare to. - * @param array $final2 The second version to compare to. - */ - function Text_Diff3($orig, $final1, $final2) - { - if (extension_loaded('xdiff')) { - $engine = new Text_Diff_Engine_xdiff(); - } else { - $engine = new Text_Diff_Engine_native(); - } - - $this->_edits = $this->_diff3($engine->diff($orig, $final1), - $engine->diff($orig, $final2)); - } - - /** - */ - function mergedOutput($label1 = false, $label2 = false) - { - $lines = array(); - foreach ($this->_edits as $edit) { - if ($edit->isConflict()) { - /* FIXME: this should probably be moved somewhere else. */ - $lines = array_merge($lines, - array('<<<<<<<' . ($label1 ? ' ' . $label1 : '')), - $edit->final1, - array("======="), - $edit->final2, - array('>>>>>>>' . ($label2 ? ' ' . $label2 : ''))); - $this->_conflictingBlocks++; - } else { - $lines = array_merge($lines, $edit->merged()); - } - } - - return $lines; - } - - /** - * @access private - */ - function _diff3($edits1, $edits2) - { - $edits = array(); - $bb = new Text_Diff3_BlockBuilder(); - - $e1 = current($edits1); - $e2 = current($edits2); - while ($e1 || $e2) { - if ($e1 && $e2 && is_a($e1, 'Text_Diff_Op_copy') && is_a($e2, 'Text_Diff_Op_copy')) { - /* We have copy blocks from both diffs. This is the (only) - * time we want to emit a diff3 copy block. Flush current - * diff3 diff block, if any. */ - if ($edit = $bb->finish()) { - $edits[] = $edit; - } - - $ncopy = min($e1->norig(), $e2->norig()); - assert($ncopy > 0); - $edits[] = new Text_Diff3_Op_copy(array_slice($e1->orig, 0, $ncopy)); - - if ($e1->norig() > $ncopy) { - array_splice($e1->orig, 0, $ncopy); - array_splice($e1->final, 0, $ncopy); - } else { - $e1 = next($edits1); - } - - if ($e2->norig() > $ncopy) { - array_splice($e2->orig, 0, $ncopy); - array_splice($e2->final, 0, $ncopy); - } else { - $e2 = next($edits2); - } - } else { - if ($e1 && $e2) { - if ($e1->orig && $e2->orig) { - $norig = min($e1->norig(), $e2->norig()); - $orig = array_splice($e1->orig, 0, $norig); - array_splice($e2->orig, 0, $norig); - $bb->input($orig); - } - - if (is_a($e1, 'Text_Diff_Op_copy')) { - $bb->out1(array_splice($e1->final, 0, $norig)); - } - - if (is_a($e2, 'Text_Diff_Op_copy')) { - $bb->out2(array_splice($e2->final, 0, $norig)); - } - } - - if ($e1 && ! $e1->orig) { - $bb->out1($e1->final); - $e1 = next($edits1); - } - if ($e2 && ! $e2->orig) { - $bb->out2($e2->final); - $e2 = next($edits2); - } - } - } - - if ($edit = $bb->finish()) { - $edits[] = $edit; - } - - return $edits; - } - -} - -/** - * @package Text_Diff - * @author Geoffrey T. Dairiki - * - * @access private - */ -class Text_Diff3_Op { - - function Text_Diff3_Op($orig = false, $final1 = false, $final2 = false) - { - $this->orig = $orig ? $orig : array(); - $this->final1 = $final1 ? $final1 : array(); - $this->final2 = $final2 ? $final2 : array(); - } - - function merged() - { - if (!isset($this->_merged)) { - if ($this->final1 === $this->final2) { - $this->_merged = &$this->final1; - } elseif ($this->final1 === $this->orig) { - $this->_merged = &$this->final2; - } elseif ($this->final2 === $this->orig) { - $this->_merged = &$this->final1; - } else { - $this->_merged = false; - } - } - - return $this->_merged; - } - - function isConflict() - { - return $this->merged() === false; - } - -} - -/** - * @package Text_Diff - * @author Geoffrey T. Dairiki - * - * @access private - */ -class Text_Diff3_Op_copy extends Text_Diff3_Op { - - function Text_Diff3_Op_Copy($lines = false) - { - $this->orig = $lines ? $lines : array(); - $this->final1 = &$this->orig; - $this->final2 = &$this->orig; - } - - function merged() - { - return $this->orig; - } - - function isConflict() - { - return false; - } - -} - -/** - * @package Text_Diff - * @author Geoffrey T. Dairiki - * - * @access private - */ -class Text_Diff3_BlockBuilder { - - function Text_Diff3_BlockBuilder() - { - $this->_init(); - } - - function input($lines) - { - if ($lines) { - $this->_append($this->orig, $lines); - } - } - - function out1($lines) - { - if ($lines) { - $this->_append($this->final1, $lines); - } - } - - function out2($lines) - { - if ($lines) { - $this->_append($this->final2, $lines); - } - } - - function isEmpty() - { - return !$this->orig && !$this->final1 && !$this->final2; - } - - function finish() - { - if ($this->isEmpty()) { - return false; - } else { - $edit = new Text_Diff3_Op($this->orig, $this->final1, $this->final2); - $this->_init(); - return $edit; - } - } - - function _init() - { - $this->orig = $this->final1 = $this->final2 = array(); - } - - function _append(&$array, $lines) - { - array_splice($array, sizeof($array), 0, $lines); - } - -} + + */ +class Text_Diff3 extends Text_Diff { + + /** + * Conflict counter. + * + * @var integer + */ + var $_conflictingBlocks = 0; + + /** + * Computes diff between 3 sequences of strings. + * + * @param array $orig The original lines to use. + * @param array $final1 The first version to compare to. + * @param array $final2 The second version to compare to. + */ + function Text_Diff3($orig, $final1, $final2) + { + if (extension_loaded('xdiff')) { + $engine = new Text_Diff_Engine_xdiff(); + } else { + $engine = new Text_Diff_Engine_native(); + } + + $this->_edits = $this->_diff3($engine->diff($orig, $final1), + $engine->diff($orig, $final2)); + } + + /** + */ + function mergedOutput($label1 = false, $label2 = false) + { + $lines = array(); + foreach ($this->_edits as $edit) { + if ($edit->isConflict()) { + /* FIXME: this should probably be moved somewhere else. */ + $lines = array_merge($lines, + array('<<<<<<<' . ($label1 ? ' ' . $label1 : '')), + $edit->final1, + array("======="), + $edit->final2, + array('>>>>>>>' . ($label2 ? ' ' . $label2 : ''))); + $this->_conflictingBlocks++; + } else { + $lines = array_merge($lines, $edit->merged()); + } + } + + return $lines; + } + + /** + * @access private + */ + function _diff3($edits1, $edits2) + { + $edits = array(); + $bb = new Text_Diff3_BlockBuilder(); + + $e1 = current($edits1); + $e2 = current($edits2); + while ($e1 || $e2) { + if ($e1 && $e2 && is_a($e1, 'Text_Diff_Op_copy') && is_a($e2, 'Text_Diff_Op_copy')) { + /* We have copy blocks from both diffs. This is the (only) + * time we want to emit a diff3 copy block. Flush current + * diff3 diff block, if any. */ + if ($edit = $bb->finish()) { + $edits[] = $edit; + } + + $ncopy = min($e1->norig(), $e2->norig()); + assert($ncopy > 0); + $edits[] = new Text_Diff3_Op_copy(array_slice($e1->orig, 0, $ncopy)); + + if ($e1->norig() > $ncopy) { + array_splice($e1->orig, 0, $ncopy); + array_splice($e1->final, 0, $ncopy); + } else { + $e1 = next($edits1); + } + + if ($e2->norig() > $ncopy) { + array_splice($e2->orig, 0, $ncopy); + array_splice($e2->final, 0, $ncopy); + } else { + $e2 = next($edits2); + } + } else { + if ($e1 && $e2) { + if ($e1->orig && $e2->orig) { + $norig = min($e1->norig(), $e2->norig()); + $orig = array_splice($e1->orig, 0, $norig); + array_splice($e2->orig, 0, $norig); + $bb->input($orig); + } + + if (is_a($e1, 'Text_Diff_Op_copy')) { + $bb->out1(array_splice($e1->final, 0, $norig)); + } + + if (is_a($e2, 'Text_Diff_Op_copy')) { + $bb->out2(array_splice($e2->final, 0, $norig)); + } + } + + if ($e1 && ! $e1->orig) { + $bb->out1($e1->final); + $e1 = next($edits1); + } + if ($e2 && ! $e2->orig) { + $bb->out2($e2->final); + $e2 = next($edits2); + } + } + } + + if ($edit = $bb->finish()) { + $edits[] = $edit; + } + + return $edits; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki + * + * @access private + */ +class Text_Diff3_Op { + + function Text_Diff3_Op($orig = false, $final1 = false, $final2 = false) + { + $this->orig = $orig ? $orig : array(); + $this->final1 = $final1 ? $final1 : array(); + $this->final2 = $final2 ? $final2 : array(); + } + + function merged() + { + if (!isset($this->_merged)) { + if ($this->final1 === $this->final2) { + $this->_merged = &$this->final1; + } elseif ($this->final1 === $this->orig) { + $this->_merged = &$this->final2; + } elseif ($this->final2 === $this->orig) { + $this->_merged = &$this->final1; + } else { + $this->_merged = false; + } + } + + return $this->_merged; + } + + function isConflict() + { + return $this->merged() === false; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki + * + * @access private + */ +class Text_Diff3_Op_copy extends Text_Diff3_Op { + + function Text_Diff3_Op_Copy($lines = false) + { + $this->orig = $lines ? $lines : array(); + $this->final1 = &$this->orig; + $this->final2 = &$this->orig; + } + + function merged() + { + return $this->orig; + } + + function isConflict() + { + return false; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki + * + * @access private + */ +class Text_Diff3_BlockBuilder { + + function Text_Diff3_BlockBuilder() + { + $this->_init(); + } + + function input($lines) + { + if ($lines) { + $this->_append($this->orig, $lines); + } + } + + function out1($lines) + { + if ($lines) { + $this->_append($this->final1, $lines); + } + } + + function out2($lines) + { + if ($lines) { + $this->_append($this->final2, $lines); + } + } + + function isEmpty() + { + return !$this->orig && !$this->final1 && !$this->final2; + } + + function finish() + { + if ($this->isEmpty()) { + return false; + } else { + $edit = new Text_Diff3_Op($this->orig, $this->final1, $this->final2); + $this->_init(); + return $edit; + } + } + + function _init() + { + $this->orig = $this->final1 = $this->final2 = array(); + } + + function _append(&$array, $lines) + { + array_splice($array, sizeof($array), 0, $lines); + } + +} diff --git a/framework/i18n/data/README.txt b/framework/i18n/data/README.txt index c37fda376..2bbbabb04 100644 --- a/framework/i18n/data/README.txt +++ b/framework/i18n/data/README.txt @@ -1,10 +1,10 @@ - - CLDR v23.1 (May 15, 2013) - -This directory contains the CLDR data files in form of PHP scripts. -They are obtained by extracting the CLDR data (http://cldr.unicode.org/index/downloads/cldr-23-1) -with the script "build/build cldr". - -Only the data relevant to date and number formatting are extracted. -Each PHP file contains an array representing the data for a particular -locale. Data inherited from parent locales are also in the array. + + CLDR v23.1 (May 15, 2013) + +This directory contains the CLDR data files in form of PHP scripts. +They are obtained by extracting the CLDR data (http://cldr.unicode.org/index/downloads/cldr-23-1) +with the script "build/build cldr". + +Only the data relevant to date and number formatting are extracted. +Each PHP file contains an array representing the data for a particular +locale. Data inherited from parent locales are also in the array. diff --git a/framework/messages/it/zii.php b/framework/messages/it/zii.php index 7fe96bc62..3eab7b534 100644 --- a/framework/messages/it/zii.php +++ b/framework/messages/it/zii.php @@ -1,36 +1,36 @@ - 'Home', - 'The button type "{type}" is not supported.' => 'Il pulsante "{type}" non è supportato.', - 'Are you sure you want to delete this item?' => 'Sei sicuro di voler cancellare questo oggetto?', - 'Delete' => 'Cancella', - 'Displaying {start}-{end} of 1 result.|Displaying {start}-{end} of {count} results.' => 'Visualizzazione {start}-{end} di 1 risultato.|Visualizzazione {start}-{end} di {count} risultati.', - 'Either "name" or "value" must be specified for CDataColumn.' => 'Per CDataColumn deve essere specificato o il "name" o il "value".', - 'No results found.' => 'Nessun risultato trovato.', - 'Not set' => 'Non impostato', - 'Please specify the "attributes" property.' => 'Occorre specificare la proprietà "attributes".', - 'Please specify the "data" property.' => 'Occorre specificare la proprietà "data".', - 'Sort by: ' => 'Ordina per:', - 'The "dataProvider" property cannot be empty.' => 'La proprietà "dataProvider" non può essere vuota.', - 'The attribute must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.' => 'L\'attributo deve essere specificato nel formato "Name:Type:Label", dove "Type" e "Label" sono opzionali.', - 'The column must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.' => 'La colonna deve essere specificata nel formato "Name:Type:Label", dove "Type" e "Label" sono opzionali.', - 'The property "itemView" cannot be empty.' => 'La proprietà "itemView" non può essere vuota.', - 'Total 1 result.|Total {count} results.' => 'Totale di 1 risultato.|Totale di {count} risultati.', - 'Update' => 'Aggiorna', - 'View' => 'Vedi', - '{class} must specify "model" and "attribute" or "name" property values.' => '{class} deve specificare il/i valore/i delle proprietà "model" e "attribute" o "name".', + 'Home', + 'The button type "{type}" is not supported.' => 'Il pulsante "{type}" non è supportato.', + 'Are you sure you want to delete this item?' => 'Sei sicuro di voler cancellare questo oggetto?', + 'Delete' => 'Cancella', + 'Displaying {start}-{end} of 1 result.|Displaying {start}-{end} of {count} results.' => 'Visualizzazione {start}-{end} di 1 risultato.|Visualizzazione {start}-{end} di {count} risultati.', + 'Either "name" or "value" must be specified for CDataColumn.' => 'Per CDataColumn deve essere specificato o il "name" o il "value".', + 'No results found.' => 'Nessun risultato trovato.', + 'Not set' => 'Non impostato', + 'Please specify the "attributes" property.' => 'Occorre specificare la proprietà "attributes".', + 'Please specify the "data" property.' => 'Occorre specificare la proprietà "data".', + 'Sort by: ' => 'Ordina per:', + 'The "dataProvider" property cannot be empty.' => 'La proprietà "dataProvider" non può essere vuota.', + 'The attribute must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.' => 'L\'attributo deve essere specificato nel formato "Name:Type:Label", dove "Type" e "Label" sono opzionali.', + 'The column must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.' => 'La colonna deve essere specificata nel formato "Name:Type:Label", dove "Type" e "Label" sono opzionali.', + 'The property "itemView" cannot be empty.' => 'La proprietà "itemView" non può essere vuota.', + 'Total 1 result.|Total {count} results.' => 'Totale di 1 risultato.|Totale di {count} risultati.', + 'Update' => 'Aggiorna', + 'View' => 'Vedi', + '{class} must specify "model" and "attribute" or "name" property values.' => '{class} deve specificare il/i valore/i delle proprietà "model" e "attribute" o "name".', ); \ No newline at end of file diff --git a/framework/messages/lv/zii.php b/framework/messages/lv/zii.php index a074ccb6f..ecfe5a747 100644 --- a/framework/messages/lv/zii.php +++ b/framework/messages/lv/zii.php @@ -1,34 +1,34 @@ - 'Vai patiešām vēlaties dzēst šo vienību?', - 'Delete' => 'Dzēst', - 'Displaying {start}-{end} of 1 result.|Displaying {start}-{end} of {count} results.' => 'Attēlo no {start}. līdz {end}. no pavisam {count} ieraksta(-iem).', - 'Either "name" or "value" must be specified for CDataColumn.' => 'CDataColumn nepieciešams norādīt "name" vai "value".', - 'No results found.' => 'Dati netika atrasti.', - 'Not set' => 'Nav uzstādīts', - 'Please specify the "attributes" property.' => 'Norādiet mainīgo "attributes".', - 'Please specify the "data" property.' => 'Norādiet mainīgo "data".', - 'Sort by: ' => 'Kārtot pēc: ', - 'The "dataProvider" property cannot be empty.' => 'Mainīgais "dataProvider" nedrīkst būt tukšs.', - 'The attribute must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.' => 'Atribūtam ir jābūt norādītam formātā "Nosaukums:Veids:Apraksts", kur "Veids" un "Apraksts" ir obligāti.', - 'The column must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.' => 'Kolonnai ir jābūt iformātā "Nosaukums:Veids:Apraksts", kur "Veids" un "Apraksts" ir obligāti.', - 'The property "itemView" cannot be empty.' => 'Mainīgais "itemView" nedrīkst būt tukšs.', - 'Total 1 result.|Total {count} results.' => 'Kopā {count} ieraksts(-i).', - 'Update' => 'Labot', - 'View' => 'Skatīties', - '{class} must specify "model" and "attribute" or "name" property values.' => 'Klasei {class} ir jābūt norādītiem vienam no mainīgajiem "attribute" vai "name".', -); + 'Vai patiešām vēlaties dzēst šo vienību?', + 'Delete' => 'Dzēst', + 'Displaying {start}-{end} of 1 result.|Displaying {start}-{end} of {count} results.' => 'Attēlo no {start}. līdz {end}. no pavisam {count} ieraksta(-iem).', + 'Either "name" or "value" must be specified for CDataColumn.' => 'CDataColumn nepieciešams norādīt "name" vai "value".', + 'No results found.' => 'Dati netika atrasti.', + 'Not set' => 'Nav uzstādīts', + 'Please specify the "attributes" property.' => 'Norādiet mainīgo "attributes".', + 'Please specify the "data" property.' => 'Norādiet mainīgo "data".', + 'Sort by: ' => 'Kārtot pēc: ', + 'The "dataProvider" property cannot be empty.' => 'Mainīgais "dataProvider" nedrīkst būt tukšs.', + 'The attribute must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.' => 'Atribūtam ir jābūt norādītam formātā "Nosaukums:Veids:Apraksts", kur "Veids" un "Apraksts" ir obligāti.', + 'The column must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.' => 'Kolonnai ir jābūt iformātā "Nosaukums:Veids:Apraksts", kur "Veids" un "Apraksts" ir obligāti.', + 'The property "itemView" cannot be empty.' => 'Mainīgais "itemView" nedrīkst būt tukšs.', + 'Total 1 result.|Total {count} results.' => 'Kopā {count} ieraksts(-i).', + 'Update' => 'Labot', + 'View' => 'Skatīties', + '{class} must specify "model" and "attribute" or "name" property values.' => 'Klasei {class} ir jābūt norādītiem vienam no mainīgajiem "attribute" vai "name".', +); diff --git a/framework/messages/th/yii.php b/framework/messages/th/yii.php index 0c15e5908..ada7e0625 100644 --- a/framework/messages/th/yii.php +++ b/framework/messages/th/yii.php @@ -1,211 +1,211 @@ - '< หน้าที่แล้ว', - '<< First' => '<< หน้าแรก', - 'Go to page: ' => 'ไปที่หน้า: ', - 'Last >>' => 'หน้าสุดท้าย >>', - 'Next >' => 'หน้าถัดไป >', - 'The asset "{asset}" to be published does not exist.' => 'ไม่พบข้อมูล "{asset}" ที่ถูกใช้งาน', - '"{path}" is not a valid directory.' => 'ไม่พบไดเรกทอรี่ "{path}"', - 'Active Record requires a "db" CDbConnection application component.' => 'ต้องการใช้งานข้อมูล "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}" ได้ โดยจะต้องทำการระบุความสัมพันธ์ ซึ่งข้อมูลที่ใช้งานจะต้องเป็นคีย์เชื่อมความสัมพันธ์ในตารางข้อมูล', - '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.' => 'ข้อมูลที่ใช้งาน "{class}" ไม่สามารถเลือกฟิลด์ "{column}" ได้ คำแนะนำ, ชื่อฟิลด์จะต้องมีอยู่ในตารางหรือระบุเป็นชื่อเสมือนของฟิลด์อื่นๆ', - 'Alias "{alias}" is invalid. Make sure it points to an existing directory or file.' => 'ชื่อเสมือน "{alias}" ไม่ถูกต้อง กรุณาตรวจสอบความถูกต้องของไดเรกทอรี่หรือไฟล์', - 'Application base path "{path}" is not a valid directory.' => 'ที่อยู่โปรแกรมหลัก "{path}" ไม่ถูกต้อง', - 'Application runtime path "{path}" is not valid. Please make sure it is a directory writable by the Web server process.' => 'ที่อยู่โปรแกรม "{path}" ไม่ถูกต้อง กรุณาตรวจสอบว่าไดเรกทอรี่นั้นถูกต้องและสามารถเขียนได้โดยคำสั่งของเซิฟเวอร์', - 'Authorization item "{item}" has already been assigned to user "{user}".' => 'รายการ "{item}" ได้ถูกกำหนดให้กับผู้ใช้งาน "{user}" เรียบร้อยแล้ว', - 'CApcCache requires PHP apc extension to be loaded.' => 'CApcCache ต้องการ PHP apc ในการเรียกใช้งาน', - 'CAssetManager.basePath "{path}" is invalid. Please make sure the directory exists and is writable by the Web server process.' => 'CAssetManager.basePath "{path}" ไม่ถูกต้อง กรุณาตรวจสอบว่าไดเรกทอรี่นั้นถูกต้องและสามารถเขียนได้โดยคำสั่งของเซิฟเวอร์', - 'CCacheHttpSession.cacheID is invalid. Please make sure "{id}" refers to a valid cache application component.' => 'CCacheHttpSession.cacheID ไม่ถูกต้อง กรุณาตรวจสอบ "{id}" ว่าเป็นค่าตามโครงสร้างของหน่วยความจำชั่วคราว (Cache) หรือไม่', - 'CCaptchaValidator.action "{id}" is invalid. Unable to find such an action in the current controller.' => 'CCaptchaValidator.action "{id}" ไม่ถูกต้อง ไม่สามารถค้นหาการดำเนินการในส่วนควบคุมปัจจุบันได้', - 'CDbAuthManager.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbAuthManager.connectionID "{id}" ไม่ถูกต้อง กรุณาตรวจสอบ ID ว่าเป็นค่าจาก CDbConnection ตามโครงสร้างของระบบหรือไม่', - 'CDbCache.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbCache.connectionID "{id}" ไม่ถูกต้อง กรุณาตรวจสอบ ID ว่าเป็นค่าจาก CDbConnection ตามโครงสร้างของระบบหรือไม่', - 'CDbCacheDependency.sql cannot be empty.' => 'CDbCacheDependency.sql ไม่สามารถเป็นค่าว่างได้', - 'CDbCommand failed to execute the SQL statement: {error}' => 'CDbCommand ล้มเหลวในการดำเนินการเรียกลักษณะของ SQL: {error}', - 'CDbCommand failed to prepare the SQL statement: {error}' => 'CDbCommand ล้มเหลวในการแจกแจงลักษณะของ SQL: {error}', - 'CDbConnection does not support reading schema for {driver} database.' => 'CDbConnection ไม่รองรับในการอ่านรายละเอียดโครงสร้างสำหรับ {driver} ในฐานข้อมูลได้', - 'CDbConnection failed to open the DB connection: {error}' => 'CDbConnection ล้มเหลวในการเชื่อมต่อฐานข้อมูล: {error}', - 'CDbConnection is inactive and cannot perform any DB operations.' => 'CDbConnection ไม่ได้ใช้งานและไม่สามารถดำเนินการกับฐานข้อมูลใดๆได้', - 'CDbConnection.connectionString cannot be empty.' => 'CDbConnection.connectionString ไม่สามารถเป็นค่าว่างได้', - 'CDbDataReader cannot rewind. It is a forward-only reader.' => 'CDbDataReader ไม่สามารถย้อนกลับได้, ถูกกำหนดให้อ่านได้เพียงอย่างเดียว', - 'CDbHttpSession.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbHttpSession.connectionID "{id}" ไม่ถูกต้อง กรุณาตรวจสอบ ID ว่าเป็นค่าจาก CDbConnection ตามโครงสร้างของระบบหรือไม่', - 'CDbLogRoute requires database table "{table}" to store log messages.' => 'CDbLogRoute ต้องการใช้งานตาราง "{table}" เพื่อเก็บบันทึกข้อความ', - 'CDbLogRoute.connectionID "{id}" does not point to a valid CDbConnection application component.' => 'CDbLogRoute.connectionID "{id}" ไม่สามารถระบุค่าที่ถูกต้องของ CDbConnection ตามโครงสร้างของระบบได้', - 'CDbMessageSource.connectionID is invalid. Please make sure "{id}" refers to a valid database application component.' => 'CDbMessageSource.connectionID ไม่ถูกต้อง กรุณาตรวจสอบว่า "{id}" เป็นข้อมูลที่ถูกต้องตามโครงสร้างของฐานข้อมูลหรือไม่', - 'CDbTransaction is inactive and cannot perform commit or roll back operations.' => 'CDbTransaction ไม่ได้ใช้งานและไม่สามารถดำเนินการกระทำต่อหรือย้อนกลับการดำเนินการได้', - 'CDirectoryCacheDependency.directory cannot be empty.' => 'CDirectoryCacheDependency.directory ไม่สามารถเป็นค่าว่างได้', - 'CFileCacheDependency.fileName cannot be empty.' => 'CFileCacheDependency.fileName ไม่สามารถเป็นค่าว่างได้', - '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}" ไม่สามารถชี้ไดเรกทอรี่ที่ถูกต้องได้ กรุณาตรวจสอบว่าไดเรกทอรี่นั้นถูกต้องและสามารถเขียนได้โดยคำสั่งของเซิฟเวอร์', - 'CFilterChain can only take objects implementing the IFilter interface.' => 'CFilterChain สามารถเรียกใช้งานกับข้อมูลที่อยู่ในรูปแบบของ IFilter ได้', - 'CFlexWidget.baseUrl cannot be empty.' => 'CFlexWidget.baseUrl ไม่สามารถเป็นค่าว่างได้', - 'CFlexWidget.name cannot be empty.' => 'CFlexWidget.name ไม่สามารถเป็นค่าว่างได้', - 'CGlobalStateCacheDependency.stateName cannot be empty.' => 'CGlobalStateCacheDependency.stateName ไม่สามารถเป็นค่าว่างได้', - 'CHttpCookieCollection can only hold CHttpCookie objects.' => 'CHttpCookieCollection สามารถใช้กับ CHttpCookie ได้เท่านั้น', - 'CHttpRequest is unable to determine the entry script URL.' => 'CHttpRequest ไม่สามารถตรวจสอบคำสั่งที่อยู่ของสคริปต์ได้', - 'CHttpRequest is unable to determine the path info of the request.' => 'CHttpRequest ไม่สามารถตรวจสอบข้อมูลที่อยู่ตามคำขอได้', - 'CHttpRequest is unable to determine the request URI.' => 'CHttpRequest ไม่สามารถตรวจสอบ URI ตามคำขอได้', - 'CHttpSession.cookieMode can only be "none", "allow" or "only".' => 'CHttpSession.cookieMode ระบุค่าเป็น "none", "allow" หรือ "only" เท่านั้น', - 'CHttpSession.gcProbability "{value}" is invalid. It must be an integer between 0 and 100.' => 'CHttpSession.gcProbability "{value}" ไม่ถูกต้อง ค่าที่ถูกต้องจะต้องเป็นจำนวนเต็มระหว่าง 0 ถึง 100 เท่านั้น', - 'CHttpSession.savePath "{path}" is not a valid directory.' => 'CHttpSession.savePath "{path}" ไม่ถูกต้อง', - 'CMemCache requires PHP memcache extension to be loaded.' => 'CMemCache ต้องการ PHP memcache ในการเรียกใช้งาน', - 'CMemCache server configuration must be an array.' => 'CMemCache ของค่าเซิฟเวอร์จะต้องอยู่ในแบบอาร์เรย์', - 'CMemCache server configuration must have "host" value.' => 'CMemCache ของค่าเซิฟเวอร์จะต้องเป็นค่าของ "host"', - 'CMultiFileUpload.name is required.' => 'CMultiFileUpload.name ต้องการในการใช้งาน', - 'CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.' => 'CProfileLogRoute พบคำสั่งที่ห้ามใช้งาน "{token}". กรุณาตรวจสอบว่าการเรียกใช้งาน Yii::beginProfile() และ Yii::endProfile() จะต้องถูกระบุคุณสมบัติต่างๆที่ถูกต้อง', - 'CProfileLogRoute.report "{report}" is invalid. Valid values include "summary" and "callstack".' => 'CProfileLogRoute.report "{report}" ไม่ถูกต้อง ค่าที่ถูกต้องจะต้องมีการเรียก "summary" หรือ "callstack".', - 'CSecurityManager requires PHP mcrypt extension to be loaded in order to use data encryption feature.' => 'CSecurityManager ต้องการ PHP mcrypt ในการเรียกใช้งานเพื่อใช้งานคุณสมบัติการบีบอัดข้อมูล', - 'CSecurityManager.encryptionKey cannot be empty.' => 'CSecurityManager.encryptionKey ไม่สามารถเป็นค่าว่างได้', - 'CSecurityManager.validation must be either "MD5" or "SHA1".' => 'CSecurityManager.validation จะต้องอยู่ในรูปแบบ "MD5" หรือ "SHA1"', - 'CSecurityManager.validationKey cannot be empty.' => 'CSecurityManager.validationKey ไม่สามารถเป็นค่าว่างได้', - 'CTypedList<{type}> can only hold objects of {type} class.' => 'CTypedList<{type}> สามารถใช้กับรูปแบบ {type} ได้เท่านั้น', - 'CUrlManager.UrlFormat must be either "path" or "get".' => 'CUrlManager.UrlFormat จะต้องมี "path" หรือ "get"', - 'CXCache requires PHP XCache extension to be loaded.' => 'CXCache ต้องการ PHP XCache ในการเรียกใช้งาน', - 'Cache table "{tableName}" does not exist.' => 'ไม่พบ Cache ของตาราง "{tableName}"', - 'Cannot add "{child}" as a child of "{name}". A loop has been detected.' => 'ไม่สามารถเพิ่ม "{child}" เป็นรายการย่อยของ "{name}" ได้ ตรวจพบการดำเนินการซ้ำ', - 'Cannot add "{child}" as a child of "{parent}". A loop has been detected.' => 'ไม่สามารถเพิ่ม "{child}" เป็นรายการย่อยของ "{parent}" ได้ ตรวจพบการดำเนินการซ้ำ', - 'Cannot add "{name}" as a child of itself.' => 'ไม่สามารถเพิ่ม "{name}" เป็นรายการย่อยได้', - 'Cannot add an item of type "{child}" to an item of type "{parent}".' => 'ไม่สามารถเพิ่มรายการ "{child}" ลงในรายการ "{parent}" ได้', - 'Either "{parent}" or "{child}" does not exist.' => '"{parent}" หรือ "{child}" ไม่มี', - 'Error: Table "{table}" does not have a primary key.' => 'ข้อผิดพลาด: ตาราง "{table}" ยังไม่มีคีย์หลัก', - 'Error: Table "{table}" has a composite primary key which is not supported by crud command.' => 'ข้อผิดพลาด: ตาราง "{table}" คีย์หลักมีรูปแบบที่ไม่สนับสนุนหรือรองรับคำสั่ง crud', - 'Event "{class}.{event}" is attached with an invalid handler "{handler}".' => 'เหตุการณ์ "{class}.{event}" มีการดำเนินการไม่ถูกต้อง "{handler}".', - 'Event "{class}.{event}" is not defined.' => 'เหตุการณ์ "{class}.{event}" ไม่ได้ถูกกำหนด', - 'Failed to write the uploaded file "{file}" to disk.' => 'ล้มเหลวในการอัพโหลดไฟล์ "{file}" ลงในดิส', - 'File upload was stopped by extension.' => 'ไฟล์ที่อัพโหลดถูกระงับเนื่องด้วยรูปแบบของไฟล์', - 'Filter "{filter}" is invalid. Controller "{class}" does have the filter method "filter{filter}".' => 'ตัวกรอง "{filter}" ไม่ถูกต้อง ส่วนควบคุม "{class}" จะต้องใช้งานตัวกรอง "filter{filter}"', - 'Get a new code' => 'สร้างรหัสใหม่', - 'Invalid MO file revision: {revision}.' => 'การแก้ไขไฟล์ MO ไม่ถูกต้อง: {revision}.', - 'Invalid MO file: {file} (magic: {magic}).' => 'ไฟล์ MO ไม่ถูกต้อง: {file} (magic: {magic}).', - 'Invalid enumerable value "{value}". Please make sure it is among ({enum}).' => 'ค่าของ enumerable "{value}" ไม่ถูกต้อง กรุณาตรวจสอบว่าเป็นค่าใน ({enum}) นี้', - 'List data must be an array or an object implementing Traversable.' => 'รายการข้อมูลจะต้องอยู่ในรูปของอาร์เรย์หรือรูปแบบ Traversable', - 'List index "{index}" is out of bound.' => 'ไม่พบรายการ "{index}" ในหน้าแรก', - 'Login Required' => 'จะต้องเข้าสู่ระบบ', - 'Map data must be an array or an object implementing Traversable.' => 'แผนผังข้อมูลจะต้องอยู่ในรูปของอาร์เรย์หรือรูปแบบ Traversable', - 'Missing the temporary folder to store the uploaded file "{file}".' => 'ไม่พบไดเรกทอรี่ไฟล์ชั่วคราวในการเก็บไฟล์ "{file}" ที่อัพโหลดเข้ามา', - '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" เท่านั้น', - 'Please fix the following input errors:' => 'โปรดแก้ไขข้อผิดพลาดทางด้านล่าง:', - 'Property "{class}.{property}" is not defined.' => 'คุณสมบัติ "{class}.{property}" ยังไม่ถูกกำหนด', - 'Property "{class}.{property}" is read only.' => 'คุณสมบัติ "{class}.{property}" สามารถอ่านได้เท่านั้น', - 'Queue data must be an array or an object implementing Traversable.' => 'ข้อมูลจากการดำเนินการจะอยู่ในรูปของอาร์เรย์หรือรูปแบบ Traversable', - 'Relation "{name}" is not defined in active record class "{class}".' => 'ความสัมพันธ์ของ "{name}" ไม่ได้ถูกกำหนดเอาไว้ใน "{class}"', - 'Stack data must be an array or an object implementing Traversable.' => 'ข้อมูลสแตกจะต้องอยู่ในรูปของอาร์เรย์หรือรูปแบบ Traversable', - 'Table "{table}" does not have a column named "{column}".' => 'ตาราง "{table}" ไม่มีฟิลด์ชื่อ "{column}" อยู่ในตาราง', - 'Table "{table}" does not have a primary key defined.' => 'ตาราง "{table}" ยังไม่ได้กำหนดคีย์หลัก', - 'The "filter" property must be specified with a valid callback.' => 'คุณสมบัติ "filter" จะต้องถูกระบุให้ถูกต้องในการเรียกตรวจสอบ', - 'The "pattern" property must be specified with a valid regular expression.' => 'คุณสมบัติ "pattern" จะต้องระบุให้ถูกต้องตามรูปแบบของ Regular Expression', - 'The "view" property is required.' => 'ต้องการคุณสมบัติ "view" ในการใช้งาน', - 'The CSRF token could not be verified.' => 'CSRF ไม่สามารถตรวจสอบได้', - 'The URL pattern "{pattern}" for route "{route}" is not a valid regular expression.' => 'รูปแบบที่อยู่ "{pattern}" สำหรับ "{route}" ไม่ถูกต้องตามรูปแบบของ Regular Expression', - 'The active record cannot be deleted because it is new.' => 'ไม่สามารถลบข้อมูลที่ต้องการได้ เนื่องจากเป็นข้อมูลใหม่', - 'The active record cannot be inserted to database because it is not new.' => 'ไม่สามารถเพิ่มข้อมูลที่ต้องการได้ เนื่องจากไม่ใช่ข้อมูลใหม่', - 'The active record cannot be updated because it is new.' => 'ไม่สามารถปรับปรุงข้อมูลที่ต้องการได้ เนื่องจากเป็นข้อมูลใหม่', - 'The asset "{asset}" to be pulished does not exist.' => 'ไม่พบข้อมูล "{asset}" ที่ต้องการ', - 'The column "{column}" is not a foreign key in table "{table}".' => 'ฟิลด์ "{column}" ไม่ใช่คีย์เชื่อมต่อในตาราง "{table}"', - 'The command path "{path}" is not a valid directory.' => 'ที่อยู่คำสั่ง "{path}" ไม่ถูกต้อง', - 'The controller path "{path}" is not a valid directory.' => 'ที่อยู่ส่วนควบคุม "{path}" ไม่ถูกต้อง', - 'The file "{file}" cannot be uploaded. Only files with these extensions are allowed: {extensions}.' => 'ไฟล์ "{file}" ไม่สามารถอัพโหลดได้ ไฟล์ที่สามารถอัพโหลดได้จะต้องเป็น: {extensions}.', - 'The file "{file}" is too large. Its size cannot exceed {limit} bytes.' => 'ไฟล์ "{file}" มีขนาดใหญ่ไป ไฟล์จะต้องมีขนาดไม่เกิน {limit} ไบต์', - 'The file "{file}" is too small. Its size cannot be smaller than {limit} bytes.' => 'ไฟล์ "{file}" มีขนาดเล็กเกินไป ไฟล์จะต้องมีขนาดมากกว่า {limit} ไบต์', - 'The file "{file}" was only partially uploaded.' => 'ไฟล์ "{file}" ถูกอัพโหลดไม่สมบูรณ์', - 'The first element in a filter configuration must be the filter class.' => 'การกำหนดค่าตัวกรอง ตัวแรกจะแทนค่าประเภทของการกรอง', - 'The item "{name}" does not exist.' => 'ไม่พบรายการ "{name}"', - 'The item "{parent}" already has a child "{child}".' => 'รายการ "{parent}" มีรายการย่อย "{child}"', - 'The layout path "{path}" is not a valid directory.' => 'รูปแบบที่อยู่ "{path}" ไม่ถูกต้อง', - 'The list is read only.' => 'รายการสามารถอ่านได้เท่านั้น', - 'The map is read only.' => 'แผนที่สามารถอ่านได้อย่างเดียว', - 'The pattern for 12 hour format must be "h" or "hh".' => 'การแสดงรูปแบบ 12 ชั่วโมง จะต้องระบุเป็น "h" หรือ "hh"', - 'The pattern for 24 hour format must be "H" or "HH".' => 'การแสดงรูปแบบ 24 ชั่วโมง จะต้องระบุเป็น "H" หรือ "HH"', - 'The pattern for AM/PM marker must be "a".' => 'การแสดงแบบ AM/PM จะต้องระบุเป็น "a"', - 'The pattern for day in month must be "F".' => 'การแสดงชื่อของเดือนจะต้องระบุเป็น "F"', - 'The pattern for day in year must be "D", "DD" or "DDD".' => 'การแสดงวันของปีจะต้องระบุเป็น "D", "DD" หรือ "DDD"', - 'The pattern for day of the month must be "d" or "dd".' => 'การแสดงวันของเดือนจะต้องระบุเป็น "d" หรือ "dd"', - 'The pattern for day of the week must be "E", "EE", "EEE", "EEEE" or "EEEEE".' => 'การแสดงวันของสัปดาห์จะต้องระบุเป็น "E", "EE", "EEE", "EEEE" หรือ "EEEEE"', - 'The pattern for era must be "G", "GG", "GGG", "GGGG" or "GGGGG".' => 'การแสดงยุคจะต้องระบุเป็น "G", "GG", "GGG", "GGGG" หรือ "GGGGG"', - 'The pattern for hour in AM/PM must be "K" or "KK".' => 'การแสดงชั่วโมง (แบบ AM/PM) จะต้องระบุเป็น "K" หรือ "KK"', - 'The pattern for hour in day must be "k" or "kk".' => 'การแสดงชั่วโมงของวันจะต้องระบุเป็น "k" หรือ "kk"', - 'The pattern for minutes must be "m" or "mm".' => 'การแสดงนาทีจะต้องระบุเป็น "m" หรือ "mm"', - 'The pattern for month must be "M", "MM", "MMM", or "MMMM".' => 'การแสดงเดือนจะต้องระบุเป็น "M", "MM", "MMM", หรือ "MMMM"', - 'The pattern for seconds must be "s" or "ss".' => 'การแสดงวินาทีจะต้องระบุเป็น "s" หรือ "ss"', - 'The pattern for time zone must be "z" or "v".' => 'การแสดงโซนเวลาจะต้องระบุเป็น "z" หรือ "v"', - '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}" กับ "{class}" ถูกระบุไว้ไม่ถูกต้อง: การเชื่อมต่อตาราง "{joinTable}" ไม่พบคีย์เชื่อมความสัมพันธ์ในฐานข้อมูล', - '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}" กับ "{class}" ถูกระบุคีย์เชื่อมต่อไม่สมบูรณ์ คีย์เชื่อมต่อจะต้องมีค่าอยู่ในตารางที่เชื่อมต่อกันทั้งสองตาราง', - 'The relation "{relation}" in active record class "{class}" is specified with an invalid foreign key "{key}". The foreign key does not point to either joining table.' => 'ความสัมพันธ์ระหว่าง "{relation}" กับ "{class}" ถูกระบุด้วยคีย์เชื่อมต่อที่ไม่ถูกต้องคือ "{key}" คีย์เชื่อมต่อนี้ไม่สามารถระบุความสัมพันธ์ระหว่างตารางได้', - '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,...)".' => 'จะต้องระบุคีย์ในการเชื่อมต่อความสัมพันธ์ระหว่าง "{class}" กับ "{relation}" รูปแบบของคีย์เชื่อมต่อจะต้องอยู่ในรูปแบบ "joinTable(fk1,fk2,...)"', - 'The requested controller "{controller}" does not exist.' => 'ไม่พบส่วนควบคุม "{controller}" ในการเรียกใช้', - 'The requested view "{name}" was not found.' => 'ไม่พบ "{name}" ในการเรียกใช้', - 'The stack is empty.' => 'สแตกข้อมูลเป็นค่าว่าง', - 'The system is unable to find the requested action "{action}".' => 'ระบบไม่สามารถดำเนินการ "{action}" ตามคำสั่งได้', - 'The system view path "{path}" is not a valid directory.' => 'ระบบตรวจพบที่อยู่ "{path}" ไม่ถูกต้อง', - 'The table "{table}" for active record class "{class}" cannot be found in the database.' => 'ตาราง "{table}" สำหรับบันทึกข้อมูลของ "{class}" ไม่พบในฐานข้อมูล', - 'The value for the primary key "{key}" is not supplied when querying the table "{table}".' => 'ค่าของคีย์หลัก "{key}" ไม่รองรับเมื่อมีการดำเนินการตาราง "{table}"', - 'The verification code is incorrect.' => 'รหัสป้องกันไม่ถูกต้อง', - 'The view path "{path}" is not a valid directory.' => 'ที่อยู่ "{path}" ไม่ถูกต้อง', - 'Theme directory "{directory}" does not exist.' => 'ไม่พบไดเรกทอรี่รูปแบบ "{directory}" ', - 'This content requires the Adobe Flash Player.' => 'ต้องการ Adobe Flash Player ในการแสดงข้อมูล', - 'Unable to add an item whose name is the same as an existing item.' => 'ไม่สามารถเพิ่มรายการที่มีชื่อเดียวกันได้', - 'Unable to change the item name. The name "{name}" is already used by another item.' => 'ไม่สามารถเปลี่ยนชื่อรายการได้ "{name}" กำลังถูกใช้งานจากรายการอื่น', - 'Unable to create application state file "{file}". Make sure the directory containing the file exists and is writable by the Web server process.' => 'ไม่สามารถสร้างไฟล์ระบบ "{file}" ได้ กรุณาตรวจสอบว่าไดเรกทอรี่เก็บไฟล์ สามารถเขียนได้โดยคำสั่งของเซิฟเวอร์', - 'Unable to find the decorator view "{view}".' => 'ไม่สามารถหาส่วนควบคุมของ "{view}" ได้', - 'Unable to find the list item.' => 'ไม่สามารถหารายการได้', - 'Unable to lock file "{file}" for reading.' => 'ไม่สามารถล็อคไฟล์ "{file}" สำหรับอ่านได้', - 'Unable to lock file "{file}" for writing.' => ' ไม่สามารถล็อคไฟล์ "{file}" สำหรับเขียนได้', - 'Unable to read file "{file}".' => 'ไม่สามารถอ่านไฟล์ "{file}" ได้', - 'Unable to replay the action "{object}.{method}". The method does not exist.' => 'ไม่สามารถดำเนินการ "{object}.{method}" ซ้ำได้ การดำเนินการถูกระงับ', - 'Unable to write file "{file}".' => 'ไม่สามารถเขียนทับไฟล์ "{file}" ได้', - 'Unknown authorization item "{name}".' => 'ไม่พบรายการ "{name}"', - 'Unrecognized locale "{locale}".' => 'ไม่พบปลายทาง "{locale}"', - 'View file "{file}" does not exist.' => 'ไม่พบไฟล์ "{file}" ที่ต้องการดู', - 'Yii application can only be created once.' => 'โปรแกรม Yii สามารถสร้างได้เพียงครั้งเดียวเท่านั้น', - 'You are not authorized to perform this action.' => 'คุณไม่ได้รับอนุญาตให้ดำเนินการดังกล่าว', - 'Your request is not valid.' => 'การดำเนินการไม่ถูกต้อง', - '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" ได้รับการบันทึก', - '{attribute} cannot be blank.' => '{attribute} ไม่ควรเป็นค่าว่าง', - '{attribute} is invalid.' => '{attribute} ไม่ถูกต้อง', - '{attribute} is not a valid URL.' => '{attribute} URL ไม่ถูกต้อง', - '{attribute} is not a valid email address.' => '{attribute} รูปแบบอีเมล์ไม่ถูกต้อง', - '{attribute} is not in the list.' => '{attribute} ไม่มีอยู่ในรายการ', - '{attribute} is of the wrong length (should be {length} characters).' => '{attribute} ขนาดความยาวไม่ถูกต้อง (จะต้องมีขนาด {length} ตัวอักษร)', - '{attribute} is too big (maximum is {max}).' => '{attribute} มีขนาดใหญ่เกินไป (ขนาดสูงสุด {max})', - '{attribute} is too long (maximum is {max} characters).' => '{attribute} ยาวเกินไป (จะต้องไม่เกิน {max} ตัวอักษร)', - '{attribute} is too short (minimum is {min} characters).' => '{attribute} สั้นเกินไป (จะต้องมากกว่า {min} ตัวอักษร)', - '{attribute} is too small (minimum is {min}).' => '{attribute} มีขนาดเล็กเกินไป (ขนาดเล็กสุด {min})', - '{attribute} must be a number.' => '{attribute} จะต้องเป็นตัวเลขเท่านั้น', - '{attribute} must be an integer.' => '{attribute} จะต้องเป็นจำนวนเต็มเท่านั้น', - '{attribute} must be repeated exactly.' => '{attribute} จะต้องมีค่าเหมือนกัน', - '{attribute} must be {type}.' => '{attribute} จะต้องเป็น {type}.', - '{className} does not support add() functionality.' => '{className} ไม่รองรับฟังก์ชั่น add()', - '{className} does not support delete() functionality.' => '{className} ไม่รองรับฟังก์ชั่น delete()', - '{className} does not support flush() functionality.' => '{className} ไม่รองรับฟังก์ชั่น flush()', - '{className} does not support get() functionality.' => '{className} ไม่รองรับฟังก์ชั่น get()', - '{className} does not support set() functionality.' => '{className} ไม่รองรับฟังก์ชั่น set()', - '{class} does not have attribute "{name}".' => '{class} ไม่มีข้อมูลของ "{name}"', - '{class} does not have relation "{name}".' => '{class} ไม่มีความสัมพันธ์กันกับ "{name}"', - '{class} does not support fetching all table names.' => '{class} ไม่รองรับการเรียกข้อมูลจากตารางทั้งหมด', - '{class} has an invalid validation rule. The rule must specify attributes to be validated and the validator name.' => '{class} ไม่ถูกต้องตามเงื่อนไข. จะต้องทำการระบุค่าของข้อมูลและชื่อของข้อมูล', - '{class} must specify "model" and "attribute" or "name" property values.' => '{class} จะต้องทำการระบุค่า "model" และ "attribute" หรือ "name" เหล่านี้ด้วย', - '{class}.allowAutoLogin must be set true in order to use cookie-based authentication.' => '{class}.allowAutoLogin จะต้องเปิดใช้งานเมื่อต้องการใช้งานคุ๊กกี้', - '{class}::authenticate() must be implemented.' => '{class}::authenticate() จะต้องถูกดำเนินการ', - '{controller} cannot find the requested view "{view}".' => '{controller} ไม่สามารถค้นหา "{view}" ตามคำสั่งได้', - '{controller} contains improperly nested widget tags in its view "{view}". A {widget} widget does not have an endWidget() call.' => '{controller} มีชุดคำสั่งที่ไม่ถูกต้องหลายอย่างในมุมมองของ "{view}" ทำให้ {widget} ไม่สามารถเรียกใช้คำสั่ง endWidget() ได้', - '{controller} has an extra endWidget({id}) call in its view.' => '{controller} กำลังถูกเรียกใช้โดย endWidget({id}) ในขณะนี้', - '{widget} cannot find the view "{view}".' => '{widget} ไม่สามารถดู "{view}" ได้', -); + '< หน้าที่แล้ว', + '<< First' => '<< หน้าแรก', + 'Go to page: ' => 'ไปที่หน้า: ', + 'Last >>' => 'หน้าสุดท้าย >>', + 'Next >' => 'หน้าถัดไป >', + 'The asset "{asset}" to be published does not exist.' => 'ไม่พบข้อมูล "{asset}" ที่ถูกใช้งาน', + '"{path}" is not a valid directory.' => 'ไม่พบไดเรกทอรี่ "{path}"', + 'Active Record requires a "db" CDbConnection application component.' => 'ต้องการใช้งานข้อมูล "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}" ได้ โดยจะต้องทำการระบุความสัมพันธ์ ซึ่งข้อมูลที่ใช้งานจะต้องเป็นคีย์เชื่อมความสัมพันธ์ในตารางข้อมูล', + '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.' => 'ข้อมูลที่ใช้งาน "{class}" ไม่สามารถเลือกฟิลด์ "{column}" ได้ คำแนะนำ, ชื่อฟิลด์จะต้องมีอยู่ในตารางหรือระบุเป็นชื่อเสมือนของฟิลด์อื่นๆ', + 'Alias "{alias}" is invalid. Make sure it points to an existing directory or file.' => 'ชื่อเสมือน "{alias}" ไม่ถูกต้อง กรุณาตรวจสอบความถูกต้องของไดเรกทอรี่หรือไฟล์', + 'Application base path "{path}" is not a valid directory.' => 'ที่อยู่โปรแกรมหลัก "{path}" ไม่ถูกต้อง', + 'Application runtime path "{path}" is not valid. Please make sure it is a directory writable by the Web server process.' => 'ที่อยู่โปรแกรม "{path}" ไม่ถูกต้อง กรุณาตรวจสอบว่าไดเรกทอรี่นั้นถูกต้องและสามารถเขียนได้โดยคำสั่งของเซิฟเวอร์', + 'Authorization item "{item}" has already been assigned to user "{user}".' => 'รายการ "{item}" ได้ถูกกำหนดให้กับผู้ใช้งาน "{user}" เรียบร้อยแล้ว', + 'CApcCache requires PHP apc extension to be loaded.' => 'CApcCache ต้องการ PHP apc ในการเรียกใช้งาน', + 'CAssetManager.basePath "{path}" is invalid. Please make sure the directory exists and is writable by the Web server process.' => 'CAssetManager.basePath "{path}" ไม่ถูกต้อง กรุณาตรวจสอบว่าไดเรกทอรี่นั้นถูกต้องและสามารถเขียนได้โดยคำสั่งของเซิฟเวอร์', + 'CCacheHttpSession.cacheID is invalid. Please make sure "{id}" refers to a valid cache application component.' => 'CCacheHttpSession.cacheID ไม่ถูกต้อง กรุณาตรวจสอบ "{id}" ว่าเป็นค่าตามโครงสร้างของหน่วยความจำชั่วคราว (Cache) หรือไม่', + 'CCaptchaValidator.action "{id}" is invalid. Unable to find such an action in the current controller.' => 'CCaptchaValidator.action "{id}" ไม่ถูกต้อง ไม่สามารถค้นหาการดำเนินการในส่วนควบคุมปัจจุบันได้', + 'CDbAuthManager.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbAuthManager.connectionID "{id}" ไม่ถูกต้อง กรุณาตรวจสอบ ID ว่าเป็นค่าจาก CDbConnection ตามโครงสร้างของระบบหรือไม่', + 'CDbCache.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbCache.connectionID "{id}" ไม่ถูกต้อง กรุณาตรวจสอบ ID ว่าเป็นค่าจาก CDbConnection ตามโครงสร้างของระบบหรือไม่', + 'CDbCacheDependency.sql cannot be empty.' => 'CDbCacheDependency.sql ไม่สามารถเป็นค่าว่างได้', + 'CDbCommand failed to execute the SQL statement: {error}' => 'CDbCommand ล้มเหลวในการดำเนินการเรียกลักษณะของ SQL: {error}', + 'CDbCommand failed to prepare the SQL statement: {error}' => 'CDbCommand ล้มเหลวในการแจกแจงลักษณะของ SQL: {error}', + 'CDbConnection does not support reading schema for {driver} database.' => 'CDbConnection ไม่รองรับในการอ่านรายละเอียดโครงสร้างสำหรับ {driver} ในฐานข้อมูลได้', + 'CDbConnection failed to open the DB connection: {error}' => 'CDbConnection ล้มเหลวในการเชื่อมต่อฐานข้อมูล: {error}', + 'CDbConnection is inactive and cannot perform any DB operations.' => 'CDbConnection ไม่ได้ใช้งานและไม่สามารถดำเนินการกับฐานข้อมูลใดๆได้', + 'CDbConnection.connectionString cannot be empty.' => 'CDbConnection.connectionString ไม่สามารถเป็นค่าว่างได้', + 'CDbDataReader cannot rewind. It is a forward-only reader.' => 'CDbDataReader ไม่สามารถย้อนกลับได้, ถูกกำหนดให้อ่านได้เพียงอย่างเดียว', + 'CDbHttpSession.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.' => 'CDbHttpSession.connectionID "{id}" ไม่ถูกต้อง กรุณาตรวจสอบ ID ว่าเป็นค่าจาก CDbConnection ตามโครงสร้างของระบบหรือไม่', + 'CDbLogRoute requires database table "{table}" to store log messages.' => 'CDbLogRoute ต้องการใช้งานตาราง "{table}" เพื่อเก็บบันทึกข้อความ', + 'CDbLogRoute.connectionID "{id}" does not point to a valid CDbConnection application component.' => 'CDbLogRoute.connectionID "{id}" ไม่สามารถระบุค่าที่ถูกต้องของ CDbConnection ตามโครงสร้างของระบบได้', + 'CDbMessageSource.connectionID is invalid. Please make sure "{id}" refers to a valid database application component.' => 'CDbMessageSource.connectionID ไม่ถูกต้อง กรุณาตรวจสอบว่า "{id}" เป็นข้อมูลที่ถูกต้องตามโครงสร้างของฐานข้อมูลหรือไม่', + 'CDbTransaction is inactive and cannot perform commit or roll back operations.' => 'CDbTransaction ไม่ได้ใช้งานและไม่สามารถดำเนินการกระทำต่อหรือย้อนกลับการดำเนินการได้', + 'CDirectoryCacheDependency.directory cannot be empty.' => 'CDirectoryCacheDependency.directory ไม่สามารถเป็นค่าว่างได้', + 'CFileCacheDependency.fileName cannot be empty.' => 'CFileCacheDependency.fileName ไม่สามารถเป็นค่าว่างได้', + '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}" ไม่สามารถชี้ไดเรกทอรี่ที่ถูกต้องได้ กรุณาตรวจสอบว่าไดเรกทอรี่นั้นถูกต้องและสามารถเขียนได้โดยคำสั่งของเซิฟเวอร์', + 'CFilterChain can only take objects implementing the IFilter interface.' => 'CFilterChain สามารถเรียกใช้งานกับข้อมูลที่อยู่ในรูปแบบของ IFilter ได้', + 'CFlexWidget.baseUrl cannot be empty.' => 'CFlexWidget.baseUrl ไม่สามารถเป็นค่าว่างได้', + 'CFlexWidget.name cannot be empty.' => 'CFlexWidget.name ไม่สามารถเป็นค่าว่างได้', + 'CGlobalStateCacheDependency.stateName cannot be empty.' => 'CGlobalStateCacheDependency.stateName ไม่สามารถเป็นค่าว่างได้', + 'CHttpCookieCollection can only hold CHttpCookie objects.' => 'CHttpCookieCollection สามารถใช้กับ CHttpCookie ได้เท่านั้น', + 'CHttpRequest is unable to determine the entry script URL.' => 'CHttpRequest ไม่สามารถตรวจสอบคำสั่งที่อยู่ของสคริปต์ได้', + 'CHttpRequest is unable to determine the path info of the request.' => 'CHttpRequest ไม่สามารถตรวจสอบข้อมูลที่อยู่ตามคำขอได้', + 'CHttpRequest is unable to determine the request URI.' => 'CHttpRequest ไม่สามารถตรวจสอบ URI ตามคำขอได้', + 'CHttpSession.cookieMode can only be "none", "allow" or "only".' => 'CHttpSession.cookieMode ระบุค่าเป็น "none", "allow" หรือ "only" เท่านั้น', + 'CHttpSession.gcProbability "{value}" is invalid. It must be an integer between 0 and 100.' => 'CHttpSession.gcProbability "{value}" ไม่ถูกต้อง ค่าที่ถูกต้องจะต้องเป็นจำนวนเต็มระหว่าง 0 ถึง 100 เท่านั้น', + 'CHttpSession.savePath "{path}" is not a valid directory.' => 'CHttpSession.savePath "{path}" ไม่ถูกต้อง', + 'CMemCache requires PHP memcache extension to be loaded.' => 'CMemCache ต้องการ PHP memcache ในการเรียกใช้งาน', + 'CMemCache server configuration must be an array.' => 'CMemCache ของค่าเซิฟเวอร์จะต้องอยู่ในแบบอาร์เรย์', + 'CMemCache server configuration must have "host" value.' => 'CMemCache ของค่าเซิฟเวอร์จะต้องเป็นค่าของ "host"', + 'CMultiFileUpload.name is required.' => 'CMultiFileUpload.name ต้องการในการใช้งาน', + 'CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.' => 'CProfileLogRoute พบคำสั่งที่ห้ามใช้งาน "{token}". กรุณาตรวจสอบว่าการเรียกใช้งาน Yii::beginProfile() และ Yii::endProfile() จะต้องถูกระบุคุณสมบัติต่างๆที่ถูกต้อง', + 'CProfileLogRoute.report "{report}" is invalid. Valid values include "summary" and "callstack".' => 'CProfileLogRoute.report "{report}" ไม่ถูกต้อง ค่าที่ถูกต้องจะต้องมีการเรียก "summary" หรือ "callstack".', + 'CSecurityManager requires PHP mcrypt extension to be loaded in order to use data encryption feature.' => 'CSecurityManager ต้องการ PHP mcrypt ในการเรียกใช้งานเพื่อใช้งานคุณสมบัติการบีบอัดข้อมูล', + 'CSecurityManager.encryptionKey cannot be empty.' => 'CSecurityManager.encryptionKey ไม่สามารถเป็นค่าว่างได้', + 'CSecurityManager.validation must be either "MD5" or "SHA1".' => 'CSecurityManager.validation จะต้องอยู่ในรูปแบบ "MD5" หรือ "SHA1"', + 'CSecurityManager.validationKey cannot be empty.' => 'CSecurityManager.validationKey ไม่สามารถเป็นค่าว่างได้', + 'CTypedList<{type}> can only hold objects of {type} class.' => 'CTypedList<{type}> สามารถใช้กับรูปแบบ {type} ได้เท่านั้น', + 'CUrlManager.UrlFormat must be either "path" or "get".' => 'CUrlManager.UrlFormat จะต้องมี "path" หรือ "get"', + 'CXCache requires PHP XCache extension to be loaded.' => 'CXCache ต้องการ PHP XCache ในการเรียกใช้งาน', + 'Cache table "{tableName}" does not exist.' => 'ไม่พบ Cache ของตาราง "{tableName}"', + 'Cannot add "{child}" as a child of "{name}". A loop has been detected.' => 'ไม่สามารถเพิ่ม "{child}" เป็นรายการย่อยของ "{name}" ได้ ตรวจพบการดำเนินการซ้ำ', + 'Cannot add "{child}" as a child of "{parent}". A loop has been detected.' => 'ไม่สามารถเพิ่ม "{child}" เป็นรายการย่อยของ "{parent}" ได้ ตรวจพบการดำเนินการซ้ำ', + 'Cannot add "{name}" as a child of itself.' => 'ไม่สามารถเพิ่ม "{name}" เป็นรายการย่อยได้', + 'Cannot add an item of type "{child}" to an item of type "{parent}".' => 'ไม่สามารถเพิ่มรายการ "{child}" ลงในรายการ "{parent}" ได้', + 'Either "{parent}" or "{child}" does not exist.' => '"{parent}" หรือ "{child}" ไม่มี', + 'Error: Table "{table}" does not have a primary key.' => 'ข้อผิดพลาด: ตาราง "{table}" ยังไม่มีคีย์หลัก', + 'Error: Table "{table}" has a composite primary key which is not supported by crud command.' => 'ข้อผิดพลาด: ตาราง "{table}" คีย์หลักมีรูปแบบที่ไม่สนับสนุนหรือรองรับคำสั่ง crud', + 'Event "{class}.{event}" is attached with an invalid handler "{handler}".' => 'เหตุการณ์ "{class}.{event}" มีการดำเนินการไม่ถูกต้อง "{handler}".', + 'Event "{class}.{event}" is not defined.' => 'เหตุการณ์ "{class}.{event}" ไม่ได้ถูกกำหนด', + 'Failed to write the uploaded file "{file}" to disk.' => 'ล้มเหลวในการอัพโหลดไฟล์ "{file}" ลงในดิส', + 'File upload was stopped by extension.' => 'ไฟล์ที่อัพโหลดถูกระงับเนื่องด้วยรูปแบบของไฟล์', + 'Filter "{filter}" is invalid. Controller "{class}" does have the filter method "filter{filter}".' => 'ตัวกรอง "{filter}" ไม่ถูกต้อง ส่วนควบคุม "{class}" จะต้องใช้งานตัวกรอง "filter{filter}"', + 'Get a new code' => 'สร้างรหัสใหม่', + 'Invalid MO file revision: {revision}.' => 'การแก้ไขไฟล์ MO ไม่ถูกต้อง: {revision}.', + 'Invalid MO file: {file} (magic: {magic}).' => 'ไฟล์ MO ไม่ถูกต้อง: {file} (magic: {magic}).', + 'Invalid enumerable value "{value}". Please make sure it is among ({enum}).' => 'ค่าของ enumerable "{value}" ไม่ถูกต้อง กรุณาตรวจสอบว่าเป็นค่าใน ({enum}) นี้', + 'List data must be an array or an object implementing Traversable.' => 'รายการข้อมูลจะต้องอยู่ในรูปของอาร์เรย์หรือรูปแบบ Traversable', + 'List index "{index}" is out of bound.' => 'ไม่พบรายการ "{index}" ในหน้าแรก', + 'Login Required' => 'จะต้องเข้าสู่ระบบ', + 'Map data must be an array or an object implementing Traversable.' => 'แผนผังข้อมูลจะต้องอยู่ในรูปของอาร์เรย์หรือรูปแบบ Traversable', + 'Missing the temporary folder to store the uploaded file "{file}".' => 'ไม่พบไดเรกทอรี่ไฟล์ชั่วคราวในการเก็บไฟล์ "{file}" ที่อัพโหลดเข้ามา', + '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" เท่านั้น', + 'Please fix the following input errors:' => 'โปรดแก้ไขข้อผิดพลาดทางด้านล่าง:', + 'Property "{class}.{property}" is not defined.' => 'คุณสมบัติ "{class}.{property}" ยังไม่ถูกกำหนด', + 'Property "{class}.{property}" is read only.' => 'คุณสมบัติ "{class}.{property}" สามารถอ่านได้เท่านั้น', + 'Queue data must be an array or an object implementing Traversable.' => 'ข้อมูลจากการดำเนินการจะอยู่ในรูปของอาร์เรย์หรือรูปแบบ Traversable', + 'Relation "{name}" is not defined in active record class "{class}".' => 'ความสัมพันธ์ของ "{name}" ไม่ได้ถูกกำหนดเอาไว้ใน "{class}"', + 'Stack data must be an array or an object implementing Traversable.' => 'ข้อมูลสแตกจะต้องอยู่ในรูปของอาร์เรย์หรือรูปแบบ Traversable', + 'Table "{table}" does not have a column named "{column}".' => 'ตาราง "{table}" ไม่มีฟิลด์ชื่อ "{column}" อยู่ในตาราง', + 'Table "{table}" does not have a primary key defined.' => 'ตาราง "{table}" ยังไม่ได้กำหนดคีย์หลัก', + 'The "filter" property must be specified with a valid callback.' => 'คุณสมบัติ "filter" จะต้องถูกระบุให้ถูกต้องในการเรียกตรวจสอบ', + 'The "pattern" property must be specified with a valid regular expression.' => 'คุณสมบัติ "pattern" จะต้องระบุให้ถูกต้องตามรูปแบบของ Regular Expression', + 'The "view" property is required.' => 'ต้องการคุณสมบัติ "view" ในการใช้งาน', + 'The CSRF token could not be verified.' => 'CSRF ไม่สามารถตรวจสอบได้', + 'The URL pattern "{pattern}" for route "{route}" is not a valid regular expression.' => 'รูปแบบที่อยู่ "{pattern}" สำหรับ "{route}" ไม่ถูกต้องตามรูปแบบของ Regular Expression', + 'The active record cannot be deleted because it is new.' => 'ไม่สามารถลบข้อมูลที่ต้องการได้ เนื่องจากเป็นข้อมูลใหม่', + 'The active record cannot be inserted to database because it is not new.' => 'ไม่สามารถเพิ่มข้อมูลที่ต้องการได้ เนื่องจากไม่ใช่ข้อมูลใหม่', + 'The active record cannot be updated because it is new.' => 'ไม่สามารถปรับปรุงข้อมูลที่ต้องการได้ เนื่องจากเป็นข้อมูลใหม่', + 'The asset "{asset}" to be pulished does not exist.' => 'ไม่พบข้อมูล "{asset}" ที่ต้องการ', + 'The column "{column}" is not a foreign key in table "{table}".' => 'ฟิลด์ "{column}" ไม่ใช่คีย์เชื่อมต่อในตาราง "{table}"', + 'The command path "{path}" is not a valid directory.' => 'ที่อยู่คำสั่ง "{path}" ไม่ถูกต้อง', + 'The controller path "{path}" is not a valid directory.' => 'ที่อยู่ส่วนควบคุม "{path}" ไม่ถูกต้อง', + 'The file "{file}" cannot be uploaded. Only files with these extensions are allowed: {extensions}.' => 'ไฟล์ "{file}" ไม่สามารถอัพโหลดได้ ไฟล์ที่สามารถอัพโหลดได้จะต้องเป็น: {extensions}.', + 'The file "{file}" is too large. Its size cannot exceed {limit} bytes.' => 'ไฟล์ "{file}" มีขนาดใหญ่ไป ไฟล์จะต้องมีขนาดไม่เกิน {limit} ไบต์', + 'The file "{file}" is too small. Its size cannot be smaller than {limit} bytes.' => 'ไฟล์ "{file}" มีขนาดเล็กเกินไป ไฟล์จะต้องมีขนาดมากกว่า {limit} ไบต์', + 'The file "{file}" was only partially uploaded.' => 'ไฟล์ "{file}" ถูกอัพโหลดไม่สมบูรณ์', + 'The first element in a filter configuration must be the filter class.' => 'การกำหนดค่าตัวกรอง ตัวแรกจะแทนค่าประเภทของการกรอง', + 'The item "{name}" does not exist.' => 'ไม่พบรายการ "{name}"', + 'The item "{parent}" already has a child "{child}".' => 'รายการ "{parent}" มีรายการย่อย "{child}"', + 'The layout path "{path}" is not a valid directory.' => 'รูปแบบที่อยู่ "{path}" ไม่ถูกต้อง', + 'The list is read only.' => 'รายการสามารถอ่านได้เท่านั้น', + 'The map is read only.' => 'แผนที่สามารถอ่านได้อย่างเดียว', + 'The pattern for 12 hour format must be "h" or "hh".' => 'การแสดงรูปแบบ 12 ชั่วโมง จะต้องระบุเป็น "h" หรือ "hh"', + 'The pattern for 24 hour format must be "H" or "HH".' => 'การแสดงรูปแบบ 24 ชั่วโมง จะต้องระบุเป็น "H" หรือ "HH"', + 'The pattern for AM/PM marker must be "a".' => 'การแสดงแบบ AM/PM จะต้องระบุเป็น "a"', + 'The pattern for day in month must be "F".' => 'การแสดงชื่อของเดือนจะต้องระบุเป็น "F"', + 'The pattern for day in year must be "D", "DD" or "DDD".' => 'การแสดงวันของปีจะต้องระบุเป็น "D", "DD" หรือ "DDD"', + 'The pattern for day of the month must be "d" or "dd".' => 'การแสดงวันของเดือนจะต้องระบุเป็น "d" หรือ "dd"', + 'The pattern for day of the week must be "E", "EE", "EEE", "EEEE" or "EEEEE".' => 'การแสดงวันของสัปดาห์จะต้องระบุเป็น "E", "EE", "EEE", "EEEE" หรือ "EEEEE"', + 'The pattern for era must be "G", "GG", "GGG", "GGGG" or "GGGGG".' => 'การแสดงยุคจะต้องระบุเป็น "G", "GG", "GGG", "GGGG" หรือ "GGGGG"', + 'The pattern for hour in AM/PM must be "K" or "KK".' => 'การแสดงชั่วโมง (แบบ AM/PM) จะต้องระบุเป็น "K" หรือ "KK"', + 'The pattern for hour in day must be "k" or "kk".' => 'การแสดงชั่วโมงของวันจะต้องระบุเป็น "k" หรือ "kk"', + 'The pattern for minutes must be "m" or "mm".' => 'การแสดงนาทีจะต้องระบุเป็น "m" หรือ "mm"', + 'The pattern for month must be "M", "MM", "MMM", or "MMMM".' => 'การแสดงเดือนจะต้องระบุเป็น "M", "MM", "MMM", หรือ "MMMM"', + 'The pattern for seconds must be "s" or "ss".' => 'การแสดงวินาทีจะต้องระบุเป็น "s" หรือ "ss"', + 'The pattern for time zone must be "z" or "v".' => 'การแสดงโซนเวลาจะต้องระบุเป็น "z" หรือ "v"', + '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}" กับ "{class}" ถูกระบุไว้ไม่ถูกต้อง: การเชื่อมต่อตาราง "{joinTable}" ไม่พบคีย์เชื่อมความสัมพันธ์ในฐานข้อมูล', + '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}" กับ "{class}" ถูกระบุคีย์เชื่อมต่อไม่สมบูรณ์ คีย์เชื่อมต่อจะต้องมีค่าอยู่ในตารางที่เชื่อมต่อกันทั้งสองตาราง', + 'The relation "{relation}" in active record class "{class}" is specified with an invalid foreign key "{key}". The foreign key does not point to either joining table.' => 'ความสัมพันธ์ระหว่าง "{relation}" กับ "{class}" ถูกระบุด้วยคีย์เชื่อมต่อที่ไม่ถูกต้องคือ "{key}" คีย์เชื่อมต่อนี้ไม่สามารถระบุความสัมพันธ์ระหว่างตารางได้', + '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,...)".' => 'จะต้องระบุคีย์ในการเชื่อมต่อความสัมพันธ์ระหว่าง "{class}" กับ "{relation}" รูปแบบของคีย์เชื่อมต่อจะต้องอยู่ในรูปแบบ "joinTable(fk1,fk2,...)"', + 'The requested controller "{controller}" does not exist.' => 'ไม่พบส่วนควบคุม "{controller}" ในการเรียกใช้', + 'The requested view "{name}" was not found.' => 'ไม่พบ "{name}" ในการเรียกใช้', + 'The stack is empty.' => 'สแตกข้อมูลเป็นค่าว่าง', + 'The system is unable to find the requested action "{action}".' => 'ระบบไม่สามารถดำเนินการ "{action}" ตามคำสั่งได้', + 'The system view path "{path}" is not a valid directory.' => 'ระบบตรวจพบที่อยู่ "{path}" ไม่ถูกต้อง', + 'The table "{table}" for active record class "{class}" cannot be found in the database.' => 'ตาราง "{table}" สำหรับบันทึกข้อมูลของ "{class}" ไม่พบในฐานข้อมูล', + 'The value for the primary key "{key}" is not supplied when querying the table "{table}".' => 'ค่าของคีย์หลัก "{key}" ไม่รองรับเมื่อมีการดำเนินการตาราง "{table}"', + 'The verification code is incorrect.' => 'รหัสป้องกันไม่ถูกต้อง', + 'The view path "{path}" is not a valid directory.' => 'ที่อยู่ "{path}" ไม่ถูกต้อง', + 'Theme directory "{directory}" does not exist.' => 'ไม่พบไดเรกทอรี่รูปแบบ "{directory}" ', + 'This content requires the Adobe Flash Player.' => 'ต้องการ Adobe Flash Player ในการแสดงข้อมูล', + 'Unable to add an item whose name is the same as an existing item.' => 'ไม่สามารถเพิ่มรายการที่มีชื่อเดียวกันได้', + 'Unable to change the item name. The name "{name}" is already used by another item.' => 'ไม่สามารถเปลี่ยนชื่อรายการได้ "{name}" กำลังถูกใช้งานจากรายการอื่น', + 'Unable to create application state file "{file}". Make sure the directory containing the file exists and is writable by the Web server process.' => 'ไม่สามารถสร้างไฟล์ระบบ "{file}" ได้ กรุณาตรวจสอบว่าไดเรกทอรี่เก็บไฟล์ สามารถเขียนได้โดยคำสั่งของเซิฟเวอร์', + 'Unable to find the decorator view "{view}".' => 'ไม่สามารถหาส่วนควบคุมของ "{view}" ได้', + 'Unable to find the list item.' => 'ไม่สามารถหารายการได้', + 'Unable to lock file "{file}" for reading.' => 'ไม่สามารถล็อคไฟล์ "{file}" สำหรับอ่านได้', + 'Unable to lock file "{file}" for writing.' => ' ไม่สามารถล็อคไฟล์ "{file}" สำหรับเขียนได้', + 'Unable to read file "{file}".' => 'ไม่สามารถอ่านไฟล์ "{file}" ได้', + 'Unable to replay the action "{object}.{method}". The method does not exist.' => 'ไม่สามารถดำเนินการ "{object}.{method}" ซ้ำได้ การดำเนินการถูกระงับ', + 'Unable to write file "{file}".' => 'ไม่สามารถเขียนทับไฟล์ "{file}" ได้', + 'Unknown authorization item "{name}".' => 'ไม่พบรายการ "{name}"', + 'Unrecognized locale "{locale}".' => 'ไม่พบปลายทาง "{locale}"', + 'View file "{file}" does not exist.' => 'ไม่พบไฟล์ "{file}" ที่ต้องการดู', + 'Yii application can only be created once.' => 'โปรแกรม Yii สามารถสร้างได้เพียงครั้งเดียวเท่านั้น', + 'You are not authorized to perform this action.' => 'คุณไม่ได้รับอนุญาตให้ดำเนินการดังกล่าว', + 'Your request is not valid.' => 'การดำเนินการไม่ถูกต้อง', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" ได้รับการบันทึก', + '{attribute} cannot be blank.' => '{attribute} ไม่ควรเป็นค่าว่าง', + '{attribute} is invalid.' => '{attribute} ไม่ถูกต้อง', + '{attribute} is not a valid URL.' => '{attribute} URL ไม่ถูกต้อง', + '{attribute} is not a valid email address.' => '{attribute} รูปแบบอีเมล์ไม่ถูกต้อง', + '{attribute} is not in the list.' => '{attribute} ไม่มีอยู่ในรายการ', + '{attribute} is of the wrong length (should be {length} characters).' => '{attribute} ขนาดความยาวไม่ถูกต้อง (จะต้องมีขนาด {length} ตัวอักษร)', + '{attribute} is too big (maximum is {max}).' => '{attribute} มีขนาดใหญ่เกินไป (ขนาดสูงสุด {max})', + '{attribute} is too long (maximum is {max} characters).' => '{attribute} ยาวเกินไป (จะต้องไม่เกิน {max} ตัวอักษร)', + '{attribute} is too short (minimum is {min} characters).' => '{attribute} สั้นเกินไป (จะต้องมากกว่า {min} ตัวอักษร)', + '{attribute} is too small (minimum is {min}).' => '{attribute} มีขนาดเล็กเกินไป (ขนาดเล็กสุด {min})', + '{attribute} must be a number.' => '{attribute} จะต้องเป็นตัวเลขเท่านั้น', + '{attribute} must be an integer.' => '{attribute} จะต้องเป็นจำนวนเต็มเท่านั้น', + '{attribute} must be repeated exactly.' => '{attribute} จะต้องมีค่าเหมือนกัน', + '{attribute} must be {type}.' => '{attribute} จะต้องเป็น {type}.', + '{className} does not support add() functionality.' => '{className} ไม่รองรับฟังก์ชั่น add()', + '{className} does not support delete() functionality.' => '{className} ไม่รองรับฟังก์ชั่น delete()', + '{className} does not support flush() functionality.' => '{className} ไม่รองรับฟังก์ชั่น flush()', + '{className} does not support get() functionality.' => '{className} ไม่รองรับฟังก์ชั่น get()', + '{className} does not support set() functionality.' => '{className} ไม่รองรับฟังก์ชั่น set()', + '{class} does not have attribute "{name}".' => '{class} ไม่มีข้อมูลของ "{name}"', + '{class} does not have relation "{name}".' => '{class} ไม่มีความสัมพันธ์กันกับ "{name}"', + '{class} does not support fetching all table names.' => '{class} ไม่รองรับการเรียกข้อมูลจากตารางทั้งหมด', + '{class} has an invalid validation rule. The rule must specify attributes to be validated and the validator name.' => '{class} ไม่ถูกต้องตามเงื่อนไข. จะต้องทำการระบุค่าของข้อมูลและชื่อของข้อมูล', + '{class} must specify "model" and "attribute" or "name" property values.' => '{class} จะต้องทำการระบุค่า "model" และ "attribute" หรือ "name" เหล่านี้ด้วย', + '{class}.allowAutoLogin must be set true in order to use cookie-based authentication.' => '{class}.allowAutoLogin จะต้องเปิดใช้งานเมื่อต้องการใช้งานคุ๊กกี้', + '{class}::authenticate() must be implemented.' => '{class}::authenticate() จะต้องถูกดำเนินการ', + '{controller} cannot find the requested view "{view}".' => '{controller} ไม่สามารถค้นหา "{view}" ตามคำสั่งได้', + '{controller} contains improperly nested widget tags in its view "{view}". A {widget} widget does not have an endWidget() call.' => '{controller} มีชุดคำสั่งที่ไม่ถูกต้องหลายอย่างในมุมมองของ "{view}" ทำให้ {widget} ไม่สามารถเรียกใช้คำสั่ง endWidget() ได้', + '{controller} has an extra endWidget({id}) call in its view.' => '{controller} กำลังถูกเรียกใช้โดย endWidget({id}) ในขณะนี้', + '{widget} cannot find the view "{view}".' => '{widget} ไม่สามารถดู "{view}" ได้', +); diff --git a/framework/messages/uk/zii.php b/framework/messages/uk/zii.php index ad52bdecc..79c41d27c 100644 --- a/framework/messages/uk/zii.php +++ b/framework/messages/uk/zii.php @@ -1,36 +1,36 @@ - 'Головна', - 'The button type "{type}" is not supported.' => 'Тип кнопки "{type}" не підтримується.', - 'Are you sure you want to delete this item?' => 'Ви впевнені, що хочете видалити цей елемент?', - 'Delete' => 'Видалити', - 'Displaying {start}-{end} of 1 result.|Displaying {start}-{end} of {count} results.' => 'Елементи {start}—{end} із {count}.', - 'Either "name" or "value" must be specified for CDataColumn.' => 'Для CDataColumn необхідно вказати або "name" або "value".', - 'No results found.' => 'Немає результатів.', - 'Not set' => 'Не заданий', - 'Please specify the "attributes" property.' => 'Визначте властивість "attributes".', - 'Please specify the "data" property.' => 'Визначте властивість "data".', - 'Sort by: ' => 'Сортування: ', - 'The "dataProvider" property cannot be empty.' => 'Властивість "dataProvider" не може бути порожньою.', - 'The attribute must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.' => 'Атрибут повинен бути заданий у форматі "Імʼя:Тип:Заголовок". "Тип" та "Заголовок" не обовʼязкові.', - 'The column must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.' => 'Стовпець повинен бути заданий у форматі "Name:Type:Label". "Тип" та "Заголовок" не обовʼязкові.', - 'The property "itemView" cannot be empty.' => 'Властивість "itemView" не може бути порожньою.', - 'Total 1 result.|Total {count} results.' => 'Всього {count} результат.|Всього {count} результата.|Всього {count} результатів.|Всього {count} результата.', - 'Update' => 'Редагувати', - 'View' => 'Переглянути', - '{class} must specify "model" and "attribute" or "name" property values.' => 'Клас {class} повинен визначати значення властивостей "model" та "attribute", або значення "name".', -); + 'Головна', + 'The button type "{type}" is not supported.' => 'Тип кнопки "{type}" не підтримується.', + 'Are you sure you want to delete this item?' => 'Ви впевнені, що хочете видалити цей елемент?', + 'Delete' => 'Видалити', + 'Displaying {start}-{end} of 1 result.|Displaying {start}-{end} of {count} results.' => 'Елементи {start}—{end} із {count}.', + 'Either "name" or "value" must be specified for CDataColumn.' => 'Для CDataColumn необхідно вказати або "name" або "value".', + 'No results found.' => 'Немає результатів.', + 'Not set' => 'Не заданий', + 'Please specify the "attributes" property.' => 'Визначте властивість "attributes".', + 'Please specify the "data" property.' => 'Визначте властивість "data".', + 'Sort by: ' => 'Сортування: ', + 'The "dataProvider" property cannot be empty.' => 'Властивість "dataProvider" не може бути порожньою.', + 'The attribute must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.' => 'Атрибут повинен бути заданий у форматі "Імʼя:Тип:Заголовок". "Тип" та "Заголовок" не обовʼязкові.', + 'The column must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.' => 'Стовпець повинен бути заданий у форматі "Name:Type:Label". "Тип" та "Заголовок" не обовʼязкові.', + 'The property "itemView" cannot be empty.' => 'Властивість "itemView" не може бути порожньою.', + 'Total 1 result.|Total {count} results.' => 'Всього {count} результат.|Всього {count} результата.|Всього {count} результатів.|Всього {count} результата.', + 'Update' => 'Редагувати', + 'View' => 'Переглянути', + '{class} must specify "model" and "attribute" or "name" property values.' => 'Клас {class} повинен визначати значення властивостей "model" та "attribute", або значення "name".', +); diff --git a/framework/vendors/TextHighlighter/Text/Highlighter.php b/framework/vendors/TextHighlighter/Text/Highlighter.php index 6a3fed965..4823feb50 100644 --- a/framework/vendors/TextHighlighter/Text/Highlighter.php +++ b/framework/vendors/TextHighlighter/Text/Highlighter.php @@ -1,397 +1,397 @@ - - * @copyright 2004-2006 Andrey Demenev - * @license http://www.php.net/license/3_0.txt PHP License - * @version CVS: $Id: Highlighter.php,v 1.1 2007/06/03 02:35:28 ssttoo Exp $ - * @link http://pear.php.net/package/Text_Highlighter - */ - -// {{{ BC constants - -// BC trick : define constants related to default -// renderer if needed -if (!defined('HL_NUMBERS_LI')) { - /**#@+ - * Constant for use with $options['numbers'] - * @see Text_Highlighter_Renderer_Html::_init() - */ - /** - * use numbered list - */ - define ('HL_NUMBERS_LI' , 1); - /** - * Use 2-column table with line numbers in left column and code in right column. - * Forces $options['tag'] = HL_TAG_PRE - */ - define ('HL_NUMBERS_TABLE' , 2); - /**#@-*/ -} - -// }}} -// {{{ constants -/** - * for our purpose, it is infinity - */ -define ('HL_INFINITY', 1000000000); - -// }}} - -/** - * Text highlighter base class - * - * @author Andrey Demenev - * @copyright 2004-2006 Andrey Demenev - * @license http://www.php.net/license/3_0.txt PHP License - * @version Release: 0.7.1 - * @link http://pear.php.net/package/Text_Highlighter - */ - -// {{{ Text_Highlighter - -/** - * Text highlighter base class - * - * This class implements all functions necessary for highlighting, - * but it does not contain highlighting rules. Actual highlighting is - * done using a descendent of this class. - * - * One is not supposed to manually create descendent classes. - * Instead, describe highlighting rules in XML format and - * use {@link Text_Highlighter_Generator} to create descendent class. - * Alternatively, an instance of a descendent class can be created - * directly. - * - * Use {@link Text_Highlighter::factory()} to create an - * object for particular language highlighter - * - * Usage example - * - *require_once 'Text/Highlighter.php'; - *$hlSQL =& Text_Highlighter::factory('SQL',array('numbers'=>true)); - *echo $hlSQL->highlight('SELECT * FROM table a WHERE id = 12'); - * - * - * @author Andrey Demenev - * @package Text_Highlighter - * @access public - */ - -class Text_Highlighter -{ - // {{{ members - - /** - * Syntax highlighting rules. - * Auto-generated classes set this var - * - * @access protected - * @see _init - * @var array - */ - var $_syntax; - - /** - * Renderer object. - * - * @access private - * @var array - */ - var $_renderer; - - /** - * Options. Keeped for BC - * - * @access protected - * @var array - */ - var $_options = array(); - - /** - * Conditionds - * - * @access protected - * @var array - */ - var $_conditions = array(); - - /** - * Disabled keywords - * - * @access protected - * @var array - */ - var $_disabled = array(); - - /** - * Language - * - * @access protected - * @var string - */ - var $_language = ''; - - // }}} - // {{{ _checkDefines - - /** - * Called by subclssses' constructors to enable/disable - * optional highlighter rules - * - * @param array $defines Conditional defines - * - * @access protected - */ - function _checkDefines() - { - if (isset($this->_options['defines'])) { - $defines = $this->_options['defines']; - } else { - $defines = array(); - } - foreach ($this->_conditions as $name => $actions) { - foreach($actions as $action) { - $present = in_array($name, $defines); - if (!$action[1]) { - $present = !$present; - } - if ($present) { - unset($this->_disabled[$action[0]]); - } else { - $this->_disabled[$action[0]] = true; - } - } - } - } - - // }}} - // {{{ factory - - /** - * Create a new Highlighter object for specified language - * - * @param string $lang language, for example "SQL" - * @param array $options Rendering options. This - * parameter is only keeped for BC reasons, use - * {@link Text_Highlighter::setRenderer()} instead - * - * @return mixed a newly created Highlighter object, or - * a PEAR error object on error - * - * @static - * @access public - */ - public static function factory($lang, $options = array()) - { - $lang = strtoupper($lang); - $langFile = dirname(__FILE__)."/Highlighter/$lang.php"; - if (is_file($langFile)) - include_once $langFile; - else - return false; - - $classname = 'Text_Highlighter_' . $lang; - - if (!class_exists($classname)) - return false; - - return new $classname($options); - } - - // }}} - // {{{ setRenderer - - /** - * Set renderer object - * - * @param object $renderer Text_Highlighter_Renderer - * - * @access public - */ - function setRenderer($renderer) - { - $this->_renderer = $renderer; - } - - // }}} - - /** - * Helper function to find matching brackets - * - * @access private - */ - function _matchingBrackets($str) - { - return strtr($str, '()<>[]{}', ')(><][}{'); - } - - - - - function _getToken() - { - if (!empty($this->_tokenStack)) { - return array_pop($this->_tokenStack); - } - if ($this->_pos >= $this->_len) { - return NULL; - } - - if ($this->_state != -1 && preg_match($this->_endpattern, $this->_str, $m, PREG_OFFSET_CAPTURE, $this->_pos)) { - $endpos = $m[0][1]; - $endmatch = $m[0][0]; - } else { - $endpos = -1; - } - preg_match ($this->_regs[$this->_state], $this->_str, $m, PREG_OFFSET_CAPTURE, $this->_pos); - $n = 1; - - - foreach ($this->_counts[$this->_state] as $i=>$count) { - if (!isset($m[$n])) { - break; - } - if ($m[$n][1]>-1 && ($endpos == -1 || $m[$n][1] < $endpos)) { - if ($this->_states[$this->_state][$i] != -1) { - $this->_tokenStack[] = array($this->_delim[$this->_state][$i], $m[$n][0]); - } else { - $inner = $this->_inner[$this->_state][$i]; - if (isset($this->_parts[$this->_state][$i])) { - $parts = array(); - $partpos = $m[$n][1]; - for ($j=1; $j<=$count; $j++) { - if ($m[$j+$n][1] < 0) { - continue; - } - if (isset($this->_parts[$this->_state][$i][$j])) { - if ($m[$j+$n][1] > $partpos) { - array_unshift($parts, array($inner, substr($this->_str, $partpos, $m[$j+$n][1]-$partpos))); - } - array_unshift($parts, array($this->_parts[$this->_state][$i][$j], $m[$j+$n][0])); - } - $partpos = $m[$j+$n][1] + strlen($m[$j+$n][0]); - } - if ($partpos < $m[$n][1] + strlen($m[$n][0])) { - array_unshift($parts, array($inner, substr($this->_str, $partpos, $m[$n][1] - $partpos + strlen($m[$n][0])))); - } - $this->_tokenStack = array_merge($this->_tokenStack, $parts); - } else { - foreach ($this->_keywords[$this->_state][$i] as $g => $re) { - if (isset($this->_disabled[$g])) { - continue; - } - if (preg_match($re, $m[$n][0])) { - $inner = $this->_kwmap[$g]; - break; - } - } - $this->_tokenStack[] = array($inner, $m[$n][0]); - } - } - if ($m[$n][1] > $this->_pos) { - $this->_tokenStack[] = array($this->_lastinner, substr($this->_str, $this->_pos, $m[$n][1]-$this->_pos)); - } - $this->_pos = $m[$n][1] + strlen($m[$n][0]); - if ($this->_states[$this->_state][$i] != -1) { - $this->_stack[] = array($this->_state, $this->_lastdelim, $this->_lastinner, $this->_endpattern); - $this->_lastinner = $this->_inner[$this->_state][$i]; - $this->_lastdelim = $this->_delim[$this->_state][$i]; - $l = $this->_state; - $this->_state = $this->_states[$this->_state][$i]; - $this->_endpattern = $this->_end[$this->_state]; - if ($this->_subst[$l][$i]) { - for ($k=0; $k<=$this->_counts[$l][$i]; $k++) { - if (!isset($m[$i+$k])) { - break; - } - $quoted = preg_quote($m[$n+$k][0], '/'); - $this->_endpattern = str_replace('%'.$k.'%', $quoted, $this->_endpattern); - $this->_endpattern = str_replace('%b'.$k.'%', $this->_matchingBrackets($quoted), $this->_endpattern); - } - } - } - return array_pop($this->_tokenStack); - } - $n += $count + 1; - } - - if ($endpos > -1) { - $this->_tokenStack[] = array($this->_lastdelim, $endmatch); - if ($endpos > $this->_pos) { - $this->_tokenStack[] = array($this->_lastinner, substr($this->_str, $this->_pos, $endpos-$this->_pos)); - } - list($this->_state, $this->_lastdelim, $this->_lastinner, $this->_endpattern) = array_pop($this->_stack); - $this->_pos = $endpos + strlen($endmatch); - return array_pop($this->_tokenStack); - } - $p = $this->_pos; - $this->_pos = HL_INFINITY; - return array($this->_lastinner, substr($this->_str, $p)); - } - - - - - // {{{ highlight - - /** - * Highlights code - * - * @param string $str Code to highlight - * @access public - * @return string Highlighted text - * - */ - - function highlight($str) - { - if (!($this->_renderer)) { - include_once(dirname(__FILE__).'/Renderer/Html.php'); - $this->_renderer = new Text_Highlighter_Renderer_Html($this->_options); - } - $this->_state = -1; - $this->_pos = 0; - $this->_stack = array(); - $this->_tokenStack = array(); - $this->_lastinner = $this->_defClass; - $this->_lastdelim = $this->_defClass; - $this->_endpattern = ''; - $this->_renderer->reset(); - $this->_renderer->setCurrentLanguage($this->_language); - $this->_str = $this->_renderer->preprocess($str); - $this->_len = strlen($this->_str); - while ($token = $this->_getToken()) { - $this->_renderer->acceptToken($token[0], $token[1]); - } - $this->_renderer->finalize(); - return $this->_renderer->getOutput(); - } - - // }}} - -} - -// }}} - -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * c-hanging-comment-ender-p: nil - * End: - */ - -?> + + * @copyright 2004-2006 Andrey Demenev + * @license http://www.php.net/license/3_0.txt PHP License + * @version CVS: $Id: Highlighter.php,v 1.1 2007/06/03 02:35:28 ssttoo Exp $ + * @link http://pear.php.net/package/Text_Highlighter + */ + +// {{{ BC constants + +// BC trick : define constants related to default +// renderer if needed +if (!defined('HL_NUMBERS_LI')) { + /**#@+ + * Constant for use with $options['numbers'] + * @see Text_Highlighter_Renderer_Html::_init() + */ + /** + * use numbered list + */ + define ('HL_NUMBERS_LI' , 1); + /** + * Use 2-column table with line numbers in left column and code in right column. + * Forces $options['tag'] = HL_TAG_PRE + */ + define ('HL_NUMBERS_TABLE' , 2); + /**#@-*/ +} + +// }}} +// {{{ constants +/** + * for our purpose, it is infinity + */ +define ('HL_INFINITY', 1000000000); + +// }}} + +/** + * Text highlighter base class + * + * @author Andrey Demenev + * @copyright 2004-2006 Andrey Demenev + * @license http://www.php.net/license/3_0.txt PHP License + * @version Release: 0.7.1 + * @link http://pear.php.net/package/Text_Highlighter + */ + +// {{{ Text_Highlighter + +/** + * Text highlighter base class + * + * This class implements all functions necessary for highlighting, + * but it does not contain highlighting rules. Actual highlighting is + * done using a descendent of this class. + * + * One is not supposed to manually create descendent classes. + * Instead, describe highlighting rules in XML format and + * use {@link Text_Highlighter_Generator} to create descendent class. + * Alternatively, an instance of a descendent class can be created + * directly. + * + * Use {@link Text_Highlighter::factory()} to create an + * object for particular language highlighter + * + * Usage example + * + *require_once 'Text/Highlighter.php'; + *$hlSQL =& Text_Highlighter::factory('SQL',array('numbers'=>true)); + *echo $hlSQL->highlight('SELECT * FROM table a WHERE id = 12'); + * + * + * @author Andrey Demenev + * @package Text_Highlighter + * @access public + */ + +class Text_Highlighter +{ + // {{{ members + + /** + * Syntax highlighting rules. + * Auto-generated classes set this var + * + * @access protected + * @see _init + * @var array + */ + var $_syntax; + + /** + * Renderer object. + * + * @access private + * @var array + */ + var $_renderer; + + /** + * Options. Keeped for BC + * + * @access protected + * @var array + */ + var $_options = array(); + + /** + * Conditionds + * + * @access protected + * @var array + */ + var $_conditions = array(); + + /** + * Disabled keywords + * + * @access protected + * @var array + */ + var $_disabled = array(); + + /** + * Language + * + * @access protected + * @var string + */ + var $_language = ''; + + // }}} + // {{{ _checkDefines + + /** + * Called by subclssses' constructors to enable/disable + * optional highlighter rules + * + * @param array $defines Conditional defines + * + * @access protected + */ + function _checkDefines() + { + if (isset($this->_options['defines'])) { + $defines = $this->_options['defines']; + } else { + $defines = array(); + } + foreach ($this->_conditions as $name => $actions) { + foreach($actions as $action) { + $present = in_array($name, $defines); + if (!$action[1]) { + $present = !$present; + } + if ($present) { + unset($this->_disabled[$action[0]]); + } else { + $this->_disabled[$action[0]] = true; + } + } + } + } + + // }}} + // {{{ factory + + /** + * Create a new Highlighter object for specified language + * + * @param string $lang language, for example "SQL" + * @param array $options Rendering options. This + * parameter is only keeped for BC reasons, use + * {@link Text_Highlighter::setRenderer()} instead + * + * @return mixed a newly created Highlighter object, or + * a PEAR error object on error + * + * @static + * @access public + */ + public static function factory($lang, $options = array()) + { + $lang = strtoupper($lang); + $langFile = dirname(__FILE__)."/Highlighter/$lang.php"; + if (is_file($langFile)) + include_once $langFile; + else + return false; + + $classname = 'Text_Highlighter_' . $lang; + + if (!class_exists($classname)) + return false; + + return new $classname($options); + } + + // }}} + // {{{ setRenderer + + /** + * Set renderer object + * + * @param object $renderer Text_Highlighter_Renderer + * + * @access public + */ + function setRenderer($renderer) + { + $this->_renderer = $renderer; + } + + // }}} + + /** + * Helper function to find matching brackets + * + * @access private + */ + function _matchingBrackets($str) + { + return strtr($str, '()<>[]{}', ')(><][}{'); + } + + + + + function _getToken() + { + if (!empty($this->_tokenStack)) { + return array_pop($this->_tokenStack); + } + if ($this->_pos >= $this->_len) { + return NULL; + } + + if ($this->_state != -1 && preg_match($this->_endpattern, $this->_str, $m, PREG_OFFSET_CAPTURE, $this->_pos)) { + $endpos = $m[0][1]; + $endmatch = $m[0][0]; + } else { + $endpos = -1; + } + preg_match ($this->_regs[$this->_state], $this->_str, $m, PREG_OFFSET_CAPTURE, $this->_pos); + $n = 1; + + + foreach ($this->_counts[$this->_state] as $i=>$count) { + if (!isset($m[$n])) { + break; + } + if ($m[$n][1]>-1 && ($endpos == -1 || $m[$n][1] < $endpos)) { + if ($this->_states[$this->_state][$i] != -1) { + $this->_tokenStack[] = array($this->_delim[$this->_state][$i], $m[$n][0]); + } else { + $inner = $this->_inner[$this->_state][$i]; + if (isset($this->_parts[$this->_state][$i])) { + $parts = array(); + $partpos = $m[$n][1]; + for ($j=1; $j<=$count; $j++) { + if ($m[$j+$n][1] < 0) { + continue; + } + if (isset($this->_parts[$this->_state][$i][$j])) { + if ($m[$j+$n][1] > $partpos) { + array_unshift($parts, array($inner, substr($this->_str, $partpos, $m[$j+$n][1]-$partpos))); + } + array_unshift($parts, array($this->_parts[$this->_state][$i][$j], $m[$j+$n][0])); + } + $partpos = $m[$j+$n][1] + strlen($m[$j+$n][0]); + } + if ($partpos < $m[$n][1] + strlen($m[$n][0])) { + array_unshift($parts, array($inner, substr($this->_str, $partpos, $m[$n][1] - $partpos + strlen($m[$n][0])))); + } + $this->_tokenStack = array_merge($this->_tokenStack, $parts); + } else { + foreach ($this->_keywords[$this->_state][$i] as $g => $re) { + if (isset($this->_disabled[$g])) { + continue; + } + if (preg_match($re, $m[$n][0])) { + $inner = $this->_kwmap[$g]; + break; + } + } + $this->_tokenStack[] = array($inner, $m[$n][0]); + } + } + if ($m[$n][1] > $this->_pos) { + $this->_tokenStack[] = array($this->_lastinner, substr($this->_str, $this->_pos, $m[$n][1]-$this->_pos)); + } + $this->_pos = $m[$n][1] + strlen($m[$n][0]); + if ($this->_states[$this->_state][$i] != -1) { + $this->_stack[] = array($this->_state, $this->_lastdelim, $this->_lastinner, $this->_endpattern); + $this->_lastinner = $this->_inner[$this->_state][$i]; + $this->_lastdelim = $this->_delim[$this->_state][$i]; + $l = $this->_state; + $this->_state = $this->_states[$this->_state][$i]; + $this->_endpattern = $this->_end[$this->_state]; + if ($this->_subst[$l][$i]) { + for ($k=0; $k<=$this->_counts[$l][$i]; $k++) { + if (!isset($m[$i+$k])) { + break; + } + $quoted = preg_quote($m[$n+$k][0], '/'); + $this->_endpattern = str_replace('%'.$k.'%', $quoted, $this->_endpattern); + $this->_endpattern = str_replace('%b'.$k.'%', $this->_matchingBrackets($quoted), $this->_endpattern); + } + } + } + return array_pop($this->_tokenStack); + } + $n += $count + 1; + } + + if ($endpos > -1) { + $this->_tokenStack[] = array($this->_lastdelim, $endmatch); + if ($endpos > $this->_pos) { + $this->_tokenStack[] = array($this->_lastinner, substr($this->_str, $this->_pos, $endpos-$this->_pos)); + } + list($this->_state, $this->_lastdelim, $this->_lastinner, $this->_endpattern) = array_pop($this->_stack); + $this->_pos = $endpos + strlen($endmatch); + return array_pop($this->_tokenStack); + } + $p = $this->_pos; + $this->_pos = HL_INFINITY; + return array($this->_lastinner, substr($this->_str, $p)); + } + + + + + // {{{ highlight + + /** + * Highlights code + * + * @param string $str Code to highlight + * @access public + * @return string Highlighted text + * + */ + + function highlight($str) + { + if (!($this->_renderer)) { + include_once(dirname(__FILE__).'/Renderer/Html.php'); + $this->_renderer = new Text_Highlighter_Renderer_Html($this->_options); + } + $this->_state = -1; + $this->_pos = 0; + $this->_stack = array(); + $this->_tokenStack = array(); + $this->_lastinner = $this->_defClass; + $this->_lastdelim = $this->_defClass; + $this->_endpattern = ''; + $this->_renderer->reset(); + $this->_renderer->setCurrentLanguage($this->_language); + $this->_str = $this->_renderer->preprocess($str); + $this->_len = strlen($this->_str); + while ($token = $this->_getToken()) { + $this->_renderer->acceptToken($token[0], $token[1]); + } + $this->_renderer->finalize(); + return $this->_renderer->getOutput(); + } + + // }}} + +} + +// }}} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ + +?> diff --git a/framework/vendors/TextHighlighter/Text/Highlighter/ABAP.php b/framework/vendors/TextHighlighter/Text/Highlighter/ABAP.php index 38214b425..187daff15 100644 --- a/framework/vendors/TextHighlighter/Text/Highlighter/ABAP.php +++ b/framework/vendors/TextHighlighter/Text/Highlighter/ABAP.php @@ -1,53 +1,53 @@ - - * - */ - -/** - * @ignore - */ - -/** - * Auto-generated class. ABAP syntax highlighting - * + * + */ + +/** + * @ignore + */ + +/** + * Auto-generated class. ABAP syntax highlighting + * * @author Stoyan Stefanov - * @category Text - * @package Text_Highlighter - * @copyright 2004-2006 Andrey Demenev - * @license http://www.php.net/license/3_0.txt PHP License - * @version Release: 0.7.1 - * @link http://pear.php.net/package/Text_Highlighter - */ -class Text_Highlighter_ABAP extends Text_Highlighter -{ + * @category Text + * @package Text_Highlighter + * @copyright 2004-2006 Andrey Demenev + * @license http://www.php.net/license/3_0.txt PHP License + * @version Release: 0.7.1 + * @link http://pear.php.net/package/Text_Highlighter + */ +class Text_Highlighter_ABAP extends Text_Highlighter +{ var $_language = 'abap'; - /** - * Constructor - * - * @param array $options - * @access public - */ - function __construct($options=array()) - { - + /** + * Constructor + * + * @param array $options + * @access public + */ + function __construct($options=array()) + { + $this->_options = $options; $this->_regs = array ( -1 => '/((?i)\\{)|((?i)\\()|((?i)\\[)|((?i)^\\*|")|((?i)\')|((?i)[a-z_\\-]\\w*)/', @@ -498,8 +498,8 @@ class Text_Highlighter_ABAP extends Text_Highlighter 'reserved' => 'reserved', 'constants' => 'reserved', ); - $this->_defClass = 'code'; - $this->_checkDefines(); - } - + $this->_defClass = 'code'; + $this->_checkDefines(); + } + } \ No newline at end of file diff --git a/framework/vendors/TextHighlighter/Text/Highlighter/CPP.php b/framework/vendors/TextHighlighter/Text/Highlighter/CPP.php index ad13437fa..eccaa084e 100644 --- a/framework/vendors/TextHighlighter/Text/Highlighter/CPP.php +++ b/framework/vendors/TextHighlighter/Text/Highlighter/CPP.php @@ -1,56 +1,56 @@ - - * - */ - -/** - * Auto-generated class. CPP syntax highlighting - * + * + */ + +/** + * Auto-generated class. CPP syntax highlighting + * * @author Aaron Kalin * @author Andrey Demenev - * @category Text - * @package Text_Highlighter - * @copyright 2004-2006 Andrey Demenev - * @license http://www.php.net/license/3_0.txt PHP License - * @version Release: 0.7.1 - * @link http://pear.php.net/package/Text_Highlighter - */ -class Text_Highlighter_CPP extends Text_Highlighter -{ + * @category Text + * @package Text_Highlighter + * @copyright 2004-2006 Andrey Demenev + * @license http://www.php.net/license/3_0.txt PHP License + * @version Release: 0.7.1 + * @link http://pear.php.net/package/Text_Highlighter + */ +class Text_Highlighter_CPP extends Text_Highlighter +{ var $_language = 'cpp'; - /** - * Constructor - * - * @param array $options - * @access public - */ - function __construct($options=array()) - { - + /** + * Constructor + * + * @param array $options + * @access public + */ + function __construct($options=array()) + { + $this->_options = $options; $this->_regs = array ( -1 => '/((?i)")|((?i)\\{)|((?i)\\()|((?i)\\[)|((?i)[a-z_]\\w*)|((?mi)^[ \\t]*#include)|((?mii)^[ \\t]*#[ \\t]*[a-z]+)|((?i)\\d*\\.?\\d+)|((?i)\\/\\*)|((?i)\\/\\/.+)/', @@ -833,8 +833,8 @@ class Text_Highlighter_CPP extends Text_Highlighter 'types' => 'types', 'Common Macros' => 'prepro', ); - $this->_defClass = 'code'; - $this->_checkDefines(); - } - + $this->_defClass = 'code'; + $this->_checkDefines(); + } + } \ No newline at end of file diff --git a/framework/vendors/TextHighlighter/Text/Highlighter/CSS.php b/framework/vendors/TextHighlighter/Text/Highlighter/CSS.php index 86a69acca..bf04a2fb0 100644 --- a/framework/vendors/TextHighlighter/Text/Highlighter/CSS.php +++ b/framework/vendors/TextHighlighter/Text/Highlighter/CSS.php @@ -1,419 +1,419 @@ - - * - */ - -/** - * Auto-generated class. CSS syntax highlighting - * - * @author Andrey Demenev - * @category Text - * @package Text_Highlighter - * @copyright 2004-2006 Andrey Demenev - * @license http://www.php.net/license/3_0.txt PHP License - * @version Release: 0.7.1 - * @link http://pear.php.net/package/Text_Highlighter - */ -class Text_Highlighter_CSS extends Text_Highlighter -{ - var $_language = 'css'; - - /** - * Constructor - * - * @param array $options - * @access public - */ - function __construct($options=array()) - { - - $this->_options = $options; - $this->_regs = array ( - -1 => '/((?i)\\/\\*)|((?i)(@[a-z\\d]+))|((?i)(((\\.|#)?[a-z]+[a-z\\d\\-]*(?![a-z\\d\\-]))|(\\*))(?!\\s*:\\s*[\\s\\{]))|((?i):[a-z][a-z\\d\\-]*)|((?i)\\[)|((?i)\\{)/', - 0 => '//', - 1 => '/((?i)\\d*\\.?\\d+(\\%|em|ex|pc|pt|px|in|mm|cm))|((?i)\\d*\\.?\\d+)|((?i)[a-z][a-z\\d\\-]*)|((?i)#([\\da-f]{6}|[\\da-f]{3})\\b)/', - 2 => '/((?i)\')|((?i)")|((?i)[\\w\\-\\:]+)/', - 3 => '/((?i)\\/\\*)|((?i)[a-z][a-z\\d\\-]*\\s*:)|((?i)(((\\.|#)?[a-z]+[a-z\\d\\-]*(?![a-z\\d\\-]))|(\\*))(?!\\s*:\\s*[\\s\\{]))|((?i)\\{)/', - 4 => '/((?i)\\\\[\\\\(\\\\)\\\\])/', - 5 => '/((?i)\\\\\\\\|\\\\"|\\\\\'|\\\\`)/', - 6 => '/((?i)\\\\\\\\|\\\\"|\\\\\'|\\\\`|\\\\t|\\\\n|\\\\r)/', - ); - $this->_counts = array ( - -1 => - array ( - 0 => 0, - 1 => 1, - 2 => 4, - 3 => 0, - 4 => 0, - 5 => 0, - ), - 0 => - array ( - ), - 1 => - array ( - 0 => 1, - 1 => 0, - 2 => 0, - 3 => 1, - ), - 2 => - array ( - 0 => 0, - 1 => 0, - 2 => 0, - ), - 3 => - array ( - 0 => 0, - 1 => 0, - 2 => 4, - 3 => 0, - ), - 4 => - array ( - 0 => 0, - ), - 5 => - array ( - 0 => 0, - ), - 6 => - array ( - 0 => 0, - ), - ); - $this->_delim = array ( - -1 => - array ( - 0 => 'comment', - 1 => '', - 2 => '', - 3 => '', - 4 => 'brackets', - 5 => 'brackets', - ), - 0 => - array ( - ), - 1 => - array ( - 0 => '', - 1 => '', - 2 => '', - 3 => '', - ), - 2 => - array ( - 0 => 'quotes', - 1 => 'quotes', - 2 => '', - ), - 3 => - array ( - 0 => 'comment', - 1 => 'reserved', - 2 => '', - 3 => 'brackets', - ), - 4 => - array ( - 0 => '', - ), - 5 => - array ( - 0 => '', - ), - 6 => - array ( - 0 => '', - ), - ); - $this->_inner = array ( - -1 => - array ( - 0 => 'comment', - 1 => 'var', - 2 => 'identifier', - 3 => 'special', - 4 => 'code', - 5 => 'code', - ), - 0 => - array ( - ), - 1 => - array ( - 0 => 'number', - 1 => 'number', - 2 => 'code', - 3 => 'var', - ), - 2 => - array ( - 0 => 'string', - 1 => 'string', - 2 => 'var', - ), - 3 => - array ( - 0 => 'comment', - 1 => 'code', - 2 => 'identifier', - 3 => 'code', - ), - 4 => - array ( - 0 => 'string', - ), - 5 => - array ( - 0 => 'special', - ), - 6 => - array ( - 0 => 'special', - ), - ); - $this->_end = array ( - 0 => '/(?i)\\*\\//', - 1 => '/(?i)(?=;|\\})/', - 2 => '/(?i)\\]/', - 3 => '/(?i)\\}/', - 4 => '/(?i)\\)/', - 5 => '/(?i)\'/', - 6 => '/(?i)"/', - ); - $this->_states = array ( - -1 => - array ( - 0 => 0, - 1 => -1, - 2 => -1, - 3 => -1, - 4 => 2, - 5 => 3, - ), - 0 => - array ( - ), - 1 => - array ( - 0 => -1, - 1 => -1, - 2 => -1, - 3 => -1, - ), - 2 => - array ( - 0 => 5, - 1 => 6, - 2 => -1, - ), - 3 => - array ( - 0 => 0, - 1 => 1, - 2 => -1, - 3 => 3, - ), - 4 => - array ( - 0 => -1, - ), - 5 => - array ( - 0 => -1, - ), - 6 => - array ( - 0 => -1, - ), - ); - $this->_keywords = array ( - -1 => - array ( - 0 => -1, - 1 => - array ( - ), - 2 => - array ( - ), - 3 => - array ( - ), - 4 => -1, - 5 => -1, - ), - 0 => - array ( - ), - 1 => - array ( - 0 => - array ( - ), - 1 => - array ( - ), - 2 => - array ( - 'propertyValue' => '/^((?i)far-left|left|center-left|center-right|center|far-right|right-side|right|behind|leftwards|rightwards|inherit|scroll|fixed|transparent|none|repeat-x|repeat-y|repeat|no-repeat|collapse|separate|auto|top|bottom|both|open-quote|close-quote|no-open-quote|no-close-quote|crosshair|default|pointer|move|e-resize|ne-resize|nw-resize|n-resize|se-resize|sw-resize|s-resize|text|wait|help|ltr|rtl|inline|block|list-item|run-in|compact|marker|table|inline-table|table-row-group|table-header-group|table-footer-group|table-row|table-column-group|table-column|table-cell|table-caption|below|level|above|higher|lower|show|hide|caption|icon|menu|message-box|small-caption|status-bar|normal|wider|narrower|ultra-condensed|extra-condensed|condensed|semi-condensed|semi-expanded|expanded|extra-expanded|ultra-expanded|italic|oblique|small-caps|bold|bolder|lighter|inside|outside|disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman|lower-greek|lower-alpha|lower-latin|upper-alpha|upper-latin|hebrew|armenian|georgian|cjk-ideographic|hiragana|katakana|hiragana-iroha|katakana-iroha|crop|cross|invert|visible|hidden|always|avoid|x-low|low|medium|high|x-high|mix?|repeat?|static|relative|absolute|portrait|landscape|spell-out|once|digits|continuous|code|x-slow|slow|fast|x-fast|faster|slower|justify|underline|overline|line-through|blink|capitalize|uppercase|lowercase|embed|bidi-override|baseline|sub|super|text-top|middle|text-bottom|silent|x-soft|soft|loud|x-loud|pre|nowrap|serif|sans-serif|cursive|fantasy|monospace|empty|string|strict|loose|char|true|false|dotted|dashed|solid|double|groove|ridge|inset|outset|larger|smaller|xx-small|x-small|small|large|x-large|xx-large|all|newspaper|distribute|distribute-all-lines|distribute-center-last|inter-word|inter-ideograph|inter-cluster|kashida|ideograph-alpha|ideograph-numeric|ideograph-parenthesis|ideograph-space|keep-all|break-all|break-word|lr-tb|tb-rl|thin|thick|inline-block|w-resize|hand|distribute-letter|distribute-space|whitespace|male|female|child)$/', - 'namedcolor' => '/^((?i)aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|purple|red|silver|teal|white|yellow|activeborder|activecaption|appworkspace|background|buttonface|buttonhighlight|buttonshadow|buttontext|captiontext|graytext|highlight|highlighttext|inactiveborder|inactivecaption|inactivecaptiontext|infobackground|infotext|menu|menutext|scrollbar|threeddarkshadow|threedface|threedhighlight|threedlightshadow|threedshadow|window|windowframe|windowtext)$/', - ), - 3 => - array ( - ), - ), - 2 => - array ( - 0 => -1, - 1 => -1, - 2 => - array ( - ), - ), - 3 => - array ( - 0 => -1, - 1 => -1, - 2 => - array ( - ), - 3 => -1, - ), - 4 => - array ( - 0 => - array ( - ), - ), - 5 => - array ( - 0 => - array ( - ), - ), - 6 => - array ( - 0 => - array ( - ), - ), - ); - $this->_parts = array ( - 0 => - array ( - ), - 1 => - array ( - 0 => - array ( - 1 => 'string', - ), - 1 => NULL, - 2 => NULL, - 3 => NULL, - ), - 2 => - array ( - 0 => NULL, - 1 => NULL, - 2 => NULL, - ), - 3 => - array ( - 0 => NULL, - 1 => NULL, - 2 => NULL, - 3 => NULL, - ), - 4 => - array ( - 0 => NULL, - ), - 5 => - array ( - 0 => NULL, - ), - 6 => - array ( - 0 => NULL, - ), - ); - $this->_subst = array ( - -1 => - array ( - 0 => false, - 1 => false, - 2 => false, - 3 => false, - 4 => false, - 5 => false, - ), - 0 => - array ( - ), - 1 => - array ( - 0 => false, - 1 => false, - 2 => false, - 3 => false, - ), - 2 => - array ( - 0 => false, - 1 => false, - 2 => false, - ), - 3 => - array ( - 0 => false, - 1 => false, - 2 => false, - 3 => false, - ), - 4 => - array ( - 0 => false, - ), - 5 => - array ( - 0 => false, - ), - 6 => - array ( - 0 => false, - ), - ); - $this->_conditions = array ( - ); - $this->_kwmap = array ( - 'propertyValue' => 'string', - 'namedcolor' => 'var', - ); - $this->_defClass = 'code'; - $this->_checkDefines(); - } - + + * + */ + +/** + * Auto-generated class. CSS syntax highlighting + * + * @author Andrey Demenev + * @category Text + * @package Text_Highlighter + * @copyright 2004-2006 Andrey Demenev + * @license http://www.php.net/license/3_0.txt PHP License + * @version Release: 0.7.1 + * @link http://pear.php.net/package/Text_Highlighter + */ +class Text_Highlighter_CSS extends Text_Highlighter +{ + var $_language = 'css'; + + /** + * Constructor + * + * @param array $options + * @access public + */ + function __construct($options=array()) + { + + $this->_options = $options; + $this->_regs = array ( + -1 => '/((?i)\\/\\*)|((?i)(@[a-z\\d]+))|((?i)(((\\.|#)?[a-z]+[a-z\\d\\-]*(?![a-z\\d\\-]))|(\\*))(?!\\s*:\\s*[\\s\\{]))|((?i):[a-z][a-z\\d\\-]*)|((?i)\\[)|((?i)\\{)/', + 0 => '//', + 1 => '/((?i)\\d*\\.?\\d+(\\%|em|ex|pc|pt|px|in|mm|cm))|((?i)\\d*\\.?\\d+)|((?i)[a-z][a-z\\d\\-]*)|((?i)#([\\da-f]{6}|[\\da-f]{3})\\b)/', + 2 => '/((?i)\')|((?i)")|((?i)[\\w\\-\\:]+)/', + 3 => '/((?i)\\/\\*)|((?i)[a-z][a-z\\d\\-]*\\s*:)|((?i)(((\\.|#)?[a-z]+[a-z\\d\\-]*(?![a-z\\d\\-]))|(\\*))(?!\\s*:\\s*[\\s\\{]))|((?i)\\{)/', + 4 => '/((?i)\\\\[\\\\(\\\\)\\\\])/', + 5 => '/((?i)\\\\\\\\|\\\\"|\\\\\'|\\\\`)/', + 6 => '/((?i)\\\\\\\\|\\\\"|\\\\\'|\\\\`|\\\\t|\\\\n|\\\\r)/', + ); + $this->_counts = array ( + -1 => + array ( + 0 => 0, + 1 => 1, + 2 => 4, + 3 => 0, + 4 => 0, + 5 => 0, + ), + 0 => + array ( + ), + 1 => + array ( + 0 => 1, + 1 => 0, + 2 => 0, + 3 => 1, + ), + 2 => + array ( + 0 => 0, + 1 => 0, + 2 => 0, + ), + 3 => + array ( + 0 => 0, + 1 => 0, + 2 => 4, + 3 => 0, + ), + 4 => + array ( + 0 => 0, + ), + 5 => + array ( + 0 => 0, + ), + 6 => + array ( + 0 => 0, + ), + ); + $this->_delim = array ( + -1 => + array ( + 0 => 'comment', + 1 => '', + 2 => '', + 3 => '', + 4 => 'brackets', + 5 => 'brackets', + ), + 0 => + array ( + ), + 1 => + array ( + 0 => '', + 1 => '', + 2 => '', + 3 => '', + ), + 2 => + array ( + 0 => 'quotes', + 1 => 'quotes', + 2 => '', + ), + 3 => + array ( + 0 => 'comment', + 1 => 'reserved', + 2 => '', + 3 => 'brackets', + ), + 4 => + array ( + 0 => '', + ), + 5 => + array ( + 0 => '', + ), + 6 => + array ( + 0 => '', + ), + ); + $this->_inner = array ( + -1 => + array ( + 0 => 'comment', + 1 => 'var', + 2 => 'identifier', + 3 => 'special', + 4 => 'code', + 5 => 'code', + ), + 0 => + array ( + ), + 1 => + array ( + 0 => 'number', + 1 => 'number', + 2 => 'code', + 3 => 'var', + ), + 2 => + array ( + 0 => 'string', + 1 => 'string', + 2 => 'var', + ), + 3 => + array ( + 0 => 'comment', + 1 => 'code', + 2 => 'identifier', + 3 => 'code', + ), + 4 => + array ( + 0 => 'string', + ), + 5 => + array ( + 0 => 'special', + ), + 6 => + array ( + 0 => 'special', + ), + ); + $this->_end = array ( + 0 => '/(?i)\\*\\//', + 1 => '/(?i)(?=;|\\})/', + 2 => '/(?i)\\]/', + 3 => '/(?i)\\}/', + 4 => '/(?i)\\)/', + 5 => '/(?i)\'/', + 6 => '/(?i)"/', + ); + $this->_states = array ( + -1 => + array ( + 0 => 0, + 1 => -1, + 2 => -1, + 3 => -1, + 4 => 2, + 5 => 3, + ), + 0 => + array ( + ), + 1 => + array ( + 0 => -1, + 1 => -1, + 2 => -1, + 3 => -1, + ), + 2 => + array ( + 0 => 5, + 1 => 6, + 2 => -1, + ), + 3 => + array ( + 0 => 0, + 1 => 1, + 2 => -1, + 3 => 3, + ), + 4 => + array ( + 0 => -1, + ), + 5 => + array ( + 0 => -1, + ), + 6 => + array ( + 0 => -1, + ), + ); + $this->_keywords = array ( + -1 => + array ( + 0 => -1, + 1 => + array ( + ), + 2 => + array ( + ), + 3 => + array ( + ), + 4 => -1, + 5 => -1, + ), + 0 => + array ( + ), + 1 => + array ( + 0 => + array ( + ), + 1 => + array ( + ), + 2 => + array ( + 'propertyValue' => '/^((?i)far-left|left|center-left|center-right|center|far-right|right-side|right|behind|leftwards|rightwards|inherit|scroll|fixed|transparent|none|repeat-x|repeat-y|repeat|no-repeat|collapse|separate|auto|top|bottom|both|open-quote|close-quote|no-open-quote|no-close-quote|crosshair|default|pointer|move|e-resize|ne-resize|nw-resize|n-resize|se-resize|sw-resize|s-resize|text|wait|help|ltr|rtl|inline|block|list-item|run-in|compact|marker|table|inline-table|table-row-group|table-header-group|table-footer-group|table-row|table-column-group|table-column|table-cell|table-caption|below|level|above|higher|lower|show|hide|caption|icon|menu|message-box|small-caption|status-bar|normal|wider|narrower|ultra-condensed|extra-condensed|condensed|semi-condensed|semi-expanded|expanded|extra-expanded|ultra-expanded|italic|oblique|small-caps|bold|bolder|lighter|inside|outside|disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman|lower-greek|lower-alpha|lower-latin|upper-alpha|upper-latin|hebrew|armenian|georgian|cjk-ideographic|hiragana|katakana|hiragana-iroha|katakana-iroha|crop|cross|invert|visible|hidden|always|avoid|x-low|low|medium|high|x-high|mix?|repeat?|static|relative|absolute|portrait|landscape|spell-out|once|digits|continuous|code|x-slow|slow|fast|x-fast|faster|slower|justify|underline|overline|line-through|blink|capitalize|uppercase|lowercase|embed|bidi-override|baseline|sub|super|text-top|middle|text-bottom|silent|x-soft|soft|loud|x-loud|pre|nowrap|serif|sans-serif|cursive|fantasy|monospace|empty|string|strict|loose|char|true|false|dotted|dashed|solid|double|groove|ridge|inset|outset|larger|smaller|xx-small|x-small|small|large|x-large|xx-large|all|newspaper|distribute|distribute-all-lines|distribute-center-last|inter-word|inter-ideograph|inter-cluster|kashida|ideograph-alpha|ideograph-numeric|ideograph-parenthesis|ideograph-space|keep-all|break-all|break-word|lr-tb|tb-rl|thin|thick|inline-block|w-resize|hand|distribute-letter|distribute-space|whitespace|male|female|child)$/', + 'namedcolor' => '/^((?i)aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|purple|red|silver|teal|white|yellow|activeborder|activecaption|appworkspace|background|buttonface|buttonhighlight|buttonshadow|buttontext|captiontext|graytext|highlight|highlighttext|inactiveborder|inactivecaption|inactivecaptiontext|infobackground|infotext|menu|menutext|scrollbar|threeddarkshadow|threedface|threedhighlight|threedlightshadow|threedshadow|window|windowframe|windowtext)$/', + ), + 3 => + array ( + ), + ), + 2 => + array ( + 0 => -1, + 1 => -1, + 2 => + array ( + ), + ), + 3 => + array ( + 0 => -1, + 1 => -1, + 2 => + array ( + ), + 3 => -1, + ), + 4 => + array ( + 0 => + array ( + ), + ), + 5 => + array ( + 0 => + array ( + ), + ), + 6 => + array ( + 0 => + array ( + ), + ), + ); + $this->_parts = array ( + 0 => + array ( + ), + 1 => + array ( + 0 => + array ( + 1 => 'string', + ), + 1 => NULL, + 2 => NULL, + 3 => NULL, + ), + 2 => + array ( + 0 => NULL, + 1 => NULL, + 2 => NULL, + ), + 3 => + array ( + 0 => NULL, + 1 => NULL, + 2 => NULL, + 3 => NULL, + ), + 4 => + array ( + 0 => NULL, + ), + 5 => + array ( + 0 => NULL, + ), + 6 => + array ( + 0 => NULL, + ), + ); + $this->_subst = array ( + -1 => + array ( + 0 => false, + 1 => false, + 2 => false, + 3 => false, + 4 => false, + 5 => false, + ), + 0 => + array ( + ), + 1 => + array ( + 0 => false, + 1 => false, + 2 => false, + 3 => false, + ), + 2 => + array ( + 0 => false, + 1 => false, + 2 => false, + ), + 3 => + array ( + 0 => false, + 1 => false, + 2 => false, + 3 => false, + ), + 4 => + array ( + 0 => false, + ), + 5 => + array ( + 0 => false, + ), + 6 => + array ( + 0 => false, + ), + ); + $this->_conditions = array ( + ); + $this->_kwmap = array ( + 'propertyValue' => 'string', + 'namedcolor' => 'var', + ); + $this->_defClass = 'code'; + $this->_checkDefines(); + } + } \ No newline at end of file diff --git a/framework/vendors/TextHighlighter/Text/Highlighter/DIFF.php b/framework/vendors/TextHighlighter/Text/Highlighter/DIFF.php index 1463e6abb..d084974b1 100644 --- a/framework/vendors/TextHighlighter/Text/Highlighter/DIFF.php +++ b/framework/vendors/TextHighlighter/Text/Highlighter/DIFF.php @@ -1,49 +1,49 @@ - - * - */ - -/** - * Auto-generated class. DIFF syntax highlighting - * + * + */ + +/** + * Auto-generated class. DIFF syntax highlighting + * * @author Andrey Demenev - * @category Text - * @package Text_Highlighter - * @copyright 2004-2006 Andrey Demenev - * @license http://www.php.net/license/3_0.txt PHP License - * @version Release: 0.7.1 - * @link http://pear.php.net/package/Text_Highlighter - */ -class Text_Highlighter_DIFF extends Text_Highlighter -{ + * @category Text + * @package Text_Highlighter + * @copyright 2004-2006 Andrey Demenev + * @license http://www.php.net/license/3_0.txt PHP License + * @version Release: 0.7.1 + * @link http://pear.php.net/package/Text_Highlighter + */ +class Text_Highlighter_DIFF extends Text_Highlighter +{ var $_language = 'diff'; - /** - * Constructor - * - * @param array $options - * @access public - */ - function __construct($options=array()) - { - + /** + * Constructor + * + * @param array $options + * @access public + */ + function __construct($options=array()) + { + $this->_options = $options; $this->_regs = array ( -1 => '/((?m)^\\\\\\sNo\\snewline.+$)|((?m)^\\-\\-\\-$)|((?m)^(diff\\s+\\-|Only\\s+|Index).*$)|((?m)^(\\-\\-\\-|\\+\\+\\+)\\s.+$)|((?m)^\\*.*$)|((?m)^\\+.*$)|((?m)^!.*$)|((?m)^\\<\\s.*$)|((?m)^\\>\\s.*$)|((?m)^\\d+(\\,\\d+)?[acd]\\d+(,\\d+)?$)|((?m)^\\-.*$)|((?m)^\\+.*$)|((?m)^@@.+@@$)|((?m)^d\\d+\\s\\d+$)|((?m)^a\\d+\\s\\d+$)|((?m)^(\\d+)(,\\d+)?(a)$)|((?m)^(\\d+)(,\\d+)?(c)$)|((?m)^(\\d+)(,\\d+)?(d)$)|((?m)^a(\\d+)(\\s\\d+)?$)|((?m)^c(\\d+)(\\s\\d+)?$)|((?m)^d(\\d+)(\\s\\d+)?$)/', @@ -359,8 +359,8 @@ class Text_Highlighter_DIFF extends Text_Highlighter ); $this->_kwmap = array ( ); - $this->_defClass = 'default'; - $this->_checkDefines(); - } - + $this->_defClass = 'default'; + $this->_checkDefines(); + } + } \ No newline at end of file diff --git a/framework/vendors/TextHighlighter/Text/Highlighter/DTD.php b/framework/vendors/TextHighlighter/Text/Highlighter/DTD.php index 51c7617b3..22eaa9c36 100644 --- a/framework/vendors/TextHighlighter/Text/Highlighter/DTD.php +++ b/framework/vendors/TextHighlighter/Text/Highlighter/DTD.php @@ -1,49 +1,49 @@ - - * - */ - -/** - * Auto-generated class. DTD syntax highlighting - * + * + */ + +/** + * Auto-generated class. DTD syntax highlighting + * * @author Andrey Demenev - * @category Text - * @package Text_Highlighter - * @copyright 2004-2006 Andrey Demenev - * @license http://www.php.net/license/3_0.txt PHP License - * @version Release: 0.7.1 - * @link http://pear.php.net/package/Text_Highlighter - */ -class Text_Highlighter_DTD extends Text_Highlighter -{ + * @category Text + * @package Text_Highlighter + * @copyright 2004-2006 Andrey Demenev + * @license http://www.php.net/license/3_0.txt PHP License + * @version Release: 0.7.1 + * @link http://pear.php.net/package/Text_Highlighter + */ +class Text_Highlighter_DTD extends Text_Highlighter +{ var $_language = 'dtd'; - /** - * Constructor - * - * @param array $options - * @access public - */ - function __construct($options=array()) - { - + /** + * Constructor + * + * @param array $options + * @access public + */ + function __construct($options=array()) + { + $this->_options = $options; $this->_regs = array ( -1 => '/(\\ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/vendors/TextHighlighter/Text/cpp.xml b/framework/vendors/TextHighlighter/Text/cpp.xml index 81f0328e4..e8b399ffa 100644 --- a/framework/vendors/TextHighlighter/Text/cpp.xml +++ b/framework/vendors/TextHighlighter/Text/cpp.xml @@ -1,201 +1,201 @@ - - - - - - - - - - - -Thanks to Aaron Kalin for initial -implementation of this highlighter - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + +Thanks to Aaron Kalin for initial +implementation of this highlighter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/vendors/TextHighlighter/Text/css.xml b/framework/vendors/TextHighlighter/Text/css.xml index d2507c662..61623ea9e 100644 --- a/framework/vendors/TextHighlighter/Text/css.xml +++ b/framework/vendors/TextHighlighter/Text/css.xml @@ -1,368 +1,368 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/vendors/TextHighlighter/Text/diff.xml b/framework/vendors/TextHighlighter/Text/diff.xml index d369a2727..a7a705363 100644 --- a/framework/vendors/TextHighlighter/Text/diff.xml +++ b/framework/vendors/TextHighlighter/Text/diff.xml @@ -1,45 +1,45 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/vendors/TextHighlighter/Text/dtd.xml b/framework/vendors/TextHighlighter/Text/dtd.xml index 8e7953136..9e02a4751 100644 --- a/framework/vendors/TextHighlighter/Text/dtd.xml +++ b/framework/vendors/TextHighlighter/Text/dtd.xml @@ -1,66 +1,66 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/vendors/TextHighlighter/Text/generate b/framework/vendors/TextHighlighter/Text/generate index 41048d91a..fa0dcbb6b 100644 --- a/framework/vendors/TextHighlighter/Text/generate +++ b/framework/vendors/TextHighlighter/Text/generate @@ -1,171 +1,171 @@ -#!@php_bin@ - - * @copyright 2004 Andrey Demenev - * @license http://www.php.net/license/3_0.txt PHP License - * @version CVS: $Id: generate,v 1.1 2007/06/03 02:35:28 ssttoo Exp $ - * @link http://pear.php.net/package/Text_Highlighter - */ - -require_once 'Text/Highlighter/Generator.php'; -require_once 'Console/Getopt.php'; - -$options = Console_Getopt::getopt($argv, 'x:p:d:h', array('xml=', 'php=','dir=', 'help')); - -if (PEAR::isError($options)) { - $message = str_replace('Console_Getopt: ','',$options->message); - usage($message); -} - -$source = array(); -$dest = array(); -$dir = ''; - -$expectp = false; -$expectx = false; -$unexpectedx = false; -$unexpectedp = false; -$si = $di = 0; - -foreach ($options[0] as $option) { - switch ($option[0]) { - case 'x': - case '--xml': - $source[$si] = $option[1]; - if ($si) { - $di++; - } - $si++; - if ($expectp) { - $unexpectedx = true; - } - $expectp = true; - $expectx = false; - break; - - case 'p': - case '--php': - if ($expectx) { - $unexpectedp = true; - } - $dest[$di] = $option[1]; - $expectp = false; - $expectx = true; - break; - - case 'd': - case '--dir': - $dir = $option[1]; - break; - - case 'h': - case '--help': - usage(); - break; - } -} - - -if ($unexpectedx && !$dir) { - usage('Unexpected -x or --xml', STDERR); -} - -if ($unexpectedp) { - usage('Unexpected -p or --php', STDERR); -} - -$nsource = count($source); -$ndest = count($dest); - -if (!$nsource && !$ndest) { - $source[]='php://stdin'; - if (!$dir) { - $dest[]='php://stdout'; - } else { - $dest[] = null; - } -} elseif ($expectp && !$dir && $nsource > 1) { - usage('-x or --xml without following -p or --php', STDERR); -} elseif ($nsource == 1 && !$ndest && !$dir) { - $dest[]='php://stdout'; -} - -if ($dir && substr($dir,-1)!='/' && substr($dir,-1)!=='\\' ) { - $dir .= DIRECTORY_SEPARATOR; -} - - -foreach ($source as $i => $xmlfile) -{ - $gen =& new Text_Highlighter_Generator; - $gen->setInputFile($xmlfile); - if ($gen->hasErrors()) { - break; - } - $gen->generate(); - if ($gen->hasErrors()) { - break; - } - if (isset($dest[$i])) { - $phpfile = $dest[$i]; - } else { - $phpfile = $dir . $gen->language . '.php'; - } - $gen->saveCode($phpfile); - if ($gen->hasErrors()) { - break; - } -} -if ($gen->hasErrors()) { - $errors = $gen->getErrors(); - foreach ($errors as $error) { - fwrite (STDERR, $error . "\n"); - } - exit(1); -} - -function usage($message='', $file=STDOUT) -{ - $code = 0; - if ($message) { - $message .= "\n\n"; - $code = 1; - } - $message .= << tag) - -h, --help - This help -MSG; - fwrite ($file, $message); - exit($code); -} -?> - +#!@php_bin@ + + * @copyright 2004 Andrey Demenev + * @license http://www.php.net/license/3_0.txt PHP License + * @version CVS: $Id: generate,v 1.1 2007/06/03 02:35:28 ssttoo Exp $ + * @link http://pear.php.net/package/Text_Highlighter + */ + +require_once 'Text/Highlighter/Generator.php'; +require_once 'Console/Getopt.php'; + +$options = Console_Getopt::getopt($argv, 'x:p:d:h', array('xml=', 'php=','dir=', 'help')); + +if (PEAR::isError($options)) { + $message = str_replace('Console_Getopt: ','',$options->message); + usage($message); +} + +$source = array(); +$dest = array(); +$dir = ''; + +$expectp = false; +$expectx = false; +$unexpectedx = false; +$unexpectedp = false; +$si = $di = 0; + +foreach ($options[0] as $option) { + switch ($option[0]) { + case 'x': + case '--xml': + $source[$si] = $option[1]; + if ($si) { + $di++; + } + $si++; + if ($expectp) { + $unexpectedx = true; + } + $expectp = true; + $expectx = false; + break; + + case 'p': + case '--php': + if ($expectx) { + $unexpectedp = true; + } + $dest[$di] = $option[1]; + $expectp = false; + $expectx = true; + break; + + case 'd': + case '--dir': + $dir = $option[1]; + break; + + case 'h': + case '--help': + usage(); + break; + } +} + + +if ($unexpectedx && !$dir) { + usage('Unexpected -x or --xml', STDERR); +} + +if ($unexpectedp) { + usage('Unexpected -p or --php', STDERR); +} + +$nsource = count($source); +$ndest = count($dest); + +if (!$nsource && !$ndest) { + $source[]='php://stdin'; + if (!$dir) { + $dest[]='php://stdout'; + } else { + $dest[] = null; + } +} elseif ($expectp && !$dir && $nsource > 1) { + usage('-x or --xml without following -p or --php', STDERR); +} elseif ($nsource == 1 && !$ndest && !$dir) { + $dest[]='php://stdout'; +} + +if ($dir && substr($dir,-1)!='/' && substr($dir,-1)!=='\\' ) { + $dir .= DIRECTORY_SEPARATOR; +} + + +foreach ($source as $i => $xmlfile) +{ + $gen =& new Text_Highlighter_Generator; + $gen->setInputFile($xmlfile); + if ($gen->hasErrors()) { + break; + } + $gen->generate(); + if ($gen->hasErrors()) { + break; + } + if (isset($dest[$i])) { + $phpfile = $dest[$i]; + } else { + $phpfile = $dir . $gen->language . '.php'; + } + $gen->saveCode($phpfile); + if ($gen->hasErrors()) { + break; + } +} +if ($gen->hasErrors()) { + $errors = $gen->getErrors(); + foreach ($errors as $error) { + fwrite (STDERR, $error . "\n"); + } + exit(1); +} + +function usage($message='', $file=STDOUT) +{ + $code = 0; + if ($message) { + $message .= "\n\n"; + $code = 1; + } + $message .= << tag) + -h, --help + This help +MSG; + fwrite ($file, $message); + exit($code); +} +?> + diff --git a/framework/vendors/TextHighlighter/Text/generate.bat b/framework/vendors/TextHighlighter/Text/generate.bat index 3170190ed..7ac00dd18 100644 --- a/framework/vendors/TextHighlighter/Text/generate.bat +++ b/framework/vendors/TextHighlighter/Text/generate.bat @@ -1,188 +1,188 @@ -@echo off -rem vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: - -rem Console highlighter class generator - -rem PHP versions 4 and 5 - -rem LICENSE: This source file is subject to version 3.0 of the PHP license -rem that is available through the world-wide-web at the following URI: -rem http://www.php.net/license/3_0.txt. If you did not receive a copy of -rem the PHP License and are unable to obtain it through the web, please -rem send a note to license@php.net so we can mail you a copy immediately. - -rem @category Text -rem @package Text_Highlighter -rem @author Andrey Demenev -rem @copyright 2004 Andrey Demenev -rem @license http://www.php.net/license/3_0.txt PHP License -rem @version CVS: $Id: generate.bat,v 1.1 2007/06/03 02:35:28 ssttoo Exp $ -rem @link http://pear.php.net/package/Text_Highlighter - -set "MHL_PARAMS=" -:doshift -set "MHL_PARAMS=%MHL_PARAMS% %1" -shift -if -%1- == -- GOTO noshift -GOTO doshift -:noshift -@php_bin@ -q -d output_buffering=1 -d include_path="@php_dir@" @bin_dir@/Text/Highlighter/generate.bat %MHL_PARAMS% - -GOTO finish -message); - usage($message); -} - -$source = array(); -$dest = array(); -$dir = ''; - -$expectp = false; -$expectx = false; -$unexpectedx = false; -$unexpectedp = false; -$si = $di = 0; - -foreach ($options[0] as $option) { - switch ($option[0]) { - case 'x': - case '--xml': - $source[$si] = $option[1]; - if ($si) { - $di++; - } - $si++; - if ($expectp) { - $unexpectedx = true; - } - $expectp = true; - $expectx = false; - break; - - case 'p': - case '--php': - if ($expectx) { - $unexpectedp = true; - } - $dest[$di] = $option[1]; - $expectp = false; - $expectx = true; - break; - - case 'd': - case '--dir': - $dir = $option[1]; - break; - - case 'h': - case '--help': - usage(); - break; - } -} - - -if ($unexpectedx && !$dir) { - usage('Unexpected -x or --xml', STDERR); -} - -if ($unexpectedp) { - usage('Unexpected -p or --php', STDERR); -} - -$nsource = count($source); -$ndest = count($dest); - -if (!$nsource && !$ndest) { - $source[]='php://stdin'; - if (!$dir) { - $dest[]='php://stdout'; - } else { - $dest[] = null; - } -} elseif ($expectp && !$dir && $nsource > 1) { - usage('-x or --xml without following -p or --php', STDERR); -} elseif ($nsource == 1 && !$ndest && !$dir) { - $dest[]='php://stdout'; -} - -if ($dir && substr($dir,-1)!='/' && substr($dir,-1)!=='\\' ) { - $dir .= DIRECTORY_SEPARATOR; -} - - -foreach ($source as $i => $xmlfile) -{ - $gen =& new Text_Highlighter_Generator; - $gen->setInputFile($xmlfile); - if ($gen->hasErrors()) { - break; - } - $gen->generate(); - if ($gen->hasErrors()) { - break; - } - if (isset($dest[$i])) { - $phpfile = $dest[$i]; - } else { - $phpfile = $dir . $gen->language . '.php'; - } - $gen->saveCode($phpfile); - if ($gen->hasErrors()) { - break; - } -} -if ($gen->hasErrors()) { - $errors = $gen->getErrors(); - foreach ($errors as $error) { - fwrite (STDERR, $error . "\n"); - } - exit(1); -} - -exit(0); - -function usage($message='', $file=STDOUT) -{ - $code = 0; - if ($message) { - $message .= "\n\n"; - $code = 1; - } - $message .= << tag) - -h, --help - This help -MSG; - fwrite ($file, $message); - exit($code); -} -?> -:finish +@echo off +rem vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: + +rem Console highlighter class generator + +rem PHP versions 4 and 5 + +rem LICENSE: This source file is subject to version 3.0 of the PHP license +rem that is available through the world-wide-web at the following URI: +rem http://www.php.net/license/3_0.txt. If you did not receive a copy of +rem the PHP License and are unable to obtain it through the web, please +rem send a note to license@php.net so we can mail you a copy immediately. + +rem @category Text +rem @package Text_Highlighter +rem @author Andrey Demenev +rem @copyright 2004 Andrey Demenev +rem @license http://www.php.net/license/3_0.txt PHP License +rem @version CVS: $Id: generate.bat,v 1.1 2007/06/03 02:35:28 ssttoo Exp $ +rem @link http://pear.php.net/package/Text_Highlighter + +set "MHL_PARAMS=" +:doshift +set "MHL_PARAMS=%MHL_PARAMS% %1" +shift +if -%1- == -- GOTO noshift +GOTO doshift +:noshift +@php_bin@ -q -d output_buffering=1 -d include_path="@php_dir@" @bin_dir@/Text/Highlighter/generate.bat %MHL_PARAMS% + +GOTO finish +message); + usage($message); +} + +$source = array(); +$dest = array(); +$dir = ''; + +$expectp = false; +$expectx = false; +$unexpectedx = false; +$unexpectedp = false; +$si = $di = 0; + +foreach ($options[0] as $option) { + switch ($option[0]) { + case 'x': + case '--xml': + $source[$si] = $option[1]; + if ($si) { + $di++; + } + $si++; + if ($expectp) { + $unexpectedx = true; + } + $expectp = true; + $expectx = false; + break; + + case 'p': + case '--php': + if ($expectx) { + $unexpectedp = true; + } + $dest[$di] = $option[1]; + $expectp = false; + $expectx = true; + break; + + case 'd': + case '--dir': + $dir = $option[1]; + break; + + case 'h': + case '--help': + usage(); + break; + } +} + + +if ($unexpectedx && !$dir) { + usage('Unexpected -x or --xml', STDERR); +} + +if ($unexpectedp) { + usage('Unexpected -p or --php', STDERR); +} + +$nsource = count($source); +$ndest = count($dest); + +if (!$nsource && !$ndest) { + $source[]='php://stdin'; + if (!$dir) { + $dest[]='php://stdout'; + } else { + $dest[] = null; + } +} elseif ($expectp && !$dir && $nsource > 1) { + usage('-x or --xml without following -p or --php', STDERR); +} elseif ($nsource == 1 && !$ndest && !$dir) { + $dest[]='php://stdout'; +} + +if ($dir && substr($dir,-1)!='/' && substr($dir,-1)!=='\\' ) { + $dir .= DIRECTORY_SEPARATOR; +} + + +foreach ($source as $i => $xmlfile) +{ + $gen =& new Text_Highlighter_Generator; + $gen->setInputFile($xmlfile); + if ($gen->hasErrors()) { + break; + } + $gen->generate(); + if ($gen->hasErrors()) { + break; + } + if (isset($dest[$i])) { + $phpfile = $dest[$i]; + } else { + $phpfile = $dir . $gen->language . '.php'; + } + $gen->saveCode($phpfile); + if ($gen->hasErrors()) { + break; + } +} +if ($gen->hasErrors()) { + $errors = $gen->getErrors(); + foreach ($errors as $error) { + fwrite (STDERR, $error . "\n"); + } + exit(1); +} + +exit(0); + +function usage($message='', $file=STDOUT) +{ + $code = 0; + if ($message) { + $message .= "\n\n"; + $code = 1; + } + $message .= << tag) + -h, --help + This help +MSG; + fwrite ($file, $message); + exit($code); +} +?> +:finish diff --git a/framework/vendors/TextHighlighter/Text/html.xml b/framework/vendors/TextHighlighter/Text/html.xml index c25a3ec58..5c7443a53 100644 --- a/framework/vendors/TextHighlighter/Text/html.xml +++ b/framework/vendors/TextHighlighter/Text/html.xml @@ -1,33 +1,33 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/vendors/TextHighlighter/Text/java.xml b/framework/vendors/TextHighlighter/Text/java.xml index cafbec055..fe95bb3d2 100644 --- a/framework/vendors/TextHighlighter/Text/java.xml +++ b/framework/vendors/TextHighlighter/Text/java.xml @@ -1,2824 +1,2824 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/vendors/TextHighlighter/Text/javascript.xml b/framework/vendors/TextHighlighter/Text/javascript.xml index b7c530c35..aa44689c1 100644 --- a/framework/vendors/TextHighlighter/Text/javascript.xml +++ b/framework/vendors/TextHighlighter/Text/javascript.xml @@ -1,174 +1,174 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/vendors/TextHighlighter/Text/mysql.xml b/framework/vendors/TextHighlighter/Text/mysql.xml index f0f1ef514..973355c50 100644 --- a/framework/vendors/TextHighlighter/Text/mysql.xml +++ b/framework/vendors/TextHighlighter/Text/mysql.xml @@ -1,424 +1,424 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/vendors/TextHighlighter/Text/package.xml b/framework/vendors/TextHighlighter/Text/package.xml index 4cac5d6c8..09ede5bfe 100644 --- a/framework/vendors/TextHighlighter/Text/package.xml +++ b/framework/vendors/TextHighlighter/Text/package.xml @@ -1,243 +1,243 @@ - - - Text_Highlighter - pear.php.net - Syntax highlighting - Text_Highlighter is a package for syntax highlighting. - -It provides a base class provining all the functionality, -and a descendent classes geneator class. - -The main idea is to simplify creation of subclasses -implementing syntax highlighting for particular language. -Subclasses do not implement any new functioanality, -they just provide syntax highlighting rules. -The rules sources are in XML format. - -To create a highlighter for a language, there is no need -to code a new class manually. Simply describe the rules -in XML file and use Text_Highlighter_Generator to create -a new class. - - Stoyan Stefanov - stoyan - ssttoo@gmail.com - yes - - - Andrey Demenev - blindman - demenev@gmail.com - yes - - 2007-06-06 - - - 0.7.0 - 0.7.0 - - - beta - beta - - PHP License - - added new renderer - Array -- HTML renderer modified to extend Array -- more new renderers - BB, HTMLTags, JSON, XML, all extending Array -(feature requests #8704 and #9188 ) -- new syntax definition - ABAP programming language (feature request #8809) -- linked elements to their online documentation option (request #7480) -- option to output code as unordered list (request #4640) -- option to set starting number when outputting code in ordered lists -(request #7077) -- option to set CSS class names mapping insead of using hardcoded -class names (request #7077) -- option to set a CSS style mappping instead of class names (request #7077) -- fixed /= issue when highlighting javascript (bug #11160) -- added sample CSS to the package (bug #11211) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 4.3.3 - - - 1.4.0b1 - - - PEAR - pear.php.net - 1.0 - - - XML_Parser - pear.php.net - 1.0.1 - - - Console_Getopt - pear.php.net - 1.0 - - - - - - - (*ix|*ux) - - - - - - - - - - windows - - - - - - - - - - - - - - - - 0.7.0 - 0.7.0 - - - beta - beta - - 2007-06-06 - PHP License - - added new renderer - Array -- HTML renderer modified to extend Array -- more new renderers - BB, HTMLTags, JSON, XML, all extending Array -(feature requests #8704 and #9188 ) -- new syntax definition - ABAP programming language (feature request #8809) -- linked elements to their online documentation option (request #7480) -- option to output code as unordered list (request #4640) -- option to set starting number when outputting code in ordered lists -(request #7077) -- option to set CSS class names mapping insead of using hardcoded -class names (request #7077) -- option to set a CSS style mappping instead of class names (request #7077) -- fixed /= issue when highlighting javascript (bug #11160) -- added sample CSS to the package (bug #11211) - - - + + + Text_Highlighter + pear.php.net + Syntax highlighting + Text_Highlighter is a package for syntax highlighting. + +It provides a base class provining all the functionality, +and a descendent classes geneator class. + +The main idea is to simplify creation of subclasses +implementing syntax highlighting for particular language. +Subclasses do not implement any new functioanality, +they just provide syntax highlighting rules. +The rules sources are in XML format. + +To create a highlighter for a language, there is no need +to code a new class manually. Simply describe the rules +in XML file and use Text_Highlighter_Generator to create +a new class. + + Stoyan Stefanov + stoyan + ssttoo@gmail.com + yes + + + Andrey Demenev + blindman + demenev@gmail.com + yes + + 2007-06-06 + + + 0.7.0 + 0.7.0 + + + beta + beta + + PHP License + - added new renderer - Array +- HTML renderer modified to extend Array +- more new renderers - BB, HTMLTags, JSON, XML, all extending Array +(feature requests #8704 and #9188 ) +- new syntax definition - ABAP programming language (feature request #8809) +- linked elements to their online documentation option (request #7480) +- option to output code as unordered list (request #4640) +- option to set starting number when outputting code in ordered lists +(request #7077) +- option to set CSS class names mapping insead of using hardcoded +class names (request #7077) +- option to set a CSS style mappping instead of class names (request #7077) +- fixed /= issue when highlighting javascript (bug #11160) +- added sample CSS to the package (bug #11211) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.3.3 + + + 1.4.0b1 + + + PEAR + pear.php.net + 1.0 + + + XML_Parser + pear.php.net + 1.0.1 + + + Console_Getopt + pear.php.net + 1.0 + + + + + + + (*ix|*ux) + + + + + + + + + + windows + + + + + + + + + + + + + + + + 0.7.0 + 0.7.0 + + + beta + beta + + 2007-06-06 + PHP License + - added new renderer - Array +- HTML renderer modified to extend Array +- more new renderers - BB, HTMLTags, JSON, XML, all extending Array +(feature requests #8704 and #9188 ) +- new syntax definition - ABAP programming language (feature request #8809) +- linked elements to their online documentation option (request #7480) +- option to output code as unordered list (request #4640) +- option to set starting number when outputting code in ordered lists +(request #7077) +- option to set CSS class names mapping insead of using hardcoded +class names (request #7077) +- option to set a CSS style mappping instead of class names (request #7077) +- fixed /= issue when highlighting javascript (bug #11160) +- added sample CSS to the package (bug #11211) + + + diff --git a/framework/vendors/TextHighlighter/Text/perl.xml b/framework/vendors/TextHighlighter/Text/perl.xml index f43176534..8dd993129 100644 --- a/framework/vendors/TextHighlighter/Text/perl.xml +++ b/framework/vendors/TextHighlighter/Text/perl.xml @@ -1,439 +1,439 @@ - - - - - - - - - - - This highlighter is EXPERIMENTAL, so that it may work incorrectly. -Most rules were created by Mariusz Jakubowski, and extended by me. -My knowledge of Perl is poor, and Perl syntax seems too -complicated to me. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + This highlighter is EXPERIMENTAL, so that it may work incorrectly. +Most rules were created by Mariusz Jakubowski, and extended by me. +My knowledge of Perl is poor, and Perl syntax seems too +complicated to me. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/vendors/TextHighlighter/Text/php.xml b/framework/vendors/TextHighlighter/Text/php.xml index de00096ad..0cdcf5fa8 100644 --- a/framework/vendors/TextHighlighter/Text/php.xml +++ b/framework/vendors/TextHighlighter/Text/php.xml @@ -1,194 +1,194 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/vendors/TextHighlighter/Text/python.xml b/framework/vendors/TextHighlighter/Text/python.xml index 0baef89cb..7e90b1dfc 100644 --- a/framework/vendors/TextHighlighter/Text/python.xml +++ b/framework/vendors/TextHighlighter/Text/python.xml @@ -1,229 +1,229 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/vendors/TextHighlighter/Text/ruby.xml b/framework/vendors/TextHighlighter/Text/ruby.xml index 191f893a4..40690cda5 100644 --- a/framework/vendors/TextHighlighter/Text/ruby.xml +++ b/framework/vendors/TextHighlighter/Text/ruby.xml @@ -1,141 +1,141 @@ - - - - - - - - - - -FIXME: While this construction : s.split /z/i -is valid, regular expression is not recognized as such -(/ folowing an identifier or number is not recognized as -start of RE), making highlighting improper - -%q(a (nested) string) does not get highlighted correctly - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + +FIXME: While this construction : s.split /z/i +is valid, regular expression is not recognized as such +(/ folowing an identifier or number is not recognized as +start of RE), making highlighting improper + +%q(a (nested) string) does not get highlighted correctly + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/vendors/TextHighlighter/Text/sample.css b/framework/vendors/TextHighlighter/Text/sample.css index 8b5c9d245..6016ca5a7 100644 --- a/framework/vendors/TextHighlighter/Text/sample.css +++ b/framework/vendors/TextHighlighter/Text/sample.css @@ -1,57 +1,57 @@ -.hl-default { - color: Black; -} -.hl-code { - color: Gray; -} -.hl-brackets { - color: Olive; -} -.hl-comment { - color: Orange; -} -.hl-quotes { - color: Darkred; -} -.hl-string { - color: Red; -} -.hl-identifier { - color: Blue; -} -.hl-builtin { - color: Teal; -} -.hl-reserved { - color: Green; -} -.hl-inlinedoc { - color: Blue; -} -.hl-var { - color: Darkblue; -} -.hl-url { - color: Blue; -} -.hl-special { - color: Navy; -} -.hl-number { - color: Maroon; -} -.hl-inlinetags { - color: Blue; -} -.hl-main { - background-color: White; -} -.hl-gutter { - background-color: #999999; - color: White -} -.hl-table { - font-family: courier; - font-size: 12px; - border: solid 1px Lightgrey; -} +.hl-default { + color: Black; +} +.hl-code { + color: Gray; +} +.hl-brackets { + color: Olive; +} +.hl-comment { + color: Orange; +} +.hl-quotes { + color: Darkred; +} +.hl-string { + color: Red; +} +.hl-identifier { + color: Blue; +} +.hl-builtin { + color: Teal; +} +.hl-reserved { + color: Green; +} +.hl-inlinedoc { + color: Blue; +} +.hl-var { + color: Darkblue; +} +.hl-url { + color: Blue; +} +.hl-special { + color: Navy; +} +.hl-number { + color: Maroon; +} +.hl-inlinetags { + color: Blue; +} +.hl-main { + background-color: White; +} +.hl-gutter { + background-color: #999999; + color: White +} +.hl-table { + font-family: courier; + font-size: 12px; + border: solid 1px Lightgrey; +} diff --git a/framework/vendors/TextHighlighter/Text/sql.xml b/framework/vendors/TextHighlighter/Text/sql.xml index dcfcfab61..9a3a87916 100644 --- a/framework/vendors/TextHighlighter/Text/sql.xml +++ b/framework/vendors/TextHighlighter/Text/sql.xml @@ -1,496 +1,496 @@ - - - - - - - - - - - Based on SQL-99 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + Based on SQL-99 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/vendors/TextHighlighter/Text/xml.xml b/framework/vendors/TextHighlighter/Text/xml.xml index a80ffc1a3..33e401223 100644 --- a/framework/vendors/TextHighlighter/Text/xml.xml +++ b/framework/vendors/TextHighlighter/Text/xml.xml @@ -1,37 +1,37 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/vendors/TextHighlighter/highlight.css b/framework/vendors/TextHighlighter/highlight.css index ba1396d7d..cd94745bd 100644 --- a/framework/vendors/TextHighlighter/highlight.css +++ b/framework/vendors/TextHighlighter/highlight.css @@ -1,383 +1,383 @@ -.php-hl-default { - color: Black; -} -.php-hl-code { - color: Gray; -} -.php-hl-brackets { - color: Olive; -} -.php-hl-comment { - color: #808080; - font-style: italic; -} -.php-hl-quotes { - color: red; -} -.php-hl-string { - color: Red; -} -.php-hl-identifier { - color: green; -} -.php-hl-builtin { - color: Teal; -} -.php-hl-reserved { - color: black; - font-weight: bold; -} -.php-hl-inlinedoc { - color: Blue; -} -.php-hl-var { - color: blue; -} -.php-hl-url { - color: Blue; -} -.php-hl-special { - color: Navy; -} -.php-hl-number { - color: Maroon; -} -.php-hl-inlinetags { - color: Blue; -} -.php-hl-main { - font-family: 'Courier New', Courier, monospace; - font-weight: normal; - font-size: 12px; -} -ol.php-hl-main pre { - margin: 0; - padding: 0; -} -.php-hl-gutter { - background-color: #999999; - color: White -} -.php-hl-table { - font-family: courier; - font-size: 12px; - border: solid 1px #505050; -} - -.xml-hl-default { - color: Black; -} -.xml-hl-code { - color: Gray; -} -.xml-hl-brackets { - color: Olive; -} -.xml-hl-comment { - color: #808080; - font-style: italic; -} -.xml-hl-quotes { - color: red; -} -.xml-hl-string { - color: Red; -} -.xml-hl-identifier { - color: Blue; -} -.xml-hl-builtin { - color: Teal; -} -.xml-hl-reserved { - color: Green; -} -.xml-hl-inlinedoc { - color: Blue; -} -.xml-hl-var { - color: #000020; -} -.xml-hl-url { - color: Blue; -} -.xml-hl-special { - color: Navy; -} -.xml-hl-number { - color: Maroon; -} -.xml-hl-inlinetags { - color: Blue; -} -.xml-hl-main { - font-family: 'Courier New', Courier, monospace; - font-weight: normal; -} -.xml-hl-gutter { - background-color: #999999; - color: White -} -.xml-hl-table { - font-family: courier; - font-size: 12px; - border: solid 1px #505050; -} - -.html-hl-default { - color: Black; -} -.html-hl-code { - color: Gray; -} -.html-hl-brackets { - color: Olive; -} -.html-hl-comment { - color: #808080; - font-style: italic; -} -.html-hl-quotes { - color: red; -} -.html-hl-string { - color: Red; -} -.html-hl-identifier { - color: Blue; -} -.html-hl-builtin { - color: Teal; -} -.html-hl-reserved { - color: Green; -} -.html-hl-inlinedoc { - color: Blue; -} -.html-hl-var { - color: #000020; -} -.html-hl-url { - color: Blue; -} -.html-hl-special { - color: Navy; -} -.html-hl-number { - color: Maroon; -} -.html-hl-inlinetags { - color: Blue; -} -.html-hl-main { - font-family: 'Courier New', Courier, monospace; - font-weight: normal; -} -.html-hl-gutter { - background-color: #999999; - color: White -} -.html-hl-table { - font-family: courier; - font-size: 12px; - border: solid 1px #505050; -} - -.css-hl-default { - color: Black; -} -.css-hl-code { - color: Gray; -} -.css-hl-brackets { - color: Olive; -} -.css-hl-comment { - color: #808080; - font-style: italic; -} -.css-hl-quotes { - color: red; -} -.css-hl-string { - color: Red; -} -.css-hl-identifier { - color: Blue; -} -.css-hl-builtin { - color: Teal; -} -.css-hl-reserved { - color: Green; -} -.css-hl-inlinedoc { - color: Blue; -} -.css-hl-var { - color: #000020; -} -.css-hl-url { - color: Blue; -} -.css-hl-special { - color: Navy; -} -.css-hl-number { - color: Maroon; -} -.css-hl-inlinetags { - color: Blue; -} -.css-hl-main { - font-family: 'Courier New', Courier, monospace; - font-weight: normal; -} -.css-hl-gutter { - background-color: #999999; - color: White -} -.css-hl-table { - font-family: courier; - font-size: 12px; - border: solid 1px #505050; -} - -.javascript-hl-default { - color: Black; -} -.javascript-hl-code { - color: Gray; -} -.javascript-hl-brackets { - color: Olive; -} -.javascript-hl-comment { - color: #808080; - font-style: italic; -} -.javascript-hl-quotes { - color: red; -} -.javascript-hl-string { - color: Red; -} -.javascript-hl-identifier { - color: Blue; -} -.javascript-hl-builtin { - color: Teal; -} -.javascript-hl-reserved { - color: Green; -} -.javascript-hl-inlinedoc { - color: Blue; -} -.javascript-hl-var { - color: #000020; -} -.javascript-hl-url { - color: Blue; -} -.javascript-hl-special { - color: Navy; -} -.javascript-hl-number { - color: Maroon; -} -.javascript-hl-inlinetags { - color: Blue; -} -.javascript-hl-main { - font-family: 'Courier New', Courier, monospace; - font-weight: normal; -} -.javascript-hl-gutter { - background-color: #999999; - color: White -} -.javascript-hl-table { - font-family: courier; - font-size: 12px; - border: solid 1px #505050; -} - - -.sql-hl-default { - color: Black; -} -.sql-hl-code { - color: Gray; -} -.sql-hl-brackets { - color: Olive; -} -.sql-hl-comment { - color: #808080; - font-style: italic; -} -.sql-hl-quotes { - color: red; -} -.sql-hl-string { - color: Red; -} -.sql-hl-identifier { - color: Blue; -} -.sql-hl-builtin { - color: Teal; -} -.sql-hl-reserved { - color: Green; -} -.sql-hl-inlinedoc { - color: Blue; -} -.sql-hl-var { - color: #000020; -} -.sql-hl-url { - color: Blue; -} -.sql-hl-special { - color: Navy; -} -.sql-hl-number { - color: Maroon; -} -.sql-hl-inlinetags { - color: Blue; -} -.sql-hl-main { - font-family: 'Courier New', Courier, monospace; - font-weight: normal; -} -.sql-hl-gutter { - background-color: #999999; - color: White -} -.sql-hl-table { - font-family: courier; - font-size: 12px; - border: solid 1px #505050; -} - -.source .copycode -{ - text-align: right; - float: right; -} - -.source .copycode a -{ - cursor: pointer; - color: blue; -} - -.source .copycode_hover a -{ - color: red; +.php-hl-default { + color: Black; +} +.php-hl-code { + color: Gray; +} +.php-hl-brackets { + color: Olive; +} +.php-hl-comment { + color: #808080; + font-style: italic; +} +.php-hl-quotes { + color: red; +} +.php-hl-string { + color: Red; +} +.php-hl-identifier { + color: green; +} +.php-hl-builtin { + color: Teal; +} +.php-hl-reserved { + color: black; + font-weight: bold; +} +.php-hl-inlinedoc { + color: Blue; +} +.php-hl-var { + color: blue; +} +.php-hl-url { + color: Blue; +} +.php-hl-special { + color: Navy; +} +.php-hl-number { + color: Maroon; +} +.php-hl-inlinetags { + color: Blue; +} +.php-hl-main { + font-family: 'Courier New', Courier, monospace; + font-weight: normal; + font-size: 12px; +} +ol.php-hl-main pre { + margin: 0; + padding: 0; +} +.php-hl-gutter { + background-color: #999999; + color: White +} +.php-hl-table { + font-family: courier; + font-size: 12px; + border: solid 1px #505050; +} + +.xml-hl-default { + color: Black; +} +.xml-hl-code { + color: Gray; +} +.xml-hl-brackets { + color: Olive; +} +.xml-hl-comment { + color: #808080; + font-style: italic; +} +.xml-hl-quotes { + color: red; +} +.xml-hl-string { + color: Red; +} +.xml-hl-identifier { + color: Blue; +} +.xml-hl-builtin { + color: Teal; +} +.xml-hl-reserved { + color: Green; +} +.xml-hl-inlinedoc { + color: Blue; +} +.xml-hl-var { + color: #000020; +} +.xml-hl-url { + color: Blue; +} +.xml-hl-special { + color: Navy; +} +.xml-hl-number { + color: Maroon; +} +.xml-hl-inlinetags { + color: Blue; +} +.xml-hl-main { + font-family: 'Courier New', Courier, monospace; + font-weight: normal; +} +.xml-hl-gutter { + background-color: #999999; + color: White +} +.xml-hl-table { + font-family: courier; + font-size: 12px; + border: solid 1px #505050; +} + +.html-hl-default { + color: Black; +} +.html-hl-code { + color: Gray; +} +.html-hl-brackets { + color: Olive; +} +.html-hl-comment { + color: #808080; + font-style: italic; +} +.html-hl-quotes { + color: red; +} +.html-hl-string { + color: Red; +} +.html-hl-identifier { + color: Blue; +} +.html-hl-builtin { + color: Teal; +} +.html-hl-reserved { + color: Green; +} +.html-hl-inlinedoc { + color: Blue; +} +.html-hl-var { + color: #000020; +} +.html-hl-url { + color: Blue; +} +.html-hl-special { + color: Navy; +} +.html-hl-number { + color: Maroon; +} +.html-hl-inlinetags { + color: Blue; +} +.html-hl-main { + font-family: 'Courier New', Courier, monospace; + font-weight: normal; +} +.html-hl-gutter { + background-color: #999999; + color: White +} +.html-hl-table { + font-family: courier; + font-size: 12px; + border: solid 1px #505050; +} + +.css-hl-default { + color: Black; +} +.css-hl-code { + color: Gray; +} +.css-hl-brackets { + color: Olive; +} +.css-hl-comment { + color: #808080; + font-style: italic; +} +.css-hl-quotes { + color: red; +} +.css-hl-string { + color: Red; +} +.css-hl-identifier { + color: Blue; +} +.css-hl-builtin { + color: Teal; +} +.css-hl-reserved { + color: Green; +} +.css-hl-inlinedoc { + color: Blue; +} +.css-hl-var { + color: #000020; +} +.css-hl-url { + color: Blue; +} +.css-hl-special { + color: Navy; +} +.css-hl-number { + color: Maroon; +} +.css-hl-inlinetags { + color: Blue; +} +.css-hl-main { + font-family: 'Courier New', Courier, monospace; + font-weight: normal; +} +.css-hl-gutter { + background-color: #999999; + color: White +} +.css-hl-table { + font-family: courier; + font-size: 12px; + border: solid 1px #505050; +} + +.javascript-hl-default { + color: Black; +} +.javascript-hl-code { + color: Gray; +} +.javascript-hl-brackets { + color: Olive; +} +.javascript-hl-comment { + color: #808080; + font-style: italic; +} +.javascript-hl-quotes { + color: red; +} +.javascript-hl-string { + color: Red; +} +.javascript-hl-identifier { + color: Blue; +} +.javascript-hl-builtin { + color: Teal; +} +.javascript-hl-reserved { + color: Green; +} +.javascript-hl-inlinedoc { + color: Blue; +} +.javascript-hl-var { + color: #000020; +} +.javascript-hl-url { + color: Blue; +} +.javascript-hl-special { + color: Navy; +} +.javascript-hl-number { + color: Maroon; +} +.javascript-hl-inlinetags { + color: Blue; +} +.javascript-hl-main { + font-family: 'Courier New', Courier, monospace; + font-weight: normal; +} +.javascript-hl-gutter { + background-color: #999999; + color: White +} +.javascript-hl-table { + font-family: courier; + font-size: 12px; + border: solid 1px #505050; +} + + +.sql-hl-default { + color: Black; +} +.sql-hl-code { + color: Gray; +} +.sql-hl-brackets { + color: Olive; +} +.sql-hl-comment { + color: #808080; + font-style: italic; +} +.sql-hl-quotes { + color: red; +} +.sql-hl-string { + color: Red; +} +.sql-hl-identifier { + color: Blue; +} +.sql-hl-builtin { + color: Teal; +} +.sql-hl-reserved { + color: Green; +} +.sql-hl-inlinedoc { + color: Blue; +} +.sql-hl-var { + color: #000020; +} +.sql-hl-url { + color: Blue; +} +.sql-hl-special { + color: Navy; +} +.sql-hl-number { + color: Maroon; +} +.sql-hl-inlinetags { + color: Blue; +} +.sql-hl-main { + font-family: 'Courier New', Courier, monospace; + font-weight: normal; +} +.sql-hl-gutter { + background-color: #999999; + color: White +} +.sql-hl-table { + font-family: courier; + font-size: 12px; + border: solid 1px #505050; +} + +.source .copycode +{ + text-align: right; + float: right; +} + +.source .copycode a +{ + cursor: pointer; + color: blue; +} + +.source .copycode_hover a +{ + color: red; } \ No newline at end of file diff --git a/framework/vendors/adodb/LICENSE.txt b/framework/vendors/adodb/LICENSE.txt index 7d3c8a028..d90f4e5ff 100644 --- a/framework/vendors/adodb/LICENSE.txt +++ b/framework/vendors/adodb/LICENSE.txt @@ -1,182 +1,182 @@ -ADOdb is dual licensed using BSD and LGPL. - -In plain English, you do not need to distribute your application in source code form, nor do you need to distribute ADOdb source code, provided you follow the rest of terms of the BSD license. - -For more info about ADOdb, visit http://adodb.sourceforge.net/ - -BSD Style-License -================= - -Copyright (c) 2000, 2001, 2002, 2003, 2004 John Lim -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this list -of conditions and the following disclaimer in the documentation and/or other materials -provided with the distribution. - -Neither the name of the John Lim nor the names of its contributors may be used to -endorse or promote products derived from this software without specific prior written -permission. - -DISCLAIMER: -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -JOHN LIM OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -========================================================== -GNU LESSER GENERAL PUBLIC LICENSE -Version 2.1, February 1999 - -Copyright (C) 1991, 1999 Free Software Foundation, Inc. -59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -Everyone is permitted to copy and distribute verbatim copies -of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - -Preamble -The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. - -This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. - -When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. - -To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. - -For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. - -We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. - -To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. - -Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. - -Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. - -When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. - -We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. - -For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. - -In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. - -Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. - -The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. - - -TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION -0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". - -A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. - -The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) - -"Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. - -Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. - -1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. - -You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. - -2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: - - -a) The modified work must itself be a software library. -b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. -c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. -d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. -(For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. - -3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. - -Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. - -This option is useful when you wish to copy part of the code of the Library into a program that is not a library. - -4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. - -If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. - -5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. - -However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. - -When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. - -If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) - -Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. - -6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. - -You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: - - -a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) -b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. -c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. -d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. -e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. -For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. - -It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. - -7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: - - -a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. -b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. -8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. - -9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. - -10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. - -11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. - -This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. - -12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. - -13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. - -14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. - -NO WARRANTY - -15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - - +ADOdb is dual licensed using BSD and LGPL. + +In plain English, you do not need to distribute your application in source code form, nor do you need to distribute ADOdb source code, provided you follow the rest of terms of the BSD license. + +For more info about ADOdb, visit http://adodb.sourceforge.net/ + +BSD Style-License +================= + +Copyright (c) 2000, 2001, 2002, 2003, 2004 John Lim +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this list +of conditions and the following disclaimer in the documentation and/or other materials +provided with the distribution. + +Neither the name of the John Lim nor the names of its contributors may be used to +endorse or promote products derived from this software without specific prior written +permission. + +DISCLAIMER: +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +JOHN LIM OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +========================================================== +GNU LESSER GENERAL PUBLIC LICENSE +Version 2.1, February 1999 + +Copyright (C) 1991, 1999 Free Software Foundation, Inc. +59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + +Preamble +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. + +This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. + +When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. + +To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. + +We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. + +Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. + +Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. + +When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. + +We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. + +For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. + +Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. + +The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. + + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". + +A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + + +a) The modified work must itself be a software library. +b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. +c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. +d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. +(For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. + +However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. + +When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. + +If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. + +6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: + + +a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) +b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. +c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. +d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. +e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. +For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: + + +a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. +b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. +8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/framework/vendors/cldr/LICENSE.txt b/framework/vendors/cldr/LICENSE.txt index 9c9bb917c..fb4acb6af 100644 --- a/framework/vendors/cldr/LICENSE.txt +++ b/framework/vendors/cldr/LICENSE.txt @@ -1,33 +1,33 @@ -Copyright © 1991-2007 Unicode, Inc. All rights reserved. Distributed -under the Terms of Use in http://www.unicode.org/copyright.html. - -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Unicode data files and any associated documentation -(the "Data Files") or Unicode software and any associated documentation -(the "Software") to deal in the Data Files or Software without -restriction, including without limitation the rights to use, copy, -modify, merge, publish, distribute, and/or sell copies of the Data -Files or Software, and to permit persons to whom the Data Files or -Software are furnished to do so, provided that (a) the above copyright -notice(s) and this permission notice appear with all copies of the -Data Files or Software, (b) both the above copyright notice(s) and -this permission notice appear in associated documentation, and (c) -there is clear notice in each modified Data File or in the Software -as well as in the documentation associated with the Data File(s) or -Software that the data or software has been modified. - -THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY -OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE -COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR -ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY -DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, -WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, -ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE -DATA FILES OR SOFTWARE. - -Except as contained in this notice, the name of a copyright holder -shall not be used in advertising or otherwise to promote the sale, -use or other dealings in these Data Files or Software without prior +Copyright © 1991-2007 Unicode, Inc. All rights reserved. Distributed +under the Terms of Use in http://www.unicode.org/copyright.html. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Unicode data files and any associated documentation +(the "Data Files") or Unicode software and any associated documentation +(the "Software") to deal in the Data Files or Software without +restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, and/or sell copies of the Data +Files or Software, and to permit persons to whom the Data Files or +Software are furnished to do so, provided that (a) the above copyright +notice(s) and this permission notice appear with all copies of the +Data Files or Software, (b) both the above copyright notice(s) and +this permission notice appear in associated documentation, and (c) +there is clear notice in each modified Data File or in the Software +as well as in the documentation associated with the Data File(s) or +Software that the data or software has been modified. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY +OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE +COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR +ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY +DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE +DATA FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in these Data Files or Software without prior written authorization of the copyright holder. \ No newline at end of file diff --git a/framework/vendors/htmlpurifier/HTMLPurifier.standalone.php b/framework/vendors/htmlpurifier/HTMLPurifier.standalone.php index 64dbf1069..233fed9ce 100644 --- a/framework/vendors/htmlpurifier/HTMLPurifier.standalone.php +++ b/framework/vendors/htmlpurifier/HTMLPurifier.standalone.php @@ -1,21873 +1,21873 @@ -config = HTMLPurifier_Config::create($config); - $this->strategy = new HTMLPurifier_Strategy_Core(); - } - - /** - * Adds a filter to process the output. First come first serve - * - * @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 - ); - $this->filters[] = $filter; - } - - /** - * Filters an HTML snippet/document to be XSS-free and standards-compliant. - * - * @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 string Purified HTML - */ - public function purify($html, $config = null) - { - // :TODO: make the config merge in, instead of replace - $config = $config ? HTMLPurifier_Config::create($config) : $this->config; - - // implementation is partially environment dependant, partially - // configuration dependant - $lexer = HTMLPurifier_Lexer::create($config); - - $context = new HTMLPurifier_Context(); - - // setup HTML generator - $this->generator = new HTMLPurifier_Generator($config, $context); - $context->register('Generator', $this->generator); - - // set up global context variables - if ($config->get('Core.CollectErrors')) { - // may get moved out if other facilities use it - $language_factory = HTMLPurifier_LanguageFactory::instance(); - $language = $language_factory->create($config, $context); - $context->register('Locale', $language); - - $error_collector = new HTMLPurifier_ErrorCollector($context); - $context->register('ErrorCollector', $error_collector); - } - - // setup id_accumulator context, necessary due to the fact that - // AttrValidator can be called from many places - $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context); - $context->register('IDAccumulator', $id_accumulator); - - $html = HTMLPurifier_Encoder::convertToUTF8($html, $config, $context); - - // setup filters - $filter_flags = $config->getBatch('Filter'); - $custom_filters = $filter_flags['Custom']; - unset($filter_flags['Custom']); - $filters = array(); - foreach ($filter_flags as $filter => $flag) { - if (!$flag) { - continue; - } - if (strpos($filter, '.') !== false) { - continue; - } - $class = "HTMLPurifier_Filter_$filter"; - $filters[] = new $class; - } - foreach ($custom_filters as $filter) { - // maybe "HTMLPurifier_Filter_$filter", but be consistent with AutoFormat - $filters[] = $filter; - } - $filters = array_merge($filters, $this->filters); - // maybe prepare(), but later - - for ($i = 0, $filter_size = count($filters); $i < $filter_size; $i++) { - $html = $filters[$i]->preFilter($html, $config, $context); - } - - // purified HTML - $html = - $this->generator->generateFromTokens( - // list of tokens - $this->strategy->execute( - // list of un-purified tokens - $lexer->tokenizeHTML( - // un-purified HTML - $html, - $config, - $context - ), - $config, - $context - ) - ); - - for ($i = $filter_size - 1; $i >= 0; $i--) { - $html = $filters[$i]->postFilter($html, $config, $context); - } - - $html = HTMLPurifier_Encoder::convertFromUTF8($html, $config, $context); - $this->context =& $context; - return $html; - } - - /** - * Filters an array of HTML snippets - * - * @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 string[] Array of purified HTML - */ - 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); - $context_array[$key] = $this->context; - } - $this->context = $context_array; - return $array_of_html; - } - - /** - * 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 - */ - public static function instance($prototype = null) - { - if (!self::$instance || $prototype) { - if ($prototype instanceof HTMLPurifier) { - self::$instance = $prototype; - } elseif ($prototype) { - self::$instance = new HTMLPurifier($prototype); - } else { - self::$instance = new HTMLPurifier(); - } - } - return self::$instance; - } - - /** - * 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) - { - 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 - */ - -class HTMLPurifier_AttrCollections -{ - - /** - * Associative array of attribute collections, indexed by name. - * @type array - */ - public $info = array(); - - /** - * Performs all expansions on internal data for use by other inclusions - * It also collects all attribute collection extensions from - * modules - * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance - * @param HTMLPurifier_HTMLModule[] $modules Hash array of HTMLPurifier_HTMLModule members - */ - public function __construct($attr_types, $modules) - { - // load extensions from the modules - foreach ($modules as $module) { - foreach ($module->attr_collections as $coll_i => $coll) { - if (!isset($this->info[$coll_i])) { - $this->info[$coll_i] = array(); - } - foreach ($coll as $attr_i => $attr) { - 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 - ); - continue; - } - $this->info[$coll_i][$attr_i] = $attr; - } - } - } - // perform internal expansions and inclusions - foreach ($this->info as $name => $attr) { - // merge attribute collections that include others - $this->performInclusions($this->info[$name]); - // replace string identifiers with actual attribute objects - $this->expandIdentifiers($this->info[$name], $attr_types); - } - } - - /** - * Takes a reference to an attribute associative array and performs - * all inclusions specified by the zero index. - * @param array &$attr Reference to attribute array - */ - 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; - } - $seen[$merge[$i]] = true; - // foreach attribute of the inclusion, copy it over - if (!isset($this->info[$merge[$i]])) { - continue; - } - foreach ($this->info[$merge[$i]] as $key => $value) { - if (isset($attr[$key])) { - continue; - } // also catches more inclusions - $attr[$key] = $value; - } - if (isset($this->info[$merge[$i]][0])) { - // recursion - $merge = array_merge($merge, $this->info[$merge[$i]][0]); - } - } - unset($attr[0]); - } - - /** - * Expands all string identifiers in an attribute array by replacing - * them with the appropriate values inside HTMLPurifier_AttrTypes - * @param array &$attr Reference to attribute array - * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance - */ - 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 (isset($processed[$def_i])) { - continue; - } - - // determine whether or not attribute is required - if ($required = (strpos($def_i, '*') !== false)) { - // rename the definition - unset($attr[$def_i]); - $def_i = trim($def_i, '*'); - $attr[$def_i] = $def; - } - - $processed[$def_i] = true; - - // if we've already got a literal object, move on - if (is_object($def)) { - // preserve previous required - $attr[$def_i]->required = ($required || $attr[$def_i]->required); - continue; - } - - if ($def === false) { - unset($attr[$def_i]); - continue; - } - - if ($t = $attr_types->get($def)) { - $attr[$def_i] = $t; - $attr[$def_i]->required = $required; - } else { - unset($attr[$def_i]); - } - } - } -} - - - - - -/** - * Base class for all validating attribute definitions. - * - * This family of classes forms the core for not only HTML attribute validation, - * but also any sort of string that needs to be validated or cleaned (which - * means CSS properties and composite definitions are defined here too). - * Besides defining (through code) what precisely makes the string valid, - * subclasses are also responsible for cleaning the code if possible. - */ - -abstract class HTMLPurifier_AttrDef -{ - - /** - * 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 - * @type bool - */ - public $required = false; - - /** - * Validates and cleans passed string according to a definition. - * - * @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); - - /** - * Convenience method that parses a string as if it were CDATA. - * - * This method process a string in the manner specified at - * by removing - * leading and trailing whitespace, ignoring line feeds, and replacing - * carriage returns and tabs with spaces. While most useful for HTML - * attributes specified as CDATA, it can also be applied to most CSS - * values. - * - * @note This method is not entirely standards compliant, as trim() removes - * more types of whitespace than specified in the spec. In practice, - * this is rarely a problem, as those extra characters usually have - * already been removed by HTMLPurifier_Encoder. - * - * @warning This processing is inconsistent with XML's whitespace handling - * as specified by section 3.3.3 and referenced XHTML 1.0 section - * 4.7. However, note that we are NOT necessarily - * parsing XML, thus, this behavior may still be correct. We - * assume that newlines have been normalized. - */ - public function parseCDATA($string) - { - $string = trim($string); - $string = str_replace(array("\n", "\t", "\r"), ' ', $string); - return $string; - } - - /** - * Factory method for creating this class from a string. - * @param string $string String construction info - * @return HTMLPurifier_AttrDef Created AttrDef object corresponding to $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 - // to clone or instantiate new copies. (Instantiation is safer.) - return $this; - } - - /** - * 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) - { - 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" - * version of it. - */ - protected function expandCSSEscape($string) - { - // flexibly parse it - $ret = ''; - for ($i = 0, $c = strlen($string); $i < $c; $i++) { - if ($string[$i] === '\\') { - $i++; - if ($i >= $c) { - $ret .= '\\'; - break; - } - if (ctype_xdigit($string[$i])) { - $code = $string[$i]; - for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) { - 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; - } - $ret .= $char; - if ($i < $c && trim($string[$i]) !== '') { - $i--; - } - continue; - } - if ($string[$i] === "\n") { - continue; - } - } - $ret .= $string[$i]; - } - return $ret; - } -} - - - - - -/** - * Processes an entire attribute array for corrections needing multiple values. - * - * Occasionally, a certain attribute will need to be removed and popped onto - * another value. Instead of creating a complex return syntax for - * HTMLPurifier_AttrDef, we just pass the whole attribute array to a - * specialized object and have that do the special work. That is the - * family of HTMLPurifier_AttrTransform. - * - * An attribute transformation can be assigned to run before or after - * HTMLPurifier_AttrDef validation. See HTMLPurifier_HTMLDefinition for - * more details. - */ - -abstract class HTMLPurifier_AttrTransform -{ - - /** - * Abstract: makes changes to the attributes dependent on multiple values. - * - * @param array $attr Assoc array of attributes, usually from - * HTMLPurifier_Token_Tag::$attr - * @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 array &$attr Attribute array to process (passed by reference) - * @param string $css CSS to prepend - */ - public function prependCSS(&$attr, $css) - { - $attr['style'] = isset($attr['style']) ? $attr['style'] : ''; - $attr['style'] = $css . $attr['style']; - } - - /** - * Retrieves and removes an attribute - * @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; - } - $value = $attr[$key]; - unset($attr[$key]); - return $value; - } -} - - - - - -/** - * Provides lookup array of attribute types to HTMLPurifier_AttrDef objects - */ -class HTMLPurifier_AttrTypes -{ - /** - * Lookup array of attribute string identifiers to concrete implementations. - * @type HTMLPurifier_AttrDef[] - */ - protected $info = array(); - - /** - * Constructs the info array, supplying default implementations for attribute - * types. - */ - 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. - // With the old implementation of Enum, that ignored its - // arguments when handling a make dispatch, the IAlign - // definition wouldn't work. - - // pseudo-types, must be instantiated via shorthand - $this->info['Enum'] = new HTMLPurifier_AttrDef_Enum(); - $this->info['Bool'] = new HTMLPurifier_AttrDef_HTML_Bool(); - - $this->info['CDATA'] = new HTMLPurifier_AttrDef_Text(); - $this->info['ID'] = new HTMLPurifier_AttrDef_HTML_ID(); - $this->info['Length'] = new HTMLPurifier_AttrDef_HTML_Length(); - $this->info['MultiLength'] = new HTMLPurifier_AttrDef_HTML_MultiLength(); - $this->info['NMTOKENS'] = new HTMLPurifier_AttrDef_HTML_Nmtokens(); - $this->info['Pixels'] = new HTMLPurifier_AttrDef_HTML_Pixels(); - $this->info['Text'] = new HTMLPurifier_AttrDef_Text(); - $this->info['URI'] = new HTMLPurifier_AttrDef_URI(); - $this->info['LanguageCode'] = new HTMLPurifier_AttrDef_Lang(); - $this->info['Color'] = new HTMLPurifier_AttrDef_HTML_Color(); - $this->info['IAlign'] = self::makeEnum('top,middle,bottom,left,right'); - $this->info['LAlign'] = self::makeEnum('top,bottom,left,right'); - $this->info['FrameTarget'] = new HTMLPurifier_AttrDef_HTML_FrameTarget(); - - // unimplemented aliases - $this->info['ContentType'] = new HTMLPurifier_AttrDef_Text(); - $this->info['ContentTypes'] = new HTMLPurifier_AttrDef_Text(); - $this->info['Charsets'] = new HTMLPurifier_AttrDef_Text(); - $this->info['Character'] = new HTMLPurifier_AttrDef_Text(); - - // "proprietary" types - $this->info['Class'] = new HTMLPurifier_AttrDef_HTML_Class(); - - // number is really a positive integer (one or more digits) - // FIXME: ^^ not always, see start and value of list items - $this->info['Number'] = new HTMLPurifier_AttrDef_Integer(false, false, true); - } - - private static function makeEnum($in) - { - return new HTMLPurifier_AttrDef_Clone(new HTMLPurifier_AttrDef_Enum(explode(',', $in))); - } - - /** - * Retrieves a type - * @param string $type String type name - * @return HTMLPurifier_AttrDef Object AttrDef for 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 (!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 string $type String type name - * @param HTMLPurifier_AttrDef $impl Object AttrDef for type - */ - public function set($type, $impl) - { - $this->info[$type] = $impl; - } -} - - - - - -/** - * Validates the attributes of a token. Doesn't manage required attributes - * very well. The only reason we factored this out was because RemoveForeignElements - * also needed it besides ValidateAttributes. - */ -class HTMLPurifier_AttrValidator -{ - - /** - * Validates the attributes of a token, mutating it as necessary. - * that has valid tokens - * @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) - { - $definition = $config->getHTMLDefinition(); - $e =& $context->get('ErrorCollector', true); - - // initialize IDAccumulator if necessary - $ok =& $context->get('IDAccumulator', true); - if (!$ok) { - $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context); - $context->register('IDAccumulator', $id_accumulator); - } - - // initialize CurrentToken if necessary - $current_token =& $context->get('CurrentToken', true); - if (!$current_token) { - $context->register('CurrentToken', $token); - } - - if (!$token instanceof HTMLPurifier_Token_Start && - !$token instanceof HTMLPurifier_Token_Empty - ) { - return; - } - - // create alias to global definition array, see also $defs - // DEFINITION CALL - $d_defs = $definition->info_global_attr; - - // don't update token until the very end, to ensure an atomic update - $attr = $token->attr; - - // do global transformations (pre) - // nothing currently utilizes this - 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); - } - } - } - - // do local transformations only applicable to this element (pre) - // ex.

to

- 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); - } - } - } - - // create alias to this element's attribute definition array, see - // also $d_defs (global attribute definition array) - // DEFINITION CALL - $defs = $definition->info[$token->name]->attr; - - $attr_key = false; - $context->register('CurrentAttr', $attr_key); - - // iterate through all the attribute keypairs - // Watch out for name collisions: $key has previously been used - foreach ($attr as $attr_key => $value) { - - // call the definition - 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. - // This is usually when there's a global definition - // that must be overridden. - // Theoretically speaking, we could have a - // AttrDef_DenyAll, but this is faster! - $result = false; - } else { - // validate according to the element's definition - $result = $defs[$attr_key]->validate( - $value, - $config, - $context - ); - } - } 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 - ); - } else { - // system never heard of the attribute? DELETE! - $result = false; - } - - // put the results into effect - 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'); - } - - // remove the attribute - unset($attr[$attr_key]); - } elseif (is_string($result)) { - // generally, if a substitution is happening, there - // was some sort of implicit correction going on. We'll - // delegate it to the attribute classes to say exactly what. - - // simple substitution - $attr[$attr_key] = $result; - } else { - // nothing happens - } - - // we'd also want slightly more complicated substitution - // involving an array as the return value, - // although we're not sure how colliding attributes would - // resolve (certain ones would be completely overriden, - // others would prepend themselves). - } - - $context->destroy('CurrentAttr'); - - // post transforms - - // global (error reporting untested) - 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); - } - } - } - - // local (error reporting untested) - 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); - } - } - } - - $token->attr = $attr; - - // destroy CurrentToken if we made it ourselves - if (!$current_token) { - $context->destroy('CurrentToken'); - } - - } - - -} - - - - - -// constants are slow, so we use as few as possible -if (!defined('HTMLPURIFIER_PREFIX')) { - define('HTMLPURIFIER_PREFIX', dirname(__FILE__) . '/standalone'); - set_include_path(HTMLPURIFIER_PREFIX . PATH_SEPARATOR . get_include_path()); -} - -// accomodations for versions earlier than 5.0.2 -// borrowed from PHP_Compat, LGPL licensed, by Aidan Lister -if (!defined('PHP_EOL')) { - switch (strtoupper(substr(PHP_OS, 0, 3))) { - case 'WIN': - define('PHP_EOL', "\r\n"); - break; - case 'DAR': - define('PHP_EOL', "\r"); - break; - default: - define('PHP_EOL', "\n"); - } -} - -/** - * Bootstrap class that contains meta-functionality for HTML Purifier such as - * the autoload function. - * - * @note - * This class may be used without any other files from HTML Purifier. - */ -class HTMLPurifier_Bootstrap -{ - - /** - * Autoload function for HTML Purifier - * @param string $class Class to load - * @return bool - */ - public static function autoload($class) - { - $file = HTMLPurifier_Bootstrap::getPath($class); - 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 - // may be broken. Since we have efficient alternatives, pay - // the cost here and avoid the bug. - require_once HTMLPURIFIER_PREFIX . '/' . $file; - return true; - } - - /** - * 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; - } - // Custom implementations - if (strncmp('HTMLPurifier_Language_', $class, 22) === 0) { - $code = str_replace('_', '-', substr($class, 22)); - $file = 'HTMLPurifier/Language/classes/' . $code . '.php'; - } else { - $file = str_replace('_', '/', $class) . '.php'; - } - if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) { - return false; - } - return $file; - } - - /** - * "Pre-registers" our autoloader on the SPL stack. - */ - public static function registerAutoload() - { - $autoload = array('HTMLPurifier_Bootstrap', 'autoload'); - if (($funcs = spl_autoload_functions()) === false) { - spl_autoload_register($autoload); - } elseif (function_exists('spl_autoload_unregister')) { - if (version_compare(PHP_VERSION, '5.3.0', '>=')) { - // prepend flag exists, no need for shenanigans - spl_autoload_register($autoload, true, true); - } else { - $buggy = version_compare(PHP_VERSION, '5.2.11', '<'); - $compat = version_compare(PHP_VERSION, '5.1.2', '<=') && - version_compare(PHP_VERSION, '5.1.0', '>='); - foreach ($funcs as $func) { - if ($buggy && is_array($func)) { - // :TRICKY: There are some compatibility issues and some - // 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 - 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.' - ); - } - // Suprisingly, spl_autoload_register supports the - // Class::staticMethod callback format, although call_user_func doesn't - if ($compat) { - $func = implode('::', $func); - } - } - spl_autoload_unregister($func); - } - spl_autoload_register($autoload); - foreach ($funcs as $func) { - spl_autoload_register($func); - } - } - } - } -} - - - - - -/** - * Super-class for definition datatype objects, implements serialization - * functions for the class. - */ -abstract class HTMLPurifier_Definition -{ - - /** - * Has setup() been called yet? - * @type bool - */ - public $setup = false; - - /** - * If true, write out the final definition object to the cache after - * setup. This will be true only if all invocations to get a raw - * definition object are also optimized. This does not cause file - * system thrashing because on subsequent calls the cached object - * 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 HTMLPurifier_Config $config - */ - abstract protected function doSetup($config); - - /** - * Setup function that aborts if already setup - * @param HTMLPurifier_Config $config - */ - public function setup($config) - { - if ($this->setup) { - return; - } - $this->setup = true; - $this->doSetup($config); - } -} - - - - - -/** - * Defines allowed CSS attributes and what their values are. - * @see HTMLPurifier_HTMLDefinition - */ -class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition -{ - - public $type = 'CSS'; - - /** - * 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) - { - $this->info['text-align'] = new HTMLPurifier_AttrDef_Enum( - 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-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style); - - $this->info['clear'] = new HTMLPurifier_AttrDef_Enum( - array('none', 'left', 'right', 'both'), - false - ); - $this->info['float'] = new HTMLPurifier_AttrDef_Enum( - array('none', 'left', 'right'), - false - ); - $this->info['font-style'] = new HTMLPurifier_AttrDef_Enum( - array('normal', 'italic', 'oblique'), - false - ); - $this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum( - array('normal', 'small-caps'), - false - ); - - $uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite( - array( - new HTMLPurifier_AttrDef_Enum(array('none')), - new HTMLPurifier_AttrDef_CSS_URI() - ) - ); - - $this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum( - 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 - ); - $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 - ); - $this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color(); - - $this->info['background-image'] = $uri_or_none; - $this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum( - array('repeat', 'repeat-x', 'repeat-y', 'no-repeat') - ); - $this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum( - array('scroll', 'fixed') - ); - $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['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-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) - ) - ); - - $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'] = 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'] = 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() - ) - ); - - $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 - ); - - $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration(); - - $this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily(); - - // 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 - ); - - // MUST be called after other font properties, as it references - // a CSSDefinition object - $this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config); - - // same here - $this->info['border'] = - $this->info['border-bottom'] = - $this->info['border-top'] = - $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['caption-side'] = new HTMLPurifier_AttrDef_Enum( - array('top', 'bottom') - ); - - $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['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') - ); - - if ($config->get('CSS.Proprietary')) { - $this->doSetupProprietary($config); - } - - if ($config->get('CSS.AllowTricky')) { - $this->doSetupTricky($config); - } - - if ($config->get('CSS.Trusted')) { - $this->doSetupTrusted($config); - } - - $allow_important = $config->get('CSS.AllowImportant'); - // wrap all attr-defs with decorator that handles !important - foreach ($this->info as $k => $v) { - $this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important); - } - - $this->setupConfigStuff($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(); - - // 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(); - - // 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')); - - } - - /** - * @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')); - } - - /** - * @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')), - ) - ); - } - - /** - * 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) - { - // setup allowed elements - $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]); - } - unset($allowed_properties[$name]); - } - // emit errors - foreach ($allowed_properties as $name => $d) { - // :TODO: Is this htmlspecialchars() call really necessary? - $name = htmlspecialchars($name); - trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING); - } - } - - $forbidden_properties = $config->get('CSS.ForbiddenProperties'); - if ($forbidden_properties !== null) { - foreach ($this->info as $name => $d) { - if (isset($forbidden_properties[$name])) { - unset($this->info[$name]); - } - } - } - } -} - - - - - -/** - * 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; - - /** - * 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. - * @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) - { - return $this->elements; - } - - /** - * Validates nodes according to definition and returns modification. - * - * @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); -} - - - - - -/** - * Configuration object that triggers customizable behavior. - * - * @warning This class is strongly defined: that means that the class - * will fail if an undefined directive is retrieved or set. - * - * @note Many classes that could (although many times don't) use the - * configuration object make it a mandatory parameter. This is - * because a configuration object should always be forwarded, - * otherwise, you run the risk of missing a parameter and then - * being stumped when a configuration directive doesn't work. - * - * @todo Reconsider some of the public member variables - */ -class HTMLPurifier_Config -{ - - /** - * HTML Purifier's version - * @type string - */ - public $version = '4.6.0'; - - /** - * 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. - * @type string[] - */ - protected $serials = array(); - - /** - * Serial for entire configuration object. - * @type string - */ - protected $serial; - - /** - * Parser for variables. - * @type HTMLPurifier_VarParser_Flexible - */ - protected $parser = null; - - /** - * 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. - * @type HTMLPurifier_Definition[] - */ - protected $definitions; - - /** - * 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. - * @type bool - */ - private $aliasMode; - - /** - * 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; - - /** - * Constructor - * @param HTMLPurifier_ConfigSchema $definition ConfigSchema that defines - * what directives are allowed. - * @param HTMLPurifier_PropertyList $parent - */ - 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 - $this->parser = new HTMLPurifier_VarParser_Flexible(); - } - - /** - * Convenience constructor that creates a config object based on a mixed var - * @param mixed $config Variable that defines the state of the config - * 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 Schema object - * @return HTMLPurifier_Config Configured object - */ - public static function create($config, $schema = null) - { - if ($config instanceof HTMLPurifier_Config) { - // pass-through - return $config; - } - if (!$schema) { - $ret = HTMLPurifier_Config::createDefault(); - } else { - $ret = new HTMLPurifier_Config($schema); - } - 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. - * @return HTMLPurifier_Config object with $config as its parent. - */ - public static function inherit(HTMLPurifier_Config $config) - { - return new HTMLPurifier_Config($config->def, $config->plist); - } - - /** - * Convenience constructor that creates a default configuration object. - * @return HTMLPurifier_Config default object. - */ - public static function createDefault() - { - $definition = HTMLPurifier_ConfigSchema::instance(); - $config = new HTMLPurifier_Config($definition); - return $config; - } - - /** - * Retrieves a value from the configuration. - * - * @param string $key String key - * @param mixed $a - * - * @return mixed - */ - public function get($key, $a = null) - { - if ($a !== null) { - $this->triggerError( - "Using deprecated API: use \$config->get('$key.$a') instead", - E_USER_WARNING - ); - $key = "$key.$a"; - } - 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 - ); - 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 - ); - 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 - ); - return; - } - } - return $this->plist->get($key); - } - - /** - * 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(); - } - $full = $this->getAll(); - if (!isset($full[$namespace])) { - $this->triggerError( - 'Cannot retrieve undefined namespace ' . - htmlspecialchars($namespace), - E_USER_WARNING - ); - return; - } - return $full[$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! - */ - public function getBatchSerial($namespace) - { - if (empty($this->serials[$namespace])) { - $batch = $this->getBatch($namespace); - unset($batch['DefinitionRev']); - $this->serials[$namespace] = sha1(serialize($batch)); - } - return $this->serials[$namespace]; - } - - /** - * Returns a SHA-1 signature for the entire configuration object - * that uniquely identifies that particular configuration - * - * @return string - */ - public function getSerial() - { - if (empty($this->serial)) { - $this->serial = sha1(serialize($this->getAll())); - } - return $this->serial; - } - - /** - * 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(); - } - $ret = array(); - foreach ($this->plist->squash() as $name => $value) { - list($ns, $key) = explode('.', $name, 2); - $ret[$ns][$key] = $value; - } - return $ret; - } - - /** - * Sets a value to configuration. - * - * @param string $key key - * @param mixed $value value - * @param mixed $a - */ - public function set($key, $value, $a = null) - { - if (strpos($key, '.') === false) { - $namespace = $key; - $directive = $value; - $value = $a; - $key = "$key.$directive"; - $this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE); - } else { - list($namespace) = explode('.', $key); - } - 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 - ); - 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 - ); - return; - } - $this->aliasMode = true; - $this->set($def->key, $value); - $this->aliasMode = false; - $this->triggerError("$key is an alias, preferred directive name is {$def->key}", E_USER_NOTICE); - return; - } - - // Raw type might be negative when using the fully optimized form - // of stdclass, which indicates allow_null == true - $rtype = is_int($def) ? $def : $def->type; - if ($rtype < 0) { - $type = -$rtype; - $allow_null = true; - } else { - $type = $rtype; - $allow_null = isset($def->allow_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 - ); - return; - } - if (is_string($value) && is_object($def)) { - // resolve value alias if defined - if (isset($def->aliases[$value])) { - $value = $def->aliases[$value]; - } - // 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 - ); - return; - } - } - $this->plist->set($key, $value); - - // reset definitions if the directives they depend on changed - // this is a very costly process, so it's discouraged - // with finalization - if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') { - $this->definitions[$namespace] = null; - } - - $this->serials[$namespace] = false; - } - - /** - * Convenience function for error reporting - * - * @param array $lookup - * - * @return string - */ - private function _listify($lookup) - { - $list = array(); - foreach ($lookup as $name => $b) { - $list[] = $name; - } - return implode(', ', $list); - } - - /** - * Retrieves object reference to the HTML definition. - * - * @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 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) - { - return $this->getDefinition('HTML', $raw, $optimized); - } - - /** - * Retrieves object reference to the CSS definition - * - * @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 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) - { - return $this->getDefinition('CSS', $raw, $optimized); - } - - /** - * Retrieves object reference to the URI definition - * - * @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 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) - { - return $this->getDefinition('URI', $raw, $optimized); - } - - /** - * Retrieves a definition - * - * @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 - * way in order to ensure that caching is done properly. - * 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) - { - if ($optimized && !$raw) { - throw new HTMLPurifier_Exception("Cannot set optimized = true when raw = false"); - } - if (!$this->finalized) { - $this->autoFinalize(); - } - // temporarily suspend locks, so we can handle recursive definition calls - $lock = $this->lock; - $this->lock = null; - $factory = HTMLPurifier_DefinitionCacheFactory::instance(); - $cache = $factory->create($type, $this); - $this->lock = $lock; - if (!$raw) { - // full definition - // --------------- - // check if definition is in memory - if (!empty($this->definitions[$type])) { - $def = $this->definitions[$type]; - // check if the definition is setup - if ($def->setup) { - return $def; - } else { - $def->setup($this); - if ($def->optimized) { - $cache->add($def, $this); - } - return $def; - } - } - // check if definition is in cache - $def = $cache->get($this); - if ($def) { - // definition in cache, save to memory and return it - $this->definitions[$type] = $def; - return $def; - } - // initialize it - $def = $this->initDefinition($type); - // set it up - $this->lock = $type; - $def->setup($this); - $this->lock = null; - // save in cache - $cache->add($def, $this); - // return it - return $def; - } else { - // raw definition - // -------------- - // check preconditions - $def = null; - 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" - ); - } - } - 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 - ); - } - if ($def->optimized === null) { - $extra = $this->chatty ? " (try flushing your cache)" : ""; - 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 - ); - } - } - // check if definition was in memory - if ($def) { - if ($def->setup) { - // invariant: $optimized === true (checked above) - return null; - } else { - return $def; - } - } - // if optimized, check if definition was in cache - // (because we do the memory check first, this formulation - // is prone to cache slamming, but I think - // guaranteeing that either /all/ of the raw - // setup code or /none/ of it is run is more important.) - if ($optimized) { - // This code path only gets run once; once we put - // something in $definitions (which is guaranteed by the - // trailing code), we always short-circuit above. - $def = $cache->get($this); - if ($def) { - // save the full definition for later, but don't - // return it yet - $this->definitions[$type] = $def; - return null; - } - } - // check invariants for creation - 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 - ); - } else { - $this->triggerError( - "Useless DefinitionID declaration", - E_USER_WARNING - ); - } - } - } - // initialize it - $def = $this->initDefinition($type); - $def->optimized = $optimized; - return $def; - } - throw new HTMLPurifier_Exception("The impossible happened!"); - } - - /** - * 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(); - } elseif ($type == 'CSS') { - $def = new HTMLPurifier_CSSDefinition(); - } elseif ($type == 'URI') { - $def = new HTMLPurifier_URIDefinition(); - } else { - throw new HTMLPurifier_Exception( - "Definition of $type type not supported" - ); - } - $this->definitions[$type] = $def; - return $def; - } - - public function maybeGetRawDefinition($name) - { - return $this->getDefinition($name, true, true); - } - - public function maybeGetRawHTMLDefinition() - { - return $this->getDefinition('HTML', true, true); - } - - public function maybeGetRawCSSDefinition() - { - return $this->getDefinition('CSS', true, true); - } - - public function maybeGetRawURIDefinition() - { - return $this->getDefinition('URI', true, true); - } - - /** - * Loads configuration values from an array with the following structure: - * Namespace.Directive => Value - * - * @param array $config_array Configuration associative array - */ - 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) { - $this->set($key, $value); - } else { - $namespace = $key; - $namespace_values = $value; - foreach ($namespace_values as $directive => $value2) { - $this->set($namespace .'.'. $directive, $value2); - } - } - } - } - - /** - * 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 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) - { - 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; - } - } - } - $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($def->isAlias)) { - continue; - } - if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') { - continue; - } - $ret[] = array($ns, $directive); - } - return $ret; - } - - /** - * Loads configuration values from $_GET/$_POST that were posted - * via ConfigForm - * - * @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) - { - $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema); - $config = HTMLPurifier_Config::create($ret, $schema); - return $config; - } - - /** - * Merges in configuration values from $_GET/$_POST to object. NOT STATIC. - * - * @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) - { - $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def); - $this->loadArray($ret); - } - - /** - * 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(); - } - $mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc(); - - $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema); - $ret = array(); - foreach ($allowed as $key) { - list($ns, $directive) = $key; - $skey = "$ns.$directive"; - if (!empty($array["Null_$skey"])) { - $ret[$ns][$directive] = null; - continue; - } - if (!isset($array[$skey])) { - continue; - } - $value = $mq ? stripslashes($array[$skey]) : $array[$skey]; - $ret[$ns][$directive] = $value; - } - return $ret; - } - - /** - * Loads configuration values from an ini file - * - * @param string $filename Name of ini file - */ - 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 string|bool $error String error message, or false for no error - * - * @return bool - */ - public function isFinalized($error = false) - { - if ($this->finalized && $error) { - $this->triggerError($error, E_USER_ERROR); - } - return $this->finalized; - } - - /** - * Finalizes configuration only if auto finalize is on and not - * already finalized - */ - public function autoFinalize() - { - if ($this->autoFinalize) { - $this->finalize(); - } else { - $this->plist->squash(true); - } - } - - /** - * Finalizes a configuration object, prohibiting further change - */ - public function finalize() - { - $this->finalized = true; - $this->parser = null; - } - - /** - * 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) - { - // determine previous stack frame - $extra = ''; - if ($this->chatty) { - $trace = debug_backtrace(); - // zip(tail(trace), trace) -- but PHP is not Haskell har har - for ($i = 0, $c = count($trace); $i < $c - 1; $i++) { - // XXX this is not correct on some versions of HTML Purifier - if ($trace[$i + 1]['class'] === 'HTMLPurifier_Config') { - continue; - } - $frame = $trace[$i]; - $extra = " invoked on line {$frame['line']} in file {$frame['file']}"; - break; - } - } - trigger_error($msg . $extra, $no); - } - - /** - * Returns a serialized form of the configuration object that can - * be reconstituted. - * - * @return string - */ - public function serialize() - { - $this->getDefinition('HTML'); - $this->getDefinition('CSS'); - $this->getDefinition('URI'); - return serialize($this); - } - -} - - - - - -/** - * Configuration definition, defines directives and their defaults. - */ -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: - * - * array( - * 'Namespace' => array( - * 'Directive' => new stdclass(), - * ) - * ) - * - * The stdclass may have the following properties: - * - * - If isAlias isn't set: - * - type: Integer type of directive, see HTMLPurifier_VarParser for definitions - * - allow_null: If set, this directive allows null values - * - aliases: If set, an associative array of value aliases to real values - * - allowed: If set, a lookup array of allowed (string) values - * - If isAlias is set: - * - namespace: Namespace this directive aliases to - * - name: Directive name this directive aliases to - * - * In certain degenerate cases, stdclass will actually be an integer. In - * that case, the value is equivalent to an stdclass with the type - * property set to the integer. If the integer is negative, type is - * equal to the absolute value of integer, and allow_null is true. - * - * 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 - */ - protected static $singleton; - - public function __construct() - { - $this->defaultPlist = new HTMLPurifier_PropertyList(); - } - - /** - * Unserializes the default ConfigSchema. - * @return HTMLPurifier_ConfigSchema - */ - public static function makeFromSerial() - { - $contents = file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema.ser'); - $r = unserialize($contents); - if (!$r) { - $hash = sha1($contents); - trigger_error("Unserialization of configuration schema failed, sha1 of file was $hash", E_USER_ERROR); - } - return $r; - } - - /** - * Retrieves an instance of the application-wide configuration definition. - * @param HTMLPurifier_ConfigSchema $prototype - * @return HTMLPurifier_ConfigSchema - */ - public static function instance($prototype = null) - { - if ($prototype !== null) { - HTMLPurifier_ConfigSchema::$singleton = $prototype; - } elseif (HTMLPurifier_ConfigSchema::$singleton === null || $prototype === true) { - HTMLPurifier_ConfigSchema::$singleton = HTMLPurifier_ConfigSchema::makeFromSerial(); - } - return HTMLPurifier_ConfigSchema::$singleton; - } - - /** - * Defines a directive for configuration - * @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 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 bool $allow_null Whether or not to allow null values - */ - 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; - } - $this->info[$key] = $obj; - $this->defaults[$key] = $default; - $this->defaultPlist->set($key, $default); - } - - /** - * Defines a directive value alias. - * - * Directive value aliases are convenient for developers because it lets - * them set a directive to several values and get the same result. - * @param string $key Name of Directive - * @param array $aliases Hash of aliased values to the real alias - */ - public function addValueAliases($key, $aliases) - { - if (!isset($this->info[$key]->aliases)) { - $this->info[$key]->aliases = array(); - } - foreach ($aliases as $alias => $real) { - $this->info[$key]->aliases[$alias] = $real; - } - } - - /** - * Defines a set of allowed values for a directive. - * @warning This is slightly different from the corresponding static - * method definition. - * @param string $key Name of directive - * @param array $allowed Lookup array of allowed values - */ - public function addAllowedValues($key, $allowed) - { - $this->info[$key]->allowed = $allowed; - } - - /** - * Defines a directive alias for backwards compatibility - * @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) - { - $obj = new stdclass; - $obj->key = $new_key; - $obj->isAlias = true; - $this->info[$key] = $obj; - } - - /** - * Replaces any stdclass that only has the type property with type integer. - */ - public function postProcess() - { - foreach ($this->info as $key => $v) { - if (count((array) $v) == 1) { - $this->info[$key] = $v->type; - } elseif (count((array) $v) == 2 && isset($v->allow_null)) { - $this->info[$key] = -$v->type; - } - } - } -} - - - - - -/** - * @todo Unit test - */ -class HTMLPurifier_ContentSets -{ - - /** - * 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). - * @type array - */ - protected $keys = array(); - /** - * 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 HTMLPurifier_HTMLModule[] $modules List of HTMLPurifier_HTMLModule - */ - 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) { - foreach ($module->content_sets as $key => $value) { - $temp = $this->convertToLookup($value); - if (isset($this->lookup[$key])) { - // add it into the existing content set - $this->lookup[$key] = array_merge($this->lookup[$key], $temp); - } else { - $this->lookup[$key] = $temp; - } - } - } - $old_lookup = false; - while ($old_lookup !== $this->lookup) { - $old_lookup = $this->lookup; - foreach ($this->lookup as $i => $set) { - $add = array(); - foreach ($set as $element => $x) { - if (isset($this->lookup[$element])) { - $add += $this->lookup[$element]; - unset($this->lookup[$i][$element]); - } - } - $this->lookup[$i] += $add; - } - } - - foreach ($this->lookup as $key => $lookup) { - $this->info[$key] = implode(' | ', array_keys($lookup)); - } - $this->keys = array_keys($this->info); - $this->values = array_values($this->info); - } - - /** - * Accepts a definition; generates and assigns a ChildDef for it - * @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)) { // already done! - return; - } - $content_model = $def->content_model; - if (is_string($content_model)) { - // Assume that $this->keys is alphanumeric - $def->content_model = preg_replace_callback( - '/\b(' . implode('|', $this->keys) . ')\b/', - array($this, 'generateChildDefCallback'), - $content_model - ); - //$def->content_model = str_replace( - // $this->keys, $this->values, $content_model); - } - $def->child = $this->getChildDef($def, $module); - } - - public function generateChildDefCallback($matches) - { - return $this->info[$matches[0]]; - } - - /** - * Instantiates a ChildDef based on content_model and content_model_type - * member variables in HTMLPurifier_ElementDef - * @note This will also defer to modules for custom HTMLPurifier_ChildDef - * subclasses that need content set expansion - * @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) - { - $value = $def->content_model; - if (is_object($value)) { - trigger_error( - 'Literal object child definitions should be stored in '. - 'ElementDef->child not ElementDef->content_model', - E_USER_NOTICE - ); - return $value; - } - switch ($def->content_model_type) { - case 'required': - return new HTMLPurifier_ChildDef_Required($value); - case 'optional': - return new HTMLPurifier_ChildDef_Optional($value); - case 'empty': - return new HTMLPurifier_ChildDef_Empty(); - case 'custom': - return new HTMLPurifier_ChildDef_Custom($value); - } - // defer to its module - $return = false; - if ($module->defines_child_def) { // save a func call - $return = $module->getChildDef($def); - } - if ($return !== false) { - return $return; - } - // error-out - trigger_error( - 'Could not determine which ChildDef class to instantiate', - E_USER_ERROR - ); - return false; - } - - /** - * Converts a string list of elements separated by pipes into - * a lookup array. - * @param string $string List of elements - * @return array Lookup array of elements - */ - protected function convertToLookup($string) - { - $array = explode('|', str_replace(' ', '', $string)); - $ret = array(); - foreach ($array as $k) { - $ret[$k] = true; - } - return $ret; - } -} - - - - - -/** - * Registry object that contains information about the current context. - * @warning Is a bit buggy when variables are set to null: it thinks - * they don't exist! So use false instead, please. - * @note Since the variables Context deals with may not be objects, - * references are very important here! Do not remove! - */ -class HTMLPurifier_Context -{ - - /** - * Private array that stores the references. - * @type array - */ - private $_storage = array(); - - /** - * Registers a variable into the context. - * @param string $name String name - * @param mixed $ref Reference to variable to be registered - */ - 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; - } - - /** - * Retrieves a variable reference from the context. - * @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 (!array_key_exists($name, $this->_storage)) { - if (!$ignore_error) { - trigger_error( - "Attempted to retrieve non-existent variable $name", - E_USER_ERROR - ); - } - $var = null; // so we can return by reference - return $var; - } - return $this->_storage[$name]; - } - - /** - * Destroys a variable in the context. - * @param string $name String name - */ - 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]); - } - - /** - * Checks whether or not the variable exists. - * @param string $name String name - * @return bool - */ - public function exists($name) - { - return array_key_exists($name, $this->_storage); - } - - /** - * Loads a series of variables from an associative array - * @param array $context_array Assoc array of variables to load - */ - public function loadArray($context_array) - { - foreach ($context_array as $key => $discard) { - $this->register($key, $context_array[$key]); - } - } -} - - - - - -/** - * Abstract class representing Definition cache managers that implements - * useful common methods and is a factory. - * @todo Create a separate maintenance file advanced users can use to - * cache their custom HTMLDefinition, which can be loaded - * via a configuration directive - * @todo Implement memcached - */ -abstract class HTMLPurifier_DefinitionCache -{ - /** - * @type string - */ - public $type; - - /** - * @param string $type Type of definition objects this instance of the - * cache will handle. - */ - public function __construct($type) - { - $this->type = $type; - } - - /** - * Generates a unique identifier for a particular configuration - * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config - * @return string - */ - public function generateKey($config) - { - return $config->version . ',' . // possibly replace with function calls - $config->getBatchSerial($this->type) . ',' . - $config->get($this->type . '.DefinitionRev'); - } - - /** - * Tests whether or not a key is old with respect to the configuration's - * version and revision number. - * @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; - } - list($version, $hash, $revision) = explode(',', $key, 3); - $compare = version_compare($version, $config->version); - // version mismatch, is always old - 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; - } - return false; - } - - /** - * Checks if a definition's type jives with the cache's type - * @note Throws an error on failure - * @param HTMLPurifier_Definition $def Definition object to check - * @return bool true if good, false if not - */ - 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; - } - return true; - } - - /** - * 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); - - /** - * Clears all expired (older version or revision) objects from cache - * @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); -} - - - - - -/** - * Responsible for creating definition caches. - */ -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() - { - $this->addDecorator('Cleanup'); - } - - /** - * Retrieves an instance of global definition cache factory. - * @param HTMLPurifier_DefinitionCacheFactory $prototype - * @return HTMLPurifier_DefinitionCacheFactory - */ - public static function instance($prototype = null) - { - static $instance; - if ($prototype !== null) { - $instance = $prototype; - } elseif ($instance === null || $prototype === true) { - $instance = new HTMLPurifier_DefinitionCacheFactory(); - $instance->setup(); - } - return $instance; - } - - /** - * Registers a new definition cache object - * @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) - { - $this->implementations[$short] = $long; - } - - /** - * Factory method that creates a cache object based on configuration - * @param string $type Name of definitions handled by cache - * @param HTMLPurifier_Config $config Config instance - * @return mixed - */ - public function create($type, $config) - { - $method = $config->get('Cache.DefinitionImpl'); - if ($method === null) { - return new HTMLPurifier_DefinitionCache_Null($type); - } - if (!empty($this->caches[$method][$type])) { - return $this->caches[$method][$type]; - } - if (isset($this->implementations[$method]) && - class_exists($class = $this->implementations[$method], false)) { - $cache = new $class($type); - } else { - if ($method != 'Serializer') { - trigger_error("Unrecognized DefinitionCache $method, using Serializer instead", E_USER_WARNING); - } - $cache = new HTMLPurifier_DefinitionCache_Serializer($type); - } - foreach ($this->decorators as $decorator) { - $new_cache = $decorator->decorate($cache); - // prevent infinite recursion in PHP 4 - unset($cache); - $cache = $new_cache; - } - $this->caches[$method][$type] = $cache; - return $this->caches[$method][$type]; - } - - /** - * Registers a decorator to add to all new cache objects - * @param HTMLPurifier_DefinitionCache_Decorator|string $decorator An instance or the name of a decorator - */ - public function addDecorator($decorator) - { - if (is_string($decorator)) { - $class = "HTMLPurifier_DefinitionCache_Decorator_$decorator"; - $decorator = new $class; - } - $this->decorators[$decorator->name] = $decorator; - } -} - - - - - -/** - * Represents a document type, contains information on which modules - * need to be loaded. - * @note This class is inspected by Printer_HTMLDefinition->renderDoctype. - * If structure changes, please update that function. - */ -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 - ) { - $this->name = $name; - $this->xml = $xml; - $this->modules = $modules; - $this->tidyModules = $tidyModules; - $this->aliases = $aliases; - $this->dtdPublic = $dtd_public; - $this->dtdSystem = $dtd_system; - } -} - - - - - -class HTMLPurifier_DoctypeRegistry -{ - - /** - * Hash of doctype names to doctype objects. - * @type array - */ - protected $doctypes; - - /** - * Lookup table of aliases to real doctype names. - * @type array - */ - protected $aliases; - - /** - * Registers a doctype to the registry - * @note Accepts a fully-formed doctype object, or the - * parameters for constructing a doctype object - * @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_object($doctype)) { - $doctype = new HTMLPurifier_Doctype( - $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; - } - $this->aliases[$alias] = $name; - } - // remove old aliases - if (isset($this->aliases[$name])) { - unset($this->aliases[$name]); - } - return $doctype; - } - - /** - * Retrieves reference to a doctype of a certain name - * @note This function resolves aliases - * @note When possible, use the more fully-featured make() - * @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]; - } - if (!isset($this->doctypes[$doctype])) { - trigger_error('Doctype ' . htmlspecialchars($doctype) . ' does not exist', E_USER_ERROR); - $anon = new HTMLPurifier_Doctype($doctype); - return $anon; - } - return $this->doctypes[$doctype]; - } - - /** - * Creates a doctype based on a configuration object, - * will perform initialization on the doctype - * @note Use this function to get a copy of doctype that config - * 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) - { - return clone $this->get($this->getDoctypeFromConfig($config)); - } - - /** - * Retrieves the doctype from the configuration object - * @param HTMLPurifier_Config $config - * @return string - */ - public function getDoctypeFromConfig($config) - { - // recommended test - $doctype = $config->get('HTML.Doctype'); - if (!empty($doctype)) { - return $doctype; - } - $doctype = $config->get('HTML.CustomDoctype'); - if (!empty($doctype)) { - return $doctype; - } - // backwards-compatibility - if ($config->get('HTML.XHTML')) { - $doctype = 'XHTML 1.0'; - } else { - $doctype = 'HTML 4.01'; - } - if ($config->get('HTML.Strict')) { - $doctype .= ' Strict'; - } else { - $doctype .= ' Transitional'; - } - return $doctype; - } -} - - - - - -/** - * Structure that stores an HTML element definition. Used by - * HTMLPurifier_HTMLDefinition and HTMLPurifier_HTMLModule. - * @note This class is inspected by HTMLPurifier_Printer_HTMLDefinition. - * Please update that class too. - * @warning If you add new properties to this class, you MUST update - * the mergeIn() method. - */ -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. - * @type array - * @note Before being processed by HTMLPurifier_AttrCollections - * when modules are finalized during - * HTMLPurifier_HTMLDefinition->setup(), this array may also - * contain an array at index 0 that indicates which attribute - * collections to load into the full array. It may also - * contain string indentifiers in lieu of HTMLPurifier_AttrDef, - * see HTMLPurifier_AttrTypes on how they are expanded during - * HTMLPurifier_HTMLDefinition->setup() processing. - */ - public $attr = array(); - - // XXX: Design note: currently, it's not possible to override - // previously defined AttrTransforms without messing around with - // the final generated config. This is by design; a previous version - // used an associated list of attr_transform, but it was extremely - // easy to accidentally override other attribute transforms by - // forgetting to specify an index (and just using 0.) While we - // could check this by checking the index number and complaining, - // there is a second problem which is that it is not at all easy to - // tell when something is getting overridden. Combine this with a - // codebase where this isn't really being used, and it's perfect for - // nuking. - - /** - * 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. - * @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 - * into an HTMLPurifier_ChildDef. - * @warning This is a temporary variable that is not available after - * being processed by HTMLDefinition - * @type string - */ - public $content_model; - - /** - * Value of $child->type, used to determine which ChildDef to use, - * used in combination with $content_model. - * @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() - * @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 - * exclusion for children, but we go the simpler, SGML-style - * route of flat-out exclusions, which correctly apply to - * all descendants and not just children. Note that the XHTML - * Modularization Abstract Modules are blithely unaware of such - * distinctions. - */ - public $excludes = array(); - - /** - * This tag is explicitly auto-closed by the following tags. - * @type array - */ - public $autoclose = array(); - - /** - * 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) - { - $def = new HTMLPurifier_ElementDef(); - $def->content_model = $content_model; - $def->content_model_type = $content_model_type; - $def->attr = $attr; - return $def; - } - - /** - * 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) - { - // later keys takes precedence - foreach ($def->attr as $k => $v) { - if ($k === 0) { - // merge in the includes - // sorry, no way to override an include - foreach ($v as $v2) { - $this->attr[0][] = $v2; - } - continue; - } - if ($v === false) { - if (isset($this->attr[$k])) { - unset($this->attr[$k]); - } - continue; - } - $this->attr[$k] = $v; - } - $this->_mergeAssocArray($this->excludes, $def->excludes); - $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)) { - $this->content_model = - str_replace("#SUPER", $this->content_model, $def->content_model); - $this->child = false; - } - 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; - } - } - - /** - * Merges one array into another, removes values which equal false - * @param $a1 Array by reference that is merged into - * @param $a2 Array that merges into $a1 - */ - private function _mergeAssocArray(&$a1, $a2) - { - foreach ($a2 as $k => $v) { - if ($v === false) { - if (isset($a1[$k])) { - unset($a1[$k]); - } - continue; - } - $a1[$k] = $v; - } - } -} - - - - - -/** - * A UTF-8 specific character encoder that handles cleaning and transforming. - * @note All functions in this class should be static. - */ -class HTMLPurifier_Encoder -{ - - /** - * Constructor throws fatal error if you attempt to instantiate class - */ - 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() - { - } - - /** - * 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) - { - set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler')); - $r = iconv($in, $out, $text); - restore_error_handler(); - return $r; - } - - /** - * iconv wrapper which mutes errors and works around bugs. - * @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); - } elseif ($code == self::ICONV_TRUNCATES) { - // we can only work around this if the input character set - // is utf-8 - if ($in == 'utf-8') { - if ($max_chunk_size < 4) { - trigger_error('max_chunk_size is too small', E_USER_WARNING); - return false; - } - // split into 8000 byte chunks, but be careful to handle - // multibyte boundaries properly - if (($c = strlen($text)) <= $max_chunk_size) { - return self::unsafeIconv($in, $out, $text); - } - $r = ''; - $i = 0; - while (true) { - if ($i + $max_chunk_size >= $c) { - $r .= self::unsafeIconv($in, $out, substr($text, $i)); - break; - } - // wibble the boundary - if (0x80 != (0xC0 & ord($text[$i + $max_chunk_size]))) { - $chunk_size = $max_chunk_size; - } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 1]))) { - $chunk_size = $max_chunk_size - 1; - } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 2]))) { - $chunk_size = $max_chunk_size - 2; - } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 3]))) { - $chunk_size = $max_chunk_size - 3; - } else { - return false; // rather confusing UTF-8... - } - $chunk = substr($text, $i, $chunk_size); // substr doesn't mind overlong lengths - $r .= self::unsafeIconv($in, $out, $chunk); - $i += $chunk_size; - } - return $r; - } else { - return false; - } - } else { - return false; - } - } - - /** - * Cleans a UTF-8 string for well-formedness and SGML validity - * - * 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 - * respectively. 128 and above the code points map to multibyte - * UTF-8 representations. - * - * @note Fallback code adapted from utf8ToUnicode by Henri Sivonen and - * hsivonen@iki.fi at under the - * LGPL license. Notes on what changed are inside, but in general, - * the original code transformed UTF-8 text into an array of integer - * Unicode codepoints. Understandably, transforming that back to - * a string would be somewhat expensive, so the function was modded to - * directly operate on the string. However, this discourages code - * reuse, and the logic enumerated here would be useful for any - * function that needs to be able to understand UTF-8 characters. - * As of right now, only smart lossless character encoding converters - * 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) - { - // 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 - )) { - return $str; - } - - $mState = 0; // cached expected number of octets after the current octet - // until the beginning of the next UTF8 character sequence - $mUcs4 = 0; // cached Unicode character - $mBytes = 1; // cached expected number of octets in the current sequence - - // original code involved an $out that was an array of Unicode - // codepoints. Instead of having to convert back into UTF-8, we've - // decided to directly append valid UTF-8 characters onto a string - // $out once they're done. $char accumulates raw bytes, while $mUcs4 - // turns into the Unicode code point, so there's some redundancy. - - $out = ''; - $char = ''; - - $len = strlen($str); - for ($i = 0; $i < $len; $i++) { - $in = ord($str{$i}); - $char .= $str[$i]; // append byte to char - if (0 == $mState) { - // When mState is zero we expect either a US-ASCII character - // or a multi-octet sequence. - if (0 == (0x80 & ($in))) { - // US-ASCII, pass straight through. - if (($in <= 31 || $in == 127) && - !($in == 9 || $in == 13 || $in == 10) // save \r\t\n - ) { - // control characters, remove - } else { - $out .= $char; - } - // reset - $char = ''; - $mBytes = 1; - } elseif (0xC0 == (0xE0 & ($in))) { - // First octet of 2 octet sequence - $mUcs4 = ($in); - $mUcs4 = ($mUcs4 & 0x1F) << 6; - $mState = 1; - $mBytes = 2; - } elseif (0xE0 == (0xF0 & ($in))) { - // First octet of 3 octet sequence - $mUcs4 = ($in); - $mUcs4 = ($mUcs4 & 0x0F) << 12; - $mState = 2; - $mBytes = 3; - } elseif (0xF0 == (0xF8 & ($in))) { - // First octet of 4 octet sequence - $mUcs4 = ($in); - $mUcs4 = ($mUcs4 & 0x07) << 18; - $mState = 3; - $mBytes = 4; - } elseif (0xF8 == (0xFC & ($in))) { - // First octet of 5 octet sequence. - // - // This is illegal because the encoded codepoint must be - // either: - // (a) not the shortest form or - // (b) outside the Unicode range of 0-0x10FFFF. - // Rather than trying to resynchronize, we will carry on - // until the end of the sequence and let the later error - // handling code catch it. - $mUcs4 = ($in); - $mUcs4 = ($mUcs4 & 0x03) << 24; - $mState = 4; - $mBytes = 5; - } elseif (0xFC == (0xFE & ($in))) { - // First octet of 6 octet sequence, see comments for 5 - // octet sequence. - $mUcs4 = ($in); - $mUcs4 = ($mUcs4 & 1) << 30; - $mState = 5; - $mBytes = 6; - } else { - // Current octet is neither in the US-ASCII range nor a - // legal first octet of a multi-octet sequence. - $mState = 0; - $mUcs4 = 0; - $mBytes = 1; - $char = ''; - } - } else { - // When mState is non-zero, we expect a continuation of the - // multi-octet sequence - if (0x80 == (0xC0 & ($in))) { - // Legal continuation. - $shift = ($mState - 1) * 6; - $tmp = $in; - $tmp = ($tmp & 0x0000003F) << $shift; - $mUcs4 |= $tmp; - - if (0 == --$mState) { - // End of the multi-octet sequence. mUcs4 now contains - // the final Unicode codepoint to be output - - // Check for illegal sequences and codepoints. - - // From Unicode 3.1, non-shortest form is illegal - if (((2 == $mBytes) && ($mUcs4 < 0x0080)) || - ((3 == $mBytes) && ($mUcs4 < 0x0800)) || - ((4 == $mBytes) && ($mUcs4 < 0x10000)) || - (4 < $mBytes) || - // From Unicode 3.2, surrogate characters = illegal - (($mUcs4 & 0xFFFFF800) == 0xD800) || - // Codepoints outside the Unicode range are illegal - ($mUcs4 > 0x10FFFF) - ) { - - } elseif (0xFEFF != $mUcs4 && // omit BOM - // check for valid Char unicode codepoints - ( - 0x9 == $mUcs4 || - 0xA == $mUcs4 || - 0xD == $mUcs4 || - (0x20 <= $mUcs4 && 0x7E >= $mUcs4) || - // 7F-9F is not strictly prohibited by XML, - // but it is non-SGML, and thus we don't allow it - (0xA0 <= $mUcs4 && 0xD7FF >= $mUcs4) || - (0x10000 <= $mUcs4 && 0x10FFFF >= $mUcs4) - ) - ) { - $out .= $char; - } - // initialize UTF8 cache (reset) - $mState = 0; - $mUcs4 = 0; - $mBytes = 1; - $char = ''; - } - } else { - // ((0xC0 & (*in) != 0x80) && (mState != 0)) - // Incomplete multi-octet sequence. - // used to result in complete fail, but we'll reset - $mState = 0; - $mUcs4 = 0; - $mBytes = 1; - $char =''; - } - } - } - return $out; - } - - /** - * Translates a Unicode codepoint into its corresponding UTF-8 character. - * @note Based on Feyd's function at - * , - * which is in public domain. - * @note While we're going to do code point parsing anyway, a good - * optimization would be to refuse to translate code points that - * are non-SGML characters. However, this could lead to duplication. - * @note This is very similar to the unichr function in - * maintenance/generate-entity-file.php (although this is superior, - * due to its sanity checks). - */ - - // +----------+----------+----------+----------+ - // | 33222222 | 22221111 | 111111 | | - // | 10987654 | 32109876 | 54321098 | 76543210 | bit - // +----------+----------+----------+----------+ - // | | | | 0xxxxxxx | 1 byte 0x00000000..0x0000007F - // | | | 110yyyyy | 10xxxxxx | 2 byte 0x00000080..0x000007FF - // | | 1110zzzz | 10yyyyyy | 10xxxxxx | 3 byte 0x00000800..0x0000FFFF - // | 11110www | 10wwzzzz | 10yyyyyy | 10xxxxxx | 4 byte 0x00010000..0x0010FFFF - // +----------+----------+----------+----------+ - // | 00000000 | 00011111 | 11111111 | 11111111 | Theoretical upper limit of legal scalars: 2097151 (0x001FFFFF) - // | 00000000 | 00010000 | 11111111 | 11111111 | Defined upper limit of legal scalar codes - // +----------+----------+----------+----------+ - - 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 - return ''; - } - - $x = $y = $z = $w = 0; - if ($code < 128) { - // regular ASCII character - $x = $code; - } else { - // set up bits for UTF-8 - $x = ($code & 63) | 128; - if ($code < 2048) { - $y = (($code & 2047) >> 6) | 192; - } else { - $y = (($code & 4032) >> 6) | 128; - if ($code < 65536) { - $z = (($code >> 12) & 15) | 224; - } else { - $z = (($code >> 12) & 63) | 128; - $w = (($code >> 18) & 7) | 240; - } - } - } - // set up the actual character - $ret = ''; - if ($w) { - $ret .= chr($w); - } - if ($z) { - $ret .= chr($z); - } - if ($y) { - $ret .= chr($y); - } - $ret .= chr($x); - - return $ret; - } - - /** - * @return bool - */ - public static function iconvAvailable() - { - static $iconv = null; - if ($iconv === null) { - $iconv = function_exists('iconv') && self::testIconvTruncateBug() != self::ICONV_UNUSABLE; - } - return $iconv; - } - - /** - * 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) - { - $encoding = $config->get('Core.Encoding'); - if ($encoding === 'utf-8') { - return $str; - } - static $iconv = null; - 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); - if ($str === false) { - // $encoding is not a valid encoding - trigger_error('Invalid encoding ' . $encoding, E_USER_ERROR); - return ''; - } - // If the string is bjorked by Shift_JIS or a similar encoding - // that doesn't support all of ASCII, convert the naughty - // characters to their true byte-wise ASCII/UTF-8 equivalents. - $str = strtr($str, self::testEncodingSupportsASCII($encoding)); - return $str; - } elseif ($encoding === 'iso-8859-1') { - $str = utf8_encode($str); - return $str; - } - $bug = HTMLPurifier_Encoder::testIconvTruncateBug(); - 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 - ); - } - } - - /** - * 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) - { - $encoding = $config->get('Core.Encoding'); - if ($escape = $config->get('Core.EscapeNonASCIICharacters')) { - $str = self::convertToASCIIDumbLossless($str); - } - if ($encoding === 'utf-8') { - return $str; - } - static $iconv = null; - 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] = ''; - } - $str = strtr($str, $clear_fix); - } - $str = strtr($str, array_flip($ascii_fix)); - // Normal stuff - $str = self::iconv('utf-8', $encoding . '//IGNORE', $str); - return $str; - } elseif ($encoding === 'iso-8859-1') { - $str = utf8_decode($str); - return $str; - } - trigger_error('Encoding not supported', E_USER_ERROR); - // You might be tempted to assume that the ASCII representation - // might be OK, however, this is *not* universally true over all - // encodings. So we take the conservative route here, rather - // than forcibly turn on %Core.EscapeNonASCIICharacters - } - - /** - * Lossless (character-wise) conversion of HTML to ASCII - * @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. - * @note Uses decimal numeric entities since they are best supported. - * @note This is a DUMB function: it has no concept of keeping - * character entities that the projected character encoding - * can allow. We could possibly implement a smart version - * but that would require it to also know which Unicode - * codepoints the charset supported (not an easy task). - * @note Sort of with cleanUTF8() but it assumes that $str is - * well-formed UTF-8 - */ - 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); - $bytesleft = 0; - } elseif ($bytevalue <= 0xBF) { //10xx xxxx - $working = $working << 6; - $working += ($bytevalue & 0x3F); - $bytesleft--; - if ($bytesleft <= 0) { - $result .= "&#" . $working . ";"; - } - } elseif ($bytevalue <= 0xDF) { //110x xxxx - $working = $bytevalue & 0x1F; - $bytesleft = 1; - } elseif ($bytevalue <= 0xEF) { //1110 xxxx - $working = $bytevalue & 0x0F; - $bytesleft = 2; - } else { //1111 0xxx - $working = $bytevalue & 0x07; - $bytesleft = 3; - } - } - return $result; - } - - /** No bugs detected in iconv. */ - const ICONV_OK = 0; - - /** Iconv truncates output if converting from UTF-8 to another - * character set with //IGNORE, and a non-encodable character is found */ - const ICONV_TRUNCATES = 1; - - /** Iconv does not support //IGNORE, making it unusable for - * transcoding purposes */ - const ICONV_UNUSABLE = 2; - - /** - * glibc iconv has a known bug where it doesn't handle the magic - * //IGNORE stanza correctly. In particular, rather than ignore - * characters, it will return an EILSEQ after consuming some number - * of characters, and expect you to restart iconv as if it were - * an E2BIG. Old versions of PHP did not respect the errno, and - * returned the fragment, so as a result you would see iconv - * mysteriously truncating output. We can work around this by - * manually chopping our input into segments of about 8000 - * characters, as long as PHP ignores the error code. If PHP starts - * paying attention to the error code, iconv becomes unusable. - * - * @return int Error code indicating severity of bug. - */ - public static function testIconvTruncateBug() - { - static $code = null; - if ($code === null) { - // better not use iconv, otherwise infinite loop! - $r = self::unsafeIconv('utf-8', 'ascii//IGNORE', "\xCE\xB1" . str_repeat('a', 9000)); - if ($r === false) { - $code = self::ICONV_UNUSABLE; - } 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 - ); - } else { - $code = self::ICONV_OK; - } - } - return $code; - } - - /** - * This expensive function tests whether or not a given character - * encoding supports ASCII. 7/8-bit encodings like Shift_JIS will - * fail this test, and require special processing. Variable width - * encodings shouldn't ever fail. - * - * @param string $encoding Encoding name to test, as per iconv format - * @param bool $bypass Whether or not to bypass the precompiled arrays. - * @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) - { - // 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, - // so bug is not triggered. - // If ICONV_UNUSABLE, this call is irrelevant - static $encodings = array(); - if (!$bypass) { - if (isset($encodings[$encoding])) { - return $encodings[$encoding]; - } - $lenc = strtolower($encoding); - switch ($lenc) { - case 'shift_jis': - return array("\xC2\xA5" => '\\', "\xE2\x80\xBE" => '~'); - case 'johab': - return array("\xE2\x82\xA9" => '\\'); - } - if (strpos($lenc, 'iso-8859-') === 0) { - return array(); - } - } - $ret = array(); - 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 === '' || - // 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) - ) { - // Reverse engineer: what's the UTF-8 equiv of this byte - // sequence? This assumes that there's no variable width - // encoding that doesn't support ASCII. - $ret[self::unsafeIconv($encoding, 'UTF-8//IGNORE', $c)] = $c; - } - } - $encodings[$encoding] = $ret; - return $ret; - } -} - - - - - -/** - * Object that provides entity lookup table from entity name to character - */ -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) - { - if (!$file) { - $file = HTMLPURIFIER_PREFIX . '/HTMLPurifier/EntityLookup/entities.ser'; - } - $this->table = unserialize(file_get_contents($file)); - } - - /** - * Retrieves sole instance of the object. - * @param bool|HTMLPurifier_EntityLookup $prototype Optional prototype of custom lookup table to overload with. - * @return HTMLPurifier_EntityLookup - */ - public static function instance($prototype = false) - { - // no references, since PHP doesn't copy unless modified - static $instance = null; - if ($prototype) { - $instance = $prototype; - } elseif (!$instance) { - $instance = new HTMLPurifier_EntityLookup(); - $instance->setup(); - } - return $instance; - } -} - - - - - -// if want to implement error collecting here, we'll need to use some sort -// of global data (probably trigger_error) because it's impossible to pass -// $config or $context to the callback functions. - -/** - * Handles referencing and derefencing character entities - */ -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) - - /** - * Decimal to parsed string conversion table for special entities. - * @type array - */ - protected $_special_dec2str = - array( - 34 => '"', - 38 => '&', - 39 => "'", - 60 => '<', - 62 => '>' - ); - - /** - * Stripped entity names to decimal conversion table for special entities. - * @type array - */ - protected $_special_ent2dec = - array( - 'quot' => 34, - 'amp' => 38, - 'lt' => 60, - 'gt' => 62 - ); - - /** - * Substitutes non-special entities with their parsed equivalents. Since - * running this whenever you have parsed character is t3h 5uck, we run - * it before everything else. - * - * @param string $string String to have non-special entities parsed. - * @return string Parsed 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 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). - * @return string Replacement string. - */ - - 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; - } - return HTMLPurifier_Encoder::unichr($code); - } else { - if (isset($this->_special_ent2dec[$matches[3]])) { - return $entity; - } - if (!$this->_entity_lookup) { - $this->_entity_lookup = HTMLPurifier_EntityLookup::instance(); - } - if (isset($this->_entity_lookup->table[$matches[3]])) { - return $this->_entity_lookup->table[$matches[3]]; - } else { - return $entity; - } - } - } - - /** - * Substitutes only special entities with their parsed equivalents. - * - * @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 String to have non-special entities parsed. - * @return string Parsed string. - */ - public function substituteSpecialEntities($string) - { - return preg_replace_callback( - $this->_substituteEntitiesRegex, - array($this, 'specialEntityCallback'), - $string - ); - } - - /** - * Callback function for substituteSpecialEntities() that does the work. - * - * This callback has same syntax as nonSpecialEntityCallback(). - * - * @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). - * @return string Replacement string. - */ - protected function specialEntityCallback($matches) - { - $entity = $matches[0]; - $is_num = (@$matches[0][1] === '#'); - if ($is_num) { - $is_hex = (@$entity[2] === 'x'); - $int = $is_hex ? hexdec($matches[1]) : (int) $matches[2]; - return isset($this->_special_dec2str[$int]) ? - $this->_special_dec2str[$int] : - $entity; - } else { - return isset($this->_special_ent2dec[$matches[3]]) ? - $this->_special_ent2dec[$matches[3]] : - $entity; - } - } -} - - - - - -/** - * Error collection class that enables HTML Purifier to report HTML - * problems back to the user - */ -class HTMLPurifier_ErrorCollector -{ - - /** - * Identifiers for the returned error array. These are purposely numeric - * so list() can be used. - */ - const LINENO = 0; - const SEVERITY = 1; - 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(); - - /** - * @param HTMLPurifier_Context $context - */ - public function __construct($context) - { - $this->locale =& $context->get('Locale'); - $this->context = $context; - $this->_current =& $this->_stacks[0]; - $this->errors =& $this->_stacks[0]; - } - - /** - * Sends an error message to the collector for later use - * @param int $severity Error severity, PHP error style (don't use E_USER_) - * @param string $msg Error message text - */ - public function send($severity, $msg) - { - $args = array(); - if (func_num_args() > 2) { - $args = func_get_args(); - array_shift($args); - unset($args[0]); - } - - $token = $this->context->get('CurrentToken', true); - $line = $token ? $token->line : $this->context->get('CurrentLine', true); - $col = $token ? $token->col : $this->context->get('CurrentCol', true); - $attr = $this->context->get('CurrentAttr', true); - - // perform special substitutions, also add custom parameters - $subst = array(); - if (!is_null($token)) { - $args['CurrentToken'] = $token; - } - if (!is_null($attr)) { - $subst['$CurrentAttr.Name'] = $attr; - if (isset($token->attr[$attr])) { - $subst['$CurrentAttr.Value'] = $token->attr[$attr]; - } - } - - if (empty($args)) { - $msg = $this->locale->getMessage($msg); - } else { - $msg = $this->locale->formatMessage($msg, $args); - } - - if (!empty($subst)) { - $msg = strtr($msg, $subst); - } - - // (numerically indexed) - $error = array( - self::LINENO => $line, - self::SEVERITY => $severity, - self::MESSAGE => $msg, - self::CHILDREN => array() - ); - $this->_current[] = $error; - - // NEW CODE BELOW ... - // 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 (is_int($line) && is_int($col)) { - if (isset($this->lines[$line][$col])) { - $struct = $this->lines[$line][$col]; - } else { - $struct = $this->lines[$line][$col] = $new_struct; - } - // These ksorts may present a performance problem - ksort($this->lines[$line], SORT_NUMERIC); - } else { - if (isset($this->lines[-1])) { - $struct = $this->lines[-1]; - } else { - $struct = $this->lines[-1] = $new_struct; - } - } - ksort($this->lines, SORT_NUMERIC); - - // Now, check if we need to operate on a lower structure - if (!empty($attr)) { - $struct = $struct->getChild(HTMLPurifier_ErrorStruct::ATTR, $attr); - if (!$struct->value) { - $struct->value = array($attr, 'PUT VALUE HERE'); - } - } - if (!empty($cssprop)) { - $struct = $struct->getChild(HTMLPurifier_ErrorStruct::CSSPROP, $cssprop); - if (!$struct->value) { - // if we tokenize CSS this might be a little more difficult to do - $struct->value = array($cssprop, 'PUT VALUE HERE'); - } - } - - // Ok, structs are all setup, now time to register the error - $struct->addError($severity, $msg); - } - - /** - * Retrieves raw error data for custom formatter to use - */ - public function getRaw() - { - return $this->errors; - } - - /** - * Default HTML formatting implementation for error messages - * @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) - { - $ret = array(); - - $this->generator = new HTMLPurifier_Generator($config, $this->context); - 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; - } - foreach ($col_array as $col => $struct) { - $this->_renderStruct($ret, $struct, $line, $col); - } - } - if (isset($this->lines[-1])) { - $this->_renderStruct($ret, $this->lines[-1]); - } - - if (empty($errors)) { - return '

' . $this->locale->getMessage('ErrorCollector: No errors') . '

'; - } else { - return '
  • ' . implode('
  • ', $ret) . '
'; - } - - } - - private function _renderStruct(&$ret, $struct, $line = null, $col = null) - { - $stack = array($struct); - $context_stack = array(array()); - while ($current = array_pop($stack)) { - $context = array_pop($context_stack); - foreach ($current->errors as $error) { - list($severity, $msg) = $error; - $string = ''; - $string .= '
'; - // W3C uses an icon to indicate the severity of the error. - $error = $this->locale->getErrorName($severity); - $string .= "$error "; - if (!is_null($line) && !is_null($col)) { - $string .= "Line $line, Column $col: "; - } else { - $string .= 'End of Document: '; - } - $string .= '' . $this->generator->escape($msg) . ' '; - $string .= '
'; - // Here, have a marker for the character on the column appropriate. - // Be sure to clip extremely long lines. - //$string .= '
';
-                //$string .= '';
-                //$string .= '
'; - $ret[] = $string; - } - foreach ($current->children as $array) { - $context[] = $current; - $stack = array_merge($stack, array_reverse($array, true)); - for ($i = count($array); $i > 0; $i--) { - $context_stack[] = $context; - } - } - } - } -} - - - - - -/** - * Records errors for particular segments of an HTML document such as tokens, - * attributes or CSS properties. They can contain error structs (which apply - * to components of what they represent), but their main purpose is to hold - * errors applying to whatever struct is being used. - */ -class HTMLPurifier_ErrorStruct -{ - - /** - * Possible values for $children first-key. Note that top-level structures - * are automatically token-level. - */ - const TOKEN = 0; - const ATTR = 1; - const CSSPROP = 2; - - /** - * Type of this struct. - * @type string - */ - public $type; - - /** - * Value of the struct we are recording errors for. There are various - * values for this: - * - 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(); - - /** - * 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(); - - /** - * @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; - } - return $this->children[$type][$id]; - } - - /** - * @param int $severity - * @param string $message - */ - public function addError($severity, $message) - { - $this->errors[] = array($severity, $message); - } -} - - - - - -/** - * Global exception class for HTML Purifier; any exceptions we throw - * are from here. - */ -class HTMLPurifier_Exception extends Exception -{ - -} - - - - - -/** - * Represents a pre or post processing filter on HTML Purifier's output - * - * Sometimes, a little ad-hoc fixing of HTML has to be done before - * it gets sent through HTML Purifier: you can use filters to acheive - * this effect. For instance, YouTube videos can be preserved using - * this manner. You could have used a decorator for this task, but - * PHP's support for them is not terribly robust, so we're going - * to just loop through the filters. - * - * Filters should be exited first in, last out. If there are three filters, - * named 1, 2 and 3, the order of execution should go 1->preFilter, - * 2->preFilter, 3->preFilter, purify, 3->postFilter, 2->postFilter, - * 1->postFilter. - * - * @note Methods are not declared abstract as it is perfectly legitimate - * for an implementation not to want anything to happen on a step - */ - -class HTMLPurifier_Filter -{ - - /** - * 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) - { - 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) - { - return $html; - } -} - - - - - -/** - * Generates HTML from tokens. - * @todo Refactor interface so that configuration/context is determined - * upon instantiation, no need for messy generateFromTokens() calls - * @todo Make some of the more internal functions protected, and have - * unit tests work around that - */ -class HTMLPurifier_Generator -{ - - /** - * 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 = $this->normalize($html, $config, $context); - - $cursor = 0; // our location in the text - $inside_tag = false; // whether or not we're parsing the inside of a tag - $array = array(); // result array - - // This is also treated to mean maintain *column* numbers too - $maintain_line_numbers = $config->get('Core.MaintainLineNumbers'); - - if ($maintain_line_numbers === null) { - // automatically determine line numbering by checking - // if error collection is on - $maintain_line_numbers = $config->get('Core.CollectErrors'); - } - - if ($maintain_line_numbers) { - $current_line = 1; - $current_col = 0; - $length = strlen($html); - } else { - $current_line = false; - $current_col = false; - $length = false; - } - $context->register('CurrentLine', $current_line); - $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 - $synchronize_interval = $config->get('Core.DirectLexLineNumberSyncInterval'); - - $e = false; - if ($config->get('Core.CollectErrors')) { - $e =& $context->get('ErrorCollector'); - } - - // for testing synchronization - $loops = 0; - - 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; - - // Column number is cheap, so we calculate it every round. - // We're interested at the *end* of the newline string, so - // we need to add strlen($nl) == 1 to $nl_pos before subtracting it - // from our "rcursor" position. - $nl_pos = strrpos($html, $nl, $rcursor - $length); - $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! - $current_line = 1 + $this->substrCount($html, $nl, 0, $cursor); - } - } - - $position_next_lt = strpos($html, '<', $cursor); - $position_next_gt = strpos($html, '>', $cursor); - - // triggers on "asdf" but not "asdf " - // special case to set up context - if ($position_next_lt === $cursor) { - $inside_tag = true; - $cursor++; - } - - 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 - ) - ) - ); - 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; - $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; - } - // Create Text of rest of string - $token = new - HTMLPurifier_Token_Text( - $this->parseData( - substr( - $html, - $cursor - ) - ) - ); - if ($maintain_line_numbers) { - $token->rawPosition($current_line, $current_col); - } - $array[] = $token; - break; - } elseif ($inside_tag && $position_next_gt !== false) { - // We are in tag and it is well formed - // Grab the internals of the tag - $strlen_segment = $position_next_gt - $cursor; - - if ($strlen_segment < 1) { - // there's nothing to process! - $token = new HTMLPurifier_Token_Text('<'); - $cursor++; - continue; - } - - $segment = substr($html, $cursor, $strlen_segment); - - if ($segment === false) { - // somehow, we attempted to access beyond the end of - // the string, defense-in-depth, reported by Nate Abele - break; - } - - // Check if it's a comment - 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'); - } - $position_comment_end = strlen($html); - $end = true; - } else { - $end = false; - } - $strlen_segment = $position_comment_end - $cursor; - $segment = substr($html, $cursor, $strlen_segment); - $token = new - 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); - } - $array[] = $token; - $cursor = $end ? $position_comment_end : $position_comment_end + 3; - $inside_tag = false; - continue; - } - - // Check if it's an end tag - $is_end_tag = (strpos($segment, '/') === 0); - if ($is_end_tag) { - $type = substr($segment, 1); - $token = new HTMLPurifier_Token_End($type); - if ($maintain_line_numbers) { - $token->rawPosition($current_line, $current_col); - $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); - } - $array[] = $token; - $inside_tag = false; - $cursor = $position_next_gt + 1; - continue; - } - - // Check leading character is alnum, if not, we may - // have accidently grabbed an emoticon. Translate into - // 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'); - } - $token = new HTMLPurifier_Token_Text('<'); - if ($maintain_line_numbers) { - $token->rawPosition($current_line, $current_col); - $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); - } - $array[] = $token; - $inside_tag = false; - continue; - } - - // Check if it is explicitly self closing, if so, remove - // 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); - if ($is_self_closing) { - $strlen_segment--; - $segment = substr($segment, 0, $strlen_segment); - } - - // Check if there are any attributes - $position_first_space = strcspn($segment, $this->_whitespace); - - if ($position_first_space >= $strlen_segment) { - if ($is_self_closing) { - $token = new HTMLPurifier_Token_Empty($segment); - } else { - $token = new HTMLPurifier_Token_Start($segment); - } - if ($maintain_line_numbers) { - $token->rawPosition($current_line, $current_col); - $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); - } - $array[] = $token; - $inside_tag = false; - $cursor = $position_next_gt + 1; - continue; - } - - // Grab out all the data - $type = substr($segment, 0, $position_first_space); - $attribute_string = - trim( - substr( - $segment, - $position_first_space - ) - ); - if ($attribute_string) { - $attr = $this->parseAttributeString( - $attribute_string, - $config, - $context - ); - } else { - $attr = array(); - } - - if ($is_self_closing) { - $token = new HTMLPurifier_Token_Empty($type, $attr); - } else { - $token = new HTMLPurifier_Token_Start($type, $attr); - } - if ($maintain_line_numbers) { - $token->rawPosition($current_line, $current_col); - $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); - } - $array[] = $token; - $cursor = $position_next_gt + 1; - $inside_tag = false; - continue; - } else { - // inside tag, but there's no ending > sign - 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); - } - // no cursor scroll? Hmm... - $array[] = $token; - break; - } - break; - } - - $context->destroy('CurrentLine'); - $context->destroy('CurrentCol'); - return $array; - } - - /** - * PHP 5.0.x compatible substr_count that implements offset and 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', '<'); - } - if ($oldVersion) { - $haystack = substr($haystack, $offset, $length); - return substr_count($haystack, $needle); - } else { - return substr_count($haystack, $needle, $offset, $length); - } - } - - /** - * Takes the inside of an HTML tag and makes an 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 - - if ($string == '') { - return array(); - } // no attributes - - $e = false; - if ($config->get('Core.CollectErrors')) { - $e =& $context->get('ErrorCollector'); - } - - // let's see if we can abort as quickly as possible - // one equal sign, no spaces => one attribute - $num_equal = substr_count($string, '='); - $has_space = strpos($string, ' '); - if ($num_equal === 0 && !$has_space) { - // bool attribute - return array($string => $string); - } elseif ($num_equal === 1 && !$has_space) { - // only one attribute - list($key, $quoted_value) = explode('=', $string); - $quoted_value = trim($quoted_value); - if (!$key) { - if ($e) { - $e->send(E_ERROR, 'Lexer: Missing attribute key'); - } - return array(); - } - if (!$quoted_value) { - return array($key => ''); - } - $first_char = @$quoted_value[0]; - $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) { - // 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'); - } - $value = substr($quoted_value, 1); - } else { - $value = $quoted_value; - } - } - if ($value === false) { - $value = ''; - } - return array($key => $this->parseData($value)); - } - - // setup loop environment - $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) - - // if we have unquoted attributes, the parser expects a terminating - // space, so let's guarantee that there's always a terminating space. - $string .= ' '; - - $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 - - $key_begin = $cursor; //we're currently at the start of the key - - // scroll past all characters that are the key (not whitespace or =) - $cursor += strcspn($string, $this->_whitespace . '=', $cursor); - - $key_end = $cursor; // now at the end of the key - - $key = substr($string, $key_begin, $key_end - $key_begin); - - if (!$key) { - if ($e) { - $e->send(E_ERROR, 'Lexer: Missing attribute key'); - } - $cursor += 1 + strcspn($string, $this->_whitespace, $cursor + 1); // prevent infinite loop - continue; // empty key - } - - // scroll past all whitespace - $cursor += strspn($string, $this->_whitespace, $cursor); - - if ($cursor >= $size) { - $array[$key] = $key; - break; - } - - // if the next character is an equal sign, we've got a regular - // pair, otherwise, it's a bool attribute - $first_char = @$string[$cursor]; - - if ($first_char == '=') { - // key="value" - - $cursor++; - $cursor += strspn($string, $this->_whitespace, $cursor); - - if ($cursor === false) { - $array[$key] = ''; - break; - } - - // we might be in front of a quote right now - - $char = @$string[$cursor]; - - if ($char == '"' || $char == "'") { - // it's quoted, end bound is $char - $cursor++; - $value_begin = $cursor; - $cursor = strpos($string, $char, $cursor); - $value_end = $cursor; - } else { - // it's not quoted, end bound is whitespace - $value_begin = $cursor; - $cursor += strcspn($string, $this->_whitespace, $cursor); - $value_end = $cursor; - } - - // we reached a premature end - if ($cursor === false) { - $cursor = $size; - $value_end = $cursor; - } - - $value = substr($string, $value_begin, $value_end - $value_begin); - 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'); - } - } - } - } - 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); - } -} - - - - - -/** - * Composite strategy that runs multiple strategies on tokens. - */ -abstract class HTMLPurifier_Strategy_Composite extends HTMLPurifier_Strategy -{ - - /** - * List of strategies to run tokens through. - * @type HTMLPurifier_Strategy[] - */ - protected $strategies = array(); - - /** - * @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; - } -} - - - - - -/** - * Core strategy composed of the big four strategies. - */ -class HTMLPurifier_Strategy_Core extends HTMLPurifier_Strategy_Composite -{ - 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(); - } -} - - - - - -/** - * Takes a well formed list of tokens and fixes their nesting. - * - * HTML elements dictate which elements are allowed to be their children, - * for example, you can't have a p tag in a span tag. Other elements have - * much more rigorous definitions: tables, for instance, require a specific - * order for their elements. There are also constraints not expressible by - * 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 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 - * task is pervasive through the strategy, though the two are distinct tasks - * and could, theoretically, be seperated (although it's not recommended). - * - * @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. This is - * easier with our new algorithm. - */ - -class HTMLPurifier_Strategy_FixNesting extends HTMLPurifier_Strategy -{ - - /** - * @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'); - - // 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 - // branch of the document tree - $is_inline = $definition->info_parent_def->descendants_are_inline; - $context->register('IsInline', $is_inline); - - // setup error collector - $e =& $context->get('ErrorCollector', true); - - //####################################################################// - // Loop initialization - - // 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($definition->info_parent_def->excludes); - - // variable that contains the start token while we are processing - // nodes. This enables error reporting to do its job - $node = $top_node; - // dummy token - list($token, $d) = $node->toTokenPair(); - $context->register('CurrentNode', $node); - $context->register('CurrentToken', $token); - - //####################################################################// - // Loop - - // 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) - ); - - 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; - } - }; - 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 { - // 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; - } - $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 { - $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'); - } - } - } - } - } - - //####################################################################// - // Post-processing - - // remove context variables - $context->destroy('IsInline'); - $context->destroy('CurrentNode'); - $context->destroy('CurrentToken'); - - //####################################################################// - // Return - - return HTMLPurifier_Arborize::flatten($node, $config, $context); - } -} - - - - - -/** - * Takes tokens makes them well-formed (balance end tags, etc.) - * - * Specification of the armor attributes this strategy uses: - * - * - MakeWellFormed_TagClosedError: This armor field is used to - * suppress tag closed errors for certain tokens [TagClosedSuppress], - * in particular, if a tag was generated automatically by HTML - * Purifier, we may rely on our infrastructure to close it for us - * and shouldn't report an error to the user [TagClosedAuto]. - */ -class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy -{ - - /** - * Array stream of tokens being processed. - * @type HTMLPurifier_Token[] - */ - protected $tokens; - - /** - * Current token. - * @type HTMLPurifier_Token - */ - protected $token; - - /** - * Zipper managing the true state. - * @type HTMLPurifier_Zipper - */ - 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; - - /** - * @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 = $definition->info_parent_def->child->getAllowedElements($config); - $e = $context->get('ErrorCollector', true); - $i = false; // injector index - 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->tokens =& $tokens; - $this->token =& $token; - $this->zipper =& $zipper; - $this->config = $config; - $this->context = $context; - - // context variables - $context->register('CurrentNesting', $stack); - $context->register('InputZipper', $zipper); - $context->register('CurrentToken', $token); - - // -- begin INJECTOR -- - - $this->injectors = array(); - - $injectors = $config->getBatch('AutoFormat'); - $def_injectors = $definition->info_injector; - $custom_injectors = $injectors['Custom']; - 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; - } - $injector = "HTMLPurifier_Injector_$injector"; - if (!$b) { - continue; - } - $this->injectors[] = new $injector; - } - foreach ($def_injectors as $injector) { - // assumed to be objects - $this->injectors[] = $injector; - } - foreach ($custom_injectors as $injector) { - if (!$injector) { - continue; - } - if (is_string($injector)) { - $injector = "HTMLPurifier_Injector_$injector"; - $injector = new $injector; - } - $this->injectors[] = $injector; - } - - // give the injectors references to the definition and context - // variables for performance reasons - foreach ($this->injectors as $ix => $injector) { - $error = $injector->prepare($config, $context); - 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); - } - - // -- end INJECTOR -- - - // a note on reprocessing: - // In order to reduce code duplication, whenever some code needs - // to make HTML changes in order to make things "correct", the - // new HTML gets sent through the purifier, regardless of its - // status. This means that if we add a start token, because it - // was totally necessary, we don't have to update nesting; we just - // punt ($reprocess = true; continue;) and it does that for us. - - // isset is in loop because $tokens size changes during loop exec - 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)) { - // 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_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($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 ($token === NULL) { - // kill processing if stack is empty - if (empty($this->stack)) { - break; - } - - // peek - $top_nesting = array_pop($this->stack); - $this->stack[] = $top_nesting; - - // send error [TagClosedSuppress] - if ($e && !isset($top_nesting->armor['MakeWellFormed_TagClosedError'])) { - $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by document end', $top_nesting); - } - - // append, don't splice, since this is the end - $token = new HTMLPurifier_Token_End($top_nesting->name); - - // punt! - $reprocess = true; - continue; - } - - //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; - } - // XXX fuckup - $r = $token; - $injector->handleText($r); - $token = $this->processToken($r, $i); - $reprocess = true; - break; - } - } - // another possibility is a comment - continue; - } - - if (isset($definition->info[$token->name])) { - $type = $definition->info[$token->name]->child->type; - } else { - $type = false; // Type is unknown, treat accordingly - } - - // quick tag checks: anything that's *not* an end tag - $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 - ); - $ok = true; - } elseif ($type && $type !== 'empty' && $token instanceof HTMLPurifier_Token_Empty) { - // claims to be empty but really is a start tag - // 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; - } elseif ($token instanceof HTMLPurifier_Token_Empty) { - // real empty token - $ok = true; - } elseif ($token instanceof HTMLPurifier_Token_Start) { - // start tag - - // ...unless they also have to close their parent - if (!empty($this->stack)) { - - // Performance note: you might think that it's rather - // inefficient, recalculating the autoclose information - // for every tag that a token closes (since when we - // do an autoclose, we push a new token into the - // stream and then /process/ that, before - // re-processing this token.) But this is - // necessary, because an injector can make an - // arbitrary transformations to the autoclosing - // tokens we introduce, so things may have changed - // in the meantime. Also, doing the inefficient thing is - // "easy" to reason about (for certain perverse definitions - // of "easy") - - $parent = array_pop($this->stack); - $this->stack[] = $parent; - - $parent_def = null; - $parent_elements = null; - $autoclose = false; - if (isset($definition->info[$parent->name])) { - $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 - // example,
      needs a
    • in between) - $wrapname = $definition->info[$token->name]->wrap; - $wrapdef = $definition->info[$wrapname]; - $elements = $wrapdef->child->getAllowedElements($config); - if (isset($elements[$token->name]) && isset($parent_elements[$wrapname])) { - $newtoken = new HTMLPurifier_Token_Start($wrapname); - $token = $this->insertBefore($newtoken); - $reprocess = true; - continue; - } - } - - $carryover = false; - if ($autoclose && $parent_def->formatting) { - $carryover = true; - } - - if ($autoclose) { - // check if this autoclose is doomed to fail - // (this rechecks $parent, which his harmless) - $autoclose_ok = isset($global_parent_allowed_elements[$token->name]); - if (!$autoclose_ok) { - foreach ($this->stack as $ancestor) { - $elements = $definition->info[$ancestor->name]->child->getAllowedElements($config); - if (isset($elements[$token->name])) { - $autoclose_ok = true; - break; - } - if ($definition->info[$token->name]->wrap) { - $wrapname = $definition->info[$token->name]->wrap; - $wrapdef = $definition->info[$wrapname]; - $wrap_elements = $wrapdef->child->getAllowedElements($config); - if (isset($wrap_elements[$token->name]) && isset($elements[$wrapname])) { - $autoclose_ok = true; - break; - } - } - } - } - if ($autoclose_ok) { - // errors need to be updated - $new_token = new HTMLPurifier_Token_End($parent->name); - $new_token->start = $parent; - // [TagClosedSuppress] - if ($e && !isset($parent->armor['MakeWellFormed_TagClosedError'])) { - if (!$carryover) { - $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag auto closed', $parent); - } else { - $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 { - $token = $this->remove(); - } - $reprocess = true; - continue; - } - - } - $ok = true; - } - - if ($ok) { - foreach ($this->injectors as $i => $injector) { - 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 - 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' - ); - } - } - continue; - } - - // sanity check: we should be dealing with a closing tag - if (!$token instanceof HTMLPurifier_Token_End) { - throw new HTMLPurifier_Exception('Unaccounted for tag token in input stream, bug in HTML Purifier'); - } - - // 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'); - } - $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token)); - } else { - if ($e) { - $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed'); - } - $token = $this->remove(); - } - $reprocess = true; - continue; - } - - // first, check for the simplest case: everything closes neatly. - // Eventually, everything passes through here; if there are problems - // we modify the input stream accordingly and then punt, so that - // the tokens get processed again. - $current_parent = array_pop($this->stack); - 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; - } - $r = $token; - $injector->handleEnd($r); - $token = $this->processToken($r, $i); - $this->stack[] = $current_parent; - $reprocess = true; - break; - } - continue; - } - - // okay, so we're trying to close the wrong tag - - // undo the pop previous pop - $this->stack[] = $current_parent; - - // scroll back the entire nest, trying to find our tag. - // (feature could be to specify how far you'd like to go) - $size = count($this->stack); - // -2 because -1 is the last element, but we already checked that - $skipped_tags = false; - for ($j = $size - 2; $j >= 0; $j--) { - if ($this->stack[$j]->name == $token->name) { - $skipped_tags = array_slice($this->stack, $j); - break; - } - } - - // we didn't find the tag, so remove - if ($skipped_tags === false) { - if ($escape_invalid_tags) { - if ($e) { - $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text'); - } - $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token)); - } else { - if ($e) { - $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed'); - } - $token = $this->remove(); - } - $reprocess = true; - continue; - } - - // do errors, in REVERSE $j order: a,b,c with - $c = count($skipped_tags); - if ($e) { - for ($j = $c - 1; $j > 0; $j--) { - // notice we exclude $j == 0, i.e. the current ending tag, from - // the errors... [TagClosedSuppress] - if (!isset($skipped_tags[$j]->armor['MakeWellFormed_TagClosedError'])) { - $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by element end', $skipped_tags[$j]); - } - } - } - - // insert tags, in FORWARD $j order: c,b,a with - $replace = array($token); - for ($j = 1; $j < $c; $j++) { - // ...as well as from the insertions - $new_token = new HTMLPurifier_Token_End($skipped_tags[$j]->name); - $new_token->start = $skipped_tags[$j]; - array_unshift($replace, $new_token); - if (isset($definition->info[$new_token->name]) && $definition->info[$new_token->name]->formatting) { - // [TagClosedAuto] - $element = clone $skipped_tags[$j]; - $element->carryover = true; - $element->armor['MakeWellFormed_TagClosedError'] = true; - $replace[] = $element; - } - } - $token = $this->processToken($replace); - $reprocess = true; - continue; - } - - $context->destroy('CurrentToken'); - $context->destroy('CurrentNesting'); - $context->destroy('InputZipper'); - - unset($this->injectors, $this->stack, $this->tokens); - return $zipper->toArray($token); - } - - /** - * Processes arbitrary token values for complicated substitution patterns. - * In general: - * - * If $token is an array, it is a list of tokens to substitute for the - * current token. These tokens then get individually processed. If there - * is a leading integer in the list, that integer determines how many - * tokens from the stream should be removed. - * - * If $token is a regular token, it is swapped with the current token. - * - * If $token is false, the current token is deleted. - * - * If $token is an integer, that number of tokens (with the first token - * being the current one) will be deleted. - * - * @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) - { - // 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'); - } - - // $token is now an array with the following form: - // array(number nodes to delete, new node 1, new node 2, ...) - - $delete = array_shift($token); - list($old, $r) = $this->zipper->splice($this->token, $delete, $token); - - if ($injector > -1) { - // determine appropriate skips - $oldskip = isset($old[0]) ? $old[0]->skip : array(); - foreach ($token as $object) { - $object->skip = $oldskip; - $object->skip[$injector] = true; - } - } - - 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) - { - // 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() - { - return $this->zipper->delete(); - } -} - - - - - -/** - * Removes all unrecognized tags from the list of tokens. - * - * This strategy iterates through all the tokens and removes unrecognized - * tokens. If a token is not recognized but a TagTransform is defined for - * that element, the element will be transformed accordingly. - */ - -class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy -{ - - /** - * @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'); - - // currently only used to determine if comments should be kept - $trusted = $config->get('HTML.Trusted'); - $comment_lookup = $config->get('HTML.AllowedComments'); - $comment_regexp = $config->get('HTML.AllowedCommentsRegexp'); - $check_comments = $comment_lookup !== array() || $comment_regexp !== null; - - $remove_script_contents = $config->get('Core.RemoveScriptContents'); - $hidden_elements = $config->get('Core.HiddenElements'); - - // remove script contents compatibility - if ($remove_script_contents === true) { - $hidden_elements['script'] = true; - } elseif ($remove_script_contents === false && isset($hidden_elements['script'])) { - unset($hidden_elements['script']); - } - - $attr_validator = new HTMLPurifier_AttrValidator(); - - // removes tokens until it reaches a closing tag with its value - $remove_until = false; - - // converts comments into text tokens when this is equal to a tag name - $textify_comments = false; - - $token = false; - $context->register('CurrentToken', $token); - - $e = false; - if ($config->get('Core.CollectErrors')) { - $e =& $context->get('ErrorCollector'); - } - - foreach ($tokens as $token) { - if ($remove_until) { - if (empty($token->is_tag) || $token->name !== $remove_until) { - continue; - } - } - if (!empty($token->is_tag)) { - // DEFINITION CALL - - // before any processing, try to transform the element - 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); - } - } - - 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) && - $definition->info[$token->name]->required_attr && - ($token->name != 'img' || $remove_invalid_img) // ensure config option still works - ) { - $attr_validator->validateToken($token, $config, $context); - $ok = true; - foreach ($definition->info[$token->name]->required_attr as $name) { - if (!isset($token->attr[$name])) { - $ok = false; - break; - } - } - if (!$ok) { - if ($e) { - $e->send( - E_ERROR, - 'Strategy_RemoveForeignElements: Missing required attribute', - $name - ); - } - continue; - } - $token->armor['ValidateAttributes'] = true; - } - - if (isset($hidden_elements[$token->name]) && $token instanceof HTMLPurifier_Token_Start) { - $textify_comments = $token->name; - } elseif ($token->name === $textify_comments && $token instanceof HTMLPurifier_Token_End) { - $textify_comments = false; - } - - } elseif ($escape_invalid_tags) { - // invalid tag, generate HTML representation and insert in - if ($e) { - $e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text'); - } - $token = new HTMLPurifier_Token_Text( - $generator->generateFromToken($token) - ); - } else { - // check if we need to destroy all of the tag's children - // CAN BE GENERICIZED - if (isset($hidden_elements[$token->name])) { - if ($token instanceof HTMLPurifier_Token_Start) { - $remove_until = $token->name; - } elseif ($token instanceof HTMLPurifier_Token_Empty) { - // do nothing: we're still looking - } else { - $remove_until = false; - } - if ($e) { - $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed'); - } - } else { - if ($e) { - $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed'); - } - } - continue; - } - } elseif ($token instanceof HTMLPurifier_Token_Comment) { - // textify comments in script tags when they are allowed - if ($textify_comments !== false) { - $data = $token->data; - $token = new HTMLPurifier_Token_Text($data); - } elseif ($trusted || $check_comments) { - // always cleanup comments - $trailing_hyphen = false; - if ($e) { - // perform check whether or not there's a trailing hyphen - if (substr($token->data, -1) == '-') { - $trailing_hyphen = true; - } - } - $token->data = rtrim($token->data, '-'); - $found_double_hyphen = false; - while (strpos($token->data, '--') !== false) { - $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)))) { - // OK good - if ($e) { - if ($trailing_hyphen) { - $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'); - } - } - } else { - if ($e) { - $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed'); - } - continue; - } - } else { - // strip comments - if ($e) { - $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed'); - } - continue; - } - } elseif ($token instanceof HTMLPurifier_Token_Text) { - } else { - continue; - } - $result[] = $token; - } - if ($remove_until && $e) { - // 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; - } -} - - - - - -/** - * Validate all attributes in the tokens. - */ - -class HTMLPurifier_Strategy_ValidateAttributes extends HTMLPurifier_Strategy -{ - - /** - * @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(); - - $token = false; - $context->register('CurrentToken', $token); - - foreach ($tokens as $key => $token) { - - // only process tokens that have attributes, - // namely start and empty tags - if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty) { - continue; - } - - // skip tokens that are armored - if (!empty($token->armor['ValidateAttributes'])) { - continue; - } - - // note that we have no facilities here for removing tokens - $validator->validateToken($token, $config, $context); - } - $context->destroy('CurrentToken'); - return $tokens; - } -} - - - - - -/** - * Transforms FONT tags to the proper form (SPAN with CSS styling) - * - * This transformation takes the three proprietary attributes of FONT and - * transforms them into their corresponding CSS attributes. These are color, - * face, and size. - * - * @note Size is an interesting case because it doesn't map cleanly to CSS. - * Thanks to - * http://style.cleverchimp.com/font_size_intervals/altintervals.html - * for reasonable mappings. - * @warning This doesn't work completely correctly; specifically, this - * TagTransform operates before well-formedness is enforced, so - * the "active formatting elements" algorithm doesn't get applied. - */ -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', - '2' => 'small', - '3' => 'medium', - '4' => 'large', - '5' => 'x-large', - '6' => 'xx-large', - '7' => '300%', - '-1' => 'smaller', - '-2' => '60%', - '+1' => 'larger', - '+2' => '150%', - '+3' => '200%', - '+4' => '300%' - ); - - /** - * @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; - return $new_tag; - } - - $attr = $tag->attr; - $prepend_style = ''; - - // handle color transform - if (isset($attr['color'])) { - $prepend_style .= 'color:' . $attr['color'] . ';'; - unset($attr['color']); - } - - // handle face transform - if (isset($attr['face'])) { - $prepend_style .= 'font-family:' . $attr['face'] . ';'; - unset($attr['face']); - } - - // handle size transform - if (isset($attr['size'])) { - // 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'; - } - } else { - $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']] . ';'; - } - unset($attr['size']); - } - - if ($prepend_style) { - $attr['style'] = isset($attr['style']) ? - $prepend_style . $attr['style'] : - $prepend_style; - } - - $new_tag = clone $tag; - $new_tag->name = $this->transform_to; - $new_tag->attr = $attr; - - return $new_tag; - } -} - - - - - -/** - * Simple transformation, just change tag name to something else, - * and possibly add some styling. This will cover most of the deprecated - * tag cases. - */ -class HTMLPurifier_TagTransform_Simple extends HTMLPurifier_TagTransform -{ - /** - * @type string - */ - protected $style; - - /** - * @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) - { - $this->transform_to = $transform_to; - $this->style = $style; - } - - /** - * @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) && - ($new_tag instanceof HTMLPurifier_Token_Start || $new_tag instanceof HTMLPurifier_Token_Empty) - ) { - $this->prependCSS($new_tag->attr, $this->style); - } - return $new_tag; - } -} - - - - - -/** - * Concrete comment token class. Generally will be ignored. - */ -class HTMLPurifier_Token_Comment extends HTMLPurifier_Token -{ - /** - * 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 toNode() { - return new HTMLPurifier_Node_Comment($this->data, $this->line, $this->col); - } -} - - - - - -/** - * Abstract class of a tag token (start, end or empty), and its behavior. - */ -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; - - /** - * 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 tag's attributes. - * @type array - */ - public $attr = array(); - - /** - * Non-overloaded constructor, which lower-cases passed tag name. - * - * @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 - if (!ctype_lower($key)) { - $new_key = strtolower($key); - if (!isset($attr[$new_key])) { - $attr[$new_key] = $attr[$key]; - } - if ($new_key !== $key) { - unset($attr[$key]); - } - } - } - $this->attr = $attr; - $this->line = $line; - $this->col = $col; - $this->armor = $armor; - } - - public function toNode() { - return new HTMLPurifier_Node_Element($this->name, $this->attr, $this->line, $this->col, $this->armor); - } -} - - - - - -/** - * Concrete empty token class. - */ -class HTMLPurifier_Token_Empty extends HTMLPurifier_Token_Tag -{ - public function toNode() { - $n = parent::toNode(); - $n->empty = true; - return $n; - } -} - - - - - -/** - * Concrete end token class. - * - * @warning This class accepts attributes even though end tags cannot. This - * is for optimization reasons, as under normal circumstances, the Lexers - * do not pass attributes. - */ -class HTMLPurifier_Token_End extends HTMLPurifier_Token_Tag -{ - /** - * 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!"); - } -} - - - - - -/** - * Concrete start token class. - */ -class HTMLPurifier_Token_Start extends HTMLPurifier_Token_Tag -{ -} - - - - - -/** - * 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_Token_Text extends HTMLPurifier_Token -{ - - /** - * @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 string $data String parsed character data. - * @param int $line - * @param int $col - */ - public function __construct($data, $line = null, $col = null) - { - $this->data = $data; - $this->is_whitespace = ctype_space($data); - $this->line = $line; - $this->col = $col; - } - - public function toNode() { - return new HTMLPurifier_Node_Text($this->data, $this->is_whitespace, $this->line, $this->col); - } -} - - - - - -class HTMLPurifier_URIFilter_DisableExternal extends HTMLPurifier_URIFilter -{ - /** - * @type string - */ - public $name = 'DisableExternal'; - - /** - * @type array - */ - protected $ourHostParts = false; - - /** - * @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)); - } - } - - /** - * @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; - } - } - return true; - } -} - - - - - -class HTMLPurifier_URIFilter_DisableExternalResources extends HTMLPurifier_URIFilter_DisableExternal -{ - /** - * @type string - */ - public $name = 'DisableExternalResources'; - - /** - * @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); - } -} - - - - - -class HTMLPurifier_URIFilter_DisableResources extends HTMLPurifier_URIFilter -{ - /** - * @type string - */ - public $name = 'DisableResources'; - - /** - * @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); - } -} - - - - - -// It's not clear to me whether or not Punycode means that hostnames -// do not have canonical forms anymore. As far as I can tell, it's -// not a problem (punycoding should be identity when no Unicode -// 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(); - - /** - * @param HTMLPurifier_Config $config - * @return bool - */ - public function prepare($config) - { - $this->blacklist = $config->get('URI.HostBlacklist'); - return true; - } - - /** - * @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; - } - } - return true; - } -} - - - - - -// does not support network paths - -class HTMLPurifier_URIFilter_MakeAbsolute extends HTMLPurifier_URIFilter -{ - /** - * @type string - */ - public $name = 'MakeAbsolute'; - - /** - * @type - */ - protected $base; - - /** - * @type array - */ - protected $basePathStack = array(); - - /** - * @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 - ); - return false; - } - $this->base->fragment = null; // fragment is invalid for base URI - $stack = explode('/', $this->base->path); - array_pop($stack); // discard last segment - $stack = $this->_collapseStack($stack); // do pre-parsing - $this->basePathStack = $stack; - return true; - } - - /** - * @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; - } - $scheme_obj = $uri->getSchemeObj($config, $context); - if (!$scheme_obj) { - // scheme not recognized - return false; - } - if (!$scheme_obj->hierarchical) { - // non-hierarchal URI with explicit scheme, don't change - return true; - } - // special case: had a scheme but always is hierarchical and had no authority - } - if (!is_null($uri->host)) { - // network path, don't bother - return true; - } - if ($uri->path === '') { - $uri->path = $this->base->path; - } elseif ($uri->path[0] !== '/') { - // relative path, needs more complicated processing - $stack = explode('/', $uri->path); - $new_stack = array_merge($this->basePathStack, $stack); - if ($new_stack[0] !== '' && !is_null($this->base->host)) { - array_unshift($new_stack, ''); - } - $new_stack = $this->_collapseStack($new_stack); - $uri->path = implode('/', $new_stack); - } else { - // absolute path, but still we should collapse - $uri->path = implode('/', $this->_collapseStack(explode('/', $uri->path))); - } - // 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; - } - return true; - } - - /** - * Resolve dots and double-dots in a path stack - * @param array $stack - * @return array - */ - 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] == '..') { - if (!empty($result)) { - $segment = array_pop($result); - if ($segment === '' && empty($result)) { - // error case: attempted to back out too far: - // restore the leading slash - $result[] = ''; - } elseif ($segment === '..') { - $result[] = '..'; // cannot remove .. with .. - } - } else { - // relative path, preserve the double-dots - $result[] = '..'; - } - $is_folder = true; - continue; - } - if ($stack[$i] == '.') { - // silently absorb - $is_folder = true; - continue; - } - $result[] = $stack[$i]; - } - if ($is_folder) { - $result[] = ''; - } - return $result; - } -} - - - - - -class HTMLPurifier_URIFilter_Munge extends HTMLPurifier_URIFilter -{ - /** - * @type string - */ - public $name = 'Munge'; - - /** - * @type bool - */ - public $post = true; - - /** - * @type string - */ - private $target; - - /** - * @type HTMLPurifier_URIParser - */ - private $parser; - - /** - * @type bool - */ - private $doEmbed; - - /** - * @type string - */ - private $secretKey; - - /** - * @type array - */ - protected $replace = array(); - - /** - * @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; - } - - /** - * @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 - - $this->makeReplace($uri, $config, $context); - $this->replace = array_map('rawurlencode', $this->replace); - - $new_uri = strtr($this->target, $this->replace); - $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; - } - $uri = $new_uri; // overwrite - return true; - } - - /** - * @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; - $this->replace['%r'] = $context->get('EmbeddedURI', true); - $token = $context->get('CurrentToken', true); - $this->replace['%n'] = $token ? $token->name : null; - $this->replace['%m'] = $context->get('CurrentAttr', true); - $this->replace['%p'] = $context->get('CurrentCSSProperty', true); - // not always available - if ($this->secretKey) { - $this->replace['%t'] = hash_hmac("sha256", $string, $this->secretKey); - } - } -} - - - - - -/** - * Implements safety checks for safe iframes. - * - * @warning This filter is *critical* for ensuring that %HTML.SafeIframe - * works safely. - */ -class HTMLPurifier_URIFilter_SafeIframe extends HTMLPurifier_URIFilter -{ - /** - * @type string - */ - public $name = 'SafeIframe'; - - /** - * @type bool - */ - public $always_load = true; - - /** - * @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. - /** - * @param HTMLPurifier_Config $config - * @return bool - */ - public function prepare($config) - { - $this->regexp = $config->get('URI.SafeIframeRegexp'); - return true; - } - - /** - * @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; - } - // check if the filter should actually trigger - if (!$context->get('EmbeddedURI', true)) { - return true; - } - $token = $context->get('CurrentToken', true); - if (!($token && $token->name == 'iframe')) { - return true; - } - // check if we actually have some whitelists enabled - if ($this->regexp === null) { - return false; - } - // actually check the whitelists - return preg_match($this->regexp, $uri->toString()); - } -} - - - - - -/** - * Implements data: URI for base64 encoded images supported by GD. - */ -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; - - /** - * @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; - $content_type = null; - if (count($result) == 2) { - list($metadata, $data) = $result; - // do some legwork on the metadata - $metas = explode(';', $metadata); - while (!empty($metas)) { - $cur = array_shift($metas); - if ($cur == 'base64') { - $is_base64 = true; - break; - } - if (substr($cur, 0, 8) == 'charset=') { - // doesn't match if there are arbitrary spaces, but - // whatever dude - if ($charset !== null) { - continue; - } // garbage - $charset = substr($cur, 8); // not used - } else { - if ($content_type !== null) { - continue; - } // garbage - $content_type = $cur; - } - } - } else { - $data = $result[0]; - } - if ($content_type !== null && empty($this->allowed_types[$content_type])) { - return false; - } - if ($charset !== null) { - // error; we don't allow plaintext stuff - $charset = null; - } - $data = rawurldecode($data); - if ($is_base64) { - $raw_data = base64_decode($data); - } else { - $raw_data = $data; - } - // XXX probably want to refactor this into a general mechanism - // for filtering arbitrary content types - $file = tempnam("/tmp", ""); - file_put_contents($file, $raw_data); - if (function_exists('exif_imagetype')) { - $image_code = exif_imagetype($file); - unlink($file); - } elseif (function_exists('getimagesize')) { - set_error_handler(array($this, 'muteErrorHandler')); - $info = getimagesize($file); - restore_error_handler(); - unlink($file); - if ($info == false) { - return false; - } - $image_code = $info[2]; - } else { - trigger_error("could not find exif_imagetype or getimagesize functions", E_USER_ERROR); - } - $real_content_type = image_type_to_mime_type($image_code); - 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; - } - $content_type = $real_content_type; - } - // ok, it's kosher, rewrite what we need - $uri->userinfo = null; - $uri->host = null; - $uri->port = null; - $uri->fragment = null; - $uri->query = null; - $uri->path = "$content_type;base64," . base64_encode($raw_data); - return true; - } - - /** - * @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. - * @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. - * @type bool - */ - public $may_omit_host = true; - - /** - * @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; - // While it seems to work on Firefox, the querystring has - // no possible effect and is thus stripped. - $uri->query = null; - return true; - } -} - - - - - -/** - * Validates ftp (File Transfer Protocol) URIs as defined by generic RFC 1738. - */ -class HTMLPurifier_URIScheme_ftp extends HTMLPurifier_URIScheme -{ - /** - * @type int - */ - public $default_port = 21; - - /** - * @type bool - */ - public $browsable = true; // usually - - /** - * @type bool - */ - public $hierarchical = true; - - /** - * @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 - if ($semicolon_pos !== false) { - $type = substr($uri->path, $semicolon_pos + 1); // no semicolon - $uri->path = substr($uri->path, 0, $semicolon_pos); - $type_ret = ''; - if (strpos($type, '=') !== false) { - // figure out whether or not the declaration is correct - list($key, $typecode) = explode('=', $type, 2); - if ($key !== 'type') { - // invalid key, tack it back on encoded - $uri->path .= '%3B' . $type; - } elseif ($typecode === 'a' || $typecode === 'i' || $typecode === 'd') { - $type_ret = ";type=$typecode"; - } - } else { - $uri->path .= '%3B' . $type; - } - $uri->path = str_replace(';', '%3B', $uri->path); - $uri->path .= $type_ret; - } - return true; - } -} - - - - - -/** - * Validates http (HyperText Transfer Protocol) as defined by RFC 2616 - */ -class HTMLPurifier_URIScheme_http extends HTMLPurifier_URIScheme -{ - /** - * @type int - */ - public $default_port = 80; - - /** - * @type bool - */ - public $browsable = true; - - /** - * @type bool - */ - public $hierarchical = true; - - /** - * @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; - } -} - - - - - -/** - * Validates https (Secure HTTP) according to http scheme. - */ -class HTMLPurifier_URIScheme_https extends HTMLPurifier_URIScheme_http -{ - /** - * @type int - */ - public $default_port = 443; - /** - * @type bool - */ - public $secure = true; -} - - - - - -// VERY RELAXED! Shouldn't cause problems, not even Firefox checks if the -// email is valid, but be careful! - -/** - * Validates mailto (for E-mail) according to RFC 2368 - * @todo Validate the email address - * @todo Filter allowed query parameters - */ - -class HTMLPurifier_URIScheme_mailto extends HTMLPurifier_URIScheme -{ - /** - * @type bool - */ - public $browsable = false; - - /** - * @type bool - */ - public $may_omit_host = true; - - /** - * @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; - } -} - - - - - -/** - * Validates news (Usenet) as defined by generic RFC 1738 - */ -class HTMLPurifier_URIScheme_news extends HTMLPurifier_URIScheme -{ - /** - * @type bool - */ - public $browsable = false; - - /** - * @type bool - */ - public $may_omit_host = true; - - /** - * @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; - // typecode check needed on path - return true; - } -} - - - - - -/** - * Validates nntp (Network News Transfer Protocol) as defined by generic RFC 1738 - */ -class HTMLPurifier_URIScheme_nntp extends HTMLPurifier_URIScheme -{ - /** - * @type int - */ - public $default_port = 119; - - /** - * @type bool - */ - public $browsable = false; - - /** - * @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; - return true; - } -} - - - - - -/** - * Performs safe variable parsing based on types which can be used by - * users. This may not be able to represent all possible data inputs, - * however. - */ -class HTMLPurifier_VarParser_Flexible extends HTMLPurifier_VarParser -{ - /** - * @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: - return $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; - } - return $var; - case self::BOOL: - if (is_int($var) && ($var === 0 || $var === 1)) { - $var = (bool)$var; - } elseif (is_string($var)) { - if ($var == 'on' || $var == 'true' || $var == '1') { - $var = true; - } elseif ($var == 'off' || $var == 'false' || $var == '0') { - $var = false; - } else { - throw new HTMLPurifier_VarParserException("Unrecognized value '$var' for $type"); - } - } - return $var; - 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 (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); - } else { - $var = preg_split('/(,|[\n\r]+)/', $var); - } - // remove spaces - 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; - } - $nvar[trim($c[0])] = trim($c[1]); - } - $var = $nvar; - } - } - if (!is_array($var)) { - break; - } - $keys = array_keys($var); - if ($keys === array_keys($keys)) { - if ($type == self::ALIST) { - return $var; - } elseif ($type == self::LOOKUP) { - $new = array(); - foreach ($var as $key) { - $new[$key] = true; - } - return $new; - } else { - break; - } - } - if ($type === self::ALIST) { - trigger_error("Array list did not have consecutive integer indexes", E_USER_WARNING); - return array_values($var); - } - 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 - ); - } - $var[$key] = true; - } - } - return $var; - default: - $this->errorInconsistent(__CLASS__, $type); - } - $this->errorGeneric($var, $type); - } -} - - - - - -/** - * This variable parser uses PHP's internal code engine. Because it does - * this, it can represent all inputs; however, it is dangerous and cannot - * be used by users. - */ -class HTMLPurifier_VarParser_Native extends HTMLPurifier_VarParser -{ - - /** - * @param mixed $var - * @param int $type - * @param bool $allow_null - * @return null|string - */ - protected function parseImplementation($var, $type, $allow_null) - { - return $this->evalExpression($var); - } - - /** - * @param string $expr - * @return mixed - * @throws HTMLPurifier_VarParserException - */ - protected function evalExpression($expr) - { - $var = null; - $result = eval("\$var = $expr;"); - if ($result === false) { - throw new HTMLPurifier_VarParserException("Fatal error in evaluated code"); - } - return $var; - } -} - +config = HTMLPurifier_Config::create($config); + $this->strategy = new HTMLPurifier_Strategy_Core(); + } + + /** + * Adds a filter to process the output. First come first serve + * + * @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 + ); + $this->filters[] = $filter; + } + + /** + * Filters an HTML snippet/document to be XSS-free and standards-compliant. + * + * @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 string Purified HTML + */ + public function purify($html, $config = null) + { + // :TODO: make the config merge in, instead of replace + $config = $config ? HTMLPurifier_Config::create($config) : $this->config; + + // implementation is partially environment dependant, partially + // configuration dependant + $lexer = HTMLPurifier_Lexer::create($config); + + $context = new HTMLPurifier_Context(); + + // setup HTML generator + $this->generator = new HTMLPurifier_Generator($config, $context); + $context->register('Generator', $this->generator); + + // set up global context variables + if ($config->get('Core.CollectErrors')) { + // may get moved out if other facilities use it + $language_factory = HTMLPurifier_LanguageFactory::instance(); + $language = $language_factory->create($config, $context); + $context->register('Locale', $language); + + $error_collector = new HTMLPurifier_ErrorCollector($context); + $context->register('ErrorCollector', $error_collector); + } + + // setup id_accumulator context, necessary due to the fact that + // AttrValidator can be called from many places + $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context); + $context->register('IDAccumulator', $id_accumulator); + + $html = HTMLPurifier_Encoder::convertToUTF8($html, $config, $context); + + // setup filters + $filter_flags = $config->getBatch('Filter'); + $custom_filters = $filter_flags['Custom']; + unset($filter_flags['Custom']); + $filters = array(); + foreach ($filter_flags as $filter => $flag) { + if (!$flag) { + continue; + } + if (strpos($filter, '.') !== false) { + continue; + } + $class = "HTMLPurifier_Filter_$filter"; + $filters[] = new $class; + } + foreach ($custom_filters as $filter) { + // maybe "HTMLPurifier_Filter_$filter", but be consistent with AutoFormat + $filters[] = $filter; + } + $filters = array_merge($filters, $this->filters); + // maybe prepare(), but later + + for ($i = 0, $filter_size = count($filters); $i < $filter_size; $i++) { + $html = $filters[$i]->preFilter($html, $config, $context); + } + + // purified HTML + $html = + $this->generator->generateFromTokens( + // list of tokens + $this->strategy->execute( + // list of un-purified tokens + $lexer->tokenizeHTML( + // un-purified HTML + $html, + $config, + $context + ), + $config, + $context + ) + ); + + for ($i = $filter_size - 1; $i >= 0; $i--) { + $html = $filters[$i]->postFilter($html, $config, $context); + } + + $html = HTMLPurifier_Encoder::convertFromUTF8($html, $config, $context); + $this->context =& $context; + return $html; + } + + /** + * Filters an array of HTML snippets + * + * @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 string[] Array of purified HTML + */ + 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); + $context_array[$key] = $this->context; + } + $this->context = $context_array; + return $array_of_html; + } + + /** + * 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 + */ + public static function instance($prototype = null) + { + if (!self::$instance || $prototype) { + if ($prototype instanceof HTMLPurifier) { + self::$instance = $prototype; + } elseif ($prototype) { + self::$instance = new HTMLPurifier($prototype); + } else { + self::$instance = new HTMLPurifier(); + } + } + return self::$instance; + } + + /** + * 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) + { + 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 + */ + +class HTMLPurifier_AttrCollections +{ + + /** + * Associative array of attribute collections, indexed by name. + * @type array + */ + public $info = array(); + + /** + * Performs all expansions on internal data for use by other inclusions + * It also collects all attribute collection extensions from + * modules + * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance + * @param HTMLPurifier_HTMLModule[] $modules Hash array of HTMLPurifier_HTMLModule members + */ + public function __construct($attr_types, $modules) + { + // load extensions from the modules + foreach ($modules as $module) { + foreach ($module->attr_collections as $coll_i => $coll) { + if (!isset($this->info[$coll_i])) { + $this->info[$coll_i] = array(); + } + foreach ($coll as $attr_i => $attr) { + 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 + ); + continue; + } + $this->info[$coll_i][$attr_i] = $attr; + } + } + } + // perform internal expansions and inclusions + foreach ($this->info as $name => $attr) { + // merge attribute collections that include others + $this->performInclusions($this->info[$name]); + // replace string identifiers with actual attribute objects + $this->expandIdentifiers($this->info[$name], $attr_types); + } + } + + /** + * Takes a reference to an attribute associative array and performs + * all inclusions specified by the zero index. + * @param array &$attr Reference to attribute array + */ + 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; + } + $seen[$merge[$i]] = true; + // foreach attribute of the inclusion, copy it over + if (!isset($this->info[$merge[$i]])) { + continue; + } + foreach ($this->info[$merge[$i]] as $key => $value) { + if (isset($attr[$key])) { + continue; + } // also catches more inclusions + $attr[$key] = $value; + } + if (isset($this->info[$merge[$i]][0])) { + // recursion + $merge = array_merge($merge, $this->info[$merge[$i]][0]); + } + } + unset($attr[0]); + } + + /** + * Expands all string identifiers in an attribute array by replacing + * them with the appropriate values inside HTMLPurifier_AttrTypes + * @param array &$attr Reference to attribute array + * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance + */ + 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 (isset($processed[$def_i])) { + continue; + } + + // determine whether or not attribute is required + if ($required = (strpos($def_i, '*') !== false)) { + // rename the definition + unset($attr[$def_i]); + $def_i = trim($def_i, '*'); + $attr[$def_i] = $def; + } + + $processed[$def_i] = true; + + // if we've already got a literal object, move on + if (is_object($def)) { + // preserve previous required + $attr[$def_i]->required = ($required || $attr[$def_i]->required); + continue; + } + + if ($def === false) { + unset($attr[$def_i]); + continue; + } + + if ($t = $attr_types->get($def)) { + $attr[$def_i] = $t; + $attr[$def_i]->required = $required; + } else { + unset($attr[$def_i]); + } + } + } +} + + + + + +/** + * Base class for all validating attribute definitions. + * + * This family of classes forms the core for not only HTML attribute validation, + * but also any sort of string that needs to be validated or cleaned (which + * means CSS properties and composite definitions are defined here too). + * Besides defining (through code) what precisely makes the string valid, + * subclasses are also responsible for cleaning the code if possible. + */ + +abstract class HTMLPurifier_AttrDef +{ + + /** + * 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 + * @type bool + */ + public $required = false; + + /** + * Validates and cleans passed string according to a definition. + * + * @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); + + /** + * Convenience method that parses a string as if it were CDATA. + * + * This method process a string in the manner specified at + * by removing + * leading and trailing whitespace, ignoring line feeds, and replacing + * carriage returns and tabs with spaces. While most useful for HTML + * attributes specified as CDATA, it can also be applied to most CSS + * values. + * + * @note This method is not entirely standards compliant, as trim() removes + * more types of whitespace than specified in the spec. In practice, + * this is rarely a problem, as those extra characters usually have + * already been removed by HTMLPurifier_Encoder. + * + * @warning This processing is inconsistent with XML's whitespace handling + * as specified by section 3.3.3 and referenced XHTML 1.0 section + * 4.7. However, note that we are NOT necessarily + * parsing XML, thus, this behavior may still be correct. We + * assume that newlines have been normalized. + */ + public function parseCDATA($string) + { + $string = trim($string); + $string = str_replace(array("\n", "\t", "\r"), ' ', $string); + return $string; + } + + /** + * Factory method for creating this class from a string. + * @param string $string String construction info + * @return HTMLPurifier_AttrDef Created AttrDef object corresponding to $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 + // to clone or instantiate new copies. (Instantiation is safer.) + return $this; + } + + /** + * 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) + { + 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" + * version of it. + */ + protected function expandCSSEscape($string) + { + // flexibly parse it + $ret = ''; + for ($i = 0, $c = strlen($string); $i < $c; $i++) { + if ($string[$i] === '\\') { + $i++; + if ($i >= $c) { + $ret .= '\\'; + break; + } + if (ctype_xdigit($string[$i])) { + $code = $string[$i]; + for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) { + 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; + } + $ret .= $char; + if ($i < $c && trim($string[$i]) !== '') { + $i--; + } + continue; + } + if ($string[$i] === "\n") { + continue; + } + } + $ret .= $string[$i]; + } + return $ret; + } +} + + + + + +/** + * Processes an entire attribute array for corrections needing multiple values. + * + * Occasionally, a certain attribute will need to be removed and popped onto + * another value. Instead of creating a complex return syntax for + * HTMLPurifier_AttrDef, we just pass the whole attribute array to a + * specialized object and have that do the special work. That is the + * family of HTMLPurifier_AttrTransform. + * + * An attribute transformation can be assigned to run before or after + * HTMLPurifier_AttrDef validation. See HTMLPurifier_HTMLDefinition for + * more details. + */ + +abstract class HTMLPurifier_AttrTransform +{ + + /** + * Abstract: makes changes to the attributes dependent on multiple values. + * + * @param array $attr Assoc array of attributes, usually from + * HTMLPurifier_Token_Tag::$attr + * @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 array &$attr Attribute array to process (passed by reference) + * @param string $css CSS to prepend + */ + public function prependCSS(&$attr, $css) + { + $attr['style'] = isset($attr['style']) ? $attr['style'] : ''; + $attr['style'] = $css . $attr['style']; + } + + /** + * Retrieves and removes an attribute + * @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; + } + $value = $attr[$key]; + unset($attr[$key]); + return $value; + } +} + + + + + +/** + * Provides lookup array of attribute types to HTMLPurifier_AttrDef objects + */ +class HTMLPurifier_AttrTypes +{ + /** + * Lookup array of attribute string identifiers to concrete implementations. + * @type HTMLPurifier_AttrDef[] + */ + protected $info = array(); + + /** + * Constructs the info array, supplying default implementations for attribute + * types. + */ + 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. + // With the old implementation of Enum, that ignored its + // arguments when handling a make dispatch, the IAlign + // definition wouldn't work. + + // pseudo-types, must be instantiated via shorthand + $this->info['Enum'] = new HTMLPurifier_AttrDef_Enum(); + $this->info['Bool'] = new HTMLPurifier_AttrDef_HTML_Bool(); + + $this->info['CDATA'] = new HTMLPurifier_AttrDef_Text(); + $this->info['ID'] = new HTMLPurifier_AttrDef_HTML_ID(); + $this->info['Length'] = new HTMLPurifier_AttrDef_HTML_Length(); + $this->info['MultiLength'] = new HTMLPurifier_AttrDef_HTML_MultiLength(); + $this->info['NMTOKENS'] = new HTMLPurifier_AttrDef_HTML_Nmtokens(); + $this->info['Pixels'] = new HTMLPurifier_AttrDef_HTML_Pixels(); + $this->info['Text'] = new HTMLPurifier_AttrDef_Text(); + $this->info['URI'] = new HTMLPurifier_AttrDef_URI(); + $this->info['LanguageCode'] = new HTMLPurifier_AttrDef_Lang(); + $this->info['Color'] = new HTMLPurifier_AttrDef_HTML_Color(); + $this->info['IAlign'] = self::makeEnum('top,middle,bottom,left,right'); + $this->info['LAlign'] = self::makeEnum('top,bottom,left,right'); + $this->info['FrameTarget'] = new HTMLPurifier_AttrDef_HTML_FrameTarget(); + + // unimplemented aliases + $this->info['ContentType'] = new HTMLPurifier_AttrDef_Text(); + $this->info['ContentTypes'] = new HTMLPurifier_AttrDef_Text(); + $this->info['Charsets'] = new HTMLPurifier_AttrDef_Text(); + $this->info['Character'] = new HTMLPurifier_AttrDef_Text(); + + // "proprietary" types + $this->info['Class'] = new HTMLPurifier_AttrDef_HTML_Class(); + + // number is really a positive integer (one or more digits) + // FIXME: ^^ not always, see start and value of list items + $this->info['Number'] = new HTMLPurifier_AttrDef_Integer(false, false, true); + } + + private static function makeEnum($in) + { + return new HTMLPurifier_AttrDef_Clone(new HTMLPurifier_AttrDef_Enum(explode(',', $in))); + } + + /** + * Retrieves a type + * @param string $type String type name + * @return HTMLPurifier_AttrDef Object AttrDef for 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 (!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 string $type String type name + * @param HTMLPurifier_AttrDef $impl Object AttrDef for type + */ + public function set($type, $impl) + { + $this->info[$type] = $impl; + } +} + + + + + +/** + * Validates the attributes of a token. Doesn't manage required attributes + * very well. The only reason we factored this out was because RemoveForeignElements + * also needed it besides ValidateAttributes. + */ +class HTMLPurifier_AttrValidator +{ + + /** + * Validates the attributes of a token, mutating it as necessary. + * that has valid tokens + * @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) + { + $definition = $config->getHTMLDefinition(); + $e =& $context->get('ErrorCollector', true); + + // initialize IDAccumulator if necessary + $ok =& $context->get('IDAccumulator', true); + if (!$ok) { + $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context); + $context->register('IDAccumulator', $id_accumulator); + } + + // initialize CurrentToken if necessary + $current_token =& $context->get('CurrentToken', true); + if (!$current_token) { + $context->register('CurrentToken', $token); + } + + if (!$token instanceof HTMLPurifier_Token_Start && + !$token instanceof HTMLPurifier_Token_Empty + ) { + return; + } + + // create alias to global definition array, see also $defs + // DEFINITION CALL + $d_defs = $definition->info_global_attr; + + // don't update token until the very end, to ensure an atomic update + $attr = $token->attr; + + // do global transformations (pre) + // nothing currently utilizes this + 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); + } + } + } + + // do local transformations only applicable to this element (pre) + // ex.

      to

      + 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); + } + } + } + + // create alias to this element's attribute definition array, see + // also $d_defs (global attribute definition array) + // DEFINITION CALL + $defs = $definition->info[$token->name]->attr; + + $attr_key = false; + $context->register('CurrentAttr', $attr_key); + + // iterate through all the attribute keypairs + // Watch out for name collisions: $key has previously been used + foreach ($attr as $attr_key => $value) { + + // call the definition + 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. + // This is usually when there's a global definition + // that must be overridden. + // Theoretically speaking, we could have a + // AttrDef_DenyAll, but this is faster! + $result = false; + } else { + // validate according to the element's definition + $result = $defs[$attr_key]->validate( + $value, + $config, + $context + ); + } + } 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 + ); + } else { + // system never heard of the attribute? DELETE! + $result = false; + } + + // put the results into effect + 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'); + } + + // remove the attribute + unset($attr[$attr_key]); + } elseif (is_string($result)) { + // generally, if a substitution is happening, there + // was some sort of implicit correction going on. We'll + // delegate it to the attribute classes to say exactly what. + + // simple substitution + $attr[$attr_key] = $result; + } else { + // nothing happens + } + + // we'd also want slightly more complicated substitution + // involving an array as the return value, + // although we're not sure how colliding attributes would + // resolve (certain ones would be completely overriden, + // others would prepend themselves). + } + + $context->destroy('CurrentAttr'); + + // post transforms + + // global (error reporting untested) + 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); + } + } + } + + // local (error reporting untested) + 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); + } + } + } + + $token->attr = $attr; + + // destroy CurrentToken if we made it ourselves + if (!$current_token) { + $context->destroy('CurrentToken'); + } + + } + + +} + + + + + +// constants are slow, so we use as few as possible +if (!defined('HTMLPURIFIER_PREFIX')) { + define('HTMLPURIFIER_PREFIX', dirname(__FILE__) . '/standalone'); + set_include_path(HTMLPURIFIER_PREFIX . PATH_SEPARATOR . get_include_path()); +} + +// accomodations for versions earlier than 5.0.2 +// borrowed from PHP_Compat, LGPL licensed, by Aidan Lister +if (!defined('PHP_EOL')) { + switch (strtoupper(substr(PHP_OS, 0, 3))) { + case 'WIN': + define('PHP_EOL', "\r\n"); + break; + case 'DAR': + define('PHP_EOL', "\r"); + break; + default: + define('PHP_EOL', "\n"); + } +} + +/** + * Bootstrap class that contains meta-functionality for HTML Purifier such as + * the autoload function. + * + * @note + * This class may be used without any other files from HTML Purifier. + */ +class HTMLPurifier_Bootstrap +{ + + /** + * Autoload function for HTML Purifier + * @param string $class Class to load + * @return bool + */ + public static function autoload($class) + { + $file = HTMLPurifier_Bootstrap::getPath($class); + 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 + // may be broken. Since we have efficient alternatives, pay + // the cost here and avoid the bug. + require_once HTMLPURIFIER_PREFIX . '/' . $file; + return true; + } + + /** + * 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; + } + // Custom implementations + if (strncmp('HTMLPurifier_Language_', $class, 22) === 0) { + $code = str_replace('_', '-', substr($class, 22)); + $file = 'HTMLPurifier/Language/classes/' . $code . '.php'; + } else { + $file = str_replace('_', '/', $class) . '.php'; + } + if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) { + return false; + } + return $file; + } + + /** + * "Pre-registers" our autoloader on the SPL stack. + */ + public static function registerAutoload() + { + $autoload = array('HTMLPurifier_Bootstrap', 'autoload'); + if (($funcs = spl_autoload_functions()) === false) { + spl_autoload_register($autoload); + } elseif (function_exists('spl_autoload_unregister')) { + if (version_compare(PHP_VERSION, '5.3.0', '>=')) { + // prepend flag exists, no need for shenanigans + spl_autoload_register($autoload, true, true); + } else { + $buggy = version_compare(PHP_VERSION, '5.2.11', '<'); + $compat = version_compare(PHP_VERSION, '5.1.2', '<=') && + version_compare(PHP_VERSION, '5.1.0', '>='); + foreach ($funcs as $func) { + if ($buggy && is_array($func)) { + // :TRICKY: There are some compatibility issues and some + // 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 + 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.' + ); + } + // Suprisingly, spl_autoload_register supports the + // Class::staticMethod callback format, although call_user_func doesn't + if ($compat) { + $func = implode('::', $func); + } + } + spl_autoload_unregister($func); + } + spl_autoload_register($autoload); + foreach ($funcs as $func) { + spl_autoload_register($func); + } + } + } + } +} + + + + + +/** + * Super-class for definition datatype objects, implements serialization + * functions for the class. + */ +abstract class HTMLPurifier_Definition +{ + + /** + * Has setup() been called yet? + * @type bool + */ + public $setup = false; + + /** + * If true, write out the final definition object to the cache after + * setup. This will be true only if all invocations to get a raw + * definition object are also optimized. This does not cause file + * system thrashing because on subsequent calls the cached object + * 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 HTMLPurifier_Config $config + */ + abstract protected function doSetup($config); + + /** + * Setup function that aborts if already setup + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + if ($this->setup) { + return; + } + $this->setup = true; + $this->doSetup($config); + } +} + + + + + +/** + * Defines allowed CSS attributes and what their values are. + * @see HTMLPurifier_HTMLDefinition + */ +class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition +{ + + public $type = 'CSS'; + + /** + * 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) + { + $this->info['text-align'] = new HTMLPurifier_AttrDef_Enum( + 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-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style); + + $this->info['clear'] = new HTMLPurifier_AttrDef_Enum( + array('none', 'left', 'right', 'both'), + false + ); + $this->info['float'] = new HTMLPurifier_AttrDef_Enum( + array('none', 'left', 'right'), + false + ); + $this->info['font-style'] = new HTMLPurifier_AttrDef_Enum( + array('normal', 'italic', 'oblique'), + false + ); + $this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum( + array('normal', 'small-caps'), + false + ); + + $uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('none')), + new HTMLPurifier_AttrDef_CSS_URI() + ) + ); + + $this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum( + 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 + ); + $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 + ); + $this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color(); + + $this->info['background-image'] = $uri_or_none; + $this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum( + array('repeat', 'repeat-x', 'repeat-y', 'no-repeat') + ); + $this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum( + array('scroll', 'fixed') + ); + $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['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-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) + ) + ); + + $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'] = 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'] = 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() + ) + ); + + $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 + ); + + $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration(); + + $this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily(); + + // 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 + ); + + // MUST be called after other font properties, as it references + // a CSSDefinition object + $this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config); + + // same here + $this->info['border'] = + $this->info['border-bottom'] = + $this->info['border-top'] = + $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['caption-side'] = new HTMLPurifier_AttrDef_Enum( + array('top', 'bottom') + ); + + $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['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') + ); + + if ($config->get('CSS.Proprietary')) { + $this->doSetupProprietary($config); + } + + if ($config->get('CSS.AllowTricky')) { + $this->doSetupTricky($config); + } + + if ($config->get('CSS.Trusted')) { + $this->doSetupTrusted($config); + } + + $allow_important = $config->get('CSS.AllowImportant'); + // wrap all attr-defs with decorator that handles !important + foreach ($this->info as $k => $v) { + $this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important); + } + + $this->setupConfigStuff($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(); + + // 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(); + + // 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')); + + } + + /** + * @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')); + } + + /** + * @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')), + ) + ); + } + + /** + * 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) + { + // setup allowed elements + $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]); + } + unset($allowed_properties[$name]); + } + // emit errors + foreach ($allowed_properties as $name => $d) { + // :TODO: Is this htmlspecialchars() call really necessary? + $name = htmlspecialchars($name); + trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING); + } + } + + $forbidden_properties = $config->get('CSS.ForbiddenProperties'); + if ($forbidden_properties !== null) { + foreach ($this->info as $name => $d) { + if (isset($forbidden_properties[$name])) { + unset($this->info[$name]); + } + } + } + } +} + + + + + +/** + * 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; + + /** + * 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. + * @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) + { + return $this->elements; + } + + /** + * Validates nodes according to definition and returns modification. + * + * @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); +} + + + + + +/** + * Configuration object that triggers customizable behavior. + * + * @warning This class is strongly defined: that means that the class + * will fail if an undefined directive is retrieved or set. + * + * @note Many classes that could (although many times don't) use the + * configuration object make it a mandatory parameter. This is + * because a configuration object should always be forwarded, + * otherwise, you run the risk of missing a parameter and then + * being stumped when a configuration directive doesn't work. + * + * @todo Reconsider some of the public member variables + */ +class HTMLPurifier_Config +{ + + /** + * HTML Purifier's version + * @type string + */ + public $version = '4.6.0'; + + /** + * 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. + * @type string[] + */ + protected $serials = array(); + + /** + * Serial for entire configuration object. + * @type string + */ + protected $serial; + + /** + * Parser for variables. + * @type HTMLPurifier_VarParser_Flexible + */ + protected $parser = null; + + /** + * 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. + * @type HTMLPurifier_Definition[] + */ + protected $definitions; + + /** + * 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. + * @type bool + */ + private $aliasMode; + + /** + * 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; + + /** + * Constructor + * @param HTMLPurifier_ConfigSchema $definition ConfigSchema that defines + * what directives are allowed. + * @param HTMLPurifier_PropertyList $parent + */ + 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 + $this->parser = new HTMLPurifier_VarParser_Flexible(); + } + + /** + * Convenience constructor that creates a config object based on a mixed var + * @param mixed $config Variable that defines the state of the config + * 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 Schema object + * @return HTMLPurifier_Config Configured object + */ + public static function create($config, $schema = null) + { + if ($config instanceof HTMLPurifier_Config) { + // pass-through + return $config; + } + if (!$schema) { + $ret = HTMLPurifier_Config::createDefault(); + } else { + $ret = new HTMLPurifier_Config($schema); + } + 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. + * @return HTMLPurifier_Config object with $config as its parent. + */ + public static function inherit(HTMLPurifier_Config $config) + { + return new HTMLPurifier_Config($config->def, $config->plist); + } + + /** + * Convenience constructor that creates a default configuration object. + * @return HTMLPurifier_Config default object. + */ + public static function createDefault() + { + $definition = HTMLPurifier_ConfigSchema::instance(); + $config = new HTMLPurifier_Config($definition); + return $config; + } + + /** + * Retrieves a value from the configuration. + * + * @param string $key String key + * @param mixed $a + * + * @return mixed + */ + public function get($key, $a = null) + { + if ($a !== null) { + $this->triggerError( + "Using deprecated API: use \$config->get('$key.$a') instead", + E_USER_WARNING + ); + $key = "$key.$a"; + } + 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 + ); + 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 + ); + 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 + ); + return; + } + } + return $this->plist->get($key); + } + + /** + * 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(); + } + $full = $this->getAll(); + if (!isset($full[$namespace])) { + $this->triggerError( + 'Cannot retrieve undefined namespace ' . + htmlspecialchars($namespace), + E_USER_WARNING + ); + return; + } + return $full[$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! + */ + public function getBatchSerial($namespace) + { + if (empty($this->serials[$namespace])) { + $batch = $this->getBatch($namespace); + unset($batch['DefinitionRev']); + $this->serials[$namespace] = sha1(serialize($batch)); + } + return $this->serials[$namespace]; + } + + /** + * Returns a SHA-1 signature for the entire configuration object + * that uniquely identifies that particular configuration + * + * @return string + */ + public function getSerial() + { + if (empty($this->serial)) { + $this->serial = sha1(serialize($this->getAll())); + } + return $this->serial; + } + + /** + * 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(); + } + $ret = array(); + foreach ($this->plist->squash() as $name => $value) { + list($ns, $key) = explode('.', $name, 2); + $ret[$ns][$key] = $value; + } + return $ret; + } + + /** + * Sets a value to configuration. + * + * @param string $key key + * @param mixed $value value + * @param mixed $a + */ + public function set($key, $value, $a = null) + { + if (strpos($key, '.') === false) { + $namespace = $key; + $directive = $value; + $value = $a; + $key = "$key.$directive"; + $this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE); + } else { + list($namespace) = explode('.', $key); + } + 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 + ); + 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 + ); + return; + } + $this->aliasMode = true; + $this->set($def->key, $value); + $this->aliasMode = false; + $this->triggerError("$key is an alias, preferred directive name is {$def->key}", E_USER_NOTICE); + return; + } + + // Raw type might be negative when using the fully optimized form + // of stdclass, which indicates allow_null == true + $rtype = is_int($def) ? $def : $def->type; + if ($rtype < 0) { + $type = -$rtype; + $allow_null = true; + } else { + $type = $rtype; + $allow_null = isset($def->allow_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 + ); + return; + } + if (is_string($value) && is_object($def)) { + // resolve value alias if defined + if (isset($def->aliases[$value])) { + $value = $def->aliases[$value]; + } + // 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 + ); + return; + } + } + $this->plist->set($key, $value); + + // reset definitions if the directives they depend on changed + // this is a very costly process, so it's discouraged + // with finalization + if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') { + $this->definitions[$namespace] = null; + } + + $this->serials[$namespace] = false; + } + + /** + * Convenience function for error reporting + * + * @param array $lookup + * + * @return string + */ + private function _listify($lookup) + { + $list = array(); + foreach ($lookup as $name => $b) { + $list[] = $name; + } + return implode(', ', $list); + } + + /** + * Retrieves object reference to the HTML definition. + * + * @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 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) + { + return $this->getDefinition('HTML', $raw, $optimized); + } + + /** + * Retrieves object reference to the CSS definition + * + * @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 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) + { + return $this->getDefinition('CSS', $raw, $optimized); + } + + /** + * Retrieves object reference to the URI definition + * + * @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 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) + { + return $this->getDefinition('URI', $raw, $optimized); + } + + /** + * Retrieves a definition + * + * @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 + * way in order to ensure that caching is done properly. + * 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) + { + if ($optimized && !$raw) { + throw new HTMLPurifier_Exception("Cannot set optimized = true when raw = false"); + } + if (!$this->finalized) { + $this->autoFinalize(); + } + // temporarily suspend locks, so we can handle recursive definition calls + $lock = $this->lock; + $this->lock = null; + $factory = HTMLPurifier_DefinitionCacheFactory::instance(); + $cache = $factory->create($type, $this); + $this->lock = $lock; + if (!$raw) { + // full definition + // --------------- + // check if definition is in memory + if (!empty($this->definitions[$type])) { + $def = $this->definitions[$type]; + // check if the definition is setup + if ($def->setup) { + return $def; + } else { + $def->setup($this); + if ($def->optimized) { + $cache->add($def, $this); + } + return $def; + } + } + // check if definition is in cache + $def = $cache->get($this); + if ($def) { + // definition in cache, save to memory and return it + $this->definitions[$type] = $def; + return $def; + } + // initialize it + $def = $this->initDefinition($type); + // set it up + $this->lock = $type; + $def->setup($this); + $this->lock = null; + // save in cache + $cache->add($def, $this); + // return it + return $def; + } else { + // raw definition + // -------------- + // check preconditions + $def = null; + 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" + ); + } + } + 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 + ); + } + if ($def->optimized === null) { + $extra = $this->chatty ? " (try flushing your cache)" : ""; + 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 + ); + } + } + // check if definition was in memory + if ($def) { + if ($def->setup) { + // invariant: $optimized === true (checked above) + return null; + } else { + return $def; + } + } + // if optimized, check if definition was in cache + // (because we do the memory check first, this formulation + // is prone to cache slamming, but I think + // guaranteeing that either /all/ of the raw + // setup code or /none/ of it is run is more important.) + if ($optimized) { + // This code path only gets run once; once we put + // something in $definitions (which is guaranteed by the + // trailing code), we always short-circuit above. + $def = $cache->get($this); + if ($def) { + // save the full definition for later, but don't + // return it yet + $this->definitions[$type] = $def; + return null; + } + } + // check invariants for creation + 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 + ); + } else { + $this->triggerError( + "Useless DefinitionID declaration", + E_USER_WARNING + ); + } + } + } + // initialize it + $def = $this->initDefinition($type); + $def->optimized = $optimized; + return $def; + } + throw new HTMLPurifier_Exception("The impossible happened!"); + } + + /** + * 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(); + } elseif ($type == 'CSS') { + $def = new HTMLPurifier_CSSDefinition(); + } elseif ($type == 'URI') { + $def = new HTMLPurifier_URIDefinition(); + } else { + throw new HTMLPurifier_Exception( + "Definition of $type type not supported" + ); + } + $this->definitions[$type] = $def; + return $def; + } + + public function maybeGetRawDefinition($name) + { + return $this->getDefinition($name, true, true); + } + + public function maybeGetRawHTMLDefinition() + { + return $this->getDefinition('HTML', true, true); + } + + public function maybeGetRawCSSDefinition() + { + return $this->getDefinition('CSS', true, true); + } + + public function maybeGetRawURIDefinition() + { + return $this->getDefinition('URI', true, true); + } + + /** + * Loads configuration values from an array with the following structure: + * Namespace.Directive => Value + * + * @param array $config_array Configuration associative array + */ + 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) { + $this->set($key, $value); + } else { + $namespace = $key; + $namespace_values = $value; + foreach ($namespace_values as $directive => $value2) { + $this->set($namespace .'.'. $directive, $value2); + } + } + } + } + + /** + * 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 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) + { + 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; + } + } + } + $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($def->isAlias)) { + continue; + } + if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') { + continue; + } + $ret[] = array($ns, $directive); + } + return $ret; + } + + /** + * Loads configuration values from $_GET/$_POST that were posted + * via ConfigForm + * + * @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) + { + $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema); + $config = HTMLPurifier_Config::create($ret, $schema); + return $config; + } + + /** + * Merges in configuration values from $_GET/$_POST to object. NOT STATIC. + * + * @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) + { + $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def); + $this->loadArray($ret); + } + + /** + * 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(); + } + $mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc(); + + $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema); + $ret = array(); + foreach ($allowed as $key) { + list($ns, $directive) = $key; + $skey = "$ns.$directive"; + if (!empty($array["Null_$skey"])) { + $ret[$ns][$directive] = null; + continue; + } + if (!isset($array[$skey])) { + continue; + } + $value = $mq ? stripslashes($array[$skey]) : $array[$skey]; + $ret[$ns][$directive] = $value; + } + return $ret; + } + + /** + * Loads configuration values from an ini file + * + * @param string $filename Name of ini file + */ + 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 string|bool $error String error message, or false for no error + * + * @return bool + */ + public function isFinalized($error = false) + { + if ($this->finalized && $error) { + $this->triggerError($error, E_USER_ERROR); + } + return $this->finalized; + } + + /** + * Finalizes configuration only if auto finalize is on and not + * already finalized + */ + public function autoFinalize() + { + if ($this->autoFinalize) { + $this->finalize(); + } else { + $this->plist->squash(true); + } + } + + /** + * Finalizes a configuration object, prohibiting further change + */ + public function finalize() + { + $this->finalized = true; + $this->parser = null; + } + + /** + * 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) + { + // determine previous stack frame + $extra = ''; + if ($this->chatty) { + $trace = debug_backtrace(); + // zip(tail(trace), trace) -- but PHP is not Haskell har har + for ($i = 0, $c = count($trace); $i < $c - 1; $i++) { + // XXX this is not correct on some versions of HTML Purifier + if ($trace[$i + 1]['class'] === 'HTMLPurifier_Config') { + continue; + } + $frame = $trace[$i]; + $extra = " invoked on line {$frame['line']} in file {$frame['file']}"; + break; + } + } + trigger_error($msg . $extra, $no); + } + + /** + * Returns a serialized form of the configuration object that can + * be reconstituted. + * + * @return string + */ + public function serialize() + { + $this->getDefinition('HTML'); + $this->getDefinition('CSS'); + $this->getDefinition('URI'); + return serialize($this); + } + +} + + + + + +/** + * Configuration definition, defines directives and their defaults. + */ +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: + * + * array( + * 'Namespace' => array( + * 'Directive' => new stdclass(), + * ) + * ) + * + * The stdclass may have the following properties: + * + * - If isAlias isn't set: + * - type: Integer type of directive, see HTMLPurifier_VarParser for definitions + * - allow_null: If set, this directive allows null values + * - aliases: If set, an associative array of value aliases to real values + * - allowed: If set, a lookup array of allowed (string) values + * - If isAlias is set: + * - namespace: Namespace this directive aliases to + * - name: Directive name this directive aliases to + * + * In certain degenerate cases, stdclass will actually be an integer. In + * that case, the value is equivalent to an stdclass with the type + * property set to the integer. If the integer is negative, type is + * equal to the absolute value of integer, and allow_null is true. + * + * 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 + */ + protected static $singleton; + + public function __construct() + { + $this->defaultPlist = new HTMLPurifier_PropertyList(); + } + + /** + * Unserializes the default ConfigSchema. + * @return HTMLPurifier_ConfigSchema + */ + public static function makeFromSerial() + { + $contents = file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema.ser'); + $r = unserialize($contents); + if (!$r) { + $hash = sha1($contents); + trigger_error("Unserialization of configuration schema failed, sha1 of file was $hash", E_USER_ERROR); + } + return $r; + } + + /** + * Retrieves an instance of the application-wide configuration definition. + * @param HTMLPurifier_ConfigSchema $prototype + * @return HTMLPurifier_ConfigSchema + */ + public static function instance($prototype = null) + { + if ($prototype !== null) { + HTMLPurifier_ConfigSchema::$singleton = $prototype; + } elseif (HTMLPurifier_ConfigSchema::$singleton === null || $prototype === true) { + HTMLPurifier_ConfigSchema::$singleton = HTMLPurifier_ConfigSchema::makeFromSerial(); + } + return HTMLPurifier_ConfigSchema::$singleton; + } + + /** + * Defines a directive for configuration + * @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 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 bool $allow_null Whether or not to allow null values + */ + 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; + } + $this->info[$key] = $obj; + $this->defaults[$key] = $default; + $this->defaultPlist->set($key, $default); + } + + /** + * Defines a directive value alias. + * + * Directive value aliases are convenient for developers because it lets + * them set a directive to several values and get the same result. + * @param string $key Name of Directive + * @param array $aliases Hash of aliased values to the real alias + */ + public function addValueAliases($key, $aliases) + { + if (!isset($this->info[$key]->aliases)) { + $this->info[$key]->aliases = array(); + } + foreach ($aliases as $alias => $real) { + $this->info[$key]->aliases[$alias] = $real; + } + } + + /** + * Defines a set of allowed values for a directive. + * @warning This is slightly different from the corresponding static + * method definition. + * @param string $key Name of directive + * @param array $allowed Lookup array of allowed values + */ + public function addAllowedValues($key, $allowed) + { + $this->info[$key]->allowed = $allowed; + } + + /** + * Defines a directive alias for backwards compatibility + * @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) + { + $obj = new stdclass; + $obj->key = $new_key; + $obj->isAlias = true; + $this->info[$key] = $obj; + } + + /** + * Replaces any stdclass that only has the type property with type integer. + */ + public function postProcess() + { + foreach ($this->info as $key => $v) { + if (count((array) $v) == 1) { + $this->info[$key] = $v->type; + } elseif (count((array) $v) == 2 && isset($v->allow_null)) { + $this->info[$key] = -$v->type; + } + } + } +} + + + + + +/** + * @todo Unit test + */ +class HTMLPurifier_ContentSets +{ + + /** + * 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). + * @type array + */ + protected $keys = array(); + /** + * 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 HTMLPurifier_HTMLModule[] $modules List of HTMLPurifier_HTMLModule + */ + 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) { + foreach ($module->content_sets as $key => $value) { + $temp = $this->convertToLookup($value); + if (isset($this->lookup[$key])) { + // add it into the existing content set + $this->lookup[$key] = array_merge($this->lookup[$key], $temp); + } else { + $this->lookup[$key] = $temp; + } + } + } + $old_lookup = false; + while ($old_lookup !== $this->lookup) { + $old_lookup = $this->lookup; + foreach ($this->lookup as $i => $set) { + $add = array(); + foreach ($set as $element => $x) { + if (isset($this->lookup[$element])) { + $add += $this->lookup[$element]; + unset($this->lookup[$i][$element]); + } + } + $this->lookup[$i] += $add; + } + } + + foreach ($this->lookup as $key => $lookup) { + $this->info[$key] = implode(' | ', array_keys($lookup)); + } + $this->keys = array_keys($this->info); + $this->values = array_values($this->info); + } + + /** + * Accepts a definition; generates and assigns a ChildDef for it + * @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)) { // already done! + return; + } + $content_model = $def->content_model; + if (is_string($content_model)) { + // Assume that $this->keys is alphanumeric + $def->content_model = preg_replace_callback( + '/\b(' . implode('|', $this->keys) . ')\b/', + array($this, 'generateChildDefCallback'), + $content_model + ); + //$def->content_model = str_replace( + // $this->keys, $this->values, $content_model); + } + $def->child = $this->getChildDef($def, $module); + } + + public function generateChildDefCallback($matches) + { + return $this->info[$matches[0]]; + } + + /** + * Instantiates a ChildDef based on content_model and content_model_type + * member variables in HTMLPurifier_ElementDef + * @note This will also defer to modules for custom HTMLPurifier_ChildDef + * subclasses that need content set expansion + * @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) + { + $value = $def->content_model; + if (is_object($value)) { + trigger_error( + 'Literal object child definitions should be stored in '. + 'ElementDef->child not ElementDef->content_model', + E_USER_NOTICE + ); + return $value; + } + switch ($def->content_model_type) { + case 'required': + return new HTMLPurifier_ChildDef_Required($value); + case 'optional': + return new HTMLPurifier_ChildDef_Optional($value); + case 'empty': + return new HTMLPurifier_ChildDef_Empty(); + case 'custom': + return new HTMLPurifier_ChildDef_Custom($value); + } + // defer to its module + $return = false; + if ($module->defines_child_def) { // save a func call + $return = $module->getChildDef($def); + } + if ($return !== false) { + return $return; + } + // error-out + trigger_error( + 'Could not determine which ChildDef class to instantiate', + E_USER_ERROR + ); + return false; + } + + /** + * Converts a string list of elements separated by pipes into + * a lookup array. + * @param string $string List of elements + * @return array Lookup array of elements + */ + protected function convertToLookup($string) + { + $array = explode('|', str_replace(' ', '', $string)); + $ret = array(); + foreach ($array as $k) { + $ret[$k] = true; + } + return $ret; + } +} + + + + + +/** + * Registry object that contains information about the current context. + * @warning Is a bit buggy when variables are set to null: it thinks + * they don't exist! So use false instead, please. + * @note Since the variables Context deals with may not be objects, + * references are very important here! Do not remove! + */ +class HTMLPurifier_Context +{ + + /** + * Private array that stores the references. + * @type array + */ + private $_storage = array(); + + /** + * Registers a variable into the context. + * @param string $name String name + * @param mixed $ref Reference to variable to be registered + */ + 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; + } + + /** + * Retrieves a variable reference from the context. + * @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 (!array_key_exists($name, $this->_storage)) { + if (!$ignore_error) { + trigger_error( + "Attempted to retrieve non-existent variable $name", + E_USER_ERROR + ); + } + $var = null; // so we can return by reference + return $var; + } + return $this->_storage[$name]; + } + + /** + * Destroys a variable in the context. + * @param string $name String name + */ + 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]); + } + + /** + * Checks whether or not the variable exists. + * @param string $name String name + * @return bool + */ + public function exists($name) + { + return array_key_exists($name, $this->_storage); + } + + /** + * Loads a series of variables from an associative array + * @param array $context_array Assoc array of variables to load + */ + public function loadArray($context_array) + { + foreach ($context_array as $key => $discard) { + $this->register($key, $context_array[$key]); + } + } +} + + + + + +/** + * Abstract class representing Definition cache managers that implements + * useful common methods and is a factory. + * @todo Create a separate maintenance file advanced users can use to + * cache their custom HTMLDefinition, which can be loaded + * via a configuration directive + * @todo Implement memcached + */ +abstract class HTMLPurifier_DefinitionCache +{ + /** + * @type string + */ + public $type; + + /** + * @param string $type Type of definition objects this instance of the + * cache will handle. + */ + public function __construct($type) + { + $this->type = $type; + } + + /** + * Generates a unique identifier for a particular configuration + * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config + * @return string + */ + public function generateKey($config) + { + return $config->version . ',' . // possibly replace with function calls + $config->getBatchSerial($this->type) . ',' . + $config->get($this->type . '.DefinitionRev'); + } + + /** + * Tests whether or not a key is old with respect to the configuration's + * version and revision number. + * @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; + } + list($version, $hash, $revision) = explode(',', $key, 3); + $compare = version_compare($version, $config->version); + // version mismatch, is always old + 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; + } + return false; + } + + /** + * Checks if a definition's type jives with the cache's type + * @note Throws an error on failure + * @param HTMLPurifier_Definition $def Definition object to check + * @return bool true if good, false if not + */ + 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; + } + return true; + } + + /** + * 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); + + /** + * Clears all expired (older version or revision) objects from cache + * @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); +} + + + + + +/** + * Responsible for creating definition caches. + */ +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() + { + $this->addDecorator('Cleanup'); + } + + /** + * Retrieves an instance of global definition cache factory. + * @param HTMLPurifier_DefinitionCacheFactory $prototype + * @return HTMLPurifier_DefinitionCacheFactory + */ + public static function instance($prototype = null) + { + static $instance; + if ($prototype !== null) { + $instance = $prototype; + } elseif ($instance === null || $prototype === true) { + $instance = new HTMLPurifier_DefinitionCacheFactory(); + $instance->setup(); + } + return $instance; + } + + /** + * Registers a new definition cache object + * @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) + { + $this->implementations[$short] = $long; + } + + /** + * Factory method that creates a cache object based on configuration + * @param string $type Name of definitions handled by cache + * @param HTMLPurifier_Config $config Config instance + * @return mixed + */ + public function create($type, $config) + { + $method = $config->get('Cache.DefinitionImpl'); + if ($method === null) { + return new HTMLPurifier_DefinitionCache_Null($type); + } + if (!empty($this->caches[$method][$type])) { + return $this->caches[$method][$type]; + } + if (isset($this->implementations[$method]) && + class_exists($class = $this->implementations[$method], false)) { + $cache = new $class($type); + } else { + if ($method != 'Serializer') { + trigger_error("Unrecognized DefinitionCache $method, using Serializer instead", E_USER_WARNING); + } + $cache = new HTMLPurifier_DefinitionCache_Serializer($type); + } + foreach ($this->decorators as $decorator) { + $new_cache = $decorator->decorate($cache); + // prevent infinite recursion in PHP 4 + unset($cache); + $cache = $new_cache; + } + $this->caches[$method][$type] = $cache; + return $this->caches[$method][$type]; + } + + /** + * Registers a decorator to add to all new cache objects + * @param HTMLPurifier_DefinitionCache_Decorator|string $decorator An instance or the name of a decorator + */ + public function addDecorator($decorator) + { + if (is_string($decorator)) { + $class = "HTMLPurifier_DefinitionCache_Decorator_$decorator"; + $decorator = new $class; + } + $this->decorators[$decorator->name] = $decorator; + } +} + + + + + +/** + * Represents a document type, contains information on which modules + * need to be loaded. + * @note This class is inspected by Printer_HTMLDefinition->renderDoctype. + * If structure changes, please update that function. + */ +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 + ) { + $this->name = $name; + $this->xml = $xml; + $this->modules = $modules; + $this->tidyModules = $tidyModules; + $this->aliases = $aliases; + $this->dtdPublic = $dtd_public; + $this->dtdSystem = $dtd_system; + } +} + + + + + +class HTMLPurifier_DoctypeRegistry +{ + + /** + * Hash of doctype names to doctype objects. + * @type array + */ + protected $doctypes; + + /** + * Lookup table of aliases to real doctype names. + * @type array + */ + protected $aliases; + + /** + * Registers a doctype to the registry + * @note Accepts a fully-formed doctype object, or the + * parameters for constructing a doctype object + * @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_object($doctype)) { + $doctype = new HTMLPurifier_Doctype( + $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; + } + $this->aliases[$alias] = $name; + } + // remove old aliases + if (isset($this->aliases[$name])) { + unset($this->aliases[$name]); + } + return $doctype; + } + + /** + * Retrieves reference to a doctype of a certain name + * @note This function resolves aliases + * @note When possible, use the more fully-featured make() + * @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]; + } + if (!isset($this->doctypes[$doctype])) { + trigger_error('Doctype ' . htmlspecialchars($doctype) . ' does not exist', E_USER_ERROR); + $anon = new HTMLPurifier_Doctype($doctype); + return $anon; + } + return $this->doctypes[$doctype]; + } + + /** + * Creates a doctype based on a configuration object, + * will perform initialization on the doctype + * @note Use this function to get a copy of doctype that config + * 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) + { + return clone $this->get($this->getDoctypeFromConfig($config)); + } + + /** + * Retrieves the doctype from the configuration object + * @param HTMLPurifier_Config $config + * @return string + */ + public function getDoctypeFromConfig($config) + { + // recommended test + $doctype = $config->get('HTML.Doctype'); + if (!empty($doctype)) { + return $doctype; + } + $doctype = $config->get('HTML.CustomDoctype'); + if (!empty($doctype)) { + return $doctype; + } + // backwards-compatibility + if ($config->get('HTML.XHTML')) { + $doctype = 'XHTML 1.0'; + } else { + $doctype = 'HTML 4.01'; + } + if ($config->get('HTML.Strict')) { + $doctype .= ' Strict'; + } else { + $doctype .= ' Transitional'; + } + return $doctype; + } +} + + + + + +/** + * Structure that stores an HTML element definition. Used by + * HTMLPurifier_HTMLDefinition and HTMLPurifier_HTMLModule. + * @note This class is inspected by HTMLPurifier_Printer_HTMLDefinition. + * Please update that class too. + * @warning If you add new properties to this class, you MUST update + * the mergeIn() method. + */ +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. + * @type array + * @note Before being processed by HTMLPurifier_AttrCollections + * when modules are finalized during + * HTMLPurifier_HTMLDefinition->setup(), this array may also + * contain an array at index 0 that indicates which attribute + * collections to load into the full array. It may also + * contain string indentifiers in lieu of HTMLPurifier_AttrDef, + * see HTMLPurifier_AttrTypes on how they are expanded during + * HTMLPurifier_HTMLDefinition->setup() processing. + */ + public $attr = array(); + + // XXX: Design note: currently, it's not possible to override + // previously defined AttrTransforms without messing around with + // the final generated config. This is by design; a previous version + // used an associated list of attr_transform, but it was extremely + // easy to accidentally override other attribute transforms by + // forgetting to specify an index (and just using 0.) While we + // could check this by checking the index number and complaining, + // there is a second problem which is that it is not at all easy to + // tell when something is getting overridden. Combine this with a + // codebase where this isn't really being used, and it's perfect for + // nuking. + + /** + * 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. + * @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 + * into an HTMLPurifier_ChildDef. + * @warning This is a temporary variable that is not available after + * being processed by HTMLDefinition + * @type string + */ + public $content_model; + + /** + * Value of $child->type, used to determine which ChildDef to use, + * used in combination with $content_model. + * @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() + * @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 + * exclusion for children, but we go the simpler, SGML-style + * route of flat-out exclusions, which correctly apply to + * all descendants and not just children. Note that the XHTML + * Modularization Abstract Modules are blithely unaware of such + * distinctions. + */ + public $excludes = array(); + + /** + * This tag is explicitly auto-closed by the following tags. + * @type array + */ + public $autoclose = array(); + + /** + * 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) + { + $def = new HTMLPurifier_ElementDef(); + $def->content_model = $content_model; + $def->content_model_type = $content_model_type; + $def->attr = $attr; + return $def; + } + + /** + * 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) + { + // later keys takes precedence + foreach ($def->attr as $k => $v) { + if ($k === 0) { + // merge in the includes + // sorry, no way to override an include + foreach ($v as $v2) { + $this->attr[0][] = $v2; + } + continue; + } + if ($v === false) { + if (isset($this->attr[$k])) { + unset($this->attr[$k]); + } + continue; + } + $this->attr[$k] = $v; + } + $this->_mergeAssocArray($this->excludes, $def->excludes); + $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)) { + $this->content_model = + str_replace("#SUPER", $this->content_model, $def->content_model); + $this->child = false; + } + 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; + } + } + + /** + * Merges one array into another, removes values which equal false + * @param $a1 Array by reference that is merged into + * @param $a2 Array that merges into $a1 + */ + private function _mergeAssocArray(&$a1, $a2) + { + foreach ($a2 as $k => $v) { + if ($v === false) { + if (isset($a1[$k])) { + unset($a1[$k]); + } + continue; + } + $a1[$k] = $v; + } + } +} + + + + + +/** + * A UTF-8 specific character encoder that handles cleaning and transforming. + * @note All functions in this class should be static. + */ +class HTMLPurifier_Encoder +{ + + /** + * Constructor throws fatal error if you attempt to instantiate class + */ + 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() + { + } + + /** + * 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) + { + set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler')); + $r = iconv($in, $out, $text); + restore_error_handler(); + return $r; + } + + /** + * iconv wrapper which mutes errors and works around bugs. + * @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); + } elseif ($code == self::ICONV_TRUNCATES) { + // we can only work around this if the input character set + // is utf-8 + if ($in == 'utf-8') { + if ($max_chunk_size < 4) { + trigger_error('max_chunk_size is too small', E_USER_WARNING); + return false; + } + // split into 8000 byte chunks, but be careful to handle + // multibyte boundaries properly + if (($c = strlen($text)) <= $max_chunk_size) { + return self::unsafeIconv($in, $out, $text); + } + $r = ''; + $i = 0; + while (true) { + if ($i + $max_chunk_size >= $c) { + $r .= self::unsafeIconv($in, $out, substr($text, $i)); + break; + } + // wibble the boundary + if (0x80 != (0xC0 & ord($text[$i + $max_chunk_size]))) { + $chunk_size = $max_chunk_size; + } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 1]))) { + $chunk_size = $max_chunk_size - 1; + } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 2]))) { + $chunk_size = $max_chunk_size - 2; + } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 3]))) { + $chunk_size = $max_chunk_size - 3; + } else { + return false; // rather confusing UTF-8... + } + $chunk = substr($text, $i, $chunk_size); // substr doesn't mind overlong lengths + $r .= self::unsafeIconv($in, $out, $chunk); + $i += $chunk_size; + } + return $r; + } else { + return false; + } + } else { + return false; + } + } + + /** + * Cleans a UTF-8 string for well-formedness and SGML validity + * + * 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 + * respectively. 128 and above the code points map to multibyte + * UTF-8 representations. + * + * @note Fallback code adapted from utf8ToUnicode by Henri Sivonen and + * hsivonen@iki.fi at under the + * LGPL license. Notes on what changed are inside, but in general, + * the original code transformed UTF-8 text into an array of integer + * Unicode codepoints. Understandably, transforming that back to + * a string would be somewhat expensive, so the function was modded to + * directly operate on the string. However, this discourages code + * reuse, and the logic enumerated here would be useful for any + * function that needs to be able to understand UTF-8 characters. + * As of right now, only smart lossless character encoding converters + * 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) + { + // 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 + )) { + return $str; + } + + $mState = 0; // cached expected number of octets after the current octet + // until the beginning of the next UTF8 character sequence + $mUcs4 = 0; // cached Unicode character + $mBytes = 1; // cached expected number of octets in the current sequence + + // original code involved an $out that was an array of Unicode + // codepoints. Instead of having to convert back into UTF-8, we've + // decided to directly append valid UTF-8 characters onto a string + // $out once they're done. $char accumulates raw bytes, while $mUcs4 + // turns into the Unicode code point, so there's some redundancy. + + $out = ''; + $char = ''; + + $len = strlen($str); + for ($i = 0; $i < $len; $i++) { + $in = ord($str{$i}); + $char .= $str[$i]; // append byte to char + if (0 == $mState) { + // When mState is zero we expect either a US-ASCII character + // or a multi-octet sequence. + if (0 == (0x80 & ($in))) { + // US-ASCII, pass straight through. + if (($in <= 31 || $in == 127) && + !($in == 9 || $in == 13 || $in == 10) // save \r\t\n + ) { + // control characters, remove + } else { + $out .= $char; + } + // reset + $char = ''; + $mBytes = 1; + } elseif (0xC0 == (0xE0 & ($in))) { + // First octet of 2 octet sequence + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 0x1F) << 6; + $mState = 1; + $mBytes = 2; + } elseif (0xE0 == (0xF0 & ($in))) { + // First octet of 3 octet sequence + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 0x0F) << 12; + $mState = 2; + $mBytes = 3; + } elseif (0xF0 == (0xF8 & ($in))) { + // First octet of 4 octet sequence + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 0x07) << 18; + $mState = 3; + $mBytes = 4; + } elseif (0xF8 == (0xFC & ($in))) { + // First octet of 5 octet sequence. + // + // This is illegal because the encoded codepoint must be + // either: + // (a) not the shortest form or + // (b) outside the Unicode range of 0-0x10FFFF. + // Rather than trying to resynchronize, we will carry on + // until the end of the sequence and let the later error + // handling code catch it. + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 0x03) << 24; + $mState = 4; + $mBytes = 5; + } elseif (0xFC == (0xFE & ($in))) { + // First octet of 6 octet sequence, see comments for 5 + // octet sequence. + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 1) << 30; + $mState = 5; + $mBytes = 6; + } else { + // Current octet is neither in the US-ASCII range nor a + // legal first octet of a multi-octet sequence. + $mState = 0; + $mUcs4 = 0; + $mBytes = 1; + $char = ''; + } + } else { + // When mState is non-zero, we expect a continuation of the + // multi-octet sequence + if (0x80 == (0xC0 & ($in))) { + // Legal continuation. + $shift = ($mState - 1) * 6; + $tmp = $in; + $tmp = ($tmp & 0x0000003F) << $shift; + $mUcs4 |= $tmp; + + if (0 == --$mState) { + // End of the multi-octet sequence. mUcs4 now contains + // the final Unicode codepoint to be output + + // Check for illegal sequences and codepoints. + + // From Unicode 3.1, non-shortest form is illegal + if (((2 == $mBytes) && ($mUcs4 < 0x0080)) || + ((3 == $mBytes) && ($mUcs4 < 0x0800)) || + ((4 == $mBytes) && ($mUcs4 < 0x10000)) || + (4 < $mBytes) || + // From Unicode 3.2, surrogate characters = illegal + (($mUcs4 & 0xFFFFF800) == 0xD800) || + // Codepoints outside the Unicode range are illegal + ($mUcs4 > 0x10FFFF) + ) { + + } elseif (0xFEFF != $mUcs4 && // omit BOM + // check for valid Char unicode codepoints + ( + 0x9 == $mUcs4 || + 0xA == $mUcs4 || + 0xD == $mUcs4 || + (0x20 <= $mUcs4 && 0x7E >= $mUcs4) || + // 7F-9F is not strictly prohibited by XML, + // but it is non-SGML, and thus we don't allow it + (0xA0 <= $mUcs4 && 0xD7FF >= $mUcs4) || + (0x10000 <= $mUcs4 && 0x10FFFF >= $mUcs4) + ) + ) { + $out .= $char; + } + // initialize UTF8 cache (reset) + $mState = 0; + $mUcs4 = 0; + $mBytes = 1; + $char = ''; + } + } else { + // ((0xC0 & (*in) != 0x80) && (mState != 0)) + // Incomplete multi-octet sequence. + // used to result in complete fail, but we'll reset + $mState = 0; + $mUcs4 = 0; + $mBytes = 1; + $char =''; + } + } + } + return $out; + } + + /** + * Translates a Unicode codepoint into its corresponding UTF-8 character. + * @note Based on Feyd's function at + * , + * which is in public domain. + * @note While we're going to do code point parsing anyway, a good + * optimization would be to refuse to translate code points that + * are non-SGML characters. However, this could lead to duplication. + * @note This is very similar to the unichr function in + * maintenance/generate-entity-file.php (although this is superior, + * due to its sanity checks). + */ + + // +----------+----------+----------+----------+ + // | 33222222 | 22221111 | 111111 | | + // | 10987654 | 32109876 | 54321098 | 76543210 | bit + // +----------+----------+----------+----------+ + // | | | | 0xxxxxxx | 1 byte 0x00000000..0x0000007F + // | | | 110yyyyy | 10xxxxxx | 2 byte 0x00000080..0x000007FF + // | | 1110zzzz | 10yyyyyy | 10xxxxxx | 3 byte 0x00000800..0x0000FFFF + // | 11110www | 10wwzzzz | 10yyyyyy | 10xxxxxx | 4 byte 0x00010000..0x0010FFFF + // +----------+----------+----------+----------+ + // | 00000000 | 00011111 | 11111111 | 11111111 | Theoretical upper limit of legal scalars: 2097151 (0x001FFFFF) + // | 00000000 | 00010000 | 11111111 | 11111111 | Defined upper limit of legal scalar codes + // +----------+----------+----------+----------+ + + 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 + return ''; + } + + $x = $y = $z = $w = 0; + if ($code < 128) { + // regular ASCII character + $x = $code; + } else { + // set up bits for UTF-8 + $x = ($code & 63) | 128; + if ($code < 2048) { + $y = (($code & 2047) >> 6) | 192; + } else { + $y = (($code & 4032) >> 6) | 128; + if ($code < 65536) { + $z = (($code >> 12) & 15) | 224; + } else { + $z = (($code >> 12) & 63) | 128; + $w = (($code >> 18) & 7) | 240; + } + } + } + // set up the actual character + $ret = ''; + if ($w) { + $ret .= chr($w); + } + if ($z) { + $ret .= chr($z); + } + if ($y) { + $ret .= chr($y); + } + $ret .= chr($x); + + return $ret; + } + + /** + * @return bool + */ + public static function iconvAvailable() + { + static $iconv = null; + if ($iconv === null) { + $iconv = function_exists('iconv') && self::testIconvTruncateBug() != self::ICONV_UNUSABLE; + } + return $iconv; + } + + /** + * 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) + { + $encoding = $config->get('Core.Encoding'); + if ($encoding === 'utf-8') { + return $str; + } + static $iconv = null; + 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); + if ($str === false) { + // $encoding is not a valid encoding + trigger_error('Invalid encoding ' . $encoding, E_USER_ERROR); + return ''; + } + // If the string is bjorked by Shift_JIS or a similar encoding + // that doesn't support all of ASCII, convert the naughty + // characters to their true byte-wise ASCII/UTF-8 equivalents. + $str = strtr($str, self::testEncodingSupportsASCII($encoding)); + return $str; + } elseif ($encoding === 'iso-8859-1') { + $str = utf8_encode($str); + return $str; + } + $bug = HTMLPurifier_Encoder::testIconvTruncateBug(); + 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 + ); + } + } + + /** + * 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) + { + $encoding = $config->get('Core.Encoding'); + if ($escape = $config->get('Core.EscapeNonASCIICharacters')) { + $str = self::convertToASCIIDumbLossless($str); + } + if ($encoding === 'utf-8') { + return $str; + } + static $iconv = null; + 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] = ''; + } + $str = strtr($str, $clear_fix); + } + $str = strtr($str, array_flip($ascii_fix)); + // Normal stuff + $str = self::iconv('utf-8', $encoding . '//IGNORE', $str); + return $str; + } elseif ($encoding === 'iso-8859-1') { + $str = utf8_decode($str); + return $str; + } + trigger_error('Encoding not supported', E_USER_ERROR); + // You might be tempted to assume that the ASCII representation + // might be OK, however, this is *not* universally true over all + // encodings. So we take the conservative route here, rather + // than forcibly turn on %Core.EscapeNonASCIICharacters + } + + /** + * Lossless (character-wise) conversion of HTML to ASCII + * @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. + * @note Uses decimal numeric entities since they are best supported. + * @note This is a DUMB function: it has no concept of keeping + * character entities that the projected character encoding + * can allow. We could possibly implement a smart version + * but that would require it to also know which Unicode + * codepoints the charset supported (not an easy task). + * @note Sort of with cleanUTF8() but it assumes that $str is + * well-formed UTF-8 + */ + 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); + $bytesleft = 0; + } elseif ($bytevalue <= 0xBF) { //10xx xxxx + $working = $working << 6; + $working += ($bytevalue & 0x3F); + $bytesleft--; + if ($bytesleft <= 0) { + $result .= "&#" . $working . ";"; + } + } elseif ($bytevalue <= 0xDF) { //110x xxxx + $working = $bytevalue & 0x1F; + $bytesleft = 1; + } elseif ($bytevalue <= 0xEF) { //1110 xxxx + $working = $bytevalue & 0x0F; + $bytesleft = 2; + } else { //1111 0xxx + $working = $bytevalue & 0x07; + $bytesleft = 3; + } + } + return $result; + } + + /** No bugs detected in iconv. */ + const ICONV_OK = 0; + + /** Iconv truncates output if converting from UTF-8 to another + * character set with //IGNORE, and a non-encodable character is found */ + const ICONV_TRUNCATES = 1; + + /** Iconv does not support //IGNORE, making it unusable for + * transcoding purposes */ + const ICONV_UNUSABLE = 2; + + /** + * glibc iconv has a known bug where it doesn't handle the magic + * //IGNORE stanza correctly. In particular, rather than ignore + * characters, it will return an EILSEQ after consuming some number + * of characters, and expect you to restart iconv as if it were + * an E2BIG. Old versions of PHP did not respect the errno, and + * returned the fragment, so as a result you would see iconv + * mysteriously truncating output. We can work around this by + * manually chopping our input into segments of about 8000 + * characters, as long as PHP ignores the error code. If PHP starts + * paying attention to the error code, iconv becomes unusable. + * + * @return int Error code indicating severity of bug. + */ + public static function testIconvTruncateBug() + { + static $code = null; + if ($code === null) { + // better not use iconv, otherwise infinite loop! + $r = self::unsafeIconv('utf-8', 'ascii//IGNORE', "\xCE\xB1" . str_repeat('a', 9000)); + if ($r === false) { + $code = self::ICONV_UNUSABLE; + } 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 + ); + } else { + $code = self::ICONV_OK; + } + } + return $code; + } + + /** + * This expensive function tests whether or not a given character + * encoding supports ASCII. 7/8-bit encodings like Shift_JIS will + * fail this test, and require special processing. Variable width + * encodings shouldn't ever fail. + * + * @param string $encoding Encoding name to test, as per iconv format + * @param bool $bypass Whether or not to bypass the precompiled arrays. + * @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) + { + // 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, + // so bug is not triggered. + // If ICONV_UNUSABLE, this call is irrelevant + static $encodings = array(); + if (!$bypass) { + if (isset($encodings[$encoding])) { + return $encodings[$encoding]; + } + $lenc = strtolower($encoding); + switch ($lenc) { + case 'shift_jis': + return array("\xC2\xA5" => '\\', "\xE2\x80\xBE" => '~'); + case 'johab': + return array("\xE2\x82\xA9" => '\\'); + } + if (strpos($lenc, 'iso-8859-') === 0) { + return array(); + } + } + $ret = array(); + 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 === '' || + // 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) + ) { + // Reverse engineer: what's the UTF-8 equiv of this byte + // sequence? This assumes that there's no variable width + // encoding that doesn't support ASCII. + $ret[self::unsafeIconv($encoding, 'UTF-8//IGNORE', $c)] = $c; + } + } + $encodings[$encoding] = $ret; + return $ret; + } +} + + + + + +/** + * Object that provides entity lookup table from entity name to character + */ +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) + { + if (!$file) { + $file = HTMLPURIFIER_PREFIX . '/HTMLPurifier/EntityLookup/entities.ser'; + } + $this->table = unserialize(file_get_contents($file)); + } + + /** + * Retrieves sole instance of the object. + * @param bool|HTMLPurifier_EntityLookup $prototype Optional prototype of custom lookup table to overload with. + * @return HTMLPurifier_EntityLookup + */ + public static function instance($prototype = false) + { + // no references, since PHP doesn't copy unless modified + static $instance = null; + if ($prototype) { + $instance = $prototype; + } elseif (!$instance) { + $instance = new HTMLPurifier_EntityLookup(); + $instance->setup(); + } + return $instance; + } +} + + + + + +// if want to implement error collecting here, we'll need to use some sort +// of global data (probably trigger_error) because it's impossible to pass +// $config or $context to the callback functions. + +/** + * Handles referencing and derefencing character entities + */ +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) + + /** + * Decimal to parsed string conversion table for special entities. + * @type array + */ + protected $_special_dec2str = + array( + 34 => '"', + 38 => '&', + 39 => "'", + 60 => '<', + 62 => '>' + ); + + /** + * Stripped entity names to decimal conversion table for special entities. + * @type array + */ + protected $_special_ent2dec = + array( + 'quot' => 34, + 'amp' => 38, + 'lt' => 60, + 'gt' => 62 + ); + + /** + * Substitutes non-special entities with their parsed equivalents. Since + * running this whenever you have parsed character is t3h 5uck, we run + * it before everything else. + * + * @param string $string String to have non-special entities parsed. + * @return string Parsed 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 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). + * @return string Replacement string. + */ + + 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; + } + return HTMLPurifier_Encoder::unichr($code); + } else { + if (isset($this->_special_ent2dec[$matches[3]])) { + return $entity; + } + if (!$this->_entity_lookup) { + $this->_entity_lookup = HTMLPurifier_EntityLookup::instance(); + } + if (isset($this->_entity_lookup->table[$matches[3]])) { + return $this->_entity_lookup->table[$matches[3]]; + } else { + return $entity; + } + } + } + + /** + * Substitutes only special entities with their parsed equivalents. + * + * @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 String to have non-special entities parsed. + * @return string Parsed string. + */ + public function substituteSpecialEntities($string) + { + return preg_replace_callback( + $this->_substituteEntitiesRegex, + array($this, 'specialEntityCallback'), + $string + ); + } + + /** + * Callback function for substituteSpecialEntities() that does the work. + * + * This callback has same syntax as nonSpecialEntityCallback(). + * + * @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). + * @return string Replacement string. + */ + protected function specialEntityCallback($matches) + { + $entity = $matches[0]; + $is_num = (@$matches[0][1] === '#'); + if ($is_num) { + $is_hex = (@$entity[2] === 'x'); + $int = $is_hex ? hexdec($matches[1]) : (int) $matches[2]; + return isset($this->_special_dec2str[$int]) ? + $this->_special_dec2str[$int] : + $entity; + } else { + return isset($this->_special_ent2dec[$matches[3]]) ? + $this->_special_ent2dec[$matches[3]] : + $entity; + } + } +} + + + + + +/** + * Error collection class that enables HTML Purifier to report HTML + * problems back to the user + */ +class HTMLPurifier_ErrorCollector +{ + + /** + * Identifiers for the returned error array. These are purposely numeric + * so list() can be used. + */ + const LINENO = 0; + const SEVERITY = 1; + 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(); + + /** + * @param HTMLPurifier_Context $context + */ + public function __construct($context) + { + $this->locale =& $context->get('Locale'); + $this->context = $context; + $this->_current =& $this->_stacks[0]; + $this->errors =& $this->_stacks[0]; + } + + /** + * Sends an error message to the collector for later use + * @param int $severity Error severity, PHP error style (don't use E_USER_) + * @param string $msg Error message text + */ + public function send($severity, $msg) + { + $args = array(); + if (func_num_args() > 2) { + $args = func_get_args(); + array_shift($args); + unset($args[0]); + } + + $token = $this->context->get('CurrentToken', true); + $line = $token ? $token->line : $this->context->get('CurrentLine', true); + $col = $token ? $token->col : $this->context->get('CurrentCol', true); + $attr = $this->context->get('CurrentAttr', true); + + // perform special substitutions, also add custom parameters + $subst = array(); + if (!is_null($token)) { + $args['CurrentToken'] = $token; + } + if (!is_null($attr)) { + $subst['$CurrentAttr.Name'] = $attr; + if (isset($token->attr[$attr])) { + $subst['$CurrentAttr.Value'] = $token->attr[$attr]; + } + } + + if (empty($args)) { + $msg = $this->locale->getMessage($msg); + } else { + $msg = $this->locale->formatMessage($msg, $args); + } + + if (!empty($subst)) { + $msg = strtr($msg, $subst); + } + + // (numerically indexed) + $error = array( + self::LINENO => $line, + self::SEVERITY => $severity, + self::MESSAGE => $msg, + self::CHILDREN => array() + ); + $this->_current[] = $error; + + // NEW CODE BELOW ... + // 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 (is_int($line) && is_int($col)) { + if (isset($this->lines[$line][$col])) { + $struct = $this->lines[$line][$col]; + } else { + $struct = $this->lines[$line][$col] = $new_struct; + } + // These ksorts may present a performance problem + ksort($this->lines[$line], SORT_NUMERIC); + } else { + if (isset($this->lines[-1])) { + $struct = $this->lines[-1]; + } else { + $struct = $this->lines[-1] = $new_struct; + } + } + ksort($this->lines, SORT_NUMERIC); + + // Now, check if we need to operate on a lower structure + if (!empty($attr)) { + $struct = $struct->getChild(HTMLPurifier_ErrorStruct::ATTR, $attr); + if (!$struct->value) { + $struct->value = array($attr, 'PUT VALUE HERE'); + } + } + if (!empty($cssprop)) { + $struct = $struct->getChild(HTMLPurifier_ErrorStruct::CSSPROP, $cssprop); + if (!$struct->value) { + // if we tokenize CSS this might be a little more difficult to do + $struct->value = array($cssprop, 'PUT VALUE HERE'); + } + } + + // Ok, structs are all setup, now time to register the error + $struct->addError($severity, $msg); + } + + /** + * Retrieves raw error data for custom formatter to use + */ + public function getRaw() + { + return $this->errors; + } + + /** + * Default HTML formatting implementation for error messages + * @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) + { + $ret = array(); + + $this->generator = new HTMLPurifier_Generator($config, $this->context); + 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; + } + foreach ($col_array as $col => $struct) { + $this->_renderStruct($ret, $struct, $line, $col); + } + } + if (isset($this->lines[-1])) { + $this->_renderStruct($ret, $this->lines[-1]); + } + + if (empty($errors)) { + return '

      ' . $this->locale->getMessage('ErrorCollector: No errors') . '

      '; + } else { + return '
      • ' . implode('
      • ', $ret) . '
      '; + } + + } + + private function _renderStruct(&$ret, $struct, $line = null, $col = null) + { + $stack = array($struct); + $context_stack = array(array()); + while ($current = array_pop($stack)) { + $context = array_pop($context_stack); + foreach ($current->errors as $error) { + list($severity, $msg) = $error; + $string = ''; + $string .= '
      '; + // W3C uses an icon to indicate the severity of the error. + $error = $this->locale->getErrorName($severity); + $string .= "$error "; + if (!is_null($line) && !is_null($col)) { + $string .= "Line $line, Column $col: "; + } else { + $string .= 'End of Document: '; + } + $string .= '' . $this->generator->escape($msg) . ' '; + $string .= '
      '; + // Here, have a marker for the character on the column appropriate. + // Be sure to clip extremely long lines. + //$string .= '
      ';
      +                //$string .= '';
      +                //$string .= '
      '; + $ret[] = $string; + } + foreach ($current->children as $array) { + $context[] = $current; + $stack = array_merge($stack, array_reverse($array, true)); + for ($i = count($array); $i > 0; $i--) { + $context_stack[] = $context; + } + } + } + } +} + + + + + +/** + * Records errors for particular segments of an HTML document such as tokens, + * attributes or CSS properties. They can contain error structs (which apply + * to components of what they represent), but their main purpose is to hold + * errors applying to whatever struct is being used. + */ +class HTMLPurifier_ErrorStruct +{ + + /** + * Possible values for $children first-key. Note that top-level structures + * are automatically token-level. + */ + const TOKEN = 0; + const ATTR = 1; + const CSSPROP = 2; + + /** + * Type of this struct. + * @type string + */ + public $type; + + /** + * Value of the struct we are recording errors for. There are various + * values for this: + * - 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(); + + /** + * 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(); + + /** + * @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; + } + return $this->children[$type][$id]; + } + + /** + * @param int $severity + * @param string $message + */ + public function addError($severity, $message) + { + $this->errors[] = array($severity, $message); + } +} + + + + + +/** + * Global exception class for HTML Purifier; any exceptions we throw + * are from here. + */ +class HTMLPurifier_Exception extends Exception +{ + +} + + + + + +/** + * Represents a pre or post processing filter on HTML Purifier's output + * + * Sometimes, a little ad-hoc fixing of HTML has to be done before + * it gets sent through HTML Purifier: you can use filters to acheive + * this effect. For instance, YouTube videos can be preserved using + * this manner. You could have used a decorator for this task, but + * PHP's support for them is not terribly robust, so we're going + * to just loop through the filters. + * + * Filters should be exited first in, last out. If there are three filters, + * named 1, 2 and 3, the order of execution should go 1->preFilter, + * 2->preFilter, 3->preFilter, purify, 3->postFilter, 2->postFilter, + * 1->postFilter. + * + * @note Methods are not declared abstract as it is perfectly legitimate + * for an implementation not to want anything to happen on a step + */ + +class HTMLPurifier_Filter +{ + + /** + * 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) + { + 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) + { + return $html; + } +} + + + + + +/** + * Generates HTML from tokens. + * @todo Refactor interface so that configuration/context is determined + * upon instantiation, no need for messy generateFromTokens() calls + * @todo Make some of the more internal functions protected, and have + * unit tests work around that + */ +class HTMLPurifier_Generator +{ + + /** + * 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 = $this->normalize($html, $config, $context); + + $cursor = 0; // our location in the text + $inside_tag = false; // whether or not we're parsing the inside of a tag + $array = array(); // result array + + // This is also treated to mean maintain *column* numbers too + $maintain_line_numbers = $config->get('Core.MaintainLineNumbers'); + + if ($maintain_line_numbers === null) { + // automatically determine line numbering by checking + // if error collection is on + $maintain_line_numbers = $config->get('Core.CollectErrors'); + } + + if ($maintain_line_numbers) { + $current_line = 1; + $current_col = 0; + $length = strlen($html); + } else { + $current_line = false; + $current_col = false; + $length = false; + } + $context->register('CurrentLine', $current_line); + $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 + $synchronize_interval = $config->get('Core.DirectLexLineNumberSyncInterval'); + + $e = false; + if ($config->get('Core.CollectErrors')) { + $e =& $context->get('ErrorCollector'); + } + + // for testing synchronization + $loops = 0; + + 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; + + // Column number is cheap, so we calculate it every round. + // We're interested at the *end* of the newline string, so + // we need to add strlen($nl) == 1 to $nl_pos before subtracting it + // from our "rcursor" position. + $nl_pos = strrpos($html, $nl, $rcursor - $length); + $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! + $current_line = 1 + $this->substrCount($html, $nl, 0, $cursor); + } + } + + $position_next_lt = strpos($html, '<', $cursor); + $position_next_gt = strpos($html, '>', $cursor); + + // triggers on "asdf" but not "asdf " + // special case to set up context + if ($position_next_lt === $cursor) { + $inside_tag = true; + $cursor++; + } + + 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 + ) + ) + ); + 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; + $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; + } + // Create Text of rest of string + $token = new + HTMLPurifier_Token_Text( + $this->parseData( + substr( + $html, + $cursor + ) + ) + ); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + } + $array[] = $token; + break; + } elseif ($inside_tag && $position_next_gt !== false) { + // We are in tag and it is well formed + // Grab the internals of the tag + $strlen_segment = $position_next_gt - $cursor; + + if ($strlen_segment < 1) { + // there's nothing to process! + $token = new HTMLPurifier_Token_Text('<'); + $cursor++; + continue; + } + + $segment = substr($html, $cursor, $strlen_segment); + + if ($segment === false) { + // somehow, we attempted to access beyond the end of + // the string, defense-in-depth, reported by Nate Abele + break; + } + + // Check if it's a comment + 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'); + } + $position_comment_end = strlen($html); + $end = true; + } else { + $end = false; + } + $strlen_segment = $position_comment_end - $cursor; + $segment = substr($html, $cursor, $strlen_segment); + $token = new + 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); + } + $array[] = $token; + $cursor = $end ? $position_comment_end : $position_comment_end + 3; + $inside_tag = false; + continue; + } + + // Check if it's an end tag + $is_end_tag = (strpos($segment, '/') === 0); + if ($is_end_tag) { + $type = substr($segment, 1); + $token = new HTMLPurifier_Token_End($type); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); + } + $array[] = $token; + $inside_tag = false; + $cursor = $position_next_gt + 1; + continue; + } + + // Check leading character is alnum, if not, we may + // have accidently grabbed an emoticon. Translate into + // 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'); + } + $token = new HTMLPurifier_Token_Text('<'); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); + } + $array[] = $token; + $inside_tag = false; + continue; + } + + // Check if it is explicitly self closing, if so, remove + // 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); + if ($is_self_closing) { + $strlen_segment--; + $segment = substr($segment, 0, $strlen_segment); + } + + // Check if there are any attributes + $position_first_space = strcspn($segment, $this->_whitespace); + + if ($position_first_space >= $strlen_segment) { + if ($is_self_closing) { + $token = new HTMLPurifier_Token_Empty($segment); + } else { + $token = new HTMLPurifier_Token_Start($segment); + } + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); + } + $array[] = $token; + $inside_tag = false; + $cursor = $position_next_gt + 1; + continue; + } + + // Grab out all the data + $type = substr($segment, 0, $position_first_space); + $attribute_string = + trim( + substr( + $segment, + $position_first_space + ) + ); + if ($attribute_string) { + $attr = $this->parseAttributeString( + $attribute_string, + $config, + $context + ); + } else { + $attr = array(); + } + + if ($is_self_closing) { + $token = new HTMLPurifier_Token_Empty($type, $attr); + } else { + $token = new HTMLPurifier_Token_Start($type, $attr); + } + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); + } + $array[] = $token; + $cursor = $position_next_gt + 1; + $inside_tag = false; + continue; + } else { + // inside tag, but there's no ending > sign + 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); + } + // no cursor scroll? Hmm... + $array[] = $token; + break; + } + break; + } + + $context->destroy('CurrentLine'); + $context->destroy('CurrentCol'); + return $array; + } + + /** + * PHP 5.0.x compatible substr_count that implements offset and 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', '<'); + } + if ($oldVersion) { + $haystack = substr($haystack, $offset, $length); + return substr_count($haystack, $needle); + } else { + return substr_count($haystack, $needle, $offset, $length); + } + } + + /** + * Takes the inside of an HTML tag and makes an 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 + + if ($string == '') { + return array(); + } // no attributes + + $e = false; + if ($config->get('Core.CollectErrors')) { + $e =& $context->get('ErrorCollector'); + } + + // let's see if we can abort as quickly as possible + // one equal sign, no spaces => one attribute + $num_equal = substr_count($string, '='); + $has_space = strpos($string, ' '); + if ($num_equal === 0 && !$has_space) { + // bool attribute + return array($string => $string); + } elseif ($num_equal === 1 && !$has_space) { + // only one attribute + list($key, $quoted_value) = explode('=', $string); + $quoted_value = trim($quoted_value); + if (!$key) { + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing attribute key'); + } + return array(); + } + if (!$quoted_value) { + return array($key => ''); + } + $first_char = @$quoted_value[0]; + $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) { + // 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'); + } + $value = substr($quoted_value, 1); + } else { + $value = $quoted_value; + } + } + if ($value === false) { + $value = ''; + } + return array($key => $this->parseData($value)); + } + + // setup loop environment + $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) + + // if we have unquoted attributes, the parser expects a terminating + // space, so let's guarantee that there's always a terminating space. + $string .= ' '; + + $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 + + $key_begin = $cursor; //we're currently at the start of the key + + // scroll past all characters that are the key (not whitespace or =) + $cursor += strcspn($string, $this->_whitespace . '=', $cursor); + + $key_end = $cursor; // now at the end of the key + + $key = substr($string, $key_begin, $key_end - $key_begin); + + if (!$key) { + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing attribute key'); + } + $cursor += 1 + strcspn($string, $this->_whitespace, $cursor + 1); // prevent infinite loop + continue; // empty key + } + + // scroll past all whitespace + $cursor += strspn($string, $this->_whitespace, $cursor); + + if ($cursor >= $size) { + $array[$key] = $key; + break; + } + + // if the next character is an equal sign, we've got a regular + // pair, otherwise, it's a bool attribute + $first_char = @$string[$cursor]; + + if ($first_char == '=') { + // key="value" + + $cursor++; + $cursor += strspn($string, $this->_whitespace, $cursor); + + if ($cursor === false) { + $array[$key] = ''; + break; + } + + // we might be in front of a quote right now + + $char = @$string[$cursor]; + + if ($char == '"' || $char == "'") { + // it's quoted, end bound is $char + $cursor++; + $value_begin = $cursor; + $cursor = strpos($string, $char, $cursor); + $value_end = $cursor; + } else { + // it's not quoted, end bound is whitespace + $value_begin = $cursor; + $cursor += strcspn($string, $this->_whitespace, $cursor); + $value_end = $cursor; + } + + // we reached a premature end + if ($cursor === false) { + $cursor = $size; + $value_end = $cursor; + } + + $value = substr($string, $value_begin, $value_end - $value_begin); + 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'); + } + } + } + } + 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); + } +} + + + + + +/** + * Composite strategy that runs multiple strategies on tokens. + */ +abstract class HTMLPurifier_Strategy_Composite extends HTMLPurifier_Strategy +{ + + /** + * List of strategies to run tokens through. + * @type HTMLPurifier_Strategy[] + */ + protected $strategies = array(); + + /** + * @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; + } +} + + + + + +/** + * Core strategy composed of the big four strategies. + */ +class HTMLPurifier_Strategy_Core extends HTMLPurifier_Strategy_Composite +{ + 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(); + } +} + + + + + +/** + * Takes a well formed list of tokens and fixes their nesting. + * + * HTML elements dictate which elements are allowed to be their children, + * for example, you can't have a p tag in a span tag. Other elements have + * much more rigorous definitions: tables, for instance, require a specific + * order for their elements. There are also constraints not expressible by + * 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 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 + * task is pervasive through the strategy, though the two are distinct tasks + * and could, theoretically, be seperated (although it's not recommended). + * + * @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. This is + * easier with our new algorithm. + */ + +class HTMLPurifier_Strategy_FixNesting extends HTMLPurifier_Strategy +{ + + /** + * @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'); + + // 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 + // branch of the document tree + $is_inline = $definition->info_parent_def->descendants_are_inline; + $context->register('IsInline', $is_inline); + + // setup error collector + $e =& $context->get('ErrorCollector', true); + + //####################################################################// + // Loop initialization + + // 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($definition->info_parent_def->excludes); + + // variable that contains the start token while we are processing + // nodes. This enables error reporting to do its job + $node = $top_node; + // dummy token + list($token, $d) = $node->toTokenPair(); + $context->register('CurrentNode', $node); + $context->register('CurrentToken', $token); + + //####################################################################// + // Loop + + // 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) + ); + + 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; + } + }; + 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 { + // 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; + } + $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 { + $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'); + } + } + } + } + } + + //####################################################################// + // Post-processing + + // remove context variables + $context->destroy('IsInline'); + $context->destroy('CurrentNode'); + $context->destroy('CurrentToken'); + + //####################################################################// + // Return + + return HTMLPurifier_Arborize::flatten($node, $config, $context); + } +} + + + + + +/** + * Takes tokens makes them well-formed (balance end tags, etc.) + * + * Specification of the armor attributes this strategy uses: + * + * - MakeWellFormed_TagClosedError: This armor field is used to + * suppress tag closed errors for certain tokens [TagClosedSuppress], + * in particular, if a tag was generated automatically by HTML + * Purifier, we may rely on our infrastructure to close it for us + * and shouldn't report an error to the user [TagClosedAuto]. + */ +class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy +{ + + /** + * Array stream of tokens being processed. + * @type HTMLPurifier_Token[] + */ + protected $tokens; + + /** + * Current token. + * @type HTMLPurifier_Token + */ + protected $token; + + /** + * Zipper managing the true state. + * @type HTMLPurifier_Zipper + */ + 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; + + /** + * @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 = $definition->info_parent_def->child->getAllowedElements($config); + $e = $context->get('ErrorCollector', true); + $i = false; // injector index + 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->tokens =& $tokens; + $this->token =& $token; + $this->zipper =& $zipper; + $this->config = $config; + $this->context = $context; + + // context variables + $context->register('CurrentNesting', $stack); + $context->register('InputZipper', $zipper); + $context->register('CurrentToken', $token); + + // -- begin INJECTOR -- + + $this->injectors = array(); + + $injectors = $config->getBatch('AutoFormat'); + $def_injectors = $definition->info_injector; + $custom_injectors = $injectors['Custom']; + 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; + } + $injector = "HTMLPurifier_Injector_$injector"; + if (!$b) { + continue; + } + $this->injectors[] = new $injector; + } + foreach ($def_injectors as $injector) { + // assumed to be objects + $this->injectors[] = $injector; + } + foreach ($custom_injectors as $injector) { + if (!$injector) { + continue; + } + if (is_string($injector)) { + $injector = "HTMLPurifier_Injector_$injector"; + $injector = new $injector; + } + $this->injectors[] = $injector; + } + + // give the injectors references to the definition and context + // variables for performance reasons + foreach ($this->injectors as $ix => $injector) { + $error = $injector->prepare($config, $context); + 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); + } + + // -- end INJECTOR -- + + // a note on reprocessing: + // In order to reduce code duplication, whenever some code needs + // to make HTML changes in order to make things "correct", the + // new HTML gets sent through the purifier, regardless of its + // status. This means that if we add a start token, because it + // was totally necessary, we don't have to update nesting; we just + // punt ($reprocess = true; continue;) and it does that for us. + + // isset is in loop because $tokens size changes during loop exec + 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)) { + // 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_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($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 ($token === NULL) { + // kill processing if stack is empty + if (empty($this->stack)) { + break; + } + + // peek + $top_nesting = array_pop($this->stack); + $this->stack[] = $top_nesting; + + // send error [TagClosedSuppress] + if ($e && !isset($top_nesting->armor['MakeWellFormed_TagClosedError'])) { + $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by document end', $top_nesting); + } + + // append, don't splice, since this is the end + $token = new HTMLPurifier_Token_End($top_nesting->name); + + // punt! + $reprocess = true; + continue; + } + + //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; + } + // XXX fuckup + $r = $token; + $injector->handleText($r); + $token = $this->processToken($r, $i); + $reprocess = true; + break; + } + } + // another possibility is a comment + continue; + } + + if (isset($definition->info[$token->name])) { + $type = $definition->info[$token->name]->child->type; + } else { + $type = false; // Type is unknown, treat accordingly + } + + // quick tag checks: anything that's *not* an end tag + $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 + ); + $ok = true; + } elseif ($type && $type !== 'empty' && $token instanceof HTMLPurifier_Token_Empty) { + // claims to be empty but really is a start tag + // 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; + } elseif ($token instanceof HTMLPurifier_Token_Empty) { + // real empty token + $ok = true; + } elseif ($token instanceof HTMLPurifier_Token_Start) { + // start tag + + // ...unless they also have to close their parent + if (!empty($this->stack)) { + + // Performance note: you might think that it's rather + // inefficient, recalculating the autoclose information + // for every tag that a token closes (since when we + // do an autoclose, we push a new token into the + // stream and then /process/ that, before + // re-processing this token.) But this is + // necessary, because an injector can make an + // arbitrary transformations to the autoclosing + // tokens we introduce, so things may have changed + // in the meantime. Also, doing the inefficient thing is + // "easy" to reason about (for certain perverse definitions + // of "easy") + + $parent = array_pop($this->stack); + $this->stack[] = $parent; + + $parent_def = null; + $parent_elements = null; + $autoclose = false; + if (isset($definition->info[$parent->name])) { + $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 + // example,
          needs a
        • in between) + $wrapname = $definition->info[$token->name]->wrap; + $wrapdef = $definition->info[$wrapname]; + $elements = $wrapdef->child->getAllowedElements($config); + if (isset($elements[$token->name]) && isset($parent_elements[$wrapname])) { + $newtoken = new HTMLPurifier_Token_Start($wrapname); + $token = $this->insertBefore($newtoken); + $reprocess = true; + continue; + } + } + + $carryover = false; + if ($autoclose && $parent_def->formatting) { + $carryover = true; + } + + if ($autoclose) { + // check if this autoclose is doomed to fail + // (this rechecks $parent, which his harmless) + $autoclose_ok = isset($global_parent_allowed_elements[$token->name]); + if (!$autoclose_ok) { + foreach ($this->stack as $ancestor) { + $elements = $definition->info[$ancestor->name]->child->getAllowedElements($config); + if (isset($elements[$token->name])) { + $autoclose_ok = true; + break; + } + if ($definition->info[$token->name]->wrap) { + $wrapname = $definition->info[$token->name]->wrap; + $wrapdef = $definition->info[$wrapname]; + $wrap_elements = $wrapdef->child->getAllowedElements($config); + if (isset($wrap_elements[$token->name]) && isset($elements[$wrapname])) { + $autoclose_ok = true; + break; + } + } + } + } + if ($autoclose_ok) { + // errors need to be updated + $new_token = new HTMLPurifier_Token_End($parent->name); + $new_token->start = $parent; + // [TagClosedSuppress] + if ($e && !isset($parent->armor['MakeWellFormed_TagClosedError'])) { + if (!$carryover) { + $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag auto closed', $parent); + } else { + $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 { + $token = $this->remove(); + } + $reprocess = true; + continue; + } + + } + $ok = true; + } + + if ($ok) { + foreach ($this->injectors as $i => $injector) { + 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 + 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' + ); + } + } + continue; + } + + // sanity check: we should be dealing with a closing tag + if (!$token instanceof HTMLPurifier_Token_End) { + throw new HTMLPurifier_Exception('Unaccounted for tag token in input stream, bug in HTML Purifier'); + } + + // 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'); + } + $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token)); + } else { + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed'); + } + $token = $this->remove(); + } + $reprocess = true; + continue; + } + + // first, check for the simplest case: everything closes neatly. + // Eventually, everything passes through here; if there are problems + // we modify the input stream accordingly and then punt, so that + // the tokens get processed again. + $current_parent = array_pop($this->stack); + 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; + } + $r = $token; + $injector->handleEnd($r); + $token = $this->processToken($r, $i); + $this->stack[] = $current_parent; + $reprocess = true; + break; + } + continue; + } + + // okay, so we're trying to close the wrong tag + + // undo the pop previous pop + $this->stack[] = $current_parent; + + // scroll back the entire nest, trying to find our tag. + // (feature could be to specify how far you'd like to go) + $size = count($this->stack); + // -2 because -1 is the last element, but we already checked that + $skipped_tags = false; + for ($j = $size - 2; $j >= 0; $j--) { + if ($this->stack[$j]->name == $token->name) { + $skipped_tags = array_slice($this->stack, $j); + break; + } + } + + // we didn't find the tag, so remove + if ($skipped_tags === false) { + if ($escape_invalid_tags) { + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text'); + } + $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token)); + } else { + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed'); + } + $token = $this->remove(); + } + $reprocess = true; + continue; + } + + // do errors, in REVERSE $j order: a,b,c with + $c = count($skipped_tags); + if ($e) { + for ($j = $c - 1; $j > 0; $j--) { + // notice we exclude $j == 0, i.e. the current ending tag, from + // the errors... [TagClosedSuppress] + if (!isset($skipped_tags[$j]->armor['MakeWellFormed_TagClosedError'])) { + $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by element end', $skipped_tags[$j]); + } + } + } + + // insert tags, in FORWARD $j order: c,b,a with + $replace = array($token); + for ($j = 1; $j < $c; $j++) { + // ...as well as from the insertions + $new_token = new HTMLPurifier_Token_End($skipped_tags[$j]->name); + $new_token->start = $skipped_tags[$j]; + array_unshift($replace, $new_token); + if (isset($definition->info[$new_token->name]) && $definition->info[$new_token->name]->formatting) { + // [TagClosedAuto] + $element = clone $skipped_tags[$j]; + $element->carryover = true; + $element->armor['MakeWellFormed_TagClosedError'] = true; + $replace[] = $element; + } + } + $token = $this->processToken($replace); + $reprocess = true; + continue; + } + + $context->destroy('CurrentToken'); + $context->destroy('CurrentNesting'); + $context->destroy('InputZipper'); + + unset($this->injectors, $this->stack, $this->tokens); + return $zipper->toArray($token); + } + + /** + * Processes arbitrary token values for complicated substitution patterns. + * In general: + * + * If $token is an array, it is a list of tokens to substitute for the + * current token. These tokens then get individually processed. If there + * is a leading integer in the list, that integer determines how many + * tokens from the stream should be removed. + * + * If $token is a regular token, it is swapped with the current token. + * + * If $token is false, the current token is deleted. + * + * If $token is an integer, that number of tokens (with the first token + * being the current one) will be deleted. + * + * @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) + { + // 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'); + } + + // $token is now an array with the following form: + // array(number nodes to delete, new node 1, new node 2, ...) + + $delete = array_shift($token); + list($old, $r) = $this->zipper->splice($this->token, $delete, $token); + + if ($injector > -1) { + // determine appropriate skips + $oldskip = isset($old[0]) ? $old[0]->skip : array(); + foreach ($token as $object) { + $object->skip = $oldskip; + $object->skip[$injector] = true; + } + } + + 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) + { + // 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() + { + return $this->zipper->delete(); + } +} + + + + + +/** + * Removes all unrecognized tags from the list of tokens. + * + * This strategy iterates through all the tokens and removes unrecognized + * tokens. If a token is not recognized but a TagTransform is defined for + * that element, the element will be transformed accordingly. + */ + +class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy +{ + + /** + * @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'); + + // currently only used to determine if comments should be kept + $trusted = $config->get('HTML.Trusted'); + $comment_lookup = $config->get('HTML.AllowedComments'); + $comment_regexp = $config->get('HTML.AllowedCommentsRegexp'); + $check_comments = $comment_lookup !== array() || $comment_regexp !== null; + + $remove_script_contents = $config->get('Core.RemoveScriptContents'); + $hidden_elements = $config->get('Core.HiddenElements'); + + // remove script contents compatibility + if ($remove_script_contents === true) { + $hidden_elements['script'] = true; + } elseif ($remove_script_contents === false && isset($hidden_elements['script'])) { + unset($hidden_elements['script']); + } + + $attr_validator = new HTMLPurifier_AttrValidator(); + + // removes tokens until it reaches a closing tag with its value + $remove_until = false; + + // converts comments into text tokens when this is equal to a tag name + $textify_comments = false; + + $token = false; + $context->register('CurrentToken', $token); + + $e = false; + if ($config->get('Core.CollectErrors')) { + $e =& $context->get('ErrorCollector'); + } + + foreach ($tokens as $token) { + if ($remove_until) { + if (empty($token->is_tag) || $token->name !== $remove_until) { + continue; + } + } + if (!empty($token->is_tag)) { + // DEFINITION CALL + + // before any processing, try to transform the element + 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); + } + } + + 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) && + $definition->info[$token->name]->required_attr && + ($token->name != 'img' || $remove_invalid_img) // ensure config option still works + ) { + $attr_validator->validateToken($token, $config, $context); + $ok = true; + foreach ($definition->info[$token->name]->required_attr as $name) { + if (!isset($token->attr[$name])) { + $ok = false; + break; + } + } + if (!$ok) { + if ($e) { + $e->send( + E_ERROR, + 'Strategy_RemoveForeignElements: Missing required attribute', + $name + ); + } + continue; + } + $token->armor['ValidateAttributes'] = true; + } + + if (isset($hidden_elements[$token->name]) && $token instanceof HTMLPurifier_Token_Start) { + $textify_comments = $token->name; + } elseif ($token->name === $textify_comments && $token instanceof HTMLPurifier_Token_End) { + $textify_comments = false; + } + + } elseif ($escape_invalid_tags) { + // invalid tag, generate HTML representation and insert in + if ($e) { + $e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text'); + } + $token = new HTMLPurifier_Token_Text( + $generator->generateFromToken($token) + ); + } else { + // check if we need to destroy all of the tag's children + // CAN BE GENERICIZED + if (isset($hidden_elements[$token->name])) { + if ($token instanceof HTMLPurifier_Token_Start) { + $remove_until = $token->name; + } elseif ($token instanceof HTMLPurifier_Token_Empty) { + // do nothing: we're still looking + } else { + $remove_until = false; + } + if ($e) { + $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed'); + } + } else { + if ($e) { + $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed'); + } + } + continue; + } + } elseif ($token instanceof HTMLPurifier_Token_Comment) { + // textify comments in script tags when they are allowed + if ($textify_comments !== false) { + $data = $token->data; + $token = new HTMLPurifier_Token_Text($data); + } elseif ($trusted || $check_comments) { + // always cleanup comments + $trailing_hyphen = false; + if ($e) { + // perform check whether or not there's a trailing hyphen + if (substr($token->data, -1) == '-') { + $trailing_hyphen = true; + } + } + $token->data = rtrim($token->data, '-'); + $found_double_hyphen = false; + while (strpos($token->data, '--') !== false) { + $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)))) { + // OK good + if ($e) { + if ($trailing_hyphen) { + $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'); + } + } + } else { + if ($e) { + $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed'); + } + continue; + } + } else { + // strip comments + if ($e) { + $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed'); + } + continue; + } + } elseif ($token instanceof HTMLPurifier_Token_Text) { + } else { + continue; + } + $result[] = $token; + } + if ($remove_until && $e) { + // 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; + } +} + + + + + +/** + * Validate all attributes in the tokens. + */ + +class HTMLPurifier_Strategy_ValidateAttributes extends HTMLPurifier_Strategy +{ + + /** + * @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(); + + $token = false; + $context->register('CurrentToken', $token); + + foreach ($tokens as $key => $token) { + + // only process tokens that have attributes, + // namely start and empty tags + if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty) { + continue; + } + + // skip tokens that are armored + if (!empty($token->armor['ValidateAttributes'])) { + continue; + } + + // note that we have no facilities here for removing tokens + $validator->validateToken($token, $config, $context); + } + $context->destroy('CurrentToken'); + return $tokens; + } +} + + + + + +/** + * Transforms FONT tags to the proper form (SPAN with CSS styling) + * + * This transformation takes the three proprietary attributes of FONT and + * transforms them into their corresponding CSS attributes. These are color, + * face, and size. + * + * @note Size is an interesting case because it doesn't map cleanly to CSS. + * Thanks to + * http://style.cleverchimp.com/font_size_intervals/altintervals.html + * for reasonable mappings. + * @warning This doesn't work completely correctly; specifically, this + * TagTransform operates before well-formedness is enforced, so + * the "active formatting elements" algorithm doesn't get applied. + */ +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', + '2' => 'small', + '3' => 'medium', + '4' => 'large', + '5' => 'x-large', + '6' => 'xx-large', + '7' => '300%', + '-1' => 'smaller', + '-2' => '60%', + '+1' => 'larger', + '+2' => '150%', + '+3' => '200%', + '+4' => '300%' + ); + + /** + * @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; + return $new_tag; + } + + $attr = $tag->attr; + $prepend_style = ''; + + // handle color transform + if (isset($attr['color'])) { + $prepend_style .= 'color:' . $attr['color'] . ';'; + unset($attr['color']); + } + + // handle face transform + if (isset($attr['face'])) { + $prepend_style .= 'font-family:' . $attr['face'] . ';'; + unset($attr['face']); + } + + // handle size transform + if (isset($attr['size'])) { + // 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'; + } + } else { + $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']] . ';'; + } + unset($attr['size']); + } + + if ($prepend_style) { + $attr['style'] = isset($attr['style']) ? + $prepend_style . $attr['style'] : + $prepend_style; + } + + $new_tag = clone $tag; + $new_tag->name = $this->transform_to; + $new_tag->attr = $attr; + + return $new_tag; + } +} + + + + + +/** + * Simple transformation, just change tag name to something else, + * and possibly add some styling. This will cover most of the deprecated + * tag cases. + */ +class HTMLPurifier_TagTransform_Simple extends HTMLPurifier_TagTransform +{ + /** + * @type string + */ + protected $style; + + /** + * @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) + { + $this->transform_to = $transform_to; + $this->style = $style; + } + + /** + * @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) && + ($new_tag instanceof HTMLPurifier_Token_Start || $new_tag instanceof HTMLPurifier_Token_Empty) + ) { + $this->prependCSS($new_tag->attr, $this->style); + } + return $new_tag; + } +} + + + + + +/** + * Concrete comment token class. Generally will be ignored. + */ +class HTMLPurifier_Token_Comment extends HTMLPurifier_Token +{ + /** + * 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 toNode() { + return new HTMLPurifier_Node_Comment($this->data, $this->line, $this->col); + } +} + + + + + +/** + * Abstract class of a tag token (start, end or empty), and its behavior. + */ +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; + + /** + * 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 tag's attributes. + * @type array + */ + public $attr = array(); + + /** + * Non-overloaded constructor, which lower-cases passed tag name. + * + * @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 + if (!ctype_lower($key)) { + $new_key = strtolower($key); + if (!isset($attr[$new_key])) { + $attr[$new_key] = $attr[$key]; + } + if ($new_key !== $key) { + unset($attr[$key]); + } + } + } + $this->attr = $attr; + $this->line = $line; + $this->col = $col; + $this->armor = $armor; + } + + public function toNode() { + return new HTMLPurifier_Node_Element($this->name, $this->attr, $this->line, $this->col, $this->armor); + } +} + + + + + +/** + * Concrete empty token class. + */ +class HTMLPurifier_Token_Empty extends HTMLPurifier_Token_Tag +{ + public function toNode() { + $n = parent::toNode(); + $n->empty = true; + return $n; + } +} + + + + + +/** + * Concrete end token class. + * + * @warning This class accepts attributes even though end tags cannot. This + * is for optimization reasons, as under normal circumstances, the Lexers + * do not pass attributes. + */ +class HTMLPurifier_Token_End extends HTMLPurifier_Token_Tag +{ + /** + * 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!"); + } +} + + + + + +/** + * Concrete start token class. + */ +class HTMLPurifier_Token_Start extends HTMLPurifier_Token_Tag +{ +} + + + + + +/** + * 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_Token_Text extends HTMLPurifier_Token +{ + + /** + * @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 string $data String parsed character data. + * @param int $line + * @param int $col + */ + public function __construct($data, $line = null, $col = null) + { + $this->data = $data; + $this->is_whitespace = ctype_space($data); + $this->line = $line; + $this->col = $col; + } + + public function toNode() { + return new HTMLPurifier_Node_Text($this->data, $this->is_whitespace, $this->line, $this->col); + } +} + + + + + +class HTMLPurifier_URIFilter_DisableExternal extends HTMLPurifier_URIFilter +{ + /** + * @type string + */ + public $name = 'DisableExternal'; + + /** + * @type array + */ + protected $ourHostParts = false; + + /** + * @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)); + } + } + + /** + * @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; + } + } + return true; + } +} + + + + + +class HTMLPurifier_URIFilter_DisableExternalResources extends HTMLPurifier_URIFilter_DisableExternal +{ + /** + * @type string + */ + public $name = 'DisableExternalResources'; + + /** + * @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); + } +} + + + + + +class HTMLPurifier_URIFilter_DisableResources extends HTMLPurifier_URIFilter +{ + /** + * @type string + */ + public $name = 'DisableResources'; + + /** + * @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); + } +} + + + + + +// It's not clear to me whether or not Punycode means that hostnames +// do not have canonical forms anymore. As far as I can tell, it's +// not a problem (punycoding should be identity when no Unicode +// 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(); + + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function prepare($config) + { + $this->blacklist = $config->get('URI.HostBlacklist'); + return true; + } + + /** + * @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; + } + } + return true; + } +} + + + + + +// does not support network paths + +class HTMLPurifier_URIFilter_MakeAbsolute extends HTMLPurifier_URIFilter +{ + /** + * @type string + */ + public $name = 'MakeAbsolute'; + + /** + * @type + */ + protected $base; + + /** + * @type array + */ + protected $basePathStack = array(); + + /** + * @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 + ); + return false; + } + $this->base->fragment = null; // fragment is invalid for base URI + $stack = explode('/', $this->base->path); + array_pop($stack); // discard last segment + $stack = $this->_collapseStack($stack); // do pre-parsing + $this->basePathStack = $stack; + return true; + } + + /** + * @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; + } + $scheme_obj = $uri->getSchemeObj($config, $context); + if (!$scheme_obj) { + // scheme not recognized + return false; + } + if (!$scheme_obj->hierarchical) { + // non-hierarchal URI with explicit scheme, don't change + return true; + } + // special case: had a scheme but always is hierarchical and had no authority + } + if (!is_null($uri->host)) { + // network path, don't bother + return true; + } + if ($uri->path === '') { + $uri->path = $this->base->path; + } elseif ($uri->path[0] !== '/') { + // relative path, needs more complicated processing + $stack = explode('/', $uri->path); + $new_stack = array_merge($this->basePathStack, $stack); + if ($new_stack[0] !== '' && !is_null($this->base->host)) { + array_unshift($new_stack, ''); + } + $new_stack = $this->_collapseStack($new_stack); + $uri->path = implode('/', $new_stack); + } else { + // absolute path, but still we should collapse + $uri->path = implode('/', $this->_collapseStack(explode('/', $uri->path))); + } + // 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; + } + return true; + } + + /** + * Resolve dots and double-dots in a path stack + * @param array $stack + * @return array + */ + 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] == '..') { + if (!empty($result)) { + $segment = array_pop($result); + if ($segment === '' && empty($result)) { + // error case: attempted to back out too far: + // restore the leading slash + $result[] = ''; + } elseif ($segment === '..') { + $result[] = '..'; // cannot remove .. with .. + } + } else { + // relative path, preserve the double-dots + $result[] = '..'; + } + $is_folder = true; + continue; + } + if ($stack[$i] == '.') { + // silently absorb + $is_folder = true; + continue; + } + $result[] = $stack[$i]; + } + if ($is_folder) { + $result[] = ''; + } + return $result; + } +} + + + + + +class HTMLPurifier_URIFilter_Munge extends HTMLPurifier_URIFilter +{ + /** + * @type string + */ + public $name = 'Munge'; + + /** + * @type bool + */ + public $post = true; + + /** + * @type string + */ + private $target; + + /** + * @type HTMLPurifier_URIParser + */ + private $parser; + + /** + * @type bool + */ + private $doEmbed; + + /** + * @type string + */ + private $secretKey; + + /** + * @type array + */ + protected $replace = array(); + + /** + * @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; + } + + /** + * @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 + + $this->makeReplace($uri, $config, $context); + $this->replace = array_map('rawurlencode', $this->replace); + + $new_uri = strtr($this->target, $this->replace); + $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; + } + $uri = $new_uri; // overwrite + return true; + } + + /** + * @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; + $this->replace['%r'] = $context->get('EmbeddedURI', true); + $token = $context->get('CurrentToken', true); + $this->replace['%n'] = $token ? $token->name : null; + $this->replace['%m'] = $context->get('CurrentAttr', true); + $this->replace['%p'] = $context->get('CurrentCSSProperty', true); + // not always available + if ($this->secretKey) { + $this->replace['%t'] = hash_hmac("sha256", $string, $this->secretKey); + } + } +} + + + + + +/** + * Implements safety checks for safe iframes. + * + * @warning This filter is *critical* for ensuring that %HTML.SafeIframe + * works safely. + */ +class HTMLPurifier_URIFilter_SafeIframe extends HTMLPurifier_URIFilter +{ + /** + * @type string + */ + public $name = 'SafeIframe'; + + /** + * @type bool + */ + public $always_load = true; + + /** + * @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. + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function prepare($config) + { + $this->regexp = $config->get('URI.SafeIframeRegexp'); + return true; + } + + /** + * @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; + } + // check if the filter should actually trigger + if (!$context->get('EmbeddedURI', true)) { + return true; + } + $token = $context->get('CurrentToken', true); + if (!($token && $token->name == 'iframe')) { + return true; + } + // check if we actually have some whitelists enabled + if ($this->regexp === null) { + return false; + } + // actually check the whitelists + return preg_match($this->regexp, $uri->toString()); + } +} + + + + + +/** + * Implements data: URI for base64 encoded images supported by GD. + */ +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; + + /** + * @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; + $content_type = null; + if (count($result) == 2) { + list($metadata, $data) = $result; + // do some legwork on the metadata + $metas = explode(';', $metadata); + while (!empty($metas)) { + $cur = array_shift($metas); + if ($cur == 'base64') { + $is_base64 = true; + break; + } + if (substr($cur, 0, 8) == 'charset=') { + // doesn't match if there are arbitrary spaces, but + // whatever dude + if ($charset !== null) { + continue; + } // garbage + $charset = substr($cur, 8); // not used + } else { + if ($content_type !== null) { + continue; + } // garbage + $content_type = $cur; + } + } + } else { + $data = $result[0]; + } + if ($content_type !== null && empty($this->allowed_types[$content_type])) { + return false; + } + if ($charset !== null) { + // error; we don't allow plaintext stuff + $charset = null; + } + $data = rawurldecode($data); + if ($is_base64) { + $raw_data = base64_decode($data); + } else { + $raw_data = $data; + } + // XXX probably want to refactor this into a general mechanism + // for filtering arbitrary content types + $file = tempnam("/tmp", ""); + file_put_contents($file, $raw_data); + if (function_exists('exif_imagetype')) { + $image_code = exif_imagetype($file); + unlink($file); + } elseif (function_exists('getimagesize')) { + set_error_handler(array($this, 'muteErrorHandler')); + $info = getimagesize($file); + restore_error_handler(); + unlink($file); + if ($info == false) { + return false; + } + $image_code = $info[2]; + } else { + trigger_error("could not find exif_imagetype or getimagesize functions", E_USER_ERROR); + } + $real_content_type = image_type_to_mime_type($image_code); + 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; + } + $content_type = $real_content_type; + } + // ok, it's kosher, rewrite what we need + $uri->userinfo = null; + $uri->host = null; + $uri->port = null; + $uri->fragment = null; + $uri->query = null; + $uri->path = "$content_type;base64," . base64_encode($raw_data); + return true; + } + + /** + * @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. + * @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. + * @type bool + */ + public $may_omit_host = true; + + /** + * @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; + // While it seems to work on Firefox, the querystring has + // no possible effect and is thus stripped. + $uri->query = null; + return true; + } +} + + + + + +/** + * Validates ftp (File Transfer Protocol) URIs as defined by generic RFC 1738. + */ +class HTMLPurifier_URIScheme_ftp extends HTMLPurifier_URIScheme +{ + /** + * @type int + */ + public $default_port = 21; + + /** + * @type bool + */ + public $browsable = true; // usually + + /** + * @type bool + */ + public $hierarchical = true; + + /** + * @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 + if ($semicolon_pos !== false) { + $type = substr($uri->path, $semicolon_pos + 1); // no semicolon + $uri->path = substr($uri->path, 0, $semicolon_pos); + $type_ret = ''; + if (strpos($type, '=') !== false) { + // figure out whether or not the declaration is correct + list($key, $typecode) = explode('=', $type, 2); + if ($key !== 'type') { + // invalid key, tack it back on encoded + $uri->path .= '%3B' . $type; + } elseif ($typecode === 'a' || $typecode === 'i' || $typecode === 'd') { + $type_ret = ";type=$typecode"; + } + } else { + $uri->path .= '%3B' . $type; + } + $uri->path = str_replace(';', '%3B', $uri->path); + $uri->path .= $type_ret; + } + return true; + } +} + + + + + +/** + * Validates http (HyperText Transfer Protocol) as defined by RFC 2616 + */ +class HTMLPurifier_URIScheme_http extends HTMLPurifier_URIScheme +{ + /** + * @type int + */ + public $default_port = 80; + + /** + * @type bool + */ + public $browsable = true; + + /** + * @type bool + */ + public $hierarchical = true; + + /** + * @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; + } +} + + + + + +/** + * Validates https (Secure HTTP) according to http scheme. + */ +class HTMLPurifier_URIScheme_https extends HTMLPurifier_URIScheme_http +{ + /** + * @type int + */ + public $default_port = 443; + /** + * @type bool + */ + public $secure = true; +} + + + + + +// VERY RELAXED! Shouldn't cause problems, not even Firefox checks if the +// email is valid, but be careful! + +/** + * Validates mailto (for E-mail) according to RFC 2368 + * @todo Validate the email address + * @todo Filter allowed query parameters + */ + +class HTMLPurifier_URIScheme_mailto extends HTMLPurifier_URIScheme +{ + /** + * @type bool + */ + public $browsable = false; + + /** + * @type bool + */ + public $may_omit_host = true; + + /** + * @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; + } +} + + + + + +/** + * Validates news (Usenet) as defined by generic RFC 1738 + */ +class HTMLPurifier_URIScheme_news extends HTMLPurifier_URIScheme +{ + /** + * @type bool + */ + public $browsable = false; + + /** + * @type bool + */ + public $may_omit_host = true; + + /** + * @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; + // typecode check needed on path + return true; + } +} + + + + + +/** + * Validates nntp (Network News Transfer Protocol) as defined by generic RFC 1738 + */ +class HTMLPurifier_URIScheme_nntp extends HTMLPurifier_URIScheme +{ + /** + * @type int + */ + public $default_port = 119; + + /** + * @type bool + */ + public $browsable = false; + + /** + * @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; + return true; + } +} + + + + + +/** + * Performs safe variable parsing based on types which can be used by + * users. This may not be able to represent all possible data inputs, + * however. + */ +class HTMLPurifier_VarParser_Flexible extends HTMLPurifier_VarParser +{ + /** + * @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: + return $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; + } + return $var; + case self::BOOL: + if (is_int($var) && ($var === 0 || $var === 1)) { + $var = (bool)$var; + } elseif (is_string($var)) { + if ($var == 'on' || $var == 'true' || $var == '1') { + $var = true; + } elseif ($var == 'off' || $var == 'false' || $var == '0') { + $var = false; + } else { + throw new HTMLPurifier_VarParserException("Unrecognized value '$var' for $type"); + } + } + return $var; + 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 (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); + } else { + $var = preg_split('/(,|[\n\r]+)/', $var); + } + // remove spaces + 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; + } + $nvar[trim($c[0])] = trim($c[1]); + } + $var = $nvar; + } + } + if (!is_array($var)) { + break; + } + $keys = array_keys($var); + if ($keys === array_keys($keys)) { + if ($type == self::ALIST) { + return $var; + } elseif ($type == self::LOOKUP) { + $new = array(); + foreach ($var as $key) { + $new[$key] = true; + } + return $new; + } else { + break; + } + } + if ($type === self::ALIST) { + trigger_error("Array list did not have consecutive integer indexes", E_USER_WARNING); + return array_values($var); + } + 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 + ); + } + $var[$key] = true; + } + } + return $var; + default: + $this->errorInconsistent(__CLASS__, $type); + } + $this->errorGeneric($var, $type); + } +} + + + + + +/** + * This variable parser uses PHP's internal code engine. Because it does + * this, it can represent all inputs; however, it is dangerous and cannot + * be used by users. + */ +class HTMLPurifier_VarParser_Native extends HTMLPurifier_VarParser +{ + + /** + * @param mixed $var + * @param int $type + * @param bool $allow_null + * @return null|string + */ + protected function parseImplementation($var, $type, $allow_null) + { + return $this->evalExpression($var); + } + + /** + * @param string $expr + * @return mixed + * @throws HTMLPurifier_VarParserException + */ + protected function evalExpression($expr) + { + $var = null; + $result = eval("\$var = $expr;"); + if ($result === false) { + throw new HTMLPurifier_VarParserException("Fatal error in evaluated code"); + } + return $var; + } +} + diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php index 92bd63095..c05668a70 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php @@ -1,44 +1,44 @@ -directives as $d) { - $schema->add( - $d->id->key, - $d->default, - $d->type, - $d->typeAllowsNull - ); - if ($d->allowed !== null) { - $schema->addAllowedValues( - $d->id->key, - $d->allowed - ); - } - foreach ($d->aliases as $alias) { - $schema->addAlias( - $alias->key, - $d->id->key - ); - } - if ($d->valueAliases !== null) { - $schema->addValueAliases( - $d->id->key, - $d->valueAliases - ); - } - } - $schema->postProcess(); - return $schema; - } - -} - -// vim: et sw=4 sts=4 +directives as $d) { + $schema->add( + $d->id->key, + $d->default, + $d->type, + $d->typeAllowsNull + ); + if ($d->allowed !== null) { + $schema->addAllowedValues( + $d->id->key, + $d->allowed + ); + } + foreach ($d->aliases as $alias) { + $schema->addAlias( + $alias->key, + $d->id->key + ); + } + if ($d->valueAliases !== null) { + $schema->addValueAliases( + $d->id->key, + $d->valueAliases + ); + } + } + $schema->postProcess(); + return $schema; + } + +} + +// vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Builder/Xml.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Builder/Xml.php index 7924b6adb..244561a37 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Builder/Xml.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Builder/Xml.php @@ -1,106 +1,106 @@ -startElement('div'); - - $purifier = HTMLPurifier::getInstance(); - $html = $purifier->purify($html); - $this->writeAttribute('xmlns', 'http://www.w3.org/1999/xhtml'); - $this->writeRaw($html); - - $this->endElement(); // div - } - - protected function export($var) { - if ($var === array()) return 'array()'; - return var_export($var, true); - } - - public function build($interchange) { - // global access, only use as last resort - $this->interchange = $interchange; - - $this->setIndent(true); - $this->startDocument('1.0', 'UTF-8'); - $this->startElement('configdoc'); - $this->writeElement('title', $interchange->name); - - foreach ($interchange->directives as $directive) { - $this->buildDirective($directive); - } - - if ($this->namespace) $this->endElement(); // namespace - - $this->endElement(); // configdoc - $this->flush(); - } - - public function buildDirective($directive) { - - // Kludge, although I suppose having a notion of a "root namespace" - // certainly makes things look nicer when documentation is built. - // Depends on things being sorted. - if (!$this->namespace || $this->namespace !== $directive->id->getRootNamespace()) { - if ($this->namespace) $this->endElement(); // namespace - $this->namespace = $directive->id->getRootNamespace(); - $this->startElement('namespace'); - $this->writeAttribute('id', $this->namespace); - $this->writeElement('name', $this->namespace); - } - - $this->startElement('directive'); - $this->writeAttribute('id', $directive->id->toString()); - - $this->writeElement('name', $directive->id->getDirective()); - - $this->startElement('aliases'); - foreach ($directive->aliases as $alias) $this->writeElement('alias', $alias->toString()); - $this->endElement(); // aliases - - $this->startElement('constraints'); - if ($directive->version) $this->writeElement('version', $directive->version); - $this->startElement('type'); - if ($directive->typeAllowsNull) $this->writeAttribute('allow-null', 'yes'); - $this->text($directive->type); - $this->endElement(); // type - if ($directive->allowed) { - $this->startElement('allowed'); - foreach ($directive->allowed as $value => $x) $this->writeElement('value', $value); - $this->endElement(); // allowed - } - $this->writeElement('default', $this->export($directive->default)); - $this->writeAttribute('xml:space', 'preserve'); - if ($directive->external) { - $this->startElement('external'); - foreach ($directive->external as $project) $this->writeElement('project', $project); - $this->endElement(); - } - $this->endElement(); // constraints - - if ($directive->deprecatedVersion) { - $this->startElement('deprecated'); - $this->writeElement('version', $directive->deprecatedVersion); - $this->writeElement('use', $directive->deprecatedUse->toString()); - $this->endElement(); // deprecated - } - - $this->startElement('description'); - $this->writeHTMLDiv($directive->description); - $this->endElement(); // description - - $this->endElement(); // directive - } - -} - -// vim: et sw=4 sts=4 +startElement('div'); + + $purifier = HTMLPurifier::getInstance(); + $html = $purifier->purify($html); + $this->writeAttribute('xmlns', 'http://www.w3.org/1999/xhtml'); + $this->writeRaw($html); + + $this->endElement(); // div + } + + protected function export($var) { + if ($var === array()) return 'array()'; + return var_export($var, true); + } + + public function build($interchange) { + // global access, only use as last resort + $this->interchange = $interchange; + + $this->setIndent(true); + $this->startDocument('1.0', 'UTF-8'); + $this->startElement('configdoc'); + $this->writeElement('title', $interchange->name); + + foreach ($interchange->directives as $directive) { + $this->buildDirective($directive); + } + + if ($this->namespace) $this->endElement(); // namespace + + $this->endElement(); // configdoc + $this->flush(); + } + + public function buildDirective($directive) { + + // Kludge, although I suppose having a notion of a "root namespace" + // certainly makes things look nicer when documentation is built. + // Depends on things being sorted. + if (!$this->namespace || $this->namespace !== $directive->id->getRootNamespace()) { + if ($this->namespace) $this->endElement(); // namespace + $this->namespace = $directive->id->getRootNamespace(); + $this->startElement('namespace'); + $this->writeAttribute('id', $this->namespace); + $this->writeElement('name', $this->namespace); + } + + $this->startElement('directive'); + $this->writeAttribute('id', $directive->id->toString()); + + $this->writeElement('name', $directive->id->getDirective()); + + $this->startElement('aliases'); + foreach ($directive->aliases as $alias) $this->writeElement('alias', $alias->toString()); + $this->endElement(); // aliases + + $this->startElement('constraints'); + if ($directive->version) $this->writeElement('version', $directive->version); + $this->startElement('type'); + if ($directive->typeAllowsNull) $this->writeAttribute('allow-null', 'yes'); + $this->text($directive->type); + $this->endElement(); // type + if ($directive->allowed) { + $this->startElement('allowed'); + foreach ($directive->allowed as $value => $x) $this->writeElement('value', $value); + $this->endElement(); // allowed + } + $this->writeElement('default', $this->export($directive->default)); + $this->writeAttribute('xml:space', 'preserve'); + if ($directive->external) { + $this->startElement('external'); + foreach ($directive->external as $project) $this->writeElement('project', $project); + $this->endElement(); + } + $this->endElement(); // constraints + + if ($directive->deprecatedVersion) { + $this->startElement('deprecated'); + $this->writeElement('version', $directive->deprecatedVersion); + $this->writeElement('use', $directive->deprecatedUse->toString()); + $this->endElement(); // deprecated + } + + $this->startElement('description'); + $this->writeHTMLDiv($directive->description); + $this->endElement(); // description + + $this->endElement(); // directive + } + +} + +// vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Exception.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Exception.php index 1abdcfc06..2671516c5 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Exception.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Exception.php @@ -1,11 +1,11 @@ - 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) - { - if (isset($this->directives[$i = $directive->id->toString()])) { - throw new HTMLPurifier_ConfigSchema_Exception("Cannot redefine directive '$i'"); - } - $this->directives[$i] = $directive; - } - - /** - * Convenience function to perform standard validation. Throws exception - * on failed validation. - */ - public function validate() - { - $validator = new HTMLPurifier_ConfigSchema_Validator(); - return $validator->validate($this); - } -} - -// vim: et sw=4 sts=4 + 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) + { + if (isset($this->directives[$i = $directive->id->toString()])) { + throw new HTMLPurifier_ConfigSchema_Exception("Cannot redefine directive '$i'"); + } + $this->directives[$i] = $directive; + } + + /** + * Convenience function to perform standard validation. Throws exception + * on failed validation. + */ + 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/Interchange/Directive.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Interchange/Directive.php index 2dced1f54..ac8be0d97 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Interchange/Directive.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Interchange/Directive.php @@ -1,77 +1,77 @@ - true). - * Null if all values are allowed. - */ - public $allowed; - - /** - * List of aliases for the directive, - * e.g. array(new HTMLPurifier_ConfigSchema_Interchange_Id('Ns', 'Dir'))). - */ - public $aliases = array(); - - /** - * Hash of value aliases, e.g. array('alt' => 'real'). Null if value - * aliasing is disabled (necessary for non-scalar types). - */ - public $valueAliases; - - /** - * Version of HTML Purifier the directive was introduced, e.g. '1.3.1'. - * Null if the directive has always existed. - */ - public $version; - - /** - * ID of directive that supercedes this old directive, is an instance - * of HTMLPurifier_ConfigSchema_Interchange_Id. Null if not deprecated. - */ - public $deprecatedUse; - - /** - * Version of HTML Purifier this directive was deprecated. Null if not - * deprecated. - */ - public $deprecatedVersion; - - /** - * List of external projects this directive depends on, e.g. array('CSSTidy'). - */ - public $external = array(); - -} - -// vim: et sw=4 sts=4 + true). + * Null if all values are allowed. + */ + public $allowed; + + /** + * List of aliases for the directive, + * e.g. array(new HTMLPurifier_ConfigSchema_Interchange_Id('Ns', 'Dir'))). + */ + public $aliases = array(); + + /** + * Hash of value aliases, e.g. array('alt' => 'real'). Null if value + * aliasing is disabled (necessary for non-scalar types). + */ + public $valueAliases; + + /** + * Version of HTML Purifier the directive was introduced, e.g. '1.3.1'. + * Null if the directive has always existed. + */ + public $version; + + /** + * ID of directive that supercedes this old directive, is an instance + * of HTMLPurifier_ConfigSchema_Interchange_Id. Null if not deprecated. + */ + public $deprecatedUse; + + /** + * Version of HTML Purifier this directive was deprecated. Null if not + * deprecated. + */ + public $deprecatedVersion; + + /** + * List of external projects this directive depends on, e.g. array('CSSTidy'). + */ + public $external = array(); + +} + +// vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Interchange/Id.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Interchange/Id.php index 404c4042e..b9b3c6f5c 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Interchange/Id.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Interchange/Id.php @@ -1,37 +1,37 @@ -key = $key; - } - - /** - * @warning This is NOT magic, to ensure that people don't abuse SPL and - * cause problems for PHP 5.0 support. - */ - public function toString() { - return $this->key; - } - - public function getRootNamespace() { - return substr($this->key, 0, strpos($this->key, ".")); - } - - public function getDirective() { - return substr($this->key, strpos($this->key, ".") + 1); - } - - public static function make($id) { - return new HTMLPurifier_ConfigSchema_Interchange_Id($id); - } - -} - -// vim: et sw=4 sts=4 +key = $key; + } + + /** + * @warning This is NOT magic, to ensure that people don't abuse SPL and + * cause problems for PHP 5.0 support. + */ + public function toString() { + return $this->key; + } + + public function getRootNamespace() { + return substr($this->key, 0, strpos($this->key, ".")); + } + + public function getDirective() { + return substr($this->key, strpos($this->key, ".") + 1); + } + + public static function make($id) { + return new HTMLPurifier_ConfigSchema_Interchange_Id($id); + } + +} + +// 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 fe9b3268f..655e6dd1b 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/InterchangeBuilder.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/InterchangeBuilder.php @@ -1,226 +1,226 @@ -varParser = $varParser ? $varParser : new HTMLPurifier_VarParser_Native(); - } - - /** - * @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); - } - - /** - * @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']; - } - - $files = array(); - $dh = opendir($dir); - while (false !== ($file = readdir($dh))) { - if (!$file || $file[0] == '.' || strrchr($file, '.') !== '.txt') { - continue; - } - $files[] = $file; - } - closedir($dh); - - sort($files); - foreach ($files as $file) { - $this->buildFile($interchange, $dir . '/' . $file); - } - return $interchange; - } - - /** - * @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)) - ); - } - - /** - * Builds an interchange object based on a hash. - * @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) - { - if (!$hash instanceof HTMLPurifier_StringHash) { - $hash = new HTMLPurifier_StringHash($hash); - } - if (!isset($hash['ID'])) { - throw new HTMLPurifier_ConfigSchema_Exception('Hash does not have any ID'); - } - if (strpos($hash['ID'], '.') === false) { - if (count($hash) == 2 && isset($hash['DESCRIPTION'])) { - $hash->offsetGet('DESCRIPTION'); // prevent complaining - } else { - throw new HTMLPurifier_ConfigSchema_Exception('All directives must have a namespace'); - } - } else { - $this->buildDirective($interchange, $hash); - } - $this->_findUnused($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: - $directive->id = $this->id($hash->offsetGet('ID')); - $id = $directive->id->toString(); // convenience - - if (isset($hash['TYPE'])) { - $type = explode('/', $hash->offsetGet('TYPE')); - if (isset($type[1])) { - $directive->typeAllowsNull = true; - } - $directive->type = $type[0]; - } else { - throw new HTMLPurifier_ConfigSchema_Exception("TYPE in directive hash '$id' not defined"); - } - - if (isset($hash['DEFAULT'])) { - try { - $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'"); - } - } - - if (isset($hash['DESCRIPTION'])) { - $directive->description = $hash->offsetGet('DESCRIPTION'); - } - - if (isset($hash['ALLOWED'])) { - $directive->allowed = $this->lookup($this->evalArray($hash->offsetGet('ALLOWED'))); - } - - if (isset($hash['VALUE-ALIASES'])) { - $directive->valueAliases = $this->evalArray($hash->offsetGet('VALUE-ALIASES')); - } - - if (isset($hash['ALIASES'])) { - $raw_aliases = trim($hash->offsetGet('ALIASES')); - $aliases = preg_split('/\s*,\s*/', $raw_aliases); - foreach ($aliases as $alias) { - $directive->aliases[] = $this->id($alias); - } - } - - if (isset($hash['VERSION'])) { - $directive->version = $hash->offsetGet('VERSION'); - } - - if (isset($hash['DEPRECATED-USE'])) { - $directive->deprecatedUse = $this->id($hash->offsetGet('DEPRECATED-USE')); - } - - if (isset($hash['DEPRECATED-VERSION'])) { - $directive->deprecatedVersion = $hash->offsetGet('DEPRECATED-VERSION'); - } - - if (isset($hash['EXTERNAL'])) { - $directive->external = preg_split('/\s*,\s*/', trim($hash->offsetGet('EXTERNAL'))); - } - - $interchange->addDirective($directive); - } - - /** - * Evaluates an array PHP code string without array() wrapper - * @param string $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) - { - $ret = array(); - 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) - { - 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 HTMLPurifier_StringHash $hash Hash to check. - */ - protected function _findUnused($hash) - { - $accessed = $hash->getAccessed(); - foreach ($hash as $k => $v) { - if (!isset($accessed[$k])) { - trigger_error("String hash key '$k' not used by builder", E_USER_NOTICE); - } - } - } -} - -// vim: et sw=4 sts=4 +varParser = $varParser ? $varParser : new HTMLPurifier_VarParser_Native(); + } + + /** + * @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); + } + + /** + * @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']; + } + + $files = array(); + $dh = opendir($dir); + while (false !== ($file = readdir($dh))) { + if (!$file || $file[0] == '.' || strrchr($file, '.') !== '.txt') { + continue; + } + $files[] = $file; + } + closedir($dh); + + sort($files); + foreach ($files as $file) { + $this->buildFile($interchange, $dir . '/' . $file); + } + return $interchange; + } + + /** + * @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)) + ); + } + + /** + * Builds an interchange object based on a hash. + * @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) + { + if (!$hash instanceof HTMLPurifier_StringHash) { + $hash = new HTMLPurifier_StringHash($hash); + } + if (!isset($hash['ID'])) { + throw new HTMLPurifier_ConfigSchema_Exception('Hash does not have any ID'); + } + if (strpos($hash['ID'], '.') === false) { + if (count($hash) == 2 && isset($hash['DESCRIPTION'])) { + $hash->offsetGet('DESCRIPTION'); // prevent complaining + } else { + throw new HTMLPurifier_ConfigSchema_Exception('All directives must have a namespace'); + } + } else { + $this->buildDirective($interchange, $hash); + } + $this->_findUnused($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: + $directive->id = $this->id($hash->offsetGet('ID')); + $id = $directive->id->toString(); // convenience + + if (isset($hash['TYPE'])) { + $type = explode('/', $hash->offsetGet('TYPE')); + if (isset($type[1])) { + $directive->typeAllowsNull = true; + } + $directive->type = $type[0]; + } else { + throw new HTMLPurifier_ConfigSchema_Exception("TYPE in directive hash '$id' not defined"); + } + + if (isset($hash['DEFAULT'])) { + try { + $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'"); + } + } + + if (isset($hash['DESCRIPTION'])) { + $directive->description = $hash->offsetGet('DESCRIPTION'); + } + + if (isset($hash['ALLOWED'])) { + $directive->allowed = $this->lookup($this->evalArray($hash->offsetGet('ALLOWED'))); + } + + if (isset($hash['VALUE-ALIASES'])) { + $directive->valueAliases = $this->evalArray($hash->offsetGet('VALUE-ALIASES')); + } + + if (isset($hash['ALIASES'])) { + $raw_aliases = trim($hash->offsetGet('ALIASES')); + $aliases = preg_split('/\s*,\s*/', $raw_aliases); + foreach ($aliases as $alias) { + $directive->aliases[] = $this->id($alias); + } + } + + if (isset($hash['VERSION'])) { + $directive->version = $hash->offsetGet('VERSION'); + } + + if (isset($hash['DEPRECATED-USE'])) { + $directive->deprecatedUse = $this->id($hash->offsetGet('DEPRECATED-USE')); + } + + if (isset($hash['DEPRECATED-VERSION'])) { + $directive->deprecatedVersion = $hash->offsetGet('DEPRECATED-VERSION'); + } + + if (isset($hash['EXTERNAL'])) { + $directive->external = preg_split('/\s*,\s*/', trim($hash->offsetGet('EXTERNAL'))); + } + + $interchange->addDirective($directive); + } + + /** + * Evaluates an array PHP code string without array() wrapper + * @param string $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) + { + $ret = array(); + 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) + { + 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 HTMLPurifier_StringHash $hash Hash to check. + */ + protected function _findUnused($hash) + { + $accessed = $hash->getAccessed(); + foreach ($hash as $k => $v) { + if (!isset($accessed[$k])) { + trigger_error("String hash key '$k' not used by builder", E_USER_NOTICE); + } + } + } +} + +// 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 9f14444f3..fb3127788 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Validator.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Validator.php @@ -1,248 +1,248 @@ -parser = new HTMLPurifier_VarParser(); - } - - /** - * Validates a fully-formed interchange object. - * @param HTMLPurifier_ConfigSchema_Interchange $interchange - * @return bool - */ - 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'"); - } - $this->validateDirective($directive); - } - return true; - } - - /** - * Validates a HTMLPurifier_ConfigSchema_Interchange_Id object. - * @param HTMLPurifier_ConfigSchema_Interchange_Id $id - */ - public function validateId($id) - { - $id_string = $id->toString(); - $this->context[] = "id '$id_string'"; - if (!$id instanceof HTMLPurifier_ConfigSchema_Interchange_Id) { - // handled by InterchangeBuilder - $this->error(false, 'is not an instance of HTMLPurifier_ConfigSchema_Interchange_Id'); - } - // keys are now unconstrained (we might want to narrow down to A-Za-z0-9.) - // we probably should check that it has at least one namespace - $this->with($id, 'key') - ->assertNotEmpty() - ->assertIsString(); // implicit assertIsString handled by InterchangeBuilder - array_pop($this->context); - } - - /** - * Validates a HTMLPurifier_ConfigSchema_Interchange_Directive object. - * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d - */ - public function validateDirective($d) - { - $id = $d->id->toString(); - $this->context[] = "directive '$id'"; - $this->validateId($d->id); - - $this->with($d, 'description') - ->assertNotEmpty(); - - // BEGIN - handled by InterchangeBuilder - $this->with($d, 'type') - ->assertNotEmpty(); - $this->with($d, 'typeAllowsNull') - ->assertIsBool(); - try { - // This also tests validity of $d->type - $this->parser->parse($d->default, $d->type, $d->typeAllowsNull); - } catch (HTMLPurifier_VarParserException $e) { - $this->error('default', 'had error: ' . $e->getMessage()); - } - // END - handled by InterchangeBuilder - - if (!is_null($d->allowed) || !empty($d->valueAliases)) { - // allowed and valueAliases require that we be dealing with - // strings, so check for that early. - $d_int = HTMLPurifier_VarParser::$types[$d->type]; - if (!isset(HTMLPurifier_VarParser::$stringTypes[$d_int])) { - $this->error('type', 'must be a string type when used with allowed or value aliases'); - } - } - - $this->validateDirectiveAllowed($d); - $this->validateDirectiveValueAliases($d); - $this->validateDirectiveAliases($d); - - array_pop($this->context); - } - - /** - * 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; - } - $this->with($d, 'allowed') - ->assertNotEmpty() - ->assertIsLookup(); // handled by InterchangeBuilder - if (is_string($d->default) && !isset($d->allowed[$d->default])) { - $this->error('default', 'must be an allowed value'); - } - $this->context[] = 'allowed'; - foreach ($d->allowed as $val => $x) { - if (!is_string($val)) { - $this->error("value $val", 'must be a string'); - } - } - array_pop($this->context); - } - - /** - * 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; - } - $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 ($alias === $real) { - $this->error("alias '$alias'", "must not be an alias to itself"); - } - } - if (!is_null($d->allowed)) { - foreach ($d->valueAliases as $alias => $real) { - if (isset($d->allowed[$alias])) { - $this->error("alias '$alias'", 'must not be an allowed value'); - } elseif (!isset($d->allowed[$real])) { - $this->error("alias '$alias'", 'must be an alias to an allowed value'); - } - } - } - array_pop($this->context); - } - - /** - * Extra validation if $aliases member variable of - * HTMLPurifier_ConfigSchema_Interchange_Directive is defined. - * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d - */ - public function validateDirectiveAliases($d) - { - $this->with($d, 'aliases') - ->assertIsArray(); // handled by InterchangeBuilder - $this->context[] = 'aliases'; - foreach ($d->aliases as $alias) { - $this->validateId($alias); - $s = $alias->toString(); - if (isset($this->interchange->directives[$s])) { - $this->error("alias '$s'", 'collides with another directive'); - } - if (isset($this->aliases[$s])) { - $other_directive = $this->aliases[$s]; - $this->error("alias '$s'", "collides with alias for directive '$other_directive'"); - } - $this->aliases[$s] = $d->id->toString(); - } - array_pop($this->context); - } - - // protected helper functions - - /** - * 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) - { - 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()); - } - throw new HTMLPurifier_ConfigSchema_Exception(trim($prefix . ' ' . $msg)); - } - - /** - * Returns a formatted context string. - * @return string - */ - protected function getFormattedContext() - { - return implode(' in ', array_reverse($this->context)); - } -} - -// vim: et sw=4 sts=4 +parser = new HTMLPurifier_VarParser(); + } + + /** + * Validates a fully-formed interchange object. + * @param HTMLPurifier_ConfigSchema_Interchange $interchange + * @return bool + */ + 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'"); + } + $this->validateDirective($directive); + } + return true; + } + + /** + * Validates a HTMLPurifier_ConfigSchema_Interchange_Id object. + * @param HTMLPurifier_ConfigSchema_Interchange_Id $id + */ + public function validateId($id) + { + $id_string = $id->toString(); + $this->context[] = "id '$id_string'"; + if (!$id instanceof HTMLPurifier_ConfigSchema_Interchange_Id) { + // handled by InterchangeBuilder + $this->error(false, 'is not an instance of HTMLPurifier_ConfigSchema_Interchange_Id'); + } + // keys are now unconstrained (we might want to narrow down to A-Za-z0-9.) + // we probably should check that it has at least one namespace + $this->with($id, 'key') + ->assertNotEmpty() + ->assertIsString(); // implicit assertIsString handled by InterchangeBuilder + array_pop($this->context); + } + + /** + * Validates a HTMLPurifier_ConfigSchema_Interchange_Directive object. + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d + */ + public function validateDirective($d) + { + $id = $d->id->toString(); + $this->context[] = "directive '$id'"; + $this->validateId($d->id); + + $this->with($d, 'description') + ->assertNotEmpty(); + + // BEGIN - handled by InterchangeBuilder + $this->with($d, 'type') + ->assertNotEmpty(); + $this->with($d, 'typeAllowsNull') + ->assertIsBool(); + try { + // This also tests validity of $d->type + $this->parser->parse($d->default, $d->type, $d->typeAllowsNull); + } catch (HTMLPurifier_VarParserException $e) { + $this->error('default', 'had error: ' . $e->getMessage()); + } + // END - handled by InterchangeBuilder + + if (!is_null($d->allowed) || !empty($d->valueAliases)) { + // allowed and valueAliases require that we be dealing with + // strings, so check for that early. + $d_int = HTMLPurifier_VarParser::$types[$d->type]; + if (!isset(HTMLPurifier_VarParser::$stringTypes[$d_int])) { + $this->error('type', 'must be a string type when used with allowed or value aliases'); + } + } + + $this->validateDirectiveAllowed($d); + $this->validateDirectiveValueAliases($d); + $this->validateDirectiveAliases($d); + + array_pop($this->context); + } + + /** + * 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; + } + $this->with($d, 'allowed') + ->assertNotEmpty() + ->assertIsLookup(); // handled by InterchangeBuilder + if (is_string($d->default) && !isset($d->allowed[$d->default])) { + $this->error('default', 'must be an allowed value'); + } + $this->context[] = 'allowed'; + foreach ($d->allowed as $val => $x) { + if (!is_string($val)) { + $this->error("value $val", 'must be a string'); + } + } + array_pop($this->context); + } + + /** + * 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; + } + $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 ($alias === $real) { + $this->error("alias '$alias'", "must not be an alias to itself"); + } + } + if (!is_null($d->allowed)) { + foreach ($d->valueAliases as $alias => $real) { + if (isset($d->allowed[$alias])) { + $this->error("alias '$alias'", 'must not be an allowed value'); + } elseif (!isset($d->allowed[$real])) { + $this->error("alias '$alias'", 'must be an alias to an allowed value'); + } + } + } + array_pop($this->context); + } + + /** + * Extra validation if $aliases member variable of + * HTMLPurifier_ConfigSchema_Interchange_Directive is defined. + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d + */ + public function validateDirectiveAliases($d) + { + $this->with($d, 'aliases') + ->assertIsArray(); // handled by InterchangeBuilder + $this->context[] = 'aliases'; + foreach ($d->aliases as $alias) { + $this->validateId($alias); + $s = $alias->toString(); + if (isset($this->interchange->directives[$s])) { + $this->error("alias '$s'", 'collides with another directive'); + } + if (isset($this->aliases[$s])) { + $other_directive = $this->aliases[$s]; + $this->error("alias '$s'", "collides with alias for directive '$other_directive'"); + } + $this->aliases[$s] = $d->id->toString(); + } + array_pop($this->context); + } + + // protected helper functions + + /** + * 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) + { + 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()); + } + throw new HTMLPurifier_ConfigSchema_Exception(trim($prefix . ' ' . $msg)); + } + + /** + * Returns a formatted context string. + * @return string + */ + 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 a2e0b4a1b..c9aa3644a 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/ValidatorAtom.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/ValidatorAtom.php @@ -1,130 +1,130 @@ -context = $context; - $this->obj = $obj; - $this->member = $member; - $this->contents =& $obj->$member; - } - - /** - * @return HTMLPurifier_ConfigSchema_ValidatorAtom - */ - public function assertIsString() - { - if (!is_string($this->contents)) { - $this->error('must be a string'); - } - return $this; - } - - /** - * @return HTMLPurifier_ConfigSchema_ValidatorAtom - */ - public function assertIsBool() - { - if (!is_bool($this->contents)) { - $this->error('must be a boolean'); - } - return $this; - } - - /** - * @return HTMLPurifier_ConfigSchema_ValidatorAtom - */ - public function assertIsArray() - { - if (!is_array($this->contents)) { - $this->error('must be an array'); - } - return $this; - } - - /** - * @return HTMLPurifier_ConfigSchema_ValidatorAtom - */ - public function assertNotNull() - { - if ($this->contents === null) { - $this->error('must not be null'); - } - return $this; - } - - /** - * @return HTMLPurifier_ConfigSchema_ValidatorAtom - */ - public function assertAlnum() - { - $this->assertIsString(); - if (!ctype_alnum($this->contents)) { - $this->error('must be alphanumeric'); - } - return $this; - } - - /** - * @return HTMLPurifier_ConfigSchema_ValidatorAtom - */ - public function assertNotEmpty() - { - if (empty($this->contents)) { - $this->error('must not be empty'); - } - return $this; - } - - /** - * @return HTMLPurifier_ConfigSchema_ValidatorAtom - */ - public function assertIsLookup() - { - $this->assertIsArray(); - foreach ($this->contents as $v) { - if ($v !== true) { - $this->error('must be a lookup array'); - } - } - return $this; - } - - /** - * @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 +context = $context; + $this->obj = $obj; + $this->member = $member; + $this->contents =& $obj->$member; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsString() + { + if (!is_string($this->contents)) { + $this->error('must be a string'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsBool() + { + if (!is_bool($this->contents)) { + $this->error('must be a boolean'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsArray() + { + if (!is_array($this->contents)) { + $this->error('must be an array'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertNotNull() + { + if ($this->contents === null) { + $this->error('must not be null'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertAlnum() + { + $this->assertIsString(); + if (!ctype_alnum($this->contents)) { + $this->error('must be alphanumeric'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertNotEmpty() + { + if (empty($this->contents)) { + $this->error('must not be empty'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsLookup() + { + $this->assertIsArray(); + foreach ($this->contents as $v) { + if ($v !== true) { + $this->error('must be a lookup array'); + } + } + return $this; + } + + /** + * @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/Attr.AllowedClasses.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt index 4a42382ec..0517fed0a 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt @@ -1,8 +1,8 @@ -Attr.AllowedClasses -TYPE: lookup/null -VERSION: 4.0.0 -DEFAULT: null ---DESCRIPTION-- -List of allowed class values in the class attribute. By default, this is null, -which means all classes are allowed. ---# vim: et sw=4 sts=4 +Attr.AllowedClasses +TYPE: lookup/null +VERSION: 4.0.0 +DEFAULT: null +--DESCRIPTION-- +List of allowed class values in the class attribute. By default, this is null, +which means all classes are allowed. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt index b033eb516..249edd647 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt @@ -1,12 +1,12 @@ -Attr.AllowedFrameTargets -TYPE: lookup -DEFAULT: array() ---DESCRIPTION-- -Lookup table of all allowed link frame targets. Some commonly used link -targets include _blank, _self, _parent and _top. Values should be -lowercase, as validation will be done in a case-sensitive manner despite -W3C's recommendation. XHTML 1.0 Strict does not permit the target attribute -so this directive will have no effect in that doctype. XHTML 1.1 does not -enable the Target module by default, you will have to manually enable it -(see the module documentation for more details.) ---# vim: et sw=4 sts=4 +Attr.AllowedFrameTargets +TYPE: lookup +DEFAULT: array() +--DESCRIPTION-- +Lookup table of all allowed link frame targets. Some commonly used link +targets include _blank, _self, _parent and _top. Values should be +lowercase, as validation will be done in a case-sensitive manner despite +W3C's recommendation. XHTML 1.0 Strict does not permit the target attribute +so this directive will have no effect in that doctype. XHTML 1.1 does not +enable the Target module by default, you will have to manually enable it +(see the module documentation for more details.) +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt index ed72a9d56..9a8fa6a2e 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt @@ -1,9 +1,9 @@ -Attr.AllowedRel -TYPE: lookup -VERSION: 1.6.0 -DEFAULT: array() ---DESCRIPTION-- -List of allowed forward document relationships in the rel attribute. Common -values may be nofollow or print. By default, this is empty, meaning that no -document relationships are allowed. ---# vim: et sw=4 sts=4 +Attr.AllowedRel +TYPE: lookup +VERSION: 1.6.0 +DEFAULT: array() +--DESCRIPTION-- +List of allowed forward document relationships in the rel attribute. Common +values may be nofollow or print. By default, this is empty, meaning that no +document relationships are allowed. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt index 1ae672d01..b01788348 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt @@ -1,9 +1,9 @@ -Attr.AllowedRev -TYPE: lookup -VERSION: 1.6.0 -DEFAULT: array() ---DESCRIPTION-- -List of allowed reverse document relationships in the rev attribute. This -attribute is a bit of an edge-case; if you don't know what it is for, stay -away. ---# vim: et sw=4 sts=4 +Attr.AllowedRev +TYPE: lookup +VERSION: 1.6.0 +DEFAULT: array() +--DESCRIPTION-- +List of allowed reverse document relationships in the rev attribute. This +attribute is a bit of an edge-case; if you don't know what it is for, stay +away. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt index 119a9d2c6..e774b823b 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt @@ -1,19 +1,19 @@ -Attr.ClassUseCDATA -TYPE: bool/null -DEFAULT: null -VERSION: 4.0.0 ---DESCRIPTION-- -If null, class will auto-detect the doctype and, if matching XHTML 1.1 or -XHTML 2.0, will use the restrictive NMTOKENS specification of class. Otherwise, -it will use a relaxed CDATA definition. If true, the relaxed CDATA definition -is forced; if false, the NMTOKENS definition is forced. To get behavior -of HTML Purifier prior to 4.0.0, set this directive to false. - -Some rational behind the auto-detection: -in previous versions of HTML Purifier, it was assumed that the form of -class was NMTOKENS, as specified by the XHTML Modularization (representing -XHTML 1.1 and XHTML 2.0). The DTDs for HTML 4.01 and XHTML 1.0, however -specify class as CDATA. HTML 5 effectively defines it as CDATA, but -with the additional constraint that each name should be unique (this is not -explicitly outlined in previous specifications). ---# vim: et sw=4 sts=4 +Attr.ClassUseCDATA +TYPE: bool/null +DEFAULT: null +VERSION: 4.0.0 +--DESCRIPTION-- +If null, class will auto-detect the doctype and, if matching XHTML 1.1 or +XHTML 2.0, will use the restrictive NMTOKENS specification of class. Otherwise, +it will use a relaxed CDATA definition. If true, the relaxed CDATA definition +is forced; if false, the NMTOKENS definition is forced. To get behavior +of HTML Purifier prior to 4.0.0, set this directive to false. + +Some rational behind the auto-detection: +in previous versions of HTML Purifier, it was assumed that the form of +class was NMTOKENS, as specified by the XHTML Modularization (representing +XHTML 1.1 and XHTML 2.0). The DTDs for HTML 4.01 and XHTML 1.0, however +specify class as CDATA. HTML 5 effectively defines it as CDATA, but +with the additional constraint that each name should be unique (this is not +explicitly outlined in previous specifications). +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt index 80b1431c3..533165e17 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt @@ -1,11 +1,11 @@ -Attr.DefaultImageAlt -TYPE: string/null -DEFAULT: null -VERSION: 3.2.0 ---DESCRIPTION-- -This is the content of the alt tag of an image if the user had not -previously specified an alt attribute. This applies to all images without -a valid alt attribute, as opposed to %Attr.DefaultInvalidImageAlt, which -only applies to invalid images, and overrides in the case of an invalid image. -Default behavior with null is to use the basename of the src tag for the alt. ---# vim: et sw=4 sts=4 +Attr.DefaultImageAlt +TYPE: string/null +DEFAULT: null +VERSION: 3.2.0 +--DESCRIPTION-- +This is the content of the alt tag of an image if the user had not +previously specified an alt attribute. This applies to all images without +a valid alt attribute, as opposed to %Attr.DefaultInvalidImageAlt, which +only applies to invalid images, and overrides in the case of an invalid image. +Default behavior with null is to use the basename of the src tag for the alt. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt index c51000d1d..9eb7e3846 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt @@ -1,9 +1,9 @@ -Attr.DefaultInvalidImage -TYPE: string -DEFAULT: '' ---DESCRIPTION-- -This is the default image an img tag will be pointed to if it does not have -a valid src attribute. In future versions, we may allow the image tag to -be removed completely, but due to design issues, this is not possible right -now. ---# vim: et sw=4 sts=4 +Attr.DefaultInvalidImage +TYPE: string +DEFAULT: '' +--DESCRIPTION-- +This is the default image an img tag will be pointed to if it does not have +a valid src attribute. In future versions, we may allow the image tag to +be removed completely, but due to design issues, this is not possible right +now. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt index c1ec4b038..2f17bf477 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt @@ -1,8 +1,8 @@ -Attr.DefaultInvalidImageAlt -TYPE: string -DEFAULT: 'Invalid image' ---DESCRIPTION-- -This is the content of the alt tag of an invalid image if the user had not -previously specified an alt attribute. It has no effect when the image is -valid but there was no alt attribute present. ---# vim: et sw=4 sts=4 +Attr.DefaultInvalidImageAlt +TYPE: string +DEFAULT: 'Invalid image' +--DESCRIPTION-- +This is the content of the alt tag of an invalid image if the user had not +previously specified an alt attribute. It has no effect when the image is +valid but there was no alt attribute present. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt index f57dcc40f..52654b53a 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt @@ -1,10 +1,10 @@ -Attr.DefaultTextDir -TYPE: string -DEFAULT: 'ltr' ---DESCRIPTION-- -Defines the default text direction (ltr or rtl) of the document being -parsed. This generally is the same as the value of the dir attribute in -HTML, or ltr if that is not specified. ---ALLOWED-- -'ltr', 'rtl' ---# vim: et sw=4 sts=4 +Attr.DefaultTextDir +TYPE: string +DEFAULT: 'ltr' +--DESCRIPTION-- +Defines the default text direction (ltr or rtl) of the document being +parsed. This generally is the same as the value of the dir attribute in +HTML, or ltr if that is not specified. +--ALLOWED-- +'ltr', 'rtl' +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt index 9b93a5575..6440d2103 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt @@ -1,16 +1,16 @@ -Attr.EnableID -TYPE: bool -DEFAULT: false -VERSION: 1.2.0 ---DESCRIPTION-- -Allows the ID attribute in HTML. This is disabled by default due to the -fact that without proper configuration user input can easily break the -validation of a webpage by specifying an ID that is already on the -surrounding HTML. If you don't mind throwing caution to the wind, enable -this directive, but I strongly recommend you also consider blacklisting IDs -you use (%Attr.IDBlacklist) or prefixing all user supplied IDs -(%Attr.IDPrefix). When set to true HTML Purifier reverts to the behavior of -pre-1.2.0 versions. ---ALIASES-- -HTML.EnableAttrID ---# vim: et sw=4 sts=4 +Attr.EnableID +TYPE: bool +DEFAULT: false +VERSION: 1.2.0 +--DESCRIPTION-- +Allows the ID attribute in HTML. This is disabled by default due to the +fact that without proper configuration user input can easily break the +validation of a webpage by specifying an ID that is already on the +surrounding HTML. If you don't mind throwing caution to the wind, enable +this directive, but I strongly recommend you also consider blacklisting IDs +you use (%Attr.IDBlacklist) or prefixing all user supplied IDs +(%Attr.IDPrefix). When set to true HTML Purifier reverts to the behavior of +pre-1.2.0 versions. +--ALIASES-- +HTML.EnableAttrID +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt index fed8954cf..f31d226f5 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt @@ -1,8 +1,8 @@ -Attr.ForbiddenClasses -TYPE: lookup -VERSION: 4.0.0 -DEFAULT: array() ---DESCRIPTION-- -List of forbidden class values in the class attribute. By default, this is -empty, which means that no classes are forbidden. See also %Attr.AllowedClasses. ---# vim: et sw=4 sts=4 +Attr.ForbiddenClasses +TYPE: lookup +VERSION: 4.0.0 +DEFAULT: array() +--DESCRIPTION-- +List of forbidden class values in the class attribute. By default, this is +empty, which means that no classes are forbidden. See also %Attr.AllowedClasses. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt index 52168bb5e..5f2b5e3d2 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt @@ -1,5 +1,5 @@ -Attr.IDBlacklist -TYPE: list -DEFAULT: array() -DESCRIPTION: Array of IDs not allowed in the document. ---# vim: et sw=4 sts=4 +Attr.IDBlacklist +TYPE: list +DEFAULT: array() +DESCRIPTION: Array of IDs not allowed in the document. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt index 7b8504307..6f5824586 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt @@ -1,9 +1,9 @@ -Attr.IDBlacklistRegexp -TYPE: string/null -VERSION: 1.6.0 -DEFAULT: NULL ---DESCRIPTION-- -PCRE regular expression to be matched against all IDs. If the expression is -matches, the ID is rejected. Use this with care: may cause significant -degradation. ID matching is done after all other validation. ---# vim: et sw=4 sts=4 +Attr.IDBlacklistRegexp +TYPE: string/null +VERSION: 1.6.0 +DEFAULT: NULL +--DESCRIPTION-- +PCRE regular expression to be matched against all IDs. If the expression is +matches, the ID is rejected. Use this with care: may cause significant +degradation. ID matching is done after all other validation. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt index 578138277..cc49d43fd 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt @@ -1,12 +1,12 @@ -Attr.IDPrefix -TYPE: string -VERSION: 1.2.0 -DEFAULT: '' ---DESCRIPTION-- -String to prefix to IDs. If you have no idea what IDs your pages may use, -you may opt to simply add a prefix to all user-submitted ID attributes so -that they are still usable, but will not conflict with core page IDs. -Example: setting the directive to 'user_' will result in a user submitted -'foo' to become 'user_foo' Be sure to set %HTML.EnableAttrID to true -before using this. ---# vim: et sw=4 sts=4 +Attr.IDPrefix +TYPE: string +VERSION: 1.2.0 +DEFAULT: '' +--DESCRIPTION-- +String to prefix to IDs. If you have no idea what IDs your pages may use, +you may opt to simply add a prefix to all user-submitted ID attributes so +that they are still usable, but will not conflict with core page IDs. +Example: setting the directive to 'user_' will result in a user submitted +'foo' to become 'user_foo' Be sure to set %HTML.EnableAttrID to true +before using this. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt index f91fcd602..2c5924a7a 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt @@ -1,14 +1,14 @@ -Attr.IDPrefixLocal -TYPE: string -VERSION: 1.2.0 -DEFAULT: '' ---DESCRIPTION-- -Temporary prefix for IDs used in conjunction with %Attr.IDPrefix. If you -need to allow multiple sets of user content on web page, you may need to -have a seperate prefix that changes with each iteration. This way, -seperately submitted user content displayed on the same page doesn't -clobber each other. Ideal values are unique identifiers for the content it -represents (i.e. the id of the row in the database). Be sure to add a -seperator (like an underscore) at the end. Warning: this directive will -not work unless %Attr.IDPrefix is set to a non-empty value! ---# vim: et sw=4 sts=4 +Attr.IDPrefixLocal +TYPE: string +VERSION: 1.2.0 +DEFAULT: '' +--DESCRIPTION-- +Temporary prefix for IDs used in conjunction with %Attr.IDPrefix. If you +need to allow multiple sets of user content on web page, you may need to +have a seperate prefix that changes with each iteration. This way, +seperately submitted user content displayed on the same page doesn't +clobber each other. Ideal values are unique identifiers for the content it +represents (i.e. the id of the row in the database). Be sure to add a +seperator (like an underscore) at the end. Warning: this directive will +not work unless %Attr.IDPrefix is set to a non-empty value! +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt index 2d7f94e02..d5caa1bb9 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt @@ -1,31 +1,31 @@ -AutoFormat.AutoParagraph -TYPE: bool -VERSION: 2.0.1 -DEFAULT: false ---DESCRIPTION-- - -

          - This directive turns on auto-paragraphing, where double newlines are - converted in to paragraphs whenever possible. Auto-paragraphing: -

          -
            -
          • Always applies to inline elements or text in the root node,
          • -
          • Applies to inline elements or text with double newlines in nodes - that allow paragraph tags,
          • -
          • Applies to double newlines in paragraph tags
          • -
          -

          - p tags must be allowed for this directive to take effect. - We do not use br tags for paragraphing, as that is - semantically incorrect. -

          -

          - To prevent auto-paragraphing as a content-producer, refrain from using - double-newlines except to specify a new paragraph or in contexts where - it has special meaning (whitespace usually has no meaning except in - tags like pre, so this should not be difficult.) To prevent - the paragraphing of inline text adjacent to block elements, wrap them - in div tags (the behavior is slightly different outside of - the root node.) -

          ---# vim: et sw=4 sts=4 +AutoFormat.AutoParagraph +TYPE: bool +VERSION: 2.0.1 +DEFAULT: false +--DESCRIPTION-- + +

          + This directive turns on auto-paragraphing, where double newlines are + converted in to paragraphs whenever possible. Auto-paragraphing: +

          +
            +
          • Always applies to inline elements or text in the root node,
          • +
          • Applies to inline elements or text with double newlines in nodes + that allow paragraph tags,
          • +
          • Applies to double newlines in paragraph tags
          • +
          +

          + p tags must be allowed for this directive to take effect. + We do not use br tags for paragraphing, as that is + semantically incorrect. +

          +

          + To prevent auto-paragraphing as a content-producer, refrain from using + double-newlines except to specify a new paragraph or in contexts where + it has special meaning (whitespace usually has no meaning except in + tags like pre, so this should not be difficult.) To prevent + the paragraphing of inline text adjacent to block elements, wrap them + in div tags (the behavior is slightly different outside of + the root node.) +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt index 2eb1974fd..2a476481a 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt @@ -1,12 +1,12 @@ -AutoFormat.Custom -TYPE: list -VERSION: 2.0.1 -DEFAULT: array() ---DESCRIPTION-- - -

          - This directive can be used to add custom auto-format injectors. - Specify an array of injector names (class name minus the prefix) - or concrete implementations. Injector class must exist. -

          ---# vim: et sw=4 sts=4 +AutoFormat.Custom +TYPE: list +VERSION: 2.0.1 +DEFAULT: array() +--DESCRIPTION-- + +

          + This directive can be used to add custom auto-format injectors. + Specify an array of injector names (class name minus the prefix) + or concrete implementations. Injector class must exist. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt index c955de7f6..663064a34 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt @@ -1,11 +1,11 @@ -AutoFormat.DisplayLinkURI -TYPE: bool -VERSION: 3.2.0 -DEFAULT: false ---DESCRIPTION-- -

          - This directive turns on the in-text display of URIs in <a> tags, and disables - those links. For example, example becomes - example (http://example.com). -

          ---# vim: et sw=4 sts=4 +AutoFormat.DisplayLinkURI +TYPE: bool +VERSION: 3.2.0 +DEFAULT: false +--DESCRIPTION-- +

          + This directive turns on the in-text display of URIs in <a> tags, and disables + those links. For example, example becomes + example (http://example.com). +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt index 328b2b2bf..3a48ba960 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt @@ -1,12 +1,12 @@ -AutoFormat.Linkify -TYPE: bool -VERSION: 2.0.1 -DEFAULT: false ---DESCRIPTION-- - -

          - This directive turns on linkification, auto-linking http, ftp and - https URLs. a tags with the href attribute - must be allowed. -

          ---# vim: et sw=4 sts=4 +AutoFormat.Linkify +TYPE: bool +VERSION: 2.0.1 +DEFAULT: false +--DESCRIPTION-- + +

          + This directive turns on linkification, auto-linking http, ftp and + https URLs. a tags with the href attribute + must be allowed. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt index d0532b6ba..db58b1346 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt @@ -1,12 +1,12 @@ -AutoFormat.PurifierLinkify.DocURL -TYPE: string -VERSION: 2.0.1 -DEFAULT: '#%s' -ALIASES: AutoFormatParam.PurifierLinkifyDocURL ---DESCRIPTION-- -

          - Location of configuration documentation to link to, let %s substitute - into the configuration's namespace and directive names sans the percent - sign. -

          ---# vim: et sw=4 sts=4 +AutoFormat.PurifierLinkify.DocURL +TYPE: string +VERSION: 2.0.1 +DEFAULT: '#%s' +ALIASES: AutoFormatParam.PurifierLinkifyDocURL +--DESCRIPTION-- +

          + Location of configuration documentation to link to, let %s substitute + into the configuration's namespace and directive names sans the percent + sign. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt index f3ab259a1..7996488be 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt @@ -1,12 +1,12 @@ -AutoFormat.PurifierLinkify -TYPE: bool -VERSION: 2.0.1 -DEFAULT: false ---DESCRIPTION-- - -

          - Internal auto-formatter that converts configuration directives in - syntax %Namespace.Directive to links. a tags - with the href attribute must be allowed. -

          ---# vim: et sw=4 sts=4 +AutoFormat.PurifierLinkify +TYPE: bool +VERSION: 2.0.1 +DEFAULT: false +--DESCRIPTION-- + +

          + Internal auto-formatter that converts configuration directives in + syntax %Namespace.Directive to links. a tags + with the href attribute must be allowed. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt index 219d04ac4..35c393b4e 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt @@ -1,11 +1,11 @@ -AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions -TYPE: lookup -VERSION: 4.0.0 -DEFAULT: array('td' => true, 'th' => true) ---DESCRIPTION-- -

          - When %AutoFormat.RemoveEmpty and %AutoFormat.RemoveEmpty.RemoveNbsp - are enabled, this directive defines what HTML elements should not be - removede if they have only a non-breaking space in them. -

          ---# vim: et sw=4 sts=4 +AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions +TYPE: lookup +VERSION: 4.0.0 +DEFAULT: array('td' => true, 'th' => true) +--DESCRIPTION-- +

          + When %AutoFormat.RemoveEmpty and %AutoFormat.RemoveEmpty.RemoveNbsp + are enabled, this directive defines what HTML elements should not be + removede if they have only a non-breaking space in them. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt index 5f355d662..ca17eb1dc 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt @@ -1,15 +1,15 @@ -AutoFormat.RemoveEmpty.RemoveNbsp -TYPE: bool -VERSION: 4.0.0 -DEFAULT: false ---DESCRIPTION-- -

          - When enabled, HTML Purifier will treat any elements that contain only - non-breaking spaces as well as regular whitespace as empty, and remove - them when %AutoForamt.RemoveEmpty is enabled. -

          -

          - See %AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions for a list of elements - that don't have this behavior applied to them. -

          ---# vim: et sw=4 sts=4 +AutoFormat.RemoveEmpty.RemoveNbsp +TYPE: bool +VERSION: 4.0.0 +DEFAULT: false +--DESCRIPTION-- +

          + When enabled, HTML Purifier will treat any elements that contain only + non-breaking spaces as well as regular whitespace as empty, and remove + them when %AutoForamt.RemoveEmpty is enabled. +

          +

          + See %AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions for a list of elements + that don't have this behavior applied to them. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt index 6b5a7a5c9..34657ba47 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt @@ -1,46 +1,46 @@ -AutoFormat.RemoveEmpty -TYPE: bool -VERSION: 3.2.0 -DEFAULT: false ---DESCRIPTION-- -

          - When enabled, HTML Purifier will attempt to remove empty elements that - contribute no semantic information to the document. The following types - of nodes will be removed: -

          -
          • - Tags with no attributes and no content, and that are not empty - elements (remove <a></a> but not - <br />), and -
          • -
          • - Tags with no content, except for:
              -
            • The colgroup element, or
            • -
            • - Elements with the id or name attribute, - when those attributes are permitted on those elements. -
            • -
          • -
          -

          - Please be very careful when using this functionality; while it may not - seem that empty elements contain useful information, they can alter the - layout of a document given appropriate styling. This directive is most - useful when you are processing machine-generated HTML, please avoid using - it on regular user HTML. -

          -

          - Elements that contain only whitespace will be treated as empty. Non-breaking - spaces, however, do not count as whitespace. See - %AutoFormat.RemoveEmpty.RemoveNbsp for alternate behavior. -

          -

          - This algorithm is not perfect; you may still notice some empty tags, - particularly if a node had elements, but those elements were later removed - because they were not permitted in that context, or tags that, after - being auto-closed by another tag, where empty. This is for safety reasons - to prevent clever code from breaking validation. The general rule of thumb: - if a tag looked empty on the way in, it will get removed; if HTML Purifier - made it empty, it will stay. -

          ---# vim: et sw=4 sts=4 +AutoFormat.RemoveEmpty +TYPE: bool +VERSION: 3.2.0 +DEFAULT: false +--DESCRIPTION-- +

          + When enabled, HTML Purifier will attempt to remove empty elements that + contribute no semantic information to the document. The following types + of nodes will be removed: +

          +
          • + Tags with no attributes and no content, and that are not empty + elements (remove <a></a> but not + <br />), and +
          • +
          • + Tags with no content, except for:
              +
            • The colgroup element, or
            • +
            • + Elements with the id or name attribute, + when those attributes are permitted on those elements. +
            • +
          • +
          +

          + Please be very careful when using this functionality; while it may not + seem that empty elements contain useful information, they can alter the + layout of a document given appropriate styling. This directive is most + useful when you are processing machine-generated HTML, please avoid using + it on regular user HTML. +

          +

          + Elements that contain only whitespace will be treated as empty. Non-breaking + spaces, however, do not count as whitespace. See + %AutoFormat.RemoveEmpty.RemoveNbsp for alternate behavior. +

          +

          + This algorithm is not perfect; you may still notice some empty tags, + particularly if a node had elements, but those elements were later removed + because they were not permitted in that context, or tags that, after + being auto-closed by another tag, where empty. This is for safety reasons + to prevent clever code from breaking validation. The general rule of thumb: + if a tag looked empty on the way in, it will get removed; if HTML Purifier + made it empty, it will stay. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt index a448770e5..dde990ab2 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt @@ -1,11 +1,11 @@ -AutoFormat.RemoveSpansWithoutAttributes -TYPE: bool -VERSION: 4.0.1 -DEFAULT: false ---DESCRIPTION-- -

          - This directive causes span tags without any attributes - to be removed. It will also remove spans that had all attributes - removed during processing. -

          ---# vim: et sw=4 sts=4 +AutoFormat.RemoveSpansWithoutAttributes +TYPE: bool +VERSION: 4.0.1 +DEFAULT: false +--DESCRIPTION-- +

          + This directive causes span tags without any attributes + to be removed. It will also remove spans that had all attributes + removed during processing. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt index 8096eb01a..b324608f7 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt @@ -1,8 +1,8 @@ -CSS.AllowImportant -TYPE: bool -DEFAULT: false -VERSION: 3.1.0 ---DESCRIPTION-- -This parameter determines whether or not !important cascade modifiers should -be allowed in user CSS. If false, !important will stripped. ---# vim: et sw=4 sts=4 +CSS.AllowImportant +TYPE: bool +DEFAULT: false +VERSION: 3.1.0 +--DESCRIPTION-- +This parameter determines whether or not !important cascade modifiers should +be allowed in user CSS. If false, !important will stripped. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt index 9d34debc4..748be0eec 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt @@ -1,11 +1,11 @@ -CSS.AllowTricky -TYPE: bool -DEFAULT: false -VERSION: 3.1.0 ---DESCRIPTION-- -This parameter determines whether or not to allow "tricky" CSS properties and -values. Tricky CSS properties/values can drastically modify page layout or -be used for deceptive practices but do not directly constitute a security risk. -For example, display:none; is considered a tricky property that -will only be allowed if this directive is set to true. ---# vim: et sw=4 sts=4 +CSS.AllowTricky +TYPE: bool +DEFAULT: false +VERSION: 3.1.0 +--DESCRIPTION-- +This parameter determines whether or not to allow "tricky" CSS properties and +values. Tricky CSS properties/values can drastically modify page layout or +be used for deceptive practices but do not directly constitute a security risk. +For example, display:none; is considered a tricky property that +will only be allowed if this directive is set to true. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt index f1ba513c3..460112ebe 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt @@ -1,18 +1,18 @@ -CSS.AllowedProperties -TYPE: lookup/null -VERSION: 3.1.0 -DEFAULT: NULL ---DESCRIPTION-- - -

          - If HTML Purifier's style attributes set is unsatisfactory for your needs, - you can overload it with your own list of tags to allow. Note that this - method is subtractive: it does its job by taking away from HTML Purifier - usual feature set, so you cannot add an attribute that HTML Purifier never - supported in the first place. -

          -

          - Warning: If another directive conflicts with the - elements here, that directive will win and override. -

          ---# vim: et sw=4 sts=4 +CSS.AllowedProperties +TYPE: lookup/null +VERSION: 3.1.0 +DEFAULT: NULL +--DESCRIPTION-- + +

          + If HTML Purifier's style attributes set is unsatisfactory for your needs, + you can overload it with your own list of tags to allow. Note that this + method is subtractive: it does its job by taking away from HTML Purifier + usual feature set, so you cannot add an attribute that HTML Purifier never + supported in the first place. +

          +

          + Warning: If another directive conflicts with the + elements here, that directive will win and override. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt index 96b410829..5cb7dda3b 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt @@ -1,11 +1,11 @@ -CSS.DefinitionRev -TYPE: int -VERSION: 2.0.0 -DEFAULT: 1 ---DESCRIPTION-- - -

          - Revision identifier for your custom definition. See - %HTML.DefinitionRev for details. -

          ---# vim: et sw=4 sts=4 +CSS.DefinitionRev +TYPE: int +VERSION: 2.0.0 +DEFAULT: 1 +--DESCRIPTION-- + +

          + Revision identifier for your custom definition. See + %HTML.DefinitionRev for details. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt index 3808581e2..7a3291470 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt @@ -1,16 +1,16 @@ -CSS.MaxImgLength -TYPE: string/null -DEFAULT: '1200px' -VERSION: 3.1.1 ---DESCRIPTION-- -

          - This parameter sets the maximum allowed length on img tags, - effectively the width and height properties. - Only absolute units of measurement (in, pt, pc, mm, cm) and pixels (px) are allowed. This is - in place to prevent imagecrash attacks, disable with null at your own risk. - This directive is similar to %HTML.MaxImgLength, and both should be - concurrently edited, although there are - subtle differences in the input format (the CSS max is a number with - a unit). -

          ---# vim: et sw=4 sts=4 +CSS.MaxImgLength +TYPE: string/null +DEFAULT: '1200px' +VERSION: 3.1.1 +--DESCRIPTION-- +

          + This parameter sets the maximum allowed length on img tags, + effectively the width and height properties. + Only absolute units of measurement (in, pt, pc, mm, cm) and pixels (px) are allowed. This is + in place to prevent imagecrash attacks, disable with null at your own risk. + This directive is similar to %HTML.MaxImgLength, and both should be + concurrently edited, although there are + subtle differences in the input format (the CSS max is a number with + a unit). +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt index 8a26f228d..148eedb8b 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt @@ -1,10 +1,10 @@ -CSS.Proprietary -TYPE: bool -VERSION: 3.0.0 -DEFAULT: false ---DESCRIPTION-- - -

          - Whether or not to allow safe, proprietary CSS values. -

          ---# vim: et sw=4 sts=4 +CSS.Proprietary +TYPE: bool +VERSION: 3.0.0 +DEFAULT: false +--DESCRIPTION-- + +

          + Whether or not to allow safe, proprietary CSS values. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt index afc6a87a6..c486724c8 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt @@ -1,14 +1,14 @@ -Cache.DefinitionImpl -TYPE: string/null -VERSION: 2.0.0 -DEFAULT: 'Serializer' ---DESCRIPTION-- - -This directive defines which method to use when caching definitions, -the complex data-type that makes HTML Purifier tick. Set to null -to disable caching (not recommended, as you will see a definite -performance degradation). - ---ALIASES-- -Core.DefinitionCache ---# vim: et sw=4 sts=4 +Cache.DefinitionImpl +TYPE: string/null +VERSION: 2.0.0 +DEFAULT: 'Serializer' +--DESCRIPTION-- + +This directive defines which method to use when caching definitions, +the complex data-type that makes HTML Purifier tick. Set to null +to disable caching (not recommended, as you will see a definite +performance degradation). + +--ALIASES-- +Core.DefinitionCache +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt index 668f248af..54036507d 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt @@ -1,13 +1,13 @@ -Cache.SerializerPath -TYPE: string/null -VERSION: 2.0.0 -DEFAULT: NULL ---DESCRIPTION-- - -

          - Absolute path with no trailing slash to store serialized definitions in. - Default is within the - HTML Purifier library inside DefinitionCache/Serializer. This - path must be writable by the webserver. -

          ---# vim: et sw=4 sts=4 +Cache.SerializerPath +TYPE: string/null +VERSION: 2.0.0 +DEFAULT: NULL +--DESCRIPTION-- + +

          + Absolute path with no trailing slash to store serialized definitions in. + Default is within the + HTML Purifier library inside DefinitionCache/Serializer. This + path must be writable by the webserver. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt index e0fa378ea..568cbf3b3 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt @@ -1,18 +1,18 @@ -Core.AggressivelyFixLt -TYPE: bool -VERSION: 2.1.0 -DEFAULT: true ---DESCRIPTION-- -

          - This directive enables aggressive pre-filter fixes HTML Purifier can - perform in order to ensure that open angled-brackets do not get killed - during parsing stage. Enabling this will result in two preg_replace_callback - calls and at least two preg_replace calls for every HTML document parsed; - if your users make very well-formed HTML, you can set this directive false. - This has no effect when DirectLex is used. -

          -

          - Notice: This directive's default turned from false to true - in HTML Purifier 3.2.0. -

          ---# vim: et sw=4 sts=4 +Core.AggressivelyFixLt +TYPE: bool +VERSION: 2.1.0 +DEFAULT: true +--DESCRIPTION-- +

          + This directive enables aggressive pre-filter fixes HTML Purifier can + perform in order to ensure that open angled-brackets do not get killed + during parsing stage. Enabling this will result in two preg_replace_callback + calls and at least two preg_replace calls for every HTML document parsed; + if your users make very well-formed HTML, you can set this directive false. + This has no effect when DirectLex is used. +

          +

          + Notice: This directive's default turned from false to true + in HTML Purifier 3.2.0. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt index c6ea06990..d7317911f 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt @@ -1,12 +1,12 @@ -Core.CollectErrors -TYPE: bool -VERSION: 2.0.0 -DEFAULT: false ---DESCRIPTION-- - -Whether or not to collect errors found while filtering the document. This -is a useful way to give feedback to your users. Warning: -Currently this feature is very patchy and experimental, with lots of -possible error messages not yet implemented. It will not cause any -problems, but it may not help your users either. ---# vim: et sw=4 sts=4 +Core.CollectErrors +TYPE: bool +VERSION: 2.0.0 +DEFAULT: false +--DESCRIPTION-- + +Whether or not to collect errors found while filtering the document. This +is a useful way to give feedback to your users. Warning: +Currently this feature is very patchy and experimental, with lots of +possible error messages not yet implemented. It will not cause any +problems, but it may not help your users either. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt index f78239825..c572c14ec 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt @@ -1,29 +1,29 @@ -Core.ColorKeywords -TYPE: hash -VERSION: 2.0.0 ---DEFAULT-- -array ( - 'maroon' => '#800000', - 'red' => '#FF0000', - 'orange' => '#FFA500', - 'yellow' => '#FFFF00', - 'olive' => '#808000', - 'purple' => '#800080', - 'fuchsia' => '#FF00FF', - 'white' => '#FFFFFF', - 'lime' => '#00FF00', - 'green' => '#008000', - 'navy' => '#000080', - 'blue' => '#0000FF', - 'aqua' => '#00FFFF', - 'teal' => '#008080', - 'black' => '#000000', - 'silver' => '#C0C0C0', - 'gray' => '#808080', -) ---DESCRIPTION-- - -Lookup array of color names to six digit hexadecimal number corresponding -to color, with preceding hash mark. Used when parsing colors. The lookup -is done in a case-insensitive manner. ---# vim: et sw=4 sts=4 +Core.ColorKeywords +TYPE: hash +VERSION: 2.0.0 +--DEFAULT-- +array ( + 'maroon' => '#800000', + 'red' => '#FF0000', + 'orange' => '#FFA500', + 'yellow' => '#FFFF00', + 'olive' => '#808000', + 'purple' => '#800080', + 'fuchsia' => '#FF00FF', + 'white' => '#FFFFFF', + 'lime' => '#00FF00', + 'green' => '#008000', + 'navy' => '#000080', + 'blue' => '#0000FF', + 'aqua' => '#00FFFF', + 'teal' => '#008080', + 'black' => '#000000', + 'silver' => '#C0C0C0', + 'gray' => '#808080', +) +--DESCRIPTION-- + +Lookup array of color names to six digit hexadecimal number corresponding +to color, with preceding hash mark. Used when parsing colors. The lookup +is done in a case-insensitive manner. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt index 656d3783a..64b114fce 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt @@ -1,14 +1,14 @@ -Core.ConvertDocumentToFragment -TYPE: bool -DEFAULT: true ---DESCRIPTION-- - -This parameter determines whether or not the filter should convert -input that is a full document with html and body tags to a fragment -of just the contents of a body tag. This parameter is simply something -HTML Purifier can do during an edge-case: for most inputs, this -processing is not necessary. - ---ALIASES-- -Core.AcceptFullDocuments ---# vim: et sw=4 sts=4 +Core.ConvertDocumentToFragment +TYPE: bool +DEFAULT: true +--DESCRIPTION-- + +This parameter determines whether or not the filter should convert +input that is a full document with html and body tags to a fragment +of just the contents of a body tag. This parameter is simply something +HTML Purifier can do during an edge-case: for most inputs, this +processing is not necessary. + +--ALIASES-- +Core.AcceptFullDocuments +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt index 2f54e462a..36f16e07e 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt @@ -1,17 +1,17 @@ -Core.DirectLexLineNumberSyncInterval -TYPE: int -VERSION: 2.0.0 -DEFAULT: 0 ---DESCRIPTION-- - -

          - Specifies the number of tokens the DirectLex line number tracking - implementations should process before attempting to resyncronize the - current line count by manually counting all previous new-lines. When - at 0, this functionality is disabled. Lower values will decrease - performance, and this is only strictly necessary if the counting - algorithm is buggy (in which case you should report it as a bug). - This has no effect when %Core.MaintainLineNumbers is disabled or DirectLex is - not being used. -

          ---# vim: et sw=4 sts=4 +Core.DirectLexLineNumberSyncInterval +TYPE: int +VERSION: 2.0.0 +DEFAULT: 0 +--DESCRIPTION-- + +

          + Specifies the number of tokens the DirectLex line number tracking + implementations should process before attempting to resyncronize the + current line count by manually counting all previous new-lines. When + at 0, this functionality is disabled. Lower values will decrease + performance, and this is only strictly necessary if the counting + algorithm is buggy (in which case you should report it as a bug). + This has no effect when %Core.MaintainLineNumbers is disabled or DirectLex is + not being used. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt index 89e2ae34b..8bfb47c3a 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt @@ -1,15 +1,15 @@ -Core.Encoding -TYPE: istring -DEFAULT: 'utf-8' ---DESCRIPTION-- -If for some reason you are unable to convert all webpages to UTF-8, you can -use this directive as a stop-gap compatibility change to let HTML Purifier -deal with non UTF-8 input. This technique has notable deficiencies: -absolutely no characters outside of the selected character encoding will be -preserved, not even the ones that have been ampersand escaped (this is due -to a UTF-8 specific feature that automatically resolves all -entities), making it pretty useless for anything except the most I18N-blind -applications, although %Core.EscapeNonASCIICharacters offers fixes this -trouble with another tradeoff. This directive only accepts ISO-8859-1 if -iconv is not enabled. ---# vim: et sw=4 sts=4 +Core.Encoding +TYPE: istring +DEFAULT: 'utf-8' +--DESCRIPTION-- +If for some reason you are unable to convert all webpages to UTF-8, you can +use this directive as a stop-gap compatibility change to let HTML Purifier +deal with non UTF-8 input. This technique has notable deficiencies: +absolutely no characters outside of the selected character encoding will be +preserved, not even the ones that have been ampersand escaped (this is due +to a UTF-8 specific feature that automatically resolves all +entities), making it pretty useless for anything except the most I18N-blind +applications, although %Core.EscapeNonASCIICharacters offers fixes this +trouble with another tradeoff. This directive only accepts ISO-8859-1 if +iconv is not enabled. +--# 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 1cc3fcda2..a3881be75 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt @@ -1,12 +1,12 @@ -Core.EscapeInvalidChildren -TYPE: bool -DEFAULT: false ---DESCRIPTION-- -

          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.

          ---# vim: et sw=4 sts=4 +Core.EscapeInvalidChildren +TYPE: bool +DEFAULT: false +--DESCRIPTION-- +

          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.

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt index 299775fab..a7a5b249b 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt @@ -1,7 +1,7 @@ -Core.EscapeInvalidTags -TYPE: bool -DEFAULT: false ---DESCRIPTION-- -When true, invalid tags will be written back to the document as plain text. -Otherwise, they are silently dropped. ---# vim: et sw=4 sts=4 +Core.EscapeInvalidTags +TYPE: bool +DEFAULT: false +--DESCRIPTION-- +When true, invalid tags will be written back to the document as plain text. +Otherwise, they are silently dropped. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt index f50db2f92..abb499948 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt @@ -1,13 +1,13 @@ -Core.EscapeNonASCIICharacters -TYPE: bool -VERSION: 1.4.0 -DEFAULT: false ---DESCRIPTION-- -This directive overcomes a deficiency in %Core.Encoding by blindly -converting all non-ASCII characters into decimal numeric entities before -converting it to its native encoding. This means that even characters that -can be expressed in the non-UTF-8 encoding will be entity-ized, which can -be a real downer for encodings like Big5. It also assumes that the ASCII -repetoire is available, although this is the case for almost all encodings. -Anyway, use UTF-8! ---# vim: et sw=4 sts=4 +Core.EscapeNonASCIICharacters +TYPE: bool +VERSION: 1.4.0 +DEFAULT: false +--DESCRIPTION-- +This directive overcomes a deficiency in %Core.Encoding by blindly +converting all non-ASCII characters into decimal numeric entities before +converting it to its native encoding. This means that even characters that +can be expressed in the non-UTF-8 encoding will be entity-ized, which can +be a real downer for encodings like Big5. It also assumes that the ASCII +repetoire is available, although this is the case for almost all encodings. +Anyway, use UTF-8! +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt index c337e47fc..915391edb 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt @@ -1,19 +1,19 @@ -Core.HiddenElements -TYPE: lookup ---DEFAULT-- -array ( - 'script' => true, - 'style' => true, -) ---DESCRIPTION-- - -

          - This directive is a lookup array of elements which should have their - contents removed when they are not allowed by the HTML definition. - For example, the contents of a script tag are not - normally shown in a document, so if script tags are to be removed, - their contents should be removed to. This is opposed to a b - tag, which defines some presentational changes but does not hide its - contents. -

          ---# vim: et sw=4 sts=4 +Core.HiddenElements +TYPE: lookup +--DEFAULT-- +array ( + 'script' => true, + 'style' => true, +) +--DESCRIPTION-- + +

          + This directive is a lookup array of elements which should have their + contents removed when they are not allowed by the HTML definition. + For example, the contents of a script tag are not + normally shown in a document, so if script tags are to be removed, + their contents should be removed to. This is opposed to a b + tag, which defines some presentational changes but does not hide its + contents. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.Language.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.Language.txt index ed1f39b5f..233fca14f 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.Language.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.Language.txt @@ -1,10 +1,10 @@ -Core.Language -TYPE: string -VERSION: 2.0.0 -DEFAULT: 'en' ---DESCRIPTION-- - -ISO 639 language code for localizable things in HTML Purifier to use, -which is mainly error reporting. There is currently only an English (en) -translation, so this directive is currently useless. ---# vim: et sw=4 sts=4 +Core.Language +TYPE: string +VERSION: 2.0.0 +DEFAULT: 'en' +--DESCRIPTION-- + +ISO 639 language code for localizable things in HTML Purifier to use, +which is mainly error reporting. There is currently only an English (en) +translation, so this directive is currently useless. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt index e11c0152c..8983e2cca 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt @@ -1,34 +1,34 @@ -Core.LexerImpl -TYPE: mixed/null -VERSION: 2.0.0 -DEFAULT: NULL ---DESCRIPTION-- - -

          - This parameter determines what lexer implementation can be used. The - valid values are: -

          -
          -
          null
          -
          - Recommended, the lexer implementation will be auto-detected based on - your PHP-version and configuration. -
          -
          string lexer identifier
          -
          - This is a slim way of manually overridding the implementation. - Currently recognized values are: DOMLex (the default PHP5 -implementation) - and DirectLex (the default PHP4 implementation). Only use this if - you know what you are doing: usually, the auto-detection will - manage things for cases you aren't even aware of. -
          -
          object lexer instance
          -
          - Super-advanced: you can specify your own, custom, implementation that - implements the interface defined by HTMLPurifier_Lexer. - I may remove this option simply because I don't expect anyone - to use it. -
          -
          ---# vim: et sw=4 sts=4 +Core.LexerImpl +TYPE: mixed/null +VERSION: 2.0.0 +DEFAULT: NULL +--DESCRIPTION-- + +

          + This parameter determines what lexer implementation can be used. The + valid values are: +

          +
          +
          null
          +
          + Recommended, the lexer implementation will be auto-detected based on + your PHP-version and configuration. +
          +
          string lexer identifier
          +
          + This is a slim way of manually overridding the implementation. + Currently recognized values are: DOMLex (the default PHP5 +implementation) + and DirectLex (the default PHP4 implementation). Only use this if + you know what you are doing: usually, the auto-detection will + manage things for cases you aren't even aware of. +
          +
          object lexer instance
          +
          + Super-advanced: you can specify your own, custom, implementation that + implements the interface defined by HTMLPurifier_Lexer. + I may remove this option simply because I don't expect anyone + to use it. +
          +
          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt index 838f10f61..eb841a759 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt @@ -1,16 +1,16 @@ -Core.MaintainLineNumbers -TYPE: bool/null -VERSION: 2.0.0 -DEFAULT: NULL ---DESCRIPTION-- - -

          - If true, HTML Purifier will add line number information to all tokens. - This is useful when error reporting is turned on, but can result in - significant performance degradation and should not be used when - unnecessary. This directive must be used with the DirectLex lexer, - as the DOMLex lexer does not (yet) support this functionality. - If the value is null, an appropriate value will be selected based - on other configuration. -

          ---# vim: et sw=4 sts=4 +Core.MaintainLineNumbers +TYPE: bool/null +VERSION: 2.0.0 +DEFAULT: NULL +--DESCRIPTION-- + +

          + If true, HTML Purifier will add line number information to all tokens. + This is useful when error reporting is turned on, but can result in + significant performance degradation and should not be used when + unnecessary. This directive must be used with the DirectLex lexer, + as the DOMLex lexer does not (yet) support this functionality. + If the value is null, an appropriate value will be selected based + on other configuration. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt index 704ac56c8..4070c2a0d 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt @@ -1,12 +1,12 @@ -Core.RemoveInvalidImg -TYPE: bool -DEFAULT: true -VERSION: 1.3.0 ---DESCRIPTION-- - -

          - This directive enables pre-emptive URI checking in img - tags, as the attribute validation strategy is not authorized to - remove elements from the document. Revert to pre-1.3.0 behavior by setting to false. -

          ---# vim: et sw=4 sts=4 +Core.RemoveInvalidImg +TYPE: bool +DEFAULT: true +VERSION: 1.3.0 +--DESCRIPTION-- + +

          + This directive enables pre-emptive URI checking in img + tags, as the attribute validation strategy is not authorized to + remove elements from the document. Revert to pre-1.3.0 behavior by setting to false. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt index efbe994c2..a4cd966df 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt @@ -1,12 +1,12 @@ -Core.RemoveScriptContents -TYPE: bool/null -DEFAULT: NULL -VERSION: 2.0.0 -DEPRECATED-VERSION: 2.1.0 -DEPRECATED-USE: Core.HiddenElements ---DESCRIPTION-- -

          - This directive enables HTML Purifier to remove not only script tags - but all of their contents. -

          ---# vim: et sw=4 sts=4 +Core.RemoveScriptContents +TYPE: bool/null +DEFAULT: NULL +VERSION: 2.0.0 +DEPRECATED-VERSION: 2.1.0 +DEPRECATED-USE: Core.HiddenElements +--DESCRIPTION-- +

          + This directive enables HTML Purifier to remove not only script tags + but all of their contents. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt index 861ae66c3..3db50ef20 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt @@ -1,11 +1,11 @@ -Filter.Custom -TYPE: list -VERSION: 3.1.0 -DEFAULT: array() ---DESCRIPTION-- -

          - This directive can be used to add custom filters; it is nearly the - equivalent of the now deprecated HTMLPurifier->addFilter() - method. Specify an array of concrete implementations. -

          ---# vim: et sw=4 sts=4 +Filter.Custom +TYPE: list +VERSION: 3.1.0 +DEFAULT: array() +--DESCRIPTION-- +

          + This directive can be used to add custom filters; it is nearly the + equivalent of the now deprecated HTMLPurifier->addFilter() + method. Specify an array of concrete implementations. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt index 69602635e..16829bcda 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt @@ -1,14 +1,14 @@ -Filter.ExtractStyleBlocks.Escaping -TYPE: bool -VERSION: 3.0.0 -DEFAULT: true -ALIASES: Filter.ExtractStyleBlocksEscaping, FilterParam.ExtractStyleBlocksEscaping ---DESCRIPTION-- - -

          - Whether or not to escape the dangerous characters <, > and & - as \3C, \3E and \26, respectively. This is can be safely set to false - if the contents of StyleBlocks will be placed in an external stylesheet, - where there is no risk of it being interpreted as HTML. -

          ---# vim: et sw=4 sts=4 +Filter.ExtractStyleBlocks.Escaping +TYPE: bool +VERSION: 3.0.0 +DEFAULT: true +ALIASES: Filter.ExtractStyleBlocksEscaping, FilterParam.ExtractStyleBlocksEscaping +--DESCRIPTION-- + +

          + Whether or not to escape the dangerous characters <, > and & + as \3C, \3E and \26, respectively. This is can be safely set to false + if the contents of StyleBlocks will be placed in an external stylesheet, + where there is no risk of it being interpreted as HTML. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt index baa81ae06..7f95f54d1 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt @@ -1,29 +1,29 @@ -Filter.ExtractStyleBlocks.Scope -TYPE: string/null -VERSION: 3.0.0 -DEFAULT: NULL -ALIASES: Filter.ExtractStyleBlocksScope, FilterParam.ExtractStyleBlocksScope ---DESCRIPTION-- - -

          - If you would like users to be able to define external stylesheets, but - only allow them to specify CSS declarations for a specific node and - prevent them from fiddling with other elements, use this directive. - It accepts any valid CSS selector, and will prepend this to any - CSS declaration extracted from the document. For example, if this - directive is set to #user-content and a user uses the - selector a:hover, the final selector will be - #user-content a:hover. -

          -

          - The comma shorthand may be used; consider the above example, with - #user-content, #user-content2, the final selector will - be #user-content a:hover, #user-content2 a:hover. -

          -

          - Warning: It is possible for users to bypass this measure - using a naughty + selector. This is a bug in CSS Tidy 1.3, not HTML - Purifier, and I am working to get it fixed. Until then, HTML Purifier - performs a basic check to prevent this. -

          ---# vim: et sw=4 sts=4 +Filter.ExtractStyleBlocks.Scope +TYPE: string/null +VERSION: 3.0.0 +DEFAULT: NULL +ALIASES: Filter.ExtractStyleBlocksScope, FilterParam.ExtractStyleBlocksScope +--DESCRIPTION-- + +

          + If you would like users to be able to define external stylesheets, but + only allow them to specify CSS declarations for a specific node and + prevent them from fiddling with other elements, use this directive. + It accepts any valid CSS selector, and will prepend this to any + CSS declaration extracted from the document. For example, if this + directive is set to #user-content and a user uses the + selector a:hover, the final selector will be + #user-content a:hover. +

          +

          + The comma shorthand may be used; consider the above example, with + #user-content, #user-content2, the final selector will + be #user-content a:hover, #user-content2 a:hover. +

          +

          + Warning: It is possible for users to bypass this measure + using a naughty + selector. This is a bug in CSS Tidy 1.3, not HTML + Purifier, and I am working to get it fixed. Until then, HTML Purifier + performs a basic check to prevent this. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt index 3b7018917..6c231b2d7 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt @@ -1,16 +1,16 @@ -Filter.ExtractStyleBlocks.TidyImpl -TYPE: mixed/null -VERSION: 3.1.0 -DEFAULT: NULL -ALIASES: FilterParam.ExtractStyleBlocksTidyImpl ---DESCRIPTION-- -

          - If left NULL, HTML Purifier will attempt to instantiate a csstidy - class to use for internal cleaning. This will usually be good enough. -

          -

          - However, for trusted user input, you can set this to false to - disable cleaning. In addition, you can supply your own concrete implementation - of Tidy's interface to use, although I don't know why you'd want to do that. -

          ---# vim: et sw=4 sts=4 +Filter.ExtractStyleBlocks.TidyImpl +TYPE: mixed/null +VERSION: 3.1.0 +DEFAULT: NULL +ALIASES: FilterParam.ExtractStyleBlocksTidyImpl +--DESCRIPTION-- +

          + If left NULL, HTML Purifier will attempt to instantiate a csstidy + class to use for internal cleaning. This will usually be good enough. +

          +

          + However, for trusted user input, you can set this to false to + disable cleaning. In addition, you can supply your own concrete implementation + of Tidy's interface to use, although I don't know why you'd want to do that. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt index be0177d4e..078d08741 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt @@ -1,74 +1,74 @@ -Filter.ExtractStyleBlocks -TYPE: bool -VERSION: 3.1.0 -DEFAULT: false -EXTERNAL: CSSTidy ---DESCRIPTION-- -

          - This directive turns on the style block extraction filter, which removes - style blocks from input HTML, cleans them up with CSSTidy, - and places them in the StyleBlocks context variable, for further - use by you, usually to be placed in an external stylesheet, or a - style block in the head of your document. -

          -

          - Sample usage: -

          -
          ';
          -?>
          -
          -
          -
          -  Filter.ExtractStyleBlocks
          -body {color:#F00;} Some text';
          -
          -    $config = HTMLPurifier_Config::createDefault();
          -    $config->set('Filter', 'ExtractStyleBlocks', true);
          -    $purifier = new HTMLPurifier($config);
          -
          -    $html = $purifier->purify($dirty);
          -
          -    // This implementation writes the stylesheets to the styles/ directory.
          -    // You can also echo the styles inside the document, but it's a bit
          -    // more difficult to make sure they get interpreted properly by
          -    // browsers; try the usual CSS armoring techniques.
          -    $styles = $purifier->context->get('StyleBlocks');
          -    $dir = 'styles/';
          -    if (!is_dir($dir)) mkdir($dir);
          -    $hash = sha1($_GET['html']);
          -    foreach ($styles as $i => $style) {
          -        file_put_contents($name = $dir . $hash . "_$i");
          -        echo '';
          -    }
          -?>
          -
          -
          -  
          - -
          - - -]]>
          -

          - Warning: It is possible for a user to mount an - imagecrash attack using this CSS. Counter-measures are difficult; - it is not simply enough to limit the range of CSS lengths (using - relative lengths with many nesting levels allows for large values - to be attained without actually specifying them in the stylesheet), - and the flexible nature of selectors makes it difficult to selectively - disable lengths on image tags (HTML Purifier, however, does disable - CSS width and height in inline styling). There are probably two effective - counter measures: an explicit width and height set to auto in all - images in your document (unlikely) or the disabling of width and - height (somewhat reasonable). Whether or not these measures should be - used is left to the reader. -

          ---# vim: et sw=4 sts=4 +Filter.ExtractStyleBlocks +TYPE: bool +VERSION: 3.1.0 +DEFAULT: false +EXTERNAL: CSSTidy +--DESCRIPTION-- +

          + This directive turns on the style block extraction filter, which removes + style blocks from input HTML, cleans them up with CSSTidy, + and places them in the StyleBlocks context variable, for further + use by you, usually to be placed in an external stylesheet, or a + style block in the head of your document. +

          +

          + Sample usage: +

          +
          ';
          +?>
          +
          +
          +
          +  Filter.ExtractStyleBlocks
          +body {color:#F00;} Some text';
          +
          +    $config = HTMLPurifier_Config::createDefault();
          +    $config->set('Filter', 'ExtractStyleBlocks', true);
          +    $purifier = new HTMLPurifier($config);
          +
          +    $html = $purifier->purify($dirty);
          +
          +    // This implementation writes the stylesheets to the styles/ directory.
          +    // You can also echo the styles inside the document, but it's a bit
          +    // more difficult to make sure they get interpreted properly by
          +    // browsers; try the usual CSS armoring techniques.
          +    $styles = $purifier->context->get('StyleBlocks');
          +    $dir = 'styles/';
          +    if (!is_dir($dir)) mkdir($dir);
          +    $hash = sha1($_GET['html']);
          +    foreach ($styles as $i => $style) {
          +        file_put_contents($name = $dir . $hash . "_$i");
          +        echo '';
          +    }
          +?>
          +
          +
          +  
          + +
          + + +]]>
          +

          + Warning: It is possible for a user to mount an + imagecrash attack using this CSS. Counter-measures are difficult; + it is not simply enough to limit the range of CSS lengths (using + relative lengths with many nesting levels allows for large values + to be attained without actually specifying them in the stylesheet), + and the flexible nature of selectors makes it difficult to selectively + disable lengths on image tags (HTML Purifier, however, does disable + CSS width and height in inline styling). There are probably two effective + counter measures: an explicit width and height set to auto in all + images in your document (unlikely) or the disabling of width and + height (somewhat reasonable). Whether or not these measures should be + used is left to the reader. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt index 882218668..321eaa2d8 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt @@ -1,16 +1,16 @@ -Filter.YouTube -TYPE: bool -VERSION: 3.1.0 -DEFAULT: false ---DESCRIPTION-- -

          - Warning: Deprecated in favor of %HTML.SafeObject and - %Output.FlashCompat (turn both on to allow YouTube videos and other - Flash content). -

          -

          - This directive enables YouTube video embedding in HTML Purifier. Check - this document - on embedding videos for more information on what this filter does. -

          ---# vim: et sw=4 sts=4 +Filter.YouTube +TYPE: bool +VERSION: 3.1.0 +DEFAULT: false +--DESCRIPTION-- +

          + Warning: Deprecated in favor of %HTML.SafeObject and + %Output.FlashCompat (turn both on to allow YouTube videos and other + Flash content). +

          +

          + This directive enables YouTube video embedding in HTML Purifier. Check + this document + on embedding videos for more information on what this filter does. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt index afd48a0d4..0b2c106da 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt @@ -1,25 +1,25 @@ -HTML.Allowed -TYPE: itext/null -VERSION: 2.0.0 -DEFAULT: NULL ---DESCRIPTION-- - -

          - This is a preferred convenience directive that combines - %HTML.AllowedElements and %HTML.AllowedAttributes. - Specify elements and attributes that are allowed using: - element1[attr1|attr2],element2.... For example, - if you would like to only allow paragraphs and links, specify - a[href],p. You can specify attributes that apply - to all elements using an asterisk, e.g. *[lang]. - You can also use newlines instead of commas to separate elements. -

          -

          - Warning: - All of the constraints on the component directives are still enforced. - The syntax is a subset of TinyMCE's valid_elements - whitelist: directly copy-pasting it here will probably result in - broken whitelists. If %HTML.AllowedElements or %HTML.AllowedAttributes - are set, this directive has no effect. -

          ---# vim: et sw=4 sts=4 +HTML.Allowed +TYPE: itext/null +VERSION: 2.0.0 +DEFAULT: NULL +--DESCRIPTION-- + +

          + This is a preferred convenience directive that combines + %HTML.AllowedElements and %HTML.AllowedAttributes. + Specify elements and attributes that are allowed using: + element1[attr1|attr2],element2.... For example, + if you would like to only allow paragraphs and links, specify + a[href],p. You can specify attributes that apply + to all elements using an asterisk, e.g. *[lang]. + You can also use newlines instead of commas to separate elements. +

          +

          + Warning: + All of the constraints on the component directives are still enforced. + The syntax is a subset of TinyMCE's valid_elements + whitelist: directly copy-pasting it here will probably result in + broken whitelists. If %HTML.AllowedElements or %HTML.AllowedAttributes + are set, this directive has no effect. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt index 0e6ec54f3..fcf093f17 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt @@ -1,19 +1,19 @@ -HTML.AllowedAttributes -TYPE: lookup/null -VERSION: 1.3.0 -DEFAULT: NULL ---DESCRIPTION-- - -

          - If HTML Purifier's attribute set is unsatisfactory, overload it! - The syntax is "tag.attr" or "*.attr" for the global attributes - (style, id, class, dir, lang, xml:lang). -

          -

          - Warning: If another directive conflicts with the - elements here, that directive will win and override. For - example, %HTML.EnableAttrID will take precedence over *.id in this - directive. You must set that directive to true before you can use - IDs at all. -

          ---# vim: et sw=4 sts=4 +HTML.AllowedAttributes +TYPE: lookup/null +VERSION: 1.3.0 +DEFAULT: NULL +--DESCRIPTION-- + +

          + If HTML Purifier's attribute set is unsatisfactory, overload it! + The syntax is "tag.attr" or "*.attr" for the global attributes + (style, id, class, dir, lang, xml:lang). +

          +

          + Warning: If another directive conflicts with the + elements here, that directive will win and override. For + example, %HTML.EnableAttrID will take precedence over *.id in this + directive. You must set that directive to true before you can use + IDs at all. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt index ca3c13ddb..1d3fa7907 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt @@ -1,23 +1,23 @@ -HTML.AllowedElements -TYPE: lookup/null -VERSION: 1.3.0 -DEFAULT: NULL ---DESCRIPTION-- -

          - If HTML Purifier's tag set is unsatisfactory for your needs, you can - overload it with your own list of tags to allow. If you change - this, you probably also want to change %HTML.AllowedAttributes; see - also %HTML.Allowed which lets you set allowed elements and - attributes at the same time. -

          -

          - If you attempt to allow an element that HTML Purifier does not know - about, HTML Purifier will raise an error. You will need to manually - tell HTML Purifier about this element by using the - advanced customization features. -

          -

          - Warning: If another directive conflicts with the - elements here, that directive will win and override. -

          ---# vim: et sw=4 sts=4 +HTML.AllowedElements +TYPE: lookup/null +VERSION: 1.3.0 +DEFAULT: NULL +--DESCRIPTION-- +

          + If HTML Purifier's tag set is unsatisfactory for your needs, you can + overload it with your own list of tags to allow. If you change + this, you probably also want to change %HTML.AllowedAttributes; see + also %HTML.Allowed which lets you set allowed elements and + attributes at the same time. +

          +

          + If you attempt to allow an element that HTML Purifier does not know + about, HTML Purifier will raise an error. You will need to manually + tell HTML Purifier about this element by using the + advanced customization features. +

          +

          + Warning: If another directive conflicts with the + elements here, that directive will win and override. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt index e373791a5..5a59a55c0 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt @@ -1,20 +1,20 @@ -HTML.AllowedModules -TYPE: lookup/null -VERSION: 2.0.0 -DEFAULT: NULL ---DESCRIPTION-- - -

          - A doctype comes with a set of usual modules to use. Without having - to mucking about with the doctypes, you can quickly activate or - disable these modules by specifying which modules you wish to allow - with this directive. This is most useful for unit testing specific - modules, although end users may find it useful for their own ends. -

          -

          - If you specify a module that does not exist, the manager will silently - fail to use it, so be careful! User-defined modules are not affected - by this directive. Modules defined in %HTML.CoreModules are not - affected by this directive. -

          ---# vim: et sw=4 sts=4 +HTML.AllowedModules +TYPE: lookup/null +VERSION: 2.0.0 +DEFAULT: NULL +--DESCRIPTION-- + +

          + A doctype comes with a set of usual modules to use. Without having + to mucking about with the doctypes, you can quickly activate or + disable these modules by specifying which modules you wish to allow + with this directive. This is most useful for unit testing specific + modules, although end users may find it useful for their own ends. +

          +

          + If you specify a module that does not exist, the manager will silently + fail to use it, so be careful! User-defined modules are not affected + by this directive. Modules defined in %HTML.CoreModules are not + affected by this directive. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt index 75d680ee1..151fb7b82 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt @@ -1,11 +1,11 @@ -HTML.Attr.Name.UseCDATA -TYPE: bool -DEFAULT: false -VERSION: 4.0.0 ---DESCRIPTION-- -The W3C specification DTD defines the name attribute to be CDATA, not ID, due -to limitations of DTD. In certain documents, this relaxed behavior is desired, -whether it is to specify duplicate names, or to specify names that would be -illegal IDs (for example, names that begin with a digit.) Set this configuration -directive to true to use the relaxed parsing rules. ---# vim: et sw=4 sts=4 +HTML.Attr.Name.UseCDATA +TYPE: bool +DEFAULT: false +VERSION: 4.0.0 +--DESCRIPTION-- +The W3C specification DTD defines the name attribute to be CDATA, not ID, due +to limitations of DTD. In certain documents, this relaxed behavior is desired, +whether it is to specify duplicate names, or to specify names that would be +illegal IDs (for example, names that begin with a digit.) Set this configuration +directive to true to use the relaxed parsing rules. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt index f32b802c6..45ae469ec 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt @@ -1,18 +1,18 @@ -HTML.BlockWrapper -TYPE: string -VERSION: 1.3.0 -DEFAULT: 'p' ---DESCRIPTION-- - -

          - String name of element to wrap inline elements that are inside a block - context. This only occurs in the children of blockquote in strict mode. -

          -

          - Example: by default value, - <blockquote>Foo</blockquote> would become - <blockquote><p>Foo</p></blockquote>. - The <p> tags can be replaced with whatever you desire, - as long as it is a block level element. -

          ---# vim: et sw=4 sts=4 +HTML.BlockWrapper +TYPE: string +VERSION: 1.3.0 +DEFAULT: 'p' +--DESCRIPTION-- + +

          + String name of element to wrap inline elements that are inside a block + context. This only occurs in the children of blockquote in strict mode. +

          +

          + Example: by default value, + <blockquote>Foo</blockquote> would become + <blockquote><p>Foo</p></blockquote>. + The <p> tags can be replaced with whatever you desire, + as long as it is a block level element. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt index fc8e40205..524618879 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt @@ -1,23 +1,23 @@ -HTML.CoreModules -TYPE: lookup -VERSION: 2.0.0 ---DEFAULT-- -array ( - 'Structure' => true, - 'Text' => true, - 'Hypertext' => true, - 'List' => true, - 'NonXMLCommonAttributes' => true, - 'XMLCommonAttributes' => true, - 'CommonAttributes' => true, -) ---DESCRIPTION-- - -

          - Certain modularized doctypes (XHTML, namely), have certain modules - that must be included for the doctype to be an conforming document - type: put those modules here. By default, XHTML's core modules - are used. You can set this to a blank array to disable core module - protection, but this is not recommended. -

          ---# vim: et sw=4 sts=4 +HTML.CoreModules +TYPE: lookup +VERSION: 2.0.0 +--DEFAULT-- +array ( + 'Structure' => true, + 'Text' => true, + 'Hypertext' => true, + 'List' => true, + 'NonXMLCommonAttributes' => true, + 'XMLCommonAttributes' => true, + 'CommonAttributes' => true, +) +--DESCRIPTION-- + +

          + Certain modularized doctypes (XHTML, namely), have certain modules + that must be included for the doctype to be an conforming document + type: put those modules here. By default, XHTML's core modules + are used. You can set this to a blank array to disable core module + protection, but this is not recommended. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt index a2bde5dc1..a64e3d7c3 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt @@ -1,9 +1,9 @@ -HTML.CustomDoctype -TYPE: string/null -VERSION: 2.0.1 -DEFAULT: NULL ---DESCRIPTION-- - -A custom doctype for power-users who defined there own document -type. This directive only applies when %HTML.Doctype is blank. ---# vim: et sw=4 sts=4 +HTML.CustomDoctype +TYPE: string/null +VERSION: 2.0.1 +DEFAULT: NULL +--DESCRIPTION-- + +A custom doctype for power-users who defined there own document +type. This directive only applies when %HTML.Doctype is blank. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt index f5433e3f1..103db754a 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt @@ -1,33 +1,33 @@ -HTML.DefinitionID -TYPE: string/null -DEFAULT: NULL -VERSION: 2.0.0 ---DESCRIPTION-- - -

          - Unique identifier for a custom-built HTML definition. If you edit - the raw version of the HTMLDefinition, introducing changes that the - configuration object does not reflect, you must specify this variable. - If you change your custom edits, you should change this directive, or - clear your cache. Example: -

          -
          -$config = HTMLPurifier_Config::createDefault();
          -$config->set('HTML', 'DefinitionID', '1');
          -$def = $config->getHTMLDefinition();
          -$def->addAttribute('a', 'tabindex', 'Number');
          -
          -

          - In the above example, the configuration is still at the defaults, but - using the advanced API, an extra attribute has been added. The - configuration object normally has no way of knowing that this change - has taken place, so it needs an extra directive: %HTML.DefinitionID. - If someone else attempts to use the default configuration, these two - pieces of code will not clobber each other in the cache, since one has - an extra directive attached to it. -

          -

          - You must specify a value to this directive to use the - advanced API features. -

          ---# vim: et sw=4 sts=4 +HTML.DefinitionID +TYPE: string/null +DEFAULT: NULL +VERSION: 2.0.0 +--DESCRIPTION-- + +

          + Unique identifier for a custom-built HTML definition. If you edit + the raw version of the HTMLDefinition, introducing changes that the + configuration object does not reflect, you must specify this variable. + If you change your custom edits, you should change this directive, or + clear your cache. Example: +

          +
          +$config = HTMLPurifier_Config::createDefault();
          +$config->set('HTML', 'DefinitionID', '1');
          +$def = $config->getHTMLDefinition();
          +$def->addAttribute('a', 'tabindex', 'Number');
          +
          +

          + In the above example, the configuration is still at the defaults, but + using the advanced API, an extra attribute has been added. The + configuration object normally has no way of knowing that this change + has taken place, so it needs an extra directive: %HTML.DefinitionID. + If someone else attempts to use the default configuration, these two + pieces of code will not clobber each other in the cache, since one has + an extra directive attached to it. +

          +

          + You must specify a value to this directive to use the + advanced API features. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt index 0bb5a718d..229ae0267 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt @@ -1,16 +1,16 @@ -HTML.DefinitionRev -TYPE: int -VERSION: 2.0.0 -DEFAULT: 1 ---DESCRIPTION-- - -

          - Revision identifier for your custom definition specified in - %HTML.DefinitionID. This serves the same purpose: uniquely identifying - your custom definition, but this one does so in a chronological - context: revision 3 is more up-to-date then revision 2. Thus, when - this gets incremented, the cache handling is smart enough to clean - up any older revisions of your definition as well as flush the - cache. -

          ---# vim: et sw=4 sts=4 +HTML.DefinitionRev +TYPE: int +VERSION: 2.0.0 +DEFAULT: 1 +--DESCRIPTION-- + +

          + Revision identifier for your custom definition specified in + %HTML.DefinitionID. This serves the same purpose: uniquely identifying + your custom definition, but this one does so in a chronological + context: revision 3 is more up-to-date then revision 2. Thus, when + this gets incremented, the cache handling is smart enough to clean + up any older revisions of your definition as well as flush the + cache. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt index a6969b995..9dab497f2 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt @@ -1,11 +1,11 @@ -HTML.Doctype -TYPE: string/null -DEFAULT: NULL ---DESCRIPTION-- -Doctype to use during filtering. Technically speaking this is not actually -a doctype (as it does not identify a corresponding DTD), but we are using -this name for sake of simplicity. When non-blank, this will override any -older directives like %HTML.XHTML or %HTML.Strict. ---ALLOWED-- -'HTML 4.01 Transitional', 'HTML 4.01 Strict', 'XHTML 1.0 Transitional', 'XHTML 1.0 Strict', 'XHTML 1.1' ---# vim: et sw=4 sts=4 +HTML.Doctype +TYPE: string/null +DEFAULT: NULL +--DESCRIPTION-- +Doctype to use during filtering. Technically speaking this is not actually +a doctype (as it does not identify a corresponding DTD), but we are using +this name for sake of simplicity. When non-blank, this will override any +older directives like %HTML.XHTML or %HTML.Strict. +--ALLOWED-- +'HTML 4.01 Transitional', 'HTML 4.01 Strict', 'XHTML 1.0 Transitional', 'XHTML 1.0 Strict', 'XHTML 1.1' +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt index 2b8df97cb..57358f9ba 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt @@ -1,21 +1,21 @@ -HTML.ForbiddenAttributes -TYPE: lookup -VERSION: 3.1.0 -DEFAULT: array() ---DESCRIPTION-- -

          - While this directive is similar to %HTML.AllowedAttributes, for - forwards-compatibility with XML, this attribute has a different syntax. Instead of - tag.attr, use tag@attr. To disallow href - attributes in a tags, set this directive to - a@href. You can also disallow an attribute globally with - attr or *@attr (either syntax is fine; the latter - is provided for consistency with %HTML.AllowedAttributes). -

          -

          - Warning: This directive complements %HTML.ForbiddenElements, - accordingly, check - out that directive for a discussion of why you - should think twice before using this directive. -

          ---# vim: et sw=4 sts=4 +HTML.ForbiddenAttributes +TYPE: lookup +VERSION: 3.1.0 +DEFAULT: array() +--DESCRIPTION-- +

          + While this directive is similar to %HTML.AllowedAttributes, for + forwards-compatibility with XML, this attribute has a different syntax. Instead of + tag.attr, use tag@attr. To disallow href + attributes in a tags, set this directive to + a@href. You can also disallow an attribute globally with + attr or *@attr (either syntax is fine; the latter + is provided for consistency with %HTML.AllowedAttributes). +

          +

          + Warning: This directive complements %HTML.ForbiddenElements, + accordingly, check + out that directive for a discussion of why you + should think twice before using this directive. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt index 40466c463..93a53e14f 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt @@ -1,20 +1,20 @@ -HTML.ForbiddenElements -TYPE: lookup -VERSION: 3.1.0 -DEFAULT: array() ---DESCRIPTION-- -

          - This was, perhaps, the most requested feature ever in HTML - Purifier. Please don't abuse it! This is the logical inverse of - %HTML.AllowedElements, and it will override that directive, or any - other directive. -

          -

          - If possible, %HTML.Allowed is recommended over this directive, because it - can sometimes be difficult to tell whether or not you've forbidden all of - the behavior you would like to disallow. If you forbid img - with the expectation of preventing images on your site, you'll be in for - a nasty surprise when people start using the background-image - CSS property. -

          ---# vim: et sw=4 sts=4 +HTML.ForbiddenElements +TYPE: lookup +VERSION: 3.1.0 +DEFAULT: array() +--DESCRIPTION-- +

          + This was, perhaps, the most requested feature ever in HTML + Purifier. Please don't abuse it! This is the logical inverse of + %HTML.AllowedElements, and it will override that directive, or any + other directive. +

          +

          + If possible, %HTML.Allowed is recommended over this directive, because it + can sometimes be difficult to tell whether or not you've forbidden all of + the behavior you would like to disallow. If you forbid img + with the expectation of preventing images on your site, you'll be in for + a nasty surprise when people start using the background-image + CSS property. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt index 319747954..e424c386e 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt @@ -1,14 +1,14 @@ -HTML.MaxImgLength -TYPE: int/null -DEFAULT: 1200 -VERSION: 3.1.1 ---DESCRIPTION-- -

          - This directive controls the maximum number of pixels in the width and - height attributes in img tags. This is - in place to prevent imagecrash attacks, disable with null at your own risk. - This directive is similar to %CSS.MaxImgLength, and both should be - concurrently edited, although there are - subtle differences in the input format (the HTML max is an integer). -

          ---# vim: et sw=4 sts=4 +HTML.MaxImgLength +TYPE: int/null +DEFAULT: 1200 +VERSION: 3.1.1 +--DESCRIPTION-- +

          + This directive controls the maximum number of pixels in the width and + height attributes in img tags. This is + in place to prevent imagecrash attacks, disable with null at your own risk. + This directive is similar to %CSS.MaxImgLength, and both should be + concurrently edited, although there are + subtle differences in the input format (the HTML max is an integer). +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt index 2d2fbd117..62e8e160c 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt @@ -1,12 +1,12 @@ -HTML.Parent -TYPE: string -VERSION: 1.3.0 -DEFAULT: 'div' ---DESCRIPTION-- - -

          - String name of element that HTML fragment passed to library will be - inserted in. An interesting variation would be using span as the - parent element, meaning that only inline tags would be allowed. -

          ---# vim: et sw=4 sts=4 +HTML.Parent +TYPE: string +VERSION: 1.3.0 +DEFAULT: 'div' +--DESCRIPTION-- + +

          + String name of element that HTML fragment passed to library will be + inserted in. An interesting variation would be using span as the + parent element, meaning that only inline tags would be allowed. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt index b3c45e190..dfb720496 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt @@ -1,12 +1,12 @@ -HTML.Proprietary -TYPE: bool -VERSION: 3.1.0 -DEFAULT: false ---DESCRIPTION-- -

          - Whether or not to allow proprietary elements and attributes in your - documents, as per HTMLPurifier_HTMLModule_Proprietary. - Warning: This can cause your documents to stop - validating! -

          ---# vim: et sw=4 sts=4 +HTML.Proprietary +TYPE: bool +VERSION: 3.1.0 +DEFAULT: false +--DESCRIPTION-- +

          + Whether or not to allow proprietary elements and attributes in your + documents, as per HTMLPurifier_HTMLModule_Proprietary. + Warning: This can cause your documents to stop + validating! +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt index 556fa674f..cdda09a4c 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt @@ -1,13 +1,13 @@ -HTML.SafeEmbed -TYPE: bool -VERSION: 3.1.1 -DEFAULT: false ---DESCRIPTION-- -

          - Whether or not to permit embed tags in documents, with a number of extra - security features added to prevent script execution. This is similar to - what websites like MySpace do to embed tags. Embed is a proprietary - element and will cause your website to stop validating; you should - see if you can use %Output.FlashCompat with %HTML.SafeObject instead - first.

          ---# vim: et sw=4 sts=4 +HTML.SafeEmbed +TYPE: bool +VERSION: 3.1.1 +DEFAULT: false +--DESCRIPTION-- +

          + Whether or not to permit embed tags in documents, with a number of extra + security features added to prevent script execution. This is similar to + what websites like MySpace do to embed tags. Embed is a proprietary + element and will cause your website to stop validating; you should + see if you can use %Output.FlashCompat with %HTML.SafeObject instead + first.

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt index 07f6e536e..ceb342e22 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt @@ -1,13 +1,13 @@ -HTML.SafeObject -TYPE: bool -VERSION: 3.1.1 -DEFAULT: false ---DESCRIPTION-- -

          - Whether or not to permit object tags in documents, with a number of extra - security features added to prevent script execution. This is similar to - what websites like MySpace do to object tags. You should also enable - %Output.FlashCompat in order to generate Internet Explorer - compatibility code for your object tags. -

          ---# vim: et sw=4 sts=4 +HTML.SafeObject +TYPE: bool +VERSION: 3.1.1 +DEFAULT: false +--DESCRIPTION-- +

          + Whether or not to permit object tags in documents, with a number of extra + security features added to prevent script execution. This is similar to + what websites like MySpace do to object tags. You should also enable + %Output.FlashCompat in order to generate Internet Explorer + compatibility code for your object tags. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt index d99663a5e..a8b1de56b 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt @@ -1,9 +1,9 @@ -HTML.Strict -TYPE: bool -VERSION: 1.3.0 -DEFAULT: false -DEPRECATED-VERSION: 1.7.0 -DEPRECATED-USE: HTML.Doctype ---DESCRIPTION-- -Determines whether or not to use Transitional (loose) or Strict rulesets. ---# vim: et sw=4 sts=4 +HTML.Strict +TYPE: bool +VERSION: 1.3.0 +DEFAULT: false +DEPRECATED-VERSION: 1.7.0 +DEPRECATED-USE: HTML.Doctype +--DESCRIPTION-- +Determines whether or not to use Transitional (loose) or Strict rulesets. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt index 602453f6e..b4c271b7f 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt @@ -1,8 +1,8 @@ -HTML.TidyAdd -TYPE: lookup -VERSION: 2.0.0 -DEFAULT: array() ---DESCRIPTION-- - -Fixes to add to the default set of Tidy fixes as per your level. ---# vim: et sw=4 sts=4 +HTML.TidyAdd +TYPE: lookup +VERSION: 2.0.0 +DEFAULT: array() +--DESCRIPTION-- + +Fixes to add to the default set of Tidy fixes as per your level. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt index bf943e8f0..4186ccd0d 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt @@ -1,24 +1,24 @@ -HTML.TidyLevel -TYPE: string -VERSION: 2.0.0 -DEFAULT: 'medium' ---DESCRIPTION-- - -

          General level of cleanliness the Tidy module should enforce. -There are four allowed values:

          -
          -
          none
          -
          No extra tidying should be done
          -
          light
          -
          Only fix elements that would be discarded otherwise due to - lack of support in doctype
          -
          medium
          -
          Enforce best practices
          -
          heavy
          -
          Transform all deprecated elements and attributes to standards - compliant equivalents
          -
          - ---ALLOWED-- -'none', 'light', 'medium', 'heavy' ---# vim: et sw=4 sts=4 +HTML.TidyLevel +TYPE: string +VERSION: 2.0.0 +DEFAULT: 'medium' +--DESCRIPTION-- + +

          General level of cleanliness the Tidy module should enforce. +There are four allowed values:

          +
          +
          none
          +
          No extra tidying should be done
          +
          light
          +
          Only fix elements that would be discarded otherwise due to + lack of support in doctype
          +
          medium
          +
          Enforce best practices
          +
          heavy
          +
          Transform all deprecated elements and attributes to standards + compliant equivalents
          +
          + +--ALLOWED-- +'none', 'light', 'medium', 'heavy' +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt index 92cca2a43..996762bd1 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt @@ -1,8 +1,8 @@ -HTML.TidyRemove -TYPE: lookup -VERSION: 2.0.0 -DEFAULT: array() ---DESCRIPTION-- - -Fixes to remove from the default set of Tidy fixes as per your level. ---# vim: et sw=4 sts=4 +HTML.TidyRemove +TYPE: lookup +VERSION: 2.0.0 +DEFAULT: array() +--DESCRIPTION-- + +Fixes to remove from the default set of Tidy fixes as per your level. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt index bc8e65499..1db9237e9 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt @@ -1,9 +1,9 @@ -HTML.Trusted -TYPE: bool -VERSION: 2.0.0 -DEFAULT: false ---DESCRIPTION-- -Indicates whether or not the user input is trusted or not. If the input is -trusted, a more expansive set of allowed tags and attributes will be used. -See also %CSS.Trusted. ---# vim: et sw=4 sts=4 +HTML.Trusted +TYPE: bool +VERSION: 2.0.0 +DEFAULT: false +--DESCRIPTION-- +Indicates whether or not the user input is trusted or not. If the input is +trusted, a more expansive set of allowed tags and attributes will be used. +See also %CSS.Trusted. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt index a3c2f42c3..2a47e384f 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt @@ -1,11 +1,11 @@ -HTML.XHTML -TYPE: bool -DEFAULT: true -VERSION: 1.1.0 -DEPRECATED-VERSION: 1.7.0 -DEPRECATED-USE: HTML.Doctype ---DESCRIPTION-- -Determines whether or not output is XHTML 1.0 or HTML 4.01 flavor. ---ALIASES-- -Core.XHTML ---# vim: et sw=4 sts=4 +HTML.XHTML +TYPE: bool +DEFAULT: true +VERSION: 1.1.0 +DEPRECATED-VERSION: 1.7.0 +DEPRECATED-USE: HTML.Doctype +--DESCRIPTION-- +Determines whether or not output is XHTML 1.0 or HTML 4.01 flavor. +--ALIASES-- +Core.XHTML +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt index 2a1370470..08921fde7 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt @@ -1,10 +1,10 @@ -Output.CommentScriptContents -TYPE: bool -VERSION: 2.0.0 -DEFAULT: true ---DESCRIPTION-- -Determines whether or not HTML Purifier should attempt to fix up the -contents of script tags for legacy browsers with comments. ---ALIASES-- -Core.CommentScriptContents ---# vim: et sw=4 sts=4 +Output.CommentScriptContents +TYPE: bool +VERSION: 2.0.0 +DEFAULT: true +--DESCRIPTION-- +Determines whether or not HTML Purifier should attempt to fix up the +contents of script tags for legacy browsers with comments. +--ALIASES-- +Core.CommentScriptContents +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt index e58f91aa8..93398e859 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt @@ -1,11 +1,11 @@ -Output.FlashCompat -TYPE: bool -VERSION: 4.1.0 -DEFAULT: false ---DESCRIPTION-- -

          - If true, HTML Purifier will generate Internet Explorer compatibility - code for all object code. This is highly recommended if you enable - %HTML.SafeObject. -

          ---# vim: et sw=4 sts=4 +Output.FlashCompat +TYPE: bool +VERSION: 4.1.0 +DEFAULT: false +--DESCRIPTION-- +

          + If true, HTML Purifier will generate Internet Explorer compatibility + code for all object code. This is highly recommended if you enable + %HTML.SafeObject. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt index 4bb902523..79f8ad82c 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt @@ -1,13 +1,13 @@ -Output.Newline -TYPE: string/null -VERSION: 2.0.1 -DEFAULT: NULL ---DESCRIPTION-- - -

          - Newline string to format final output with. If left null, HTML Purifier - will auto-detect the default newline type of the system and use that; - you can manually override it here. Remember, \r\n is Windows, \r - is Mac, and \n is Unix. -

          ---# vim: et sw=4 sts=4 +Output.Newline +TYPE: string/null +VERSION: 2.0.1 +DEFAULT: NULL +--DESCRIPTION-- + +

          + Newline string to format final output with. If left null, HTML Purifier + will auto-detect the default newline type of the system and use that; + you can manually override it here. Remember, \r\n is Windows, \r + is Mac, and \n is Unix. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt index 322310651..232b02362 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt @@ -1,14 +1,14 @@ -Output.SortAttr -TYPE: bool -VERSION: 3.2.0 -DEFAULT: false ---DESCRIPTION-- -

          - If true, HTML Purifier will sort attributes by name before writing them back - to the document, converting a tag like: <el b="" a="" c="" /> - to <el a="" b="" c="" />. This is a workaround for - a bug in FCKeditor which causes it to swap attributes order, adding noise - to text diffs. If you're not seeing this bug, chances are, you don't need - this directive. -

          ---# vim: et sw=4 sts=4 +Output.SortAttr +TYPE: bool +VERSION: 3.2.0 +DEFAULT: false +--DESCRIPTION-- +

          + If true, HTML Purifier will sort attributes by name before writing them back + to the document, converting a tag like: <el b="" a="" c="" /> + to <el a="" b="" c="" />. This is a workaround for + a bug in FCKeditor which causes it to swap attributes order, adding noise + to text diffs. If you're not seeing this bug, chances are, you don't need + this directive. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt index 23dd4d3d5..06bab00a0 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt @@ -1,25 +1,25 @@ -Output.TidyFormat -TYPE: bool -VERSION: 1.1.1 -DEFAULT: false ---DESCRIPTION-- -

          - Determines whether or not to run Tidy on the final output for pretty - formatting reasons, such as indentation and wrap. -

          -

          - This can greatly improve readability for editors who are hand-editing - the HTML, but is by no means necessary as HTML Purifier has already - fixed all major errors the HTML may have had. Tidy is a non-default - extension, and this directive will silently fail if Tidy is not - available. -

          -

          - If you are looking to make the overall look of your page's source - better, I recommend running Tidy on the entire page rather than just - user-content (after all, the indentation relative to the containing - blocks will be incorrect). -

          ---ALIASES-- -Core.TidyFormat ---# vim: et sw=4 sts=4 +Output.TidyFormat +TYPE: bool +VERSION: 1.1.1 +DEFAULT: false +--DESCRIPTION-- +

          + Determines whether or not to run Tidy on the final output for pretty + formatting reasons, such as indentation and wrap. +

          +

          + This can greatly improve readability for editors who are hand-editing + the HTML, but is by no means necessary as HTML Purifier has already + fixed all major errors the HTML may have had. Tidy is a non-default + extension, and this directive will silently fail if Tidy is not + available. +

          +

          + If you are looking to make the overall look of your page's source + better, I recommend running Tidy on the entire page rather than just + user-content (after all, the indentation relative to the containing + blocks will be incorrect). +

          +--ALIASES-- +Core.TidyFormat +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt index d1820cdbd..071bc0295 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt @@ -1,7 +1,7 @@ -Test.ForceNoIconv -TYPE: bool -DEFAULT: false ---DESCRIPTION-- -When set to true, HTMLPurifier_Encoder will act as if iconv does not exist -and use only pure PHP implementations. ---# vim: et sw=4 sts=4 +Test.ForceNoIconv +TYPE: bool +DEFAULT: false +--DESCRIPTION-- +When set to true, HTMLPurifier_Encoder will act as if iconv does not exist +and use only pure PHP implementations. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt index 47714f5d2..666635a5f 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt @@ -1,17 +1,17 @@ -URI.AllowedSchemes -TYPE: lookup ---DEFAULT-- -array ( - 'http' => true, - 'https' => true, - 'mailto' => true, - 'ftp' => true, - 'nntp' => true, - 'news' => true, -) ---DESCRIPTION-- -Whitelist that defines the schemes that a URI is allowed to have. This -prevents XSS attacks from using pseudo-schemes like javascript or mocha. -There is also support for the data and file -URI schemes, but they are not enabled by default. ---# vim: et sw=4 sts=4 +URI.AllowedSchemes +TYPE: lookup +--DEFAULT-- +array ( + 'http' => true, + 'https' => true, + 'mailto' => true, + 'ftp' => true, + 'nntp' => true, + 'news' => true, +) +--DESCRIPTION-- +Whitelist that defines the schemes that a URI is allowed to have. This +prevents XSS attacks from using pseudo-schemes like javascript or mocha. +There is also support for the data and file +URI schemes, but they are not enabled by default. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Base.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Base.txt index ba4730808..876f0680c 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Base.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Base.txt @@ -1,17 +1,17 @@ -URI.Base -TYPE: string/null -VERSION: 2.1.0 -DEFAULT: NULL ---DESCRIPTION-- - -

          - The base URI is the URI of the document this purified HTML will be - inserted into. This information is important if HTML Purifier needs - to calculate absolute URIs from relative URIs, such as when %URI.MakeAbsolute - is on. You may use a non-absolute URI for this value, but behavior - may vary (%URI.MakeAbsolute deals nicely with both absolute and - relative paths, but forwards-compatibility is not guaranteed). - Warning: If set, the scheme on this URI - overrides the one specified by %URI.DefaultScheme. -

          ---# vim: et sw=4 sts=4 +URI.Base +TYPE: string/null +VERSION: 2.1.0 +DEFAULT: NULL +--DESCRIPTION-- + +

          + The base URI is the URI of the document this purified HTML will be + inserted into. This information is important if HTML Purifier needs + to calculate absolute URIs from relative URIs, such as when %URI.MakeAbsolute + is on. You may use a non-absolute URI for this value, but behavior + may vary (%URI.MakeAbsolute deals nicely with both absolute and + relative paths, but forwards-compatibility is not guaranteed). + Warning: If set, the scheme on this URI + overrides the one specified by %URI.DefaultScheme. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt index 0700e0b1b..728e378cb 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt @@ -1,10 +1,10 @@ -URI.DefaultScheme -TYPE: string -DEFAULT: 'http' ---DESCRIPTION-- - -

          - Defines through what scheme the output will be served, in order to - select the proper object validator when no scheme information is present. -

          ---# vim: et sw=4 sts=4 +URI.DefaultScheme +TYPE: string +DEFAULT: 'http' +--DESCRIPTION-- + +

          + Defines through what scheme the output will be served, in order to + select the proper object validator when no scheme information is present. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt index 523204c08..f05312ba8 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt @@ -1,11 +1,11 @@ -URI.DefinitionID -TYPE: string/null -VERSION: 2.1.0 -DEFAULT: NULL ---DESCRIPTION-- - -

          - Unique identifier for a custom-built URI definition. If you want - to add custom URIFilters, you must specify this value. -

          ---# vim: et sw=4 sts=4 +URI.DefinitionID +TYPE: string/null +VERSION: 2.1.0 +DEFAULT: NULL +--DESCRIPTION-- + +

          + Unique identifier for a custom-built URI definition. If you want + to add custom URIFilters, you must specify this value. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt index a9c07b1a3..80cfea93f 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt @@ -1,11 +1,11 @@ -URI.DefinitionRev -TYPE: int -VERSION: 2.1.0 -DEFAULT: 1 ---DESCRIPTION-- - -

          - Revision identifier for your custom definition. See - %HTML.DefinitionRev for details. -

          ---# vim: et sw=4 sts=4 +URI.DefinitionRev +TYPE: int +VERSION: 2.1.0 +DEFAULT: 1 +--DESCRIPTION-- + +

          + Revision identifier for your custom definition. See + %HTML.DefinitionRev for details. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt index b19ca1d5b..71ce025a2 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt @@ -1,14 +1,14 @@ -URI.Disable -TYPE: bool -VERSION: 1.3.0 -DEFAULT: false ---DESCRIPTION-- - -

          - Disables all URIs in all forms. Not sure why you'd want to do that - (after all, the Internet's founded on the notion of a hyperlink). -

          - ---ALIASES-- -Attr.DisableURI ---# vim: et sw=4 sts=4 +URI.Disable +TYPE: bool +VERSION: 1.3.0 +DEFAULT: false +--DESCRIPTION-- + +

          + Disables all URIs in all forms. Not sure why you'd want to do that + (after all, the Internet's founded on the notion of a hyperlink). +

          + +--ALIASES-- +Attr.DisableURI +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt index 9132ea4f5..13c122c8c 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt @@ -1,11 +1,11 @@ -URI.DisableExternal -TYPE: bool -VERSION: 1.2.0 -DEFAULT: false ---DESCRIPTION-- -Disables links to external websites. This is a highly effective anti-spam -and anti-pagerank-leech measure, but comes at a hefty price: nolinks or -images outside of your domain will be allowed. Non-linkified URIs will -still be preserved. If you want to be able to link to subdomains or use -absolute URIs, specify %URI.Host for your website. ---# vim: et sw=4 sts=4 +URI.DisableExternal +TYPE: bool +VERSION: 1.2.0 +DEFAULT: false +--DESCRIPTION-- +Disables links to external websites. This is a highly effective anti-spam +and anti-pagerank-leech measure, but comes at a hefty price: nolinks or +images outside of your domain will be allowed. Non-linkified URIs will +still be preserved. If you want to be able to link to subdomains or use +absolute URIs, specify %URI.Host for your website. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt index d74bc1e3d..abcc1efd6 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt @@ -1,13 +1,13 @@ -URI.DisableExternalResources -TYPE: bool -VERSION: 1.3.0 -DEFAULT: false ---DESCRIPTION-- -Disables the embedding of external resources, preventing users from -embedding things like images from other hosts. This prevents access -tracking (good for email viewers), bandwidth leeching, cross-site request -forging, goatse.cx posting, and other nasties, but also results in a loss -of end-user functionality (they can't directly post a pic they posted from -Flickr anymore). Use it if you don't have a robust user-content moderation -team. ---# vim: et sw=4 sts=4 +URI.DisableExternalResources +TYPE: bool +VERSION: 1.3.0 +DEFAULT: false +--DESCRIPTION-- +Disables the embedding of external resources, preventing users from +embedding things like images from other hosts. This prevents access +tracking (good for email viewers), bandwidth leeching, cross-site request +forging, goatse.cx posting, and other nasties, but also results in a loss +of end-user functionality (they can't directly post a pic they posted from +Flickr anymore). Use it if you don't have a robust user-content moderation +team. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt index 6c106144a..f891de499 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt @@ -1,15 +1,15 @@ -URI.DisableResources -TYPE: bool -VERSION: 4.2.0 -DEFAULT: false ---DESCRIPTION-- -

          - Disables embedding resources, essentially meaning no pictures. You can - still link to them though. See %URI.DisableExternalResources for why - this might be a good idea. -

          -

          - Note: While this directive has been available since 1.3.0, - it didn't actually start doing anything until 4.2.0. -

          ---# vim: et sw=4 sts=4 +URI.DisableResources +TYPE: bool +VERSION: 4.2.0 +DEFAULT: false +--DESCRIPTION-- +

          + Disables embedding resources, essentially meaning no pictures. You can + still link to them though. See %URI.DisableExternalResources for why + this might be a good idea. +

          +

          + Note: While this directive has been available since 1.3.0, + it didn't actually start doing anything until 4.2.0. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Host.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Host.txt index ba0e6bce1..ee83b121d 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Host.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Host.txt @@ -1,19 +1,19 @@ -URI.Host -TYPE: string/null -VERSION: 1.2.0 -DEFAULT: NULL ---DESCRIPTION-- - -

          - Defines the domain name of the server, so we can determine whether or - an absolute URI is from your website or not. Not strictly necessary, - as users should be using relative URIs to reference resources on your - website. It will, however, let you use absolute URIs to link to - subdomains of the domain you post here: i.e. example.com will allow - sub.example.com. However, higher up domains will still be excluded: - if you set %URI.Host to sub.example.com, example.com will be blocked. - Note: This directive overrides %URI.Base because - a given page may be on a sub-domain, but you wish HTML Purifier to be - more relaxed and allow some of the parent domains too. -

          ---# vim: et sw=4 sts=4 +URI.Host +TYPE: string/null +VERSION: 1.2.0 +DEFAULT: NULL +--DESCRIPTION-- + +

          + Defines the domain name of the server, so we can determine whether or + an absolute URI is from your website or not. Not strictly necessary, + as users should be using relative URIs to reference resources on your + website. It will, however, let you use absolute URIs to link to + subdomains of the domain you post here: i.e. example.com will allow + sub.example.com. However, higher up domains will still be excluded: + if you set %URI.Host to sub.example.com, example.com will be blocked. + Note: This directive overrides %URI.Base because + a given page may be on a sub-domain, but you wish HTML Purifier to be + more relaxed and allow some of the parent domains too. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt index 825fef276..0b6df7625 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt @@ -1,9 +1,9 @@ -URI.HostBlacklist -TYPE: list -VERSION: 1.3.0 -DEFAULT: array() ---DESCRIPTION-- -List of strings that are forbidden in the host of any URI. Use it to kill -domain names of spam, etc. Note that it will catch anything in the domain, -so moo.com will catch moo.com.example.com. ---# vim: et sw=4 sts=4 +URI.HostBlacklist +TYPE: list +VERSION: 1.3.0 +DEFAULT: array() +--DESCRIPTION-- +List of strings that are forbidden in the host of any URI. Use it to kill +domain names of spam, etc. Note that it will catch anything in the domain, +so moo.com will catch moo.com.example.com. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt index eb58c7f1a..4214900a5 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt @@ -1,13 +1,13 @@ -URI.MakeAbsolute -TYPE: bool -VERSION: 2.1.0 -DEFAULT: false ---DESCRIPTION-- - -

          - Converts all URIs into absolute forms. This is useful when the HTML - being filtered assumes a specific base path, but will actually be - viewed in a different context (and setting an alternate base URI is - not possible). %URI.Base must be set for this directive to work. -

          ---# vim: et sw=4 sts=4 +URI.MakeAbsolute +TYPE: bool +VERSION: 2.1.0 +DEFAULT: false +--DESCRIPTION-- + +

          + Converts all URIs into absolute forms. This is useful when the HTML + being filtered assumes a specific base path, but will actually be + viewed in a different context (and setting an alternate base URI is + not possible). %URI.Base must be set for this directive to work. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt index bedd610d6..58c81dcc4 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt @@ -1,83 +1,83 @@ -URI.Munge -TYPE: string/null -VERSION: 1.3.0 -DEFAULT: NULL ---DESCRIPTION-- - -

          - Munges all browsable (usually http, https and ftp) - absolute URIs into another URI, usually a URI redirection service. - This directive accepts a URI, formatted with a %s where - the url-encoded original URI should be inserted (sample: - http://www.google.com/url?q=%s). -

          -

          - Uses for this directive: -

          -
            -
          • - Prevent PageRank leaks, while being fairly transparent - to users (you may also want to add some client side JavaScript to - override the text in the statusbar). Notice: - Many security experts believe that this form of protection does not deter spam-bots. -
          • -
          • - Redirect users to a splash page telling them they are leaving your - website. While this is poor usability practice, it is often mandated - in corporate environments. -
          • -
          -

          - Prior to HTML Purifier 3.1.1, this directive also enabled the munging - of browsable external resources, which could break things if your redirection - script was a splash page or used meta tags. To revert to - previous behavior, please use %URI.MungeResources. -

          -

          - You may want to also use %URI.MungeSecretKey along with this directive - in order to enforce what URIs your redirector script allows. Open - redirector scripts can be a security risk and negatively affect the - reputation of your domain name. -

          -

          - Starting with HTML Purifier 3.1.1, there is also these substitutions: -

          - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          KeyDescriptionExample <a href="">
          %r1 - The URI embeds a resource
          (blank) - The URI is merely a link
          %nThe name of the tag this URI came froma
          %mThe name of the attribute this URI came fromhref
          %pThe name of the CSS property this URI came from, or blank if irrelevant
          -

          - Admittedly, these letters are somewhat arbitrary; the only stipulation - was that they couldn't be a through f. r is for resource (I would have preferred - e, but you take what you can get), n is for name, m - was picked because it came after n (and I couldn't use a), p is for - property. -

          ---# vim: et sw=4 sts=4 +URI.Munge +TYPE: string/null +VERSION: 1.3.0 +DEFAULT: NULL +--DESCRIPTION-- + +

          + Munges all browsable (usually http, https and ftp) + absolute URIs into another URI, usually a URI redirection service. + This directive accepts a URI, formatted with a %s where + the url-encoded original URI should be inserted (sample: + http://www.google.com/url?q=%s). +

          +

          + Uses for this directive: +

          +
            +
          • + Prevent PageRank leaks, while being fairly transparent + to users (you may also want to add some client side JavaScript to + override the text in the statusbar). Notice: + Many security experts believe that this form of protection does not deter spam-bots. +
          • +
          • + Redirect users to a splash page telling them they are leaving your + website. While this is poor usability practice, it is often mandated + in corporate environments. +
          • +
          +

          + Prior to HTML Purifier 3.1.1, this directive also enabled the munging + of browsable external resources, which could break things if your redirection + script was a splash page or used meta tags. To revert to + previous behavior, please use %URI.MungeResources. +

          +

          + You may want to also use %URI.MungeSecretKey along with this directive + in order to enforce what URIs your redirector script allows. Open + redirector scripts can be a security risk and negatively affect the + reputation of your domain name. +

          +

          + Starting with HTML Purifier 3.1.1, there is also these substitutions: +

          + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
          KeyDescriptionExample <a href="">
          %r1 - The URI embeds a resource
          (blank) - The URI is merely a link
          %nThe name of the tag this URI came froma
          %mThe name of the attribute this URI came fromhref
          %pThe name of the CSS property this URI came from, or blank if irrelevant
          +

          + Admittedly, these letters are somewhat arbitrary; the only stipulation + was that they couldn't be a through f. r is for resource (I would have preferred + e, but you take what you can get), n is for name, m + was picked because it came after n (and I couldn't use a), p is for + property. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt index ed4b5b0d0..6fce0fdc3 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt @@ -1,17 +1,17 @@ -URI.MungeResources -TYPE: bool -VERSION: 3.1.1 -DEFAULT: false ---DESCRIPTION-- -

          - If true, any URI munging directives like %URI.Munge - will also apply to embedded resources, such as <img src="">. - Be careful enabling this directive if you have a redirector script - that does not use the Location HTTP header; all of your images - and other embedded resources will break. -

          -

          - Warning: It is strongly advised you use this in conjunction - %URI.MungeSecretKey to mitigate the security risk of an open redirector. -

          ---# vim: et sw=4 sts=4 +URI.MungeResources +TYPE: bool +VERSION: 3.1.1 +DEFAULT: false +--DESCRIPTION-- +

          + If true, any URI munging directives like %URI.Munge + will also apply to embedded resources, such as <img src="">. + Be careful enabling this directive if you have a redirector script + that does not use the Location HTTP header; all of your images + and other embedded resources will break. +

          +

          + Warning: It is strongly advised you use this in conjunction + %URI.MungeSecretKey to mitigate the security risk of an open redirector. +

          +--# 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 123b6e26b..1e17c1d46 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt @@ -1,30 +1,30 @@ -URI.MungeSecretKey -TYPE: string/null -VERSION: 3.1.1 -DEFAULT: NULL ---DESCRIPTION-- -

          - This directive enables secure checksum generation along with %URI.Munge. - It should be set to a secure key that is not shared with anyone else. - The checksum can be placed in the URI using %t. Use of this checksum - affords an additional level of protection by allowing a redirector - to check if a URI has passed through HTML Purifier with this line: -

          - -
          $checksum === hash_hmac("sha256", $url, $secret_key)
          - -

          - If the output is TRUE, the redirector script should accept the URI. -

          - -

          - Please note that it would still be possible for an attacker to procure - secure hashes en-mass by abusing your website's Preview feature or the - like, but this service affords an additional level of protection - that should be combined with website blacklisting. -

          - -

          - Remember this has no effect if %URI.Munge is not on. -

          ---# vim: et sw=4 sts=4 +URI.MungeSecretKey +TYPE: string/null +VERSION: 3.1.1 +DEFAULT: NULL +--DESCRIPTION-- +

          + This directive enables secure checksum generation along with %URI.Munge. + It should be set to a secure key that is not shared with anyone else. + The checksum can be placed in the URI using %t. Use of this checksum + affords an additional level of protection by allowing a redirector + to check if a URI has passed through HTML Purifier with this line: +

          + +
          $checksum === hash_hmac("sha256", $url, $secret_key)
          + +

          + If the output is TRUE, the redirector script should accept the URI. +

          + +

          + Please note that it would still be possible for an attacker to procure + secure hashes en-mass by abusing your website's Preview feature or the + like, but this service affords an additional level of protection + that should be combined with website blacklisting. +

          + +

          + Remember this has no effect if %URI.Munge is not on. +

          +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt index 8b387dea3..23331a4e7 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt @@ -1,9 +1,9 @@ -URI.OverrideAllowedSchemes -TYPE: bool -DEFAULT: true ---DESCRIPTION-- -If this is set to true (which it is by default), you can override -%URI.AllowedSchemes by simply registering a HTMLPurifier_URIScheme to the -registry. If false, you will also have to update that directive in order -to add more schemes. ---# vim: et sw=4 sts=4 +URI.OverrideAllowedSchemes +TYPE: bool +DEFAULT: true +--DESCRIPTION-- +If this is set to true (which it is by default), you can override +%URI.AllowedSchemes by simply registering a HTMLPurifier_URIScheme to the +registry. If false, you will also have to update that directive in order +to add more schemes. +--# vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/info.ini b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/info.ini index 58e0ce4a1..5de4505e1 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/info.ini +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/info.ini @@ -1,3 +1,3 @@ -name = "HTML Purifier" - -; vim: et sw=4 sts=4 +name = "HTML Purifier" + +; vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Filter/ExtractStyleBlocks.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Filter/ExtractStyleBlocks.php index aeb25df7e..df937ace7 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Filter/ExtractStyleBlocks.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Filter/ExtractStyleBlocks.php @@ -1,289 +1,289 @@ - blocks from input HTML, cleans them up - * using CSSTidy, and then places them in $purifier->context->get('StyleBlocks') - * so they can be used elsewhere in the document. - * - * @note - * See tests/HTMLPurifier/Filter/ExtractStyleBlocksTest.php for - * sample usage. - * - * @note - * This filter can also be used on stylesheets not included in the - * document--something purists would probably prefer. Just directly - * call HTMLPurifier_Filter_ExtractStyleBlocks->cleanCSS() - */ -class HTMLPurifier_Filter_ExtractStyleBlocks extends HTMLPurifier_Filter -{ - - public $name = 'ExtractStyleBlocks'; - private $_styleMatches = array(); - private $_tidy; - - private $_id_attrdef; - private $_class_attrdef; - private $_enum_attrdef; - - public function __construct() { - $this->_tidy = new csstidy(); - $this->_tidy->set_cfg('lowercase_s', false); - $this->_id_attrdef = new HTMLPurifier_AttrDef_HTML_ID(true); - $this->_class_attrdef = new HTMLPurifier_AttrDef_CSS_Ident(); - $this->_enum_attrdef = new HTMLPurifier_AttrDef_Enum(array('first-child', 'link', 'visited', 'active', 'hover', 'focus')); - } - - /** - * Save the contents of CSS blocks to style matches - * @param $matches preg_replace style $matches array - */ - protected function styleCallback($matches) { - $this->_styleMatches[] = $matches[1]; - } - - /** - * Removes inline #isU', array($this, 'styleCallback'), $html); - $style_blocks = $this->_styleMatches; - $this->_styleMatches = array(); // reset - $context->register('StyleBlocks', $style_blocks); // $context must not be reused - if ($this->_tidy) { - foreach ($style_blocks as &$style) { - $style = $this->cleanCSS($style, $config, $context); - } - } - return $html; - } - - /** - * Takes CSS (the stuff found in in a font-family prop). - if ($config->get('Filter.ExtractStyleBlocks.Escaping')) { - $css = str_replace( - array('<', '>', '&'), - array('\3C ', '\3E ', '\26 '), - $css - ); - } - return $css; - } - -} - -// vim: et sw=4 sts=4 + blocks from input HTML, cleans them up + * using CSSTidy, and then places them in $purifier->context->get('StyleBlocks') + * so they can be used elsewhere in the document. + * + * @note + * See tests/HTMLPurifier/Filter/ExtractStyleBlocksTest.php for + * sample usage. + * + * @note + * This filter can also be used on stylesheets not included in the + * document--something purists would probably prefer. Just directly + * call HTMLPurifier_Filter_ExtractStyleBlocks->cleanCSS() + */ +class HTMLPurifier_Filter_ExtractStyleBlocks extends HTMLPurifier_Filter +{ + + public $name = 'ExtractStyleBlocks'; + private $_styleMatches = array(); + private $_tidy; + + private $_id_attrdef; + private $_class_attrdef; + private $_enum_attrdef; + + public function __construct() { + $this->_tidy = new csstidy(); + $this->_tidy->set_cfg('lowercase_s', false); + $this->_id_attrdef = new HTMLPurifier_AttrDef_HTML_ID(true); + $this->_class_attrdef = new HTMLPurifier_AttrDef_CSS_Ident(); + $this->_enum_attrdef = new HTMLPurifier_AttrDef_Enum(array('first-child', 'link', 'visited', 'active', 'hover', 'focus')); + } + + /** + * Save the contents of CSS blocks to style matches + * @param $matches preg_replace style $matches array + */ + protected function styleCallback($matches) { + $this->_styleMatches[] = $matches[1]; + } + + /** + * Removes inline #isU', array($this, 'styleCallback'), $html); + $style_blocks = $this->_styleMatches; + $this->_styleMatches = array(); // reset + $context->register('StyleBlocks', $style_blocks); // $context must not be reused + if ($this->_tidy) { + foreach ($style_blocks as &$style) { + $style = $this->cleanCSS($style, $config, $context); + } + } + return $html; + } + + /** + * Takes CSS (the stuff found in in a font-family prop). + if ($config->get('Filter.ExtractStyleBlocks.Escaping')) { + $css = str_replace( + array('<', '>', '&'), + array('\3C ', '\3E ', '\26 '), + $css + ); + } + return $css; + } + +} + +// vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Filter/YouTube.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Filter/YouTube.php index 46338876a..23df221ea 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Filter/YouTube.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Filter/YouTube.php @@ -1,39 +1,39 @@ -]+>.+?'. - 'http://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+).+?#s'; - $pre_replace = '\1'; - return preg_replace($pre_regex, $pre_replace, $html); - } - - public function postFilter($html, $config, $context) { - $post_regex = '#((?:v|cp)/[A-Za-z0-9\-_=]+)#'; - return preg_replace_callback($post_regex, array($this, 'postFilterCallback'), $html); - } - - protected function armorUrl($url) { - return str_replace('--', '--', $url); - } - - protected function postFilterCallback($matches) { - $url = $this->armorUrl($matches[1]); - return ''. - ''. - ''. - ''; - - } -} - -// vim: et sw=4 sts=4 +]+>.+?'. + 'http://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+).+?#s'; + $pre_replace = '\1'; + return preg_replace($pre_regex, $pre_replace, $html); + } + + public function postFilter($html, $config, $context) { + $post_regex = '#((?:v|cp)/[A-Za-z0-9\-_=]+)#'; + return preg_replace_callback($post_regex, array($this, 'postFilterCallback'), $html); + } + + protected function armorUrl($url) { + return str_replace('--', '--', $url); + } + + protected function postFilterCallback($matches) { + $url = $this->armorUrl($matches[1]); + return ''. + ''. + ''. + ''; + + } +} + +// vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Language/classes/en-x-test.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Language/classes/en-x-test.php index d2f9baaaa..d52fcb7ac 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Language/classes/en-x-test.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Language/classes/en-x-test.php @@ -1,12 +1,12 @@ - 'HTML Purifier X' -); - -// vim: et sw=4 sts=4 + 'HTML Purifier X' +); + +// vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Language/messages/en-x-testmini.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Language/messages/en-x-testmini.php index ed8560fd5..806c83fbf 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Language/messages/en-x-testmini.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Language/messages/en-x-testmini.php @@ -1,12 +1,12 @@ - 'HTML Purifier XNone' -); - -// vim: et sw=4 sts=4 + 'HTML Purifier XNone' +); + +// vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Language/messages/en.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Language/messages/en.php index 1fa30bdfe..c7f197e1e 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Language/messages/en.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Language/messages/en.php @@ -1,55 +1,55 @@ - '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', -); - -$errorNames = array( - E_ERROR => 'Error', - E_WARNING => 'Warning', - E_NOTICE => 'Notice' -); - -// vim: et sw=4 sts=4 + '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', +); + +$errorNames = array( + E_ERROR => 'Error', + E_WARNING => 'Warning', + E_NOTICE => 'Notice' +); + +// vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Lexer/PH5P.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Lexer/PH5P.php index a43c56f9e..faf00b829 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Lexer/PH5P.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Lexer/PH5P.php @@ -1,3904 +1,3904 @@ -normalize($html, $config, $context); - $new_html = $this->wrapHTML($new_html, $config, $context); - try { - $parser = new HTML5($new_html); - $doc = $parser->save(); - } catch (DOMException $e) { - // Uh oh, it failed. Punt to DirectLex. - $lexer = new HTMLPurifier_Lexer_DirectLex(); - $context->register('PH5PError', $e); // save the error, so we can detect it - return $lexer->tokenizeHTML($html, $config, $context); // use original HTML - } - $tokens = array(); - $this->tokenizeDOM( - $doc->getElementsByTagName('html')->item(0)-> // - getElementsByTagName('body')->item(0)-> // - getElementsByTagName('div')->item(0) //
          - , $tokens); - return $tokens; - } - -} - -/* - -Copyright 2007 Jeroen van der Meer - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -class HTML5 { - private $data; - private $char; - private $EOF; - private $state; - private $tree; - private $token; - private $content_model; - private $escape = false; - private $entities = array('AElig;','AElig','AMP;','AMP','Aacute;','Aacute', - 'Acirc;','Acirc','Agrave;','Agrave','Alpha;','Aring;','Aring','Atilde;', - 'Atilde','Auml;','Auml','Beta;','COPY;','COPY','Ccedil;','Ccedil','Chi;', - 'Dagger;','Delta;','ETH;','ETH','Eacute;','Eacute','Ecirc;','Ecirc','Egrave;', - 'Egrave','Epsilon;','Eta;','Euml;','Euml','GT;','GT','Gamma;','Iacute;', - 'Iacute','Icirc;','Icirc','Igrave;','Igrave','Iota;','Iuml;','Iuml','Kappa;', - 'LT;','LT','Lambda;','Mu;','Ntilde;','Ntilde','Nu;','OElig;','Oacute;', - 'Oacute','Ocirc;','Ocirc','Ograve;','Ograve','Omega;','Omicron;','Oslash;', - 'Oslash','Otilde;','Otilde','Ouml;','Ouml','Phi;','Pi;','Prime;','Psi;', - 'QUOT;','QUOT','REG;','REG','Rho;','Scaron;','Sigma;','THORN;','THORN', - 'TRADE;','Tau;','Theta;','Uacute;','Uacute','Ucirc;','Ucirc','Ugrave;', - 'Ugrave','Upsilon;','Uuml;','Uuml','Xi;','Yacute;','Yacute','Yuml;','Zeta;', - 'aacute;','aacute','acirc;','acirc','acute;','acute','aelig;','aelig', - 'agrave;','agrave','alefsym;','alpha;','amp;','amp','and;','ang;','apos;', - 'aring;','aring','asymp;','atilde;','atilde','auml;','auml','bdquo;','beta;', - 'brvbar;','brvbar','bull;','cap;','ccedil;','ccedil','cedil;','cedil', - 'cent;','cent','chi;','circ;','clubs;','cong;','copy;','copy','crarr;', - 'cup;','curren;','curren','dArr;','dagger;','darr;','deg;','deg','delta;', - 'diams;','divide;','divide','eacute;','eacute','ecirc;','ecirc','egrave;', - 'egrave','empty;','emsp;','ensp;','epsilon;','equiv;','eta;','eth;','eth', - 'euml;','euml','euro;','exist;','fnof;','forall;','frac12;','frac12', - 'frac14;','frac14','frac34;','frac34','frasl;','gamma;','ge;','gt;','gt', - 'hArr;','harr;','hearts;','hellip;','iacute;','iacute','icirc;','icirc', - 'iexcl;','iexcl','igrave;','igrave','image;','infin;','int;','iota;', - 'iquest;','iquest','isin;','iuml;','iuml','kappa;','lArr;','lambda;','lang;', - 'laquo;','laquo','larr;','lceil;','ldquo;','le;','lfloor;','lowast;','loz;', - 'lrm;','lsaquo;','lsquo;','lt;','lt','macr;','macr','mdash;','micro;','micro', - 'middot;','middot','minus;','mu;','nabla;','nbsp;','nbsp','ndash;','ne;', - 'ni;','not;','not','notin;','nsub;','ntilde;','ntilde','nu;','oacute;', - 'oacute','ocirc;','ocirc','oelig;','ograve;','ograve','oline;','omega;', - 'omicron;','oplus;','or;','ordf;','ordf','ordm;','ordm','oslash;','oslash', - 'otilde;','otilde','otimes;','ouml;','ouml','para;','para','part;','permil;', - 'perp;','phi;','pi;','piv;','plusmn;','plusmn','pound;','pound','prime;', - 'prod;','prop;','psi;','quot;','quot','rArr;','radic;','rang;','raquo;', - 'raquo','rarr;','rceil;','rdquo;','real;','reg;','reg','rfloor;','rho;', - 'rlm;','rsaquo;','rsquo;','sbquo;','scaron;','sdot;','sect;','sect','shy;', - 'shy','sigma;','sigmaf;','sim;','spades;','sub;','sube;','sum;','sup1;', - 'sup1','sup2;','sup2','sup3;','sup3','sup;','supe;','szlig;','szlig','tau;', - 'there4;','theta;','thetasym;','thinsp;','thorn;','thorn','tilde;','times;', - 'times','trade;','uArr;','uacute;','uacute','uarr;','ucirc;','ucirc', - 'ugrave;','ugrave','uml;','uml','upsih;','upsilon;','uuml;','uuml','weierp;', - 'xi;','yacute;','yacute','yen;','yen','yuml;','yuml','zeta;','zwj;','zwnj;'); - - const PCDATA = 0; - const RCDATA = 1; - const CDATA = 2; - const PLAINTEXT = 3; - - const DOCTYPE = 0; - const STARTTAG = 1; - const ENDTAG = 2; - const COMMENT = 3; - const CHARACTR = 4; - const EOF = 5; - - public function __construct($data) { - - $this->data = $data; - $this->char = -1; - $this->EOF = strlen($data); - $this->tree = new HTML5TreeConstructer; - $this->content_model = self::PCDATA; - - $this->state = 'data'; - - while($this->state !== null) { - $this->{$this->state.'State'}(); - } - } - - public function save() { - return $this->tree->save(); - } - - private function char() { - return ($this->char < $this->EOF) - ? $this->data[$this->char] - : false; - } - - private function character($s, $l = 0) { - if($s + $l < $this->EOF) { - if($l === 0) { - return $this->data[$s]; - } else { - return substr($this->data, $s, $l); - } - } - } - - private function characters($char_class, $start) { - return preg_replace('#^(['.$char_class.']+).*#s', '\\1', substr($this->data, $start)); - } - - private function dataState() { - // Consume the next input character - $this->char++; - $char = $this->char(); - - if($char === '&' && ($this->content_model === self::PCDATA || $this->content_model === self::RCDATA)) { - /* U+0026 AMPERSAND (&) - When the content model flag is set to one of the PCDATA or RCDATA - states: switch to the entity data state. Otherwise: treat it as per - the "anything else" entry below. */ - $this->state = 'entityData'; - - } elseif($char === '-') { - /* If the content model flag is set to either the RCDATA state or - the CDATA state, and the escape flag is false, and there are at - least three characters before this one in the input stream, and the - last four characters in the input stream, including this one, are - U+003C LESS-THAN SIGN, U+0021 EXCLAMATION MARK, U+002D HYPHEN-MINUS, - and U+002D HYPHEN-MINUS (""), - set the escape flag to false. */ - if(($this->content_model === self::RCDATA || - $this->content_model === self::CDATA) && $this->escape === true && - $this->character($this->char, 3) === '-->') { - $this->escape = false; - } - - /* In any case, emit the input character as a character token. - Stay in the data state. */ - $this->emitToken(array( - 'type' => self::CHARACTR, - 'data' => $char - )); - - } elseif($this->char === $this->EOF) { - /* EOF - Emit an end-of-file token. */ - $this->EOF(); - - } elseif($this->content_model === self::PLAINTEXT) { - /* When the content model flag is set to the PLAINTEXT state - THIS DIFFERS GREATLY FROM THE SPEC: Get the remaining characters of - the text and emit it as a character token. */ - $this->emitToken(array( - 'type' => self::CHARACTR, - 'data' => substr($this->data, $this->char) - )); - - $this->EOF(); - - } else { - /* Anything else - THIS DIFFERS GREATLY FROM THE SPEC: Get as many character that - otherwise would also be treated as a character token and emit it - as a single character token. Stay in the data state. */ - $len = strcspn($this->data, '<&', $this->char); - $char = substr($this->data, $this->char, $len); - $this->char += $len - 1; - - $this->emitToken(array( - 'type' => self::CHARACTR, - 'data' => $char - )); - - $this->state = 'data'; - } - } - - private function entityDataState() { - // Attempt to consume an entity. - $entity = $this->entity(); - - // If nothing is returned, emit a U+0026 AMPERSAND character token. - // Otherwise, emit the character token that was returned. - $char = (!$entity) ? '&' : $entity; - $this->emitToken(array( - 'type' => self::CHARACTR, - 'data' => $char - )); - - // Finally, switch to the data state. - $this->state = 'data'; - } - - private function tagOpenState() { - switch($this->content_model) { - case self::RCDATA: - case self::CDATA: - /* If the next input character is a U+002F SOLIDUS (/) character, - consume it and switch to the close tag open state. If the next - input character is not a U+002F SOLIDUS (/) character, emit a - U+003C LESS-THAN SIGN character token and switch to the data - state to process the next input character. */ - if($this->character($this->char + 1) === '/') { - $this->char++; - $this->state = 'closeTagOpen'; - - } else { - $this->emitToken(array( - 'type' => self::CHARACTR, - 'data' => '<' - )); - - $this->state = 'data'; - } - break; - - case self::PCDATA: - // If the content model flag is set to the PCDATA state - // Consume the next input character: - $this->char++; - $char = $this->char(); - - if($char === '!') { - /* U+0021 EXCLAMATION MARK (!) - Switch to the markup declaration open state. */ - $this->state = 'markupDeclarationOpen'; - - } elseif($char === '/') { - /* U+002F SOLIDUS (/) - Switch to the close tag open state. */ - $this->state = 'closeTagOpen'; - - } elseif(preg_match('/^[A-Za-z]$/', $char)) { - /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z - Create a new start tag token, set its tag name to the lowercase - version of the input character (add 0x0020 to the character's code - point), then switch to the tag name state. (Don't emit the token - yet; further details will be filled in before it is emitted.) */ - $this->token = array( - 'name' => strtolower($char), - 'type' => self::STARTTAG, - 'attr' => array() - ); - - $this->state = 'tagName'; - - } elseif($char === '>') { - /* U+003E GREATER-THAN SIGN (>) - Parse error. Emit a U+003C LESS-THAN SIGN character token and a - U+003E GREATER-THAN SIGN character token. Switch to the data state. */ - $this->emitToken(array( - 'type' => self::CHARACTR, - 'data' => '<>' - )); - - $this->state = 'data'; - - } elseif($char === '?') { - /* U+003F QUESTION MARK (?) - Parse error. Switch to the bogus comment state. */ - $this->state = 'bogusComment'; - - } else { - /* Anything else - Parse error. Emit a U+003C LESS-THAN SIGN character token and - reconsume the current input character in the data state. */ - $this->emitToken(array( - 'type' => self::CHARACTR, - 'data' => '<' - )); - - $this->char--; - $this->state = 'data'; - } - break; - } - } - - private function closeTagOpenState() { - $next_node = strtolower($this->characters('A-Za-z', $this->char + 1)); - $the_same = count($this->tree->stack) > 0 && $next_node === end($this->tree->stack)->nodeName; - - if(($this->content_model === self::RCDATA || $this->content_model === self::CDATA) && - (!$the_same || ($the_same && (!preg_match('/[\t\n\x0b\x0c >\/]/', - $this->character($this->char + 1 + strlen($next_node))) || $this->EOF === $this->char)))) { - /* If the content model flag is set to the RCDATA or CDATA states then - examine the next few characters. If they do not match the tag name of - the last start tag token emitted (case insensitively), or if they do but - they are not immediately followed by one of the following characters: - * U+0009 CHARACTER TABULATION - * U+000A LINE FEED (LF) - * U+000B LINE TABULATION - * U+000C FORM FEED (FF) - * U+0020 SPACE - * U+003E GREATER-THAN SIGN (>) - * U+002F SOLIDUS (/) - * EOF - ...then there is a parse error. Emit a U+003C LESS-THAN SIGN character - token, a U+002F SOLIDUS character token, and switch to the data state - to process the next input character. */ - $this->emitToken(array( - 'type' => self::CHARACTR, - 'data' => 'state = 'data'; - - } else { - /* Otherwise, if the content model flag is set to the PCDATA state, - or if the next few characters do match that tag name, consume the - next input character: */ - $this->char++; - $char = $this->char(); - - if(preg_match('/^[A-Za-z]$/', $char)) { - /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z - Create a new end tag token, set its tag name to the lowercase version - of the input character (add 0x0020 to the character's code point), then - switch to the tag name state. (Don't emit the token yet; further details - will be filled in before it is emitted.) */ - $this->token = array( - 'name' => strtolower($char), - 'type' => self::ENDTAG - ); - - $this->state = 'tagName'; - - } elseif($char === '>') { - /* U+003E GREATER-THAN SIGN (>) - Parse error. Switch to the data state. */ - $this->state = 'data'; - - } elseif($this->char === $this->EOF) { - /* EOF - Parse error. Emit a U+003C LESS-THAN SIGN character token and a U+002F - SOLIDUS character token. Reconsume the EOF character in the data state. */ - $this->emitToken(array( - 'type' => self::CHARACTR, - 'data' => 'char--; - $this->state = 'data'; - - } else { - /* Parse error. Switch to the bogus comment state. */ - $this->state = 'bogusComment'; - } - } - } - - private function tagNameState() { - // Consume the next input character: - $this->char++; - $char = $this->character($this->char); - - if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { - /* U+0009 CHARACTER TABULATION - U+000A LINE FEED (LF) - U+000B LINE TABULATION - U+000C FORM FEED (FF) - U+0020 SPACE - Switch to the before attribute name state. */ - $this->state = 'beforeAttributeName'; - - } elseif($char === '>') { - /* U+003E GREATER-THAN SIGN (>) - Emit the current tag token. Switch to the data state. */ - $this->emitToken($this->token); - $this->state = 'data'; - - } elseif($this->char === $this->EOF) { - /* EOF - Parse error. Emit the current tag token. Reconsume the EOF - character in the data state. */ - $this->emitToken($this->token); - - $this->char--; - $this->state = 'data'; - - } elseif($char === '/') { - /* U+002F SOLIDUS (/) - Parse error unless this is a permitted slash. Switch to the before - attribute name state. */ - $this->state = 'beforeAttributeName'; - - } else { - /* Anything else - Append the current input character to the current tag token's tag name. - Stay in the tag name state. */ - $this->token['name'] .= strtolower($char); - $this->state = 'tagName'; - } - } - - private function beforeAttributeNameState() { - // Consume the next input character: - $this->char++; - $char = $this->character($this->char); - - if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { - /* U+0009 CHARACTER TABULATION - U+000A LINE FEED (LF) - U+000B LINE TABULATION - U+000C FORM FEED (FF) - U+0020 SPACE - Stay in the before attribute name state. */ - $this->state = 'beforeAttributeName'; - - } elseif($char === '>') { - /* U+003E GREATER-THAN SIGN (>) - Emit the current tag token. Switch to the data state. */ - $this->emitToken($this->token); - $this->state = 'data'; - - } elseif($char === '/') { - /* U+002F SOLIDUS (/) - Parse error unless this is a permitted slash. Stay in the before - attribute name state. */ - $this->state = 'beforeAttributeName'; - - } elseif($this->char === $this->EOF) { - /* EOF - Parse error. Emit the current tag token. Reconsume the EOF - character in the data state. */ - $this->emitToken($this->token); - - $this->char--; - $this->state = 'data'; - - } else { - /* Anything else - Start a new attribute in the current tag token. Set that attribute's - name to the current input character, and its value to the empty string. - Switch to the attribute name state. */ - $this->token['attr'][] = array( - 'name' => strtolower($char), - 'value' => null - ); - - $this->state = 'attributeName'; - } - } - - private function attributeNameState() { - // Consume the next input character: - $this->char++; - $char = $this->character($this->char); - - if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { - /* U+0009 CHARACTER TABULATION - U+000A LINE FEED (LF) - U+000B LINE TABULATION - U+000C FORM FEED (FF) - U+0020 SPACE - Stay in the before attribute name state. */ - $this->state = 'afterAttributeName'; - - } elseif($char === '=') { - /* U+003D EQUALS SIGN (=) - Switch to the before attribute value state. */ - $this->state = 'beforeAttributeValue'; - - } elseif($char === '>') { - /* U+003E GREATER-THAN SIGN (>) - Emit the current tag token. Switch to the data state. */ - $this->emitToken($this->token); - $this->state = 'data'; - - } elseif($char === '/' && $this->character($this->char + 1) !== '>') { - /* U+002F SOLIDUS (/) - Parse error unless this is a permitted slash. Switch to the before - attribute name state. */ - $this->state = 'beforeAttributeName'; - - } elseif($this->char === $this->EOF) { - /* EOF - Parse error. Emit the current tag token. Reconsume the EOF - character in the data state. */ - $this->emitToken($this->token); - - $this->char--; - $this->state = 'data'; - - } else { - /* Anything else - Append the current input character to the current attribute's name. - Stay in the attribute name state. */ - $last = count($this->token['attr']) - 1; - $this->token['attr'][$last]['name'] .= strtolower($char); - - $this->state = 'attributeName'; - } - } - - private function afterAttributeNameState() { - // Consume the next input character: - $this->char++; - $char = $this->character($this->char); - - if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { - /* U+0009 CHARACTER TABULATION - U+000A LINE FEED (LF) - U+000B LINE TABULATION - U+000C FORM FEED (FF) - U+0020 SPACE - Stay in the after attribute name state. */ - $this->state = 'afterAttributeName'; - - } elseif($char === '=') { - /* U+003D EQUALS SIGN (=) - Switch to the before attribute value state. */ - $this->state = 'beforeAttributeValue'; - - } elseif($char === '>') { - /* U+003E GREATER-THAN SIGN (>) - Emit the current tag token. Switch to the data state. */ - $this->emitToken($this->token); - $this->state = 'data'; - - } elseif($char === '/' && $this->character($this->char + 1) !== '>') { - /* U+002F SOLIDUS (/) - Parse error unless this is a permitted slash. Switch to the - before attribute name state. */ - $this->state = 'beforeAttributeName'; - - } elseif($this->char === $this->EOF) { - /* EOF - Parse error. Emit the current tag token. Reconsume the EOF - character in the data state. */ - $this->emitToken($this->token); - - $this->char--; - $this->state = 'data'; - - } else { - /* Anything else - Start a new attribute in the current tag token. Set that attribute's - name to the current input character, and its value to the empty string. - Switch to the attribute name state. */ - $this->token['attr'][] = array( - 'name' => strtolower($char), - 'value' => null - ); - - $this->state = 'attributeName'; - } - } - - private function beforeAttributeValueState() { - // Consume the next input character: - $this->char++; - $char = $this->character($this->char); - - if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { - /* U+0009 CHARACTER TABULATION - U+000A LINE FEED (LF) - U+000B LINE TABULATION - U+000C FORM FEED (FF) - U+0020 SPACE - Stay in the before attribute value state. */ - $this->state = 'beforeAttributeValue'; - - } elseif($char === '"') { - /* U+0022 QUOTATION MARK (") - Switch to the attribute value (double-quoted) state. */ - $this->state = 'attributeValueDoubleQuoted'; - - } elseif($char === '&') { - /* U+0026 AMPERSAND (&) - Switch to the attribute value (unquoted) state and reconsume - this input character. */ - $this->char--; - $this->state = 'attributeValueUnquoted'; - - } elseif($char === '\'') { - /* U+0027 APOSTROPHE (') - Switch to the attribute value (single-quoted) state. */ - $this->state = 'attributeValueSingleQuoted'; - - } elseif($char === '>') { - /* U+003E GREATER-THAN SIGN (>) - Emit the current tag token. Switch to the data state. */ - $this->emitToken($this->token); - $this->state = 'data'; - - } else { - /* Anything else - Append the current input character to the current attribute's value. - Switch to the attribute value (unquoted) state. */ - $last = count($this->token['attr']) - 1; - $this->token['attr'][$last]['value'] .= $char; - - $this->state = 'attributeValueUnquoted'; - } - } - - private function attributeValueDoubleQuotedState() { - // Consume the next input character: - $this->char++; - $char = $this->character($this->char); - - if($char === '"') { - /* U+0022 QUOTATION MARK (") - Switch to the before attribute name state. */ - $this->state = 'beforeAttributeName'; - - } elseif($char === '&') { - /* U+0026 AMPERSAND (&) - Switch to the entity in attribute value state. */ - $this->entityInAttributeValueState('double'); - - } elseif($this->char === $this->EOF) { - /* EOF - Parse error. Emit the current tag token. Reconsume the character - in the data state. */ - $this->emitToken($this->token); - - $this->char--; - $this->state = 'data'; - - } else { - /* Anything else - Append the current input character to the current attribute's value. - Stay in the attribute value (double-quoted) state. */ - $last = count($this->token['attr']) - 1; - $this->token['attr'][$last]['value'] .= $char; - - $this->state = 'attributeValueDoubleQuoted'; - } - } - - private function attributeValueSingleQuotedState() { - // Consume the next input character: - $this->char++; - $char = $this->character($this->char); - - if($char === '\'') { - /* U+0022 QUOTATION MARK (') - Switch to the before attribute name state. */ - $this->state = 'beforeAttributeName'; - - } elseif($char === '&') { - /* U+0026 AMPERSAND (&) - Switch to the entity in attribute value state. */ - $this->entityInAttributeValueState('single'); - - } elseif($this->char === $this->EOF) { - /* EOF - Parse error. Emit the current tag token. Reconsume the character - in the data state. */ - $this->emitToken($this->token); - - $this->char--; - $this->state = 'data'; - - } else { - /* Anything else - Append the current input character to the current attribute's value. - Stay in the attribute value (single-quoted) state. */ - $last = count($this->token['attr']) - 1; - $this->token['attr'][$last]['value'] .= $char; - - $this->state = 'attributeValueSingleQuoted'; - } - } - - private function attributeValueUnquotedState() { - // Consume the next input character: - $this->char++; - $char = $this->character($this->char); - - if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { - /* U+0009 CHARACTER TABULATION - U+000A LINE FEED (LF) - U+000B LINE TABULATION - U+000C FORM FEED (FF) - U+0020 SPACE - Switch to the before attribute name state. */ - $this->state = 'beforeAttributeName'; - - } elseif($char === '&') { - /* U+0026 AMPERSAND (&) - Switch to the entity in attribute value state. */ - $this->entityInAttributeValueState(); - - } elseif($char === '>') { - /* U+003E GREATER-THAN SIGN (>) - Emit the current tag token. Switch to the data state. */ - $this->emitToken($this->token); - $this->state = 'data'; - - } else { - /* Anything else - Append the current input character to the current attribute's value. - Stay in the attribute value (unquoted) state. */ - $last = count($this->token['attr']) - 1; - $this->token['attr'][$last]['value'] .= $char; - - $this->state = 'attributeValueUnquoted'; - } - } - - private function entityInAttributeValueState() { - // Attempt to consume an entity. - $entity = $this->entity(); - - // If nothing is returned, append a U+0026 AMPERSAND character to the - // current attribute's value. Otherwise, emit the character token that - // was returned. - $char = (!$entity) - ? '&' - : $entity; - - $last = count($this->token['attr']) - 1; - $this->token['attr'][$last]['value'] .= $char; - } - - private function bogusCommentState() { - /* Consume every character up to the first U+003E GREATER-THAN SIGN - character (>) or the end of the file (EOF), whichever comes first. Emit - a comment token whose data is the concatenation of all the characters - starting from and including the character that caused the state machine - to switch into the bogus comment state, up to and including the last - consumed character before the U+003E character, if any, or up to the - end of the file otherwise. (If the comment was started by the end of - the file (EOF), the token is empty.) */ - $data = $this->characters('^>', $this->char); - $this->emitToken(array( - 'data' => $data, - 'type' => self::COMMENT - )); - - $this->char += strlen($data); - - /* Switch to the data state. */ - $this->state = 'data'; - - /* If the end of the file was reached, reconsume the EOF character. */ - if($this->char === $this->EOF) { - $this->char = $this->EOF - 1; - } - } - - private function markupDeclarationOpenState() { - /* If the next two characters are both U+002D HYPHEN-MINUS (-) - characters, consume those two characters, create a comment token whose - data is the empty string, and switch to the comment state. */ - if($this->character($this->char + 1, 2) === '--') { - $this->char += 2; - $this->state = 'comment'; - $this->token = array( - 'data' => null, - 'type' => self::COMMENT - ); - - /* Otherwise if the next seven chacacters are a case-insensitive match - for the word "DOCTYPE", then consume those characters and switch to the - DOCTYPE state. */ - } elseif(strtolower($this->character($this->char + 1, 7)) === 'doctype') { - $this->char += 7; - $this->state = 'doctype'; - - /* Otherwise, is is a parse error. Switch to the bogus comment state. - The next character that is consumed, if any, is the first character - that will be in the comment. */ - } else { - $this->char++; - $this->state = 'bogusComment'; - } - } - - private function commentState() { - /* Consume the next input character: */ - $this->char++; - $char = $this->char(); - - /* U+002D HYPHEN-MINUS (-) */ - if($char === '-') { - /* Switch to the comment dash state */ - $this->state = 'commentDash'; - - /* EOF */ - } elseif($this->char === $this->EOF) { - /* Parse error. Emit the comment token. Reconsume the EOF character - in the data state. */ - $this->emitToken($this->token); - $this->char--; - $this->state = 'data'; - - /* Anything else */ - } else { - /* Append the input character to the comment token's data. Stay in - the comment state. */ - $this->token['data'] .= $char; - } - } - - private function commentDashState() { - /* Consume the next input character: */ - $this->char++; - $char = $this->char(); - - /* U+002D HYPHEN-MINUS (-) */ - if($char === '-') { - /* Switch to the comment end state */ - $this->state = 'commentEnd'; - - /* EOF */ - } elseif($this->char === $this->EOF) { - /* Parse error. Emit the comment token. Reconsume the EOF character - in the data state. */ - $this->emitToken($this->token); - $this->char--; - $this->state = 'data'; - - /* Anything else */ - } else { - /* Append a U+002D HYPHEN-MINUS (-) character and the input - character to the comment token's data. Switch to the comment state. */ - $this->token['data'] .= '-'.$char; - $this->state = 'comment'; - } - } - - private function commentEndState() { - /* Consume the next input character: */ - $this->char++; - $char = $this->char(); - - if($char === '>') { - $this->emitToken($this->token); - $this->state = 'data'; - - } elseif($char === '-') { - $this->token['data'] .= '-'; - - } elseif($this->char === $this->EOF) { - $this->emitToken($this->token); - $this->char--; - $this->state = 'data'; - - } else { - $this->token['data'] .= '--'.$char; - $this->state = 'comment'; - } - } - - private function doctypeState() { - /* Consume the next input character: */ - $this->char++; - $char = $this->char(); - - if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { - $this->state = 'beforeDoctypeName'; - - } else { - $this->char--; - $this->state = 'beforeDoctypeName'; - } - } - - private function beforeDoctypeNameState() { - /* Consume the next input character: */ - $this->char++; - $char = $this->char(); - - if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { - // Stay in the before DOCTYPE name state. - - } elseif(preg_match('/^[a-z]$/', $char)) { - $this->token = array( - 'name' => strtoupper($char), - 'type' => self::DOCTYPE, - 'error' => true - ); - - $this->state = 'doctypeName'; - - } elseif($char === '>') { - $this->emitToken(array( - 'name' => null, - 'type' => self::DOCTYPE, - 'error' => true - )); - - $this->state = 'data'; - - } elseif($this->char === $this->EOF) { - $this->emitToken(array( - 'name' => null, - 'type' => self::DOCTYPE, - 'error' => true - )); - - $this->char--; - $this->state = 'data'; - - } else { - $this->token = array( - 'name' => $char, - 'type' => self::DOCTYPE, - 'error' => true - ); - - $this->state = 'doctypeName'; - } - } - - private function doctypeNameState() { - /* Consume the next input character: */ - $this->char++; - $char = $this->char(); - - if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { - $this->state = 'AfterDoctypeName'; - - } elseif($char === '>') { - $this->emitToken($this->token); - $this->state = 'data'; - - } elseif(preg_match('/^[a-z]$/', $char)) { - $this->token['name'] .= strtoupper($char); - - } elseif($this->char === $this->EOF) { - $this->emitToken($this->token); - $this->char--; - $this->state = 'data'; - - } else { - $this->token['name'] .= $char; - } - - $this->token['error'] = ($this->token['name'] === 'HTML') - ? false - : true; - } - - private function afterDoctypeNameState() { - /* Consume the next input character: */ - $this->char++; - $char = $this->char(); - - if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { - // Stay in the DOCTYPE name state. - - } elseif($char === '>') { - $this->emitToken($this->token); - $this->state = 'data'; - - } elseif($this->char === $this->EOF) { - $this->emitToken($this->token); - $this->char--; - $this->state = 'data'; - - } else { - $this->token['error'] = true; - $this->state = 'bogusDoctype'; - } - } - - private function bogusDoctypeState() { - /* Consume the next input character: */ - $this->char++; - $char = $this->char(); - - if($char === '>') { - $this->emitToken($this->token); - $this->state = 'data'; - - } elseif($this->char === $this->EOF) { - $this->emitToken($this->token); - $this->char--; - $this->state = 'data'; - - } else { - // Stay in the bogus DOCTYPE state. - } - } - - private function entity() { - $start = $this->char; - - // This section defines how to consume an entity. This definition is - // used when parsing entities in text and in attributes. - - // The behaviour depends on the identity of the next character (the - // one immediately after the U+0026 AMPERSAND character): - - switch($this->character($this->char + 1)) { - // U+0023 NUMBER SIGN (#) - case '#': - - // The behaviour further depends on the character after the - // U+0023 NUMBER SIGN: - switch($this->character($this->char + 1)) { - // U+0078 LATIN SMALL LETTER X - // U+0058 LATIN CAPITAL LETTER X - case 'x': - case 'X': - // Follow the steps below, but using the range of - // characters U+0030 DIGIT ZERO through to U+0039 DIGIT - // NINE, U+0061 LATIN SMALL LETTER A through to U+0066 - // LATIN SMALL LETTER F, and U+0041 LATIN CAPITAL LETTER - // A, through to U+0046 LATIN CAPITAL LETTER F (in other - // words, 0-9, A-F, a-f). - $char = 1; - $char_class = '0-9A-Fa-f'; - break; - - // Anything else - default: - // Follow the steps below, but using the range of - // characters U+0030 DIGIT ZERO through to U+0039 DIGIT - // NINE (i.e. just 0-9). - $char = 0; - $char_class = '0-9'; - break; - } - - // Consume as many characters as match the range of characters - // given above. - $this->char++; - $e_name = $this->characters($char_class, $this->char + $char + 1); - $entity = $this->character($start, $this->char); - $cond = strlen($e_name) > 0; - - // The rest of the parsing happens bellow. - break; - - // Anything else - default: - // Consume the maximum number of characters possible, with the - // consumed characters case-sensitively matching one of the - // identifiers in the first column of the entities table. - $e_name = $this->characters('0-9A-Za-z;', $this->char + 1); - $len = strlen($e_name); - - for($c = 1; $c <= $len; $c++) { - $id = substr($e_name, 0, $c); - $this->char++; - - if(in_array($id, $this->entities)) { - if ($e_name[$c-1] !== ';') { - if ($c < $len && $e_name[$c] == ';') { - $this->char++; // consume extra semicolon - } - } - $entity = $id; - break; - } - } - - $cond = isset($entity); - // The rest of the parsing happens bellow. - break; - } - - if(!$cond) { - // If no match can be made, then this is a parse error. No - // characters are consumed, and nothing is returned. - $this->char = $start; - return false; - } - - // Return a character token for the character corresponding to the - // entity name (as given by the second column of the entities table). - return html_entity_decode('&'.$entity.';', ENT_QUOTES, 'UTF-8'); - } - - private function emitToken($token) { - $emit = $this->tree->emitToken($token); - - if(is_int($emit)) { - $this->content_model = $emit; - - } elseif($token['type'] === self::ENDTAG) { - $this->content_model = self::PCDATA; - } - } - - private function EOF() { - $this->state = null; - $this->tree->emitToken(array( - 'type' => self::EOF - )); - } -} - -class HTML5TreeConstructer { - public $stack = array(); - - private $phase; - private $mode; - private $dom; - private $foster_parent = null; - private $a_formatting = array(); - - private $head_pointer = null; - private $form_pointer = null; - - private $scoping = array('button','caption','html','marquee','object','table','td','th'); - private $formatting = array('a','b','big','em','font','i','nobr','s','small','strike','strong','tt','u'); - private $special = array('address','area','base','basefont','bgsound', - 'blockquote','body','br','center','col','colgroup','dd','dir','div','dl', - 'dt','embed','fieldset','form','frame','frameset','h1','h2','h3','h4','h5', - 'h6','head','hr','iframe','image','img','input','isindex','li','link', - 'listing','menu','meta','noembed','noframes','noscript','ol','optgroup', - 'option','p','param','plaintext','pre','script','select','spacer','style', - 'tbody','textarea','tfoot','thead','title','tr','ul','wbr'); - - // The different phases. - const INIT_PHASE = 0; - const ROOT_PHASE = 1; - const MAIN_PHASE = 2; - const END_PHASE = 3; - - // The different insertion modes for the main phase. - const BEFOR_HEAD = 0; - const IN_HEAD = 1; - const AFTER_HEAD = 2; - const IN_BODY = 3; - const IN_TABLE = 4; - const IN_CAPTION = 5; - const IN_CGROUP = 6; - const IN_TBODY = 7; - const IN_ROW = 8; - const IN_CELL = 9; - const IN_SELECT = 10; - const AFTER_BODY = 11; - const IN_FRAME = 12; - const AFTR_FRAME = 13; - - // The different types of elements. - const SPECIAL = 0; - const SCOPING = 1; - const FORMATTING = 2; - const PHRASING = 3; - - const MARKER = 0; - - public function __construct() { - $this->phase = self::INIT_PHASE; - $this->mode = self::BEFOR_HEAD; - $this->dom = new DOMDocument; - - $this->dom->encoding = 'UTF-8'; - $this->dom->preserveWhiteSpace = true; - $this->dom->substituteEntities = true; - $this->dom->strictErrorChecking = false; - } - - // Process tag tokens - public function emitToken($token) { - switch($this->phase) { - case self::INIT_PHASE: return $this->initPhase($token); break; - case self::ROOT_PHASE: return $this->rootElementPhase($token); break; - case self::MAIN_PHASE: return $this->mainPhase($token); break; - case self::END_PHASE : return $this->trailingEndPhase($token); break; - } - } - - private function initPhase($token) { - /* Initially, the tree construction stage must handle each token - emitted from the tokenisation stage as follows: */ - - /* A DOCTYPE token that is marked as being in error - A comment token - A start tag token - An end tag token - A character token that is not one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE - An end-of-file token */ - if((isset($token['error']) && $token['error']) || - $token['type'] === HTML5::COMMENT || - $token['type'] === HTML5::STARTTAG || - $token['type'] === HTML5::ENDTAG || - $token['type'] === HTML5::EOF || - ($token['type'] === HTML5::CHARACTR && isset($token['data']) && - !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']))) { - /* This specification does not define how to handle this case. In - particular, user agents may ignore the entirety of this specification - altogether for such documents, and instead invoke special parse modes - with a greater emphasis on backwards compatibility. */ - - $this->phase = self::ROOT_PHASE; - return $this->rootElementPhase($token); - - /* A DOCTYPE token marked as being correct */ - } elseif(isset($token['error']) && !$token['error']) { - /* Append a DocumentType node to the Document node, with the name - attribute set to the name given in the DOCTYPE token (which will be - "HTML"), and the other attributes specific to DocumentType objects - set to null, empty lists, or the empty string as appropriate. */ - $doctype = new DOMDocumentType(null, null, 'HTML'); - - /* Then, switch to the root element phase of the tree construction - stage. */ - $this->phase = self::ROOT_PHASE; - - /* A character token that is one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE */ - } elseif(isset($token['data']) && preg_match('/^[\t\n\x0b\x0c ]+$/', - $token['data'])) { - /* Append that character to the Document node. */ - $text = $this->dom->createTextNode($token['data']); - $this->dom->appendChild($text); - } - } - - private function rootElementPhase($token) { - /* After the initial phase, as each token is emitted from the tokenisation - stage, it must be processed as described in this section. */ - - /* A DOCTYPE token */ - if($token['type'] === HTML5::DOCTYPE) { - // Parse error. Ignore the token. - - /* A comment token */ - } elseif($token['type'] === HTML5::COMMENT) { - /* Append a Comment node to the Document object with the data - attribute set to the data given in the comment token. */ - $comment = $this->dom->createComment($token['data']); - $this->dom->appendChild($comment); - - /* A character token that is one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE */ - } elseif($token['type'] === HTML5::CHARACTR && - preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { - /* Append that character to the Document node. */ - $text = $this->dom->createTextNode($token['data']); - $this->dom->appendChild($text); - - /* A character token that is not one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED - (FF), or U+0020 SPACE - A start tag token - An end tag token - An end-of-file token */ - } elseif(($token['type'] === HTML5::CHARACTR && - !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || - $token['type'] === HTML5::STARTTAG || - $token['type'] === HTML5::ENDTAG || - $token['type'] === HTML5::EOF) { - /* Create an HTMLElement node with the tag name html, in the HTML - namespace. Append it to the Document object. Switch to the main - phase and reprocess the current token. */ - $html = $this->dom->createElement('html'); - $this->dom->appendChild($html); - $this->stack[] = $html; - - $this->phase = self::MAIN_PHASE; - return $this->mainPhase($token); - } - } - - private function mainPhase($token) { - /* Tokens in the main phase must be handled as follows: */ - - /* A DOCTYPE token */ - if($token['type'] === HTML5::DOCTYPE) { - // Parse error. Ignore the token. - - /* A start tag token with the tag name "html" */ - } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'html') { - /* If this start tag token was not the first start tag token, then - it is a parse error. */ - - /* For each attribute on the token, check to see if the attribute - is already present on the top element of the stack of open elements. - If it is not, add the attribute and its corresponding value to that - element. */ - foreach($token['attr'] as $attr) { - if(!$this->stack[0]->hasAttribute($attr['name'])) { - $this->stack[0]->setAttribute($attr['name'], $attr['value']); - } - } - - /* An end-of-file token */ - } elseif($token['type'] === HTML5::EOF) { - /* Generate implied end tags. */ - $this->generateImpliedEndTags(); - - /* Anything else. */ - } else { - /* Depends on the insertion mode: */ - switch($this->mode) { - case self::BEFOR_HEAD: return $this->beforeHead($token); break; - case self::IN_HEAD: return $this->inHead($token); break; - case self::AFTER_HEAD: return $this->afterHead($token); break; - case self::IN_BODY: return $this->inBody($token); break; - case self::IN_TABLE: return $this->inTable($token); break; - case self::IN_CAPTION: return $this->inCaption($token); break; - case self::IN_CGROUP: return $this->inColumnGroup($token); break; - case self::IN_TBODY: return $this->inTableBody($token); break; - case self::IN_ROW: return $this->inRow($token); break; - case self::IN_CELL: return $this->inCell($token); break; - case self::IN_SELECT: return $this->inSelect($token); break; - case self::AFTER_BODY: return $this->afterBody($token); break; - case self::IN_FRAME: return $this->inFrameset($token); break; - case self::AFTR_FRAME: return $this->afterFrameset($token); break; - case self::END_PHASE: return $this->trailingEndPhase($token); break; - } - } - } - - private function beforeHead($token) { - /* Handle the token as follows: */ - - /* A character token that is one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE */ - if($token['type'] === HTML5::CHARACTR && - preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { - /* Append the character to the current node. */ - $this->insertText($token['data']); - - /* A comment token */ - } elseif($token['type'] === HTML5::COMMENT) { - /* Append a Comment node to the current node with the data attribute - set to the data given in the comment token. */ - $this->insertComment($token['data']); - - /* A start tag token with the tag name "head" */ - } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') { - /* Create an element for the token, append the new element to the - current node and push it onto the stack of open elements. */ - $element = $this->insertElement($token); - - /* Set the head element pointer to this new element node. */ - $this->head_pointer = $element; - - /* Change the insertion mode to "in head". */ - $this->mode = self::IN_HEAD; - - /* A start tag token whose tag name is one of: "base", "link", "meta", - "script", "style", "title". Or an end tag with the tag name "html". - Or a character token that is not one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE. Or any other start tag token */ - } elseif($token['type'] === HTML5::STARTTAG || - ($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') || - ($token['type'] === HTML5::CHARACTR && !preg_match('/^[\t\n\x0b\x0c ]$/', - $token['data']))) { - /* Act as if a start tag token with the tag name "head" and no - attributes had been seen, then reprocess the current token. */ - $this->beforeHead(array( - 'name' => 'head', - 'type' => HTML5::STARTTAG, - 'attr' => array() - )); - - return $this->inHead($token); - - /* Any other end tag */ - } elseif($token['type'] === HTML5::ENDTAG) { - /* Parse error. Ignore the token. */ - } - } - - private function inHead($token) { - /* Handle the token as follows: */ - - /* A character token that is one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE. - - THIS DIFFERS FROM THE SPEC: If the current node is either a title, style - or script element, append the character to the current node regardless - of its content. */ - if(($token['type'] === HTML5::CHARACTR && - preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || ( - $token['type'] === HTML5::CHARACTR && in_array(end($this->stack)->nodeName, - array('title', 'style', 'script')))) { - /* Append the character to the current node. */ - $this->insertText($token['data']); - - /* A comment token */ - } elseif($token['type'] === HTML5::COMMENT) { - /* Append a Comment node to the current node with the data attribute - set to the data given in the comment token. */ - $this->insertComment($token['data']); - - } elseif($token['type'] === HTML5::ENDTAG && - in_array($token['name'], array('title', 'style', 'script'))) { - array_pop($this->stack); - return HTML5::PCDATA; - - /* A start tag with the tag name "title" */ - } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'title') { - /* Create an element for the token and append the new element to the - node pointed to by the head element pointer, or, if that is null - (innerHTML case), to the current node. */ - if($this->head_pointer !== null) { - $element = $this->insertElement($token, false); - $this->head_pointer->appendChild($element); - - } else { - $element = $this->insertElement($token); - } - - /* Switch the tokeniser's content model flag to the RCDATA state. */ - return HTML5::RCDATA; - - /* A start tag with the tag name "style" */ - } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'style') { - /* Create an element for the token and append the new element to the - node pointed to by the head element pointer, or, if that is null - (innerHTML case), to the current node. */ - if($this->head_pointer !== null) { - $element = $this->insertElement($token, false); - $this->head_pointer->appendChild($element); - - } else { - $this->insertElement($token); - } - - /* Switch the tokeniser's content model flag to the CDATA state. */ - return HTML5::CDATA; - - /* A start tag with the tag name "script" */ - } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'script') { - /* Create an element for the token. */ - $element = $this->insertElement($token, false); - $this->head_pointer->appendChild($element); - - /* Switch the tokeniser's content model flag to the CDATA state. */ - return HTML5::CDATA; - - /* A start tag with the tag name "base", "link", or "meta" */ - } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], - array('base', 'link', 'meta'))) { - /* Create an element for the token and append the new element to the - node pointed to by the head element pointer, or, if that is null - (innerHTML case), to the current node. */ - if($this->head_pointer !== null) { - $element = $this->insertElement($token, false); - $this->head_pointer->appendChild($element); - array_pop($this->stack); - - } else { - $this->insertElement($token); - } - - /* An end tag with the tag name "head" */ - } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'head') { - /* If the current node is a head element, pop the current node off - the stack of open elements. */ - if($this->head_pointer->isSameNode(end($this->stack))) { - array_pop($this->stack); - - /* Otherwise, this is a parse error. */ - } else { - // k - } - - /* Change the insertion mode to "after head". */ - $this->mode = self::AFTER_HEAD; - - /* A start tag with the tag name "head" or an end tag except "html". */ - } elseif(($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') || - ($token['type'] === HTML5::ENDTAG && $token['name'] !== 'html')) { - // Parse error. Ignore the token. - - /* Anything else */ - } else { - /* If the current node is a head element, act as if an end tag - token with the tag name "head" had been seen. */ - if($this->head_pointer->isSameNode(end($this->stack))) { - $this->inHead(array( - 'name' => 'head', - 'type' => HTML5::ENDTAG - )); - - /* Otherwise, change the insertion mode to "after head". */ - } else { - $this->mode = self::AFTER_HEAD; - } - - /* Then, reprocess the current token. */ - return $this->afterHead($token); - } - } - - private function afterHead($token) { - /* Handle the token as follows: */ - - /* A character token that is one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE */ - if($token['type'] === HTML5::CHARACTR && - preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { - /* Append the character to the current node. */ - $this->insertText($token['data']); - - /* A comment token */ - } elseif($token['type'] === HTML5::COMMENT) { - /* Append a Comment node to the current node with the data attribute - set to the data given in the comment token. */ - $this->insertComment($token['data']); - - /* A start tag token with the tag name "body" */ - } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'body') { - /* Insert a body element for the token. */ - $this->insertElement($token); - - /* Change the insertion mode to "in body". */ - $this->mode = self::IN_BODY; - - /* A start tag token with the tag name "frameset" */ - } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'frameset') { - /* Insert a frameset element for the token. */ - $this->insertElement($token); - - /* Change the insertion mode to "in frameset". */ - $this->mode = self::IN_FRAME; - - /* A start tag token whose tag name is one of: "base", "link", "meta", - "script", "style", "title" */ - } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], - array('base', 'link', 'meta', 'script', 'style', 'title'))) { - /* Parse error. Switch the insertion mode back to "in head" and - reprocess the token. */ - $this->mode = self::IN_HEAD; - return $this->inHead($token); - - /* Anything else */ - } else { - /* Act as if a start tag token with the tag name "body" and no - attributes had been seen, and then reprocess the current token. */ - $this->afterHead(array( - 'name' => 'body', - 'type' => HTML5::STARTTAG, - 'attr' => array() - )); - - return $this->inBody($token); - } - } - - private function inBody($token) { - /* Handle the token as follows: */ - - switch($token['type']) { - /* A character token */ - case HTML5::CHARACTR: - /* Reconstruct the active formatting elements, if any. */ - $this->reconstructActiveFormattingElements(); - - /* Append the token's character to the current node. */ - $this->insertText($token['data']); - break; - - /* A comment token */ - case HTML5::COMMENT: - /* Append a Comment node to the current node with the data - attribute set to the data given in the comment token. */ - $this->insertComment($token['data']); - break; - - case HTML5::STARTTAG: - switch($token['name']) { - /* A start tag token whose tag name is one of: "script", - "style" */ - case 'script': case 'style': - /* Process the token as if the insertion mode had been "in - head". */ - return $this->inHead($token); - break; - - /* A start tag token whose tag name is one of: "base", "link", - "meta", "title" */ - case 'base': case 'link': case 'meta': case 'title': - /* Parse error. Process the token as if the insertion mode - had been "in head". */ - return $this->inHead($token); - break; - - /* A start tag token with the tag name "body" */ - case 'body': - /* Parse error. If the second element on the stack of open - elements is not a body element, or, if the stack of open - elements has only one node on it, then ignore the token. - (innerHTML case) */ - if(count($this->stack) === 1 || $this->stack[1]->nodeName !== 'body') { - // Ignore - - /* Otherwise, for each attribute on the token, check to see - if the attribute is already present on the body element (the - second element) on the stack of open elements. If it is not, - add the attribute and its corresponding value to that - element. */ - } else { - foreach($token['attr'] as $attr) { - if(!$this->stack[1]->hasAttribute($attr['name'])) { - $this->stack[1]->setAttribute($attr['name'], $attr['value']); - } - } - } - break; - - /* A start tag whose tag name is one of: "address", - "blockquote", "center", "dir", "div", "dl", "fieldset", - "listing", "menu", "ol", "p", "ul" */ - case 'address': case 'blockquote': case 'center': case 'dir': - case 'div': case 'dl': case 'fieldset': case 'listing': - case 'menu': case 'ol': case 'p': case 'ul': - /* If the stack of open elements has a p element in scope, - then act as if an end tag with the tag name p had been - seen. */ - if($this->elementInScope('p')) { - $this->emitToken(array( - 'name' => 'p', - 'type' => HTML5::ENDTAG - )); - } - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - break; - - /* A start tag whose tag name is "form" */ - case 'form': - /* If the form element pointer is not null, ignore the - token with a parse error. */ - if($this->form_pointer !== null) { - // Ignore. - - /* Otherwise: */ - } else { - /* If the stack of open elements has a p element in - scope, then act as if an end tag with the tag name p - had been seen. */ - if($this->elementInScope('p')) { - $this->emitToken(array( - 'name' => 'p', - 'type' => HTML5::ENDTAG - )); - } - - /* Insert an HTML element for the token, and set the - form element pointer to point to the element created. */ - $element = $this->insertElement($token); - $this->form_pointer = $element; - } - break; - - /* A start tag whose tag name is "li", "dd" or "dt" */ - case 'li': case 'dd': case 'dt': - /* If the stack of open elements has a p element in scope, - then act as if an end tag with the tag name p had been - seen. */ - if($this->elementInScope('p')) { - $this->emitToken(array( - 'name' => 'p', - 'type' => HTML5::ENDTAG - )); - } - - $stack_length = count($this->stack) - 1; - - for($n = $stack_length; 0 <= $n; $n--) { - /* 1. Initialise node to be the current node (the - bottommost node of the stack). */ - $stop = false; - $node = $this->stack[$n]; - $cat = $this->getElementCategory($node->tagName); - - /* 2. If node is an li, dd or dt element, then pop all - the nodes from the current node up to node, including - node, then stop this algorithm. */ - if($token['name'] === $node->tagName || ($token['name'] !== 'li' - && ($node->tagName === 'dd' || $node->tagName === 'dt'))) { - for($x = $stack_length; $x >= $n ; $x--) { - array_pop($this->stack); - } - - break; - } - - /* 3. If node is not in the formatting category, and is - not in the phrasing category, and is not an address or - div element, then stop this algorithm. */ - if($cat !== self::FORMATTING && $cat !== self::PHRASING && - $node->tagName !== 'address' && $node->tagName !== 'div') { - break; - } - } - - /* Finally, insert an HTML element with the same tag - name as the token's. */ - $this->insertElement($token); - break; - - /* A start tag token whose tag name is "plaintext" */ - case 'plaintext': - /* If the stack of open elements has a p element in scope, - then act as if an end tag with the tag name p had been - seen. */ - if($this->elementInScope('p')) { - $this->emitToken(array( - 'name' => 'p', - 'type' => HTML5::ENDTAG - )); - } - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - - return HTML5::PLAINTEXT; - break; - - /* A start tag whose tag name is one of: "h1", "h2", "h3", "h4", - "h5", "h6" */ - case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6': - /* If the stack of open elements has a p element in scope, - then act as if an end tag with the tag name p had been seen. */ - if($this->elementInScope('p')) { - $this->emitToken(array( - 'name' => 'p', - 'type' => HTML5::ENDTAG - )); - } - - /* If the stack of open elements has in scope an element whose - tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then - this is a parse error; pop elements from the stack until an - element with one of those tag names has been popped from the - stack. */ - while($this->elementInScope(array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'))) { - array_pop($this->stack); - } - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - break; - - /* A start tag whose tag name is "a" */ - case 'a': - /* If the list of active formatting elements contains - an element whose tag name is "a" between the end of the - list and the last marker on the list (or the start of - the list if there is no marker on the list), then this - is a parse error; act as if an end tag with the tag name - "a" had been seen, then remove that element from the list - of active formatting elements and the stack of open - elements if the end tag didn't already remove it (it - might not have if the element is not in table scope). */ - $leng = count($this->a_formatting); - - for($n = $leng - 1; $n >= 0; $n--) { - if($this->a_formatting[$n] === self::MARKER) { - break; - - } elseif($this->a_formatting[$n]->nodeName === 'a') { - $this->emitToken(array( - 'name' => 'a', - 'type' => HTML5::ENDTAG - )); - break; - } - } - - /* Reconstruct the active formatting elements, if any. */ - $this->reconstructActiveFormattingElements(); - - /* Insert an HTML element for the token. */ - $el = $this->insertElement($token); - - /* Add that element to the list of active formatting - elements. */ - $this->a_formatting[] = $el; - break; - - /* A start tag whose tag name is one of: "b", "big", "em", "font", - "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */ - case 'b': case 'big': case 'em': case 'font': case 'i': - case 'nobr': case 's': case 'small': case 'strike': - case 'strong': case 'tt': case 'u': - /* Reconstruct the active formatting elements, if any. */ - $this->reconstructActiveFormattingElements(); - - /* Insert an HTML element for the token. */ - $el = $this->insertElement($token); - - /* Add that element to the list of active formatting - elements. */ - $this->a_formatting[] = $el; - break; - - /* A start tag token whose tag name is "button" */ - case 'button': - /* If the stack of open elements has a button element in scope, - then this is a parse error; act as if an end tag with the tag - name "button" had been seen, then reprocess the token. (We don't - do that. Unnecessary.) */ - if($this->elementInScope('button')) { - $this->inBody(array( - 'name' => 'button', - 'type' => HTML5::ENDTAG - )); - } - - /* Reconstruct the active formatting elements, if any. */ - $this->reconstructActiveFormattingElements(); - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - - /* Insert a marker at the end of the list of active - formatting elements. */ - $this->a_formatting[] = self::MARKER; - break; - - /* A start tag token whose tag name is one of: "marquee", "object" */ - case 'marquee': case 'object': - /* Reconstruct the active formatting elements, if any. */ - $this->reconstructActiveFormattingElements(); - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - - /* Insert a marker at the end of the list of active - formatting elements. */ - $this->a_formatting[] = self::MARKER; - break; - - /* A start tag token whose tag name is "xmp" */ - case 'xmp': - /* Reconstruct the active formatting elements, if any. */ - $this->reconstructActiveFormattingElements(); - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - - /* Switch the content model flag to the CDATA state. */ - return HTML5::CDATA; - break; - - /* A start tag whose tag name is "table" */ - case 'table': - /* If the stack of open elements has a p element in scope, - then act as if an end tag with the tag name p had been seen. */ - if($this->elementInScope('p')) { - $this->emitToken(array( - 'name' => 'p', - 'type' => HTML5::ENDTAG - )); - } - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - - /* Change the insertion mode to "in table". */ - $this->mode = self::IN_TABLE; - break; - - /* A start tag whose tag name is one of: "area", "basefont", - "bgsound", "br", "embed", "img", "param", "spacer", "wbr" */ - case 'area': case 'basefont': case 'bgsound': case 'br': - case 'embed': case 'img': case 'param': case 'spacer': - case 'wbr': - /* Reconstruct the active formatting elements, if any. */ - $this->reconstructActiveFormattingElements(); - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - - /* Immediately pop the current node off the stack of open elements. */ - array_pop($this->stack); - break; - - /* A start tag whose tag name is "hr" */ - case 'hr': - /* If the stack of open elements has a p element in scope, - then act as if an end tag with the tag name p had been seen. */ - if($this->elementInScope('p')) { - $this->emitToken(array( - 'name' => 'p', - 'type' => HTML5::ENDTAG - )); - } - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - - /* Immediately pop the current node off the stack of open elements. */ - array_pop($this->stack); - break; - - /* A start tag whose tag name is "image" */ - case 'image': - /* Parse error. Change the token's tag name to "img" and - reprocess it. (Don't ask.) */ - $token['name'] = 'img'; - return $this->inBody($token); - break; - - /* A start tag whose tag name is "input" */ - case 'input': - /* Reconstruct the active formatting elements, if any. */ - $this->reconstructActiveFormattingElements(); - - /* Insert an input element for the token. */ - $element = $this->insertElement($token, false); - - /* If the form element pointer is not null, then associate the - input element with the form element pointed to by the form - element pointer. */ - $this->form_pointer !== null - ? $this->form_pointer->appendChild($element) - : end($this->stack)->appendChild($element); - - /* Pop that input element off the stack of open elements. */ - array_pop($this->stack); - break; - - /* A start tag whose tag name is "isindex" */ - case 'isindex': - /* Parse error. */ - // w/e - - /* If the form element pointer is not null, - then ignore the token. */ - if($this->form_pointer === null) { - /* Act as if a start tag token with the tag name "form" had - been seen. */ - $this->inBody(array( - 'name' => 'body', - 'type' => HTML5::STARTTAG, - 'attr' => array() - )); - - /* Act as if a start tag token with the tag name "hr" had - been seen. */ - $this->inBody(array( - 'name' => 'hr', - 'type' => HTML5::STARTTAG, - 'attr' => array() - )); - - /* Act as if a start tag token with the tag name "p" had - been seen. */ - $this->inBody(array( - 'name' => 'p', - 'type' => HTML5::STARTTAG, - 'attr' => array() - )); - - /* Act as if a start tag token with the tag name "label" - had been seen. */ - $this->inBody(array( - 'name' => 'label', - 'type' => HTML5::STARTTAG, - 'attr' => array() - )); - - /* Act as if a stream of character tokens had been seen. */ - $this->insertText('This is a searchable index. '. - 'Insert your search keywords here: '); - - /* Act as if a start tag token with the tag name "input" - had been seen, with all the attributes from the "isindex" - token, except with the "name" attribute set to the value - "isindex" (ignoring any explicit "name" attribute). */ - $attr = $token['attr']; - $attr[] = array('name' => 'name', 'value' => 'isindex'); - - $this->inBody(array( - 'name' => 'input', - 'type' => HTML5::STARTTAG, - 'attr' => $attr - )); - - /* Act as if a stream of character tokens had been seen - (see below for what they should say). */ - $this->insertText('This is a searchable index. '. - 'Insert your search keywords here: '); - - /* Act as if an end tag token with the tag name "label" - had been seen. */ - $this->inBody(array( - 'name' => 'label', - 'type' => HTML5::ENDTAG - )); - - /* Act as if an end tag token with the tag name "p" had - been seen. */ - $this->inBody(array( - 'name' => 'p', - 'type' => HTML5::ENDTAG - )); - - /* Act as if a start tag token with the tag name "hr" had - been seen. */ - $this->inBody(array( - 'name' => 'hr', - 'type' => HTML5::ENDTAG - )); - - /* Act as if an end tag token with the tag name "form" had - been seen. */ - $this->inBody(array( - 'name' => 'form', - 'type' => HTML5::ENDTAG - )); - } - break; - - /* A start tag whose tag name is "textarea" */ - case 'textarea': - $this->insertElement($token); - - /* Switch the tokeniser's content model flag to the - RCDATA state. */ - return HTML5::RCDATA; - break; - - /* A start tag whose tag name is one of: "iframe", "noembed", - "noframes" */ - case 'iframe': case 'noembed': case 'noframes': - $this->insertElement($token); - - /* Switch the tokeniser's content model flag to the CDATA state. */ - return HTML5::CDATA; - break; - - /* A start tag whose tag name is "select" */ - case 'select': - /* Reconstruct the active formatting elements, if any. */ - $this->reconstructActiveFormattingElements(); - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - - /* Change the insertion mode to "in select". */ - $this->mode = self::IN_SELECT; - break; - - /* A start or end tag whose tag name is one of: "caption", "col", - "colgroup", "frame", "frameset", "head", "option", "optgroup", - "tbody", "td", "tfoot", "th", "thead", "tr". */ - case 'caption': case 'col': case 'colgroup': case 'frame': - case 'frameset': case 'head': case 'option': case 'optgroup': - case 'tbody': case 'td': case 'tfoot': case 'th': case 'thead': - case 'tr': - // Parse error. Ignore the token. - break; - - /* A start or end tag whose tag name is one of: "event-source", - "section", "nav", "article", "aside", "header", "footer", - "datagrid", "command" */ - case 'event-source': case 'section': case 'nav': case 'article': - case 'aside': case 'header': case 'footer': case 'datagrid': - case 'command': - // Work in progress! - break; - - /* A start tag token not covered by the previous entries */ - default: - /* Reconstruct the active formatting elements, if any. */ - $this->reconstructActiveFormattingElements(); - - $this->insertElement($token, true, true); - break; - } - break; - - case HTML5::ENDTAG: - switch($token['name']) { - /* An end tag with the tag name "body" */ - case 'body': - /* If the second element in the stack of open elements is - not a body element, this is a parse error. Ignore the token. - (innerHTML case) */ - if(count($this->stack) < 2 || $this->stack[1]->nodeName !== 'body') { - // Ignore. - - /* If the current node is not the body element, then this - is a parse error. */ - } elseif(end($this->stack)->nodeName !== 'body') { - // Parse error. - } - - /* Change the insertion mode to "after body". */ - $this->mode = self::AFTER_BODY; - break; - - /* An end tag with the tag name "html" */ - case 'html': - /* Act as if an end tag with tag name "body" had been seen, - then, if that token wasn't ignored, reprocess the current - token. */ - $this->inBody(array( - 'name' => 'body', - 'type' => HTML5::ENDTAG - )); - - return $this->afterBody($token); - break; - - /* An end tag whose tag name is one of: "address", "blockquote", - "center", "dir", "div", "dl", "fieldset", "listing", "menu", - "ol", "pre", "ul" */ - case 'address': case 'blockquote': case 'center': case 'dir': - case 'div': case 'dl': case 'fieldset': case 'listing': - case 'menu': case 'ol': case 'pre': case 'ul': - /* If the stack of open elements has an element in scope - with the same tag name as that of the token, then generate - implied end tags. */ - if($this->elementInScope($token['name'])) { - $this->generateImpliedEndTags(); - - /* Now, if the current node is not an element with - the same tag name as that of the token, then this - is a parse error. */ - // w/e - - /* If the stack of open elements has an element in - scope with the same tag name as that of the token, - then pop elements from this stack until an element - with that tag name has been popped from the stack. */ - for($n = count($this->stack) - 1; $n >= 0; $n--) { - if($this->stack[$n]->nodeName === $token['name']) { - $n = -1; - } - - array_pop($this->stack); - } - } - break; - - /* An end tag whose tag name is "form" */ - case 'form': - /* If the stack of open elements has an element in scope - with the same tag name as that of the token, then generate - implied end tags. */ - if($this->elementInScope($token['name'])) { - $this->generateImpliedEndTags(); - - } - - if(end($this->stack)->nodeName !== $token['name']) { - /* Now, if the current node is not an element with the - same tag name as that of the token, then this is a parse - error. */ - // w/e - - } else { - /* Otherwise, if the current node is an element with - the same tag name as that of the token pop that element - from the stack. */ - array_pop($this->stack); - } - - /* In any case, set the form element pointer to null. */ - $this->form_pointer = null; - break; - - /* An end tag whose tag name is "p" */ - case 'p': - /* If the stack of open elements has a p element in scope, - then generate implied end tags, except for p elements. */ - if($this->elementInScope('p')) { - $this->generateImpliedEndTags(array('p')); - - /* If the current node is not a p element, then this is - a parse error. */ - // k - - /* If the stack of open elements has a p element in - scope, then pop elements from this stack until the stack - no longer has a p element in scope. */ - for($n = count($this->stack) - 1; $n >= 0; $n--) { - if($this->elementInScope('p')) { - array_pop($this->stack); - - } else { - break; - } - } - } - break; - - /* An end tag whose tag name is "dd", "dt", or "li" */ - case 'dd': case 'dt': case 'li': - /* If the stack of open elements has an element in scope - whose tag name matches the tag name of the token, then - generate implied end tags, except for elements with the - same tag name as the token. */ - if($this->elementInScope($token['name'])) { - $this->generateImpliedEndTags(array($token['name'])); - - /* If the current node is not an element with the same - tag name as the token, then this is a parse error. */ - // w/e - - /* If the stack of open elements has an element in scope - whose tag name matches the tag name of the token, then - pop elements from this stack until an element with that - tag name has been popped from the stack. */ - for($n = count($this->stack) - 1; $n >= 0; $n--) { - if($this->stack[$n]->nodeName === $token['name']) { - $n = -1; - } - - array_pop($this->stack); - } - } - break; - - /* An end tag whose tag name is one of: "h1", "h2", "h3", "h4", - "h5", "h6" */ - case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6': - $elements = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'); - - /* If the stack of open elements has in scope an element whose - tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then - generate implied end tags. */ - if($this->elementInScope($elements)) { - $this->generateImpliedEndTags(); - - /* Now, if the current node is not an element with the same - tag name as that of the token, then this is a parse error. */ - // w/e - - /* If the stack of open elements has in scope an element - whose tag name is one of "h1", "h2", "h3", "h4", "h5", or - "h6", then pop elements from the stack until an element - with one of those tag names has been popped from the stack. */ - while($this->elementInScope($elements)) { - array_pop($this->stack); - } - } - break; - - /* An end tag whose tag name is one of: "a", "b", "big", "em", - "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */ - case 'a': case 'b': case 'big': case 'em': case 'font': - case 'i': case 'nobr': case 's': case 'small': case 'strike': - case 'strong': case 'tt': case 'u': - /* 1. Let the formatting element be the last element in - the list of active formatting elements that: - * is between the end of the list and the last scope - marker in the list, if any, or the start of the list - otherwise, and - * has the same tag name as the token. - */ - while(true) { - for($a = count($this->a_formatting) - 1; $a >= 0; $a--) { - if($this->a_formatting[$a] === self::MARKER) { - break; - - } elseif($this->a_formatting[$a]->tagName === $token['name']) { - $formatting_element = $this->a_formatting[$a]; - $in_stack = in_array($formatting_element, $this->stack, true); - $fe_af_pos = $a; - break; - } - } - - /* If there is no such node, or, if that node is - also in the stack of open elements but the element - is not in scope, then this is a parse error. Abort - these steps. The token is ignored. */ - if(!isset($formatting_element) || ($in_stack && - !$this->elementInScope($token['name']))) { - break; - - /* Otherwise, if there is such a node, but that node - is not in the stack of open elements, then this is a - parse error; remove the element from the list, and - abort these steps. */ - } elseif(isset($formatting_element) && !$in_stack) { - unset($this->a_formatting[$fe_af_pos]); - $this->a_formatting = array_merge($this->a_formatting); - break; - } - - /* 2. Let the furthest block be the topmost node in the - stack of open elements that is lower in the stack - than the formatting element, and is not an element in - the phrasing or formatting categories. There might - not be one. */ - $fe_s_pos = array_search($formatting_element, $this->stack, true); - $length = count($this->stack); - - for($s = $fe_s_pos + 1; $s < $length; $s++) { - $category = $this->getElementCategory($this->stack[$s]->nodeName); - - if($category !== self::PHRASING && $category !== self::FORMATTING) { - $furthest_block = $this->stack[$s]; - } - } - - /* 3. If there is no furthest block, then the UA must - skip the subsequent steps and instead just pop all - the nodes from the bottom of the stack of open - elements, from the current node up to the formatting - element, and remove the formatting element from the - list of active formatting elements. */ - if(!isset($furthest_block)) { - for($n = $length - 1; $n >= $fe_s_pos; $n--) { - array_pop($this->stack); - } - - unset($this->a_formatting[$fe_af_pos]); - $this->a_formatting = array_merge($this->a_formatting); - break; - } - - /* 4. Let the common ancestor be the element - immediately above the formatting element in the stack - of open elements. */ - $common_ancestor = $this->stack[$fe_s_pos - 1]; - - /* 5. If the furthest block has a parent node, then - remove the furthest block from its parent node. */ - if($furthest_block->parentNode !== null) { - $furthest_block->parentNode->removeChild($furthest_block); - } - - /* 6. Let a bookmark note the position of the - formatting element in the list of active formatting - elements relative to the elements on either side - of it in the list. */ - $bookmark = $fe_af_pos; - - /* 7. Let node and last node be the furthest block. - Follow these steps: */ - $node = $furthest_block; - $last_node = $furthest_block; - - while(true) { - for($n = array_search($node, $this->stack, true) - 1; $n >= 0; $n--) { - /* 7.1 Let node be the element immediately - prior to node in the stack of open elements. */ - $node = $this->stack[$n]; - - /* 7.2 If node is not in the list of active - formatting elements, then remove node from - the stack of open elements and then go back - to step 1. */ - if(!in_array($node, $this->a_formatting, true)) { - unset($this->stack[$n]); - $this->stack = array_merge($this->stack); - - } else { - break; - } - } - - /* 7.3 Otherwise, if node is the formatting - element, then go to the next step in the overall - algorithm. */ - if($node === $formatting_element) { - break; - - /* 7.4 Otherwise, if last node is the furthest - block, then move the aforementioned bookmark to - be immediately after the node in the list of - active formatting elements. */ - } elseif($last_node === $furthest_block) { - $bookmark = array_search($node, $this->a_formatting, true) + 1; - } - - /* 7.5 If node has any children, perform a - shallow clone of node, replace the entry for - node in the list of active formatting elements - with an entry for the clone, replace the entry - for node in the stack of open elements with an - entry for the clone, and let node be the clone. */ - if($node->hasChildNodes()) { - $clone = $node->cloneNode(); - $s_pos = array_search($node, $this->stack, true); - $a_pos = array_search($node, $this->a_formatting, true); - - $this->stack[$s_pos] = $clone; - $this->a_formatting[$a_pos] = $clone; - $node = $clone; - } - - /* 7.6 Insert last node into node, first removing - it from its previous parent node if any. */ - if($last_node->parentNode !== null) { - $last_node->parentNode->removeChild($last_node); - } - - $node->appendChild($last_node); - - /* 7.7 Let last node be node. */ - $last_node = $node; - } - - /* 8. Insert whatever last node ended up being in - the previous step into the common ancestor node, - first removing it from its previous parent node if - any. */ - if($last_node->parentNode !== null) { - $last_node->parentNode->removeChild($last_node); - } - - $common_ancestor->appendChild($last_node); - - /* 9. Perform a shallow clone of the formatting - element. */ - $clone = $formatting_element->cloneNode(); - - /* 10. Take all of the child nodes of the furthest - block and append them to the clone created in the - last step. */ - while($furthest_block->hasChildNodes()) { - $child = $furthest_block->firstChild; - $furthest_block->removeChild($child); - $clone->appendChild($child); - } - - /* 11. Append that clone to the furthest block. */ - $furthest_block->appendChild($clone); - - /* 12. Remove the formatting element from the list - of active formatting elements, and insert the clone - into the list of active formatting elements at the - position of the aforementioned bookmark. */ - $fe_af_pos = array_search($formatting_element, $this->a_formatting, true); - unset($this->a_formatting[$fe_af_pos]); - $this->a_formatting = array_merge($this->a_formatting); - - $af_part1 = array_slice($this->a_formatting, 0, $bookmark - 1); - $af_part2 = array_slice($this->a_formatting, $bookmark, count($this->a_formatting)); - $this->a_formatting = array_merge($af_part1, array($clone), $af_part2); - - /* 13. Remove the formatting element from the stack - of open elements, and insert the clone into the stack - of open elements immediately after (i.e. in a more - deeply nested position than) the position of the - furthest block in that stack. */ - $fe_s_pos = array_search($formatting_element, $this->stack, true); - $fb_s_pos = array_search($furthest_block, $this->stack, true); - unset($this->stack[$fe_s_pos]); - - $s_part1 = array_slice($this->stack, 0, $fb_s_pos); - $s_part2 = array_slice($this->stack, $fb_s_pos + 1, count($this->stack)); - $this->stack = array_merge($s_part1, array($clone), $s_part2); - - /* 14. Jump back to step 1 in this series of steps. */ - unset($formatting_element, $fe_af_pos, $fe_s_pos, $furthest_block); - } - break; - - /* An end tag token whose tag name is one of: "button", - "marquee", "object" */ - case 'button': case 'marquee': case 'object': - /* If the stack of open elements has an element in scope whose - tag name matches the tag name of the token, then generate implied - tags. */ - if($this->elementInScope($token['name'])) { - $this->generateImpliedEndTags(); - - /* Now, if the current node is not an element with the same - tag name as the token, then this is a parse error. */ - // k - - /* Now, if the stack of open elements has an element in scope - whose tag name matches the tag name of the token, then pop - elements from the stack until that element has been popped from - the stack, and clear the list of active formatting elements up - to the last marker. */ - for($n = count($this->stack) - 1; $n >= 0; $n--) { - if($this->stack[$n]->nodeName === $token['name']) { - $n = -1; - } - - array_pop($this->stack); - } - - $marker = end(array_keys($this->a_formatting, self::MARKER, true)); - - for($n = count($this->a_formatting) - 1; $n > $marker; $n--) { - array_pop($this->a_formatting); - } - } - break; - - /* Or an end tag whose tag name is one of: "area", "basefont", - "bgsound", "br", "embed", "hr", "iframe", "image", "img", - "input", "isindex", "noembed", "noframes", "param", "select", - "spacer", "table", "textarea", "wbr" */ - case 'area': case 'basefont': case 'bgsound': case 'br': - case 'embed': case 'hr': case 'iframe': case 'image': - case 'img': case 'input': case 'isindex': case 'noembed': - case 'noframes': case 'param': case 'select': case 'spacer': - case 'table': case 'textarea': case 'wbr': - // Parse error. Ignore the token. - break; - - /* An end tag token not covered by the previous entries */ - default: - for($n = count($this->stack) - 1; $n >= 0; $n--) { - /* Initialise node to be the current node (the bottommost - node of the stack). */ - $node = end($this->stack); - - /* If node has the same tag name as the end tag token, - then: */ - if($token['name'] === $node->nodeName) { - /* Generate implied end tags. */ - $this->generateImpliedEndTags(); - - /* If the tag name of the end tag token does not - match the tag name of the current node, this is a - parse error. */ - // k - - /* Pop all the nodes from the current node up to - node, including node, then stop this algorithm. */ - for($x = count($this->stack) - $n; $x >= $n; $x--) { - array_pop($this->stack); - } - - } else { - $category = $this->getElementCategory($node); - - if($category !== self::SPECIAL && $category !== self::SCOPING) { - /* Otherwise, if node is in neither the formatting - category nor the phrasing category, then this is a - parse error. Stop this algorithm. The end tag token - is ignored. */ - return false; - } - } - } - break; - } - break; - } - } - - private function inTable($token) { - $clear = array('html', 'table'); - - /* A character token that is one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE */ - if($token['type'] === HTML5::CHARACTR && - preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { - /* Append the character to the current node. */ - $text = $this->dom->createTextNode($token['data']); - end($this->stack)->appendChild($text); - - /* A comment token */ - } elseif($token['type'] === HTML5::COMMENT) { - /* Append a Comment node to the current node with the data - attribute set to the data given in the comment token. */ - $comment = $this->dom->createComment($token['data']); - end($this->stack)->appendChild($comment); - - /* A start tag whose tag name is "caption" */ - } elseif($token['type'] === HTML5::STARTTAG && - $token['name'] === 'caption') { - /* Clear the stack back to a table context. */ - $this->clearStackToTableContext($clear); - - /* Insert a marker at the end of the list of active - formatting elements. */ - $this->a_formatting[] = self::MARKER; - - /* Insert an HTML element for the token, then switch the - insertion mode to "in caption". */ - $this->insertElement($token); - $this->mode = self::IN_CAPTION; - - /* A start tag whose tag name is "colgroup" */ - } elseif($token['type'] === HTML5::STARTTAG && - $token['name'] === 'colgroup') { - /* Clear the stack back to a table context. */ - $this->clearStackToTableContext($clear); - - /* Insert an HTML element for the token, then switch the - insertion mode to "in column group". */ - $this->insertElement($token); - $this->mode = self::IN_CGROUP; - - /* A start tag whose tag name is "col" */ - } elseif($token['type'] === HTML5::STARTTAG && - $token['name'] === 'col') { - $this->inTable(array( - 'name' => 'colgroup', - 'type' => HTML5::STARTTAG, - 'attr' => array() - )); - - $this->inColumnGroup($token); - - /* A start tag whose tag name is one of: "tbody", "tfoot", "thead" */ - } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], - array('tbody', 'tfoot', 'thead'))) { - /* Clear the stack back to a table context. */ - $this->clearStackToTableContext($clear); - - /* Insert an HTML element for the token, then switch the insertion - mode to "in table body". */ - $this->insertElement($token); - $this->mode = self::IN_TBODY; - - /* A start tag whose tag name is one of: "td", "th", "tr" */ - } elseif($token['type'] === HTML5::STARTTAG && - in_array($token['name'], array('td', 'th', 'tr'))) { - /* Act as if a start tag token with the tag name "tbody" had been - seen, then reprocess the current token. */ - $this->inTable(array( - 'name' => 'tbody', - 'type' => HTML5::STARTTAG, - 'attr' => array() - )); - - return $this->inTableBody($token); - - /* A start tag whose tag name is "table" */ - } elseif($token['type'] === HTML5::STARTTAG && - $token['name'] === 'table') { - /* Parse error. Act as if an end tag token with the tag name "table" - had been seen, then, if that token wasn't ignored, reprocess the - current token. */ - $this->inTable(array( - 'name' => 'table', - 'type' => HTML5::ENDTAG - )); - - return $this->mainPhase($token); - - /* An end tag whose tag name is "table" */ - } elseif($token['type'] === HTML5::ENDTAG && - $token['name'] === 'table') { - /* If the stack of open elements does not have an element in table - scope with the same tag name as the token, this is a parse error. - Ignore the token. (innerHTML case) */ - if(!$this->elementInScope($token['name'], true)) { - return false; - - /* Otherwise: */ - } else { - /* Generate implied end tags. */ - $this->generateImpliedEndTags(); - - /* Now, if the current node is not a table element, then this - is a parse error. */ - // w/e - - /* Pop elements from this stack until a table element has been - popped from the stack. */ - while(true) { - $current = end($this->stack)->nodeName; - array_pop($this->stack); - - if($current === 'table') { - break; - } - } - - /* Reset the insertion mode appropriately. */ - $this->resetInsertionMode(); - } - - /* An end tag whose tag name is one of: "body", "caption", "col", - "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr" */ - } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], - array('body', 'caption', 'col', 'colgroup', 'html', 'tbody', 'td', - 'tfoot', 'th', 'thead', 'tr'))) { - // Parse error. Ignore the token. - - /* Anything else */ - } else { - /* Parse error. Process the token as if the insertion mode was "in - body", with the following exception: */ - - /* If the current node is a table, tbody, tfoot, thead, or tr - element, then, whenever a node would be inserted into the current - node, it must instead be inserted into the foster parent element. */ - if(in_array(end($this->stack)->nodeName, - array('table', 'tbody', 'tfoot', 'thead', 'tr'))) { - /* The foster parent element is the parent element of the last - table element in the stack of open elements, if there is a - table element and it has such a parent element. If there is no - table element in the stack of open elements (innerHTML case), - then the foster parent element is the first element in the - stack of open elements (the html element). Otherwise, if there - is a table element in the stack of open elements, but the last - table element in the stack of open elements has no parent, or - its parent node is not an element, then the foster parent - element is the element before the last table element in the - stack of open elements. */ - for($n = count($this->stack) - 1; $n >= 0; $n--) { - if($this->stack[$n]->nodeName === 'table') { - $table = $this->stack[$n]; - break; - } - } - - if(isset($table) && $table->parentNode !== null) { - $this->foster_parent = $table->parentNode; - - } elseif(!isset($table)) { - $this->foster_parent = $this->stack[0]; - - } elseif(isset($table) && ($table->parentNode === null || - $table->parentNode->nodeType !== XML_ELEMENT_NODE)) { - $this->foster_parent = $this->stack[$n - 1]; - } - } - - $this->inBody($token); - } - } - - private function inCaption($token) { - /* An end tag whose tag name is "caption" */ - if($token['type'] === HTML5::ENDTAG && $token['name'] === 'caption') { - /* If the stack of open elements does not have an element in table - scope with the same tag name as the token, this is a parse error. - Ignore the token. (innerHTML case) */ - if(!$this->elementInScope($token['name'], true)) { - // Ignore - - /* Otherwise: */ - } else { - /* Generate implied end tags. */ - $this->generateImpliedEndTags(); - - /* Now, if the current node is not a caption element, then this - is a parse error. */ - // w/e - - /* Pop elements from this stack until a caption element has - been popped from the stack. */ - while(true) { - $node = end($this->stack)->nodeName; - array_pop($this->stack); - - if($node === 'caption') { - break; - } - } - - /* Clear the list of active formatting elements up to the last - marker. */ - $this->clearTheActiveFormattingElementsUpToTheLastMarker(); - - /* Switch the insertion mode to "in table". */ - $this->mode = self::IN_TABLE; - } - - /* A start tag whose tag name is one of: "caption", "col", "colgroup", - "tbody", "td", "tfoot", "th", "thead", "tr", or an end tag whose tag - name is "table" */ - } elseif(($token['type'] === HTML5::STARTTAG && in_array($token['name'], - array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', - 'thead', 'tr'))) || ($token['type'] === HTML5::ENDTAG && - $token['name'] === 'table')) { - /* Parse error. Act as if an end tag with the tag name "caption" - had been seen, then, if that token wasn't ignored, reprocess the - current token. */ - $this->inCaption(array( - 'name' => 'caption', - 'type' => HTML5::ENDTAG - )); - - return $this->inTable($token); - - /* An end tag whose tag name is one of: "body", "col", "colgroup", - "html", "tbody", "td", "tfoot", "th", "thead", "tr" */ - } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], - array('body', 'col', 'colgroup', 'html', 'tbody', 'tfoot', 'th', - 'thead', 'tr'))) { - // Parse error. Ignore the token. - - /* Anything else */ - } else { - /* Process the token as if the insertion mode was "in body". */ - $this->inBody($token); - } - } - - private function inColumnGroup($token) { - /* A character token that is one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE */ - if($token['type'] === HTML5::CHARACTR && - preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { - /* Append the character to the current node. */ - $text = $this->dom->createTextNode($token['data']); - end($this->stack)->appendChild($text); - - /* A comment token */ - } elseif($token['type'] === HTML5::COMMENT) { - /* Append a Comment node to the current node with the data - attribute set to the data given in the comment token. */ - $comment = $this->dom->createComment($token['data']); - end($this->stack)->appendChild($comment); - - /* A start tag whose tag name is "col" */ - } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'col') { - /* Insert a col element for the token. Immediately pop the current - node off the stack of open elements. */ - $this->insertElement($token); - array_pop($this->stack); - - /* An end tag whose tag name is "colgroup" */ - } elseif($token['type'] === HTML5::ENDTAG && - $token['name'] === 'colgroup') { - /* If the current node is the root html element, then this is a - parse error, ignore the token. (innerHTML case) */ - if(end($this->stack)->nodeName === 'html') { - // Ignore - - /* Otherwise, pop the current node (which will be a colgroup - element) from the stack of open elements. Switch the insertion - mode to "in table". */ - } else { - array_pop($this->stack); - $this->mode = self::IN_TABLE; - } - - /* An end tag whose tag name is "col" */ - } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'col') { - /* Parse error. Ignore the token. */ - - /* Anything else */ - } else { - /* Act as if an end tag with the tag name "colgroup" had been seen, - and then, if that token wasn't ignored, reprocess the current token. */ - $this->inColumnGroup(array( - 'name' => 'colgroup', - 'type' => HTML5::ENDTAG - )); - - return $this->inTable($token); - } - } - - private function inTableBody($token) { - $clear = array('tbody', 'tfoot', 'thead', 'html'); - - /* A start tag whose tag name is "tr" */ - if($token['type'] === HTML5::STARTTAG && $token['name'] === 'tr') { - /* Clear the stack back to a table body context. */ - $this->clearStackToTableContext($clear); - - /* Insert a tr element for the token, then switch the insertion - mode to "in row". */ - $this->insertElement($token); - $this->mode = self::IN_ROW; - - /* A start tag whose tag name is one of: "th", "td" */ - } elseif($token['type'] === HTML5::STARTTAG && - ($token['name'] === 'th' || $token['name'] === 'td')) { - /* Parse error. Act as if a start tag with the tag name "tr" had - been seen, then reprocess the current token. */ - $this->inTableBody(array( - 'name' => 'tr', - 'type' => HTML5::STARTTAG, - 'attr' => array() - )); - - return $this->inRow($token); - - /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */ - } elseif($token['type'] === HTML5::ENDTAG && - in_array($token['name'], array('tbody', 'tfoot', 'thead'))) { - /* If the stack of open elements does not have an element in table - scope with the same tag name as the token, this is a parse error. - Ignore the token. */ - if(!$this->elementInScope($token['name'], true)) { - // Ignore - - /* Otherwise: */ - } else { - /* Clear the stack back to a table body context. */ - $this->clearStackToTableContext($clear); - - /* Pop the current node from the stack of open elements. Switch - the insertion mode to "in table". */ - array_pop($this->stack); - $this->mode = self::IN_TABLE; - } - - /* A start tag whose tag name is one of: "caption", "col", "colgroup", - "tbody", "tfoot", "thead", or an end tag whose tag name is "table" */ - } elseif(($token['type'] === HTML5::STARTTAG && in_array($token['name'], - array('caption', 'col', 'colgroup', 'tbody', 'tfoor', 'thead'))) || - ($token['type'] === HTML5::STARTTAG && $token['name'] === 'table')) { - /* If the stack of open elements does not have a tbody, thead, or - tfoot element in table scope, this is a parse error. Ignore the - token. (innerHTML case) */ - if(!$this->elementInScope(array('tbody', 'thead', 'tfoot'), true)) { - // Ignore. - - /* Otherwise: */ - } else { - /* Clear the stack back to a table body context. */ - $this->clearStackToTableContext($clear); - - /* Act as if an end tag with the same tag name as the current - node ("tbody", "tfoot", or "thead") had been seen, then - reprocess the current token. */ - $this->inTableBody(array( - 'name' => end($this->stack)->nodeName, - 'type' => HTML5::ENDTAG - )); - - return $this->mainPhase($token); - } - - /* An end tag whose tag name is one of: "body", "caption", "col", - "colgroup", "html", "td", "th", "tr" */ - } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], - array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr'))) { - /* Parse error. Ignore the token. */ - - /* Anything else */ - } else { - /* Process the token as if the insertion mode was "in table". */ - $this->inTable($token); - } - } - - private function inRow($token) { - $clear = array('tr', 'html'); - - /* A start tag whose tag name is one of: "th", "td" */ - if($token['type'] === HTML5::STARTTAG && - ($token['name'] === 'th' || $token['name'] === 'td')) { - /* Clear the stack back to a table row context. */ - $this->clearStackToTableContext($clear); - - /* Insert an HTML element for the token, then switch the insertion - mode to "in cell". */ - $this->insertElement($token); - $this->mode = self::IN_CELL; - - /* Insert a marker at the end of the list of active formatting - elements. */ - $this->a_formatting[] = self::MARKER; - - /* An end tag whose tag name is "tr" */ - } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'tr') { - /* If the stack of open elements does not have an element in table - scope with the same tag name as the token, this is a parse error. - Ignore the token. (innerHTML case) */ - if(!$this->elementInScope($token['name'], true)) { - // Ignore. - - /* Otherwise: */ - } else { - /* Clear the stack back to a table row context. */ - $this->clearStackToTableContext($clear); - - /* Pop the current node (which will be a tr element) from the - stack of open elements. Switch the insertion mode to "in table - body". */ - array_pop($this->stack); - $this->mode = self::IN_TBODY; - } - - /* A start tag whose tag name is one of: "caption", "col", "colgroup", - "tbody", "tfoot", "thead", "tr" or an end tag whose tag name is "table" */ - } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], - array('caption', 'col', 'colgroup', 'tbody', 'tfoot', 'thead', 'tr'))) { - /* Act as if an end tag with the tag name "tr" had been seen, then, - if that token wasn't ignored, reprocess the current token. */ - $this->inRow(array( - 'name' => 'tr', - 'type' => HTML5::ENDTAG - )); - - return $this->inCell($token); - - /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */ - } elseif($token['type'] === HTML5::ENDTAG && - in_array($token['name'], array('tbody', 'tfoot', 'thead'))) { - /* If the stack of open elements does not have an element in table - scope with the same tag name as the token, this is a parse error. - Ignore the token. */ - if(!$this->elementInScope($token['name'], true)) { - // Ignore. - - /* Otherwise: */ - } else { - /* Otherwise, act as if an end tag with the tag name "tr" had - been seen, then reprocess the current token. */ - $this->inRow(array( - 'name' => 'tr', - 'type' => HTML5::ENDTAG - )); - - return $this->inCell($token); - } - - /* An end tag whose tag name is one of: "body", "caption", "col", - "colgroup", "html", "td", "th" */ - } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], - array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr'))) { - /* Parse error. Ignore the token. */ - - /* Anything else */ - } else { - /* Process the token as if the insertion mode was "in table". */ - $this->inTable($token); - } - } - - private function inCell($token) { - /* An end tag whose tag name is one of: "td", "th" */ - if($token['type'] === HTML5::ENDTAG && - ($token['name'] === 'td' || $token['name'] === 'th')) { - /* If the stack of open elements does not have an element in table - scope with the same tag name as that of the token, then this is a - parse error and the token must be ignored. */ - if(!$this->elementInScope($token['name'], true)) { - // Ignore. - - /* Otherwise: */ - } else { - /* Generate implied end tags, except for elements with the same - tag name as the token. */ - $this->generateImpliedEndTags(array($token['name'])); - - /* Now, if the current node is not an element with the same tag - name as the token, then this is a parse error. */ - // k - - /* Pop elements from this stack until an element with the same - tag name as the token has been popped from the stack. */ - while(true) { - $node = end($this->stack)->nodeName; - array_pop($this->stack); - - if($node === $token['name']) { - break; - } - } - - /* Clear the list of active formatting elements up to the last - marker. */ - $this->clearTheActiveFormattingElementsUpToTheLastMarker(); - - /* Switch the insertion mode to "in row". (The current node - will be a tr element at this point.) */ - $this->mode = self::IN_ROW; - } - - /* A start tag whose tag name is one of: "caption", "col", "colgroup", - "tbody", "td", "tfoot", "th", "thead", "tr" */ - } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], - array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', - 'thead', 'tr'))) { - /* If the stack of open elements does not have a td or th element - in table scope, then this is a parse error; ignore the token. - (innerHTML case) */ - if(!$this->elementInScope(array('td', 'th'), true)) { - // Ignore. - - /* Otherwise, close the cell (see below) and reprocess the current - token. */ - } else { - $this->closeCell(); - return $this->inRow($token); - } - - /* A start tag whose tag name is one of: "caption", "col", "colgroup", - "tbody", "td", "tfoot", "th", "thead", "tr" */ - } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], - array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', - 'thead', 'tr'))) { - /* If the stack of open elements does not have a td or th element - in table scope, then this is a parse error; ignore the token. - (innerHTML case) */ - if(!$this->elementInScope(array('td', 'th'), true)) { - // Ignore. - - /* Otherwise, close the cell (see below) and reprocess the current - token. */ - } else { - $this->closeCell(); - return $this->inRow($token); - } - - /* An end tag whose tag name is one of: "body", "caption", "col", - "colgroup", "html" */ - } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], - array('body', 'caption', 'col', 'colgroup', 'html'))) { - /* Parse error. Ignore the token. */ - - /* An end tag whose tag name is one of: "table", "tbody", "tfoot", - "thead", "tr" */ - } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], - array('table', 'tbody', 'tfoot', 'thead', 'tr'))) { - /* If the stack of open elements does not have an element in table - scope with the same tag name as that of the token (which can only - happen for "tbody", "tfoot" and "thead", or, in the innerHTML case), - then this is a parse error and the token must be ignored. */ - if(!$this->elementInScope($token['name'], true)) { - // Ignore. - - /* Otherwise, close the cell (see below) and reprocess the current - token. */ - } else { - $this->closeCell(); - return $this->inRow($token); - } - - /* Anything else */ - } else { - /* Process the token as if the insertion mode was "in body". */ - $this->inBody($token); - } - } - - private function inSelect($token) { - /* Handle the token as follows: */ - - /* A character token */ - if($token['type'] === HTML5::CHARACTR) { - /* Append the token's character to the current node. */ - $this->insertText($token['data']); - - /* A comment token */ - } elseif($token['type'] === HTML5::COMMENT) { - /* Append a Comment node to the current node with the data - attribute set to the data given in the comment token. */ - $this->insertComment($token['data']); - - /* A start tag token whose tag name is "option" */ - } elseif($token['type'] === HTML5::STARTTAG && - $token['name'] === 'option') { - /* If the current node is an option element, act as if an end tag - with the tag name "option" had been seen. */ - if(end($this->stack)->nodeName === 'option') { - $this->inSelect(array( - 'name' => 'option', - 'type' => HTML5::ENDTAG - )); - } - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - - /* A start tag token whose tag name is "optgroup" */ - } elseif($token['type'] === HTML5::STARTTAG && - $token['name'] === 'optgroup') { - /* If the current node is an option element, act as if an end tag - with the tag name "option" had been seen. */ - if(end($this->stack)->nodeName === 'option') { - $this->inSelect(array( - 'name' => 'option', - 'type' => HTML5::ENDTAG - )); - } - - /* If the current node is an optgroup element, act as if an end tag - with the tag name "optgroup" had been seen. */ - if(end($this->stack)->nodeName === 'optgroup') { - $this->inSelect(array( - 'name' => 'optgroup', - 'type' => HTML5::ENDTAG - )); - } - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - - /* An end tag token whose tag name is "optgroup" */ - } elseif($token['type'] === HTML5::ENDTAG && - $token['name'] === 'optgroup') { - /* First, if the current node is an option element, and the node - immediately before it in the stack of open elements is an optgroup - element, then act as if an end tag with the tag name "option" had - been seen. */ - $elements_in_stack = count($this->stack); - - if($this->stack[$elements_in_stack - 1]->nodeName === 'option' && - $this->stack[$elements_in_stack - 2]->nodeName === 'optgroup') { - $this->inSelect(array( - 'name' => 'option', - 'type' => HTML5::ENDTAG - )); - } - - /* If the current node is an optgroup element, then pop that node - from the stack of open elements. Otherwise, this is a parse error, - ignore the token. */ - if($this->stack[$elements_in_stack - 1] === 'optgroup') { - array_pop($this->stack); - } - - /* An end tag token whose tag name is "option" */ - } elseif($token['type'] === HTML5::ENDTAG && - $token['name'] === 'option') { - /* If the current node is an option element, then pop that node - from the stack of open elements. Otherwise, this is a parse error, - ignore the token. */ - if(end($this->stack)->nodeName === 'option') { - array_pop($this->stack); - } - - /* An end tag whose tag name is "select" */ - } elseif($token['type'] === HTML5::ENDTAG && - $token['name'] === 'select') { - /* If the stack of open elements does not have an element in table - scope with the same tag name as the token, this is a parse error. - Ignore the token. (innerHTML case) */ - if(!$this->elementInScope($token['name'], true)) { - // w/e - - /* Otherwise: */ - } else { - /* Pop elements from the stack of open elements until a select - element has been popped from the stack. */ - while(true) { - $current = end($this->stack)->nodeName; - array_pop($this->stack); - - if($current === 'select') { - break; - } - } - - /* Reset the insertion mode appropriately. */ - $this->resetInsertionMode(); - } - - /* A start tag whose tag name is "select" */ - } elseif($token['name'] === 'select' && - $token['type'] === HTML5::STARTTAG) { - /* Parse error. Act as if the token had been an end tag with the - tag name "select" instead. */ - $this->inSelect(array( - 'name' => 'select', - 'type' => HTML5::ENDTAG - )); - - /* An end tag whose tag name is one of: "caption", "table", "tbody", - "tfoot", "thead", "tr", "td", "th" */ - } elseif(in_array($token['name'], array('caption', 'table', 'tbody', - 'tfoot', 'thead', 'tr', 'td', 'th')) && $token['type'] === HTML5::ENDTAG) { - /* Parse error. */ - // w/e - - /* If the stack of open elements has an element in table scope with - the same tag name as that of the token, then act as if an end tag - with the tag name "select" had been seen, and reprocess the token. - Otherwise, ignore the token. */ - if($this->elementInScope($token['name'], true)) { - $this->inSelect(array( - 'name' => 'select', - 'type' => HTML5::ENDTAG - )); - - $this->mainPhase($token); - } - - /* Anything else */ - } else { - /* Parse error. Ignore the token. */ - } - } - - private function afterBody($token) { - /* Handle the token as follows: */ - - /* A character token that is one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE */ - if($token['type'] === HTML5::CHARACTR && - preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { - /* Process the token as it would be processed if the insertion mode - was "in body". */ - $this->inBody($token); - - /* A comment token */ - } elseif($token['type'] === HTML5::COMMENT) { - /* Append a Comment node to the first element in the stack of open - elements (the html element), with the data attribute set to the - data given in the comment token. */ - $comment = $this->dom->createComment($token['data']); - $this->stack[0]->appendChild($comment); - - /* An end tag with the tag name "html" */ - } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') { - /* If the parser was originally created in order to handle the - setting of an element's innerHTML attribute, this is a parse error; - ignore the token. (The element will be an html element in this - case.) (innerHTML case) */ - - /* Otherwise, switch to the trailing end phase. */ - $this->phase = self::END_PHASE; - - /* Anything else */ - } else { - /* Parse error. Set the insertion mode to "in body" and reprocess - the token. */ - $this->mode = self::IN_BODY; - return $this->inBody($token); - } - } - - private function inFrameset($token) { - /* Handle the token as follows: */ - - /* A character token that is one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */ - if($token['type'] === HTML5::CHARACTR && - preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { - /* Append the character to the current node. */ - $this->insertText($token['data']); - - /* A comment token */ - } elseif($token['type'] === HTML5::COMMENT) { - /* Append a Comment node to the current node with the data - attribute set to the data given in the comment token. */ - $this->insertComment($token['data']); - - /* A start tag with the tag name "frameset" */ - } elseif($token['name'] === 'frameset' && - $token['type'] === HTML5::STARTTAG) { - $this->insertElement($token); - - /* An end tag with the tag name "frameset" */ - } elseif($token['name'] === 'frameset' && - $token['type'] === HTML5::ENDTAG) { - /* If the current node is the root html element, then this is a - parse error; ignore the token. (innerHTML case) */ - if(end($this->stack)->nodeName === 'html') { - // Ignore - - } else { - /* Otherwise, pop the current node from the stack of open - elements. */ - array_pop($this->stack); - - /* If the parser was not originally created in order to handle - the setting of an element's innerHTML attribute (innerHTML case), - and the current node is no longer a frameset element, then change - the insertion mode to "after frameset". */ - $this->mode = self::AFTR_FRAME; - } - - /* A start tag with the tag name "frame" */ - } elseif($token['name'] === 'frame' && - $token['type'] === HTML5::STARTTAG) { - /* Insert an HTML element for the token. */ - $this->insertElement($token); - - /* Immediately pop the current node off the stack of open elements. */ - array_pop($this->stack); - - /* A start tag with the tag name "noframes" */ - } elseif($token['name'] === 'noframes' && - $token['type'] === HTML5::STARTTAG) { - /* Process the token as if the insertion mode had been "in body". */ - $this->inBody($token); - - /* Anything else */ - } else { - /* Parse error. Ignore the token. */ - } - } - - private function afterFrameset($token) { - /* Handle the token as follows: */ - - /* A character token that is one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */ - if($token['type'] === HTML5::CHARACTR && - preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { - /* Append the character to the current node. */ - $this->insertText($token['data']); - - /* A comment token */ - } elseif($token['type'] === HTML5::COMMENT) { - /* Append a Comment node to the current node with the data - attribute set to the data given in the comment token. */ - $this->insertComment($token['data']); - - /* An end tag with the tag name "html" */ - } elseif($token['name'] === 'html' && - $token['type'] === HTML5::ENDTAG) { - /* Switch to the trailing end phase. */ - $this->phase = self::END_PHASE; - - /* A start tag with the tag name "noframes" */ - } elseif($token['name'] === 'noframes' && - $token['type'] === HTML5::STARTTAG) { - /* Process the token as if the insertion mode had been "in body". */ - $this->inBody($token); - - /* Anything else */ - } else { - /* Parse error. Ignore the token. */ - } - } - - private function trailingEndPhase($token) { - /* After the main phase, as each token is emitted from the tokenisation - stage, it must be processed as described in this section. */ - - /* A DOCTYPE token */ - if($token['type'] === HTML5::DOCTYPE) { - // Parse error. Ignore the token. - - /* A comment token */ - } elseif($token['type'] === HTML5::COMMENT) { - /* Append a Comment node to the Document object with the data - attribute set to the data given in the comment token. */ - $comment = $this->dom->createComment($token['data']); - $this->dom->appendChild($comment); - - /* A character token that is one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE */ - } elseif($token['type'] === HTML5::CHARACTR && - preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { - /* Process the token as it would be processed in the main phase. */ - $this->mainPhase($token); - - /* A character token that is not one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE. Or a start tag token. Or an end tag token. */ - } elseif(($token['type'] === HTML5::CHARACTR && - preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || - $token['type'] === HTML5::STARTTAG || $token['type'] === HTML5::ENDTAG) { - /* Parse error. Switch back to the main phase and reprocess the - token. */ - $this->phase = self::MAIN_PHASE; - return $this->mainPhase($token); - - /* An end-of-file token */ - } elseif($token['type'] === HTML5::EOF) { - /* OMG DONE!! */ - } - } - - private function insertElement($token, $append = true, $check = false) { - // Proprietary workaround for libxml2's limitations with tag names - if ($check) { - // Slightly modified HTML5 tag-name modification, - // removing anything that's not an ASCII letter, digit, or hyphen - $token['name'] = preg_replace('/[^a-z0-9-]/i', '', $token['name']); - // Remove leading hyphens and numbers - $token['name'] = ltrim($token['name'], '-0..9'); - // In theory, this should ever be needed, but just in case - if ($token['name'] === '') $token['name'] = 'span'; // arbitrary generic choice - } - - $el = $this->dom->createElement($token['name']); - - foreach($token['attr'] as $attr) { - if(!$el->hasAttribute($attr['name'])) { - $el->setAttribute($attr['name'], $attr['value']); - } - } - - $this->appendToRealParent($el); - $this->stack[] = $el; - - return $el; - } - - private function insertText($data) { - $text = $this->dom->createTextNode($data); - $this->appendToRealParent($text); - } - - private function insertComment($data) { - $comment = $this->dom->createComment($data); - $this->appendToRealParent($comment); - } - - private function appendToRealParent($node) { - if($this->foster_parent === null) { - end($this->stack)->appendChild($node); - - } elseif($this->foster_parent !== null) { - /* If the foster parent element is the parent element of the - last table element in the stack of open elements, then the new - node must be inserted immediately before the last table element - in the stack of open elements in the foster parent element; - otherwise, the new node must be appended to the foster parent - element. */ - for($n = count($this->stack) - 1; $n >= 0; $n--) { - if($this->stack[$n]->nodeName === 'table' && - $this->stack[$n]->parentNode !== null) { - $table = $this->stack[$n]; - break; - } - } - - if(isset($table) && $this->foster_parent->isSameNode($table->parentNode)) - $this->foster_parent->insertBefore($node, $table); - else - $this->foster_parent->appendChild($node); - - $this->foster_parent = null; - } - } - - private function elementInScope($el, $table = false) { - if(is_array($el)) { - foreach($el as $element) { - if($this->elementInScope($element, $table)) { - return true; - } - } - - return false; - } - - $leng = count($this->stack); - - for($n = 0; $n < $leng; $n++) { - /* 1. Initialise node to be the current node (the bottommost node of - the stack). */ - $node = $this->stack[$leng - 1 - $n]; - - if($node->tagName === $el) { - /* 2. If node is the target node, terminate in a match state. */ - return true; - - } elseif($node->tagName === 'table') { - /* 3. Otherwise, if node is a table element, terminate in a failure - state. */ - return false; - - } elseif($table === true && in_array($node->tagName, array('caption', 'td', - 'th', 'button', 'marquee', 'object'))) { - /* 4. Otherwise, if the algorithm is the "has an element in scope" - variant (rather than the "has an element in table scope" variant), - and node is one of the following, terminate in a failure state. */ - return false; - - } elseif($node === $node->ownerDocument->documentElement) { - /* 5. Otherwise, if node is an html element (root element), terminate - in a failure state. (This can only happen if the node is the topmost - node of the stack of open elements, and prevents the next step from - being invoked if there are no more elements in the stack.) */ - return false; - } - - /* Otherwise, set node to the previous entry in the stack of open - elements and return to step 2. (This will never fail, since the loop - will always terminate in the previous step if the top of the stack - is reached.) */ - } - } - - private function reconstructActiveFormattingElements() { - /* 1. If there are no entries in the list of active formatting elements, - then there is nothing to reconstruct; stop this algorithm. */ - $formatting_elements = count($this->a_formatting); - - if($formatting_elements === 0) { - return false; - } - - /* 3. Let entry be the last (most recently added) element in the list - of active formatting elements. */ - $entry = end($this->a_formatting); - - /* 2. If the last (most recently added) entry in the list of active - formatting elements is a marker, or if it is an element that is in the - stack of open elements, then there is nothing to reconstruct; stop this - algorithm. */ - if($entry === self::MARKER || in_array($entry, $this->stack, true)) { - return false; - } - - for($a = $formatting_elements - 1; $a >= 0; true) { - /* 4. If there are no entries before entry in the list of active - formatting elements, then jump to step 8. */ - if($a === 0) { - $step_seven = false; - break; - } - - /* 5. Let entry be the entry one earlier than entry in the list of - active formatting elements. */ - $a--; - $entry = $this->a_formatting[$a]; - - /* 6. If entry is neither a marker nor an element that is also in - thetack of open elements, go to step 4. */ - if($entry === self::MARKER || in_array($entry, $this->stack, true)) { - break; - } - } - - while(true) { - /* 7. Let entry be the element one later than entry in the list of - active formatting elements. */ - if(isset($step_seven) && $step_seven === true) { - $a++; - $entry = $this->a_formatting[$a]; - } - - /* 8. Perform a shallow clone of the element entry to obtain clone. */ - $clone = $entry->cloneNode(); - - /* 9. Append clone to the current node and push it onto the stack - of open elements so that it is the new current node. */ - end($this->stack)->appendChild($clone); - $this->stack[] = $clone; - - /* 10. Replace the entry for entry in the list with an entry for - clone. */ - $this->a_formatting[$a] = $clone; - - /* 11. If the entry for clone in the list of active formatting - elements is not the last entry in the list, return to step 7. */ - if(end($this->a_formatting) !== $clone) { - $step_seven = true; - } else { - break; - } - } - } - - private function clearTheActiveFormattingElementsUpToTheLastMarker() { - /* When the steps below require the UA to clear the list of active - formatting elements up to the last marker, the UA must perform the - following steps: */ - - while(true) { - /* 1. Let entry be the last (most recently added) entry in the list - of active formatting elements. */ - $entry = end($this->a_formatting); - - /* 2. Remove entry from the list of active formatting elements. */ - array_pop($this->a_formatting); - - /* 3. If entry was a marker, then stop the algorithm at this point. - The list has been cleared up to the last marker. */ - if($entry === self::MARKER) { - break; - } - } - } - - private function generateImpliedEndTags($exclude = array()) { - /* When the steps below require the UA to generate implied end tags, - then, if the current node is a dd element, a dt element, an li element, - a p element, a td element, a th element, or a tr element, the UA must - act as if an end tag with the respective tag name had been seen and - then generate implied end tags again. */ - $node = end($this->stack); - $elements = array_diff(array('dd', 'dt', 'li', 'p', 'td', 'th', 'tr'), $exclude); - - while(in_array(end($this->stack)->nodeName, $elements)) { - array_pop($this->stack); - } - } - - private function getElementCategory($node) { - $name = $node->tagName; - if(in_array($name, $this->special)) - return self::SPECIAL; - - elseif(in_array($name, $this->scoping)) - return self::SCOPING; - - elseif(in_array($name, $this->formatting)) - return self::FORMATTING; - - else - return self::PHRASING; - } - - private function clearStackToTableContext($elements) { - /* When the steps above require the UA to clear the stack back to a - table context, it means that the UA must, while the current node is not - a table element or an html element, pop elements from the stack of open - elements. If this causes any elements to be popped from the stack, then - this is a parse error. */ - while(true) { - $node = end($this->stack)->nodeName; - - if(in_array($node, $elements)) { - break; - } else { - array_pop($this->stack); - } - } - } - - private function resetInsertionMode() { - /* 1. Let last be false. */ - $last = false; - $leng = count($this->stack); - - for($n = $leng - 1; $n >= 0; $n--) { - /* 2. Let node be the last node in the stack of open elements. */ - $node = $this->stack[$n]; - - /* 3. If node is the first node in the stack of open elements, then - set last to true. If the element whose innerHTML attribute is being - set is neither a td element nor a th element, then set node to the - element whose innerHTML attribute is being set. (innerHTML case) */ - if($this->stack[0]->isSameNode($node)) { - $last = true; - } - - /* 4. If node is a select element, then switch the insertion mode to - "in select" and abort these steps. (innerHTML case) */ - if($node->nodeName === 'select') { - $this->mode = self::IN_SELECT; - break; - - /* 5. If node is a td or th element, then switch the insertion mode - to "in cell" and abort these steps. */ - } elseif($node->nodeName === 'td' || $node->nodeName === 'th') { - $this->mode = self::IN_CELL; - break; - - /* 6. If node is a tr element, then switch the insertion mode to - "in row" and abort these steps. */ - } elseif($node->nodeName === 'tr') { - $this->mode = self::IN_ROW; - break; - - /* 7. If node is a tbody, thead, or tfoot element, then switch the - insertion mode to "in table body" and abort these steps. */ - } elseif(in_array($node->nodeName, array('tbody', 'thead', 'tfoot'))) { - $this->mode = self::IN_TBODY; - break; - - /* 8. If node is a caption element, then switch the insertion mode - to "in caption" and abort these steps. */ - } elseif($node->nodeName === 'caption') { - $this->mode = self::IN_CAPTION; - break; - - /* 9. If node is a colgroup element, then switch the insertion mode - to "in column group" and abort these steps. (innerHTML case) */ - } elseif($node->nodeName === 'colgroup') { - $this->mode = self::IN_CGROUP; - break; - - /* 10. If node is a table element, then switch the insertion mode - to "in table" and abort these steps. */ - } elseif($node->nodeName === 'table') { - $this->mode = self::IN_TABLE; - break; - - /* 11. If node is a head element, then switch the insertion mode - to "in body" ("in body"! not "in head"!) and abort these steps. - (innerHTML case) */ - } elseif($node->nodeName === 'head') { - $this->mode = self::IN_BODY; - break; - - /* 12. If node is a body element, then switch the insertion mode to - "in body" and abort these steps. */ - } elseif($node->nodeName === 'body') { - $this->mode = self::IN_BODY; - break; - - /* 13. If node is a frameset element, then switch the insertion - mode to "in frameset" and abort these steps. (innerHTML case) */ - } elseif($node->nodeName === 'frameset') { - $this->mode = self::IN_FRAME; - break; - - /* 14. If node is an html element, then: if the head element - pointer is null, switch the insertion mode to "before head", - otherwise, switch the insertion mode to "after head". In either - case, abort these steps. (innerHTML case) */ - } elseif($node->nodeName === 'html') { - $this->mode = ($this->head_pointer === null) - ? self::BEFOR_HEAD - : self::AFTER_HEAD; - - break; - - /* 15. If last is true, then set the insertion mode to "in body" - and abort these steps. (innerHTML case) */ - } elseif($last) { - $this->mode = self::IN_BODY; - break; - } - } - } - - private function closeCell() { - /* If the stack of open elements has a td or th element in table scope, - then act as if an end tag token with that tag name had been seen. */ - foreach(array('td', 'th') as $cell) { - if($this->elementInScope($cell, true)) { - $this->inCell(array( - 'name' => $cell, - 'type' => HTML5::ENDTAG - )); - - break; - } - } - } - - public function save() { - return $this->dom; - } -} -?> +normalize($html, $config, $context); + $new_html = $this->wrapHTML($new_html, $config, $context); + try { + $parser = new HTML5($new_html); + $doc = $parser->save(); + } catch (DOMException $e) { + // Uh oh, it failed. Punt to DirectLex. + $lexer = new HTMLPurifier_Lexer_DirectLex(); + $context->register('PH5PError', $e); // save the error, so we can detect it + return $lexer->tokenizeHTML($html, $config, $context); // use original HTML + } + $tokens = array(); + $this->tokenizeDOM( + $doc->getElementsByTagName('html')->item(0)-> // + getElementsByTagName('body')->item(0)-> // + getElementsByTagName('div')->item(0) //
          + , $tokens); + return $tokens; + } + +} + +/* + +Copyright 2007 Jeroen van der Meer + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +class HTML5 { + private $data; + private $char; + private $EOF; + private $state; + private $tree; + private $token; + private $content_model; + private $escape = false; + private $entities = array('AElig;','AElig','AMP;','AMP','Aacute;','Aacute', + 'Acirc;','Acirc','Agrave;','Agrave','Alpha;','Aring;','Aring','Atilde;', + 'Atilde','Auml;','Auml','Beta;','COPY;','COPY','Ccedil;','Ccedil','Chi;', + 'Dagger;','Delta;','ETH;','ETH','Eacute;','Eacute','Ecirc;','Ecirc','Egrave;', + 'Egrave','Epsilon;','Eta;','Euml;','Euml','GT;','GT','Gamma;','Iacute;', + 'Iacute','Icirc;','Icirc','Igrave;','Igrave','Iota;','Iuml;','Iuml','Kappa;', + 'LT;','LT','Lambda;','Mu;','Ntilde;','Ntilde','Nu;','OElig;','Oacute;', + 'Oacute','Ocirc;','Ocirc','Ograve;','Ograve','Omega;','Omicron;','Oslash;', + 'Oslash','Otilde;','Otilde','Ouml;','Ouml','Phi;','Pi;','Prime;','Psi;', + 'QUOT;','QUOT','REG;','REG','Rho;','Scaron;','Sigma;','THORN;','THORN', + 'TRADE;','Tau;','Theta;','Uacute;','Uacute','Ucirc;','Ucirc','Ugrave;', + 'Ugrave','Upsilon;','Uuml;','Uuml','Xi;','Yacute;','Yacute','Yuml;','Zeta;', + 'aacute;','aacute','acirc;','acirc','acute;','acute','aelig;','aelig', + 'agrave;','agrave','alefsym;','alpha;','amp;','amp','and;','ang;','apos;', + 'aring;','aring','asymp;','atilde;','atilde','auml;','auml','bdquo;','beta;', + 'brvbar;','brvbar','bull;','cap;','ccedil;','ccedil','cedil;','cedil', + 'cent;','cent','chi;','circ;','clubs;','cong;','copy;','copy','crarr;', + 'cup;','curren;','curren','dArr;','dagger;','darr;','deg;','deg','delta;', + 'diams;','divide;','divide','eacute;','eacute','ecirc;','ecirc','egrave;', + 'egrave','empty;','emsp;','ensp;','epsilon;','equiv;','eta;','eth;','eth', + 'euml;','euml','euro;','exist;','fnof;','forall;','frac12;','frac12', + 'frac14;','frac14','frac34;','frac34','frasl;','gamma;','ge;','gt;','gt', + 'hArr;','harr;','hearts;','hellip;','iacute;','iacute','icirc;','icirc', + 'iexcl;','iexcl','igrave;','igrave','image;','infin;','int;','iota;', + 'iquest;','iquest','isin;','iuml;','iuml','kappa;','lArr;','lambda;','lang;', + 'laquo;','laquo','larr;','lceil;','ldquo;','le;','lfloor;','lowast;','loz;', + 'lrm;','lsaquo;','lsquo;','lt;','lt','macr;','macr','mdash;','micro;','micro', + 'middot;','middot','minus;','mu;','nabla;','nbsp;','nbsp','ndash;','ne;', + 'ni;','not;','not','notin;','nsub;','ntilde;','ntilde','nu;','oacute;', + 'oacute','ocirc;','ocirc','oelig;','ograve;','ograve','oline;','omega;', + 'omicron;','oplus;','or;','ordf;','ordf','ordm;','ordm','oslash;','oslash', + 'otilde;','otilde','otimes;','ouml;','ouml','para;','para','part;','permil;', + 'perp;','phi;','pi;','piv;','plusmn;','plusmn','pound;','pound','prime;', + 'prod;','prop;','psi;','quot;','quot','rArr;','radic;','rang;','raquo;', + 'raquo','rarr;','rceil;','rdquo;','real;','reg;','reg','rfloor;','rho;', + 'rlm;','rsaquo;','rsquo;','sbquo;','scaron;','sdot;','sect;','sect','shy;', + 'shy','sigma;','sigmaf;','sim;','spades;','sub;','sube;','sum;','sup1;', + 'sup1','sup2;','sup2','sup3;','sup3','sup;','supe;','szlig;','szlig','tau;', + 'there4;','theta;','thetasym;','thinsp;','thorn;','thorn','tilde;','times;', + 'times','trade;','uArr;','uacute;','uacute','uarr;','ucirc;','ucirc', + 'ugrave;','ugrave','uml;','uml','upsih;','upsilon;','uuml;','uuml','weierp;', + 'xi;','yacute;','yacute','yen;','yen','yuml;','yuml','zeta;','zwj;','zwnj;'); + + const PCDATA = 0; + const RCDATA = 1; + const CDATA = 2; + const PLAINTEXT = 3; + + const DOCTYPE = 0; + const STARTTAG = 1; + const ENDTAG = 2; + const COMMENT = 3; + const CHARACTR = 4; + const EOF = 5; + + public function __construct($data) { + + $this->data = $data; + $this->char = -1; + $this->EOF = strlen($data); + $this->tree = new HTML5TreeConstructer; + $this->content_model = self::PCDATA; + + $this->state = 'data'; + + while($this->state !== null) { + $this->{$this->state.'State'}(); + } + } + + public function save() { + return $this->tree->save(); + } + + private function char() { + return ($this->char < $this->EOF) + ? $this->data[$this->char] + : false; + } + + private function character($s, $l = 0) { + if($s + $l < $this->EOF) { + if($l === 0) { + return $this->data[$s]; + } else { + return substr($this->data, $s, $l); + } + } + } + + private function characters($char_class, $start) { + return preg_replace('#^(['.$char_class.']+).*#s', '\\1', substr($this->data, $start)); + } + + private function dataState() { + // Consume the next input character + $this->char++; + $char = $this->char(); + + if($char === '&' && ($this->content_model === self::PCDATA || $this->content_model === self::RCDATA)) { + /* U+0026 AMPERSAND (&) + When the content model flag is set to one of the PCDATA or RCDATA + states: switch to the entity data state. Otherwise: treat it as per + the "anything else" entry below. */ + $this->state = 'entityData'; + + } elseif($char === '-') { + /* If the content model flag is set to either the RCDATA state or + the CDATA state, and the escape flag is false, and there are at + least three characters before this one in the input stream, and the + last four characters in the input stream, including this one, are + U+003C LESS-THAN SIGN, U+0021 EXCLAMATION MARK, U+002D HYPHEN-MINUS, + and U+002D HYPHEN-MINUS (""), + set the escape flag to false. */ + if(($this->content_model === self::RCDATA || + $this->content_model === self::CDATA) && $this->escape === true && + $this->character($this->char, 3) === '-->') { + $this->escape = false; + } + + /* In any case, emit the input character as a character token. + Stay in the data state. */ + $this->emitToken(array( + 'type' => self::CHARACTR, + 'data' => $char + )); + + } elseif($this->char === $this->EOF) { + /* EOF + Emit an end-of-file token. */ + $this->EOF(); + + } elseif($this->content_model === self::PLAINTEXT) { + /* When the content model flag is set to the PLAINTEXT state + THIS DIFFERS GREATLY FROM THE SPEC: Get the remaining characters of + the text and emit it as a character token. */ + $this->emitToken(array( + 'type' => self::CHARACTR, + 'data' => substr($this->data, $this->char) + )); + + $this->EOF(); + + } else { + /* Anything else + THIS DIFFERS GREATLY FROM THE SPEC: Get as many character that + otherwise would also be treated as a character token and emit it + as a single character token. Stay in the data state. */ + $len = strcspn($this->data, '<&', $this->char); + $char = substr($this->data, $this->char, $len); + $this->char += $len - 1; + + $this->emitToken(array( + 'type' => self::CHARACTR, + 'data' => $char + )); + + $this->state = 'data'; + } + } + + private function entityDataState() { + // Attempt to consume an entity. + $entity = $this->entity(); + + // If nothing is returned, emit a U+0026 AMPERSAND character token. + // Otherwise, emit the character token that was returned. + $char = (!$entity) ? '&' : $entity; + $this->emitToken(array( + 'type' => self::CHARACTR, + 'data' => $char + )); + + // Finally, switch to the data state. + $this->state = 'data'; + } + + private function tagOpenState() { + switch($this->content_model) { + case self::RCDATA: + case self::CDATA: + /* If the next input character is a U+002F SOLIDUS (/) character, + consume it and switch to the close tag open state. If the next + input character is not a U+002F SOLIDUS (/) character, emit a + U+003C LESS-THAN SIGN character token and switch to the data + state to process the next input character. */ + if($this->character($this->char + 1) === '/') { + $this->char++; + $this->state = 'closeTagOpen'; + + } else { + $this->emitToken(array( + 'type' => self::CHARACTR, + 'data' => '<' + )); + + $this->state = 'data'; + } + break; + + case self::PCDATA: + // If the content model flag is set to the PCDATA state + // Consume the next input character: + $this->char++; + $char = $this->char(); + + if($char === '!') { + /* U+0021 EXCLAMATION MARK (!) + Switch to the markup declaration open state. */ + $this->state = 'markupDeclarationOpen'; + + } elseif($char === '/') { + /* U+002F SOLIDUS (/) + Switch to the close tag open state. */ + $this->state = 'closeTagOpen'; + + } elseif(preg_match('/^[A-Za-z]$/', $char)) { + /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z + Create a new start tag token, set its tag name to the lowercase + version of the input character (add 0x0020 to the character's code + point), then switch to the tag name state. (Don't emit the token + yet; further details will be filled in before it is emitted.) */ + $this->token = array( + 'name' => strtolower($char), + 'type' => self::STARTTAG, + 'attr' => array() + ); + + $this->state = 'tagName'; + + } elseif($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Parse error. Emit a U+003C LESS-THAN SIGN character token and a + U+003E GREATER-THAN SIGN character token. Switch to the data state. */ + $this->emitToken(array( + 'type' => self::CHARACTR, + 'data' => '<>' + )); + + $this->state = 'data'; + + } elseif($char === '?') { + /* U+003F QUESTION MARK (?) + Parse error. Switch to the bogus comment state. */ + $this->state = 'bogusComment'; + + } else { + /* Anything else + Parse error. Emit a U+003C LESS-THAN SIGN character token and + reconsume the current input character in the data state. */ + $this->emitToken(array( + 'type' => self::CHARACTR, + 'data' => '<' + )); + + $this->char--; + $this->state = 'data'; + } + break; + } + } + + private function closeTagOpenState() { + $next_node = strtolower($this->characters('A-Za-z', $this->char + 1)); + $the_same = count($this->tree->stack) > 0 && $next_node === end($this->tree->stack)->nodeName; + + if(($this->content_model === self::RCDATA || $this->content_model === self::CDATA) && + (!$the_same || ($the_same && (!preg_match('/[\t\n\x0b\x0c >\/]/', + $this->character($this->char + 1 + strlen($next_node))) || $this->EOF === $this->char)))) { + /* If the content model flag is set to the RCDATA or CDATA states then + examine the next few characters. If they do not match the tag name of + the last start tag token emitted (case insensitively), or if they do but + they are not immediately followed by one of the following characters: + * U+0009 CHARACTER TABULATION + * U+000A LINE FEED (LF) + * U+000B LINE TABULATION + * U+000C FORM FEED (FF) + * U+0020 SPACE + * U+003E GREATER-THAN SIGN (>) + * U+002F SOLIDUS (/) + * EOF + ...then there is a parse error. Emit a U+003C LESS-THAN SIGN character + token, a U+002F SOLIDUS character token, and switch to the data state + to process the next input character. */ + $this->emitToken(array( + 'type' => self::CHARACTR, + 'data' => 'state = 'data'; + + } else { + /* Otherwise, if the content model flag is set to the PCDATA state, + or if the next few characters do match that tag name, consume the + next input character: */ + $this->char++; + $char = $this->char(); + + if(preg_match('/^[A-Za-z]$/', $char)) { + /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z + Create a new end tag token, set its tag name to the lowercase version + of the input character (add 0x0020 to the character's code point), then + switch to the tag name state. (Don't emit the token yet; further details + will be filled in before it is emitted.) */ + $this->token = array( + 'name' => strtolower($char), + 'type' => self::ENDTAG + ); + + $this->state = 'tagName'; + + } elseif($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Parse error. Switch to the data state. */ + $this->state = 'data'; + + } elseif($this->char === $this->EOF) { + /* EOF + Parse error. Emit a U+003C LESS-THAN SIGN character token and a U+002F + SOLIDUS character token. Reconsume the EOF character in the data state. */ + $this->emitToken(array( + 'type' => self::CHARACTR, + 'data' => 'char--; + $this->state = 'data'; + + } else { + /* Parse error. Switch to the bogus comment state. */ + $this->state = 'bogusComment'; + } + } + } + + private function tagNameState() { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } elseif($char === '/') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Switch to the before + attribute name state. */ + $this->state = 'beforeAttributeName'; + + } else { + /* Anything else + Append the current input character to the current tag token's tag name. + Stay in the tag name state. */ + $this->token['name'] .= strtolower($char); + $this->state = 'tagName'; + } + } + + private function beforeAttributeNameState() { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif($char === '/') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Stay in the before + attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Start a new attribute in the current tag token. Set that attribute's + name to the current input character, and its value to the empty string. + Switch to the attribute name state. */ + $this->token['attr'][] = array( + 'name' => strtolower($char), + 'value' => null + ); + + $this->state = 'attributeName'; + } + } + + private function attributeNameState() { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the before attribute name state. */ + $this->state = 'afterAttributeName'; + + } elseif($char === '=') { + /* U+003D EQUALS SIGN (=) + Switch to the before attribute value state. */ + $this->state = 'beforeAttributeValue'; + + } elseif($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif($char === '/' && $this->character($this->char + 1) !== '>') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Switch to the before + attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's name. + Stay in the attribute name state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['name'] .= strtolower($char); + + $this->state = 'attributeName'; + } + } + + private function afterAttributeNameState() { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the after attribute name state. */ + $this->state = 'afterAttributeName'; + + } elseif($char === '=') { + /* U+003D EQUALS SIGN (=) + Switch to the before attribute value state. */ + $this->state = 'beforeAttributeValue'; + + } elseif($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif($char === '/' && $this->character($this->char + 1) !== '>') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Switch to the + before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Start a new attribute in the current tag token. Set that attribute's + name to the current input character, and its value to the empty string. + Switch to the attribute name state. */ + $this->token['attr'][] = array( + 'name' => strtolower($char), + 'value' => null + ); + + $this->state = 'attributeName'; + } + } + + private function beforeAttributeValueState() { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the before attribute value state. */ + $this->state = 'beforeAttributeValue'; + + } elseif($char === '"') { + /* U+0022 QUOTATION MARK (") + Switch to the attribute value (double-quoted) state. */ + $this->state = 'attributeValueDoubleQuoted'; + + } elseif($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the attribute value (unquoted) state and reconsume + this input character. */ + $this->char--; + $this->state = 'attributeValueUnquoted'; + + } elseif($char === '\'') { + /* U+0027 APOSTROPHE (') + Switch to the attribute value (single-quoted) state. */ + $this->state = 'attributeValueSingleQuoted'; + + } elseif($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Switch to the attribute value (unquoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueUnquoted'; + } + } + + private function attributeValueDoubleQuotedState() { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if($char === '"') { + /* U+0022 QUOTATION MARK (") + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the entity in attribute value state. */ + $this->entityInAttributeValueState('double'); + + } elseif($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the character + in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Stay in the attribute value (double-quoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueDoubleQuoted'; + } + } + + private function attributeValueSingleQuotedState() { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if($char === '\'') { + /* U+0022 QUOTATION MARK (') + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the entity in attribute value state. */ + $this->entityInAttributeValueState('single'); + + } elseif($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the character + in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Stay in the attribute value (single-quoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueSingleQuoted'; + } + } + + private function attributeValueUnquotedState() { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the entity in attribute value state. */ + $this->entityInAttributeValueState(); + + } elseif($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Stay in the attribute value (unquoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueUnquoted'; + } + } + + private function entityInAttributeValueState() { + // Attempt to consume an entity. + $entity = $this->entity(); + + // If nothing is returned, append a U+0026 AMPERSAND character to the + // current attribute's value. Otherwise, emit the character token that + // was returned. + $char = (!$entity) + ? '&' + : $entity; + + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + } + + private function bogusCommentState() { + /* Consume every character up to the first U+003E GREATER-THAN SIGN + character (>) or the end of the file (EOF), whichever comes first. Emit + a comment token whose data is the concatenation of all the characters + starting from and including the character that caused the state machine + to switch into the bogus comment state, up to and including the last + consumed character before the U+003E character, if any, or up to the + end of the file otherwise. (If the comment was started by the end of + the file (EOF), the token is empty.) */ + $data = $this->characters('^>', $this->char); + $this->emitToken(array( + 'data' => $data, + 'type' => self::COMMENT + )); + + $this->char += strlen($data); + + /* Switch to the data state. */ + $this->state = 'data'; + + /* If the end of the file was reached, reconsume the EOF character. */ + if($this->char === $this->EOF) { + $this->char = $this->EOF - 1; + } + } + + private function markupDeclarationOpenState() { + /* If the next two characters are both U+002D HYPHEN-MINUS (-) + characters, consume those two characters, create a comment token whose + data is the empty string, and switch to the comment state. */ + if($this->character($this->char + 1, 2) === '--') { + $this->char += 2; + $this->state = 'comment'; + $this->token = array( + 'data' => null, + 'type' => self::COMMENT + ); + + /* Otherwise if the next seven chacacters are a case-insensitive match + for the word "DOCTYPE", then consume those characters and switch to the + DOCTYPE state. */ + } elseif(strtolower($this->character($this->char + 1, 7)) === 'doctype') { + $this->char += 7; + $this->state = 'doctype'; + + /* Otherwise, is is a parse error. Switch to the bogus comment state. + The next character that is consumed, if any, is the first character + that will be in the comment. */ + } else { + $this->char++; + $this->state = 'bogusComment'; + } + } + + private function commentState() { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + /* U+002D HYPHEN-MINUS (-) */ + if($char === '-') { + /* Switch to the comment dash state */ + $this->state = 'commentDash'; + + /* EOF */ + } elseif($this->char === $this->EOF) { + /* Parse error. Emit the comment token. Reconsume the EOF character + in the data state. */ + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + /* Anything else */ + } else { + /* Append the input character to the comment token's data. Stay in + the comment state. */ + $this->token['data'] .= $char; + } + } + + private function commentDashState() { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + /* U+002D HYPHEN-MINUS (-) */ + if($char === '-') { + /* Switch to the comment end state */ + $this->state = 'commentEnd'; + + /* EOF */ + } elseif($this->char === $this->EOF) { + /* Parse error. Emit the comment token. Reconsume the EOF character + in the data state. */ + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + /* Anything else */ + } else { + /* Append a U+002D HYPHEN-MINUS (-) character and the input + character to the comment token's data. Switch to the comment state. */ + $this->token['data'] .= '-'.$char; + $this->state = 'comment'; + } + } + + private function commentEndState() { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif($char === '-') { + $this->token['data'] .= '-'; + + } elseif($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + $this->token['data'] .= '--'.$char; + $this->state = 'comment'; + } + } + + private function doctypeState() { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + $this->state = 'beforeDoctypeName'; + + } else { + $this->char--; + $this->state = 'beforeDoctypeName'; + } + } + + private function beforeDoctypeNameState() { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + // Stay in the before DOCTYPE name state. + + } elseif(preg_match('/^[a-z]$/', $char)) { + $this->token = array( + 'name' => strtoupper($char), + 'type' => self::DOCTYPE, + 'error' => true + ); + + $this->state = 'doctypeName'; + + } elseif($char === '>') { + $this->emitToken(array( + 'name' => null, + 'type' => self::DOCTYPE, + 'error' => true + )); + + $this->state = 'data'; + + } elseif($this->char === $this->EOF) { + $this->emitToken(array( + 'name' => null, + 'type' => self::DOCTYPE, + 'error' => true + )); + + $this->char--; + $this->state = 'data'; + + } else { + $this->token = array( + 'name' => $char, + 'type' => self::DOCTYPE, + 'error' => true + ); + + $this->state = 'doctypeName'; + } + } + + private function doctypeNameState() { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + $this->state = 'AfterDoctypeName'; + + } elseif($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif(preg_match('/^[a-z]$/', $char)) { + $this->token['name'] .= strtoupper($char); + + } elseif($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + $this->token['name'] .= $char; + } + + $this->token['error'] = ($this->token['name'] === 'HTML') + ? false + : true; + } + + private function afterDoctypeNameState() { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + // Stay in the DOCTYPE name state. + + } elseif($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + $this->token['error'] = true; + $this->state = 'bogusDoctype'; + } + } + + private function bogusDoctypeState() { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + // Stay in the bogus DOCTYPE state. + } + } + + private function entity() { + $start = $this->char; + + // This section defines how to consume an entity. This definition is + // used when parsing entities in text and in attributes. + + // The behaviour depends on the identity of the next character (the + // one immediately after the U+0026 AMPERSAND character): + + switch($this->character($this->char + 1)) { + // U+0023 NUMBER SIGN (#) + case '#': + + // The behaviour further depends on the character after the + // U+0023 NUMBER SIGN: + switch($this->character($this->char + 1)) { + // U+0078 LATIN SMALL LETTER X + // U+0058 LATIN CAPITAL LETTER X + case 'x': + case 'X': + // Follow the steps below, but using the range of + // characters U+0030 DIGIT ZERO through to U+0039 DIGIT + // NINE, U+0061 LATIN SMALL LETTER A through to U+0066 + // LATIN SMALL LETTER F, and U+0041 LATIN CAPITAL LETTER + // A, through to U+0046 LATIN CAPITAL LETTER F (in other + // words, 0-9, A-F, a-f). + $char = 1; + $char_class = '0-9A-Fa-f'; + break; + + // Anything else + default: + // Follow the steps below, but using the range of + // characters U+0030 DIGIT ZERO through to U+0039 DIGIT + // NINE (i.e. just 0-9). + $char = 0; + $char_class = '0-9'; + break; + } + + // Consume as many characters as match the range of characters + // given above. + $this->char++; + $e_name = $this->characters($char_class, $this->char + $char + 1); + $entity = $this->character($start, $this->char); + $cond = strlen($e_name) > 0; + + // The rest of the parsing happens bellow. + break; + + // Anything else + default: + // Consume the maximum number of characters possible, with the + // consumed characters case-sensitively matching one of the + // identifiers in the first column of the entities table. + $e_name = $this->characters('0-9A-Za-z;', $this->char + 1); + $len = strlen($e_name); + + for($c = 1; $c <= $len; $c++) { + $id = substr($e_name, 0, $c); + $this->char++; + + if(in_array($id, $this->entities)) { + if ($e_name[$c-1] !== ';') { + if ($c < $len && $e_name[$c] == ';') { + $this->char++; // consume extra semicolon + } + } + $entity = $id; + break; + } + } + + $cond = isset($entity); + // The rest of the parsing happens bellow. + break; + } + + if(!$cond) { + // If no match can be made, then this is a parse error. No + // characters are consumed, and nothing is returned. + $this->char = $start; + return false; + } + + // Return a character token for the character corresponding to the + // entity name (as given by the second column of the entities table). + return html_entity_decode('&'.$entity.';', ENT_QUOTES, 'UTF-8'); + } + + private function emitToken($token) { + $emit = $this->tree->emitToken($token); + + if(is_int($emit)) { + $this->content_model = $emit; + + } elseif($token['type'] === self::ENDTAG) { + $this->content_model = self::PCDATA; + } + } + + private function EOF() { + $this->state = null; + $this->tree->emitToken(array( + 'type' => self::EOF + )); + } +} + +class HTML5TreeConstructer { + public $stack = array(); + + private $phase; + private $mode; + private $dom; + private $foster_parent = null; + private $a_formatting = array(); + + private $head_pointer = null; + private $form_pointer = null; + + private $scoping = array('button','caption','html','marquee','object','table','td','th'); + private $formatting = array('a','b','big','em','font','i','nobr','s','small','strike','strong','tt','u'); + private $special = array('address','area','base','basefont','bgsound', + 'blockquote','body','br','center','col','colgroup','dd','dir','div','dl', + 'dt','embed','fieldset','form','frame','frameset','h1','h2','h3','h4','h5', + 'h6','head','hr','iframe','image','img','input','isindex','li','link', + 'listing','menu','meta','noembed','noframes','noscript','ol','optgroup', + 'option','p','param','plaintext','pre','script','select','spacer','style', + 'tbody','textarea','tfoot','thead','title','tr','ul','wbr'); + + // The different phases. + const INIT_PHASE = 0; + const ROOT_PHASE = 1; + const MAIN_PHASE = 2; + const END_PHASE = 3; + + // The different insertion modes for the main phase. + const BEFOR_HEAD = 0; + const IN_HEAD = 1; + const AFTER_HEAD = 2; + const IN_BODY = 3; + const IN_TABLE = 4; + const IN_CAPTION = 5; + const IN_CGROUP = 6; + const IN_TBODY = 7; + const IN_ROW = 8; + const IN_CELL = 9; + const IN_SELECT = 10; + const AFTER_BODY = 11; + const IN_FRAME = 12; + const AFTR_FRAME = 13; + + // The different types of elements. + const SPECIAL = 0; + const SCOPING = 1; + const FORMATTING = 2; + const PHRASING = 3; + + const MARKER = 0; + + public function __construct() { + $this->phase = self::INIT_PHASE; + $this->mode = self::BEFOR_HEAD; + $this->dom = new DOMDocument; + + $this->dom->encoding = 'UTF-8'; + $this->dom->preserveWhiteSpace = true; + $this->dom->substituteEntities = true; + $this->dom->strictErrorChecking = false; + } + + // Process tag tokens + public function emitToken($token) { + switch($this->phase) { + case self::INIT_PHASE: return $this->initPhase($token); break; + case self::ROOT_PHASE: return $this->rootElementPhase($token); break; + case self::MAIN_PHASE: return $this->mainPhase($token); break; + case self::END_PHASE : return $this->trailingEndPhase($token); break; + } + } + + private function initPhase($token) { + /* Initially, the tree construction stage must handle each token + emitted from the tokenisation stage as follows: */ + + /* A DOCTYPE token that is marked as being in error + A comment token + A start tag token + An end tag token + A character token that is not one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE + An end-of-file token */ + if((isset($token['error']) && $token['error']) || + $token['type'] === HTML5::COMMENT || + $token['type'] === HTML5::STARTTAG || + $token['type'] === HTML5::ENDTAG || + $token['type'] === HTML5::EOF || + ($token['type'] === HTML5::CHARACTR && isset($token['data']) && + !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']))) { + /* This specification does not define how to handle this case. In + particular, user agents may ignore the entirety of this specification + altogether for such documents, and instead invoke special parse modes + with a greater emphasis on backwards compatibility. */ + + $this->phase = self::ROOT_PHASE; + return $this->rootElementPhase($token); + + /* A DOCTYPE token marked as being correct */ + } elseif(isset($token['error']) && !$token['error']) { + /* Append a DocumentType node to the Document node, with the name + attribute set to the name given in the DOCTYPE token (which will be + "HTML"), and the other attributes specific to DocumentType objects + set to null, empty lists, or the empty string as appropriate. */ + $doctype = new DOMDocumentType(null, null, 'HTML'); + + /* Then, switch to the root element phase of the tree construction + stage. */ + $this->phase = self::ROOT_PHASE; + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + } elseif(isset($token['data']) && preg_match('/^[\t\n\x0b\x0c ]+$/', + $token['data'])) { + /* Append that character to the Document node. */ + $text = $this->dom->createTextNode($token['data']); + $this->dom->appendChild($text); + } + } + + private function rootElementPhase($token) { + /* After the initial phase, as each token is emitted from the tokenisation + stage, it must be processed as described in this section. */ + + /* A DOCTYPE token */ + if($token['type'] === HTML5::DOCTYPE) { + // Parse error. Ignore the token. + + /* A comment token */ + } elseif($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the Document object with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + $this->dom->appendChild($comment); + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + } elseif($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { + /* Append that character to the Document node. */ + $text = $this->dom->createTextNode($token['data']); + $this->dom->appendChild($text); + + /* A character token that is not one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED + (FF), or U+0020 SPACE + A start tag token + An end tag token + An end-of-file token */ + } elseif(($token['type'] === HTML5::CHARACTR && + !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || + $token['type'] === HTML5::STARTTAG || + $token['type'] === HTML5::ENDTAG || + $token['type'] === HTML5::EOF) { + /* Create an HTMLElement node with the tag name html, in the HTML + namespace. Append it to the Document object. Switch to the main + phase and reprocess the current token. */ + $html = $this->dom->createElement('html'); + $this->dom->appendChild($html); + $this->stack[] = $html; + + $this->phase = self::MAIN_PHASE; + return $this->mainPhase($token); + } + } + + private function mainPhase($token) { + /* Tokens in the main phase must be handled as follows: */ + + /* A DOCTYPE token */ + if($token['type'] === HTML5::DOCTYPE) { + // Parse error. Ignore the token. + + /* A start tag token with the tag name "html" */ + } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'html') { + /* If this start tag token was not the first start tag token, then + it is a parse error. */ + + /* For each attribute on the token, check to see if the attribute + is already present on the top element of the stack of open elements. + If it is not, add the attribute and its corresponding value to that + element. */ + foreach($token['attr'] as $attr) { + if(!$this->stack[0]->hasAttribute($attr['name'])) { + $this->stack[0]->setAttribute($attr['name'], $attr['value']); + } + } + + /* An end-of-file token */ + } elseif($token['type'] === HTML5::EOF) { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* Anything else. */ + } else { + /* Depends on the insertion mode: */ + switch($this->mode) { + case self::BEFOR_HEAD: return $this->beforeHead($token); break; + case self::IN_HEAD: return $this->inHead($token); break; + case self::AFTER_HEAD: return $this->afterHead($token); break; + case self::IN_BODY: return $this->inBody($token); break; + case self::IN_TABLE: return $this->inTable($token); break; + case self::IN_CAPTION: return $this->inCaption($token); break; + case self::IN_CGROUP: return $this->inColumnGroup($token); break; + case self::IN_TBODY: return $this->inTableBody($token); break; + case self::IN_ROW: return $this->inRow($token); break; + case self::IN_CELL: return $this->inCell($token); break; + case self::IN_SELECT: return $this->inSelect($token); break; + case self::AFTER_BODY: return $this->afterBody($token); break; + case self::IN_FRAME: return $this->inFrameset($token); break; + case self::AFTR_FRAME: return $this->afterFrameset($token); break; + case self::END_PHASE: return $this->trailingEndPhase($token); break; + } + } + } + + private function beforeHead($token) { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data attribute + set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag token with the tag name "head" */ + } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') { + /* Create an element for the token, append the new element to the + current node and push it onto the stack of open elements. */ + $element = $this->insertElement($token); + + /* Set the head element pointer to this new element node. */ + $this->head_pointer = $element; + + /* Change the insertion mode to "in head". */ + $this->mode = self::IN_HEAD; + + /* A start tag token whose tag name is one of: "base", "link", "meta", + "script", "style", "title". Or an end tag with the tag name "html". + Or a character token that is not one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE. Or any other start tag token */ + } elseif($token['type'] === HTML5::STARTTAG || + ($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') || + ($token['type'] === HTML5::CHARACTR && !preg_match('/^[\t\n\x0b\x0c ]$/', + $token['data']))) { + /* Act as if a start tag token with the tag name "head" and no + attributes had been seen, then reprocess the current token. */ + $this->beforeHead(array( + 'name' => 'head', + 'type' => HTML5::STARTTAG, + 'attr' => array() + )); + + return $this->inHead($token); + + /* Any other end tag */ + } elseif($token['type'] === HTML5::ENDTAG) { + /* Parse error. Ignore the token. */ + } + } + + private function inHead($token) { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE. + + THIS DIFFERS FROM THE SPEC: If the current node is either a title, style + or script element, append the character to the current node regardless + of its content. */ + if(($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || ( + $token['type'] === HTML5::CHARACTR && in_array(end($this->stack)->nodeName, + array('title', 'style', 'script')))) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data attribute + set to the data given in the comment token. */ + $this->insertComment($token['data']); + + } elseif($token['type'] === HTML5::ENDTAG && + in_array($token['name'], array('title', 'style', 'script'))) { + array_pop($this->stack); + return HTML5::PCDATA; + + /* A start tag with the tag name "title" */ + } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'title') { + /* Create an element for the token and append the new element to the + node pointed to by the head element pointer, or, if that is null + (innerHTML case), to the current node. */ + if($this->head_pointer !== null) { + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + + } else { + $element = $this->insertElement($token); + } + + /* Switch the tokeniser's content model flag to the RCDATA state. */ + return HTML5::RCDATA; + + /* A start tag with the tag name "style" */ + } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'style') { + /* Create an element for the token and append the new element to the + node pointed to by the head element pointer, or, if that is null + (innerHTML case), to the current node. */ + if($this->head_pointer !== null) { + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + + } else { + $this->insertElement($token); + } + + /* Switch the tokeniser's content model flag to the CDATA state. */ + return HTML5::CDATA; + + /* A start tag with the tag name "script" */ + } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'script') { + /* Create an element for the token. */ + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + + /* Switch the tokeniser's content model flag to the CDATA state. */ + return HTML5::CDATA; + + /* A start tag with the tag name "base", "link", or "meta" */ + } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], + array('base', 'link', 'meta'))) { + /* Create an element for the token and append the new element to the + node pointed to by the head element pointer, or, if that is null + (innerHTML case), to the current node. */ + if($this->head_pointer !== null) { + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + array_pop($this->stack); + + } else { + $this->insertElement($token); + } + + /* An end tag with the tag name "head" */ + } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'head') { + /* If the current node is a head element, pop the current node off + the stack of open elements. */ + if($this->head_pointer->isSameNode(end($this->stack))) { + array_pop($this->stack); + + /* Otherwise, this is a parse error. */ + } else { + // k + } + + /* Change the insertion mode to "after head". */ + $this->mode = self::AFTER_HEAD; + + /* A start tag with the tag name "head" or an end tag except "html". */ + } elseif(($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') || + ($token['type'] === HTML5::ENDTAG && $token['name'] !== 'html')) { + // Parse error. Ignore the token. + + /* Anything else */ + } else { + /* If the current node is a head element, act as if an end tag + token with the tag name "head" had been seen. */ + if($this->head_pointer->isSameNode(end($this->stack))) { + $this->inHead(array( + 'name' => 'head', + 'type' => HTML5::ENDTAG + )); + + /* Otherwise, change the insertion mode to "after head". */ + } else { + $this->mode = self::AFTER_HEAD; + } + + /* Then, reprocess the current token. */ + return $this->afterHead($token); + } + } + + private function afterHead($token) { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data attribute + set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag token with the tag name "body" */ + } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'body') { + /* Insert a body element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in body". */ + $this->mode = self::IN_BODY; + + /* A start tag token with the tag name "frameset" */ + } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'frameset') { + /* Insert a frameset element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in frameset". */ + $this->mode = self::IN_FRAME; + + /* A start tag token whose tag name is one of: "base", "link", "meta", + "script", "style", "title" */ + } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], + array('base', 'link', 'meta', 'script', 'style', 'title'))) { + /* Parse error. Switch the insertion mode back to "in head" and + reprocess the token. */ + $this->mode = self::IN_HEAD; + return $this->inHead($token); + + /* Anything else */ + } else { + /* Act as if a start tag token with the tag name "body" and no + attributes had been seen, and then reprocess the current token. */ + $this->afterHead(array( + 'name' => 'body', + 'type' => HTML5::STARTTAG, + 'attr' => array() + )); + + return $this->inBody($token); + } + } + + private function inBody($token) { + /* Handle the token as follows: */ + + switch($token['type']) { + /* A character token */ + case HTML5::CHARACTR: + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Append the token's character to the current node. */ + $this->insertText($token['data']); + break; + + /* A comment token */ + case HTML5::COMMENT: + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + break; + + case HTML5::STARTTAG: + switch($token['name']) { + /* A start tag token whose tag name is one of: "script", + "style" */ + case 'script': case 'style': + /* Process the token as if the insertion mode had been "in + head". */ + return $this->inHead($token); + break; + + /* A start tag token whose tag name is one of: "base", "link", + "meta", "title" */ + case 'base': case 'link': case 'meta': case 'title': + /* Parse error. Process the token as if the insertion mode + had been "in head". */ + return $this->inHead($token); + break; + + /* A start tag token with the tag name "body" */ + case 'body': + /* Parse error. If the second element on the stack of open + elements is not a body element, or, if the stack of open + elements has only one node on it, then ignore the token. + (innerHTML case) */ + if(count($this->stack) === 1 || $this->stack[1]->nodeName !== 'body') { + // Ignore + + /* Otherwise, for each attribute on the token, check to see + if the attribute is already present on the body element (the + second element) on the stack of open elements. If it is not, + add the attribute and its corresponding value to that + element. */ + } else { + foreach($token['attr'] as $attr) { + if(!$this->stack[1]->hasAttribute($attr['name'])) { + $this->stack[1]->setAttribute($attr['name'], $attr['value']); + } + } + } + break; + + /* A start tag whose tag name is one of: "address", + "blockquote", "center", "dir", "div", "dl", "fieldset", + "listing", "menu", "ol", "p", "ul" */ + case 'address': case 'blockquote': case 'center': case 'dir': + case 'div': case 'dl': case 'fieldset': case 'listing': + case 'menu': case 'ol': case 'p': case 'ul': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been + seen. */ + if($this->elementInScope('p')) { + $this->emitToken(array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + )); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + break; + + /* A start tag whose tag name is "form" */ + case 'form': + /* If the form element pointer is not null, ignore the + token with a parse error. */ + if($this->form_pointer !== null) { + // Ignore. + + /* Otherwise: */ + } else { + /* If the stack of open elements has a p element in + scope, then act as if an end tag with the tag name p + had been seen. */ + if($this->elementInScope('p')) { + $this->emitToken(array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + )); + } + + /* Insert an HTML element for the token, and set the + form element pointer to point to the element created. */ + $element = $this->insertElement($token); + $this->form_pointer = $element; + } + break; + + /* A start tag whose tag name is "li", "dd" or "dt" */ + case 'li': case 'dd': case 'dt': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been + seen. */ + if($this->elementInScope('p')) { + $this->emitToken(array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + )); + } + + $stack_length = count($this->stack) - 1; + + for($n = $stack_length; 0 <= $n; $n--) { + /* 1. Initialise node to be the current node (the + bottommost node of the stack). */ + $stop = false; + $node = $this->stack[$n]; + $cat = $this->getElementCategory($node->tagName); + + /* 2. If node is an li, dd or dt element, then pop all + the nodes from the current node up to node, including + node, then stop this algorithm. */ + if($token['name'] === $node->tagName || ($token['name'] !== 'li' + && ($node->tagName === 'dd' || $node->tagName === 'dt'))) { + for($x = $stack_length; $x >= $n ; $x--) { + array_pop($this->stack); + } + + break; + } + + /* 3. If node is not in the formatting category, and is + not in the phrasing category, and is not an address or + div element, then stop this algorithm. */ + if($cat !== self::FORMATTING && $cat !== self::PHRASING && + $node->tagName !== 'address' && $node->tagName !== 'div') { + break; + } + } + + /* Finally, insert an HTML element with the same tag + name as the token's. */ + $this->insertElement($token); + break; + + /* A start tag token whose tag name is "plaintext" */ + case 'plaintext': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been + seen. */ + if($this->elementInScope('p')) { + $this->emitToken(array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + )); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + return HTML5::PLAINTEXT; + break; + + /* A start tag whose tag name is one of: "h1", "h2", "h3", "h4", + "h5", "h6" */ + case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been seen. */ + if($this->elementInScope('p')) { + $this->emitToken(array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + )); + } + + /* If the stack of open elements has in scope an element whose + tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then + this is a parse error; pop elements from the stack until an + element with one of those tag names has been popped from the + stack. */ + while($this->elementInScope(array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'))) { + array_pop($this->stack); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + break; + + /* A start tag whose tag name is "a" */ + case 'a': + /* If the list of active formatting elements contains + an element whose tag name is "a" between the end of the + list and the last marker on the list (or the start of + the list if there is no marker on the list), then this + is a parse error; act as if an end tag with the tag name + "a" had been seen, then remove that element from the list + of active formatting elements and the stack of open + elements if the end tag didn't already remove it (it + might not have if the element is not in table scope). */ + $leng = count($this->a_formatting); + + for($n = $leng - 1; $n >= 0; $n--) { + if($this->a_formatting[$n] === self::MARKER) { + break; + + } elseif($this->a_formatting[$n]->nodeName === 'a') { + $this->emitToken(array( + 'name' => 'a', + 'type' => HTML5::ENDTAG + )); + break; + } + } + + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $el = $this->insertElement($token); + + /* Add that element to the list of active formatting + elements. */ + $this->a_formatting[] = $el; + break; + + /* A start tag whose tag name is one of: "b", "big", "em", "font", + "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */ + case 'b': case 'big': case 'em': case 'font': case 'i': + case 'nobr': case 's': case 'small': case 'strike': + case 'strong': case 'tt': case 'u': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $el = $this->insertElement($token); + + /* Add that element to the list of active formatting + elements. */ + $this->a_formatting[] = $el; + break; + + /* A start tag token whose tag name is "button" */ + case 'button': + /* If the stack of open elements has a button element in scope, + then this is a parse error; act as if an end tag with the tag + name "button" had been seen, then reprocess the token. (We don't + do that. Unnecessary.) */ + if($this->elementInScope('button')) { + $this->inBody(array( + 'name' => 'button', + 'type' => HTML5::ENDTAG + )); + } + + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Insert a marker at the end of the list of active + formatting elements. */ + $this->a_formatting[] = self::MARKER; + break; + + /* A start tag token whose tag name is one of: "marquee", "object" */ + case 'marquee': case 'object': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Insert a marker at the end of the list of active + formatting elements. */ + $this->a_formatting[] = self::MARKER; + break; + + /* A start tag token whose tag name is "xmp" */ + case 'xmp': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Switch the content model flag to the CDATA state. */ + return HTML5::CDATA; + break; + + /* A start tag whose tag name is "table" */ + case 'table': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been seen. */ + if($this->elementInScope('p')) { + $this->emitToken(array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + )); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in table". */ + $this->mode = self::IN_TABLE; + break; + + /* A start tag whose tag name is one of: "area", "basefont", + "bgsound", "br", "embed", "img", "param", "spacer", "wbr" */ + case 'area': case 'basefont': case 'bgsound': case 'br': + case 'embed': case 'img': case 'param': case 'spacer': + case 'wbr': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Immediately pop the current node off the stack of open elements. */ + array_pop($this->stack); + break; + + /* A start tag whose tag name is "hr" */ + case 'hr': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been seen. */ + if($this->elementInScope('p')) { + $this->emitToken(array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + )); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Immediately pop the current node off the stack of open elements. */ + array_pop($this->stack); + break; + + /* A start tag whose tag name is "image" */ + case 'image': + /* Parse error. Change the token's tag name to "img" and + reprocess it. (Don't ask.) */ + $token['name'] = 'img'; + return $this->inBody($token); + break; + + /* A start tag whose tag name is "input" */ + case 'input': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an input element for the token. */ + $element = $this->insertElement($token, false); + + /* If the form element pointer is not null, then associate the + input element with the form element pointed to by the form + element pointer. */ + $this->form_pointer !== null + ? $this->form_pointer->appendChild($element) + : end($this->stack)->appendChild($element); + + /* Pop that input element off the stack of open elements. */ + array_pop($this->stack); + break; + + /* A start tag whose tag name is "isindex" */ + case 'isindex': + /* Parse error. */ + // w/e + + /* If the form element pointer is not null, + then ignore the token. */ + if($this->form_pointer === null) { + /* Act as if a start tag token with the tag name "form" had + been seen. */ + $this->inBody(array( + 'name' => 'body', + 'type' => HTML5::STARTTAG, + 'attr' => array() + )); + + /* Act as if a start tag token with the tag name "hr" had + been seen. */ + $this->inBody(array( + 'name' => 'hr', + 'type' => HTML5::STARTTAG, + 'attr' => array() + )); + + /* Act as if a start tag token with the tag name "p" had + been seen. */ + $this->inBody(array( + 'name' => 'p', + 'type' => HTML5::STARTTAG, + 'attr' => array() + )); + + /* Act as if a start tag token with the tag name "label" + had been seen. */ + $this->inBody(array( + 'name' => 'label', + 'type' => HTML5::STARTTAG, + 'attr' => array() + )); + + /* Act as if a stream of character tokens had been seen. */ + $this->insertText('This is a searchable index. '. + 'Insert your search keywords here: '); + + /* Act as if a start tag token with the tag name "input" + had been seen, with all the attributes from the "isindex" + token, except with the "name" attribute set to the value + "isindex" (ignoring any explicit "name" attribute). */ + $attr = $token['attr']; + $attr[] = array('name' => 'name', 'value' => 'isindex'); + + $this->inBody(array( + 'name' => 'input', + 'type' => HTML5::STARTTAG, + 'attr' => $attr + )); + + /* Act as if a stream of character tokens had been seen + (see below for what they should say). */ + $this->insertText('This is a searchable index. '. + 'Insert your search keywords here: '); + + /* Act as if an end tag token with the tag name "label" + had been seen. */ + $this->inBody(array( + 'name' => 'label', + 'type' => HTML5::ENDTAG + )); + + /* Act as if an end tag token with the tag name "p" had + been seen. */ + $this->inBody(array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + )); + + /* Act as if a start tag token with the tag name "hr" had + been seen. */ + $this->inBody(array( + 'name' => 'hr', + 'type' => HTML5::ENDTAG + )); + + /* Act as if an end tag token with the tag name "form" had + been seen. */ + $this->inBody(array( + 'name' => 'form', + 'type' => HTML5::ENDTAG + )); + } + break; + + /* A start tag whose tag name is "textarea" */ + case 'textarea': + $this->insertElement($token); + + /* Switch the tokeniser's content model flag to the + RCDATA state. */ + return HTML5::RCDATA; + break; + + /* A start tag whose tag name is one of: "iframe", "noembed", + "noframes" */ + case 'iframe': case 'noembed': case 'noframes': + $this->insertElement($token); + + /* Switch the tokeniser's content model flag to the CDATA state. */ + return HTML5::CDATA; + break; + + /* A start tag whose tag name is "select" */ + case 'select': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in select". */ + $this->mode = self::IN_SELECT; + break; + + /* A start or end tag whose tag name is one of: "caption", "col", + "colgroup", "frame", "frameset", "head", "option", "optgroup", + "tbody", "td", "tfoot", "th", "thead", "tr". */ + case 'caption': case 'col': case 'colgroup': case 'frame': + case 'frameset': case 'head': case 'option': case 'optgroup': + case 'tbody': case 'td': case 'tfoot': case 'th': case 'thead': + case 'tr': + // Parse error. Ignore the token. + break; + + /* A start or end tag whose tag name is one of: "event-source", + "section", "nav", "article", "aside", "header", "footer", + "datagrid", "command" */ + case 'event-source': case 'section': case 'nav': case 'article': + case 'aside': case 'header': case 'footer': case 'datagrid': + case 'command': + // Work in progress! + break; + + /* A start tag token not covered by the previous entries */ + default: + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + $this->insertElement($token, true, true); + break; + } + break; + + case HTML5::ENDTAG: + switch($token['name']) { + /* An end tag with the tag name "body" */ + case 'body': + /* If the second element in the stack of open elements is + not a body element, this is a parse error. Ignore the token. + (innerHTML case) */ + if(count($this->stack) < 2 || $this->stack[1]->nodeName !== 'body') { + // Ignore. + + /* If the current node is not the body element, then this + is a parse error. */ + } elseif(end($this->stack)->nodeName !== 'body') { + // Parse error. + } + + /* Change the insertion mode to "after body". */ + $this->mode = self::AFTER_BODY; + break; + + /* An end tag with the tag name "html" */ + case 'html': + /* Act as if an end tag with tag name "body" had been seen, + then, if that token wasn't ignored, reprocess the current + token. */ + $this->inBody(array( + 'name' => 'body', + 'type' => HTML5::ENDTAG + )); + + return $this->afterBody($token); + break; + + /* An end tag whose tag name is one of: "address", "blockquote", + "center", "dir", "div", "dl", "fieldset", "listing", "menu", + "ol", "pre", "ul" */ + case 'address': case 'blockquote': case 'center': case 'dir': + case 'div': case 'dl': case 'fieldset': case 'listing': + case 'menu': case 'ol': case 'pre': case 'ul': + /* If the stack of open elements has an element in scope + with the same tag name as that of the token, then generate + implied end tags. */ + if($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(); + + /* Now, if the current node is not an element with + the same tag name as that of the token, then this + is a parse error. */ + // w/e + + /* If the stack of open elements has an element in + scope with the same tag name as that of the token, + then pop elements from this stack until an element + with that tag name has been popped from the stack. */ + for($n = count($this->stack) - 1; $n >= 0; $n--) { + if($this->stack[$n]->nodeName === $token['name']) { + $n = -1; + } + + array_pop($this->stack); + } + } + break; + + /* An end tag whose tag name is "form" */ + case 'form': + /* If the stack of open elements has an element in scope + with the same tag name as that of the token, then generate + implied end tags. */ + if($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(); + + } + + if(end($this->stack)->nodeName !== $token['name']) { + /* Now, if the current node is not an element with the + same tag name as that of the token, then this is a parse + error. */ + // w/e + + } else { + /* Otherwise, if the current node is an element with + the same tag name as that of the token pop that element + from the stack. */ + array_pop($this->stack); + } + + /* In any case, set the form element pointer to null. */ + $this->form_pointer = null; + break; + + /* An end tag whose tag name is "p" */ + case 'p': + /* If the stack of open elements has a p element in scope, + then generate implied end tags, except for p elements. */ + if($this->elementInScope('p')) { + $this->generateImpliedEndTags(array('p')); + + /* If the current node is not a p element, then this is + a parse error. */ + // k + + /* If the stack of open elements has a p element in + scope, then pop elements from this stack until the stack + no longer has a p element in scope. */ + for($n = count($this->stack) - 1; $n >= 0; $n--) { + if($this->elementInScope('p')) { + array_pop($this->stack); + + } else { + break; + } + } + } + break; + + /* An end tag whose tag name is "dd", "dt", or "li" */ + case 'dd': case 'dt': case 'li': + /* If the stack of open elements has an element in scope + whose tag name matches the tag name of the token, then + generate implied end tags, except for elements with the + same tag name as the token. */ + if($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(array($token['name'])); + + /* If the current node is not an element with the same + tag name as the token, then this is a parse error. */ + // w/e + + /* If the stack of open elements has an element in scope + whose tag name matches the tag name of the token, then + pop elements from this stack until an element with that + tag name has been popped from the stack. */ + for($n = count($this->stack) - 1; $n >= 0; $n--) { + if($this->stack[$n]->nodeName === $token['name']) { + $n = -1; + } + + array_pop($this->stack); + } + } + break; + + /* An end tag whose tag name is one of: "h1", "h2", "h3", "h4", + "h5", "h6" */ + case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6': + $elements = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'); + + /* If the stack of open elements has in scope an element whose + tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then + generate implied end tags. */ + if($this->elementInScope($elements)) { + $this->generateImpliedEndTags(); + + /* Now, if the current node is not an element with the same + tag name as that of the token, then this is a parse error. */ + // w/e + + /* If the stack of open elements has in scope an element + whose tag name is one of "h1", "h2", "h3", "h4", "h5", or + "h6", then pop elements from the stack until an element + with one of those tag names has been popped from the stack. */ + while($this->elementInScope($elements)) { + array_pop($this->stack); + } + } + break; + + /* An end tag whose tag name is one of: "a", "b", "big", "em", + "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */ + case 'a': case 'b': case 'big': case 'em': case 'font': + case 'i': case 'nobr': case 's': case 'small': case 'strike': + case 'strong': case 'tt': case 'u': + /* 1. Let the formatting element be the last element in + the list of active formatting elements that: + * is between the end of the list and the last scope + marker in the list, if any, or the start of the list + otherwise, and + * has the same tag name as the token. + */ + while(true) { + for($a = count($this->a_formatting) - 1; $a >= 0; $a--) { + if($this->a_formatting[$a] === self::MARKER) { + break; + + } elseif($this->a_formatting[$a]->tagName === $token['name']) { + $formatting_element = $this->a_formatting[$a]; + $in_stack = in_array($formatting_element, $this->stack, true); + $fe_af_pos = $a; + break; + } + } + + /* If there is no such node, or, if that node is + also in the stack of open elements but the element + is not in scope, then this is a parse error. Abort + these steps. The token is ignored. */ + if(!isset($formatting_element) || ($in_stack && + !$this->elementInScope($token['name']))) { + break; + + /* Otherwise, if there is such a node, but that node + is not in the stack of open elements, then this is a + parse error; remove the element from the list, and + abort these steps. */ + } elseif(isset($formatting_element) && !$in_stack) { + unset($this->a_formatting[$fe_af_pos]); + $this->a_formatting = array_merge($this->a_formatting); + break; + } + + /* 2. Let the furthest block be the topmost node in the + stack of open elements that is lower in the stack + than the formatting element, and is not an element in + the phrasing or formatting categories. There might + not be one. */ + $fe_s_pos = array_search($formatting_element, $this->stack, true); + $length = count($this->stack); + + for($s = $fe_s_pos + 1; $s < $length; $s++) { + $category = $this->getElementCategory($this->stack[$s]->nodeName); + + if($category !== self::PHRASING && $category !== self::FORMATTING) { + $furthest_block = $this->stack[$s]; + } + } + + /* 3. If there is no furthest block, then the UA must + skip the subsequent steps and instead just pop all + the nodes from the bottom of the stack of open + elements, from the current node up to the formatting + element, and remove the formatting element from the + list of active formatting elements. */ + if(!isset($furthest_block)) { + for($n = $length - 1; $n >= $fe_s_pos; $n--) { + array_pop($this->stack); + } + + unset($this->a_formatting[$fe_af_pos]); + $this->a_formatting = array_merge($this->a_formatting); + break; + } + + /* 4. Let the common ancestor be the element + immediately above the formatting element in the stack + of open elements. */ + $common_ancestor = $this->stack[$fe_s_pos - 1]; + + /* 5. If the furthest block has a parent node, then + remove the furthest block from its parent node. */ + if($furthest_block->parentNode !== null) { + $furthest_block->parentNode->removeChild($furthest_block); + } + + /* 6. Let a bookmark note the position of the + formatting element in the list of active formatting + elements relative to the elements on either side + of it in the list. */ + $bookmark = $fe_af_pos; + + /* 7. Let node and last node be the furthest block. + Follow these steps: */ + $node = $furthest_block; + $last_node = $furthest_block; + + while(true) { + for($n = array_search($node, $this->stack, true) - 1; $n >= 0; $n--) { + /* 7.1 Let node be the element immediately + prior to node in the stack of open elements. */ + $node = $this->stack[$n]; + + /* 7.2 If node is not in the list of active + formatting elements, then remove node from + the stack of open elements and then go back + to step 1. */ + if(!in_array($node, $this->a_formatting, true)) { + unset($this->stack[$n]); + $this->stack = array_merge($this->stack); + + } else { + break; + } + } + + /* 7.3 Otherwise, if node is the formatting + element, then go to the next step in the overall + algorithm. */ + if($node === $formatting_element) { + break; + + /* 7.4 Otherwise, if last node is the furthest + block, then move the aforementioned bookmark to + be immediately after the node in the list of + active formatting elements. */ + } elseif($last_node === $furthest_block) { + $bookmark = array_search($node, $this->a_formatting, true) + 1; + } + + /* 7.5 If node has any children, perform a + shallow clone of node, replace the entry for + node in the list of active formatting elements + with an entry for the clone, replace the entry + for node in the stack of open elements with an + entry for the clone, and let node be the clone. */ + if($node->hasChildNodes()) { + $clone = $node->cloneNode(); + $s_pos = array_search($node, $this->stack, true); + $a_pos = array_search($node, $this->a_formatting, true); + + $this->stack[$s_pos] = $clone; + $this->a_formatting[$a_pos] = $clone; + $node = $clone; + } + + /* 7.6 Insert last node into node, first removing + it from its previous parent node if any. */ + if($last_node->parentNode !== null) { + $last_node->parentNode->removeChild($last_node); + } + + $node->appendChild($last_node); + + /* 7.7 Let last node be node. */ + $last_node = $node; + } + + /* 8. Insert whatever last node ended up being in + the previous step into the common ancestor node, + first removing it from its previous parent node if + any. */ + if($last_node->parentNode !== null) { + $last_node->parentNode->removeChild($last_node); + } + + $common_ancestor->appendChild($last_node); + + /* 9. Perform a shallow clone of the formatting + element. */ + $clone = $formatting_element->cloneNode(); + + /* 10. Take all of the child nodes of the furthest + block and append them to the clone created in the + last step. */ + while($furthest_block->hasChildNodes()) { + $child = $furthest_block->firstChild; + $furthest_block->removeChild($child); + $clone->appendChild($child); + } + + /* 11. Append that clone to the furthest block. */ + $furthest_block->appendChild($clone); + + /* 12. Remove the formatting element from the list + of active formatting elements, and insert the clone + into the list of active formatting elements at the + position of the aforementioned bookmark. */ + $fe_af_pos = array_search($formatting_element, $this->a_formatting, true); + unset($this->a_formatting[$fe_af_pos]); + $this->a_formatting = array_merge($this->a_formatting); + + $af_part1 = array_slice($this->a_formatting, 0, $bookmark - 1); + $af_part2 = array_slice($this->a_formatting, $bookmark, count($this->a_formatting)); + $this->a_formatting = array_merge($af_part1, array($clone), $af_part2); + + /* 13. Remove the formatting element from the stack + of open elements, and insert the clone into the stack + of open elements immediately after (i.e. in a more + deeply nested position than) the position of the + furthest block in that stack. */ + $fe_s_pos = array_search($formatting_element, $this->stack, true); + $fb_s_pos = array_search($furthest_block, $this->stack, true); + unset($this->stack[$fe_s_pos]); + + $s_part1 = array_slice($this->stack, 0, $fb_s_pos); + $s_part2 = array_slice($this->stack, $fb_s_pos + 1, count($this->stack)); + $this->stack = array_merge($s_part1, array($clone), $s_part2); + + /* 14. Jump back to step 1 in this series of steps. */ + unset($formatting_element, $fe_af_pos, $fe_s_pos, $furthest_block); + } + break; + + /* An end tag token whose tag name is one of: "button", + "marquee", "object" */ + case 'button': case 'marquee': case 'object': + /* If the stack of open elements has an element in scope whose + tag name matches the tag name of the token, then generate implied + tags. */ + if($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(); + + /* Now, if the current node is not an element with the same + tag name as the token, then this is a parse error. */ + // k + + /* Now, if the stack of open elements has an element in scope + whose tag name matches the tag name of the token, then pop + elements from the stack until that element has been popped from + the stack, and clear the list of active formatting elements up + to the last marker. */ + for($n = count($this->stack) - 1; $n >= 0; $n--) { + if($this->stack[$n]->nodeName === $token['name']) { + $n = -1; + } + + array_pop($this->stack); + } + + $marker = end(array_keys($this->a_formatting, self::MARKER, true)); + + for($n = count($this->a_formatting) - 1; $n > $marker; $n--) { + array_pop($this->a_formatting); + } + } + break; + + /* Or an end tag whose tag name is one of: "area", "basefont", + "bgsound", "br", "embed", "hr", "iframe", "image", "img", + "input", "isindex", "noembed", "noframes", "param", "select", + "spacer", "table", "textarea", "wbr" */ + case 'area': case 'basefont': case 'bgsound': case 'br': + case 'embed': case 'hr': case 'iframe': case 'image': + case 'img': case 'input': case 'isindex': case 'noembed': + case 'noframes': case 'param': case 'select': case 'spacer': + case 'table': case 'textarea': case 'wbr': + // Parse error. Ignore the token. + break; + + /* An end tag token not covered by the previous entries */ + default: + for($n = count($this->stack) - 1; $n >= 0; $n--) { + /* Initialise node to be the current node (the bottommost + node of the stack). */ + $node = end($this->stack); + + /* If node has the same tag name as the end tag token, + then: */ + if($token['name'] === $node->nodeName) { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* If the tag name of the end tag token does not + match the tag name of the current node, this is a + parse error. */ + // k + + /* Pop all the nodes from the current node up to + node, including node, then stop this algorithm. */ + for($x = count($this->stack) - $n; $x >= $n; $x--) { + array_pop($this->stack); + } + + } else { + $category = $this->getElementCategory($node); + + if($category !== self::SPECIAL && $category !== self::SCOPING) { + /* Otherwise, if node is in neither the formatting + category nor the phrasing category, then this is a + parse error. Stop this algorithm. The end tag token + is ignored. */ + return false; + } + } + } + break; + } + break; + } + } + + private function inTable($token) { + $clear = array('html', 'table'); + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { + /* Append the character to the current node. */ + $text = $this->dom->createTextNode($token['data']); + end($this->stack)->appendChild($text); + + /* A comment token */ + } elseif($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + end($this->stack)->appendChild($comment); + + /* A start tag whose tag name is "caption" */ + } elseif($token['type'] === HTML5::STARTTAG && + $token['name'] === 'caption') { + /* Clear the stack back to a table context. */ + $this->clearStackToTableContext($clear); + + /* Insert a marker at the end of the list of active + formatting elements. */ + $this->a_formatting[] = self::MARKER; + + /* Insert an HTML element for the token, then switch the + insertion mode to "in caption". */ + $this->insertElement($token); + $this->mode = self::IN_CAPTION; + + /* A start tag whose tag name is "colgroup" */ + } elseif($token['type'] === HTML5::STARTTAG && + $token['name'] === 'colgroup') { + /* Clear the stack back to a table context. */ + $this->clearStackToTableContext($clear); + + /* Insert an HTML element for the token, then switch the + insertion mode to "in column group". */ + $this->insertElement($token); + $this->mode = self::IN_CGROUP; + + /* A start tag whose tag name is "col" */ + } elseif($token['type'] === HTML5::STARTTAG && + $token['name'] === 'col') { + $this->inTable(array( + 'name' => 'colgroup', + 'type' => HTML5::STARTTAG, + 'attr' => array() + )); + + $this->inColumnGroup($token); + + /* A start tag whose tag name is one of: "tbody", "tfoot", "thead" */ + } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], + array('tbody', 'tfoot', 'thead'))) { + /* Clear the stack back to a table context. */ + $this->clearStackToTableContext($clear); + + /* Insert an HTML element for the token, then switch the insertion + mode to "in table body". */ + $this->insertElement($token); + $this->mode = self::IN_TBODY; + + /* A start tag whose tag name is one of: "td", "th", "tr" */ + } elseif($token['type'] === HTML5::STARTTAG && + in_array($token['name'], array('td', 'th', 'tr'))) { + /* Act as if a start tag token with the tag name "tbody" had been + seen, then reprocess the current token. */ + $this->inTable(array( + 'name' => 'tbody', + 'type' => HTML5::STARTTAG, + 'attr' => array() + )); + + return $this->inTableBody($token); + + /* A start tag whose tag name is "table" */ + } elseif($token['type'] === HTML5::STARTTAG && + $token['name'] === 'table') { + /* Parse error. Act as if an end tag token with the tag name "table" + had been seen, then, if that token wasn't ignored, reprocess the + current token. */ + $this->inTable(array( + 'name' => 'table', + 'type' => HTML5::ENDTAG + )); + + return $this->mainPhase($token); + + /* An end tag whose tag name is "table" */ + } elseif($token['type'] === HTML5::ENDTAG && + $token['name'] === 'table') { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if(!$this->elementInScope($token['name'], true)) { + return false; + + /* Otherwise: */ + } else { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* Now, if the current node is not a table element, then this + is a parse error. */ + // w/e + + /* Pop elements from this stack until a table element has been + popped from the stack. */ + while(true) { + $current = end($this->stack)->nodeName; + array_pop($this->stack); + + if($current === 'table') { + break; + } + } + + /* Reset the insertion mode appropriately. */ + $this->resetInsertionMode(); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], + array('body', 'caption', 'col', 'colgroup', 'html', 'tbody', 'td', + 'tfoot', 'th', 'thead', 'tr'))) { + // Parse error. Ignore the token. + + /* Anything else */ + } else { + /* Parse error. Process the token as if the insertion mode was "in + body", with the following exception: */ + + /* If the current node is a table, tbody, tfoot, thead, or tr + element, then, whenever a node would be inserted into the current + node, it must instead be inserted into the foster parent element. */ + if(in_array(end($this->stack)->nodeName, + array('table', 'tbody', 'tfoot', 'thead', 'tr'))) { + /* The foster parent element is the parent element of the last + table element in the stack of open elements, if there is a + table element and it has such a parent element. If there is no + table element in the stack of open elements (innerHTML case), + then the foster parent element is the first element in the + stack of open elements (the html element). Otherwise, if there + is a table element in the stack of open elements, but the last + table element in the stack of open elements has no parent, or + its parent node is not an element, then the foster parent + element is the element before the last table element in the + stack of open elements. */ + for($n = count($this->stack) - 1; $n >= 0; $n--) { + if($this->stack[$n]->nodeName === 'table') { + $table = $this->stack[$n]; + break; + } + } + + if(isset($table) && $table->parentNode !== null) { + $this->foster_parent = $table->parentNode; + + } elseif(!isset($table)) { + $this->foster_parent = $this->stack[0]; + + } elseif(isset($table) && ($table->parentNode === null || + $table->parentNode->nodeType !== XML_ELEMENT_NODE)) { + $this->foster_parent = $this->stack[$n - 1]; + } + } + + $this->inBody($token); + } + } + + private function inCaption($token) { + /* An end tag whose tag name is "caption" */ + if($token['type'] === HTML5::ENDTAG && $token['name'] === 'caption') { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if(!$this->elementInScope($token['name'], true)) { + // Ignore + + /* Otherwise: */ + } else { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* Now, if the current node is not a caption element, then this + is a parse error. */ + // w/e + + /* Pop elements from this stack until a caption element has + been popped from the stack. */ + while(true) { + $node = end($this->stack)->nodeName; + array_pop($this->stack); + + if($node === 'caption') { + break; + } + } + + /* Clear the list of active formatting elements up to the last + marker. */ + $this->clearTheActiveFormattingElementsUpToTheLastMarker(); + + /* Switch the insertion mode to "in table". */ + $this->mode = self::IN_TABLE; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "td", "tfoot", "th", "thead", "tr", or an end tag whose tag + name is "table" */ + } elseif(($token['type'] === HTML5::STARTTAG && in_array($token['name'], + array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', + 'thead', 'tr'))) || ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'table')) { + /* Parse error. Act as if an end tag with the tag name "caption" + had been seen, then, if that token wasn't ignored, reprocess the + current token. */ + $this->inCaption(array( + 'name' => 'caption', + 'type' => HTML5::ENDTAG + )); + + return $this->inTable($token); + + /* An end tag whose tag name is one of: "body", "col", "colgroup", + "html", "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], + array('body', 'col', 'colgroup', 'html', 'tbody', 'tfoot', 'th', + 'thead', 'tr'))) { + // Parse error. Ignore the token. + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in body". */ + $this->inBody($token); + } + } + + private function inColumnGroup($token) { + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { + /* Append the character to the current node. */ + $text = $this->dom->createTextNode($token['data']); + end($this->stack)->appendChild($text); + + /* A comment token */ + } elseif($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + end($this->stack)->appendChild($comment); + + /* A start tag whose tag name is "col" */ + } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'col') { + /* Insert a col element for the token. Immediately pop the current + node off the stack of open elements. */ + $this->insertElement($token); + array_pop($this->stack); + + /* An end tag whose tag name is "colgroup" */ + } elseif($token['type'] === HTML5::ENDTAG && + $token['name'] === 'colgroup') { + /* If the current node is the root html element, then this is a + parse error, ignore the token. (innerHTML case) */ + if(end($this->stack)->nodeName === 'html') { + // Ignore + + /* Otherwise, pop the current node (which will be a colgroup + element) from the stack of open elements. Switch the insertion + mode to "in table". */ + } else { + array_pop($this->stack); + $this->mode = self::IN_TABLE; + } + + /* An end tag whose tag name is "col" */ + } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'col') { + /* Parse error. Ignore the token. */ + + /* Anything else */ + } else { + /* Act as if an end tag with the tag name "colgroup" had been seen, + and then, if that token wasn't ignored, reprocess the current token. */ + $this->inColumnGroup(array( + 'name' => 'colgroup', + 'type' => HTML5::ENDTAG + )); + + return $this->inTable($token); + } + } + + private function inTableBody($token) { + $clear = array('tbody', 'tfoot', 'thead', 'html'); + + /* A start tag whose tag name is "tr" */ + if($token['type'] === HTML5::STARTTAG && $token['name'] === 'tr') { + /* Clear the stack back to a table body context. */ + $this->clearStackToTableContext($clear); + + /* Insert a tr element for the token, then switch the insertion + mode to "in row". */ + $this->insertElement($token); + $this->mode = self::IN_ROW; + + /* A start tag whose tag name is one of: "th", "td" */ + } elseif($token['type'] === HTML5::STARTTAG && + ($token['name'] === 'th' || $token['name'] === 'td')) { + /* Parse error. Act as if a start tag with the tag name "tr" had + been seen, then reprocess the current token. */ + $this->inTableBody(array( + 'name' => 'tr', + 'type' => HTML5::STARTTAG, + 'attr' => array() + )); + + return $this->inRow($token); + + /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */ + } elseif($token['type'] === HTML5::ENDTAG && + in_array($token['name'], array('tbody', 'tfoot', 'thead'))) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. */ + if(!$this->elementInScope($token['name'], true)) { + // Ignore + + /* Otherwise: */ + } else { + /* Clear the stack back to a table body context. */ + $this->clearStackToTableContext($clear); + + /* Pop the current node from the stack of open elements. Switch + the insertion mode to "in table". */ + array_pop($this->stack); + $this->mode = self::IN_TABLE; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "tfoot", "thead", or an end tag whose tag name is "table" */ + } elseif(($token['type'] === HTML5::STARTTAG && in_array($token['name'], + array('caption', 'col', 'colgroup', 'tbody', 'tfoor', 'thead'))) || + ($token['type'] === HTML5::STARTTAG && $token['name'] === 'table')) { + /* If the stack of open elements does not have a tbody, thead, or + tfoot element in table scope, this is a parse error. Ignore the + token. (innerHTML case) */ + if(!$this->elementInScope(array('tbody', 'thead', 'tfoot'), true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Clear the stack back to a table body context. */ + $this->clearStackToTableContext($clear); + + /* Act as if an end tag with the same tag name as the current + node ("tbody", "tfoot", or "thead") had been seen, then + reprocess the current token. */ + $this->inTableBody(array( + 'name' => end($this->stack)->nodeName, + 'type' => HTML5::ENDTAG + )); + + return $this->mainPhase($token); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html", "td", "th", "tr" */ + } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], + array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr'))) { + /* Parse error. Ignore the token. */ + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in table". */ + $this->inTable($token); + } + } + + private function inRow($token) { + $clear = array('tr', 'html'); + + /* A start tag whose tag name is one of: "th", "td" */ + if($token['type'] === HTML5::STARTTAG && + ($token['name'] === 'th' || $token['name'] === 'td')) { + /* Clear the stack back to a table row context. */ + $this->clearStackToTableContext($clear); + + /* Insert an HTML element for the token, then switch the insertion + mode to "in cell". */ + $this->insertElement($token); + $this->mode = self::IN_CELL; + + /* Insert a marker at the end of the list of active formatting + elements. */ + $this->a_formatting[] = self::MARKER; + + /* An end tag whose tag name is "tr" */ + } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'tr') { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if(!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Clear the stack back to a table row context. */ + $this->clearStackToTableContext($clear); + + /* Pop the current node (which will be a tr element) from the + stack of open elements. Switch the insertion mode to "in table + body". */ + array_pop($this->stack); + $this->mode = self::IN_TBODY; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "tfoot", "thead", "tr" or an end tag whose tag name is "table" */ + } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], + array('caption', 'col', 'colgroup', 'tbody', 'tfoot', 'thead', 'tr'))) { + /* Act as if an end tag with the tag name "tr" had been seen, then, + if that token wasn't ignored, reprocess the current token. */ + $this->inRow(array( + 'name' => 'tr', + 'type' => HTML5::ENDTAG + )); + + return $this->inCell($token); + + /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */ + } elseif($token['type'] === HTML5::ENDTAG && + in_array($token['name'], array('tbody', 'tfoot', 'thead'))) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. */ + if(!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Otherwise, act as if an end tag with the tag name "tr" had + been seen, then reprocess the current token. */ + $this->inRow(array( + 'name' => 'tr', + 'type' => HTML5::ENDTAG + )); + + return $this->inCell($token); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html", "td", "th" */ + } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], + array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr'))) { + /* Parse error. Ignore the token. */ + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in table". */ + $this->inTable($token); + } + } + + private function inCell($token) { + /* An end tag whose tag name is one of: "td", "th" */ + if($token['type'] === HTML5::ENDTAG && + ($token['name'] === 'td' || $token['name'] === 'th')) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as that of the token, then this is a + parse error and the token must be ignored. */ + if(!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Generate implied end tags, except for elements with the same + tag name as the token. */ + $this->generateImpliedEndTags(array($token['name'])); + + /* Now, if the current node is not an element with the same tag + name as the token, then this is a parse error. */ + // k + + /* Pop elements from this stack until an element with the same + tag name as the token has been popped from the stack. */ + while(true) { + $node = end($this->stack)->nodeName; + array_pop($this->stack); + + if($node === $token['name']) { + break; + } + } + + /* Clear the list of active formatting elements up to the last + marker. */ + $this->clearTheActiveFormattingElementsUpToTheLastMarker(); + + /* Switch the insertion mode to "in row". (The current node + will be a tr element at this point.) */ + $this->mode = self::IN_ROW; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], + array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', + 'thead', 'tr'))) { + /* If the stack of open elements does not have a td or th element + in table scope, then this is a parse error; ignore the token. + (innerHTML case) */ + if(!$this->elementInScope(array('td', 'th'), true)) { + // Ignore. + + /* Otherwise, close the cell (see below) and reprocess the current + token. */ + } else { + $this->closeCell(); + return $this->inRow($token); + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], + array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', + 'thead', 'tr'))) { + /* If the stack of open elements does not have a td or th element + in table scope, then this is a parse error; ignore the token. + (innerHTML case) */ + if(!$this->elementInScope(array('td', 'th'), true)) { + // Ignore. + + /* Otherwise, close the cell (see below) and reprocess the current + token. */ + } else { + $this->closeCell(); + return $this->inRow($token); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html" */ + } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], + array('body', 'caption', 'col', 'colgroup', 'html'))) { + /* Parse error. Ignore the token. */ + + /* An end tag whose tag name is one of: "table", "tbody", "tfoot", + "thead", "tr" */ + } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], + array('table', 'tbody', 'tfoot', 'thead', 'tr'))) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as that of the token (which can only + happen for "tbody", "tfoot" and "thead", or, in the innerHTML case), + then this is a parse error and the token must be ignored. */ + if(!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise, close the cell (see below) and reprocess the current + token. */ + } else { + $this->closeCell(); + return $this->inRow($token); + } + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in body". */ + $this->inBody($token); + } + } + + private function inSelect($token) { + /* Handle the token as follows: */ + + /* A character token */ + if($token['type'] === HTML5::CHARACTR) { + /* Append the token's character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag token whose tag name is "option" */ + } elseif($token['type'] === HTML5::STARTTAG && + $token['name'] === 'option') { + /* If the current node is an option element, act as if an end tag + with the tag name "option" had been seen. */ + if(end($this->stack)->nodeName === 'option') { + $this->inSelect(array( + 'name' => 'option', + 'type' => HTML5::ENDTAG + )); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* A start tag token whose tag name is "optgroup" */ + } elseif($token['type'] === HTML5::STARTTAG && + $token['name'] === 'optgroup') { + /* If the current node is an option element, act as if an end tag + with the tag name "option" had been seen. */ + if(end($this->stack)->nodeName === 'option') { + $this->inSelect(array( + 'name' => 'option', + 'type' => HTML5::ENDTAG + )); + } + + /* If the current node is an optgroup element, act as if an end tag + with the tag name "optgroup" had been seen. */ + if(end($this->stack)->nodeName === 'optgroup') { + $this->inSelect(array( + 'name' => 'optgroup', + 'type' => HTML5::ENDTAG + )); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* An end tag token whose tag name is "optgroup" */ + } elseif($token['type'] === HTML5::ENDTAG && + $token['name'] === 'optgroup') { + /* First, if the current node is an option element, and the node + immediately before it in the stack of open elements is an optgroup + element, then act as if an end tag with the tag name "option" had + been seen. */ + $elements_in_stack = count($this->stack); + + if($this->stack[$elements_in_stack - 1]->nodeName === 'option' && + $this->stack[$elements_in_stack - 2]->nodeName === 'optgroup') { + $this->inSelect(array( + 'name' => 'option', + 'type' => HTML5::ENDTAG + )); + } + + /* If the current node is an optgroup element, then pop that node + from the stack of open elements. Otherwise, this is a parse error, + ignore the token. */ + if($this->stack[$elements_in_stack - 1] === 'optgroup') { + array_pop($this->stack); + } + + /* An end tag token whose tag name is "option" */ + } elseif($token['type'] === HTML5::ENDTAG && + $token['name'] === 'option') { + /* If the current node is an option element, then pop that node + from the stack of open elements. Otherwise, this is a parse error, + ignore the token. */ + if(end($this->stack)->nodeName === 'option') { + array_pop($this->stack); + } + + /* An end tag whose tag name is "select" */ + } elseif($token['type'] === HTML5::ENDTAG && + $token['name'] === 'select') { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if(!$this->elementInScope($token['name'], true)) { + // w/e + + /* Otherwise: */ + } else { + /* Pop elements from the stack of open elements until a select + element has been popped from the stack. */ + while(true) { + $current = end($this->stack)->nodeName; + array_pop($this->stack); + + if($current === 'select') { + break; + } + } + + /* Reset the insertion mode appropriately. */ + $this->resetInsertionMode(); + } + + /* A start tag whose tag name is "select" */ + } elseif($token['name'] === 'select' && + $token['type'] === HTML5::STARTTAG) { + /* Parse error. Act as if the token had been an end tag with the + tag name "select" instead. */ + $this->inSelect(array( + 'name' => 'select', + 'type' => HTML5::ENDTAG + )); + + /* An end tag whose tag name is one of: "caption", "table", "tbody", + "tfoot", "thead", "tr", "td", "th" */ + } elseif(in_array($token['name'], array('caption', 'table', 'tbody', + 'tfoot', 'thead', 'tr', 'td', 'th')) && $token['type'] === HTML5::ENDTAG) { + /* Parse error. */ + // w/e + + /* If the stack of open elements has an element in table scope with + the same tag name as that of the token, then act as if an end tag + with the tag name "select" had been seen, and reprocess the token. + Otherwise, ignore the token. */ + if($this->elementInScope($token['name'], true)) { + $this->inSelect(array( + 'name' => 'select', + 'type' => HTML5::ENDTAG + )); + + $this->mainPhase($token); + } + + /* Anything else */ + } else { + /* Parse error. Ignore the token. */ + } + } + + private function afterBody($token) { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { + /* Process the token as it would be processed if the insertion mode + was "in body". */ + $this->inBody($token); + + /* A comment token */ + } elseif($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the first element in the stack of open + elements (the html element), with the data attribute set to the + data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + $this->stack[0]->appendChild($comment); + + /* An end tag with the tag name "html" */ + } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') { + /* If the parser was originally created in order to handle the + setting of an element's innerHTML attribute, this is a parse error; + ignore the token. (The element will be an html element in this + case.) (innerHTML case) */ + + /* Otherwise, switch to the trailing end phase. */ + $this->phase = self::END_PHASE; + + /* Anything else */ + } else { + /* Parse error. Set the insertion mode to "in body" and reprocess + the token. */ + $this->mode = self::IN_BODY; + return $this->inBody($token); + } + } + + private function inFrameset($token) { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */ + if($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag with the tag name "frameset" */ + } elseif($token['name'] === 'frameset' && + $token['type'] === HTML5::STARTTAG) { + $this->insertElement($token); + + /* An end tag with the tag name "frameset" */ + } elseif($token['name'] === 'frameset' && + $token['type'] === HTML5::ENDTAG) { + /* If the current node is the root html element, then this is a + parse error; ignore the token. (innerHTML case) */ + if(end($this->stack)->nodeName === 'html') { + // Ignore + + } else { + /* Otherwise, pop the current node from the stack of open + elements. */ + array_pop($this->stack); + + /* If the parser was not originally created in order to handle + the setting of an element's innerHTML attribute (innerHTML case), + and the current node is no longer a frameset element, then change + the insertion mode to "after frameset". */ + $this->mode = self::AFTR_FRAME; + } + + /* A start tag with the tag name "frame" */ + } elseif($token['name'] === 'frame' && + $token['type'] === HTML5::STARTTAG) { + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Immediately pop the current node off the stack of open elements. */ + array_pop($this->stack); + + /* A start tag with the tag name "noframes" */ + } elseif($token['name'] === 'noframes' && + $token['type'] === HTML5::STARTTAG) { + /* Process the token as if the insertion mode had been "in body". */ + $this->inBody($token); + + /* Anything else */ + } else { + /* Parse error. Ignore the token. */ + } + } + + private function afterFrameset($token) { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */ + if($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* An end tag with the tag name "html" */ + } elseif($token['name'] === 'html' && + $token['type'] === HTML5::ENDTAG) { + /* Switch to the trailing end phase. */ + $this->phase = self::END_PHASE; + + /* A start tag with the tag name "noframes" */ + } elseif($token['name'] === 'noframes' && + $token['type'] === HTML5::STARTTAG) { + /* Process the token as if the insertion mode had been "in body". */ + $this->inBody($token); + + /* Anything else */ + } else { + /* Parse error. Ignore the token. */ + } + } + + private function trailingEndPhase($token) { + /* After the main phase, as each token is emitted from the tokenisation + stage, it must be processed as described in this section. */ + + /* A DOCTYPE token */ + if($token['type'] === HTML5::DOCTYPE) { + // Parse error. Ignore the token. + + /* A comment token */ + } elseif($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the Document object with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + $this->dom->appendChild($comment); + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + } elseif($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { + /* Process the token as it would be processed in the main phase. */ + $this->mainPhase($token); + + /* A character token that is not one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE. Or a start tag token. Or an end tag token. */ + } elseif(($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || + $token['type'] === HTML5::STARTTAG || $token['type'] === HTML5::ENDTAG) { + /* Parse error. Switch back to the main phase and reprocess the + token. */ + $this->phase = self::MAIN_PHASE; + return $this->mainPhase($token); + + /* An end-of-file token */ + } elseif($token['type'] === HTML5::EOF) { + /* OMG DONE!! */ + } + } + + private function insertElement($token, $append = true, $check = false) { + // Proprietary workaround for libxml2's limitations with tag names + if ($check) { + // Slightly modified HTML5 tag-name modification, + // removing anything that's not an ASCII letter, digit, or hyphen + $token['name'] = preg_replace('/[^a-z0-9-]/i', '', $token['name']); + // Remove leading hyphens and numbers + $token['name'] = ltrim($token['name'], '-0..9'); + // In theory, this should ever be needed, but just in case + if ($token['name'] === '') $token['name'] = 'span'; // arbitrary generic choice + } + + $el = $this->dom->createElement($token['name']); + + foreach($token['attr'] as $attr) { + if(!$el->hasAttribute($attr['name'])) { + $el->setAttribute($attr['name'], $attr['value']); + } + } + + $this->appendToRealParent($el); + $this->stack[] = $el; + + return $el; + } + + private function insertText($data) { + $text = $this->dom->createTextNode($data); + $this->appendToRealParent($text); + } + + private function insertComment($data) { + $comment = $this->dom->createComment($data); + $this->appendToRealParent($comment); + } + + private function appendToRealParent($node) { + if($this->foster_parent === null) { + end($this->stack)->appendChild($node); + + } elseif($this->foster_parent !== null) { + /* If the foster parent element is the parent element of the + last table element in the stack of open elements, then the new + node must be inserted immediately before the last table element + in the stack of open elements in the foster parent element; + otherwise, the new node must be appended to the foster parent + element. */ + for($n = count($this->stack) - 1; $n >= 0; $n--) { + if($this->stack[$n]->nodeName === 'table' && + $this->stack[$n]->parentNode !== null) { + $table = $this->stack[$n]; + break; + } + } + + if(isset($table) && $this->foster_parent->isSameNode($table->parentNode)) + $this->foster_parent->insertBefore($node, $table); + else + $this->foster_parent->appendChild($node); + + $this->foster_parent = null; + } + } + + private function elementInScope($el, $table = false) { + if(is_array($el)) { + foreach($el as $element) { + if($this->elementInScope($element, $table)) { + return true; + } + } + + return false; + } + + $leng = count($this->stack); + + for($n = 0; $n < $leng; $n++) { + /* 1. Initialise node to be the current node (the bottommost node of + the stack). */ + $node = $this->stack[$leng - 1 - $n]; + + if($node->tagName === $el) { + /* 2. If node is the target node, terminate in a match state. */ + return true; + + } elseif($node->tagName === 'table') { + /* 3. Otherwise, if node is a table element, terminate in a failure + state. */ + return false; + + } elseif($table === true && in_array($node->tagName, array('caption', 'td', + 'th', 'button', 'marquee', 'object'))) { + /* 4. Otherwise, if the algorithm is the "has an element in scope" + variant (rather than the "has an element in table scope" variant), + and node is one of the following, terminate in a failure state. */ + return false; + + } elseif($node === $node->ownerDocument->documentElement) { + /* 5. Otherwise, if node is an html element (root element), terminate + in a failure state. (This can only happen if the node is the topmost + node of the stack of open elements, and prevents the next step from + being invoked if there are no more elements in the stack.) */ + return false; + } + + /* Otherwise, set node to the previous entry in the stack of open + elements and return to step 2. (This will never fail, since the loop + will always terminate in the previous step if the top of the stack + is reached.) */ + } + } + + private function reconstructActiveFormattingElements() { + /* 1. If there are no entries in the list of active formatting elements, + then there is nothing to reconstruct; stop this algorithm. */ + $formatting_elements = count($this->a_formatting); + + if($formatting_elements === 0) { + return false; + } + + /* 3. Let entry be the last (most recently added) element in the list + of active formatting elements. */ + $entry = end($this->a_formatting); + + /* 2. If the last (most recently added) entry in the list of active + formatting elements is a marker, or if it is an element that is in the + stack of open elements, then there is nothing to reconstruct; stop this + algorithm. */ + if($entry === self::MARKER || in_array($entry, $this->stack, true)) { + return false; + } + + for($a = $formatting_elements - 1; $a >= 0; true) { + /* 4. If there are no entries before entry in the list of active + formatting elements, then jump to step 8. */ + if($a === 0) { + $step_seven = false; + break; + } + + /* 5. Let entry be the entry one earlier than entry in the list of + active formatting elements. */ + $a--; + $entry = $this->a_formatting[$a]; + + /* 6. If entry is neither a marker nor an element that is also in + thetack of open elements, go to step 4. */ + if($entry === self::MARKER || in_array($entry, $this->stack, true)) { + break; + } + } + + while(true) { + /* 7. Let entry be the element one later than entry in the list of + active formatting elements. */ + if(isset($step_seven) && $step_seven === true) { + $a++; + $entry = $this->a_formatting[$a]; + } + + /* 8. Perform a shallow clone of the element entry to obtain clone. */ + $clone = $entry->cloneNode(); + + /* 9. Append clone to the current node and push it onto the stack + of open elements so that it is the new current node. */ + end($this->stack)->appendChild($clone); + $this->stack[] = $clone; + + /* 10. Replace the entry for entry in the list with an entry for + clone. */ + $this->a_formatting[$a] = $clone; + + /* 11. If the entry for clone in the list of active formatting + elements is not the last entry in the list, return to step 7. */ + if(end($this->a_formatting) !== $clone) { + $step_seven = true; + } else { + break; + } + } + } + + private function clearTheActiveFormattingElementsUpToTheLastMarker() { + /* When the steps below require the UA to clear the list of active + formatting elements up to the last marker, the UA must perform the + following steps: */ + + while(true) { + /* 1. Let entry be the last (most recently added) entry in the list + of active formatting elements. */ + $entry = end($this->a_formatting); + + /* 2. Remove entry from the list of active formatting elements. */ + array_pop($this->a_formatting); + + /* 3. If entry was a marker, then stop the algorithm at this point. + The list has been cleared up to the last marker. */ + if($entry === self::MARKER) { + break; + } + } + } + + private function generateImpliedEndTags($exclude = array()) { + /* When the steps below require the UA to generate implied end tags, + then, if the current node is a dd element, a dt element, an li element, + a p element, a td element, a th element, or a tr element, the UA must + act as if an end tag with the respective tag name had been seen and + then generate implied end tags again. */ + $node = end($this->stack); + $elements = array_diff(array('dd', 'dt', 'li', 'p', 'td', 'th', 'tr'), $exclude); + + while(in_array(end($this->stack)->nodeName, $elements)) { + array_pop($this->stack); + } + } + + private function getElementCategory($node) { + $name = $node->tagName; + if(in_array($name, $this->special)) + return self::SPECIAL; + + elseif(in_array($name, $this->scoping)) + return self::SCOPING; + + elseif(in_array($name, $this->formatting)) + return self::FORMATTING; + + else + return self::PHRASING; + } + + private function clearStackToTableContext($elements) { + /* When the steps above require the UA to clear the stack back to a + table context, it means that the UA must, while the current node is not + a table element or an html element, pop elements from the stack of open + elements. If this causes any elements to be popped from the stack, then + this is a parse error. */ + while(true) { + $node = end($this->stack)->nodeName; + + if(in_array($node, $elements)) { + break; + } else { + array_pop($this->stack); + } + } + } + + private function resetInsertionMode() { + /* 1. Let last be false. */ + $last = false; + $leng = count($this->stack); + + for($n = $leng - 1; $n >= 0; $n--) { + /* 2. Let node be the last node in the stack of open elements. */ + $node = $this->stack[$n]; + + /* 3. If node is the first node in the stack of open elements, then + set last to true. If the element whose innerHTML attribute is being + set is neither a td element nor a th element, then set node to the + element whose innerHTML attribute is being set. (innerHTML case) */ + if($this->stack[0]->isSameNode($node)) { + $last = true; + } + + /* 4. If node is a select element, then switch the insertion mode to + "in select" and abort these steps. (innerHTML case) */ + if($node->nodeName === 'select') { + $this->mode = self::IN_SELECT; + break; + + /* 5. If node is a td or th element, then switch the insertion mode + to "in cell" and abort these steps. */ + } elseif($node->nodeName === 'td' || $node->nodeName === 'th') { + $this->mode = self::IN_CELL; + break; + + /* 6. If node is a tr element, then switch the insertion mode to + "in row" and abort these steps. */ + } elseif($node->nodeName === 'tr') { + $this->mode = self::IN_ROW; + break; + + /* 7. If node is a tbody, thead, or tfoot element, then switch the + insertion mode to "in table body" and abort these steps. */ + } elseif(in_array($node->nodeName, array('tbody', 'thead', 'tfoot'))) { + $this->mode = self::IN_TBODY; + break; + + /* 8. If node is a caption element, then switch the insertion mode + to "in caption" and abort these steps. */ + } elseif($node->nodeName === 'caption') { + $this->mode = self::IN_CAPTION; + break; + + /* 9. If node is a colgroup element, then switch the insertion mode + to "in column group" and abort these steps. (innerHTML case) */ + } elseif($node->nodeName === 'colgroup') { + $this->mode = self::IN_CGROUP; + break; + + /* 10. If node is a table element, then switch the insertion mode + to "in table" and abort these steps. */ + } elseif($node->nodeName === 'table') { + $this->mode = self::IN_TABLE; + break; + + /* 11. If node is a head element, then switch the insertion mode + to "in body" ("in body"! not "in head"!) and abort these steps. + (innerHTML case) */ + } elseif($node->nodeName === 'head') { + $this->mode = self::IN_BODY; + break; + + /* 12. If node is a body element, then switch the insertion mode to + "in body" and abort these steps. */ + } elseif($node->nodeName === 'body') { + $this->mode = self::IN_BODY; + break; + + /* 13. If node is a frameset element, then switch the insertion + mode to "in frameset" and abort these steps. (innerHTML case) */ + } elseif($node->nodeName === 'frameset') { + $this->mode = self::IN_FRAME; + break; + + /* 14. If node is an html element, then: if the head element + pointer is null, switch the insertion mode to "before head", + otherwise, switch the insertion mode to "after head". In either + case, abort these steps. (innerHTML case) */ + } elseif($node->nodeName === 'html') { + $this->mode = ($this->head_pointer === null) + ? self::BEFOR_HEAD + : self::AFTER_HEAD; + + break; + + /* 15. If last is true, then set the insertion mode to "in body" + and abort these steps. (innerHTML case) */ + } elseif($last) { + $this->mode = self::IN_BODY; + break; + } + } + } + + private function closeCell() { + /* If the stack of open elements has a td or th element in table scope, + then act as if an end tag token with that tag name had been seen. */ + foreach(array('td', 'th') as $cell) { + if($this->elementInScope($cell, true)) { + $this->inCell(array( + 'name' => $cell, + 'type' => HTML5::ENDTAG + )); + + break; + } + } + } + + public function save() { + return $this->dom; + } +} +?> diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer.php index 16acd4157..549e4cea1 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer.php @@ -1,218 +1,218 @@ -getAll(); - $context = new HTMLPurifier_Context(); - $this->generator = new HTMLPurifier_Generator($config, $context); - } - - /** - * Main function that renders object or aspect of that object - * @note Parameters vary depending on printer - */ - // function render() {} - - /** - * Returns a start tag - * @param string $tag Tag name - * @param array $attr Attribute array - * @return string - */ - protected function start($tag, $attr = array()) - { - return $this->generator->generateFromToken( - new HTMLPurifier_Token_Start($tag, $attr ? $attr : array()) - ); - } - - /** - * Returns an end tag - * @param string $tag Tag name - * @return string - */ - protected function end($tag) - { - return $this->generator->generateFromToken( - new HTMLPurifier_Token_End($tag) - ); - } - - /** - * Prints a complete element with content inside - * @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) - { - return $this->start($tag, $attr) . - ($escape ? $this->escape($contents) : $contents) . - $this->end($tag); - } - - /** - * @param string $tag - * @param array $attr - * @return string - */ - protected function elementEmpty($tag, $attr = array()) - { - return $this->generator->generateFromToken( - new HTMLPurifier_Token_Empty($tag, $attr) - ); - } - - /** - * @param string $text - * @return string - */ - protected function text($text) - { - return $this->generator->generateFromToken( - new HTMLPurifier_Token_Text($text) - ); - } - - /** - * Prints a simple key/value row in a table. - * @param string $name Key - * @param mixed $value Value - * @return string - */ - 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'); - } - - /** - * Escapes a string for HTML output. - * @param string $string String to escape - * @return string - */ - protected function escape($string) - { - $string = HTMLPurifier_Encoder::cleanUTF8($string); - $string = htmlspecialchars($string, ENT_COMPAT, 'UTF-8'); - return $string; - } - - /** - * Takes a list of strings and turns them into a single list - * @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'; - } - $ret = ''; - $i = count($array); - foreach ($array as $value) { - $i--; - $ret .= $value; - 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 object $obj Object to determine class of - * @param string $sec_prefix Further prefix to remove - * @return string - */ - protected function getClass($obj, $sec_prefix = '') - { - static $five = null; - if ($five === null) { - $five = version_compare(PHP_VERSION, '5', '>='); - } - $prefix = 'HTMLPurifier_' . $sec_prefix; - if (!$five) { - $prefix = strtolower($prefix); - } - $class = str_replace($prefix, '', get_class($obj)); - $lclass = strtolower($class); - $class .= '('; - switch ($lclass) { - case 'enum': - $values = array(); - foreach ($obj->valid_values as $value => $bool) { - $values[] = $value; - } - $class .= implode(', ', $values); - break; - case 'css_composite': - $values = array(); - foreach ($obj->defs as $def) { - $values[] = $this->getClass($def, $sec_prefix); - } - $class .= implode(', ', $values); - break; - case 'css_multiple': - $class .= $this->getClass($obj->single, $sec_prefix) . ', '; - $class .= $obj->max; - break; - case 'css_denyelementdecorator': - $class .= $this->getClass($obj->def, $sec_prefix) . ', '; - $class .= $obj->element; - break; - case 'css_importantdecorator': - $class .= $this->getClass($obj->def, $sec_prefix); - if ($obj->allow) { - $class .= ', !important'; - } - break; - } - $class .= ')'; - return $class; - } -} - -// vim: et sw=4 sts=4 +getAll(); + $context = new HTMLPurifier_Context(); + $this->generator = new HTMLPurifier_Generator($config, $context); + } + + /** + * Main function that renders object or aspect of that object + * @note Parameters vary depending on printer + */ + // function render() {} + + /** + * Returns a start tag + * @param string $tag Tag name + * @param array $attr Attribute array + * @return string + */ + protected function start($tag, $attr = array()) + { + return $this->generator->generateFromToken( + new HTMLPurifier_Token_Start($tag, $attr ? $attr : array()) + ); + } + + /** + * Returns an end tag + * @param string $tag Tag name + * @return string + */ + protected function end($tag) + { + return $this->generator->generateFromToken( + new HTMLPurifier_Token_End($tag) + ); + } + + /** + * Prints a complete element with content inside + * @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) + { + return $this->start($tag, $attr) . + ($escape ? $this->escape($contents) : $contents) . + $this->end($tag); + } + + /** + * @param string $tag + * @param array $attr + * @return string + */ + protected function elementEmpty($tag, $attr = array()) + { + return $this->generator->generateFromToken( + new HTMLPurifier_Token_Empty($tag, $attr) + ); + } + + /** + * @param string $text + * @return string + */ + protected function text($text) + { + return $this->generator->generateFromToken( + new HTMLPurifier_Token_Text($text) + ); + } + + /** + * Prints a simple key/value row in a table. + * @param string $name Key + * @param mixed $value Value + * @return string + */ + 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'); + } + + /** + * Escapes a string for HTML output. + * @param string $string String to escape + * @return string + */ + protected function escape($string) + { + $string = HTMLPurifier_Encoder::cleanUTF8($string); + $string = htmlspecialchars($string, ENT_COMPAT, 'UTF-8'); + return $string; + } + + /** + * Takes a list of strings and turns them into a single list + * @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'; + } + $ret = ''; + $i = count($array); + foreach ($array as $value) { + $i--; + $ret .= $value; + 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 object $obj Object to determine class of + * @param string $sec_prefix Further prefix to remove + * @return string + */ + protected function getClass($obj, $sec_prefix = '') + { + static $five = null; + if ($five === null) { + $five = version_compare(PHP_VERSION, '5', '>='); + } + $prefix = 'HTMLPurifier_' . $sec_prefix; + if (!$five) { + $prefix = strtolower($prefix); + } + $class = str_replace($prefix, '', get_class($obj)); + $lclass = strtolower($class); + $class .= '('; + switch ($lclass) { + case 'enum': + $values = array(); + foreach ($obj->valid_values as $value => $bool) { + $values[] = $value; + } + $class .= implode(', ', $values); + break; + case 'css_composite': + $values = array(); + foreach ($obj->defs as $def) { + $values[] = $this->getClass($def, $sec_prefix); + } + $class .= implode(', ', $values); + break; + case 'css_multiple': + $class .= $this->getClass($obj->single, $sec_prefix) . ', '; + $class .= $obj->max; + break; + case 'css_denyelementdecorator': + $class .= $this->getClass($obj->def, $sec_prefix) . ', '; + $class .= $obj->element; + break; + case 'css_importantdecorator': + $class .= $this->getClass($obj->def, $sec_prefix); + 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 afc8c18ab..29505fe12 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/CSSDefinition.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/CSSDefinition.php @@ -1,44 +1,44 @@ -def = $config->getCSSDefinition(); - $ret = ''; - - $ret .= $this->start('div', array('class' => 'HTMLPurifier_Printer')); - $ret .= $this->start('table'); - - $ret .= $this->element('caption', 'Properties ($info)'); - - $ret .= $this->start('thead'); - $ret .= $this->start('tr'); - $ret .= $this->element('th', 'Property', array('class' => 'heavy')); - $ret .= $this->element('th', 'Definition', array('class' => 'heavy', 'style' => 'width:auto;')); - $ret .= $this->end('tr'); - $ret .= $this->end('thead'); - - ksort($this->def->info); - foreach ($this->def->info as $property => $obj) { - $name = $this->getClass($obj, 'AttrDef_'); - $ret .= $this->row($property, $name); - } - - $ret .= $this->end('table'); - $ret .= $this->end('div'); - - return $ret; - } -} - -// vim: et sw=4 sts=4 +def = $config->getCSSDefinition(); + $ret = ''; + + $ret .= $this->start('div', array('class' => 'HTMLPurifier_Printer')); + $ret .= $this->start('table'); + + $ret .= $this->element('caption', 'Properties ($info)'); + + $ret .= $this->start('thead'); + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Property', array('class' => 'heavy')); + $ret .= $this->element('th', 'Definition', array('class' => 'heavy', 'style' => 'width:auto;')); + $ret .= $this->end('tr'); + $ret .= $this->end('thead'); + + ksort($this->def->info); + foreach ($this->def->info as $property => $obj) { + $name = $this->getClass($obj, 'AttrDef_'); + $ret .= $this->row($property, $name); + } + + $ret .= $this->end('table'); + $ret .= $this->end('div'); + + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/ConfigForm.css b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/ConfigForm.css index 7af30fc3a..3ff1a88aa 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/ConfigForm.css +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/ConfigForm.css @@ -1,10 +1,10 @@ - -.hp-config {} - -.hp-config tbody th {text-align:right; padding-right:0.5em;} -.hp-config thead, .hp-config .namespace {background:#3C578C; color:#FFF;} -.hp-config .namespace th {text-align:center;} -.hp-config .verbose {display:none;} -.hp-config .controls {text-align:center;} - -/* vim: et sw=4 sts=4 */ + +.hp-config {} + +.hp-config tbody th {text-align:right; padding-right:0.5em;} +.hp-config thead, .hp-config .namespace {background:#3C578C; color:#FFF;} +.hp-config .namespace th {text-align:center;} +.hp-config .verbose {display:none;} +.hp-config .controls {text-align:center;} + +/* vim: et sw=4 sts=4 */ diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/ConfigForm.js b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/ConfigForm.js index 83e065531..cba00c9b8 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/ConfigForm.js +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/ConfigForm.js @@ -1,5 +1,5 @@ -function toggleWriteability(id_of_patient, checked) { - document.getElementById(id_of_patient).disabled = checked; -} - -// vim: et sw=4 sts=4 +function toggleWriteability(id_of_patient, checked) { + document.getElementById(id_of_patient).disabled = checked; +} + +// 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 660960f37..36100ce73 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/ConfigForm.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/ConfigForm.php @@ -1,447 +1,447 @@ -docURL = $doc_url; - $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(); - } - - /** - * Sets default column and row size for textareas in sub-printers - * @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; - } - } - - /** - * Retrieves styling, in case it is not accessible by webserver - */ - 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() - { - return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.js'); - } - - /** - * Returns HTML output for a configuration form - * @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 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) - { - if (is_array($config) && isset($config[0])) { - $gen_config = $config[0]; - $config = $config[1]; - } else { - $gen_config = $config; - } - - $this->config = $config; - $this->genConfig = $gen_config; - $this->prepareGenerator($gen_config); - - $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $config->def); - $all = array(); - foreach ($allowed as $key) { - list($ns, $directive) = $key; - $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->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->end('table'); - return $ret; - } - - /** - * Renders a single namespace - * @param $ns String namespace name - * @param array $directives array of directives to values - * @return string - */ - 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->end('tr'); - $ret .= $this->end('tbody'); - $ret .= $this->start('tbody'); - foreach ($directives as $directive => $value) { - $ret .= $this->start('tr'); - $ret .= $this->start('th'); - if ($this->docURL) { - $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; - } - - $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)); - $ret .= $this->end('td'); - $ret .= $this->end('tr'); - } - $ret .= $this->end('tbody'); - return $ret; - } - -} - -/** - * Printer decorator for directives that accept null - */ -class HTMLPurifier_Printer_ConfigForm_NullDecorator extends HTMLPurifier_Printer -{ - /** - * Printer being decorated - * @type HTMLPurifier_Printer - */ - protected $obj; - - /** - * @param HTMLPurifier_Printer $obj Printer to decorate - */ - public function __construct($obj) - { - parent::__construct(); - $this->obj = $obj; - } - - /** - * @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]; - } else { - $gen_config = $config; - } - $this->prepareGenerator($gen_config); - - $ret = ''; - $ret .= $this->start('label', array('for' => "$name:Null_$ns.$directive")); - $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose')); - $ret .= $this->text(' Null/Disabled'); - $ret .= $this->end('label'); - $attr = array( - 'type' => 'checkbox', - 'value' => '1', - 'class' => 'null-toggle', - '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)"; - } - if ($value === null) { - $attr['checked'] = 'checked'; - } - $ret .= $this->elementEmpty('input', $attr); - $ret .= $this->text(' or '); - $ret .= $this->elementEmpty('br'); - $ret .= $this->obj->render($ns, $directive, $value, $name, array($gen_config, $config)); - return $ret; - } -} - -/** - * Swiss-army knife configuration form field printer - */ -class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer -{ - /** - * @type int - */ - public $cols = 18; - - /** - * @type int - */ - public $rows = 5; - - /** - * @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]; - } else { - $gen_config = $config; - } - $this->prepareGenerator($gen_config); - // this should probably be split up a little - $ret = ''; - $def = $config->def->info["$ns.$directive"]; - if (is_int($def)) { - $type = abs($def); - } else { - $type = $def->type; - } - if (is_array($value)) { - switch ($type) { - case HTMLPurifier_VarParser::LOOKUP: - $array = $value; - $value = array(); - foreach ($array as $val => $b) { - $value[] = $val; - } - //TODO does this need a break? - case HTMLPurifier_VarParser::ALIST: - $value = implode(PHP_EOL, $value); - break; - case HTMLPurifier_VarParser::HASH: - $nvalue = ''; - foreach ($value as $i => $v) { - $nvalue .= "$i:$v" . PHP_EOL; - } - $value = $nvalue; - break; - default: - $value = ''; - } - } - if ($type === HTMLPurifier_VarParser::MIXED) { - return 'Not supported'; - $value = serialize($value); - } - $attr = array( - 'name' => "$name" . "[$ns.$directive]", - 'id' => "$name:$ns.$directive" - ); - 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'; - } - $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) { - $attr['cols'] = $this->cols; - $attr['rows'] = $this->rows; - $ret .= $this->start('textarea', $attr); - $ret .= $this->text($value); - $ret .= $this->end('textarea'); - } else { - $attr['value'] = $value; - $attr['type'] = 'text'; - $ret .= $this->elementEmpty('input', $attr); - } - return $ret; - } -} - -/** - * Bool form field printer - */ -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]; - } else { - $gen_config = $config; - } - $this->prepareGenerator($gen_config); - $ret = ''; - $ret .= $this->start('div', array('id' => "$name:$ns.$directive")); - - $ret .= $this->start('label', array('for' => "$name:Yes_$ns.$directive")); - $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose')); - $ret .= $this->text(' Yes'); - $ret .= $this->end('label'); - - $attr = array( - 'type' => 'radio', - 'name' => "$name" . "[$ns.$directive]", - 'id' => "$name:Yes_$ns.$directive", - 'value' => '1' - ); - 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")); - $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose')); - $ret .= $this->text(' No'); - $ret .= $this->end('label'); - - $attr = array( - 'type' => 'radio', - 'name' => "$name" . "[$ns.$directive]", - 'id' => "$name:No_$ns.$directive", - 'value' => '0' - ); - if ($value === false) { - $attr['checked'] = 'checked'; - } - if ($value === null) { - $attr['disabled'] = 'disabled'; - } - $ret .= $this->elementEmpty('input', $attr); - - $ret .= $this->end('div'); - - return $ret; - } -} - -// vim: et sw=4 sts=4 +docURL = $doc_url; + $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(); + } + + /** + * Sets default column and row size for textareas in sub-printers + * @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; + } + } + + /** + * Retrieves styling, in case it is not accessible by webserver + */ + 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() + { + return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.js'); + } + + /** + * Returns HTML output for a configuration form + * @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 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) + { + if (is_array($config) && isset($config[0])) { + $gen_config = $config[0]; + $config = $config[1]; + } else { + $gen_config = $config; + } + + $this->config = $config; + $this->genConfig = $gen_config; + $this->prepareGenerator($gen_config); + + $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $config->def); + $all = array(); + foreach ($allowed as $key) { + list($ns, $directive) = $key; + $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->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->end('table'); + return $ret; + } + + /** + * Renders a single namespace + * @param $ns String namespace name + * @param array $directives array of directives to values + * @return string + */ + 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->end('tr'); + $ret .= $this->end('tbody'); + $ret .= $this->start('tbody'); + foreach ($directives as $directive => $value) { + $ret .= $this->start('tr'); + $ret .= $this->start('th'); + if ($this->docURL) { + $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; + } + + $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)); + $ret .= $this->end('td'); + $ret .= $this->end('tr'); + } + $ret .= $this->end('tbody'); + return $ret; + } + +} + +/** + * Printer decorator for directives that accept null + */ +class HTMLPurifier_Printer_ConfigForm_NullDecorator extends HTMLPurifier_Printer +{ + /** + * Printer being decorated + * @type HTMLPurifier_Printer + */ + protected $obj; + + /** + * @param HTMLPurifier_Printer $obj Printer to decorate + */ + public function __construct($obj) + { + parent::__construct(); + $this->obj = $obj; + } + + /** + * @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]; + } else { + $gen_config = $config; + } + $this->prepareGenerator($gen_config); + + $ret = ''; + $ret .= $this->start('label', array('for' => "$name:Null_$ns.$directive")); + $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose')); + $ret .= $this->text(' Null/Disabled'); + $ret .= $this->end('label'); + $attr = array( + 'type' => 'checkbox', + 'value' => '1', + 'class' => 'null-toggle', + '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)"; + } + if ($value === null) { + $attr['checked'] = 'checked'; + } + $ret .= $this->elementEmpty('input', $attr); + $ret .= $this->text(' or '); + $ret .= $this->elementEmpty('br'); + $ret .= $this->obj->render($ns, $directive, $value, $name, array($gen_config, $config)); + return $ret; + } +} + +/** + * Swiss-army knife configuration form field printer + */ +class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer +{ + /** + * @type int + */ + public $cols = 18; + + /** + * @type int + */ + public $rows = 5; + + /** + * @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]; + } else { + $gen_config = $config; + } + $this->prepareGenerator($gen_config); + // this should probably be split up a little + $ret = ''; + $def = $config->def->info["$ns.$directive"]; + if (is_int($def)) { + $type = abs($def); + } else { + $type = $def->type; + } + if (is_array($value)) { + switch ($type) { + case HTMLPurifier_VarParser::LOOKUP: + $array = $value; + $value = array(); + foreach ($array as $val => $b) { + $value[] = $val; + } + //TODO does this need a break? + case HTMLPurifier_VarParser::ALIST: + $value = implode(PHP_EOL, $value); + break; + case HTMLPurifier_VarParser::HASH: + $nvalue = ''; + foreach ($value as $i => $v) { + $nvalue .= "$i:$v" . PHP_EOL; + } + $value = $nvalue; + break; + default: + $value = ''; + } + } + if ($type === HTMLPurifier_VarParser::MIXED) { + return 'Not supported'; + $value = serialize($value); + } + $attr = array( + 'name' => "$name" . "[$ns.$directive]", + 'id' => "$name:$ns.$directive" + ); + 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'; + } + $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) { + $attr['cols'] = $this->cols; + $attr['rows'] = $this->rows; + $ret .= $this->start('textarea', $attr); + $ret .= $this->text($value); + $ret .= $this->end('textarea'); + } else { + $attr['value'] = $value; + $attr['type'] = 'text'; + $ret .= $this->elementEmpty('input', $attr); + } + return $ret; + } +} + +/** + * Bool form field printer + */ +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]; + } else { + $gen_config = $config; + } + $this->prepareGenerator($gen_config); + $ret = ''; + $ret .= $this->start('div', array('id' => "$name:$ns.$directive")); + + $ret .= $this->start('label', array('for' => "$name:Yes_$ns.$directive")); + $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose')); + $ret .= $this->text(' Yes'); + $ret .= $this->end('label'); + + $attr = array( + 'type' => 'radio', + 'name' => "$name" . "[$ns.$directive]", + 'id' => "$name:Yes_$ns.$directive", + 'value' => '1' + ); + 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")); + $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose')); + $ret .= $this->text(' No'); + $ret .= $this->end('label'); + + $attr = array( + 'type' => 'radio', + 'name' => "$name" . "[$ns.$directive]", + 'id' => "$name:No_$ns.$directive", + 'value' => '0' + ); + if ($value === false) { + $attr['checked'] = 'checked'; + } + if ($value === null) { + $attr['disabled'] = 'disabled'; + } + $ret .= $this->elementEmpty('input', $attr); + + $ret .= $this->end('div'); + + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/HTMLDefinition.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/HTMLDefinition.php index 679d19ba3..5f2f2f8a7 100644 --- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/HTMLDefinition.php +++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/HTMLDefinition.php @@ -1,324 +1,324 @@ -config =& $config; - - $this->def = $config->getHTMLDefinition(); - - $ret .= $this->start('div', array('class' => 'HTMLPurifier_Printer')); - - $ret .= $this->renderDoctype(); - $ret .= $this->renderEnvironment(); - $ret .= $this->renderContentSets(); - $ret .= $this->renderInfo(); - - $ret .= $this->end('div'); - - return $ret; - } - - /** - * Renders the Doctype table - * @return string - */ - protected function renderDoctype() - { - $doctype = $this->def->doctype; - $ret = ''; - $ret .= $this->start('table'); - $ret .= $this->element('caption', 'Doctype'); - $ret .= $this->row('Name', $doctype->name); - $ret .= $this->row('XML', $doctype->xml ? 'Yes' : 'No'); - $ret .= $this->row('Default Modules', implode($doctype->modules, ', ')); - $ret .= $this->row('Default Tidy Modules', implode($doctype->tidyModules, ', ')); - $ret .= $this->end('table'); - return $ret; - } - - - /** - * Renders environment table, which is miscellaneous info - * @return string - */ - protected function renderEnvironment() - { - $def = $this->def; - - $ret = ''; - - $ret .= $this->start('table'); - $ret .= $this->element('caption', 'Environment'); - - $ret .= $this->row('Parent of fragment', $def->info_parent); - $ret .= $this->renderChildren($def->info_parent_def->child); - $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), 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->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->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->end('tr'); - - $ret .= $this->end('table'); - return $ret; - } - - /** - * Renders the Content Sets table - * @return string - */ - protected function renderContentSets() - { - $ret = ''; - $ret .= $this->start('table'); - $ret .= $this->element('caption', 'Content Sets'); - foreach ($this->def->info_content_sets as $name => $lookup) { - $ret .= $this->heavyHeader($name); - $ret .= $this->start('tr'); - $ret .= $this->element('td', $this->listifyTagLookup($lookup)); - $ret .= $this->end('tr'); - } - $ret .= $this->end('table'); - return $ret; - } - - /** - * Renders the Elements ($info) table - * @return string - */ - protected function renderInfo() - { - $ret = ''; - $ret .= $this->start('table'); - $ret .= $this->element('caption', 'Elements ($info)'); - ksort($this->def->info); - $ret .= $this->heavyHeader('Allowed tags', 2); - $ret .= $this->start('tr'); - $ret .= $this->element('td', $this->listifyTagLookup($this->def->info), array('colspan' => 2)); - $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->end('tr'); - $ret .= $this->start('tr'); - $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->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->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->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->end('tr'); - } - $ret .= $this->start('tr'); - $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)) { - $ret .= $this->row('Required attributes', $this->listify($def->required_attr)); - } - - $ret .= $this->renderChildren($def->child); - } - $ret .= $this->end('table'); - return $ret; - } - - /** - * Renders a row describing the allowed children of an element - * @param HTMLPurifier_ChildDef $def HTMLPurifier_ChildDef of pertinent element - * @return string - */ - 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; - } - 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)), - 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 $array Tag lookup array in form of array('tagname' => true) - * @return string - */ - protected function listifyTagLookup($array) - { - ksort($array); - $list = array(); - foreach ($array as $name => $discard) { - if ($name !== '#PCDATA' && !isset($this->def->info[$name])) { - continue; - } - $list[] = $name; - } - return $this->listify($list); - } - - /** - * Listifies a list of objects by retrieving class names and internal state - * @param array $array List of objects - * @return string - * @todo Also add information about internal state - */ - protected function listifyObjectList($array) - { - ksort($array); - $list = array(); - foreach ($array as $obj) { - $list[] = $this->getClass($obj, 'AttrTransform_'); - } - return $this->listify($list); - } - - /** - * Listifies a hash of attributes to AttrDef classes - * @param array $array Array hash in form of array('attrname' => HTMLPurifier_AttrDef) - * @return string - */ - protected function listifyAttr($array) - { - ksort($array); - $list = array(); - foreach ($array as $name => $obj) { - if ($obj === false) { - continue; - } - $list[] = "$name = " . $this->getClass($obj, 'AttrDef_') . ''; - } - return $this->listify($list); - } - - /** - * Creates a heavy header row - * @param string $text - * @param int $num - * @return string - */ - 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 +config =& $config; + + $this->def = $config->getHTMLDefinition(); + + $ret .= $this->start('div', array('class' => 'HTMLPurifier_Printer')); + + $ret .= $this->renderDoctype(); + $ret .= $this->renderEnvironment(); + $ret .= $this->renderContentSets(); + $ret .= $this->renderInfo(); + + $ret .= $this->end('div'); + + return $ret; + } + + /** + * Renders the Doctype table + * @return string + */ + protected function renderDoctype() + { + $doctype = $this->def->doctype; + $ret = ''; + $ret .= $this->start('table'); + $ret .= $this->element('caption', 'Doctype'); + $ret .= $this->row('Name', $doctype->name); + $ret .= $this->row('XML', $doctype->xml ? 'Yes' : 'No'); + $ret .= $this->row('Default Modules', implode($doctype->modules, ', ')); + $ret .= $this->row('Default Tidy Modules', implode($doctype->tidyModules, ', ')); + $ret .= $this->end('table'); + return $ret; + } + + + /** + * Renders environment table, which is miscellaneous info + * @return string + */ + protected function renderEnvironment() + { + $def = $this->def; + + $ret = ''; + + $ret .= $this->start('table'); + $ret .= $this->element('caption', 'Environment'); + + $ret .= $this->row('Parent of fragment', $def->info_parent); + $ret .= $this->renderChildren($def->info_parent_def->child); + $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), 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->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->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->end('tr'); + + $ret .= $this->end('table'); + return $ret; + } + + /** + * Renders the Content Sets table + * @return string + */ + protected function renderContentSets() + { + $ret = ''; + $ret .= $this->start('table'); + $ret .= $this->element('caption', 'Content Sets'); + foreach ($this->def->info_content_sets as $name => $lookup) { + $ret .= $this->heavyHeader($name); + $ret .= $this->start('tr'); + $ret .= $this->element('td', $this->listifyTagLookup($lookup)); + $ret .= $this->end('tr'); + } + $ret .= $this->end('table'); + return $ret; + } + + /** + * Renders the Elements ($info) table + * @return string + */ + protected function renderInfo() + { + $ret = ''; + $ret .= $this->start('table'); + $ret .= $this->element('caption', 'Elements ($info)'); + ksort($this->def->info); + $ret .= $this->heavyHeader('Allowed tags', 2); + $ret .= $this->start('tr'); + $ret .= $this->element('td', $this->listifyTagLookup($this->def->info), array('colspan' => 2)); + $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->end('tr'); + $ret .= $this->start('tr'); + $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->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->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->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->end('tr'); + } + $ret .= $this->start('tr'); + $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)) { + $ret .= $this->row('Required attributes', $this->listify($def->required_attr)); + } + + $ret .= $this->renderChildren($def->child); + } + $ret .= $this->end('table'); + return $ret; + } + + /** + * Renders a row describing the allowed children of an element + * @param HTMLPurifier_ChildDef $def HTMLPurifier_ChildDef of pertinent element + * @return string + */ + 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; + } + 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)), + 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 $array Tag lookup array in form of array('tagname' => true) + * @return string + */ + protected function listifyTagLookup($array) + { + ksort($array); + $list = array(); + foreach ($array as $name => $discard) { + if ($name !== '#PCDATA' && !isset($this->def->info[$name])) { + continue; + } + $list[] = $name; + } + return $this->listify($list); + } + + /** + * Listifies a list of objects by retrieving class names and internal state + * @param array $array List of objects + * @return string + * @todo Also add information about internal state + */ + protected function listifyObjectList($array) + { + ksort($array); + $list = array(); + foreach ($array as $obj) { + $list[] = $this->getClass($obj, 'AttrTransform_'); + } + return $this->listify($list); + } + + /** + * Listifies a hash of attributes to AttrDef classes + * @param array $array Array hash in form of array('attrname' => HTMLPurifier_AttrDef) + * @return string + */ + protected function listifyAttr($array) + { + ksort($array); + $list = array(); + foreach ($array as $name => $obj) { + if ($obj === false) { + continue; + } + $list[] = "$name = " . $this->getClass($obj, 'AttrDef_') . ''; + } + return $this->listify($list); + } + + /** + * Creates a heavy header row + * @param string $text + * @param int $num + * @return string + */ + 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/vendors/jquery/LICENSE.txt b/framework/vendors/jquery/LICENSE.txt index 5ca4047d2..6a62c1d83 100644 --- a/framework/vendors/jquery/LICENSE.txt +++ b/framework/vendors/jquery/LICENSE.txt @@ -1,21 +1,21 @@ -Copyright 2014 jQuery Foundation and other contributors -http://jquery.com/ - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +Copyright 2014 jQuery Foundation and other contributors +http://jquery.com/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/framework/vendors/jquery/autocomplete/LICENSE.txt b/framework/vendors/jquery/autocomplete/LICENSE.txt index 5ae737907..b6dcc6c40 100644 --- a/framework/vendors/jquery/autocomplete/LICENSE.txt +++ b/framework/vendors/jquery/autocomplete/LICENSE.txt @@ -1,20 +1,20 @@ -Copyright (c) 2008 Jörn Zaefferer - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Copyright (c) 2008 Jörn Zaefferer + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/framework/vendors/jquery/maskedinput/LICENSE.txt b/framework/vendors/jquery/maskedinput/LICENSE.txt index cc02e84d5..21329981b 100644 --- a/framework/vendors/jquery/maskedinput/LICENSE.txt +++ b/framework/vendors/jquery/maskedinput/LICENSE.txt @@ -1,22 +1,22 @@ -Copyright (c) 2007-2009 Josh Bush (digitalbush.com) - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +Copyright (c) 2007-2009 Josh Bush (digitalbush.com) + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/framework/vendors/jquery/treeview/LICENSE.txt b/framework/vendors/jquery/treeview/LICENSE.txt index 5ae737907..b6dcc6c40 100644 --- a/framework/vendors/jquery/treeview/LICENSE.txt +++ b/framework/vendors/jquery/treeview/LICENSE.txt @@ -1,20 +1,20 @@ -Copyright (c) 2008 Jörn Zaefferer - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Copyright (c) 2008 Jörn Zaefferer + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/framework/vendors/json/LICENSE.txt b/framework/vendors/json/LICENSE.txt index 6b2c70ef3..6dcc9a7f0 100644 --- a/framework/vendors/json/LICENSE.txt +++ b/framework/vendors/json/LICENSE.txt @@ -1,22 +1,22 @@ -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF -USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/framework/views/fr/error.php b/framework/views/fr/error.php index 3df6624cc..ea2bf712e 100644 --- a/framework/views/fr/error.php +++ b/framework/views/fr/error.php @@ -1,36 +1,36 @@ - - - - - -Erreur <?php echo $data['code']; ?> - - - -

          Erreur

          -

          -

          -L'erreur ci-dessus est apparue pendant que le serveur Web traitait votre requête. -

          -

          -Si vous pensez qu'il s'agit d'une erreur du serveur, veuillez contacter . -

          -

          -Merci. -

          -
          - -
          - + + + + + +Erreur <?php echo $data['code']; ?> + + + +

          Erreur

          +

          +

          +L'erreur ci-dessus est apparue pendant que le serveur Web traitait votre requête. +

          +

          +Si vous pensez qu'il s'agit d'une erreur du serveur, veuillez contacter . +

          +

          +Merci. +

          +
          + +
          + \ No newline at end of file diff --git a/framework/views/fr/error400.php b/framework/views/fr/error400.php index 31a32e396..efa4e612c 100644 --- a/framework/views/fr/error400.php +++ b/framework/views/fr/error400.php @@ -1,34 +1,34 @@ - - - - - -Demande incorrecte - - - -

          Demande Incorrecte

          -

          -

          -La demande n'a pas pu être interprétée par le serveur, due à une mauvaise syntaxe. -Merci de ne pas répeter la requete sans modifications. -

          -

          -Si vous pensez qu'il s'agit d'une erreur du serveur, veuillez contacter . -

          -
          - -
          - + + + + + +Demande incorrecte + + + +

          Demande Incorrecte

          +

          +

          +La demande n'a pas pu être interprétée par le serveur, due à une mauvaise syntaxe. +Merci de ne pas répeter la requete sans modifications. +

          +

          +Si vous pensez qu'il s'agit d'une erreur du serveur, veuillez contacter . +

          +
          + +
          + \ No newline at end of file diff --git a/framework/views/fr/error403.php b/framework/views/fr/error403.php index f7ad53cbb..625c23603 100644 --- a/framework/views/fr/error403.php +++ b/framework/views/fr/error403.php @@ -1,33 +1,33 @@ - - - - - -Accès interdit - - - -

          Accès interdit

          -

          -

          -Vous n'avez pas les autorisations nécessaires pour accéder à cette page. -

          -

          -Si vous pensez qu'il s'agit d'une erreur du serveur, veuillez contacter . -

          -
          - -
          - + + + + + +Accès interdit + + + +

          Accès interdit

          +

          +

          +Vous n'avez pas les autorisations nécessaires pour accéder à cette page. +

          +

          +Si vous pensez qu'il s'agit d'une erreur du serveur, veuillez contacter . +

          +
          + +
          + \ No newline at end of file diff --git a/framework/views/fr/error404.php b/framework/views/fr/error404.php index 1d7fb2fcc..5eb9d3bfb 100644 --- a/framework/views/fr/error404.php +++ b/framework/views/fr/error404.php @@ -1,34 +1,34 @@ - - - - - -Page Non trouvée - - - -

          Page Non trouvée

          -

          -

          -L'URL demandée n'existe pas sur ce serveur. -Si vous avez saisi l'URL manuellement, vérifiez la, et réessayez. -

          -

          -Si vous pensez qu'il s'agit d'une erreur du serveur, veuillez contacter . -

          -
          - -
          - + + + + + +Page Non trouvée + + + +

          Page Non trouvée

          +

          +

          +L'URL demandée n'existe pas sur ce serveur. +Si vous avez saisi l'URL manuellement, vérifiez la, et réessayez. +

          +

          +Si vous pensez qu'il s'agit d'une erreur du serveur, veuillez contacter . +

          +
          + +
          + \ No newline at end of file diff --git a/framework/views/fr/error500.php b/framework/views/fr/error500.php index fe6b5f065..e40513a39 100644 --- a/framework/views/fr/error500.php +++ b/framework/views/fr/error500.php @@ -1,34 +1,34 @@ - - - - - -Erreur interne du serveur - - - -

          Erreur interne du serveur

          -

          -

          -Une erreur interne est apparue lorsque le serveur web traitait votre requete. -Veuillez contacter pour signaler ce problème. -

          -

          -Merci. -

          -
          - -
          - + + + + + +Erreur interne du serveur + + + +

          Erreur interne du serveur

          +

          +

          +Une erreur interne est apparue lorsque le serveur web traitait votre requete. +Veuillez contacter pour signaler ce problème. +

          +

          +Merci. +

          +
          + +
          + \ No newline at end of file diff --git a/framework/views/fr/error503.php b/framework/views/fr/error503.php index 918e0e412..ba9d11d8f 100644 --- a/framework/views/fr/error503.php +++ b/framework/views/fr/error503.php @@ -1,32 +1,32 @@ - - - - - -Service non disponible - - - -

          Service non disponible

          -

          -Notre système est momentanément indisponible. Merci de réessayer plus tard. -

          -

          -Merci. -

          -
          - -
          - + + + + + +Service non disponible + + + +

          Service non disponible

          +

          +Notre système est momentanément indisponible. Merci de réessayer plus tard. +

          +

          +Merci. +

          +
          + +
          + \ No newline at end of file diff --git a/framework/views/fr/log-firebug.php b/framework/views/fr/log-firebug.php index 3a5539c4e..244e2f92a 100644 --- a/framework/views/fr/log-firebug.php +++ b/framework/views/fr/log-firebug.php @@ -1,23 +1,23 @@ - \ No newline at end of file diff --git a/framework/views/fr/log.php b/framework/views/fr/log.php index 175169142..1d8ab0b6f 100644 --- a/framework/views/fr/log.php +++ b/framework/views/fr/log.php @@ -1,40 +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; -} -?> -
          - Journal d'application -
          HeureNiveauCategorieMessage
          {$time}{$log[1]}{$log[2]}{$message}
          + + + + + + + + + + + +'#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; +} +?> +
          + Journal d'application +
          HeureNiveauCategorieMessage
          {$time}{$log[1]}{$log[2]}{$message}
          \ No newline at end of file diff --git a/framework/views/fr/profile-callstack-firebug.php b/framework/views/fr/profile-callstack-firebug.php index e9ae17666..cfe1a2696 100644 --- a/framework/views/fr/profile-callstack-firebug.php +++ b/framework/views/fr/profile-callstack-firebug.php @@ -1,19 +1,19 @@ - \ No newline at end of file diff --git a/framework/views/fr/profile-callstack.php b/framework/views/fr/profile-callstack.php index 5aa9095ff..adbd96ffe 100644 --- a/framework/views/fr/profile-callstack.php +++ b/framework/views/fr/profile-callstack.php @@ -1,30 +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; -} -?> -
          - Rapport de l'analyse de la pile d'appel -
          FonctionDurée (s)
          {$spaces}{$proc}{$time}
          - + + + + + + + + + +$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; +} +?> +
          + Rapport de l'analyse de la pile d'appel +
          FonctionDurée (s)
          {$spaces}{$proc}{$time}
          + diff --git a/framework/views/fr/profile-summary-firebug.php b/framework/views/fr/profile-summary-firebug.php index 01afc08e2..4b9454c5a 100644 --- a/framework/views/fr/profile-summary-firebug.php +++ b/framework/views/fr/profile-summary-firebug.php @@ -1,22 +1,22 @@ - + diff --git a/framework/views/fr/profile-summary.php b/framework/views/fr/profile-summary.php index 9501431d1..fb2c21bae 100644 --- a/framework/views/fr/profile-summary.php +++ b/framework/views/fr/profile-summary.php @@ -1,41 +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; -} -?> -
          - Sommaire du rapport de profilage - (Durée: getExecutionTime()); ?>s, - Memoire: getMemoryUsage()/1024); ?>KB) -
          FonctionNbTotal (s)Moy. (s)Min. (s)Max. (s)
          {$proc}{$entry[1]}{$total}{$average}{$min}{$max}
          - + + + + + + + + + + + + + +$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; +} +?> +
          + Sommaire du rapport de profilage + (Durée: getExecutionTime()); ?>s, + Memoire: getMemoryUsage()/1024); ?>KB) +
          FonctionNbTotal (s)Moy. (s)Min. (s)Max. (s)
          {$proc}{$entry[1]}{$total}{$average}{$min}{$max}
          + diff --git a/framework/views/nl/profile-summary-firebug.php b/framework/views/nl/profile-summary-firebug.php index fd90c1a3b..24584e9b9 100644 --- a/framework/views/nl/profile-summary-firebug.php +++ b/framework/views/nl/profile-summary-firebug.php @@ -1,20 +1,20 @@ - \ No newline at end of file diff --git a/framework/views/nl/profile-summary.php b/framework/views/nl/profile-summary.php index 6d904dec9..a6d9dca40 100644 --- a/framework/views/nl/profile-summary.php +++ b/framework/views/nl/profile-summary.php @@ -1,41 +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; -} -?> -
          - Profiling Samenvatting Rapport - (Tijd: getExecutionTime()); ?>s, - Geheugen: getMemoryUsage()/1024); ?>KB) -
          ProcedureAantalTotaal (s)Gem. (s)Min. (s)Max. (s)
          {$proc}{$entry[1]}{$total}{$average}{$min}{$max}
          + + + + + + + + + + + + + +$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; +} +?> +
          + Profiling Samenvatting Rapport + (Tijd: getExecutionTime()); ?>s, + Geheugen: getMemoryUsage()/1024); ?>KB) +
          ProcedureAantalTotaal (s)Gem. (s)Min. (s)Max. (s)
          {$proc}{$entry[1]}{$total}{$average}{$min}{$max}
          \ No newline at end of file diff --git a/framework/views/sk/log.php b/framework/views/sk/log.php index a8c704c76..caab08a22 100644 --- a/framework/views/sk/log.php +++ b/framework/views/sk/log.php @@ -1,46 +1,46 @@ - - - - - - - - - - - -'#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); - $time .= "
          [+".round(($log[3]-YII_BEGIN_TIME)*1000, 0).'] ms'; - - echo << - - - - - -EOD; -} -?> -
          - Log aplikácie - -
          ČasÚroveňKategóriaZpráva
          {$time}{$log[1]}{$log[2]}{$message}
          + + + + + + + + + + + +'#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); + $time .= "
          [+".round(($log[3]-YII_BEGIN_TIME)*1000, 0).'] ms'; + + echo << + + + + + +EOD; +} +?> +
          + Log aplikácie + +
          ČasÚroveňKategóriaZpráva
          {$time}{$log[1]}{$log[2]}{$message}
          \ No newline at end of file diff --git a/framework/views/zh_cn/profile-callstack.php b/framework/views/zh_cn/profile-callstack.php index 61b300655..60c5a96ab 100644 --- a/framework/views/zh_cn/profile-callstack.php +++ b/framework/views/zh_cn/profile-callstack.php @@ -8,21 +8,21 @@ 步骤 时间 (秒) - $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 ); - + $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 << {$spaces}{$proc} {$time} -EOD; -} +EOD; +} ?> \ No newline at end of file diff --git a/framework/web/js/source/jquery.ajaxqueue.js b/framework/web/js/source/jquery.ajaxqueue.js index bdd2e4f82..ca42082a8 100644 --- a/framework/web/js/source/jquery.ajaxqueue.js +++ b/framework/web/js/source/jquery.ajaxqueue.js @@ -1,116 +1,116 @@ -/** - * Ajax Queue Plugin - * - * Homepage: http://jquery.com/plugins/project/ajaxqueue - * Documentation: http://docs.jquery.com/AjaxQueue - */ - -/** - - -
            - - */ -/* - * Queued Ajax requests. - * A new Ajax request won't be started until the previous queued - * request has finished. - */ - -/* - * Synced Ajax requests. - * The Ajax request will happen as soon as you call this method, but - * the callbacks (success/error/complete) won't fire until all previous - * synced requests have been completed. - */ - - -(function($) { - - var ajax = $.ajax; - - var pendingRequests = {}; - - var synced = []; - var syncedData = []; - - $.ajax = function(settings) { - // create settings for compatibility with ajaxSetup - settings = jQuery.extend(settings, jQuery.extend({}, jQuery.ajaxSettings, settings)); - - var port = settings.port; - - switch(settings.mode) { - case "abort": - if ( pendingRequests[port] ) { - pendingRequests[port].abort(); - } - return pendingRequests[port] = ajax.apply(this, arguments); - case "queue": - var _old = settings.complete; - settings.complete = function(){ - if ( _old ) - _old.apply( this, arguments ); - jQuery([ajax]).dequeue("ajax" + port );; - }; - - jQuery([ ajax ]).queue("ajax" + port, function(){ - ajax( settings ); - }); - return; - case "sync": - var pos = synced.length; - - synced[ pos ] = { - error: settings.error, - success: settings.success, - complete: settings.complete, - done: false - }; - - syncedData[ pos ] = { - error: [], - success: [], - complete: [] - }; - - settings.error = function(){ syncedData[ pos ].error = arguments; }; - settings.success = function(){ syncedData[ pos ].success = arguments; }; - settings.complete = function(){ - syncedData[ pos ].complete = arguments; - synced[ pos ].done = true; - - if ( pos == 0 || !synced[ pos-1 ] ) - for ( var i = pos; i < synced.length && synced[i].done; i++ ) { - if ( synced[i].error ) synced[i].error.apply( jQuery, syncedData[i].error ); - if ( synced[i].success ) synced[i].success.apply( jQuery, syncedData[i].success ); - if ( synced[i].complete ) synced[i].complete.apply( jQuery, syncedData[i].complete ); - - synced[i] = null; - syncedData[i] = null; - } - }; - } - return ajax.apply(this, arguments); - }; - +/** + * Ajax Queue Plugin + * + * Homepage: http://jquery.com/plugins/project/ajaxqueue + * Documentation: http://docs.jquery.com/AjaxQueue + */ + +/** + + +
              + + */ +/* + * Queued Ajax requests. + * A new Ajax request won't be started until the previous queued + * request has finished. + */ + +/* + * Synced Ajax requests. + * The Ajax request will happen as soon as you call this method, but + * the callbacks (success/error/complete) won't fire until all previous + * synced requests have been completed. + */ + + +(function($) { + + var ajax = $.ajax; + + var pendingRequests = {}; + + var synced = []; + var syncedData = []; + + $.ajax = function(settings) { + // create settings for compatibility with ajaxSetup + settings = jQuery.extend(settings, jQuery.extend({}, jQuery.ajaxSettings, settings)); + + var port = settings.port; + + switch(settings.mode) { + case "abort": + if ( pendingRequests[port] ) { + pendingRequests[port].abort(); + } + return pendingRequests[port] = ajax.apply(this, arguments); + case "queue": + var _old = settings.complete; + settings.complete = function(){ + if ( _old ) + _old.apply( this, arguments ); + jQuery([ajax]).dequeue("ajax" + port );; + }; + + jQuery([ ajax ]).queue("ajax" + port, function(){ + ajax( settings ); + }); + return; + case "sync": + var pos = synced.length; + + synced[ pos ] = { + error: settings.error, + success: settings.success, + complete: settings.complete, + done: false + }; + + syncedData[ pos ] = { + error: [], + success: [], + complete: [] + }; + + settings.error = function(){ syncedData[ pos ].error = arguments; }; + settings.success = function(){ syncedData[ pos ].success = arguments; }; + settings.complete = function(){ + syncedData[ pos ].complete = arguments; + synced[ pos ].done = true; + + if ( pos == 0 || !synced[ pos-1 ] ) + for ( var i = pos; i < synced.length && synced[i].done; i++ ) { + if ( synced[i].error ) synced[i].error.apply( jQuery, syncedData[i].error ); + if ( synced[i].success ) synced[i].success.apply( jQuery, syncedData[i].success ); + if ( synced[i].complete ) synced[i].complete.apply( jQuery, syncedData[i].complete ); + + synced[i] = null; + syncedData[i] = null; + } + }; + } + return ajax.apply(this, arguments); + }; + })(jQuery); \ No newline at end of file diff --git a/framework/web/js/source/jquery.multifile.js b/framework/web/js/source/jquery.multifile.js index 47cd1c7b0..e89eb08d3 100644 --- a/framework/web/js/source/jquery.multifile.js +++ b/framework/web/js/source/jquery.multifile.js @@ -1,539 +1,539 @@ -/* - ### jQuery Multiple File Upload Plugin v1.48 - 2012-07-19 ### - * Home: http://www.fyneworks.com/jquery/multiple-file-upload/ - * Code: http://code.google.com/p/jquery-multifile-plugin/ - * - * Licensed under http://en.wikipedia.org/wiki/MIT_License - ### -*/ - -/*# AVOID COLLISIONS #*/ -;if(window.jQuery) (function($){ -/*# AVOID COLLISIONS #*/ - - // plugin initialization - $.fn.MultiFile = function(options){ - if(this.length==0) return this; // quick fail - - // Handle API methods - if(typeof arguments[0]=='string'){ - // Perform API methods on individual elements - if(this.length>1){ - var args = arguments; - return this.each(function(){ - $.fn.MultiFile.apply($(this), args); - }); - }; - // Invoke API method handler - $.fn.MultiFile[arguments[0]].apply(this, $.makeArray(arguments).slice(1) || []); - // Quick exit... - return this; - }; - - // Initialize options for this call - var options = $.extend( - {}/* new object */, - $.fn.MultiFile.options/* default options */, - options || {} /* just-in-time options */ - ); - - // Empty Element Fix!!! - // this code will automatically intercept native form submissions - // and disable empty file elements - $('form') - .not('MultiFile-intercepted') - .addClass('MultiFile-intercepted') - .submit($.fn.MultiFile.disableEmpty); - - //### http://plugins.jquery.com/node/1363 - // utility method to integrate this plugin with others... - if($.fn.MultiFile.options.autoIntercept){ - $.fn.MultiFile.intercept( $.fn.MultiFile.options.autoIntercept /* array of methods to intercept */ ); - $.fn.MultiFile.options.autoIntercept = null; /* only run this once */ - }; - - // loop through each matched element - this - .not('.MultiFile-applied') - .addClass('MultiFile-applied') - .each(function(){ - //##################################################################### - // MAIN PLUGIN FUNCTIONALITY - START - //##################################################################### - - // BUG 1251 FIX: http://plugins.jquery.com/project/comments/add/1251 - // variable group_count would repeat itself on multiple calls to the plugin. - // this would cause a conflict with multiple elements - // changes scope of variable to global so id will be unique over n calls - window.MultiFile = (window.MultiFile || 0) + 1; - var group_count = window.MultiFile; - - // Copy parent attributes - Thanks to Jonas Wagner - // we will use this one to create new input elements - var MultiFile = {e:this, E:$(this), clone:$(this).clone()}; - - //=== - - //# USE CONFIGURATION - if(typeof options=='number') options = {max:options}; - var o = $.extend({}, - $.fn.MultiFile.options, - options || {}, - ($.metadata? MultiFile.E.metadata(): ($.meta?MultiFile.E.data():null)) || {}, /* metadata options */ - {} /* internals */ - ); - // limit number of files that can be selected? - if(!(o.max>0) /*IsNull(MultiFile.max)*/){ - o.max = MultiFile.E.attr('maxlength'); - }; - if(!(o.max>0) /*IsNull(MultiFile.max)*/){ - o.max = (String(MultiFile.e.className.match(/\b(max|limit)\-([0-9]+)\b/gi) || ['']).match(/[0-9]+/gi) || [''])[0]; - if(!(o.max>0)) o.max = -1; - else o.max = String(o.max).match(/[0-9]+/gi)[0]; - } - o.max = new Number(o.max); - // limit extensions? - o.accept = o.accept || MultiFile.E.attr('accept') || ''; - if(!o.accept){ - o.accept = (MultiFile.e.className.match(/\b(accept\-[\w\|]+)\b/gi)) || ''; - o.accept = new String(o.accept).replace(/^(accept|ext)\-/i,''); - }; - - //=== - - // APPLY CONFIGURATION - $.extend(MultiFile, o || {}); - MultiFile.STRING = $.extend({},$.fn.MultiFile.options.STRING,MultiFile.STRING); - - //=== - - //######################################### - // PRIVATE PROPERTIES/METHODS - $.extend(MultiFile, { - n: 0, // How many elements are currently selected? - slaves: [], files: [], - instanceKey: MultiFile.e.id || 'MultiFile'+String(group_count), // Instance Key? - generateID: function(z){ return MultiFile.instanceKey + (z>0 ?'_F'+String(z):''); }, - trigger: function(event, element){ - var handler = MultiFile[event], value = $(element).attr('value'); - if(handler){ - var returnValue = handler(element, value, MultiFile); - if( returnValue!=null ) return returnValue; - } - return true; - } - }); - - //=== - - // Setup dynamic regular expression for extension validation - // - thanks to John-Paul Bader: http://smyck.de/2006/08/11/javascript-dynamic-regular-expresions/ - if(String(MultiFile.accept).length>1){ - MultiFile.accept = MultiFile.accept.replace(/\W+/g,'|').replace(/^\W|\W$/g,''); - MultiFile.rxAccept = new RegExp('\\.('+(MultiFile.accept?MultiFile.accept:'')+')$','gi'); - }; - - //=== - - // Create wrapper to hold our file list - MultiFile.wrapID = MultiFile.instanceKey+'_wrap'; // Wrapper ID? - MultiFile.E.wrap('
              '); - MultiFile.wrapper = $('#'+MultiFile.wrapID+''); - - //=== - - // MultiFile MUST have a name - default: file1[], file2[], file3[] - MultiFile.e.name = MultiFile.e.name || 'file'+ group_count +'[]'; - - //=== - - if(!MultiFile.list){ - // Create a wrapper for the list - // * OPERA BUG: NO_MODIFICATION_ALLOWED_ERR ('list' is a read-only property) - // this change allows us to keep the files in the order they were selected - MultiFile.wrapper.append( '
              ' ); - MultiFile.list = $('#'+MultiFile.wrapID+'_list'); - }; - MultiFile.list = $(MultiFile.list); - - //=== - - // Bind a new element - MultiFile.addSlave = function( slave, slave_count ){ - //if(window.console) console.log('MultiFile.addSlave',slave_count); - - // Keep track of how many elements have been displayed - MultiFile.n++; - // Add reference to master element - slave.MultiFile = MultiFile; - - // BUG FIX: http://plugins.jquery.com/node/1495 - // Clear identifying properties from clones - if(slave_count>0) slave.id = slave.name = ''; - - // Define element's ID and name (upload components need this!) - //slave.id = slave.id || MultiFile.generateID(slave_count); - if(slave_count>0) slave.id = MultiFile.generateID(slave_count); - //FIX for: http://code.google.com/p/jquery-multifile-plugin/issues/detail?id=23 - - // 2008-Apr-29: New customizable naming convention (see url below) - // http://groups.google.com/group/jquery-dev/browse_frm/thread/765c73e41b34f924# - slave.name = String(MultiFile.namePattern - /*master name*/.replace(/\$name/gi,$(MultiFile.clone).attr('name')) - /*master id */.replace(/\$id/gi, $(MultiFile.clone).attr('id')) - /*group count*/.replace(/\$g/gi, group_count)//(group_count>0?group_count:'')) - /*slave count*/.replace(/\$i/gi, slave_count)//(slave_count>0?slave_count:'')) - ); - - // If we've reached maximum number, disable input slave - if( (MultiFile.max > 0) && ((MultiFile.n-1) > (MultiFile.max)) )//{ // MultiFile.n Starts at 1, so subtract 1 to find true count - slave.disabled = true; - //}; - - // Remember most recent slave - MultiFile.current = MultiFile.slaves[slave_count] = slave; - - // We'll use jQuery from now on - slave = $(slave); - - // Clear value - slave.val('').attr('value','')[0].value = ''; - - // Stop plugin initializing on slaves - slave.addClass('MultiFile-applied'); - - // Triggered when a file is selected - slave.change(function(){ - //if(window.console) console.log('MultiFile.slave.change',slave_count); - - // Lose focus to stop IE7 firing onchange again - $(this).blur(); - - //# Trigger Event! onFileSelect - if(!MultiFile.trigger('onFileSelect', this, MultiFile)) return false; - //# End Event! - - //# Retrive value of selected file from element - var ERROR = '', v = String(this.value || ''/*.attr('value)*/); - - // check extension - if(MultiFile.accept && v && !v.match(MultiFile.rxAccept))//{ - ERROR = MultiFile.STRING.denied.replace('$ext', String(v.match(/\.\w{1,4}$/gi))); - //} - //}; - - // Disallow duplicates - for(var f in MultiFile.slaves)//{ - if(MultiFile.slaves[f] && MultiFile.slaves[f]!=this)//{ - //console.log(MultiFile.slaves[f],MultiFile.slaves[f].value); - if(MultiFile.slaves[f].value==v)//{ - ERROR = MultiFile.STRING.duplicate.replace('$file', v.match(/[^\/\\]+$/gi)); - //}; - //}; - //}; - - // Create a new file input element - var newEle = $(MultiFile.clone).clone();// Copy parent attributes - Thanks to Jonas Wagner - //# Let's remember which input we've generated so - // we can disable the empty ones before submission - // See: http://plugins.jquery.com/node/1495 - newEle.addClass('MultiFile'); - - // Handle error - if(ERROR!=''){ - // Handle error - MultiFile.error(ERROR); - - // 2007-06-24: BUG FIX - Thanks to Adrian Wrbel - // Ditch the trouble maker and add a fresh new element - MultiFile.n--; - MultiFile.addSlave(newEle[0], slave_count); - slave.parent().prepend(newEle); - slave.remove(); - return false; - }; - - // Hide this element (NB: display:none is evil!) - $(this).css({ position:'absolute', top: '-3000px' }); - - // Add new element to the form - slave.after(newEle); - - // Update list - MultiFile.addToList( this, slave_count ); - - // Bind functionality - MultiFile.addSlave( newEle[0], slave_count+1 ); - - //# Trigger Event! afterFileSelect - if(!MultiFile.trigger('afterFileSelect', this, MultiFile)) return false; - //# End Event! - - }); // slave.change() - - // Save control to element - $(slave).data('MultiFile', MultiFile); - - };// MultiFile.addSlave - // Bind a new element - - - - // Add a new file to the list - MultiFile.addToList = function( slave, slave_count ){ - //if(window.console) console.log('MultiFile.addToList',slave_count); - - //# Trigger Event! onFileAppend - if(!MultiFile.trigger('onFileAppend', slave, MultiFile)) return false; - //# End Event! - - // Create label elements - var - r = $('
              '), - v = String(slave.value || ''/*.attr('value)*/), - a = $(''+MultiFile.STRING.file.replace('$file', v.match(/[^\/\\]+$/gi)[0])+''), - b = $(''+MultiFile.STRING.remove+''); - - // Insert label - MultiFile.list.append( - r.append(b, ' ', a) - ); - - b - .click(function(){ - - //# Trigger Event! onFileRemove - if(!MultiFile.trigger('onFileRemove', slave, MultiFile)) return false; - //# End Event! - - MultiFile.n--; - MultiFile.current.disabled = false; - - // Remove element, remove label, point to current - MultiFile.slaves[slave_count] = null; - $(slave).remove(); - $(this).parent().remove(); - - // Show most current element again (move into view) and clear selection - $(MultiFile.current).css({ position:'', top: '' }); - $(MultiFile.current).reset().val('').attr('value', '')[0].value = ''; - - //# Trigger Event! afterFileRemove - if(!MultiFile.trigger('afterFileRemove', slave, MultiFile)) return false; - //# End Event! - - return false; - }); - - //# Trigger Event! afterFileAppend - if(!MultiFile.trigger('afterFileAppend', slave, MultiFile)) return false; - //# End Event! - - }; // MultiFile.addToList - // Add element to selected files list - - - - // Bind functionality to the first element - if(!MultiFile.MultiFile) MultiFile.addSlave(MultiFile.e, 0); - - // Increment control count - //MultiFile.I++; // using window.MultiFile - MultiFile.n++; - - // Save control to element - MultiFile.E.data('MultiFile', MultiFile); - - - //##################################################################### - // MAIN PLUGIN FUNCTIONALITY - END - //##################################################################### - }); // each element - }; - - /*--------------------------------------------------------*/ - - /* - ### Core functionality and API ### - */ - $.extend($.fn.MultiFile, { - /** - * This method removes all selected files - * - * Returns a jQuery collection of all affected elements. - * - * @name reset - * @type jQuery - * @cat Plugins/MultiFile - * @author Diego A. (http://www.fyneworks.com/) - * - * @example $.fn.MultiFile.reset(); - */ - reset: function(){ - var settings = $(this).data('MultiFile'); - //if(settings) settings.wrapper.find('a.MultiFile-remove').click(); - if(settings) settings.list.find('a.MultiFile-remove').click(); - return $(this); - }, - - - /** - * This utility makes it easy to disable all 'empty' file elements in the document before submitting a form. - * It marks the affected elements so they can be easily re-enabled after the form submission or validation. - * - * Returns a jQuery collection of all affected elements. - * - * @name disableEmpty - * @type jQuery - * @cat Plugins/MultiFile - * @author Diego A. (http://www.fyneworks.com/) - * - * @example $.fn.MultiFile.disableEmpty(); - * @param String class (optional) A string specifying a class to be applied to all affected elements - Default: 'mfD'. - */ - disableEmpty: function(klass){ klass = (typeof(klass)=='string'?klass:'')||'mfD'; - var o = []; - $('input:file.MultiFile').each(function(){ if($(this).val()=='') o[o.length] = this; }); - return $(o).each(function(){ this.disabled = true }).addClass(klass); - }, - - - /** - * This method re-enables 'empty' file elements that were disabled (and marked) with the $.fn.MultiFile.disableEmpty method. - * - * Returns a jQuery collection of all affected elements. - * - * @name reEnableEmpty - * @type jQuery - * @cat Plugins/MultiFile - * @author Diego A. (http://www.fyneworks.com/) - * - * @example $.fn.MultiFile.reEnableEmpty(); - * @param String klass (optional) A string specifying the class that was used to mark affected elements - Default: 'mfD'. - */ - reEnableEmpty: function(klass){ klass = (typeof(klass)=='string'?klass:'')||'mfD'; - return $('input:file.'+klass).removeClass(klass).each(function(){ this.disabled = false }); - }, - - - /** - * This method will intercept other jQuery plugins and disable empty file input elements prior to form submission - * - - * @name intercept - * @cat Plugins/MultiFile - * @author Diego A. (http://www.fyneworks.com/) - * - * @example $.fn.MultiFile.intercept(); - * @param Array methods (optional) Array of method names to be intercepted - */ - intercepted: {}, - intercept: function(methods, context, args){ - var method, value; args = args || []; - if(args.constructor.toString().indexOf("Array")<0) args = [ args ]; - if(typeof(methods)=='function'){ - $.fn.MultiFile.disableEmpty(); - value = methods.apply(context || window, args); - //SEE-http://code.google.com/p/jquery-multifile-plugin/issues/detail?id=27 - setTimeout(function(){ $.fn.MultiFile.reEnableEmpty() },1000); - return value; - }; - if(methods.constructor.toString().indexOf("Array")<0) methods = [methods]; - for(var i=0;i'), - css: { - border:'none', padding:'15px', size:'12.0pt', - backgroundColor:'#900', color:'#fff', - opacity:'.8','-webkit-border-radius': '10px','-moz-border-radius': '10px' - } - }); - window.setTimeout($.unblockUI, 2000); - } - else//{// save a byte! - */ - alert(s); - //}// save a byte! - } - }; //} }); - - /*--------------------------------------------------------*/ - - /* - ### Additional Methods ### - Required functionality outside the plugin's scope - */ - - // Native input reset method - because this alone doesn't always work: $(element).val('').attr('value', '')[0].value = ''; - $.fn.reset = function(){ return this.each(function(){ try{ this.reset(); }catch(e){} }); }; - - /*--------------------------------------------------------*/ - - /* - ### Default implementation ### - The plugin will attach itself to file inputs - with the class 'multi' when the page loads - */ - $(function(){ - //$("input:file.multi").MultiFile(); - $("input[type=file].multi").MultiFile(); - }); - - - -/*# AVOID COLLISIONS #*/ -})(jQuery); -/*# AVOID COLLISIONS #*/ +/* + ### jQuery Multiple File Upload Plugin v1.48 - 2012-07-19 ### + * Home: http://www.fyneworks.com/jquery/multiple-file-upload/ + * Code: http://code.google.com/p/jquery-multifile-plugin/ + * + * Licensed under http://en.wikipedia.org/wiki/MIT_License + ### +*/ + +/*# AVOID COLLISIONS #*/ +;if(window.jQuery) (function($){ +/*# AVOID COLLISIONS #*/ + + // plugin initialization + $.fn.MultiFile = function(options){ + if(this.length==0) return this; // quick fail + + // Handle API methods + if(typeof arguments[0]=='string'){ + // Perform API methods on individual elements + if(this.length>1){ + var args = arguments; + return this.each(function(){ + $.fn.MultiFile.apply($(this), args); + }); + }; + // Invoke API method handler + $.fn.MultiFile[arguments[0]].apply(this, $.makeArray(arguments).slice(1) || []); + // Quick exit... + return this; + }; + + // Initialize options for this call + var options = $.extend( + {}/* new object */, + $.fn.MultiFile.options/* default options */, + options || {} /* just-in-time options */ + ); + + // Empty Element Fix!!! + // this code will automatically intercept native form submissions + // and disable empty file elements + $('form') + .not('MultiFile-intercepted') + .addClass('MultiFile-intercepted') + .submit($.fn.MultiFile.disableEmpty); + + //### http://plugins.jquery.com/node/1363 + // utility method to integrate this plugin with others... + if($.fn.MultiFile.options.autoIntercept){ + $.fn.MultiFile.intercept( $.fn.MultiFile.options.autoIntercept /* array of methods to intercept */ ); + $.fn.MultiFile.options.autoIntercept = null; /* only run this once */ + }; + + // loop through each matched element + this + .not('.MultiFile-applied') + .addClass('MultiFile-applied') + .each(function(){ + //##################################################################### + // MAIN PLUGIN FUNCTIONALITY - START + //##################################################################### + + // BUG 1251 FIX: http://plugins.jquery.com/project/comments/add/1251 + // variable group_count would repeat itself on multiple calls to the plugin. + // this would cause a conflict with multiple elements + // changes scope of variable to global so id will be unique over n calls + window.MultiFile = (window.MultiFile || 0) + 1; + var group_count = window.MultiFile; + + // Copy parent attributes - Thanks to Jonas Wagner + // we will use this one to create new input elements + var MultiFile = {e:this, E:$(this), clone:$(this).clone()}; + + //=== + + //# USE CONFIGURATION + if(typeof options=='number') options = {max:options}; + var o = $.extend({}, + $.fn.MultiFile.options, + options || {}, + ($.metadata? MultiFile.E.metadata(): ($.meta?MultiFile.E.data():null)) || {}, /* metadata options */ + {} /* internals */ + ); + // limit number of files that can be selected? + if(!(o.max>0) /*IsNull(MultiFile.max)*/){ + o.max = MultiFile.E.attr('maxlength'); + }; + if(!(o.max>0) /*IsNull(MultiFile.max)*/){ + o.max = (String(MultiFile.e.className.match(/\b(max|limit)\-([0-9]+)\b/gi) || ['']).match(/[0-9]+/gi) || [''])[0]; + if(!(o.max>0)) o.max = -1; + else o.max = String(o.max).match(/[0-9]+/gi)[0]; + } + o.max = new Number(o.max); + // limit extensions? + o.accept = o.accept || MultiFile.E.attr('accept') || ''; + if(!o.accept){ + o.accept = (MultiFile.e.className.match(/\b(accept\-[\w\|]+)\b/gi)) || ''; + o.accept = new String(o.accept).replace(/^(accept|ext)\-/i,''); + }; + + //=== + + // APPLY CONFIGURATION + $.extend(MultiFile, o || {}); + MultiFile.STRING = $.extend({},$.fn.MultiFile.options.STRING,MultiFile.STRING); + + //=== + + //######################################### + // PRIVATE PROPERTIES/METHODS + $.extend(MultiFile, { + n: 0, // How many elements are currently selected? + slaves: [], files: [], + instanceKey: MultiFile.e.id || 'MultiFile'+String(group_count), // Instance Key? + generateID: function(z){ return MultiFile.instanceKey + (z>0 ?'_F'+String(z):''); }, + trigger: function(event, element){ + var handler = MultiFile[event], value = $(element).attr('value'); + if(handler){ + var returnValue = handler(element, value, MultiFile); + if( returnValue!=null ) return returnValue; + } + return true; + } + }); + + //=== + + // Setup dynamic regular expression for extension validation + // - thanks to John-Paul Bader: http://smyck.de/2006/08/11/javascript-dynamic-regular-expresions/ + if(String(MultiFile.accept).length>1){ + MultiFile.accept = MultiFile.accept.replace(/\W+/g,'|').replace(/^\W|\W$/g,''); + MultiFile.rxAccept = new RegExp('\\.('+(MultiFile.accept?MultiFile.accept:'')+')$','gi'); + }; + + //=== + + // Create wrapper to hold our file list + MultiFile.wrapID = MultiFile.instanceKey+'_wrap'; // Wrapper ID? + MultiFile.E.wrap('
              '); + MultiFile.wrapper = $('#'+MultiFile.wrapID+''); + + //=== + + // MultiFile MUST have a name - default: file1[], file2[], file3[] + MultiFile.e.name = MultiFile.e.name || 'file'+ group_count +'[]'; + + //=== + + if(!MultiFile.list){ + // Create a wrapper for the list + // * OPERA BUG: NO_MODIFICATION_ALLOWED_ERR ('list' is a read-only property) + // this change allows us to keep the files in the order they were selected + MultiFile.wrapper.append( '
              ' ); + MultiFile.list = $('#'+MultiFile.wrapID+'_list'); + }; + MultiFile.list = $(MultiFile.list); + + //=== + + // Bind a new element + MultiFile.addSlave = function( slave, slave_count ){ + //if(window.console) console.log('MultiFile.addSlave',slave_count); + + // Keep track of how many elements have been displayed + MultiFile.n++; + // Add reference to master element + slave.MultiFile = MultiFile; + + // BUG FIX: http://plugins.jquery.com/node/1495 + // Clear identifying properties from clones + if(slave_count>0) slave.id = slave.name = ''; + + // Define element's ID and name (upload components need this!) + //slave.id = slave.id || MultiFile.generateID(slave_count); + if(slave_count>0) slave.id = MultiFile.generateID(slave_count); + //FIX for: http://code.google.com/p/jquery-multifile-plugin/issues/detail?id=23 + + // 2008-Apr-29: New customizable naming convention (see url below) + // http://groups.google.com/group/jquery-dev/browse_frm/thread/765c73e41b34f924# + slave.name = String(MultiFile.namePattern + /*master name*/.replace(/\$name/gi,$(MultiFile.clone).attr('name')) + /*master id */.replace(/\$id/gi, $(MultiFile.clone).attr('id')) + /*group count*/.replace(/\$g/gi, group_count)//(group_count>0?group_count:'')) + /*slave count*/.replace(/\$i/gi, slave_count)//(slave_count>0?slave_count:'')) + ); + + // If we've reached maximum number, disable input slave + if( (MultiFile.max > 0) && ((MultiFile.n-1) > (MultiFile.max)) )//{ // MultiFile.n Starts at 1, so subtract 1 to find true count + slave.disabled = true; + //}; + + // Remember most recent slave + MultiFile.current = MultiFile.slaves[slave_count] = slave; + + // We'll use jQuery from now on + slave = $(slave); + + // Clear value + slave.val('').attr('value','')[0].value = ''; + + // Stop plugin initializing on slaves + slave.addClass('MultiFile-applied'); + + // Triggered when a file is selected + slave.change(function(){ + //if(window.console) console.log('MultiFile.slave.change',slave_count); + + // Lose focus to stop IE7 firing onchange again + $(this).blur(); + + //# Trigger Event! onFileSelect + if(!MultiFile.trigger('onFileSelect', this, MultiFile)) return false; + //# End Event! + + //# Retrive value of selected file from element + var ERROR = '', v = String(this.value || ''/*.attr('value)*/); + + // check extension + if(MultiFile.accept && v && !v.match(MultiFile.rxAccept))//{ + ERROR = MultiFile.STRING.denied.replace('$ext', String(v.match(/\.\w{1,4}$/gi))); + //} + //}; + + // Disallow duplicates + for(var f in MultiFile.slaves)//{ + if(MultiFile.slaves[f] && MultiFile.slaves[f]!=this)//{ + //console.log(MultiFile.slaves[f],MultiFile.slaves[f].value); + if(MultiFile.slaves[f].value==v)//{ + ERROR = MultiFile.STRING.duplicate.replace('$file', v.match(/[^\/\\]+$/gi)); + //}; + //}; + //}; + + // Create a new file input element + var newEle = $(MultiFile.clone).clone();// Copy parent attributes - Thanks to Jonas Wagner + //# Let's remember which input we've generated so + // we can disable the empty ones before submission + // See: http://plugins.jquery.com/node/1495 + newEle.addClass('MultiFile'); + + // Handle error + if(ERROR!=''){ + // Handle error + MultiFile.error(ERROR); + + // 2007-06-24: BUG FIX - Thanks to Adrian Wrbel + // Ditch the trouble maker and add a fresh new element + MultiFile.n--; + MultiFile.addSlave(newEle[0], slave_count); + slave.parent().prepend(newEle); + slave.remove(); + return false; + }; + + // Hide this element (NB: display:none is evil!) + $(this).css({ position:'absolute', top: '-3000px' }); + + // Add new element to the form + slave.after(newEle); + + // Update list + MultiFile.addToList( this, slave_count ); + + // Bind functionality + MultiFile.addSlave( newEle[0], slave_count+1 ); + + //# Trigger Event! afterFileSelect + if(!MultiFile.trigger('afterFileSelect', this, MultiFile)) return false; + //# End Event! + + }); // slave.change() + + // Save control to element + $(slave).data('MultiFile', MultiFile); + + };// MultiFile.addSlave + // Bind a new element + + + + // Add a new file to the list + MultiFile.addToList = function( slave, slave_count ){ + //if(window.console) console.log('MultiFile.addToList',slave_count); + + //# Trigger Event! onFileAppend + if(!MultiFile.trigger('onFileAppend', slave, MultiFile)) return false; + //# End Event! + + // Create label elements + var + r = $('
              '), + v = String(slave.value || ''/*.attr('value)*/), + a = $(''+MultiFile.STRING.file.replace('$file', v.match(/[^\/\\]+$/gi)[0])+''), + b = $(''+MultiFile.STRING.remove+''); + + // Insert label + MultiFile.list.append( + r.append(b, ' ', a) + ); + + b + .click(function(){ + + //# Trigger Event! onFileRemove + if(!MultiFile.trigger('onFileRemove', slave, MultiFile)) return false; + //# End Event! + + MultiFile.n--; + MultiFile.current.disabled = false; + + // Remove element, remove label, point to current + MultiFile.slaves[slave_count] = null; + $(slave).remove(); + $(this).parent().remove(); + + // Show most current element again (move into view) and clear selection + $(MultiFile.current).css({ position:'', top: '' }); + $(MultiFile.current).reset().val('').attr('value', '')[0].value = ''; + + //# Trigger Event! afterFileRemove + if(!MultiFile.trigger('afterFileRemove', slave, MultiFile)) return false; + //# End Event! + + return false; + }); + + //# Trigger Event! afterFileAppend + if(!MultiFile.trigger('afterFileAppend', slave, MultiFile)) return false; + //# End Event! + + }; // MultiFile.addToList + // Add element to selected files list + + + + // Bind functionality to the first element + if(!MultiFile.MultiFile) MultiFile.addSlave(MultiFile.e, 0); + + // Increment control count + //MultiFile.I++; // using window.MultiFile + MultiFile.n++; + + // Save control to element + MultiFile.E.data('MultiFile', MultiFile); + + + //##################################################################### + // MAIN PLUGIN FUNCTIONALITY - END + //##################################################################### + }); // each element + }; + + /*--------------------------------------------------------*/ + + /* + ### Core functionality and API ### + */ + $.extend($.fn.MultiFile, { + /** + * This method removes all selected files + * + * Returns a jQuery collection of all affected elements. + * + * @name reset + * @type jQuery + * @cat Plugins/MultiFile + * @author Diego A. (http://www.fyneworks.com/) + * + * @example $.fn.MultiFile.reset(); + */ + reset: function(){ + var settings = $(this).data('MultiFile'); + //if(settings) settings.wrapper.find('a.MultiFile-remove').click(); + if(settings) settings.list.find('a.MultiFile-remove').click(); + return $(this); + }, + + + /** + * This utility makes it easy to disable all 'empty' file elements in the document before submitting a form. + * It marks the affected elements so they can be easily re-enabled after the form submission or validation. + * + * Returns a jQuery collection of all affected elements. + * + * @name disableEmpty + * @type jQuery + * @cat Plugins/MultiFile + * @author Diego A. (http://www.fyneworks.com/) + * + * @example $.fn.MultiFile.disableEmpty(); + * @param String class (optional) A string specifying a class to be applied to all affected elements - Default: 'mfD'. + */ + disableEmpty: function(klass){ klass = (typeof(klass)=='string'?klass:'')||'mfD'; + var o = []; + $('input:file.MultiFile').each(function(){ if($(this).val()=='') o[o.length] = this; }); + return $(o).each(function(){ this.disabled = true }).addClass(klass); + }, + + + /** + * This method re-enables 'empty' file elements that were disabled (and marked) with the $.fn.MultiFile.disableEmpty method. + * + * Returns a jQuery collection of all affected elements. + * + * @name reEnableEmpty + * @type jQuery + * @cat Plugins/MultiFile + * @author Diego A. (http://www.fyneworks.com/) + * + * @example $.fn.MultiFile.reEnableEmpty(); + * @param String klass (optional) A string specifying the class that was used to mark affected elements - Default: 'mfD'. + */ + reEnableEmpty: function(klass){ klass = (typeof(klass)=='string'?klass:'')||'mfD'; + return $('input:file.'+klass).removeClass(klass).each(function(){ this.disabled = false }); + }, + + + /** + * This method will intercept other jQuery plugins and disable empty file input elements prior to form submission + * + + * @name intercept + * @cat Plugins/MultiFile + * @author Diego A. (http://www.fyneworks.com/) + * + * @example $.fn.MultiFile.intercept(); + * @param Array methods (optional) Array of method names to be intercepted + */ + intercepted: {}, + intercept: function(methods, context, args){ + var method, value; args = args || []; + if(args.constructor.toString().indexOf("Array")<0) args = [ args ]; + if(typeof(methods)=='function'){ + $.fn.MultiFile.disableEmpty(); + value = methods.apply(context || window, args); + //SEE-http://code.google.com/p/jquery-multifile-plugin/issues/detail?id=27 + setTimeout(function(){ $.fn.MultiFile.reEnableEmpty() },1000); + return value; + }; + if(methods.constructor.toString().indexOf("Array")<0) methods = [methods]; + for(var i=0;i'), + css: { + border:'none', padding:'15px', size:'12.0pt', + backgroundColor:'#900', color:'#fff', + opacity:'.8','-webkit-border-radius': '10px','-moz-border-radius': '10px' + } + }); + window.setTimeout($.unblockUI, 2000); + } + else//{// save a byte! + */ + alert(s); + //}// save a byte! + } + }; //} }); + + /*--------------------------------------------------------*/ + + /* + ### Additional Methods ### + Required functionality outside the plugin's scope + */ + + // Native input reset method - because this alone doesn't always work: $(element).val('').attr('value', '')[0].value = ''; + $.fn.reset = function(){ return this.each(function(){ try{ this.reset(); }catch(e){} }); }; + + /*--------------------------------------------------------*/ + + /* + ### Default implementation ### + The plugin will attach itself to file inputs + with the class 'multi' when the page loads + */ + $(function(){ + //$("input:file.multi").MultiFile(); + $("input[type=file].multi").MultiFile(); + }); + + + +/*# AVOID COLLISIONS #*/ +})(jQuery); +/*# AVOID COLLISIONS #*/ diff --git a/framework/web/js/source/jquery.rating.js b/framework/web/js/source/jquery.rating.js index f1c95f434..76dadd81b 100644 --- a/framework/web/js/source/jquery.rating.js +++ b/framework/web/js/source/jquery.rating.js @@ -1,376 +1,376 @@ -/* - ### jQuery Star Rating Plugin v4.11 - 2013-03-14 ### - * Home: http://www.fyneworks.com/jquery/star-rating/ - * Code: http://code.google.com/p/jquery-star-rating-plugin/ - * - * Licensed under http://en.wikipedia.org/wiki/MIT_License - ### -*/ - -/*# AVOID COLLISIONS #*/ -;if(window.jQuery) (function($){ -/*# AVOID COLLISIONS #*/ - - // IE6 Background Image Fix - if ((!$.support.opacity && !$.support.style)) try { document.execCommand("BackgroundImageCache", false, true)} catch(e) { }; - // Thanks to http://www.visualjquery.com/rating/rating_redux.html - - // plugin initialization - $.fn.rating = function(options){ - if(this.length==0) return this; // quick fail - - // Handle API methods - if(typeof arguments[0]=='string'){ - // Perform API methods on individual elements - if(this.length>1){ - var args = arguments; - return this.each(function(){ - $.fn.rating.apply($(this), args); - }); - }; - // Invoke API method handler - $.fn.rating[arguments[0]].apply(this, $.makeArray(arguments).slice(1) || []); - // Quick exit... - return this; - }; - - // Initialize options for this call - var options = $.extend( - {}/* new object */, - $.fn.rating.options/* default options */, - options || {} /* just-in-time options */ - ); - - // Allow multiple controls with the same name by making each call unique - $.fn.rating.calls++; - - // loop through each matched element - this - .not('.star-rating-applied') - .addClass('star-rating-applied') - .each(function(){ - - // Load control parameters / find context / etc - var control, input = $(this); - var eid = (this.name || 'unnamed-rating').replace(/\[|\]/g, '_').replace(/^\_+|\_+$/g,''); - var context = $(this.form || document.body); - - // FIX: http://code.google.com/p/jquery-star-rating-plugin/issues/detail?id=23 - var raters = context.data('rating'); - if(!raters || raters.call!=$.fn.rating.calls) raters = { count:0, call:$.fn.rating.calls }; - var rater = raters[eid] || context.data('rating'+eid); - - // if rater is available, verify that the control still exists - if(rater) control = rater.data('rating'); - - if(rater && control)//{// save a byte! - // add star to control if rater is available and the same control still exists - control.count++; - - //}// save a byte! - else{ - // create new control if first star or control element was removed/replaced - - // Initialize options for this rater - control = $.extend( - {}/* new object */, - options || {} /* current call options */, - ($.metadata? input.metadata(): ($.meta?input.data():null)) || {}, /* metadata options */ - { count:0, stars: [], inputs: [] } - ); - - // increment number of rating controls - control.serial = raters.count++; - - // create rating element - rater = $(''); - input.before(rater); - - // Mark element for initialization (once all stars are ready) - rater.addClass('rating-to-be-drawn'); - - // Accept readOnly setting from 'disabled' property - if(input.attr('disabled') || input.hasClass('disabled')) control.readOnly = true; - - // Accept required setting from class property (class='required') - if(input.hasClass('required')) control.required = true; - - // Create 'cancel' button - rater.append( - control.cancel = $('') - .on('mouseover',function(){ - $(this).rating('drain'); - $(this).addClass('star-rating-hover'); - //$(this).rating('focus'); - }) - .on('mouseout',function(){ - $(this).rating('draw'); - $(this).removeClass('star-rating-hover'); - //$(this).rating('blur'); - }) - .on('click',function(){ - $(this).rating('select'); - }) - .data('rating', control) - ); - - }; // first element of group - - // insert rating star (thanks Jan Fanslau rev125 for blind support https://code.google.com/p/jquery-star-rating-plugin/issues/detail?id=125) - var star = $(''); - rater.append(star); - - // inherit attributes from input element - if(this.id) star.attr('id', this.id); - if(this.className) star.addClass(this.className); - - // Half-stars? - if(control.half) control.split = 2; - - // Prepare division control - if(typeof control.split=='number' && control.split>0){ - var stw = ($.fn.width ? star.width() : 0) || control.starWidth; - var spi = (control.count % control.split), spw = Math.floor(stw/control.split); - star - // restrict star's width and hide overflow (already in CSS) - .width(spw) - // move the star left by using a negative margin - // this is work-around to IE's stupid box model (position:relative doesn't work) - .find('a').css({ 'margin-left':'-'+ (spi*spw) +'px' }) - }; - - // readOnly? - if(control.readOnly)//{ //save a byte! - // Mark star as readOnly so user can customize display - star.addClass('star-rating-readonly'); - //} //save a byte! - else//{ //save a byte! - // Enable hover css effects - star.addClass('star-rating-live') - // Attach mouse events - .on('mouseover',function(){ - $(this).rating('fill'); - $(this).rating('focus'); - }) - .on('mouseout',function(){ - $(this).rating('draw'); - $(this).rating('blur'); - }) - .on('click',function(){ - $(this).rating('select'); - }) - ; - //}; //save a byte! - - // set current selection - if(this.checked) control.current = star; - - // set current select for links - if(this.nodeName=="A"){ - if($(this).hasClass('selected')) - control.current = star; - }; - - // hide input element - input.hide(); - - // backward compatibility, form element to plugin - input.on('change.rating',function(event){ - if(event.selfTriggered) return false; - $(this).rating('select'); - }); - - // attach reference to star to input element and vice-versa - star.data('rating.input', input.data('rating.star', star)); - - // store control information in form (or body when form not available) - control.stars[control.stars.length] = star[0]; - control.inputs[control.inputs.length] = input[0]; - control.rater = raters[eid] = rater; - control.context = context; - - input.data('rating', control); - rater.data('rating', control); - star.data('rating', control); - context.data('rating', raters); - context.data('rating'+eid, rater); // required for ajax forms - }); // each element - - // Initialize ratings (first draw) - $('.rating-to-be-drawn').rating('draw').removeClass('rating-to-be-drawn'); - - return this; // don't break the chain... - }; - - /*--------------------------------------------------------*/ - - /* - ### Core functionality and API ### - */ - $.extend($.fn.rating, { - // Used to append a unique serial number to internal control ID - // each time the plugin is invoked so same name controls can co-exist - calls: 0, - - focus: function(){ - var control = this.data('rating'); if(!control) return this; - if(!control.focus) return this; // quick fail if not required - // find data for event - var input = $(this).data('rating.input') || $( this.tagName=='INPUT' ? this : null ); - // focus handler, as requested by focusdigital.co.uk - if(control.focus) control.focus.apply(input[0], [input.val(), $('a', input.data('rating.star'))[0]]); - }, // $.fn.rating.focus - - blur: function(){ - var control = this.data('rating'); if(!control) return this; - if(!control.blur) return this; // quick fail if not required - // find data for event - var input = $(this).data('rating.input') || $( this.tagName=='INPUT' ? this : null ); - // blur handler, as requested by focusdigital.co.uk - if(control.blur) control.blur.apply(input[0], [input.val(), $('a', input.data('rating.star'))[0]]); - }, // $.fn.rating.blur - - fill: function(){ // fill to the current mouse position. - var control = this.data('rating'); if(!control) return this; - // do not execute when control is in read-only mode - if(control.readOnly) return; - // Reset all stars and highlight them up to this element - this.rating('drain'); - this.prevAll().addBack().filter('.rater-'+ control.serial).addClass('star-rating-hover'); - },// $.fn.rating.fill - - drain: function() { // drain all the stars. - var control = this.data('rating'); if(!control) return this; - // do not execute when control is in read-only mode - if(control.readOnly) return; - // Reset all stars - control.rater.children().filter('.rater-'+ control.serial).removeClass('star-rating-on').removeClass('star-rating-hover'); - },// $.fn.rating.drain - - draw: function(){ // set value and stars to reflect current selection - var control = this.data('rating'); if(!control) return this; - // Clear all stars - this.rating('drain'); - // Set control value - var current = $( control.current );//? control.current.data('rating.input') : null ); - var starson = current.length ? current.prevAll().addBack().filter('.rater-'+ control.serial) : null; - if(starson) starson.addClass('star-rating-on'); - // Show/hide 'cancel' button - control.cancel[control.readOnly || control.required?'hide':'show'](); - // Add/remove read-only classes to remove hand pointer - this.siblings()[control.readOnly?'addClass':'removeClass']('star-rating-readonly'); - },// $.fn.rating.draw - - - - - - select: function(value,wantCallBack){ // select a value - var control = this.data('rating'); if(!control) return this; - // do not execute when control is in read-only mode - if(control.readOnly) return; - // clear selection - control.current = null; - // programmatically (based on user input) - if(typeof value!='undefined' || this.length>1){ - // select by index (0 based) - if(typeof value=='number') - return $(control.stars[value]).rating('select',undefined,wantCallBack); - // select by literal value (must be passed as a string - if(typeof value=='string'){ - //return - $.each(control.stars, function(){ - //console.log($(this).data('rating.input'), $(this).data('rating.input').val(), value, $(this).data('rating.input').val()==value?'BINGO!':''); - if($(this).data('rating.input').val()==value) $(this).rating('select',undefined,wantCallBack); - }); - // don't break the chain - return this; - }; - } - else{ - control.current = this[0].tagName=='INPUT' ? - this.data('rating.star') : - (this.is('.rater-'+ control.serial) ? this : null); - }; - // Update rating control state - this.data('rating', control); - // Update display - this.rating('draw'); - // find current input and its sibblings - var current = $( control.current ? control.current.data('rating.input') : null ); - var lastipt = $( control.inputs ).filter(':checked'); - var deadipt = $( control.inputs ).not(current); - // check and uncheck elements as required - deadipt.prop('checked',false);//.removeAttr('checked'); - current.prop('checked',true);//.attr('checked','checked'); - // trigger change on current or last selected input - $(current.length? current : lastipt ).trigger({ type:'change', selfTriggered:true }); - // click callback, as requested here: http://plugins.jquery.com/node/1655 - if((wantCallBack || wantCallBack == undefined) && control.callback) control.callback.apply(current[0], [current.val(), $('a', control.current)[0]]);// callback event - // don't break the chain - return this; - },// $.fn.rating.select - - - - - - readOnly: function(toggle, disable){ // make the control read-only (still submits value) - var control = this.data('rating'); if(!control) return this; - // setread-only status - control.readOnly = toggle || toggle==undefined ? true : false; - // enable/disable control value submission - if(disable) $(control.inputs).attr("disabled", "disabled"); - else $(control.inputs).removeAttr("disabled"); - // Update rating control state - this.data('rating', control); - // Update display - this.rating('draw'); - },// $.fn.rating.readOnly - - disable: function(){ // make read-only and never submit value - this.rating('readOnly', true, true); - },// $.fn.rating.disable - - enable: function(){ // make read/write and submit value - this.rating('readOnly', false, false); - }// $.fn.rating.select - - }); - - /*--------------------------------------------------------*/ - - /* - ### Default Settings ### - eg.: You can override default control like this: - $.fn.rating.options.cancel = 'Clear'; - */ - $.fn.rating.options = { //$.extend($.fn.rating, { options: { - cancel: 'Cancel Rating', // advisory title for the 'cancel' link - cancelValue: '', // value to submit when user click the 'cancel' link - split: 0, // split the star into how many parts? - - // Width of star image in case the plugin can't work it out. This can happen if - // the jQuery.dimensions plugin is not available OR the image is hidden at installation - starWidth: 16//, - - //NB.: These don't need to be pre-defined (can be undefined/null) so let's save some code! - //half: false, // just a shortcut to control.split = 2 - //required: false, // disables the 'cancel' button so user can only select one of the specified values - //readOnly: false, // disable rating plugin interaction/ values cannot be.one('change', //focus: function(){}, // executed when stars are focused - //blur: function(){}, // executed when stars are focused - //callback: function(){}, // executed when a star is clicked - }; //} }); - - /*--------------------------------------------------------*/ - - - // auto-initialize plugin - $(function(){ - $('input[type=radio].star').rating(); - }); - - -/*# AVOID COLLISIONS #*/ -})(jQuery); -/*# AVOID COLLISIONS #*/ +/* + ### jQuery Star Rating Plugin v4.11 - 2013-03-14 ### + * Home: http://www.fyneworks.com/jquery/star-rating/ + * Code: http://code.google.com/p/jquery-star-rating-plugin/ + * + * Licensed under http://en.wikipedia.org/wiki/MIT_License + ### +*/ + +/*# AVOID COLLISIONS #*/ +;if(window.jQuery) (function($){ +/*# AVOID COLLISIONS #*/ + + // IE6 Background Image Fix + if ((!$.support.opacity && !$.support.style)) try { document.execCommand("BackgroundImageCache", false, true)} catch(e) { }; + // Thanks to http://www.visualjquery.com/rating/rating_redux.html + + // plugin initialization + $.fn.rating = function(options){ + if(this.length==0) return this; // quick fail + + // Handle API methods + if(typeof arguments[0]=='string'){ + // Perform API methods on individual elements + if(this.length>1){ + var args = arguments; + return this.each(function(){ + $.fn.rating.apply($(this), args); + }); + }; + // Invoke API method handler + $.fn.rating[arguments[0]].apply(this, $.makeArray(arguments).slice(1) || []); + // Quick exit... + return this; + }; + + // Initialize options for this call + var options = $.extend( + {}/* new object */, + $.fn.rating.options/* default options */, + options || {} /* just-in-time options */ + ); + + // Allow multiple controls with the same name by making each call unique + $.fn.rating.calls++; + + // loop through each matched element + this + .not('.star-rating-applied') + .addClass('star-rating-applied') + .each(function(){ + + // Load control parameters / find context / etc + var control, input = $(this); + var eid = (this.name || 'unnamed-rating').replace(/\[|\]/g, '_').replace(/^\_+|\_+$/g,''); + var context = $(this.form || document.body); + + // FIX: http://code.google.com/p/jquery-star-rating-plugin/issues/detail?id=23 + var raters = context.data('rating'); + if(!raters || raters.call!=$.fn.rating.calls) raters = { count:0, call:$.fn.rating.calls }; + var rater = raters[eid] || context.data('rating'+eid); + + // if rater is available, verify that the control still exists + if(rater) control = rater.data('rating'); + + if(rater && control)//{// save a byte! + // add star to control if rater is available and the same control still exists + control.count++; + + //}// save a byte! + else{ + // create new control if first star or control element was removed/replaced + + // Initialize options for this rater + control = $.extend( + {}/* new object */, + options || {} /* current call options */, + ($.metadata? input.metadata(): ($.meta?input.data():null)) || {}, /* metadata options */ + { count:0, stars: [], inputs: [] } + ); + + // increment number of rating controls + control.serial = raters.count++; + + // create rating element + rater = $(''); + input.before(rater); + + // Mark element for initialization (once all stars are ready) + rater.addClass('rating-to-be-drawn'); + + // Accept readOnly setting from 'disabled' property + if(input.attr('disabled') || input.hasClass('disabled')) control.readOnly = true; + + // Accept required setting from class property (class='required') + if(input.hasClass('required')) control.required = true; + + // Create 'cancel' button + rater.append( + control.cancel = $('') + .on('mouseover',function(){ + $(this).rating('drain'); + $(this).addClass('star-rating-hover'); + //$(this).rating('focus'); + }) + .on('mouseout',function(){ + $(this).rating('draw'); + $(this).removeClass('star-rating-hover'); + //$(this).rating('blur'); + }) + .on('click',function(){ + $(this).rating('select'); + }) + .data('rating', control) + ); + + }; // first element of group + + // insert rating star (thanks Jan Fanslau rev125 for blind support https://code.google.com/p/jquery-star-rating-plugin/issues/detail?id=125) + var star = $(''); + rater.append(star); + + // inherit attributes from input element + if(this.id) star.attr('id', this.id); + if(this.className) star.addClass(this.className); + + // Half-stars? + if(control.half) control.split = 2; + + // Prepare division control + if(typeof control.split=='number' && control.split>0){ + var stw = ($.fn.width ? star.width() : 0) || control.starWidth; + var spi = (control.count % control.split), spw = Math.floor(stw/control.split); + star + // restrict star's width and hide overflow (already in CSS) + .width(spw) + // move the star left by using a negative margin + // this is work-around to IE's stupid box model (position:relative doesn't work) + .find('a').css({ 'margin-left':'-'+ (spi*spw) +'px' }) + }; + + // readOnly? + if(control.readOnly)//{ //save a byte! + // Mark star as readOnly so user can customize display + star.addClass('star-rating-readonly'); + //} //save a byte! + else//{ //save a byte! + // Enable hover css effects + star.addClass('star-rating-live') + // Attach mouse events + .on('mouseover',function(){ + $(this).rating('fill'); + $(this).rating('focus'); + }) + .on('mouseout',function(){ + $(this).rating('draw'); + $(this).rating('blur'); + }) + .on('click',function(){ + $(this).rating('select'); + }) + ; + //}; //save a byte! + + // set current selection + if(this.checked) control.current = star; + + // set current select for links + if(this.nodeName=="A"){ + if($(this).hasClass('selected')) + control.current = star; + }; + + // hide input element + input.hide(); + + // backward compatibility, form element to plugin + input.on('change.rating',function(event){ + if(event.selfTriggered) return false; + $(this).rating('select'); + }); + + // attach reference to star to input element and vice-versa + star.data('rating.input', input.data('rating.star', star)); + + // store control information in form (or body when form not available) + control.stars[control.stars.length] = star[0]; + control.inputs[control.inputs.length] = input[0]; + control.rater = raters[eid] = rater; + control.context = context; + + input.data('rating', control); + rater.data('rating', control); + star.data('rating', control); + context.data('rating', raters); + context.data('rating'+eid, rater); // required for ajax forms + }); // each element + + // Initialize ratings (first draw) + $('.rating-to-be-drawn').rating('draw').removeClass('rating-to-be-drawn'); + + return this; // don't break the chain... + }; + + /*--------------------------------------------------------*/ + + /* + ### Core functionality and API ### + */ + $.extend($.fn.rating, { + // Used to append a unique serial number to internal control ID + // each time the plugin is invoked so same name controls can co-exist + calls: 0, + + focus: function(){ + var control = this.data('rating'); if(!control) return this; + if(!control.focus) return this; // quick fail if not required + // find data for event + var input = $(this).data('rating.input') || $( this.tagName=='INPUT' ? this : null ); + // focus handler, as requested by focusdigital.co.uk + if(control.focus) control.focus.apply(input[0], [input.val(), $('a', input.data('rating.star'))[0]]); + }, // $.fn.rating.focus + + blur: function(){ + var control = this.data('rating'); if(!control) return this; + if(!control.blur) return this; // quick fail if not required + // find data for event + var input = $(this).data('rating.input') || $( this.tagName=='INPUT' ? this : null ); + // blur handler, as requested by focusdigital.co.uk + if(control.blur) control.blur.apply(input[0], [input.val(), $('a', input.data('rating.star'))[0]]); + }, // $.fn.rating.blur + + fill: function(){ // fill to the current mouse position. + var control = this.data('rating'); if(!control) return this; + // do not execute when control is in read-only mode + if(control.readOnly) return; + // Reset all stars and highlight them up to this element + this.rating('drain'); + this.prevAll().addBack().filter('.rater-'+ control.serial).addClass('star-rating-hover'); + },// $.fn.rating.fill + + drain: function() { // drain all the stars. + var control = this.data('rating'); if(!control) return this; + // do not execute when control is in read-only mode + if(control.readOnly) return; + // Reset all stars + control.rater.children().filter('.rater-'+ control.serial).removeClass('star-rating-on').removeClass('star-rating-hover'); + },// $.fn.rating.drain + + draw: function(){ // set value and stars to reflect current selection + var control = this.data('rating'); if(!control) return this; + // Clear all stars + this.rating('drain'); + // Set control value + var current = $( control.current );//? control.current.data('rating.input') : null ); + var starson = current.length ? current.prevAll().addBack().filter('.rater-'+ control.serial) : null; + if(starson) starson.addClass('star-rating-on'); + // Show/hide 'cancel' button + control.cancel[control.readOnly || control.required?'hide':'show'](); + // Add/remove read-only classes to remove hand pointer + this.siblings()[control.readOnly?'addClass':'removeClass']('star-rating-readonly'); + },// $.fn.rating.draw + + + + + + select: function(value,wantCallBack){ // select a value + var control = this.data('rating'); if(!control) return this; + // do not execute when control is in read-only mode + if(control.readOnly) return; + // clear selection + control.current = null; + // programmatically (based on user input) + if(typeof value!='undefined' || this.length>1){ + // select by index (0 based) + if(typeof value=='number') + return $(control.stars[value]).rating('select',undefined,wantCallBack); + // select by literal value (must be passed as a string + if(typeof value=='string'){ + //return + $.each(control.stars, function(){ + //console.log($(this).data('rating.input'), $(this).data('rating.input').val(), value, $(this).data('rating.input').val()==value?'BINGO!':''); + if($(this).data('rating.input').val()==value) $(this).rating('select',undefined,wantCallBack); + }); + // don't break the chain + return this; + }; + } + else{ + control.current = this[0].tagName=='INPUT' ? + this.data('rating.star') : + (this.is('.rater-'+ control.serial) ? this : null); + }; + // Update rating control state + this.data('rating', control); + // Update display + this.rating('draw'); + // find current input and its sibblings + var current = $( control.current ? control.current.data('rating.input') : null ); + var lastipt = $( control.inputs ).filter(':checked'); + var deadipt = $( control.inputs ).not(current); + // check and uncheck elements as required + deadipt.prop('checked',false);//.removeAttr('checked'); + current.prop('checked',true);//.attr('checked','checked'); + // trigger change on current or last selected input + $(current.length? current : lastipt ).trigger({ type:'change', selfTriggered:true }); + // click callback, as requested here: http://plugins.jquery.com/node/1655 + if((wantCallBack || wantCallBack == undefined) && control.callback) control.callback.apply(current[0], [current.val(), $('a', control.current)[0]]);// callback event + // don't break the chain + return this; + },// $.fn.rating.select + + + + + + readOnly: function(toggle, disable){ // make the control read-only (still submits value) + var control = this.data('rating'); if(!control) return this; + // setread-only status + control.readOnly = toggle || toggle==undefined ? true : false; + // enable/disable control value submission + if(disable) $(control.inputs).attr("disabled", "disabled"); + else $(control.inputs).removeAttr("disabled"); + // Update rating control state + this.data('rating', control); + // Update display + this.rating('draw'); + },// $.fn.rating.readOnly + + disable: function(){ // make read-only and never submit value + this.rating('readOnly', true, true); + },// $.fn.rating.disable + + enable: function(){ // make read/write and submit value + this.rating('readOnly', false, false); + }// $.fn.rating.select + + }); + + /*--------------------------------------------------------*/ + + /* + ### Default Settings ### + eg.: You can override default control like this: + $.fn.rating.options.cancel = 'Clear'; + */ + $.fn.rating.options = { //$.extend($.fn.rating, { options: { + cancel: 'Cancel Rating', // advisory title for the 'cancel' link + cancelValue: '', // value to submit when user click the 'cancel' link + split: 0, // split the star into how many parts? + + // Width of star image in case the plugin can't work it out. This can happen if + // the jQuery.dimensions plugin is not available OR the image is hidden at installation + starWidth: 16//, + + //NB.: These don't need to be pre-defined (can be undefined/null) so let's save some code! + //half: false, // just a shortcut to control.split = 2 + //required: false, // disables the 'cancel' button so user can only select one of the specified values + //readOnly: false, // disable rating plugin interaction/ values cannot be.one('change', //focus: function(){}, // executed when stars are focused + //blur: function(){}, // executed when stars are focused + //callback: function(){}, // executed when a star is clicked + }; //} }); + + /*--------------------------------------------------------*/ + + + // auto-initialize plugin + $(function(){ + $('input[type=radio].star').rating(); + }); + + +/*# AVOID COLLISIONS #*/ +})(jQuery); +/*# AVOID COLLISIONS #*/ diff --git a/framework/web/js/source/jquery.treeview.async.js b/framework/web/js/source/jquery.treeview.async.js index 50826c005..219bd93c7 100644 --- a/framework/web/js/source/jquery.treeview.async.js +++ b/framework/web/js/source/jquery.treeview.async.js @@ -1,110 +1,110 @@ -/* - * Async Treeview 0.1 - Lazy-loading extension for Treeview - * - * http://bassistance.de/jquery-plugins/jquery-plugin-treeview/ - * - * Copyright (c) 2007 Jörn Zaefferer - * - * Dual licensed under the MIT and GPL licenses: - * http://www.opensource.org/licenses/mit-license.php - * http://www.gnu.org/licenses/gpl.html - * - * Revision: $Id$ - * - */ - -;(function($) { - -function load(settings, root, child, container) { - function createNode(parent) { - var current = $("
            • ").attr("id", this.id || "").html("" + this.text + "").appendTo(parent); - if (this.classes) { - current.children("span").addClass(this.classes); - } - if (this.expanded) { - current.addClass("open"); - } - if (this.hasChildren || this.children && this.children.length) { - var branch = $("
                ").appendTo(current); - if (this.hasChildren) { - current.addClass("hasChildren"); - createNode.call({ - classes: "placeholder", - text: " ", - children:[] - }, branch); - } - if (this.children && this.children.length) { - $.each(this.children, createNode, [branch]) - } - } - } - $.ajax($.extend(true, { - url: settings.url, - dataType: "json", - data: { - root: root - }, - success: function(response) { - child.empty(); - $.each(response, createNode, [child]); - $(container).treeview({add: child}); - } - }, settings.ajax)); - /* - $.getJSON(settings.url, {root: root}, function(response) { - function createNode(parent) { - var current = $("
              • ").attr("id", this.id || "").html("" + this.text + "").appendTo(parent); - if (this.classes) { - current.children("span").addClass(this.classes); - } - if (this.expanded) { - current.addClass("open"); - } - if (this.hasChildren || this.children && this.children.length) { - var branch = $("
                  ").appendTo(current); - if (this.hasChildren) { - current.addClass("hasChildren"); - createNode.call({ - classes: "placeholder", - text: " ", - children:[] - }, branch); - } - if (this.children && this.children.length) { - $.each(this.children, createNode, [branch]) - } - } - } - child.empty(); - $.each(response, createNode, [child]); - $(container).treeview({add: child}); - }); - */ -} - -var proxied = $.fn.treeview; -$.fn.treeview = function(settings) { - if (!settings.url) { - return proxied.apply(this, arguments); - } - var container = this; - if (!container.children().size()) - load(settings, "source", this, container); - var userToggle = settings.toggle; - return proxied.call(this, $.extend({}, settings, { - collapsed: true, - toggle: function() { - var $this = $(this); - if ($this.hasClass("hasChildren")) { - var childList = $this.removeClass("hasChildren").find("ul"); - load(settings, this.id, childList, container); - } - if (userToggle) { - userToggle.apply(this, arguments); - } - } - })); -}; - +/* + * Async Treeview 0.1 - Lazy-loading extension for Treeview + * + * http://bassistance.de/jquery-plugins/jquery-plugin-treeview/ + * + * Copyright (c) 2007 Jörn Zaefferer + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * Revision: $Id$ + * + */ + +;(function($) { + +function load(settings, root, child, container) { + function createNode(parent) { + var current = $("
                • ").attr("id", this.id || "").html("" + this.text + "").appendTo(parent); + if (this.classes) { + current.children("span").addClass(this.classes); + } + if (this.expanded) { + current.addClass("open"); + } + if (this.hasChildren || this.children && this.children.length) { + var branch = $("
                    ").appendTo(current); + if (this.hasChildren) { + current.addClass("hasChildren"); + createNode.call({ + classes: "placeholder", + text: " ", + children:[] + }, branch); + } + if (this.children && this.children.length) { + $.each(this.children, createNode, [branch]) + } + } + } + $.ajax($.extend(true, { + url: settings.url, + dataType: "json", + data: { + root: root + }, + success: function(response) { + child.empty(); + $.each(response, createNode, [child]); + $(container).treeview({add: child}); + } + }, settings.ajax)); + /* + $.getJSON(settings.url, {root: root}, function(response) { + function createNode(parent) { + var current = $("
                  • ").attr("id", this.id || "").html("" + this.text + "").appendTo(parent); + if (this.classes) { + current.children("span").addClass(this.classes); + } + if (this.expanded) { + current.addClass("open"); + } + if (this.hasChildren || this.children && this.children.length) { + var branch = $("
                      ").appendTo(current); + if (this.hasChildren) { + current.addClass("hasChildren"); + createNode.call({ + classes: "placeholder", + text: " ", + children:[] + }, branch); + } + if (this.children && this.children.length) { + $.each(this.children, createNode, [branch]) + } + } + } + child.empty(); + $.each(response, createNode, [child]); + $(container).treeview({add: child}); + }); + */ +} + +var proxied = $.fn.treeview; +$.fn.treeview = function(settings) { + if (!settings.url) { + return proxied.apply(this, arguments); + } + var container = this; + if (!container.children().size()) + load(settings, "source", this, container); + var userToggle = settings.toggle; + return proxied.call(this, $.extend({}, settings, { + collapsed: true, + toggle: function() { + var $this = $(this); + if ($this.hasClass("hasChildren")) { + var childList = $this.removeClass("hasChildren").find("ul"); + load(settings, this.id, childList, container); + } + if (userToggle) { + userToggle.apply(this, arguments); + } + } + })); +}; + })(jQuery); \ No newline at end of file diff --git a/framework/web/js/source/rating/jquery.rating.css b/framework/web/js/source/rating/jquery.rating.css index 50cf2e2d2..45042e8c1 100644 --- a/framework/web/js/source/rating/jquery.rating.css +++ b/framework/web/js/source/rating/jquery.rating.css @@ -1,12 +1,12 @@ -/* jQuery.Rating Plugin CSS - http://www.fyneworks.com/jquery/star-rating/ */ -div.rating-cancel,div.star-rating{float:left;width:17px;height:15px;text-indent:-999em;cursor:pointer;display:block;background:transparent;overflow:hidden} -div.rating-cancel,div.rating-cancel a{background:url(delete.gif) no-repeat 0 -16px} -div.star-rating,div.star-rating a{background:url(star.gif) no-repeat 0 0px} -div.rating-cancel a,div.star-rating a{display:block;width:16px;height:100%;background-position:0 0px;border:0} -div.star-rating-on a{background-position:0 -16px!important} -div.star-rating-hover a{background-position:0 -32px} -/* Read Only CSS */ -div.star-rating-readonly a{cursor:default !important} -/* Partial Star CSS */ -div.star-rating{background:transparent!important;overflow:hidden!important} +/* jQuery.Rating Plugin CSS - http://www.fyneworks.com/jquery/star-rating/ */ +div.rating-cancel,div.star-rating{float:left;width:17px;height:15px;text-indent:-999em;cursor:pointer;display:block;background:transparent;overflow:hidden} +div.rating-cancel,div.rating-cancel a{background:url(delete.gif) no-repeat 0 -16px} +div.star-rating,div.star-rating a{background:url(star.gif) no-repeat 0 0px} +div.rating-cancel a,div.star-rating a{display:block;width:16px;height:100%;background-position:0 0px;border:0} +div.star-rating-on a{background-position:0 -16px!important} +div.star-rating-hover a{background-position:0 -32px} +/* Read Only CSS */ +div.star-rating-readonly a{cursor:default !important} +/* Partial Star CSS */ +div.star-rating{background:transparent!important;overflow:hidden!important} /* END jQuery.Rating Plugin CSS */ \ No newline at end of file diff --git a/framework/web/js/source/treeview/jquery.treeview.css b/framework/web/js/source/treeview/jquery.treeview.css index 298672be9..5d3200e5a 100644 --- a/framework/web/js/source/treeview/jquery.treeview.css +++ b/framework/web/js/source/treeview/jquery.treeview.css @@ -1,74 +1,74 @@ -.treeview, .treeview ul { - padding: 0; - margin: 0; - list-style: none; -} - -.treeview ul { - background-color: white; - margin-top: 4px; -} - -.treeview .hitarea { - background: url(images/treeview-default.gif) -64px -25px no-repeat; - height: 16px; - width: 16px; - margin-left: -16px; - float: left; - cursor: pointer; -} -/* fix for IE6 */ -* html .hitarea { - display: inline; - float:none; -} - -.treeview li { - margin: 0; - padding: 3px 0pt 3px 16px; -} - -.treeview a.selected { - background-color: #eee; -} - -#treecontrol { margin: 1em 0; display: none; } - -.treeview .hover { color: red; cursor: pointer; } - -.treeview li { background: url(images/treeview-default-line.gif) 0 0 no-repeat; } -.treeview li.collapsable, .treeview li.expandable { background-position: 0 -176px; } - -.treeview .expandable-hitarea { background-position: -80px -3px; } - -.treeview li.last { background-position: 0 -1766px } -.treeview li.lastCollapsable, .treeview li.lastExpandable { background-image: url(images/treeview-default.gif); } -.treeview li.lastCollapsable { background-position: 0 -111px } -.treeview li.lastExpandable { background-position: -32px -67px } - -.treeview div.lastCollapsable-hitarea, .treeview div.lastExpandable-hitarea { background-position: 0; } - -.treeview-red li { background-image: url(images/treeview-red-line.gif); } -.treeview-red .hitarea, .treeview-red li.lastCollapsable, .treeview-red li.lastExpandable { background-image: url(images/treeview-red.gif); } - -.treeview-black li { background-image: url(images/treeview-black-line.gif); } -.treeview-black .hitarea, .treeview-black li.lastCollapsable, .treeview-black li.lastExpandable { background-image: url(images/treeview-black.gif); } - -.treeview-gray li { background-image: url(images/treeview-gray-line.gif); } -.treeview-gray .hitarea, .treeview-gray li.lastCollapsable, .treeview-gray li.lastExpandable { background-image: url(images/treeview-gray.gif); } - -.treeview-famfamfam li { background-image: url(images/treeview-famfamfam-line.gif); } -.treeview-famfamfam .hitarea, .treeview-famfamfam li.lastCollapsable, .treeview-famfamfam li.lastExpandable { background-image: url(images/treeview-famfamfam.gif); } - -.treeview .placeholder { - background: url(images/ajax-loader.gif) 0 0 no-repeat; - height: 16px; - width: 16px; - display: block; -} - -.filetree li { padding: 3px 0 2px 16px; } -.filetree span.folder, .filetree span.file { padding: 1px 0 1px 16px; display: block; } -.filetree span.folder { background: url(images/folder.gif) 0 0 no-repeat; } -.filetree li.expandable span.folder { background: url(images/folder-closed.gif) 0 0 no-repeat; } -.filetree span.file { background: url(images/file.gif) 0 0 no-repeat; } +.treeview, .treeview ul { + padding: 0; + margin: 0; + list-style: none; +} + +.treeview ul { + background-color: white; + margin-top: 4px; +} + +.treeview .hitarea { + background: url(images/treeview-default.gif) -64px -25px no-repeat; + height: 16px; + width: 16px; + margin-left: -16px; + float: left; + cursor: pointer; +} +/* fix for IE6 */ +* html .hitarea { + display: inline; + float:none; +} + +.treeview li { + margin: 0; + padding: 3px 0pt 3px 16px; +} + +.treeview a.selected { + background-color: #eee; +} + +#treecontrol { margin: 1em 0; display: none; } + +.treeview .hover { color: red; cursor: pointer; } + +.treeview li { background: url(images/treeview-default-line.gif) 0 0 no-repeat; } +.treeview li.collapsable, .treeview li.expandable { background-position: 0 -176px; } + +.treeview .expandable-hitarea { background-position: -80px -3px; } + +.treeview li.last { background-position: 0 -1766px } +.treeview li.lastCollapsable, .treeview li.lastExpandable { background-image: url(images/treeview-default.gif); } +.treeview li.lastCollapsable { background-position: 0 -111px } +.treeview li.lastExpandable { background-position: -32px -67px } + +.treeview div.lastCollapsable-hitarea, .treeview div.lastExpandable-hitarea { background-position: 0; } + +.treeview-red li { background-image: url(images/treeview-red-line.gif); } +.treeview-red .hitarea, .treeview-red li.lastCollapsable, .treeview-red li.lastExpandable { background-image: url(images/treeview-red.gif); } + +.treeview-black li { background-image: url(images/treeview-black-line.gif); } +.treeview-black .hitarea, .treeview-black li.lastCollapsable, .treeview-black li.lastExpandable { background-image: url(images/treeview-black.gif); } + +.treeview-gray li { background-image: url(images/treeview-gray-line.gif); } +.treeview-gray .hitarea, .treeview-gray li.lastCollapsable, .treeview-gray li.lastExpandable { background-image: url(images/treeview-gray.gif); } + +.treeview-famfamfam li { background-image: url(images/treeview-famfamfam-line.gif); } +.treeview-famfamfam .hitarea, .treeview-famfamfam li.lastCollapsable, .treeview-famfamfam li.lastExpandable { background-image: url(images/treeview-famfamfam.gif); } + +.treeview .placeholder { + background: url(images/ajax-loader.gif) 0 0 no-repeat; + height: 16px; + width: 16px; + display: block; +} + +.filetree li { padding: 3px 0 2px 16px; } +.filetree span.folder, .filetree span.file { padding: 1px 0 1px 16px; display: block; } +.filetree span.folder { background: url(images/folder.gif) 0 0 no-repeat; } +.filetree li.expandable span.folder { background: url(images/folder-closed.gif) 0 0 no-repeat; } +.filetree span.file { background: url(images/file.gif) 0 0 no-repeat; } diff --git a/framework/zii/widgets/jui/CJuiDraggable.php b/framework/zii/widgets/jui/CJuiDraggable.php index 640c5c7b0..72f6cf539 100644 --- a/framework/zii/widgets/jui/CJuiDraggable.php +++ b/framework/zii/widgets/jui/CJuiDraggable.php @@ -1,78 +1,78 @@ - - * @link http://www.yiiframework.com/ - * @copyright 2008-2013 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -Yii::import('zii.widgets.jui.CJuiWidget'); - -/** - * CJuiDraggable displays a draggable widget. - * - * CJuiDraggable encapsulates the {@link http://jqueryui.com/draggable/ JUI Draggable} - * plugin. - * - * To use this widget, you may insert the following code in a view: - *
                      - * $this->beginWidget('zii.widgets.jui.CJuiDraggable',array(
                      - *     // additional javascript options for the draggable plugin
                      - *     'options'=>array(
                      - *         'scope'=>'myScope',
                      - *     ),
                      - * ));
                      - *     echo 'Your draggable content here';
                      - *
                      - * $this->endWidget();
                      - *
                      - * 
                      - * - * By configuring the {@link options} property, you may specify the options - * that need to be passed to the JUI Draggable plugin. Please refer to - * the {@link http://api.jqueryui.com/draggable/ JUI Draggable API} documentation - * for possible options (name-value pairs) and - * {@link http://jqueryui.com/draggable/ JUI Draggable page} for general - * description and demo. - * - * @author Sebastian Thierer - * @package zii.widgets.jui - * @since 1.1 - */ -class CJuiDraggable extends CJuiWidget -{ - /** - * @var string the name of the Draggable element. Defaults to 'div'. - */ - public $tagName='div'; - - /** - * Renders the open tag of the draggable element. - * This method also registers the necessary javascript code. - */ - public function init() - { - parent::init(); - - $id=$this->getId(); - if(isset($this->htmlOptions['id'])) - $id=$this->htmlOptions['id']; - else - $this->htmlOptions['id']=$id; - - $options=CJavaScript::encode($this->options); - Yii::app()->getClientScript()->registerScript(__CLASS__.'#'.$id,"jQuery('#{$id}').draggable($options);"); - - echo CHtml::openTag($this->tagName,$this->htmlOptions)."\n"; - } - - /** - * Renders the close tag of the draggable element. - */ - public function run() - { - echo CHtml::closeTag($this->tagName); - } + + * @link http://www.yiiframework.com/ + * @copyright 2008-2013 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +Yii::import('zii.widgets.jui.CJuiWidget'); + +/** + * CJuiDraggable displays a draggable widget. + * + * CJuiDraggable encapsulates the {@link http://jqueryui.com/draggable/ JUI Draggable} + * plugin. + * + * To use this widget, you may insert the following code in a view: + *
                      + * $this->beginWidget('zii.widgets.jui.CJuiDraggable',array(
                      + *     // additional javascript options for the draggable plugin
                      + *     'options'=>array(
                      + *         'scope'=>'myScope',
                      + *     ),
                      + * ));
                      + *     echo 'Your draggable content here';
                      + *
                      + * $this->endWidget();
                      + *
                      + * 
                      + * + * By configuring the {@link options} property, you may specify the options + * that need to be passed to the JUI Draggable plugin. Please refer to + * the {@link http://api.jqueryui.com/draggable/ JUI Draggable API} documentation + * for possible options (name-value pairs) and + * {@link http://jqueryui.com/draggable/ JUI Draggable page} for general + * description and demo. + * + * @author Sebastian Thierer + * @package zii.widgets.jui + * @since 1.1 + */ +class CJuiDraggable extends CJuiWidget +{ + /** + * @var string the name of the Draggable element. Defaults to 'div'. + */ + public $tagName='div'; + + /** + * Renders the open tag of the draggable element. + * This method also registers the necessary javascript code. + */ + public function init() + { + parent::init(); + + $id=$this->getId(); + if(isset($this->htmlOptions['id'])) + $id=$this->htmlOptions['id']; + else + $this->htmlOptions['id']=$id; + + $options=CJavaScript::encode($this->options); + Yii::app()->getClientScript()->registerScript(__CLASS__.'#'.$id,"jQuery('#{$id}').draggable($options);"); + + echo CHtml::openTag($this->tagName,$this->htmlOptions)."\n"; + } + + /** + * Renders the close tag of the draggable element. + */ + public function run() + { + echo CHtml::closeTag($this->tagName); + } } \ No newline at end of file diff --git a/framework/zii/widgets/jui/CJuiDroppable.php b/framework/zii/widgets/jui/CJuiDroppable.php index cafb3fb46..b429d5f22 100644 --- a/framework/zii/widgets/jui/CJuiDroppable.php +++ b/framework/zii/widgets/jui/CJuiDroppable.php @@ -1,78 +1,78 @@ - - * @link http://www.yiiframework.com/ - * @copyright 2008-2013 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -Yii::import('zii.widgets.jui.CJuiWidget'); - -/** - * CJuiDroppable displays a droppable widget. - * - * CJuiDroppable encapsulates the {@link http://jqueryui.com/droppable/ JUI Droppable} - * plugin. - * - * To use this widget, you may insert the following code in a view: - *
                      - * $this->beginWidget('zii.widgets.jui.CJuiDroppable',array(
                      - *     // additional javascript options for the droppable plugin
                      - *     'options'=>array(
                      - *         'scope'=>'myScope',
                      - *     ),
                      - * ));
                      - *     echo 'Your droppable content here';
                      - *
                      - * $this->endWidget();
                      - *
                      - * 
                      - * - * By configuring the {@link options} property, you may specify the options - * that need to be passed to the JUI Droppable plugin. Please refer to - * the {@link http://api.jqueryui.com/droppable/ JUI Droppable API} documentation - * for possible options (name-value pairs) and - * {@link http://jqueryui.com/droppable/ JUI Droppable page} for general - * description and demo. - * - * @author Sebastian Thierer - * @package zii.widgets.jui - * @since 1.1 - */ -class CJuiDroppable extends CJuiWidget -{ - /** - * @var string the HTML tag name of the Droppable element. Defaults to 'div'. - */ - public $tagName='div'; - - /** - * Renders the open tag of the droppable element. - * This method also registers the necessary javascript code. - */ - public function init() - { - parent::init(); - - $id=$this->getId(); - if(isset($this->htmlOptions['id'])) - $id=$this->htmlOptions['id']; - else - $this->htmlOptions['id']=$id; - - $options=CJavaScript::encode($this->options); - Yii::app()->getClientScript()->registerScript(__CLASS__.'#'.$id,"jQuery('#{$id}').droppable($options);"); - - echo CHtml::openTag($this->tagName,$this->htmlOptions)."\n"; - } - - /** - * Renders the close tag of the droppable element. - */ - public function run() - { - echo CHtml::closeTag($this->tagName); - } + + * @link http://www.yiiframework.com/ + * @copyright 2008-2013 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +Yii::import('zii.widgets.jui.CJuiWidget'); + +/** + * CJuiDroppable displays a droppable widget. + * + * CJuiDroppable encapsulates the {@link http://jqueryui.com/droppable/ JUI Droppable} + * plugin. + * + * To use this widget, you may insert the following code in a view: + *
                      + * $this->beginWidget('zii.widgets.jui.CJuiDroppable',array(
                      + *     // additional javascript options for the droppable plugin
                      + *     'options'=>array(
                      + *         'scope'=>'myScope',
                      + *     ),
                      + * ));
                      + *     echo 'Your droppable content here';
                      + *
                      + * $this->endWidget();
                      + *
                      + * 
                      + * + * By configuring the {@link options} property, you may specify the options + * that need to be passed to the JUI Droppable plugin. Please refer to + * the {@link http://api.jqueryui.com/droppable/ JUI Droppable API} documentation + * for possible options (name-value pairs) and + * {@link http://jqueryui.com/droppable/ JUI Droppable page} for general + * description and demo. + * + * @author Sebastian Thierer + * @package zii.widgets.jui + * @since 1.1 + */ +class CJuiDroppable extends CJuiWidget +{ + /** + * @var string the HTML tag name of the Droppable element. Defaults to 'div'. + */ + public $tagName='div'; + + /** + * Renders the open tag of the droppable element. + * This method also registers the necessary javascript code. + */ + public function init() + { + parent::init(); + + $id=$this->getId(); + if(isset($this->htmlOptions['id'])) + $id=$this->htmlOptions['id']; + else + $this->htmlOptions['id']=$id; + + $options=CJavaScript::encode($this->options); + Yii::app()->getClientScript()->registerScript(__CLASS__.'#'.$id,"jQuery('#{$id}').droppable($options);"); + + echo CHtml::openTag($this->tagName,$this->htmlOptions)."\n"; + } + + /** + * Renders the close tag of the droppable element. + */ + public function run() + { + echo CHtml::closeTag($this->tagName); + } } \ No newline at end of file diff --git a/requirements/css/main.css b/requirements/css/main.css index 7966881fc..bff20d15f 100644 --- a/requirements/css/main.css +++ b/requirements/css/main.css @@ -1,93 +1,93 @@ -body -{ - background: white; - font-family:'Lucida Grande',Verdana,Geneva,Lucida,Helvetica,Arial,sans-serif; - font-size:10pt; - font-weight:normal; -} - -#page -{ - width: 800px; - margin: 0 auto; -} - -#header -{ -} - -#content -{ -} - -#footer -{ - color: gray; - font-size:8pt; - border-top:1px solid #aaa; - margin-top:10px; -} - -h1 -{ - color:black; - font-size:1.6em; - font-weight:bold; - margin:0.5em 0pt; -} - -h2 -{ - color:black; - font-size:1.25em; - font-weight:bold; - margin:0.3em 0pt; -} - -h3 -{ - color:black; - font-size:1.1em; - font-weight:bold; - margin:0.2em 0pt; -} - -table.result -{ - background:#E6ECFF none repeat scroll 0% 0%; - border-collapse:collapse; - width:100%; -} - -table.result th -{ - background:#CCD9FF none repeat scroll 0% 0%; - text-align:left; -} - -table.result th, table.result td -{ - border:1px solid #BFCFFF; - padding:0.2em; -} - -td.passed -{ - background-color: #60BF60; - border: 1px solid silver; - padding: 2px; -} - -td.warning -{ - background-color: #FFFFBF; - border: 1px solid silver; - padding: 2px; -} - -td.failed -{ - background-color: #FF8080; - border: 1px solid silver; - padding: 2px; -} +body +{ + background: white; + font-family:'Lucida Grande',Verdana,Geneva,Lucida,Helvetica,Arial,sans-serif; + font-size:10pt; + font-weight:normal; +} + +#page +{ + width: 800px; + margin: 0 auto; +} + +#header +{ +} + +#content +{ +} + +#footer +{ + color: gray; + font-size:8pt; + border-top:1px solid #aaa; + margin-top:10px; +} + +h1 +{ + color:black; + font-size:1.6em; + font-weight:bold; + margin:0.5em 0pt; +} + +h2 +{ + color:black; + font-size:1.25em; + font-weight:bold; + margin:0.3em 0pt; +} + +h3 +{ + color:black; + font-size:1.1em; + font-weight:bold; + margin:0.2em 0pt; +} + +table.result +{ + background:#E6ECFF none repeat scroll 0% 0%; + border-collapse:collapse; + width:100%; +} + +table.result th +{ + background:#CCD9FF none repeat scroll 0% 0%; + text-align:left; +} + +table.result th, table.result td +{ + border:1px solid #BFCFFF; + padding:0.2em; +} + +td.passed +{ + background-color: #60BF60; + border: 1px solid silver; + padding: 2px; +} + +td.warning +{ + background-color: #FFFFBF; + border: 1px solid silver; + padding: 2px; +} + +td.failed +{ + background-color: #FF8080; + border: 1px solid silver; + padding: 2px; +} diff --git a/requirements/messages/.htaccess b/requirements/messages/.htaccess index e01983226..8d2f25636 100644 --- a/requirements/messages/.htaccess +++ b/requirements/messages/.htaccess @@ -1 +1 @@ -deny from all +deny from all diff --git a/requirements/views/.htaccess b/requirements/views/.htaccess index e01983226..8d2f25636 100644 --- a/requirements/views/.htaccess +++ b/requirements/views/.htaccess @@ -1 +1 @@ -deny from all +deny from all diff --git a/requirements/views/fr/index.php b/requirements/views/fr/index.php index 614c5a30f..653fb66db 100644 --- a/requirements/views/fr/index.php +++ b/requirements/views/fr/index.php @@ -1,75 +1,75 @@ - - - - - - -Vérification de la configuration nécessaire pour Yii - - - -
                      - - - -
                      -

                      Description

                      -

                      -Ce script vérifie si la configuration de votre serveur satisfait toutes les dépendances nécessaires -pour exécuter les applications Yii. -Il vérifie si le serveur exécute la version correcte de PHP, -si toutes les extensions PHP nécessaires ont été chargées, et si les paramètres du fichier php.ini sont corrects -

                      - -

                      Conclusion

                      -

                      -0): ?> -Félicitations ! Votre configuration vérifie toutes les exigences de Yii. - -Votre configuration satisfait les exigences minimales de Yii. Notez les avertissements listés ci-dessous si votre application utilise les fonctionnalités correspondantes. - -Malheureusement, votre configuration ne satisfait pas les exigences de Yii. - -

                      - -

                      Details

                      - - - - - - - - - - - -
                      NomResultatRequis ParInfo
                      - - - - - - - -
                      - - - - - - - -
                       Ok Echec Avertissement
                      - -
                      - - - -
                      - + + + + + + +Vérification de la configuration nécessaire pour Yii + + + +
                      + + + +
                      +

                      Description

                      +

                      +Ce script vérifie si la configuration de votre serveur satisfait toutes les dépendances nécessaires +pour exécuter les applications Yii. +Il vérifie si le serveur exécute la version correcte de PHP, +si toutes les extensions PHP nécessaires ont été chargées, et si les paramètres du fichier php.ini sont corrects +

                      + +

                      Conclusion

                      +

                      +0): ?> +Félicitations ! Votre configuration vérifie toutes les exigences de Yii. + +Votre configuration satisfait les exigences minimales de Yii. Notez les avertissements listés ci-dessous si votre application utilise les fonctionnalités correspondantes. + +Malheureusement, votre configuration ne satisfait pas les exigences de Yii. + +

                      + +

                      Details

                      + + + + + + + + + + + +
                      NomResultatRequis ParInfo
                      + + + + + + + +
                      + + + + + + + +
                       Ok Echec Avertissement
                      + +
                      + + + +
                      + \ No newline at end of file diff --git a/tests/framework/db/schema/CMysqlTest.php b/tests/framework/db/schema/CMysqlTest.php index 5829411fc..6f1240a93 100644 --- a/tests/framework/db/schema/CMysqlTest.php +++ b/tests/framework/db/schema/CMysqlTest.php @@ -201,16 +201,16 @@ class CMysqlTest extends CTestCase $c=$builder->createSqlCommand('SELECT author_id FROM posts WHERE id=5'); $this->assertEquals(2,$c->queryScalar()); - // test for updates with joins - $c=$builder->createUpdateCommand($table,array('title'=>'new post 1'),new CDbCriteria(array( - 'condition'=>'u.`username`=:username', - 'join'=>'JOIN `users` u ON `author_id`=u.`id`', - 'params'=>array(':username'=>'user1')))); - $c->execute(); - $c=$builder->createFindCommand($table,new CDbCriteria(array( - 'select'=>'title', - 'condition'=>'id=:id', - 'params'=>array('id'=>1)))); + // test for updates with joins + $c=$builder->createUpdateCommand($table,array('title'=>'new post 1'),new CDbCriteria(array( + 'condition'=>'u.`username`=:username', + 'join'=>'JOIN `users` u ON `author_id`=u.`id`', + 'params'=>array(':username'=>'user1')))); + $c->execute(); + $c=$builder->createFindCommand($table,new CDbCriteria(array( + 'select'=>'title', + 'condition'=>'id=:id', + 'params'=>array('id'=>1)))); $this->assertEquals('new post 1',$c->queryScalar()); $c=$builder->createUpdateCounterCommand($table,array('author_id'=>-1),new CDbCriteria(array(