From 3c2e614d876a6042ce8452b7045f4e0bf9275466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A4=D1=91=D0=B4=D0=BE=D1=80=20=D0=9F=D0=BE=D0=B4=D0=BB?= =?UTF-8?q?=D0=B5=D1=81=D0=BD=D0=BE=D0=B2?= Date: Wed, 29 Jun 2016 18:51:32 +0300 Subject: [PATCH] =?UTF-8?q?=D0=91=D0=B8=D0=B1=D0=BB=D0=B8=D0=BE=D1=82?= =?UTF-8?q?=D0=B5=D0=BA=D0=B0=20=D0=B4=D0=BB=D1=8F=20cis,=20online,=20cms1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHPTAL.php | 788 +++++ PHPTAL/CommentFilter.php | 12 + PHPTAL/Context.php | 405 +++ PHPTAL/Dom/Defs.php | 237 ++ PHPTAL/Dom/Node.php | 254 ++ PHPTAL/Dom/Parser.php | 154 + PHPTAL/Dom/XmlParser.php | 386 +++ PHPTAL/Dom/XmlnsState.php | 98 + PHPTAL/Exception.php | 65 + PHPTAL/FileSource.php | 64 + PHPTAL/Filter.php | 31 + PHPTAL/GetTextTranslator.php | 143 + PHPTAL/Namespace.php | 163 + PHPTAL/Namespace/I18N.php | 28 + PHPTAL/Namespace/METAL.php | 28 + PHPTAL/Namespace/PHPTAL.php | 28 + PHPTAL/Namespace/TAL.php | 37 + PHPTAL/Php/Attribute.php | 98 + PHPTAL/Php/Attribute/I18N/Attributes.php | 107 + PHPTAL/Php/Attribute/I18N/Data.php | 24 + PHPTAL/Php/Attribute/I18N/Domain.php | 41 + PHPTAL/Php/Attribute/I18N/Name.php | 40 + PHPTAL/Php/Attribute/I18N/Source.php | 38 + PHPTAL/Php/Attribute/I18N/Target.php | 32 + PHPTAL/Php/Attribute/I18N/Translate.php | 97 + PHPTAL/Php/Attribute/METAL/DefineMacro.php | 71 + PHPTAL/Php/Attribute/METAL/DefineSlot.php | 75 + PHPTAL/Php/Attribute/METAL/FillSlot.php | 75 + PHPTAL/Php/Attribute/METAL/UseMacro.php | 131 + PHPTAL/Php/Attribute/PHPTAL/Cache.php | 92 + PHPTAL/Php/Attribute/PHPTAL/Debug.php | 44 + PHPTAL/Php/Attribute/PHPTAL/Id.php | 66 + PHPTAL/Php/Attribute/PHPTAL/Tales.php | 60 + PHPTAL/Php/Attribute/TAL/Attributes.php | 168 + PHPTAL/Php/Attribute/TAL/Comment.php | 40 + PHPTAL/Php/Attribute/TAL/Condition.php | 101 + PHPTAL/Php/Attribute/TAL/Content.php | 98 + PHPTAL/Php/Attribute/TAL/Define.php | 183 ++ PHPTAL/Php/Attribute/TAL/OmitTag.php | 74 + PHPTAL/Php/Attribute/TAL/OnError.php | 81 + PHPTAL/Php/Attribute/TAL/Repeat.php | 115 + PHPTAL/Php/Attribute/TAL/Replace.php | 117 + PHPTAL/Php/CodeGenerator.php | 55 + PHPTAL/Php/CodeWriter.php | 394 +++ PHPTAL/Php/ElementWriter.php | 101 + PHPTAL/Php/Node.php | 507 +++ PHPTAL/Php/State.php | 152 + PHPTAL/Php/Tales.php | 135 + PHPTAL/Php/TalesChainExecutor.php | 99 + PHPTAL/Php/TalesInternal.php | 331 ++ PHPTAL/Php/Transformer.php | 384 +++ PHPTAL/RepeatController.php | 477 +++ PHPTAL/Source.php | 16 + PHPTAL/SourceResolver.php | 16 + PHPTAL/StringSource.php | 34 + PHPTAL/Tales.php | 7 + PHPTAL/TalesRegistry.php | 109 + PHPTAL/TranslationService.php | 41 + PHPTAL/Trigger.php | 36 + core/adapter.php | 24 + core/arr.php | 7 + core/collection.php | 100 + core/connection/all.php | 4 + core/connection/data.txt | 10 + core/connection/httpconnection.php | 95 + core/connection/httpconnectionresponse.php | 82 + core/connection/idna_convert.php | 2707 +++++++++++++++++ core/controller/admincontroller.php | 433 +++ core/controller/component.php | 185 ++ core/controller/controller.php | 334 ++ core/controller/frontcontroller.php | 92 + core/controller/installer.php | 89 + core/controller/state.php | 79 + core/data/areas.php | 26 + core/data/city.php | 28 + core/data/mime.php | 196 ++ core/data/okato.php | 35 + core/data/regions.php | 92 + core/data/states.php | 35 + core/database.php | 18 + core/database_pdo.php | 305 ++ core/drivers/database.mysql.php | 50 + core/drivers/database.odbc.php | 54 + core/drivers/database.pgsql.php | 74 + core/drivers/db.php | 50 + core/error.php | 86 + core/file.php | 82 + core/filesystem.php | 583 ++++ core/filter/actionaccess.php | 34 + core/filter/actionlogger.php | 26 + core/filter/filter.php | 72 + core/filter/filterbase.php | 28 + core/filter/filterlogin.php | 105 + core/form.php | 335 ++ core/form/form.php | 180 ++ core/form/viewstate.php | 43 + core/formats/dot.php | 52 + core/formats/helix.php | 86 + core/functions.php | 346 +++ core/geometry/point.php | 13 + core/geometry/rectangle.php | 53 + core/httprequest.php | 136 + core/json.php | 97 + core/layout/layout.php | 93 + core/mail.php | 210 ++ core/mapper/factory.php | 30 + core/mapper/mapper.php | 267 ++ core/mapper/pathmapper.php | 78 + core/markup/simple_bb_code.php | 113 + core/numbers.php | 24 + core/path.php | 319 ++ core/primitive.php | 105 + core/query/meta.php | 10 + core/query/query.php | 206 ++ core/query/sql.txt | 865 ++++++ core/query/table.php | 15 + core/registry.php | 25 + core/safecollection.php | 37 + core/search/htmlhelper.php | 85 + core/search/index.php | 86 + core/search/lexer.php | 93 + core/search/search.php | 98 + core/search/searcher.php | 102 + core/search/stemmer.php | 181 ++ core/session.php | 36 + core/settings.php | 167 + core/setup.php | 59 + core/shortcut.php | 58 + core/sort.php | 58 + core/spell.php | 100 + core/spyc.php | 741 +++++ core/tabletree.php | 69 + core/tales.php | 36 + core/tools/drawing.php | 123 + core/tools/exceltable.php | 438 +++ core/tools/image.php | 40 + core/tools/password.php | 33 + core/tools/string.php | 37 + core/tools/tableview.php | 94 + core/tools/templateimage.php | 207 ++ core/tools/translit.php | 14 + core/tree/database.php | 135 + core/tree/dbtree.php | 636 ++++ core/tree/sort.php | 79 + core/validator/rule/abstract.php | 41 + core/validator/rule/all.php | 2 + core/validator/rule/alpha.php | 20 + core/validator/rule/code.php | 59 + core/validator/rule/count.php | 34 + core/validator/rule/date.php | 24 + core/validator/rule/email.php | 30 + core/validator/rule/emaillist.php | 28 + core/validator/rule/match.php | 28 + core/validator/rule/notnull.php | 25 + core/validator/rule/numeric.php | 19 + core/validator/rule/time.php | 35 + core/validator/rule/unique.php | 20 + core/validator/validator.php | 118 + core/view/compositeview.php | 298 ++ core/view/view.php | 40 + core/widgets/dialog.php | 36 + core/widgets/filebrowser.php | 39 + core/widgets/listtable.php | 71 + core/widgets/menu.php | 38 + core/widgets/pagemenu.php | 30 + core/widgets/pages.php | 16 + core/widgets/search.php | 32 + core/widgets/setup.php | 20 + core/widgets/tree.php | 17 + core/widgets/widget.php | 87 + core/zipfile.php | 41 + creole/CallableStatement.php | 135 + creole/Connection.php | 220 ++ creole/Creole.php | 390 +++ creole/CreoleTypes.php | 183 ++ creole/IdGenerator.php | 57 + creole/PreparedStatement.php | 253 ++ creole/ResultSet.php | 380 +++ creole/ResultSetIterator.php | 113 + creole/SQLException.php | 105 + creole/Statement.php | 147 + creole/common/ConnectionCommon.php | 258 ++ creole/common/PreparedStatementCommon.php | 640 ++++ creole/common/ResultSetCommon.php | 447 +++ creole/common/StatementCommon.php | 289 ++ creole/contrib/DebugConnection.php | 268 ++ .../drivers/mssql/MSSQLCallableStatement.php | 478 +++ creole/drivers/mssql/MSSQLConnection.php | 283 ++ creole/drivers/mssql/MSSQLIdGenerator.php | 62 + .../drivers/mssql/MSSQLPreparedStatement.php | 99 + creole/drivers/mssql/MSSQLResultSet.php | 159 + creole/drivers/mssql/MSSQLStatement.php | 72 + creole/drivers/mssql/MSSQLTypes.php | 94 + .../mssql/metadata/MSSQLDatabaseInfo.php | 69 + .../drivers/mssql/metadata/MSSQLTableInfo.php | 183 ++ creole/drivers/mysql/MySQLConnection.php | 290 ++ creole/drivers/mysql/MySQLIdGenerator.php | 75 + .../drivers/mysql/MySQLPreparedStatement.php | 44 + creole/drivers/mysql/MySQLResultSet.php | 148 + creole/drivers/mysql/MySQLStatement.php | 36 + creole/drivers/mysql/MySQLTypes.php | 102 + .../mysql/metadata/MySQLDatabaseInfo.php | 66 + .../drivers/mysql/metadata/MySQLTableInfo.php | 252 ++ creole/drivers/mysqli/MySQLiConnection.php | 243 ++ creole/drivers/mysqli/MySQLiIdGenerator.php | 96 + .../mysqli/MySQLiPreparedStatement.php | 42 + creole/drivers/mysqli/MySQLiResultSet.php | 173 ++ creole/drivers/mysqli/MySQLiStatement.php | 33 + .../mysqli/metadata/MySQLiDatabaseInfo.php | 61 + .../mysqli/metadata/MySQLiTableInfo.php | 143 + creole/drivers/odbc/ODBCCachedResultSet.php | 218 ++ creole/drivers/odbc/ODBCConnection.php | 362 +++ creole/drivers/odbc/ODBCIdGenerator.php | 118 + creole/drivers/odbc/ODBCPreparedStatement.php | 246 ++ creole/drivers/odbc/ODBCResultSet.php | 209 ++ creole/drivers/odbc/ODBCResultSetCommon.php | 188 ++ creole/drivers/odbc/ODBCStatement.php | 64 + creole/drivers/odbc/ODBCTypes.php | 189 ++ creole/drivers/odbc/README | 90 + .../drivers/odbc/adapters/CodeBaseAdapter.php | 73 + creole/drivers/odbc/adapters/MySQLAdapter.php | 78 + creole/drivers/odbc/adapters/ODBCAdapter.php | 115 + .../odbc/metadata/ODBCDatabaseInfo.php | 66 + .../drivers/odbc/metadata/ODBCTableInfo.php | 141 + creole/drivers/oracle/OCI8Connection.php | 393 +++ creole/drivers/oracle/OCI8IdGenerator.php | 65 + .../drivers/oracle/OCI8PreparedStatement.php | 424 +++ creole/drivers/oracle/OCI8ResultSet.php | 131 + creole/drivers/oracle/OCI8Statement.php | 34 + creole/drivers/oracle/OCI8Types.php | 88 + .../oracle/metadata/OCI8DatabaseInfo.php | 90 + .../drivers/oracle/metadata/OCI8TableInfo.php | 273 ++ creole/drivers/pgsql/PgSQLConnection.php | 260 ++ creole/drivers/pgsql/PgSQLIdGenerator.php | 84 + .../drivers/pgsql/PgSQLPreparedStatement.php | 157 + creole/drivers/pgsql/PgSQLResultSet.php | 213 ++ .../drivers/pgsql/PgSQLResultSetIterator.php | 109 + creole/drivers/pgsql/PgSQLStatement.php | 34 + creole/drivers/pgsql/PgSQLTypes.php | 101 + .../pgsql/metadata/PgSQLDatabaseInfo.php | 113 + .../drivers/pgsql/metadata/PgSQLTableInfo.php | 423 +++ creole/drivers/sqlite/SQLiteConnection.php | 245 ++ creole/drivers/sqlite/SQLiteIdGenerator.php | 60 + .../sqlite/SQLitePreparedStatement.php | 61 + creole/drivers/sqlite/SQLiteResultSet.php | 119 + .../sqlite/SQLiteResultSetIterator.php | 88 + creole/drivers/sqlite/SQLiteStatement.php | 34 + creole/drivers/sqlite/SQLiteTypes.php | 108 + .../sqlite/metadata/SQLiteDatabaseInfo.php | 64 + .../sqlite/metadata/SQLiteTableInfo.php | 135 + creole/metadata/ColumnInfo.php | 232 ++ creole/metadata/DatabaseInfo.php | 206 ++ creole/metadata/ForeignKeyInfo.php | 103 + creole/metadata/IndexInfo.php | 84 + creole/metadata/PrimaryKeyInfo.php | 91 + creole/metadata/TableInfo.php | 305 ++ creole/util/Blob.php | 62 + creole/util/Clob.php | 112 + creole/util/Lob.php | 243 ++ creole/util/sql/SQLStatementExtractor.php | 164 + h2o.php | 267 ++ h2o/context.php | 264 ++ h2o/datatype.php | 176 ++ h2o/errors.php | 11 + h2o/filters.php | 345 +++ h2o/loaders.php | 229 ++ h2o/nodes.php | 88 + h2o/parser.php | 291 ++ h2o/tags.php | 436 +++ 269 files changed, 39854 insertions(+) create mode 100644 PHPTAL.php create mode 100644 PHPTAL/CommentFilter.php create mode 100644 PHPTAL/Context.php create mode 100644 PHPTAL/Dom/Defs.php create mode 100644 PHPTAL/Dom/Node.php create mode 100644 PHPTAL/Dom/Parser.php create mode 100644 PHPTAL/Dom/XmlParser.php create mode 100644 PHPTAL/Dom/XmlnsState.php create mode 100644 PHPTAL/Exception.php create mode 100644 PHPTAL/FileSource.php create mode 100644 PHPTAL/Filter.php create mode 100644 PHPTAL/GetTextTranslator.php create mode 100644 PHPTAL/Namespace.php create mode 100644 PHPTAL/Namespace/I18N.php create mode 100644 PHPTAL/Namespace/METAL.php create mode 100644 PHPTAL/Namespace/PHPTAL.php create mode 100644 PHPTAL/Namespace/TAL.php create mode 100644 PHPTAL/Php/Attribute.php create mode 100644 PHPTAL/Php/Attribute/I18N/Attributes.php create mode 100644 PHPTAL/Php/Attribute/I18N/Data.php create mode 100644 PHPTAL/Php/Attribute/I18N/Domain.php create mode 100644 PHPTAL/Php/Attribute/I18N/Name.php create mode 100644 PHPTAL/Php/Attribute/I18N/Source.php create mode 100644 PHPTAL/Php/Attribute/I18N/Target.php create mode 100644 PHPTAL/Php/Attribute/I18N/Translate.php create mode 100644 PHPTAL/Php/Attribute/METAL/DefineMacro.php create mode 100644 PHPTAL/Php/Attribute/METAL/DefineSlot.php create mode 100644 PHPTAL/Php/Attribute/METAL/FillSlot.php create mode 100644 PHPTAL/Php/Attribute/METAL/UseMacro.php create mode 100644 PHPTAL/Php/Attribute/PHPTAL/Cache.php create mode 100644 PHPTAL/Php/Attribute/PHPTAL/Debug.php create mode 100644 PHPTAL/Php/Attribute/PHPTAL/Id.php create mode 100644 PHPTAL/Php/Attribute/PHPTAL/Tales.php create mode 100644 PHPTAL/Php/Attribute/TAL/Attributes.php create mode 100644 PHPTAL/Php/Attribute/TAL/Comment.php create mode 100644 PHPTAL/Php/Attribute/TAL/Condition.php create mode 100644 PHPTAL/Php/Attribute/TAL/Content.php create mode 100644 PHPTAL/Php/Attribute/TAL/Define.php create mode 100644 PHPTAL/Php/Attribute/TAL/OmitTag.php create mode 100644 PHPTAL/Php/Attribute/TAL/OnError.php create mode 100644 PHPTAL/Php/Attribute/TAL/Repeat.php create mode 100644 PHPTAL/Php/Attribute/TAL/Replace.php create mode 100644 PHPTAL/Php/CodeGenerator.php create mode 100644 PHPTAL/Php/CodeWriter.php create mode 100644 PHPTAL/Php/ElementWriter.php create mode 100644 PHPTAL/Php/Node.php create mode 100644 PHPTAL/Php/State.php create mode 100644 PHPTAL/Php/Tales.php create mode 100644 PHPTAL/Php/TalesChainExecutor.php create mode 100644 PHPTAL/Php/TalesInternal.php create mode 100644 PHPTAL/Php/Transformer.php create mode 100644 PHPTAL/RepeatController.php create mode 100644 PHPTAL/Source.php create mode 100644 PHPTAL/SourceResolver.php create mode 100644 PHPTAL/StringSource.php create mode 100644 PHPTAL/Tales.php create mode 100644 PHPTAL/TalesRegistry.php create mode 100644 PHPTAL/TranslationService.php create mode 100644 PHPTAL/Trigger.php create mode 100644 core/adapter.php create mode 100644 core/arr.php create mode 100644 core/collection.php create mode 100644 core/connection/all.php create mode 100644 core/connection/data.txt create mode 100644 core/connection/httpconnection.php create mode 100644 core/connection/httpconnectionresponse.php create mode 100644 core/connection/idna_convert.php create mode 100644 core/controller/admincontroller.php create mode 100644 core/controller/component.php create mode 100644 core/controller/controller.php create mode 100644 core/controller/frontcontroller.php create mode 100644 core/controller/installer.php create mode 100644 core/controller/state.php create mode 100644 core/data/areas.php create mode 100644 core/data/city.php create mode 100644 core/data/mime.php create mode 100644 core/data/okato.php create mode 100644 core/data/regions.php create mode 100644 core/data/states.php create mode 100644 core/database.php create mode 100644 core/database_pdo.php create mode 100644 core/drivers/database.mysql.php create mode 100644 core/drivers/database.odbc.php create mode 100644 core/drivers/database.pgsql.php create mode 100644 core/drivers/db.php create mode 100644 core/error.php create mode 100644 core/file.php create mode 100644 core/filesystem.php create mode 100644 core/filter/actionaccess.php create mode 100644 core/filter/actionlogger.php create mode 100644 core/filter/filter.php create mode 100644 core/filter/filterbase.php create mode 100644 core/filter/filterlogin.php create mode 100644 core/form.php create mode 100644 core/form/form.php create mode 100644 core/form/viewstate.php create mode 100644 core/formats/dot.php create mode 100644 core/formats/helix.php create mode 100644 core/functions.php create mode 100644 core/geometry/point.php create mode 100644 core/geometry/rectangle.php create mode 100644 core/httprequest.php create mode 100644 core/json.php create mode 100644 core/layout/layout.php create mode 100644 core/mail.php create mode 100644 core/mapper/factory.php create mode 100644 core/mapper/mapper.php create mode 100644 core/mapper/pathmapper.php create mode 100644 core/markup/simple_bb_code.php create mode 100644 core/numbers.php create mode 100644 core/path.php create mode 100644 core/primitive.php create mode 100644 core/query/meta.php create mode 100644 core/query/query.php create mode 100644 core/query/sql.txt create mode 100644 core/query/table.php create mode 100644 core/registry.php create mode 100644 core/safecollection.php create mode 100644 core/search/htmlhelper.php create mode 100644 core/search/index.php create mode 100644 core/search/lexer.php create mode 100644 core/search/search.php create mode 100644 core/search/searcher.php create mode 100644 core/search/stemmer.php create mode 100644 core/session.php create mode 100644 core/settings.php create mode 100644 core/setup.php create mode 100644 core/shortcut.php create mode 100644 core/sort.php create mode 100644 core/spell.php create mode 100644 core/spyc.php create mode 100644 core/tabletree.php create mode 100644 core/tales.php create mode 100644 core/tools/drawing.php create mode 100644 core/tools/exceltable.php create mode 100644 core/tools/image.php create mode 100644 core/tools/password.php create mode 100644 core/tools/string.php create mode 100644 core/tools/tableview.php create mode 100644 core/tools/templateimage.php create mode 100644 core/tools/translit.php create mode 100644 core/tree/database.php create mode 100644 core/tree/dbtree.php create mode 100644 core/tree/sort.php create mode 100644 core/validator/rule/abstract.php create mode 100644 core/validator/rule/all.php create mode 100644 core/validator/rule/alpha.php create mode 100644 core/validator/rule/code.php create mode 100644 core/validator/rule/count.php create mode 100644 core/validator/rule/date.php create mode 100644 core/validator/rule/email.php create mode 100644 core/validator/rule/emaillist.php create mode 100644 core/validator/rule/match.php create mode 100644 core/validator/rule/notnull.php create mode 100644 core/validator/rule/numeric.php create mode 100644 core/validator/rule/time.php create mode 100644 core/validator/rule/unique.php create mode 100644 core/validator/validator.php create mode 100644 core/view/compositeview.php create mode 100644 core/view/view.php create mode 100644 core/widgets/dialog.php create mode 100644 core/widgets/filebrowser.php create mode 100644 core/widgets/listtable.php create mode 100644 core/widgets/menu.php create mode 100644 core/widgets/pagemenu.php create mode 100644 core/widgets/pages.php create mode 100644 core/widgets/search.php create mode 100644 core/widgets/setup.php create mode 100644 core/widgets/tree.php create mode 100644 core/widgets/widget.php create mode 100644 core/zipfile.php create mode 100644 creole/CallableStatement.php create mode 100644 creole/Connection.php create mode 100644 creole/Creole.php create mode 100644 creole/CreoleTypes.php create mode 100644 creole/IdGenerator.php create mode 100644 creole/PreparedStatement.php create mode 100644 creole/ResultSet.php create mode 100644 creole/ResultSetIterator.php create mode 100644 creole/SQLException.php create mode 100644 creole/Statement.php create mode 100644 creole/common/ConnectionCommon.php create mode 100644 creole/common/PreparedStatementCommon.php create mode 100644 creole/common/ResultSetCommon.php create mode 100644 creole/common/StatementCommon.php create mode 100644 creole/contrib/DebugConnection.php create mode 100644 creole/drivers/mssql/MSSQLCallableStatement.php create mode 100644 creole/drivers/mssql/MSSQLConnection.php create mode 100644 creole/drivers/mssql/MSSQLIdGenerator.php create mode 100644 creole/drivers/mssql/MSSQLPreparedStatement.php create mode 100644 creole/drivers/mssql/MSSQLResultSet.php create mode 100644 creole/drivers/mssql/MSSQLStatement.php create mode 100644 creole/drivers/mssql/MSSQLTypes.php create mode 100644 creole/drivers/mssql/metadata/MSSQLDatabaseInfo.php create mode 100644 creole/drivers/mssql/metadata/MSSQLTableInfo.php create mode 100644 creole/drivers/mysql/MySQLConnection.php create mode 100644 creole/drivers/mysql/MySQLIdGenerator.php create mode 100644 creole/drivers/mysql/MySQLPreparedStatement.php create mode 100644 creole/drivers/mysql/MySQLResultSet.php create mode 100644 creole/drivers/mysql/MySQLStatement.php create mode 100644 creole/drivers/mysql/MySQLTypes.php create mode 100644 creole/drivers/mysql/metadata/MySQLDatabaseInfo.php create mode 100644 creole/drivers/mysql/metadata/MySQLTableInfo.php create mode 100644 creole/drivers/mysqli/MySQLiConnection.php create mode 100644 creole/drivers/mysqli/MySQLiIdGenerator.php create mode 100644 creole/drivers/mysqli/MySQLiPreparedStatement.php create mode 100644 creole/drivers/mysqli/MySQLiResultSet.php create mode 100644 creole/drivers/mysqli/MySQLiStatement.php create mode 100644 creole/drivers/mysqli/metadata/MySQLiDatabaseInfo.php create mode 100644 creole/drivers/mysqli/metadata/MySQLiTableInfo.php create mode 100644 creole/drivers/odbc/ODBCCachedResultSet.php create mode 100644 creole/drivers/odbc/ODBCConnection.php create mode 100644 creole/drivers/odbc/ODBCIdGenerator.php create mode 100644 creole/drivers/odbc/ODBCPreparedStatement.php create mode 100644 creole/drivers/odbc/ODBCResultSet.php create mode 100644 creole/drivers/odbc/ODBCResultSetCommon.php create mode 100644 creole/drivers/odbc/ODBCStatement.php create mode 100644 creole/drivers/odbc/ODBCTypes.php create mode 100644 creole/drivers/odbc/README create mode 100644 creole/drivers/odbc/adapters/CodeBaseAdapter.php create mode 100644 creole/drivers/odbc/adapters/MySQLAdapter.php create mode 100644 creole/drivers/odbc/adapters/ODBCAdapter.php create mode 100644 creole/drivers/odbc/metadata/ODBCDatabaseInfo.php create mode 100644 creole/drivers/odbc/metadata/ODBCTableInfo.php create mode 100644 creole/drivers/oracle/OCI8Connection.php create mode 100644 creole/drivers/oracle/OCI8IdGenerator.php create mode 100644 creole/drivers/oracle/OCI8PreparedStatement.php create mode 100644 creole/drivers/oracle/OCI8ResultSet.php create mode 100644 creole/drivers/oracle/OCI8Statement.php create mode 100644 creole/drivers/oracle/OCI8Types.php create mode 100644 creole/drivers/oracle/metadata/OCI8DatabaseInfo.php create mode 100644 creole/drivers/oracle/metadata/OCI8TableInfo.php create mode 100644 creole/drivers/pgsql/PgSQLConnection.php create mode 100644 creole/drivers/pgsql/PgSQLIdGenerator.php create mode 100644 creole/drivers/pgsql/PgSQLPreparedStatement.php create mode 100644 creole/drivers/pgsql/PgSQLResultSet.php create mode 100644 creole/drivers/pgsql/PgSQLResultSetIterator.php create mode 100644 creole/drivers/pgsql/PgSQLStatement.php create mode 100644 creole/drivers/pgsql/PgSQLTypes.php create mode 100644 creole/drivers/pgsql/metadata/PgSQLDatabaseInfo.php create mode 100644 creole/drivers/pgsql/metadata/PgSQLTableInfo.php create mode 100644 creole/drivers/sqlite/SQLiteConnection.php create mode 100644 creole/drivers/sqlite/SQLiteIdGenerator.php create mode 100644 creole/drivers/sqlite/SQLitePreparedStatement.php create mode 100644 creole/drivers/sqlite/SQLiteResultSet.php create mode 100644 creole/drivers/sqlite/SQLiteResultSetIterator.php create mode 100644 creole/drivers/sqlite/SQLiteStatement.php create mode 100644 creole/drivers/sqlite/SQLiteTypes.php create mode 100644 creole/drivers/sqlite/metadata/SQLiteDatabaseInfo.php create mode 100644 creole/drivers/sqlite/metadata/SQLiteTableInfo.php create mode 100644 creole/metadata/ColumnInfo.php create mode 100644 creole/metadata/DatabaseInfo.php create mode 100644 creole/metadata/ForeignKeyInfo.php create mode 100644 creole/metadata/IndexInfo.php create mode 100644 creole/metadata/PrimaryKeyInfo.php create mode 100644 creole/metadata/TableInfo.php create mode 100644 creole/util/Blob.php create mode 100644 creole/util/Clob.php create mode 100644 creole/util/Lob.php create mode 100644 creole/util/sql/SQLStatementExtractor.php create mode 100644 h2o.php create mode 100644 h2o/context.php create mode 100644 h2o/datatype.php create mode 100644 h2o/errors.php create mode 100644 h2o/filters.php create mode 100644 h2o/loaders.php create mode 100644 h2o/nodes.php create mode 100644 h2o/parser.php create mode 100644 h2o/tags.php diff --git a/PHPTAL.php b/PHPTAL.php new file mode 100644 index 0000000..88acbb7 --- /dev/null +++ b/PHPTAL.php @@ -0,0 +1,788 @@ + +// + +define('PHPTAL_VERSION', '1_1_14'); + +//{{{PHPTAL_DIR +if (!defined('PHPTAL_DIR')) define('PHPTAL_DIR',dirname(__FILE__).DIRECTORY_SEPARATOR); +else assert('substr(PHPTAL_DIR,-1) == DIRECTORY_SEPARATOR'); +//}}} + +/* Please don't use the following constants. They have been replaced by methods in the PHPTAL class and are kept for backwards compatibility only. */ +//{{{ +if (!defined('PHPTAL_PHP_CODE_DESTINATION')) { + if (function_exists('sys_get_temp_dir')) define('PHPTAL_PHP_CODE_DESTINATION',rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR); + else if (substr(PHP_OS,0,3) == 'WIN') { + if (file_exists('c:\\WINNT\\Temp\\')) define('PHPTAL_PHP_CODE_DESTINATION', 'c:\\WINNT\\Temp\\'); + else define('PHPTAL_PHP_CODE_DESTINATION', 'c:\\WINDOWS\\Temp\\'); + } + else define('PHPTAL_PHP_CODE_DESTINATION', '/tmp/'); +} +if (!defined('PHPTAL_DEFAULT_ENCODING')) define('PHPTAL_DEFAULT_ENCODING', 'UTF-8'); +if (!defined('PHPTAL_PHP_CODE_EXTENSION')) define('PHPTAL_PHP_CODE_EXTENSION', 'php'); +//}}} + +define('PHPTAL_XHTML', 1); +define('PHPTAL_XML', 2); + +require_once PHPTAL_DIR.'PHPTAL/FileSource.php'; +require_once PHPTAL_DIR.'PHPTAL/RepeatController.php'; +require_once PHPTAL_DIR.'PHPTAL/Context.php'; +require_once PHPTAL_DIR.'PHPTAL/Exception.php'; +require_once PHPTAL_DIR.'PHPTAL/TalesRegistry.php'; + + +/** + * PHPTAL template entry point. + * + * + * title = 'Welcome here'; + * $tpl->result = range(1, 100); + * ... + * echo $tpl->execute(); + * } + * catch (Exception $e) { + * echo $e; + * } + * ?> + * + * + * @author Laurent Bedubourg + */ +class PHPTAL +{ + const XHTML = 1; + const XML = 2; + + /** + * PHPTAL Constructor. + * + * @param string $path Template file path. + */ + public function __construct($path=false) + { + $this->_path = $path; + $this->_repositories = array(); + if (defined('PHPTAL_TEMPLATE_REPOSITORY')){ + $this->_repositories[] = PHPTAL_TEMPLATE_REPOSITORY; + } + $this->_resolvers = array(); + $this->_globalContext = new StdClass(); + $this->_context = new PHPTAL_Context(); + $this->_context->setGlobal($this->_globalContext); + } + + /** + * create + * returns a new PHPTAL object + * + * @param string $path Template file path. + * @return PHPTAL + */ + public static function create($path=false) + { + return new PHPTAL($path); + } + + /** + * Clone template state and context. + */ + public function __clone() + { + $context = $this->_context; + $this->_context = clone $this->_context; + $this->_context->setParent($context); + $this->_context->setGlobal($this->_globalContext); + } + + /** + * Set template from file path. + * @param $path string + */ + public function setTemplate($path) + { + $this->_prepared = false; + $this->_functionName = null; + $this->_path = $path; + $this->_source = null; + return $this; + } + + /** + * Set template from source. + * + * Should be used only with temporary template sources. Use setTemplate() whenever possible. + * + * @param $src string The phptal template source. + * @param path string Fake and 'unique' template path. + */ + public function setSource($src, $path=false) + { + if ($path == false) + $path = ' '.md5($src); + + require_once PHPTAL_DIR.'PHPTAL/StringSource.php'; + $this->_source = new PHPTAL_StringSource($src, $path); + $this->_path = $path; + return $this; + } + + /** + * Specify where to look for templates. + * + * @param $rep mixed string or Array of repositories + */ + public function setTemplateRepository($rep) + { + if (is_array($rep)){ + $this->_repositories = $rep; + } + else { + $this->_repositories[] = $rep; + } + return $this; + } + + /** + * Get template repositories. + */ + public function getTemplateRepositories() + { + return $this->_repositories; + } + + /** + * Clears the template repositories. + */ + public function clearTemplateRepositories() + { + $this->_repositories = array(); + return $this; + } + + /** + * Ignore XML/XHTML comments on parsing. + * @param $bool bool + */ + public function stripComments($bool) + { + $this->_stripComments = $bool; + return $this; + } + + /** + * Set output mode + * XHTML output mode will force elements like , and , etc. to be empty + * and threats attributes like selected, checked to be boolean attributes. + * + * XML output mode outputs XML without such modifications and is neccessary to generate RSS feeds properly. + * @param $mode int (PHPTAL::XML or PHPTAL::XHTML). + */ + public function setOutputMode($mode=PHPTAL_XHTML) + { + if ($mode != PHPTAL::XHTML && $mode != PHPTAL::XML){ + throw new PHPTAL_Exception('Unsupported output mode '.$mode); + } + $this->_outputMode = $mode; + return $this; + } + + /** + * Get output mode + */ + public function getOutputMode() + { + return $this->_outputMode; + } + + /** + * Set input and ouput encoding. + * @param $enc string example: 'UTF-8' + */ + public function setEncoding($enc) + { + $this->_encoding = $enc; + if ($this->_translator) $this->_translator->setEncoding($enc); + return $this; + } + + /** + * Get input and ouput encoding. + * @param $enc string example: 'UTF-8' + */ + public function getEncoding() + { + return $this->_encoding; + } + + /** + * Set the storage location for intermediate PHP files. The path cannot contain characters that would be interpreted by glob() (e.g. * or ?) + * @param string $path Intermediate file path. + */ + public function setPhpCodeDestination($path) + { + $this->_phpCodeDestination = rtrim($path, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; + return $this; + } + + /** + * Get the storage location for intermediate PHP files. + */ + public function getPhpCodeDestination() + { + return $this->_phpCodeDestination; + } + + /** + * Set the file extension for intermediate PHP files. + * @param string $extension The file extension. + */ + public function setPhpCodeExtension($extension) + { + $this->_phpCodeExtension = $extension; + return $this; + } + + /** + * Get the file extension for intermediate PHP files. + */ + public function getPhpCodeExtension() + { + return $this->_phpCodeExtension; + } + + /** + * Flags whether to ignore intermediate php files and to + * reparse templates every time (if set to true). + * Don't use in production - this makes PHPTAL significantly slower. + * @param bool bool Forced reparse state. + */ + public function setForceReparse($bool) + { + $this->_forceReparse = (bool) $bool; + return $this; + } + + /** + * Get the value of the force reparse state. + */ + public function getForceReparse() + { + return $this->_forceReparse !== NULL ? $this->_forceReparse : (defined('PHPTAL_FORCE_REPARSE') && PHPTAL_FORCE_REPARSE); + } + + /** + * Set I18N translator. + * This sets encoding used by the translator, so be sure to use encoding-dependent features of the translator (e.g. addDomain) _after_ calling setTranslator. + */ + public function setTranslator(PHPTAL_TranslationService $t) + { + $this->_translator = $t; + $this->_translator->setEncoding($this->getEncoding()); + return $this; + } + + /** + * Set template pre filter. It will be called once before template is compiled. + */ + public function setPreFilter(PHPTAL_Filter $filter) + { + $this->_prefilter = $filter; + return $this; + } + + /** + * Set template post filter. It will be called every time after template generates output. + */ + public function setPostFilter(PHPTAL_Filter $filter) + { + $this->_postfilter = $filter; + return $this; + } + + /** + * Register a trigger for specified phptal:id. + * @param $id string phptal:id to look for + */ + public function addTrigger($id, PHPTAL_Trigger $trigger) + { + $this->_triggers[$id] = $trigger; + return $this; + } + + /** + * Returns trigger for specified phptal:id. + * @param $id string phptal:id + */ + public function getTrigger($id) + { + if (array_key_exists($id, $this->_triggers)){ + return $this->_triggers[$id]; + } + return null; + } + + /** + * Set a context variable. + * @param $varname string + * @param $value mixed + */ + public function __set($varname, $value) + { + $this->_context->__set($varname, $value); + } + + /** + * Set a context variable. + * @param $varname string + * @param $value mixed + */ + public function set($varname, $value) + { + $this->_context->__set($varname, $value); + return $this; + } + + /** + * Execute the template code. + * + * @return string + */ + public function execute() + { + if (!$this->_prepared) { + $this->prepare(); + } + + // includes generated template PHP code + $this->_context->__file = $this->__file; + require_once $this->getCodePath(); + $templateFunction = $this->getFunctionName(); + try { + ob_start(); + $templateFunction($this, $this->_context); + $res = ob_get_clean(); + } + catch (Exception $e){ + ob_end_clean(); + throw $e; + } + + // unshift doctype + $docType = $this->_context->__docType; + if ($docType){ + $res = $docType . "\n" . $res; + } + // unshift xml declaration + $xmlDec = $this->_context->__xmlDeclaration; + if ($xmlDec){ + $res = $xmlDec . "\n" . $res; + } + + if ($this->_postfilter != null){ + return $this->_postfilter->filter($res); + } + return $res; + } + + protected function setConfigurationFrom(PHPTAL $from) + { + // use references - this way config of both objects will be more-or-less in sync + $this->_encoding = &$from->_encoding; + $this->_outputMode = &$from->_outputMode; + $this->_stripComments = &$from->_stripComments; + $this->_forceReparse = &$from->_forceReparse; + $this->_phpCodeDestination = &$from->_phpCodeDestination; + $this->_phpCodeExtension = &$from->_phpCodeExtension; + $this->_cacheLifetime = &$from->_cacheLifetime; + $this->_cachePurgeFrequency = &$from->_cachePurgeFrequency; + $this->setTemplateRepository($from->_repositories); + array_unshift($this->_repositories, dirname($from->_source->getRealPath())); + $this->_resolvers = &$from->_resolvers; + $this->_prefilter = &$from->_prefilter; + $this->_postfilter = &$from->_postfilter; + } + + private $externalMacroTempaltesCache = array(); + + /** + * Execute a template macro. + * Should be used only from within generated template code! + * + * @param $path string Template macro path + */ + public function executeMacro($path) + { + // extract macro source file from macro name, if not source file + // found in $path, then the macro is assumed to be local + if (preg_match('/^(.*?)\/([a-z0-9_]*)$/i', $path, $m)){ + list(,$file,$macroName) = $m; + + if (isset($this->externalMacroTempaltesCache[$file])) + { + $tpl = $this->externalMacroTempaltesCache[$file]; + } + else + { + $tpl = new PHPTAL($file); + $tpl->setConfigurationFrom($this); + $tpl->prepare(); + // require PHP generated code + require_once $tpl->getCodePath(); + + $this->externalMacroTempaltesCache[$file] = $tpl; + if (count($this->externalMacroTempaltesCache) > 10) $this->externalMacroTempaltesCache = array(); // keep it small (typically only 1 or 2 external files are used) + } + + // save current file + $currentFile = $this->_context->__file; + $this->_context->__file = $tpl->__file; + + $fun = $tpl->getFunctionName() . '_' . $macroName; + if (!function_exists($fun)) throw new PHPTAL_Exception("Macro '$macroName' is not defined in $file",$this->_source->getRealPath()); + $fun($this, $this->_context); + + // restore current file + $this->_context->__file = $currentFile; + } + else + { + // call local macro + $fun = $this->getFunctionName() . '_' . trim($path); + if (!function_exists($fun)) throw new PHPTAL_Exception("Macro '$path' is not defined",$this->_source->getRealPath()); + $fun( $this, $this->_context ); + } + } + + private function setCodeFile() + { + $this->_codeFile = $this->getPhpCodeDestination() . $this->getFunctionName() . '.' . $this->getPhpCodeExtension(); + } + + /** + * Prepare template without executing it. + */ + public function prepare() + { + // clear just in case settings changed and cache is out of date + $this->externalMacroTempaltesCache = array(); + + // find the template source file + $this->findTemplate(); + $this->__file = $this->_source->getRealPath(); + $this->setCodeFile(); + + // parse template if php generated code does not exists or template + // source file modified since last generation of PHPTAL_FORCE_REPARSE + // is defined. + if ($this->getForceReparse() || !file_exists($this->_codeFile)) + { + if ($this->getCachePurgeFrequency() && mt_rand()%$this->getCachePurgeFrequency() == 0) + { + $this->cleanUpGarbage(); + } + $this->parse(); + } + $this->_prepared = true; + return $this; + } + + public function getCacheLifetime() + { + return $this->_cacheLifetime; + } + + /** + * how long compiled templates and phptal:cache files are kept, in days + */ + public function setCacheLifetime($days) + { + $this->_cacheLifetime = max(0.5,$days); + return $this; + } + + /** + * PHPTAL will scan cache and remove old files on every nth compile + * Set to 0 to disable cleanups + */ + public function setCachePurgeFrequency($n) + { + $this->_cachePurgeFrequency = (int)$n; + return $this; + } + + public function getCachePurgeFrequency() + { + return $this->_cachePurgeFrequency; + } + + + /** + * Removes all compiled templates from cache after PHPTAL_CACHE_LIFETIME days + */ + public function cleanUpGarbage() + { + $phptalCacheFilesExpire = time() - $this->getCacheLifetime() * 3600 * 24; + $upperLimit = $this->getPhpCodeDestination() . 'tpl_' . $phptalCacheFilesExpire . '_'; + $lowerLimit = $this->getPhpCodeDestination() . 'tpl_0_'; + $phptalCacheFiles = glob($this->getPhpCodeDestination() . 'tpl_*.' . $this->getPhpCodeExtension() . '*'); + + if ($phptalCacheFiles) + { + foreach($phptalCacheFiles as $index => $file) + { + if ($file > $upperLimit && substr($file,0,strlen($lowerLimit)) !== $lowerLimit) + { + unset($phptalCacheFiles[$index]); + } + } + foreach($phptalCacheFiles as $file) + { + $time = filemtime($file); + if ($time && $time < $phptalCacheFilesExpire) @unlink($file); + } + } + } + + /** + * Removes single compiled template from cache and all its fragments cached by phptal:cache. + * Must be called after setSource/setTemplate. + */ + public function cleanUpCache() + { + if (!$this->getCodePath()) + { + $this->findTemplate(); $this->setCodeFile(); + if (!$this->getCodePath()) throw new PHPTAL_Exception("No codefile"); + } + + $filename = $this->getCodePath(); + $phptalCacheFiles = glob($filename . '*'); + if ($phptalCacheFiles) foreach($phptalCacheFiles as $file) + { + if (substr($file, 0, strlen($filename)) !== $filename) continue; // safety net + @unlink($file); + } + $this->_prepared = false; + } + + /** + * Returns the path of the intermediate PHP code file. + * + * The returned file may be used to cleanup (unlink) temporary files + * generated by temporary templates or more simply for debug. + * + * @return string + */ + public function getCodePath() + { + return $this->_codeFile; + } + + /** + * Returns the generated template function name. + * @return string + */ + public function getFunctionName() + { + if (!$this->_functionName) { + $this->_functionName = 'tpl_' . $this->_source->getLastModifiedTime() . '_' . PHPTAL_VERSION . + substr(preg_replace('/[^a-zA-Z]/','',basename($this->_source->getRealPath())),0,10) . md5($this->_source->getRealPath()); + } + return $this->_functionName; + } + + /** + * Returns template translator. + * @return PHPTAL_TranslationService + */ + public function getTranslator() + { + return $this->_translator; + } + + /** + * Returns array of exceptions caught by tal:on-error attribute. + * @return array + */ + public function getErrors() + { + return $this->_errors; + } + + /** + * Public for phptal templates, private for user. + * @access private + */ + public function addError(Exception $error) + { + array_push($this->_errors, $error); + return $this; + } + + /** + * Returns current context object. + * Use only in Triggers. + * + * @return PHPTAL_Context + */ + public function getContext() + { + return $this->_context; + } + + /** + * only for use in generated template code + * @access private + */ + public function getGlobalContext() + { + return $this->_globalContext; + } + + /** + * only for use in generated template code + * @access private + */ + public function pushContext() + { + $this->_context = $this->_context->pushContext(); + return $this->_context; + } + + /** + * only for use in generated template code + * @access private + */ + public function popContext() + { + $this->_context = $this->_context->popContext(); + return $this->_context; + } + + protected function parse() + { + require_once PHPTAL_DIR.'PHPTAL/Dom/Parser.php'; + + // instantiate the PHPTAL source parser + $parser = new PHPTAL_Dom_Parser($this->_encoding); + $parser->stripComments($this->_stripComments); + + $data = $this->_source->getData(); + $realpath = $this->_source->getRealPath(); + + if ($this->_prefilter) + $data = $this->_prefilter->filter($data); + $tree = $parser->parseString($data, $realpath); + + require_once PHPTAL_DIR.'PHPTAL/Php/CodeGenerator.php'; + $generator = new PHPTAL_Php_CodeGenerator($this->getFunctionName(), $this->_source->getRealPath()); + $generator->setEncoding($this->_encoding); + $generator->setOutputMode($this->_outputMode); + $generator->generate($tree); + + if (!@file_put_contents($this->_codeFile, $generator->getResult())) { + throw new PHPTAL_Exception('Unable to open '.$this->_codeFile.' for writing'); + } + + return $this; + } + + /** + * Search template source location. + */ + protected function findTemplate() + { + if ($this->_path == false){ + throw new PHPTAL_Exception('No template file specified'); + } + + // template source already defined + if ($this->_source != null){ + return; + } + + array_push($this->_resolvers, new PHPTAL_FileSourceResolver($this->_repositories)); + foreach ($this->_resolvers as $resolver){ + $source = $resolver->resolve($this->_path); + if ($source != null){ + $this->_source = $source; + break; + } + } + array_pop($this->_resolvers); + + if ($this->_source == null){ + throw new PHPTAL_Exception('Unable to locate template file '.$this->_path); + } + } + + protected $_prefilter = null; + protected $_postfilter = null; + + // list of template source repositories + protected $_repositories = array(); + // template path + protected $_path = null; + // template source resolvers + protected $_resolvers = array(); + // template source (only set when not working with file) + protected $_source = null; + // destination of PHP intermediate file + protected $_codeFile = null; + // php function generated for the template + protected $_functionName = null; + // set to true when template is ready for execution + protected $_prepared = false; + + // associative array of phptal:id => PHPTAL_Trigger + protected $_triggers = array(); + // i18n translator + protected $_translator = null; + + // global execution context + protected $_globalContext = null; + // current execution context + protected $_context = null; + // current template file (changes within macros) + public $__file = false; + // list of on-error caught exceptions + protected $_errors = array(); + + protected $_encoding = PHPTAL_DEFAULT_ENCODING; + protected $_outputMode = PHPTAL::XHTML; + protected $_stripComments = false; + + // configuration properties + protected $_forceReparse = NULL; + protected $_phpCodeDestination = PHPTAL_PHP_CODE_DESTINATION; + protected $_phpCodeExtension = PHPTAL_PHP_CODE_EXTENSION; + + protected $_cacheLifetime = 30; + protected $_cachePurgeFrequency = 50; +} + +?> diff --git a/PHPTAL/CommentFilter.php b/PHPTAL/CommentFilter.php new file mode 100644 index 0000000..822b62f --- /dev/null +++ b/PHPTAL/CommentFilter.php @@ -0,0 +1,12 @@ +)/s', '', $src); + } +} + +?> diff --git a/PHPTAL/Context.php b/PHPTAL/Context.php new file mode 100644 index 0000000..729fc2e --- /dev/null +++ b/PHPTAL/Context.php @@ -0,0 +1,405 @@ + +// + +/** + * This class handles template execution context. + * @package phptal + */ +class PHPTAL_Context +{ + public static $USE_GLOBAL = false; + + public $__line = false; + public $__file = false; + public $__repeat; + public $__xmlDeclaration; + public $__docType; + public $__nothrow; + public $__translator; + + public function __construct() + { + $this->__repeat = new StdClass(); + } + + public function __clone() + { + $this->__repeat = clone($this->__repeat); + } + + public function setParent(PHPTAL_Context $parent) + { + $this->_parentContext = $parent; + } + + public function setGlobal(StdClass $globalContext) + { + $this->_globalContext = $globalContext; + } + + public function pushContext() + { + if (self::$USE_GLOBAL) return $this; + $res = clone $this; + $res->setParent($this); + return $res; + } + + public function popContext() + { + if (self::$USE_GLOBAL) return $this; + return $this->_parentContext; + } + + /** + * Set output document type if not already set. + * + * This method ensure PHPTAL uses the first DOCTYPE encountered (main + * template or any macro template source containing a DOCTYPE. + */ + public function setDocType($doctype) + { + if ($this->_parentContext != null){ + return $this->_parentContext->setDocType($doctype); + } + if ($this->_parentContext != null){ + return $this->_parentContext->setDocType($doctype); + } + if (!$this->__docType){ + $this->__docType = $doctype; + } + } + + /** + * Set output document xml declaration. + * + * This method ensure PHPTAL uses the first xml declaration encountered + * (main template or any macro template source containing an xml + * declaration). + */ + public function setXmlDeclaration($xmldec) + { + if ($this->_parentContext != null){ + return $this->_parentContext->setXmlDeclaration($xmldec); + } + if ($this->_parentContext != null){ + return $this->_parentContext->setXmlDeclaration($xmldec); + } + if (!$this->__xmlDeclaration){ + $this->__xmlDeclaration = $xmldec; + } + } + + /** + * Activate or deactivate exception throwing during unknown path + * resolution. + */ + public function noThrow($bool) + { + $this->__nothrow = $bool; + } + + /** + * Returns true if specified slot is filled. + */ + public function hasSlot($key) + { + if ($this->_parentContext) return $this->_parentContext->hasSlot($key); // setting slots in any context + return array_key_exists($key, $this->_slots); + } + + /** + * Returns the content of specified filled slot. + */ + public function getSlot($key) + { + if ($this->_parentContext) return $this->_parentContext->getSlot($key); // setting slots in any context + return $this->_slots[$key]; + } + + /** + * Fill a macro slot. + */ + public function fillSlot($key, $content) + { + if ($this->_parentContext) $this->_parentContext->fillSlot($key,$content); // setting slots in any context + else $this->_slots[$key] = $content; + } + + /** + * Push current filled slots on stack. + */ + public function pushSlots() + { + array_push($this->_slotsStack, $this->_slots); + $this->_slots = array(); + } + + /** + * Restore filled slots stack. + */ + public function popSlots() + { + $this->_slots = array_pop($this->_slotsStack); + } + + /** + * Context setter. + */ + public function __set($varname, $value) + { + if ($varname[0] == '_') + { + throw new PHPTAL_Exception('Template variable error \''.$varname.'\' must not begin with underscore'); + } + $this->$varname = $value; + } + + + + /** + * Context getter. + */ + public function __get($varname) + { + if ($varname == 'repeat') + return $this->__repeat; + + if (isset($this->$varname)){ + return $this->$varname; + } + + if (isset($this->_globalContext->$varname)){ + return $this->_globalContext->$varname; + } + + if (defined($varname)) + { + return constant($varname); + } + + if ($this->__nothrow) + return null; + + $e = sprintf('Unable to find path %s in current scope', $varname); + throw new PHPTAL_Exception($e, $this->__file, $this->__line); + } + + private $_slots = array(); + private $_slotsStack = array(); + private $_parentContext = null; + private $_globalContext = null; +} + +// emulate property_exists() function, this is slow but much better than +// isset(), use next release of PHP5 as soon as available ! +if (!function_exists('property_exists')){ + function property_exists($o, $property) + { + return array_key_exists($property, get_object_vars($o)); + } +} + + +/** + * Resolve TALES path starting from the first path element. + * + * The TALES path : object/method1/10/method2 + * will call : phptal_path($ctx->object, 'method1/10/method2') + * + * The nothrow param is used by phptal_exists() and prevent this function to + * throw an exception when a part of the path cannot be resolved, null is + * returned instead. + */ +function phptal_path($base, $path, $nothrow=false) +{//{{{ + $parts = explode('/', $path); + $current = true; + + if ($base === null) + { + if ($nothrow) return null; + throw new PHPTAL_Exception("Trying to read property '$path' from NULL"); + } + + while (($current = array_shift($parts)) !== null){ + // object handling + if (is_object($base)){ + // look for method + if (method_exists($base, $current)){ + $base = $base->$current(); + continue; + } + + // look for variable + if (property_exists($base, $current)){ + $base = $base->$current; + continue; + } + + if ($base instanceof ArrayAccess && $base->offsetExists($current)) + { + $base = $base->offsetGet($current); + continue; + } + + if ($base instanceof Countable && ($current === 'length' || $current === 'size')) + { + $base = count($base); + continue; + } + + // look for isset (priority over __get) + if (method_exists($base,'__isset') && is_callable(array($base, '__isset'))) + { + if ($base->__isset($current)){ + $base = $base->$current; + continue; + } + } + // ask __get and discard if it returns null + else if (method_exists($base,'__get') && is_callable(array($base, '__get'))) + { + $tmp = $base->$current; + if (NULL !== $tmp){ + $base = $tmp; + continue; + } + } + + // magic method call + if (method_exists($base, '__call')){ + try + { + $base = $base->$current(); + continue; + } + catch(BadMethodCallException $e){} + } + + // emulate array behaviour + if (is_numeric($current) && method_exists($base, '__getAt')){ + $base = $base->__getAt($current); + continue; + } + + if ($nothrow) + return null; + + $err = 'Unable to find part "%s" in path "%s" inside '.(is_object($base)?get_class($base):gettype($base)); + $err = sprintf($err, $current, $path); + throw new PHPTAL_Exception($err); + } + + // array handling + if (is_array($base)) { + // key or index + if (array_key_exists((string)$current, $base)){ + $base = $base[$current]; + continue; + } + + // virtual methods provided by phptal + if ($current == 'length' || $current == 'size'){ + $base = count($base); + continue; + } + + if ($nothrow) + return null; + + $err = 'Unable to find array key "%s" in path "%s"'; + $err = sprintf($err, $current, $path); + throw new PHPTAL_Exception($err); + } + + // string handling + if (is_string($base)) { + // virtual methods provided by phptal + if ($current == 'length' || $current == 'size'){ + $base = strlen($base); + continue; + } + + // access char at index + if (is_numeric($current)){ + $base = $base[$current]; + continue; + } + } + + // if this point is reached, then the part cannot be resolved + + if ($nothrow) + return null; + + $err = 'Unable to find part "%s" in path "%s" with base "%s"'; + $err = sprintf($err, $current, $path, is_scalar($base)?"$base":(is_object($base)?get_class($base):gettype($base))); + throw new PHPTAL_Exception($err); + } + + return $base; +} + +function phptal_true($ctx, $path) +{ + $ctx->noThrow(true); + $res = phptal_path($ctx, $path, true); + $ctx->noThrow(false); + return !!$res; +} + +/** + * Returns true if $path can be fully resolved in $ctx context. + */ +function phptal_exists($ctx, $path) +{ + // special note: this method may requires to be extended to a full + // phptal_path() sibling to avoid calling latest path part if it is a + // method or a function... + $ctx->noThrow(true); + $res = phptal_path($ctx, $path, true); + $ctx->noThrow(false); + return $res !== NULL; +} + +function phptal_isempty($var) +{ + return $var === null || $var === false || $var === '' + || ((is_array($var) || $var instanceof Countable) && count($var)===0); +} + +function phptal_escape($var, $ent, $encoding) +{ + if (is_string($var)) { + return htmlspecialchars($var, $ent, $encoding); + } + elseif (is_object($var)) { + if ($var instanceof SimpleXMLElement) return $var->asXML(); + return htmlspecialchars($var->__toString(), $ent, $encoding); + } + elseif (is_bool($var)){ + return (int)$var; + } + return $var; +} +?> diff --git a/PHPTAL/Dom/Defs.php b/PHPTAL/Dom/Defs.php new file mode 100644 index 0000000..1a529c9 --- /dev/null +++ b/PHPTAL/Dom/Defs.php @@ -0,0 +1,237 @@ + +// + +// From http://dev.zope.org/Wikis/DevSite/Projects/ZPT/TAL%20Specification%201.4 +// +// Order of Operations +// +// When there is only one TAL statement per element, the order in which +// they are executed is simple. Starting with the root element, each +// element's statements are executed, then each of its child elements is +// visited, in order, to do the same. +// +// Any combination of statements may appear on the same elements, except +// that the content and replace statements may not appear together. +// +// When an element has multiple statements, they are executed in this +// order: +// +// * define +// * condition +// * repeat +// * content or replace +// * attributes +// * omit-tag +// +// Since the on-error statement is only invoked when an error occurs, it +// does not appear in the list. +// +// The reasoning behind this ordering goes like this: You often want to set +// up variables for use in other statements, so define comes first. The +// very next thing to do is decide whether this element will be included at +// all, so condition is next; since the condition may depend on variables +// you just set, it comes after define. It is valuable be able to replace +// various parts of an element with different values on each iteration of a +// repeat, so repeat is next. It makes no sense to replace attributes and +// then throw them away, so attributes is last. The remaining statements +// clash, because they each replace or edit the statement element. +// +// If you want to override this ordering, you must do so by enclosing the +// element in another element, possibly div or span, and placing some of +// the statements on this new element. +// + +require_once PHPTAL_DIR.'PHPTAL/Namespace.php'; +require_once PHPTAL_DIR.'PHPTAL/Namespace/TAL.php'; +require_once PHPTAL_DIR.'PHPTAL/Namespace/METAL.php'; +require_once PHPTAL_DIR.'PHPTAL/Namespace/I18N.php'; +require_once PHPTAL_DIR.'PHPTAL/Namespace/PHPTAL.php'; + +/** + * PHPTAL constants. + * + * This is a pseudo singleton class, a user may decide to provide + * his own singleton instance which will then be used by PHPTAL. + * + * This behaviour is mainly usefull to remove builtin namespaces + * and provide custom ones. + * + * @package phptal.dom + * @author Laurent Bedubourg + */ +class PHPTAL_Dom_Defs +{ + public static function getInstance() + { + if (self::$_instance == null){ + self::$_instance = new PHPTAL_Dom_Defs(); + } + return self::$_instance; + } + + public static function setInstance(PHPTAL_Dom_Defs $instance) + { + self::$_instance = $instance; + } + + + public function __construct() + { + $this->_dictionary = array(); + $this->_namespaces = array(); + $this->_xmlns = array(); + } + + public function isEmptyTag($tagName) + { + return in_array(strtolower($tagName), self::$XHTML_EMPTY_TAGS); + } + + public function xmlnsToLocalName($xmlns) + { + return $this->_xmlns[$xmlns]; + } + + /** + * Returns true if the attribute is an xhtml boolean attribute. + * + * @return bool + */ + public function isBooleanAttribute($att) + { + return in_array($att, self::$XHTML_BOOLEAN_ATTRIBUTES); + } + + /** + * Returns true if the attribute is in the phptal dictionnary. + * + * @return bool + */ + public function isPhpTalAttribute($att) + { + return array_key_exists(strtolower($att), $this->_dictionary); + } + + /** + * Returns true if the attribute is a valid phptal attribute or an unknown + * attribute. + * + * Examples of valid attributes: tal:content, metal:use-slot + * Examples of invalid attributes: tal:unknown, metal:content + * + * @return bool + */ + public function isValidAttribute($att) + { + if (preg_match('/^(.*):(.*)$/', $att, $m)) { + list (,$ns,$sub) = $m; + if (array_key_exists(strtolower($ns), $this->_namespaces) + && !$this->isPhpTalAttribute($att)) { + return false; + } + } + return true; + } + + /** + * Returns true if the attribute is a phptal handled xml namespace + * declaration. + * + * Examples of handled xmlns: xmlns:tal, xmlns:metal + * + * @return bool + */ + public function isHandledXmlNs($att, $value) + { + $att = strtolower($att); + return substr($att, 0, 6) == 'xmlns:' + && array_key_exists($value, $this->_xmlns); + } + + public function getNamespaceAttribute($attName) + { + return $this->_dictionary[strtolower($attName)]; + } + + /** + * Register a PHPTAL_Namespace and its attribute into PHPTAL. + */ + public function registerNamespace(PHPTAL_Namespace $ns) + { + $nsname = strtolower($ns->name); + $this->_namespaces[$nsname] = $ns; + $this->_xmlns[$ns->xmlns] = $nsname; + foreach ($ns->getAttributes() as $name => $attribute){ + $key = $nsname.':'.strtolower($name); + $this->_dictionary[$key] = $attribute; + } + } + + private static $_instance = null; + private $_dictionary; + private $_namespaces; + private $_xmlns; + + /** + * This array contains XHTML tags that must be echoed in a <tag/> form + * instead of the <tag></tag> form. + * + * In fact, some browsers does not support the later form so PHPTAL + * ensure these tags are correctly echoed. + */ + private static $XHTML_EMPTY_TAGS = array( + 'area', + 'base', + 'basefont', + 'br', + 'col', + 'frame', + 'hr', + 'img', + 'input', + 'isindex', + 'link', + 'meta', + 'param', + ); + + /** + * This array contains XHTML boolean attributes, their value is self + * contained (ie: they are present or not). + */ + private static $XHTML_BOOLEAN_ATTRIBUTES = array( + 'checked', + 'compact', + 'declare', + 'defer', + 'disabled', + 'ismap', + 'multiple', + 'noresize', + 'noshade', + 'nowrap', + 'readonly', + 'selected', + ); +} + +?> diff --git a/PHPTAL/Dom/Node.php b/PHPTAL/Dom/Node.php new file mode 100644 index 0000000..7e3b33b --- /dev/null +++ b/PHPTAL/Dom/Node.php @@ -0,0 +1,254 @@ + +// + +require_once PHPTAL_DIR.'PHPTAL/Dom/Defs.php'; +require_once PHPTAL_DIR.'PHPTAL/Php/CodeWriter.php'; +require_once PHPTAL_DIR.'PHPTAL/Php/Attribute.php'; + +/** + * Document node abstract class. + * + * @package phptal.dom + * @author Laurent Bedubourg + */ +abstract class PHPTAL_Dom_Node +{ + public function __construct() + { + } + + public function setSource($file, $line) + { + $this->_file = $file; + $this->_line = $line; + } + + public function getSourceLine() + { + return $this->_line; + } + + public function getSourceFile() + { + return $this->_file; + } + + private $_file; + private $_line; +} + +/** + * Node container. + * + * @package phptal.dom + * @author Laurent Bedubourg + */ +class PHPTAL_Dom_Tree extends PHPTAL_Dom_Node +{ + public function __construct() + { + parent::__construct(); + $this->_children = array(); + } + + public function addChild(PHPTAL_Dom_Node $node) + { + array_push($this->_children, $node); + } + + public function &getChildren() + { + return $this->_children; + } + + public function clearChildren() + { + $this->_children = array(); + } + + protected $_children; +} + +/** + * Document Tag representation. + * + * This is the main class used by PHPTAL because TAL is a Template Attribute + * Language, other Node kinds are (usefull) toys. + * + * @package phptal.dom + * @author Laurent Bedubourg + */ +class PHPTAL_Dom_Element extends PHPTAL_Dom_Tree +{ + private $name; + public $attributes = array(); + + public function __construct($name, $attributes) + { + if (!preg_match('/^[a-z_:][a-z0-9._:\x80-\xff-]*$/i',$name)) throw new PHPTAL_Exception("Invalid element name '$name'"); + parent::__construct(); + $this->name = $name; + $this->attributes = $attributes; + } + + public function setXmlnsState(PHPTAL_Dom_XmlnsState $state) + { + $this->_xmlns = $state; + $this->xmlns = $state; + } + + public function getName() + { + return $this->name; + } + + public function getXmlnsState() + { + return $this->_xmlns; + } + + /** Returns true if the element contains specified PHPTAL attribute. */ + public function hasAttribute($name) + { + $ns = $this->getNodePrefix(); + foreach ($this->attributes as $key=>$value){ + if ($this->_xmlns->unAliasAttribute($key) == $name){ + return true; + } + if ($ns && $this->_xmlns->unAliasAttribute("$ns:$key") == $name){ + return true; + } + } + return false; + } + + /** Returns the value of specified PHPTAL attribute. */ + public function getAttribute($name) + { + $ns = $this->getNodePrefix(); + + foreach ($this->attributes as $key=>$value){ + if ($this->_xmlns->unAliasAttribute($key) == $name){ + return $value; + } + if ($ns && $this->_xmlns->unAliasAttribute("$ns:$key") == $name){ + return $value; + } + } + return false; + } + + /** + * Returns true if this element or one of its PHPTAL attributes has some + * content to print (an empty text node child does not count). + */ + public function hasRealContent() + { + if (count($this->_children) == 0) + return false; + + if (count($this->_children) == 1){ + $child = $this->_children[0]; + if ($child instanceOf PHPTAL_Dom_Text && $child->getValue() == ''){ + return false; + } + } + + return true; + } + + private function getNodePrefix() + { + $result = false; + if (preg_match('/^(.*?):block$/', $this->name, $m)){ + list(,$result) = $m; + } + return $result; + } + + private function hasContent() + { + return count($this->_children) > 0; + } + + /** + * XMLNS aliases propagated from parent nodes and defined by this node + * attributes. + */ + protected $_xmlns; +} + +/** + * @package phptal.dom + */ +class PHPTAL_Dom_ValueNode extends PHPTAL_Dom_Node +{ + public function __construct($data) + { + $this->_value = $data; + } + + public function getValue() + { + return $this->_value; + } + + private $_value; +} + +/** + * Document text data representation. + * @package phptal.dom + */ +class PHPTAL_Dom_Text extends PHPTAL_Dom_ValueNode{} + +/** + * Preprocessor, etc... representation. + * + * @package phptal.dom + * @author Laurent Bedubourg + */ +class PHPTAL_Dom_Specific extends PHPTAL_Dom_ValueNode {} + +/** + * Comment nodes. + * @package phptal.dom + */ +class PHPTAL_Dom_Comment extends PHPTAL_Dom_ValueNode {} + +/** + * Document doctype representation. + * + * @package phptal.dom + * @author Laurent Bedubourg + */ +class PHPTAL_Dom_Doctype extends PHPTAL_Dom_ValueNode {} + +/** + * XML declaration node. + * + * @package phptal.dom + * @author Laurent Bedubourg + */ +class PHPTAL_Dom_XmlDeclaration extends PHPTAL_Dom_ValueNode {} + +?> diff --git a/PHPTAL/Dom/Parser.php b/PHPTAL/Dom/Parser.php new file mode 100644 index 0000000..76d8824 --- /dev/null +++ b/PHPTAL/Dom/Parser.php @@ -0,0 +1,154 @@ + +// + +require_once PHPTAL_DIR.'PHPTAL/Dom/Defs.php'; +require_once PHPTAL_DIR.'PHPTAL/Dom/Node.php'; +require_once PHPTAL_DIR.'PHPTAL/Dom/XmlParser.php'; +require_once PHPTAL_DIR.'PHPTAL/Dom/XmlnsState.php'; +require_once PHPTAL_DIR.'PHPTAL/Php/Tales.php'; + +/** + * Template parser. + * + * @package phptal.dom + * @author Laurent Bedubourg + */ +class PHPTAL_Dom_Parser extends PHPTAL_XmlParser +{ + const ERR_DOCUMENT_END_STACK_NOT_EMPTY = "Not all elements were closed before end of the document (element stack not empty)"; + const ERR_UNSUPPORTED_ATTRIBUTE = "Unsupported attribute '%s'"; + const ERR_ELEMENT_CLOSE_MISMATCH = "Tag closure mismatch, expected '%s' but was '%s'"; + + public function __construct($input_encoding = 'UTF-8') + { + parent::__construct($input_encoding); + $this->_xmlns = new PHPTAL_Dom_XmlnsState(); + } + + public function getXmlnsState() + { + return $this->_xmlns; + } + + public function stripComments($b) + { + $this->_stripComments = $b; + } + + public function parseString($src, $filename = '') + { + parent::parseString($src, $filename); + return $this->_tree; + } + + public function parseFile($path) + { + parent::parseFile($path); + return $this->_tree; + } + + // ~~~~~ XmlParser implementation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + public function onDocumentStart() + { + $this->_tree = new PHPTAL_Dom_Tree(); + $this->_tree->setSource($this->getSourceFile(), $this->getLineNumber()); + $this->_stack = array(); + $this->_current = $this->_tree; + } + + public function onDocumentEnd() + { + if (count($this->_stack) > 0) { + $this->raiseError(self::ERR_DOCUMENT_END_STACK_NOT_EMPTY); + } + } + + public function onDocType($doctype) + { + $this->pushNode(new PHPTAL_Dom_DocType($doctype)); + } + + public function onXmlDecl($decl) + { + $this->pushNode(new PHPTAL_Dom_XmlDeclaration($decl)); + } + + public function onComment($data) + { + if ($this->_stripComments) + return; + $this->pushNode(new PHPTAL_Dom_Comment($data)); + } + + public function onSpecific($data) + { + $this->pushNode(new PHPTAL_Dom_Specific($data)); + } + + public function onElementStart($name, $attributes) + { + $this->_xmlns = PHPTAL_Dom_XmlnsState::newElement($this->_xmlns, $attributes); + + foreach ($attributes as $key=>$value) { + if (!$this->_xmlns->isValidAttribute($key)) { + $this->raiseError(self::ERR_UNSUPPORTED_ATTRIBUTE, $key); + } + } + + $node = new PHPTAL_Dom_Element($name, $attributes); + $node->setXmlnsState($this->getXmlnsState()); + $this->pushNode($node); + array_push($this->_stack, $this->_current); + $this->_current = $node; + } + + public function onElementData($data) + { + $this->pushNode(new PHPTAL_Dom_Text($data)); + } + + public function onElementClose($name) + { + if (!$this->_current instanceof PHPTAL_Dom_Element) $this->raiseError("Found closing tag for '$name' where there are no open tags"); + if ($this->_current->getName() != $name) { + $this->raiseError(self::ERR_ELEMENT_CLOSE_MISMATCH, $this->_current->getName(), $name); + } + $this->_current = array_pop($this->_stack); + if ($this->_current instanceOf PHPTAL_Dom_Element) + $this->_xmlns = $this->_current->getXmlnsState(); + } + + private function pushNode(PHPTAL_Dom_Node $node) + { + $node->setSource($this->getSourceFile(), $this->getLineNumber()); + $this->_current->addChild($node); + } + + private $_tree; /* PHPTAL_Dom_Parser_NodeTree */ + private $_stack; /* array */ + private $_current; /* PHPTAL_Dom_Parser_Node */ + private $_xmlns; /* PHPTAL_Dom_Parser_XmlnsState */ + private $_stripComments = false; +} + +?> diff --git a/PHPTAL/Dom/XmlParser.php b/PHPTAL/Dom/XmlParser.php new file mode 100644 index 0000000..8f6c30c --- /dev/null +++ b/PHPTAL/Dom/XmlParser.php @@ -0,0 +1,386 @@ + +// + +/** + * Simple sax like xml parser for PHPTAL. + * + * Because PHP Xml parser libraries tends to fail giving a real xml document + * representation (at the time this file was created, it was impossible to + * retrieve doctypes, xml declaration, problem with comments and CDATA) this + * parser was created and can be manipulated to accept some user errors + * like < and < in attribute values or inside text nodes. + * + * @package phptal + * @author Laurent Bedubourg + * @see PHPTAL_Dom_Parser + */ +abstract class PHPTAL_XmlParser +{ + // available parser states + const ST_ROOT = 0; + const ST_TEXT = 1; + const ST_LT = 2; + const ST_TAG_NAME = 3; + const ST_TAG_CLOSE = 4; + const ST_TAG_SINGLE = 5; + const ST_TAG_ATTRIBUTES = 6; + const ST_CDATA = 7; + const ST_COMMENT = 8; + const ST_DOCTYPE = 9; + const ST_XMLDEC = 15; + const ST_PREPROC = 10; + const ST_ATTR_KEY = 11; + const ST_ATTR_EQ = 12; + const ST_ATTR_QUOTE = 13; + const ST_ATTR_VALUE = 14; + + // exceptions error messages + const ERR_CHARS_BEFORE_DOC_START = + "Characters found before the begining of the document!"; + const ERR_EXPECT_VALUE_QUOTE = + "Unexpected '%s' character, expecting attribute single or double quote"; + + const BOM_STR = "\xef\xbb\xbf"; + + + static $state_names = array( + self::ST_ROOT => 'root node', + self::ST_TEXT => 'text', + self::ST_LT => 'start of tag', + self::ST_TAG_NAME => 'tag name', + self::ST_TAG_CLOSE => 'closing tag', + self::ST_TAG_SINGLE => 'self-closing tag', + self::ST_TAG_ATTRIBUTES => 'tag', + self::ST_CDATA => 'CDATA', + self::ST_COMMENT => 'comment', + self::ST_DOCTYPE => 'doctype', + self::ST_XMLDEC => 'XML declaration', + self::ST_PREPROC => 'preprocessor directive', + self::ST_ATTR_KEY => 'attribute name', + self::ST_ATTR_EQ => 'attribute value', + self::ST_ATTR_QUOTE => 'quoted attribute value', + self::ST_ATTR_VALUE => 'unquoted attribute value', + ); + + public function __construct() + { + $this->_file = ""; + } + + public function parseFile($src) + { + if (!file_exists($src)) { + throw new PHPTAL_Exception("file $src not found"); + } + $this->parseString(file_get_contents($src), $src); + } + + public function parseString($src, $filename = '') + { + $this->_file = $filename; + + // remove BOM (utf8 byte order mark)... + if (substr($src,0,3) == self::BOM_STR){ + $src = substr($src, 3); + } + + $this->_line = 1; + $state = self::ST_ROOT; + $mark = 0; + $len = strlen($src); + + $quoteStyle = '"'; + $tagname = ""; + $attribute = ""; + $attributes = array(); + + $customDoctype = false; + + $this->onDocumentStart(); + for ($i=0; $i<$len; $i++) { + $c = $src[$i]; + + if ($c == "\n") $this->_line++; + + switch ($state) { + case self::ST_ROOT: + if ($c == '<') { + $mark = $i; // mark tag start + $state = self::ST_LT; + } + else if (!self::isWhiteChar($c)) { + $this->raiseError(self::ERR_CHARS_BEFORE_DOC_START); + } + break; + + case self::ST_TEXT: + if ($c == '<') { + if ($mark != $i) { + $this->onElementData(substr($src, $mark, $i-$mark)); + } + $mark = $i; + $state = self::ST_LT; + } + break; + + case self::ST_LT: + if ($c == '/') { + $mark = $i+1; + $state = self::ST_TAG_CLOSE; + } + else if ($c == '?' and substr($src, $i, 4) == '?xml') { + $state = self::ST_XMLDEC; + } + else if ($c == '?') { + $state = self::ST_PREPROC; + } + else if ($c == '!' and substr($src, $i, 3) == '!--') { + $state = self::ST_COMMENT; + } + else if ($c == '!' and substr($src, $i, 8) == '![CDATA[') { + $state = self::ST_CDATA; + } + else if ($c == '!' and substr($src, $i, 8) == '!DOCTYPE') { + $state = self::ST_DOCTYPE; + } + else if (!self::isAlpha($c)) { + $state = self::ST_TEXT; + } + else { + $mark = $i; // mark node name start + $attributes = array(); + $attribute = ""; + $state = self::ST_TAG_NAME; + } + break; + + case self::ST_TAG_NAME: + if (self::isWhiteChar($c)) { + $tagname = substr($src, $mark, $i-$mark); + $state = self::ST_TAG_ATTRIBUTES; + } + else if ($c == '/') { + $tagname = substr($src, $mark, $i-$mark); + $state = self::ST_TAG_SINGLE; + } + else if ($c == '>') { + $tagname = substr($src, $mark, $i-$mark); + $mark = $i+1; // mark text start + $state = self::ST_TEXT; + $this->onElementStart($tagname, $attributes); + } + break; + + case self::ST_TAG_CLOSE: + if ($c == '>') { + $tagname = rtrim(substr($src, $mark, $i-$mark)); + $this->onElementClose($tagname); + $mark = $i+1; // mark text start + $state = self::ST_TEXT; + } + break; + + case self::ST_TAG_SINGLE: + if ($c != '>') { + // error + } + $mark = $i+1; // mark text start + $state = self::ST_TEXT; + $this->onElementStart($tagname, $attributes); + $this->onElementClose($tagname); + break; + + case self::ST_TAG_ATTRIBUTES: + if ($c == '>') { + $mark = $i+1; // mark text start + $state = self::ST_TEXT; + $this->onElementStart($tagname, $attributes); + } + else if ($c == '/') { + $state = self::ST_TAG_SINGLE; + } + else if (self::isWhiteChar($c)) { + } + else { + $mark = $i; // mark attribute key start + $state = self::ST_ATTR_KEY; + } + break; + + case self::ST_COMMENT: + if ($c == '>' and substr($src, $i-2, 2) == '--') { + $this->onComment(substr($src, $mark, $i-$mark+1)); + $mark = $i+1; // mark text start + $state = self::ST_TEXT; + } + break; + + case self::ST_CDATA: + if ($c == '>' and substr($src, $i-2, 2) == ']]') { + $this->onSpecific(substr($src, $mark, $i-$mark+1)); + $mark = $i+1; // mark text start + $state = self::ST_TEXT; + } + break; + + case self::ST_XMLDEC: + if ($c == '?' && substr($src, $i, 2) == '?>') { + $this->onXmlDecl(substr($src, $mark, $i-$mark+2)); + $i++; // skip '>' + $mark = $i+1; // mark text start + $state = self::ST_TEXT; + } + break; + + case self::ST_DOCTYPE: + if ($c == '[') { + $customDoctype = true; + } + else if ($customDoctype && $c == '>' && substr($src, $i-1, 2) == ']>'){ + $customDoctype = false; + $this->onDocType(substr($src, $mark, $i-$mark+1)); + $mark = $i+1; // mark text start + $state = self::ST_TEXT; + } + else if (!$customDoctype && $c == '>') { + $customDoctype = false; + $this->onDocType(substr($src, $mark, $i-$mark+1)); + $mark = $i+1; // mark text start + $state = self::ST_TEXT; + } + break; + + case self::ST_PREPROC: + if ($c == '>' and $src[$i-1] == '?') { + $this->onSpecific(substr($src, $mark, $i-$mark+1)); + $mark = $i+1; // mark text start + $state = self::ST_TEXT; + } + break; + + case self::ST_ATTR_KEY: + if (self::isWhiteChar($c)) { + $attribute = substr($src, $mark, $i-$mark); + $state = self::ST_ATTR_EQ; + } + else if ($c == '=') { + $attribute = substr($src, $mark, $i-$mark); + $state = self::ST_ATTR_VALUE; + } + break; + + case self::ST_ATTR_EQ: + if ($c == '=') { + $state = self::ST_ATTR_VALUE; + } + break; + + case self::ST_ATTR_VALUE: + if (self::isWhiteChar($c)){ + } + else if ($c == '"' or $c == '\'') { + $quoteStyle = $c; + $state = self::ST_ATTR_QUOTE; + $mark = $i+1; // mark attribute real value start + } + else { + $err = self::ERR_EXPECT_VALUE_QUOTE; + $err = sprintf($err, $c); + $this->raiseError($err); + } + break; + + case self::ST_ATTR_QUOTE: + if ($c == $quoteStyle) { + if (isset($attributes[$attribute])) $this->raiseError("Attribute '$attribute' on '$tagname' is defined more than once"); + $attributes[$attribute] = substr($src, $mark, $i-$mark); + $state = self::ST_TAG_ATTRIBUTES; + } + break; + } + } + + if ($state == self::ST_TEXT) // allows text past root node, which is in violation of XML spec + { + if ($i > $mark) + { + $text = substr($src, $mark, $i-$mark); + //if (!ctype_space($text)) $this->onElementData($text); + if (!ctype_space($text)) $this->raiseError("Characters found after end of the root element"); + } + } + else + { + throw new PHPTAL_Exception("Finished document in unexpected state: ".self::$state_names[$state]." is not finished"); + } + + $this->onDocumentEnd(); + } + + public function getSourceFile() + { + return $this->_file; + } + + public function getLineNumber() + { + return $this->_line; + } + + public static function isWhiteChar($c) + { + return strpos(" \t\n\r\0", $c) !== false; + } + + public static function isAlpha($c) + { + $char = strtolower($c); + return ($char >= 'a' && $char <= 'z'); + } + + public abstract function onDocType($doctype); + public abstract function onXmlDecl($decl); + public abstract function onSpecific($data); + public abstract function onComment($data); + public abstract function onElementStart($name, $attributes); + public abstract function onElementClose($name); + public abstract function onElementData($data); + public abstract function onDocumentStart(); + public abstract function onDocumentEnd(); + + protected function raiseError($errFmt) + { + $args = func_get_args(); + $errStr = call_user_func_array('sprintf', $args); + + $str = "%s error: %s in %s:%d"; + $str = sprintf($str, get_class($this), $errStr, $this->_file, $this->_line); + throw new PHPTAL_Exception($str); + } + + private $_file; + private $_line; + private $_source; +} + + +?> diff --git a/PHPTAL/Dom/XmlnsState.php b/PHPTAL/Dom/XmlnsState.php new file mode 100644 index 0000000..782e84c --- /dev/null +++ b/PHPTAL/Dom/XmlnsState.php @@ -0,0 +1,98 @@ + +// + +/** + * Stores XMLNS aliases fluctuation in the xml flow. + * + * This class is used to bind a PHPTAL namespace to an alias, for example using + * xmlns:t="http://xml.zope.org/namespaces/tal" and later use t:repeat instead + * of tal:repeat. + * + * @package phptal.dom + * @author Laurent Bedubourg + */ +class PHPTAL_Dom_XmlnsState +{ + /** Create a new XMLNS state inheriting provided aliases. */ + public function __construct($aliases = array()) + { + assert(is_array($aliases)); + $this->_aliases = $aliases; + } + + /** Returns true if $attName is a valid attribute name, false otherwise. */ + public function isValidAttribute($attName) + { + $unaliased = $this->unAliasAttribute($attName); + return PHPTAL_Dom_Defs::getInstance()->isValidAttribute($unaliased); + } + + /** Returns true if $attName is a PHPTAL attribute, false otherwise. */ + public function isPhpTalAttribute($attName) + { + $unaliased = $this->unAliasAttribute($attName); + return PHPTAL_Dom_Defs::getInstance()->isPhpTalAttribute($unaliased); + } + + /** Returns the unaliased name of specified attribute. */ + public function unAliasAttribute($attName) + { + if (count($this->_aliases) == 0) + return $attName; + + $result = $attName; + foreach ($this->_aliases as $alias => $real){ + $result = str_replace("$alias:", "$real:", $result); + } + return $result; + } + + /** + * Returns a new XmlnsState inheriting of $currentState if $nodeAttributes contains + * xmlns attributes, returns $currentState otherwise. + * + * This method is used by the PHPTAL parser to keep track of xmlns fluctuation for + * each encountered node. + */ + public static function newElement(PHPTAL_Dom_XmlnsState $currentState, $nodeAttributes) + { + $aliases = array(); + foreach ($nodeAttributes as $att => $value){ + if (PHPTAL_Dom_Defs::getInstance()->isHandledXmlNs($att, $value)){ + preg_match('/^xmlns:(.*?)$/', $att, $m); + list(,$alias) = $m; + $aliases[$alias] = PHPTAL_Dom_Defs::getInstance()->xmlnsToLocalName($value); + } + } + if (count($aliases) > 0){ + // inherit aliases with maybe an overwrite + $aliases = array_merge($currentState->_aliases, $aliases); + return new PHPTAL_Dom_XmlnsState($aliases); + } + return $currentState; + } + + private $_aliases; +} + + +?> diff --git a/PHPTAL/Exception.php b/PHPTAL/Exception.php new file mode 100644 index 0000000..b2e2627 --- /dev/null +++ b/PHPTAL/Exception.php @@ -0,0 +1,65 @@ + +// + +/** + * @package phptal + */ +class PHPTAL_Exception extends Exception +{ + public $srcFile; + public $srcLine; + + public function __construct($msg, $srcFile=false, $srcLine=false) + { + parent::__construct($msg); + $this->srcFile = $srcFile; + $this->srcLine = $srcLine; + } + + public function __toString() + { + if (empty($this->srcFile)){ + return parent::__toString(); + } + $res = sprintf('From %s around line %d'."\n", $this->srcFile, $this->srcLine); + $res .= parent::__toString(); + return $res; + } + + public static function formatted($format /*, ...*/) + { + $args = func_get_args(); + $msg = call_user_func('sprintf', $args); + return new PHPTAL_Exception($format); + } + + /** + * set new source line/file only if one hasn't been set previously + */ + public function hintSrcPosition($srcFile, $srcLine) + { + if ($srcFile && $this->srcFile === false) $this->srcFile = $srcFile; + if ($srcLine && $this->srcLine === false) $this->srcLine = $srcLine; + } +} + +?> diff --git a/PHPTAL/FileSource.php b/PHPTAL/FileSource.php new file mode 100644 index 0000000..246db00 --- /dev/null +++ b/PHPTAL/FileSource.php @@ -0,0 +1,64 @@ +_path = realpath($path); + if ($this->_path === false) throw new PHPTAL_Exception("Unable to normalize path '$path'"); + } + + public function getRealPath() + { + return $this->_path; + } + + public function getLastModifiedTime() + { + return filemtime($this->_path); + } + + public function getData() + { + return file_get_contents($this->_path); + } + + private $_path; +} + +/** + * @package phptal + */ +class PHPTAL_FileSourceResolver implements PHPTAL_SourceResolver +{ + public function __construct($repositories) + { + $this->_repositories = $repositories; + } + + public function resolve($path) + { + foreach ($this->_repositories as $repository){ + $file = $repository . DIRECTORY_SEPARATOR . $path; + if (file_exists($file)){ + return new PHPTAL_FileSource($file); + } + } + + if (file_exists($path)){ + return new PHPTAL_FileSource($path); + } + + return null; + } + + private $_repositories; +} + +?> diff --git a/PHPTAL/Filter.php b/PHPTAL/Filter.php new file mode 100644 index 0000000..36dee6b --- /dev/null +++ b/PHPTAL/Filter.php @@ -0,0 +1,31 @@ + +// + +/** + * @package phptal + */ +interface PHPTAL_Filter +{ + public function filter($str); +} + +?> diff --git a/PHPTAL/GetTextTranslator.php b/PHPTAL/GetTextTranslator.php new file mode 100644 index 0000000..53e2aaa --- /dev/null +++ b/PHPTAL/GetTextTranslator.php @@ -0,0 +1,143 @@ + +// + +require_once PHPTAL_DIR.'PHPTAL/TranslationService.php'; + +/** + * PHPTAL_TranslationService gettext implementation. + * + * Because gettext is the most common translation library in use, this + * implementation is shipped with the PHPTAL library. + * + * Please refer to the PHPTAL documentation for usage examples. + * + * @package phptal + * @author Laurent Bedubourg + */ +class PHPTAL_GetTextTranslator implements PHPTAL_TranslationService +{ + public function __construct() + { + if (!function_exists('gettext')) throw new PHPTAL_Exception("Gettext not installed"); + $this->useDomain("messages"); // PHP bug #21965 + } + + private $_vars = array(); + private $_currentDomain; + private $_encoding = 'UTF-8'; + private $_canonicalize = false; + + public function setEncoding($enc) + { + $this->_encoding = $enc; + } + + /** + * if true, all non-ASCII characters in keys will be converted to C form. This impacts performance. + * by default keys will be passed to gettext unmodified. + */ + public function setCanonicalize($bool) + { + $this->_canonicalize = $bool; + } + + public function setLanguage() + { + $langs = func_get_args(); + foreach ($langs as $langCode){ + putenv("LANG=$langCode"); + putenv("LC_ALL=$langCode"); + putenv("LANGUAGE=$langCode"); + if (setlocale(LC_ALL, $langCode)) { + return; + } + } + + $err = sprintf('Language(s) code(s) "%s" not supported by your system', join(',', $langs)); + throw new PHPTAL_Exception($err); + } + + /** + * encoding must be set before calling addDomain + */ + public function addDomain($domain, $path='./locale/') + { + bindtextdomain($domain, $path); + if ($this->_encoding){ + bind_textdomain_codeset($domain, $this->_encoding); + } + $this->useDomain($domain); + } + + public function useDomain($domain) + { + $old = $this->_currentDomain; + $this->_currentDomain = $domain; + textdomain($domain); + return $old; + } + + public function setVar($key, $value) + { + $this->_vars[$key] = $value; + } + + public function translate($key, $htmlencode=true) + { + if ($this->_canonicalize) $key = self::_canonicalizeKey($key); + + $value = gettext($key); + + if ($htmlencode){ + $value = @htmlspecialchars($value, ENT_QUOTES, $this->_encoding); // silence unsupported encoding error for ISO-8859-x, which doesn't matter. + } + while (preg_match('/\${(.*?)\}/sm', $value, $m)){ + list($src,$var) = $m; + if (!array_key_exists($var, $this->_vars)){ + $err = sprintf('Interpolation error, var "%s" not set', $var); + throw new PHPTAL_Exception($err); + } + $value = str_replace($src, $this->_vars[$var], $value); + } + return $value; + } + + static function _canonicalizeKey($key_) + { + $result = ""; + $key_ = trim($key_); + $key_ = str_replace("\n", "", $key_); + $key_ = str_replace("\r", "", $key_); + for ($i = 0; $i 127){ + $result .= 'C<'.$o.'>'; + } + else { + $result .= $c; + } + } + return $result; + } +} + diff --git a/PHPTAL/Namespace.php b/PHPTAL/Namespace.php new file mode 100644 index 0000000..a249b4f --- /dev/null +++ b/PHPTAL/Namespace.php @@ -0,0 +1,163 @@ + +// + +/** + * @package phptal + */ +abstract class PHPTAL_NamespaceAttribute +{ + /** + * @param $name string The attribute name + * @param $priority int Attribute execution priority + */ + public function __construct($name, $priority) + { + $this->_name = $name; + $this->_priority = $priority; + } + + /** + * @return string + */ + public function getName() + { + return $this->_name; + } + + public function getFullName() + { + return $this->_namespace->getName() . ':' . $this->_name; + } + + public function getPriority(){ return $this->_priority; } + public function getNamespace(){ return $this->_namespace; } + public function setNamespace(PHPTAL_Namespace $ns){ $this->_namespace = $ns; } + + public function createAttributeHandler(PHPTAL_Php_Element $tag, $expression) + { + return $this->_namespace->createAttributeHandler($this, $tag, $expression); + } + + private $_name; /* Attribute name without the namespace: prefix */ + private $_priority; /* [0 - 1000] */ + private $_namespace; /* PHPTAL_Namespace */ +} + +/** + * @package phptal + */ +class PHPTAL_NamespaceAttributeSurround extends PHPTAL_NamespaceAttribute +{ + public function __construct($name, $priority) + { + parent::__construct($name, $priority); + } +} + +/** + * @package phptal + */ +class PHPTAL_NamespaceAttributeReplace extends PHPTAL_NamespaceAttribute +{ + public function __construct($name, $priority) + { + parent::__construct($name, $priority); + } +} + +/** + * @package phptal + */ +class PHPTAL_NamespaceAttributeContent extends PHPTAL_NamespaceAttribute +{ + public function __construct($name, $priority) + { + parent::__construct($name, $priority); + } +} + +/** + * @package phptal + */ +abstract class PHPTAL_Namespace +{ + public $xmlns; + public $name; + + public function __construct($name, $xmlns) + { + $this->_attributes = array(); + $this->name = $name; + $this->xmlns = $xmlns; + } + + public function getName() + { + return $this->name; + } + + public function hasAttribute($attributeName) + { + return array_key_exists(strtolower($attributeName), $this->_attributes); + } + + public function getAttribute($attributeName) + { + return $this->_attributes[strtolower($attributeName)]; + } + + public function addAttribute(PHPTAL_NamespaceAttribute $attribute) + { + $attribute->setNamespace($this); + $this->_attributes[strtolower($attribute->getName())] = $attribute; + } + + public function getAttributes() + { + return $this->_attributes; + } + + abstract public function createAttributeHandler(PHPTAL_NamespaceAttribute $att, PHPTAL_Php_Element $tag, $expression); + + protected $_attributes; +} + +/** + * @package phptal + */ +class PHPTAL_BuiltinNamespace extends PHPTAL_Namespace +{ + public function createAttributeHandler(PHPTAL_NamespaceAttribute $att, PHPTAL_Php_Element $tag, $expression) + { + $name = $att->getName(); + $name = str_replace('-', '', $name); + + $class = 'PHPTAL_Php_Attribute_'.$this->getName().'_'.$name; + $result = new $class(); + $result->tag = $tag; + $result->name = strtoupper($att->getFullName()); + $result->expression = $expression; + return $result; + } +} + +?> diff --git a/PHPTAL/Namespace/I18N.php b/PHPTAL/Namespace/I18N.php new file mode 100644 index 0000000..1a3cc18 --- /dev/null +++ b/PHPTAL/Namespace/I18N.php @@ -0,0 +1,28 @@ +addAttribute(new PHPTAL_NamespaceAttributeContent('translate', 5)); + $this->addAttribute(new PHPTAL_NamespaceAttributeSurround('name', 5)); + $this->addAttribute(new PHPTAL_NamespaceAttributeSurround('attributes', 10)); + $this->addAttribute(new PHPTAL_NamespaceAttributeSurround('domain', 3)); + } +} + +PHPTAL_Dom_Defs::getInstance()->registerNamespace(new PHPTAL_Namespace_I18N()); + +?> diff --git a/PHPTAL/Namespace/METAL.php b/PHPTAL/Namespace/METAL.php new file mode 100644 index 0000000..dd64a6c --- /dev/null +++ b/PHPTAL/Namespace/METAL.php @@ -0,0 +1,28 @@ +addAttribute(new PHPTAL_NamespaceAttributeSurround('define-macro', 1)); + $this->addAttribute(new PHPTAL_NamespaceAttributeReplace('use-macro', 9)); + $this->addAttribute(new PHPTAL_NamespaceAttributeSurround('define-slot', 9)); + $this->addAttribute(new PHPTAL_NamespaceAttributeSurround('fill-slot', 9)); + } +} + +PHPTAL_Dom_Defs::getInstance()->registerNamespace(new PHPTAL_Namespace_METAL()); + +?> diff --git a/PHPTAL/Namespace/PHPTAL.php b/PHPTAL/Namespace/PHPTAL.php new file mode 100644 index 0000000..b458f7a --- /dev/null +++ b/PHPTAL/Namespace/PHPTAL.php @@ -0,0 +1,28 @@ +addAttribute(new PHPTAL_NamespaceAttributeSurround('tales', -1)); + $this->addAttribute(new PHPTAL_NamespaceAttributeSurround('debug', -2)); + $this->addAttribute(new PHPTAL_NamespaceAttributeSurround('id', 7)); + $this->addAttribute(new PHPTAL_NamespaceAttributeSurround('cache', -3)); + } +} + +PHPTAL_Dom_Defs::getInstance()->registerNamespace(new PHPTAL_Namespace_PHPTAL()); + +?> diff --git a/PHPTAL/Namespace/TAL.php b/PHPTAL/Namespace/TAL.php new file mode 100644 index 0000000..57916fc --- /dev/null +++ b/PHPTAL/Namespace/TAL.php @@ -0,0 +1,37 @@ +addAttribute(new PHPTAL_NamespaceAttributeSurround('define', 4)); + $this->addAttribute(new PHPTAL_NamespaceAttributeSurround('condition', 6)); + $this->addAttribute(new PHPTAL_NamespaceAttributeSurround('repeat', 8)); + $this->addAttribute(new PHPTAL_NamespaceAttributeContent('content', 11)); + $this->addAttribute(new PHPTAL_NamespaceAttributeReplace('replace', 9)); + $this->addAttribute(new PHPTAL_NamespaceAttributeSurround('attributes', 9)); + $this->addAttribute(new PHPTAL_NamespaceAttributeSurround('omit-tag', 0)); + $this->addAttribute(new PHPTAL_NamespaceAttributeSurround('comment', 12)); + $this->addAttribute(new PHPTAL_NamespaceAttributeSurround('on-error', 2)); + } +} + +PHPTAL_Dom_Defs::getInstance()->registerNamespace(new PHPTAL_Namespace_TAL()); + +?> diff --git a/PHPTAL/Php/Attribute.php b/PHPTAL/Php/Attribute.php new file mode 100644 index 0000000..00f701e --- /dev/null +++ b/PHPTAL/Php/Attribute.php @@ -0,0 +1,98 @@ + +// + +require_once PHPTAL_DIR.'PHPTAL/Dom/Node.php'; + +/** + * Base class for all PHPTAL attributes. + * + * Attributes are first ordered by PHPTAL then called depending on their + * priority before and after the element printing. + * + * An attribute must implements start() and end(). + * + * @package phptal.php + * @author Laurent Bedubourg + */ +abstract class PHPTAL_Php_Attribute +{ + const ECHO_TEXT = 'text'; + const ECHO_STRUCTURE = 'structure'; + + /** Attribute name (ie: 'tal:content'). */ + public $name; + /** Attribute value specified by the element. */ + public $expression; + /** Element using this attribute (xml node). */ + public $tag; + + /** Called before element printing. */ + public abstract function start(); + /** Called after element printing. */ + public abstract function end(); + + /** + * Remove structure|text keyword from expression and stores it for later + * doEcho() usage. + * + * $expression = 'stucture my/path'; + * $expression = $this->extractEchoType($expression); + * + * ... + * + * $this->doEcho($code); + */ + protected function extractEchoType($expression) + { + $echoType = self::ECHO_TEXT; + $expression = trim($expression); + if (preg_match('/^(text|structure)\s+(.*?)$/ism', $expression, $m)) { + list(, $echoType, $expression) = $m; + } + $this->_echoType = strtolower($echoType); + return trim($expression); + } + + protected function doEcho($code) + { + if ($this->_echoType == self::ECHO_TEXT) + $this->tag->generator->doEcho($code); + else + $this->tag->generator->doEchoRaw($code); + } + + protected function parseSetExpression($exp) + { + $exp = trim($exp); + // (dest) (value) + if (preg_match('/^([a-z0-9:\-_]+)\s+(.*?)$/i', $exp, $m)){ + array_shift($m); + return $m; + } + // (dest) + return array($exp, null); + } + + protected $_echoType = PHPTAL_Php_Attribute::ECHO_TEXT; +} + +?> diff --git a/PHPTAL/Php/Attribute/I18N/Attributes.php b/PHPTAL/Php/Attribute/I18N/Attributes.php new file mode 100644 index 0000000..2d5c923 --- /dev/null +++ b/PHPTAL/Php/Attribute/I18N/Attributes.php @@ -0,0 +1,107 @@ + +// +// +// In this example, let tal:attributes set the value of the alt attribute to +// the text "Stop by for a visit!". This text will be passed to the +// translation service, which uses the result of language negotiation to +// translate "Stop by for a visit!" into the requested language. The example +// text in the template, "Visit us", will simply be discarded. +// +// Another example, with explicit message IDs: +// +// Up +// +// Here, the message ID up-arrow-icon will be used to generate the link to +// an icon image file, and the message ID up-arrow-alttext will be used for +// the "alt" text. +// + +/** + * @package phptal.php.attribute + */ +class PHPTAL_Php_Attribute_I18N_Attributes extends PHPTAL_Php_Attribute +{ + public function start() + { + // split attributes to translate + $expressions = $this->tag->generator->splitExpression($this->expression); + // foreach attribute + foreach ($expressions as $exp){ + list($attribute, $key) = $this->parseSetExpression($exp); + // if the translation key is specified + if ($key != null){ + // we use it and replace the tag attribute with the result of + // the translation + $key = str_replace('\'', '\\\'', $key); + $this->tag->attributes[$attribute] = $this->_getTranslationCode("'$key'"); + } + else if ($this->tag->isOverwrittenAttribute($attribute)){ + $varn = $this->tag->getOverwrittenAttributeVarName($attribute); + $this->tag->attributes[$attribute] = $this->_getTranslationCode($varn); + } + // else if the attribute has a default value + else if ($this->tag->hasAttribute($attribute)){ + // we use this default value as the translation key + $key = $this->tag->getAttribute($attribute); + $key = str_replace('\'', '\\\'', $key); + $this->tag->attributes[$attribute] = $this->_getTranslationCode("'$key'"); + } + else { + // unable to translate the attribute + throw new PHPTAL_Exception("Unable to translate attribute $attribute"); + } + } + } + + public function end() + { + } + + private function _getTranslationCode($key) + { + $code = 'setVar(\''.$name.'\','.phptal_tale($name).');'; // allow more complex TAL expressions + } + $code .= "\n"; + } + + // notice the false boolean which indicate that the html is escaped + // elsewhere looks like an hack doesn't it ? :) + $result = $this->tag->generator->escapeCode(sprintf('$_translator->translate(%s, false)', $key)); + $code .= 'echo '.$result.'?>'; + return $code; + } +} + +?> diff --git a/PHPTAL/Php/Attribute/I18N/Data.php b/PHPTAL/Php/Attribute/I18N/Data.php new file mode 100644 index 0000000..073bf2f --- /dev/null +++ b/PHPTAL/Php/Attribute/I18N/Data.php @@ -0,0 +1,24 @@ + diff --git a/PHPTAL/Php/Attribute/I18N/Domain.php b/PHPTAL/Php/Attribute/I18N/Domain.php new file mode 100644 index 0000000..dba7fdb --- /dev/null +++ b/PHPTAL/Php/Attribute/I18N/Domain.php @@ -0,0 +1,41 @@ +tag->generator->doIf('!isset($__i18n_domains)'); + $this->tag->generator->pushCode('$__i18n_domains = array()'); + $this->tag->generator->doEnd(); + + //\''.str_replace(array('\\',"'"),array('\\\\',"\\'"),$expression).'\' + $expression = $this->tag->generator->interpolateTalesVarsInString($this->expression); + + // push current domain and use new domain + $code = '$__i18n_domains[] = $_translator->useDomain('.$expression.')'; + $this->tag->generator->pushCode($code); + } + + public function end() + { + // restore domain + $code = '$_translator->useDomain(array_pop($__i18n_domains))'; + $this->tag->generator->pushCode($code); + } +} + +?> diff --git a/PHPTAL/Php/Attribute/I18N/Name.php b/PHPTAL/Php/Attribute/I18N/Name.php new file mode 100644 index 0000000..d68152b --- /dev/null +++ b/PHPTAL/Php/Attribute/I18N/Name.php @@ -0,0 +1,40 @@ + +// was born in +// . +// +// +// would cause this text to be passed to the translation service: +// +// "${name} was born in ${country}." +// + +/** + * @package phptal.php.attribute.i18n + */ +class PHPTAL_Php_Attribute_I18N_Name extends PHPTAL_Php_Attribute +{ + public function start() + { + $this->tag->generator->pushCode('ob_start()'); + } + + public function end() + { + $code = '$_translator->setVar(\'%s\', ob_get_contents())'; + $code = sprintf($code, $this->expression); + $this->tag->generator->pushCode($code); + $this->tag->generator->pushCode('ob_end_clean()'); + } +} + +?> diff --git a/PHPTAL/Php/Attribute/I18N/Source.php b/PHPTAL/Php/Attribute/I18N/Source.php new file mode 100644 index 0000000..ce5a7d2 --- /dev/null +++ b/PHPTAL/Php/Attribute/I18N/Source.php @@ -0,0 +1,38 @@ +tag->generator->doIf('!isset($__i18n_sources)'); + $this->tag->generator->pushCode('$__i18n_sources = array()'); + $this->tag->generator->end(); + + // push current source and use new one + $code = '$__i18n_sources[] = $_translator->setSource(\'%s\')'; + $code = sprintf($code, $this->expression); + $this->tag->generator->pushCode($code); + } + + public function end() + { + // restore source + $code = '$_translator->setSource(array_pop($__i18n_sources))'; + $this->tag->generator->pushCode($code); + } +} + +?> diff --git a/PHPTAL/Php/Attribute/I18N/Target.php b/PHPTAL/Php/Attribute/I18N/Target.php new file mode 100644 index 0000000..6face57 --- /dev/null +++ b/PHPTAL/Php/Attribute/I18N/Target.php @@ -0,0 +1,32 @@ + diff --git a/PHPTAL/Php/Attribute/I18N/Translate.php b/PHPTAL/Php/Attribute/I18N/Translate.php new file mode 100644 index 0000000..1492531 --- /dev/null +++ b/PHPTAL/Php/Attribute/I18N/Translate.php @@ -0,0 +1,97 @@ +expression,$m)) + { + if ($m[1]=='structure') $escape=false; + $this->expression = isset($m[2])?$m[2]:''; + } + + // if no expression is given, the content of the node is used as + // a translation key + if (strlen(trim($this->expression)) == 0){ + $key = $this->_getTranslationKey($this->tag, !$escape); + $key = trim(preg_replace('/\s+/sm'.($this->tag->generator->getEncoding()=='UTF-8'?'u':''), ' ', $key)); + $code = '\'' . str_replace('\'', '\\\'', $key) . '\''; + } + else { + $code = $this->tag->generator->evaluateExpression($this->expression); + } + $this->_prepareNames($this->tag); + + $php = sprintf('echo $_translator->translate(%s,%s);', $code, $escape ? 'true':'false'); + $this->tag->generator->pushCode($php); + } + + public function end() + { + } + + private function _getTranslationKey($tag, $preserve_tags) + { + $result = ''; + foreach ($tag->children as $child){ + if ($child instanceOf PHPTAL_Php_Text){ + $result .= $child->node->getValue(); + } + else if ($child instanceOf PHPTAL_Php_Element){ + if ($child->hasAttribute('i18n:name')){ + $value = $child->getAttribute('i18n:name'); + $result .= '${' . $value . '}'; + } + else { + + if ($preserve_tags) + { + $result .= '<'.$child->name; + foreach($child->attributes as $k => $v) + { + $result .= ' '.$k.'="'.$v.'"'; + } + $result .= '>'.$this->_getTranslationKey($child, $preserve_tags).'name.'>'; + } + else + { + $result .= $this->_getTranslationKey($child, $preserve_tags); + } + } + } + } + return $result; + } + + private function _prepareNames($tag) + { + foreach ($tag->children as $child){ + if ($child instanceOf PHPTAL_Php_Element){ + if ($child->hasAttribute('i18n:name')){ + $child->generate(); + } + else { + $this->_prepareNames($child); + } + } + } + } +} + +?> diff --git a/PHPTAL/Php/Attribute/METAL/DefineMacro.php b/PHPTAL/Php/Attribute/METAL/DefineMacro.php new file mode 100644 index 0000000..fd42cd9 --- /dev/null +++ b/PHPTAL/Php/Attribute/METAL/DefineMacro.php @@ -0,0 +1,71 @@ + +// + +require_once PHPTAL_DIR.'PHPTAL/Php/Attribute.php'; + +// METAL Specification 1.0 +// +// argument ::= Name +// +// Example: +// +//

+// Copyright 2001, Foobar Inc. +//

+// +// PHPTAL: +// +// +//

+// Copyright 2001, Foobar Inc. +//

+// +// + +/** + * @package phptal.php.attribute.metal + * @author Laurent Bedubourg + */ +class PHPTAL_Php_Attribute_METAL_DefineMacro extends PHPTAL_Php_Attribute +{ + public function start() + { + $macroname = strtr(trim($this->expression),'-','_'); + if (!preg_match('/^[a-z0-9_]+$/i', $macroname)){ + throw new PHPTAL_Exception('Bad macro name "'.$macroname.'"', $this->tag->getSourceFile(), $this->tag->getSourceLine()); + } + + $this->tag->generator->doFunction($macroname, '$tpl, $ctx'); + $this->tag->generator->doXmlDeclaration(); + $this->tag->generator->doDoctype(); + $this->tag->generator->doSetVar('$tpl', 'clone $tpl'); + $this->tag->generator->doSetVar('$ctx', '$tpl->getContext()'); + $this->tag->generator->doSetVar('$glb', '$tpl->getGlobalContext()'); + $this->tag->generator->doSetVar('$_translator', '$tpl->getTranslator()'); + } + + public function end() + { + $this->tag->generator->doEnd(); + } +} + diff --git a/PHPTAL/Php/Attribute/METAL/DefineSlot.php b/PHPTAL/Php/Attribute/METAL/DefineSlot.php new file mode 100644 index 0000000..837d813 --- /dev/null +++ b/PHPTAL/Php/Attribute/METAL/DefineSlot.php @@ -0,0 +1,75 @@ + +// + +require_once PHPTAL_DIR.'PHPTAL/Php/Attribute.php'; + +// METAL Specification 1.0 +// +// argument ::= Name +// +// Example: +// +// +// +// +//
Links
+// A Link +//
+// +// PHPTAL: (access to slots may be renamed) +// +// +// +// +// +// slots->links)): ? > +// slots->links ? > +// +// +//
Links
+// A Link +//
+// +// + +/** + * @package phptal.php.attribute.metal + * @author Laurent Bedubourg + */ +class PHPTAL_Php_Attribute_METAL_DefineSlot extends PHPTAL_Php_Attribute +{ + public function start() + { + $cond = sprintf('$ctx->hasSlot("%s")', $this->expression); + $this->tag->generator->doIf($cond); + $code = sprintf('getSlot("%s") ?>', $this->expression); + $this->tag->generator->pushHtml($code); + $this->tag->generator->doElse(); + } + + public function end() + { + $this->tag->generator->doEnd(); + } +} + +?> diff --git a/PHPTAL/Php/Attribute/METAL/FillSlot.php b/PHPTAL/Php/Attribute/METAL/FillSlot.php new file mode 100644 index 0000000..2e8d24b --- /dev/null +++ b/PHPTAL/Php/Attribute/METAL/FillSlot.php @@ -0,0 +1,75 @@ + +// + +require_once PHPTAL_DIR.'PHPTAL/Php/Attribute.php'; + +// METAL Specification 1.0 +// +// argument ::= Name +// +// Example: +// +// +// +// +//
Links
+// Good Place
+// Bad Place
+// Other Place +//
+// +// PHPTAL: +// +// 1. evaluate slots +// +// +// +// Good Place
+// Bad Place
+// Other Place +// +// slots->links = ob_get_contents(); ob_end_clean(); ? > +// +// 2. call the macro (here not supported) +// +// +// + +/** + * @package phptal.php.attribute.metal + * @author Laurent Bedubourg + */ +class PHPTAL_Php_Attribute_METAL_FillSlot extends PHPTAL_Php_Attribute +{ + public function start() + { + $this->tag->generator->pushCode('ob_start()'); + } + + public function end() + { + $code = '$ctx->fillSlot("'.$this->expression.'", ob_get_clean())'; + $this->tag->generator->pushCode($code); + } +} + + diff --git a/PHPTAL/Php/Attribute/METAL/UseMacro.php b/PHPTAL/Php/Attribute/METAL/UseMacro.php new file mode 100644 index 0000000..4671990 --- /dev/null +++ b/PHPTAL/Php/Attribute/METAL/UseMacro.php @@ -0,0 +1,131 @@ + +// + +require_once PHPTAL_DIR.'PHPTAL/Php/Attribute.php'; + +// METAL Specification 1.0 +// +// argument ::= expression +// +// Example: +// +//
+//

+//


+// +// PHPTAL: (here not supported) +// +// +// + +/** + * @package phptal.php.attribute.metal + * @author Laurent Bedubourg + */ +class PHPTAL_Php_Attribute_METAL_UseMacro extends PHPTAL_Php_Attribute +{ + static $ALLOWED_ATTRIBUTES = array( + 'metal:fill-slot', + 'metal:define-macro', + 'tal:define', + ); + + public function start() + { + $this->pushSlots(); + + foreach ($this->tag->children as $child){ + $this->generateFillSlots($child); + } + + $macroname = strtr($this->expression,'-','_'); + + // local macro (no filename specified) and non dynamic macro name + if (preg_match('/^[a-z0-9_]+$/i', $macroname)) { + $code = sprintf( + '%s%s($tpl, $ctx)', + $this->tag->generator->getFunctionPrefix(), + $macroname + ); + $this->tag->generator->pushCode($code); + } + // external macro or ${macroname}, use PHPTAL at runtime to resolve it + else { + $code = $this->tag->generator->interpolateTalesVarsInString($this->expression); + $code = sprintf('executeMacro(%s); ?>', $code); + $this->tag->generator->pushHtml($code); + } + + $this->popSlots(); + } + + public function end() + { + } + + private function pushSlots() + { + // reset template slots on each macro call ? + // + // NOTE: defining a macro and using another macro on the same tag + // means inheriting from the used macro, thus slots are shared, it + // is a little tricky to understand but very natural to use. + // + // For example, we may have a main design.html containing our main + // website presentation with some slots (menu, content, etc...) then + // we may define a member.html macro which use the design.html macro + // for the general layout, fill the menu slot and let caller templates + // fill the parent content slot without interfering. + if (!$this->tag->hasAttribute('metal:define-macro')){ + $this->tag->generator->pushCode('$ctx->pushSlots()'); + } + } + + private function popSlots() + { + // restore slots if not inherited macro + if (!$this->tag->hasAttribute('metal:define-macro')){ + $this->tag->generator->pushCode('$ctx->popSlots()'); + } + } + + private function generateFillSlots($tag) + { + if (false == ($tag instanceOf PHPTAL_Php_Tree)) + return; + + // if the tag contains one of the allowed attribute, we generate it + foreach (self::$ALLOWED_ATTRIBUTES as $attribute){ + if ($tag->hasAttribute($attribute)){ + $tag->generate(); + return; + } + } + + // recurse + foreach ($tag->children as $child){ + $this->generateFillSlots($child); + } + } +} + +?> diff --git a/PHPTAL/Php/Attribute/PHPTAL/Cache.php b/PHPTAL/Php/Attribute/PHPTAL/Cache.php new file mode 100644 index 0000000..a43ebc3 --- /dev/null +++ b/PHPTAL/Php/Attribute/PHPTAL/Cache.php @@ -0,0 +1,92 @@ + +// + +require_once PHPTAL_DIR.'PHPTAL/Php/Attribute.php'; + +/** phptal:cache (note that's not tal:cache) caches element's HTML for a given time. Time is a number with 'd', 'h', 'm' or 's' suffix. + There's optional parameter that defines how cache should be shared. By default cache is not sensitive to template's context at all + - it's shared between all pages that use that template. + You can add per url to have separate copy of given element for every URL. + + You can add per expression to have different cache copy for every different value of an expression (which MUST evaluate to a string). + Expression cannot refer to variables defined using tal:define on the same element. + + NB: + * phptal:cache blocks can be nested, but outmost block will cache other blocks regardless of their freshness. + * you cannot use metal:fill-slot inside elements with phptal:cache + + Examples: +
...
+
    ...
+*/ +class PHPTAL_Php_Attribute_PHPTAL_Cache extends PHPTAL_Php_Attribute +{ + private $cache_tag; + + public function start() + { + if (!preg_match('/^\s*([0-9]+\s*|[a-zA-Z][a-zA-Z0-9_]*\s+)([dhms])\s*(?:\;?\s*per\s+([^;]+)|)\s*$/',$this->expression, $matches)) + throw new PHPTAL_Exception("Cache attribute syntax error: ".$this->expression); + + $cache_len = $matches[1]; + if (!is_numeric($cache_len)) $cache_len = '$ctx->'.$cache_len; + switch($matches[2]) + { + case 'd': $cache_len .= '*24'; /* no break */ + case 'h': $cache_len .= '*60'; /* no break */ + case 'm': $cache_len .= '*60'; /* no break */ + } + + $this->cache_tag = '"'.addslashes( $this->tag->node->getName() . ':' . $this->tag->node->getSourceLine()).'"'; + + $cache_per_expression = isset($matches[3])?trim($matches[3]):NULL; + if ($cache_per_expression == 'url') + { + $this->cache_tag .= '.$_SERVER["REQUEST_URI"]'; + } + else if ($cache_per_expression == 'nothing') { } + else if ($cache_per_expression) + { + $code = $this->tag->generator->evaluateExpression($cache_per_expression); + + if (is_array($code)) { throw new PHPTAL_Exception("Chained expressions in per-cache directive are not supported"); } + + $old_cache_tag = $this->cache_tag; + $this->cache_tag = '$ctx->cache_tag_'; + $this->tag->generator->doSetVar($this->cache_tag, '('.$code.')."@".' . $old_cache_tag ); + } + + $cond = '!file_exists(__FILE__.md5('.$this->cache_tag.')) || time() - '.$cache_len.' >= @filemtime(__FILE__.md5('.$this->cache_tag.'))'; + + $this->tag->generator->doIf($cond); + $this->tag->generator->doEval('ob_start()'); + } + + public function end() + { + $this->tag->generator->doEval('file_put_contents(__FILE__.md5('.$this->cache_tag.'), ob_get_flush())'); + $this->tag->generator->doElse(); + $this->tag->generator->doEval('readfile(__FILE__.md5('.$this->cache_tag.'))'); + $this->tag->generator->doEnd(); + } +} + diff --git a/PHPTAL/Php/Attribute/PHPTAL/Debug.php b/PHPTAL/Php/Attribute/PHPTAL/Debug.php new file mode 100644 index 0000000..a41da0f --- /dev/null +++ b/PHPTAL/Php/Attribute/PHPTAL/Debug.php @@ -0,0 +1,44 @@ + +// + +require_once PHPTAL_DIR.'PHPTAL/Php/Attribute.php'; + +/** + * @package phptal.php.attribute.phptal + * @author Laurent Bedubourg + */ +class PHPTAL_Php_Attribute_PHPTAL_DEBUG extends PHPTAL_Php_Attribute +{ + public function start() + { + $this->_oldMode = $this->tag->generator->setDebug(true); + } + + public function end() + { + $this->tag->generator->setDebug( $this->_oldMode ); + } + + private $_oldMode; +} + +?> diff --git a/PHPTAL/Php/Attribute/PHPTAL/Id.php b/PHPTAL/Php/Attribute/PHPTAL/Id.php new file mode 100644 index 0000000..591a8be --- /dev/null +++ b/PHPTAL/Php/Attribute/PHPTAL/Id.php @@ -0,0 +1,66 @@ + +// + +require_once PHPTAL_DIR.'PHPTAL/Php/Attribute.php'; + +/** + * @package phptal.php.attribute.phptal + * @author Laurent Bedubourg + */ +class PHPTAL_Php_Attribute_PHPTAL_ID extends PHPTAL_Php_Attribute +{ + private $id; + + public function start() + { + $this->id = str_replace('"', '\\\"', $this->expression); + + // retrieve trigger + $this->tag->generator->doSetVar( + '$trigger', + '$tpl->getTrigger("'.$this->id.'")' + ); + + // if trigger found and trigger tells to proceed, we execute + // the node content + $cond = '$trigger && ' + . '$trigger->start("%s", $tpl) == PHPTAL_Trigger::PROCEED'; + $cond = sprintf($cond, $this->id); + + $this->tag->generator->doIf($cond); + } + + public function end() + { + // end of if PROCEED + $this->tag->generator->doEnd(); + + // if trigger found, notify the end of the node + $this->tag->generator->doIf('$trigger'); + $this->tag->generator->pushCode( + '$trigger->end("'.$this->id.'", $tpl)' + ); + $this->tag->generator->doEnd(); + } +} + +?> diff --git a/PHPTAL/Php/Attribute/PHPTAL/Tales.php b/PHPTAL/Php/Attribute/PHPTAL/Tales.php new file mode 100644 index 0000000..2bf5b01 --- /dev/null +++ b/PHPTAL/Php/Attribute/PHPTAL/Tales.php @@ -0,0 +1,60 @@ + +// + +require_once PHPTAL_DIR.'PHPTAL/Php/Attribute.php'; + +/** + * @package phptal.php.attribute.phptal + * @author Laurent Bedubourg + */ +class PHPTAL_Php_Attribute_PHPTAL_TALES extends PHPTAL_Php_Attribute +{ + public function start() + { + $mode = trim($this->expression); + $mode = strtolower($mode); + + if ($mode == '' || $mode == 'default') + $mode = 'tales'; + + if ($mode != 'php' && $mode != 'tales') { + $err = "Unsupported TALES mode '%s'"; + $err = sprintf($err, $mode); + throw new PHPTAL_Exception( + $err, + $this->tag->getSourceFile(), + $this->tag->getSourceLine() + ); + } + + $this->_oldMode = $this->tag->generator->setTalesMode( $mode ); + } + + public function end() + { + $this->tag->generator->setTalesMode( $this->_oldMode ); + } + + private $_oldMode; +} + +?> diff --git a/PHPTAL/Php/Attribute/TAL/Attributes.php b/PHPTAL/Php/Attribute/TAL/Attributes.php new file mode 100644 index 0000000..99de33a --- /dev/null +++ b/PHPTAL/Php/Attribute/TAL/Attributes.php @@ -0,0 +1,168 @@ + +// + +require_once PHPTAL_DIR.'PHPTAL/Php/Attribute.php'; + +// TAL Specifications 1.4 +// +// argument ::= attribute_statement [';' attribute_statement]* +// attribute_statement ::= attribute_name expression +// attribute_name ::= [namespace ':'] Name +// namespace ::= Name +// +// examples: +// +// +//