From c9bd3033ed0c3a9f0db84eb0f7975fae3056e37a Mon Sep 17 00:00:00 2001 From: Mostafa Barmshory Date: Wed, 10 Feb 2021 03:12:34 +0330 Subject: [PATCH] Enabling test units --- .gitignore | 3 +- composer.json | 30 +- examples/HelloWord/Module.php | 17 - .../Processors/HelloWordProcessor.php | 17 - examples/HelloWord/module.json | 4 - examples/HelloWord/urls.php | 11 - examples/NoteBook/Book.php | 140 -- examples/NoteBook/Item.php | 92 - examples/NoteBook/Library.php | 58 - examples/NoteBook/Module.php | 17 - examples/NoteBook/Tag.php | 44 - examples/NoteBook/module.json | 20 - examples/NoteBook/url.php | 3 - examples/README | 13 - examples/Relation/ManyToManyOne.php | 60 - examples/Relation/ManyToManyTwo.php | 59 - examples/Relation/Model.php | 118 -- examples/Relation/ModelCount.php | 60 - examples/Relation/ModelRecurse.php | 59 - examples/Relation/Module.php | 40 - examples/Relation/RelatedToTestModel.php | 89 - examples/Relation/RelatedToTestModel2.php | 60 - examples/Relation/module.json | 13 - examples/Smallest/Module.php | 17 - examples/Smallest/module.json | 4 - examples/Smallest/url.php | 2 - phpunit.xml | 85 +- src/Cache.php | 151 -- src/Cache/Apcu.php | 113 - src/Cache/ArrayCache.php | 47 - src/Cache/File.php | 124 -- src/Cache/Memcached.php | 95 - src/ConfigTrait.php | 209 -- src/Crypt.php | 262 --- src/Data/Encoder/JsonEncoder.php | 15 - src/Data/Exception.php | 16 - src/Data/InvalidRelationKeyException.php | 44 - src/Data/Model.php | 1232 ----------- src/Data/ModelDescription.php | 184 -- src/Data/ModelEncoder.php | 14 - src/Data/ModelProperty.php | 122 -- src/Data/ModelUtils.php | 293 --- src/Data/Query.php | 270 --- src/Data/QueryBuilder.php | 218 -- src/Data/QueryBuilder/RequestQueryBuilder.php | 165 -- src/Data/Repository.php | 246 --- src/Data/Repository/ModelRepository.php | 413 ---- src/Data/Repository/RelationRepository.php | 286 --- src/Data/Schema.php | 1195 ----------- src/Data/Schema/MySQLSchema.php | 386 ---- src/Data/Schema/SQLiteSchema.php | 273 --- src/Date.php | 251 --- src/Db/Connection.php | 427 ---- src/Db/Connection/Counter.php | 101 - src/Db/Connection/Dumper.php | 67 - src/Db/Connection/Oracle.php | 68 - src/Db/Connection/Oracle12.php | 18 - src/Db/Connection/PgSQL.php | 35 - src/Db/Connection/Proxy.php | 55 - src/Db/Engine.php | 394 ---- src/Db/Exception.php | 9 - src/Db/Expression.php | 714 ------- src/Db/Expression/MySQL.php | 17 - src/Db/Expressionable.php | 10 - src/Db/Query.php | 1646 --------------- src/Db/Query/MySQL.php | 50 - src/Db/Query/Oracle.php | 62 - src/Db/Query/Oracle12c.php | 32 - src/Db/Query/OracleAbstract.php | 76 - src/Db/Query/PgSQL.php | 66 - src/Db/Query/SQLite.php | 42 - src/Db/ResultSet.php | 18 - src/DiContainerTrait.php | 88 - src/Dispatcher.php | 227 -- src/Encoder.php | 177 -- src/Exception.php | 302 +-- src/ExceptionBuilder.php | 120 ++ src/ExceptionRenderer/Console.php | 133 -- src/ExceptionRenderer/HTML.php | 193 -- src/ExceptionRenderer/HTMLText.php | 137 -- src/ExceptionRenderer/JSON.php | 162 -- src/ExceptionRenderer/RendererAbstract.php | 139 -- src/FileUtil.php | 293 --- src/Graphql.php | 127 -- src/Graphql/Compiler.php | 407 ---- src/HTTP.php | 105 - src/HTTP/Error403.php | 24 - src/HTTP/Error404.php | 23 - src/HTTP/Error500.php | 22 - src/HTTP/Request.php | 304 --- src/HTTP/Response.php | 257 --- src/HTTP/Response/CommandPassThru.php | 54 - src/HTTP/Response/File.php | 90 - src/HTTP/Response/Forbidden.php | 48 - src/HTTP/Response/Json.php | 35 - src/HTTP/Response/NotAvailable.php | 63 - src/HTTP/Response/NotFound.php | 71 - src/HTTP/Response/PlainText.php | 34 - src/HTTP/Response/Redirect.php | 39 - src/HTTP/Response/RedirectToLogin.php | 69 - src/HTTP/Response/ResumableFile.php | 200 -- src/HTTP/Response/ServerError.php | 64 - src/HTTP/ResponseFileBuilder.php | 70 - src/HTTP/URL.php | 231 --- src/Logger.php | 215 -- src/LoggerAppender/File.php | 45 - src/LoggerAppender/Remote.php | 62 - src/LoggerFormatter.php | 14 - src/LoggerFormatter/Plain.php | 18 - src/LoggerManager.php | 113 - src/Mail.php | 229 --- src/Mail/Batch.php | 252 --- src/Migration.php | 553 ----- src/Module.php | 183 -- src/ObjectMapper.php | 63 - src/ObjectMapper/ArrayMapper.php | 62 - src/ObjectMapper/RequestMapper.php | 78 - src/ObjectStorage.php | 134 -- src/ObjectValidator.php | 24 - src/Options.php | 213 -- src/Pluf/Module.php | 52 - src/Pluf/Queue.php | 134 -- src/Pluf/Queue/Processor.php | 98 - src/Pluf/README | 3 - src/Pluf/Search.php | 256 --- src/Pluf/Search/Occ.php | 81 - src/Pluf/Search/ResultSet.php | 77 - src/Pluf/Search/Stats.php | 85 - src/Pluf/Search/Word.php | 55 - src/Pluf/Session.php | 222 -- src/Pluf/Tenant.php | 278 --- src/Pluf/module.json | 13 - src/Precondition.php | 72 - src/Process/Http/AccessLog.php | 113 + .../Exception/InvalidBodyContentException.php | 7 + src/Process/Http/FileToResponse.php | 200 ++ src/Process/Http/IfMethodIs.php | 43 + .../Http/IfMethodIsDelete.php} | 15 +- .../Http/IfMethodIsGet.php} | 16 +- .../Http/IfMethodIsPost.php} | 16 +- .../Http/IfMethodIsPut.php} | 15 +- src/Process/Http/IfPathAndMethodIs.php | 61 + src/Process/Http/IfPathIs.php | 64 + src/Process/Http/RequestBodyParser.php | 93 + .../Http/ResourceNotFoundException.php} | 23 +- src/Process/Http/ResponseBodyEncoder.php | 48 + src/Processor.php | 50 - src/ProcessorAdaptor.php | 54 - src/Processors/DispatcherSignals.php | 56 - src/Processors/ExceptionProcessor.php | 33 - src/Processors/GraphqlRender.php | 36 - src/Processors/HttpResponseEncoder.php | 46 - src/Processors/ItemBinaryUpload.php | 35 - src/Processors/ItemCollectionCreate.php | 34 - src/Processors/ItemCollectionDelete.php | 34 - src/Processors/ItemCollectionRead.php | 34 - src/Processors/ItemCollectionUpdate.php | 34 - src/Processors/ItemCreate.php | 87 - src/Processors/ItemDelete.php | 52 - src/Processors/ItemRead.php | 109 - src/Processors/ItemUpdate.php | 46 - src/Processors/ReadOnly.php | 123 -- src/Processors/Session.php | 171 -- src/Processors/TenantProcessor.php | 161 -- src/SQL.php | 173 -- src/Shortcuts.php | 148 -- src/Signal.php | 78 - src/Template.php | 365 ---- src/Template/Compiler.php | 730 ------- src/Template/Context.php | 49 - src/Template/Context/Request.php | 68 - src/Template/ContextVars.php | 43 - src/Template/SafeString.php | 47 - src/Template/Tag.php | 32 - src/Template/Tag/APerm.php | 51 - src/Template/Tag/Cfg.php | 57 - src/Template/Tag/Cycle.php | 157 -- src/Template/Tag/Firstof.php | 80 - src/Template/Tag/MediaUrl.php | 39 - src/Template/Tag/Messages.php | 40 - src/Template/Tag/Mytag.php | 83 - src/Template/Tag/Now.php | 47 - src/Template/Tag/Regroup.php | 171 -- src/Template/Tag/Rurl.php | 33 - src/Template/Tag/UrlTag.php | 30 - src/Text.php | 420 ---- src/Text/HTML/Filter.php | 446 ---- src/Text/Lang.php | 182 -- src/Text/UTF8.php | 1827 ----------------- src/TranslatableTrait.php | 40 - src/Translator/Adapter/Generic.php | 155 -- src/Translator/ITranslatorAdapter.php | 20 - src/Translator/Translator.php | 171 -- src/Utils.php | 379 ---- src/Views.php | 99 - src/Views/AbstractCollectionView.php | 7 - src/Views/ItemBinary.php | 61 - src/Views/ItemCollectionView.php | 108 - src/Views/ItemView.php | 207 -- src/Views/ManyToManyCollectionView.php | 7 - src/Views/ManyToOneCollectionView.php | 167 -- src/bootstrap.php | 376 ---- tests/Cache/ApcuTest.php | 168 -- tests/Cache/BasicTest.php | 21 - tests/Cache/FileTest.php | 153 -- tests/Data/OldPlufModelTest.php | 655 ------ tests/Data/PlufModelMTTest.php | 356 ---- tests/Data/PlufModelRestAPITest.php | 80 - tests/Data/PlufModelTest.php | 358 ---- tests/Data/QueryBuilder/GeoPaginatorTest.php | 100 - tests/Data/QueryBuilder/PaginatorTest.php | 606 ------ .../Data/QueryBuilder/RequestBuilderTest.php | 266 --- tests/Data/RepositoryTest.php | 248 --- tests/Data/SchemaSQLiteTest.php | 163 -- tests/Db/ConnectionTest.php | 374 ---- tests/Db/ExceptionTest.php | 58 - tests/Db/ExpressionTest.php | 553 ----- tests/Db/OracleTest.php | 145 -- tests/Db/PlufDBTest.php | 94 - tests/Db/QueryTest.php | 1629 --------------- tests/Db/RandomTest.php | 113 - tests/Db/db/ConnectionTest.php | 43 - tests/Db/db/PdoSelectTest.php | 20 - tests/Db/db/SelectTest.php | 328 --- tests/Db/db/SelectTest.xml | 7 - tests/Db/db/TransactionTest.php | 288 --- tests/Dispatcher/CounterProcessor.php | 48 - tests/Dispatcher/DispatcherTest.php | 195 -- tests/Dispatcher/PlufDispatcherTest.php | 49 - tests/Dispatcher/RedirectProcessor.php | 28 - tests/Dispatcher/SimpleTextProcessor.php | 16 - tests/Encoder/PlufEncoderTest.php | 141 -- tests/ExceptionWrapper.php | 25 - tests/Graphql/Compiler/ModelRelationTest.php | 160 -- tests/Graphql/Compiler/ModelTest.php | 136 -- tests/Graphql/Compiler/PaginatorTest.php | 101 - tests/Graphql/RenderTest.php | 100 - tests/HTTP/Response/ExceptionTest.php | 42 - tests/HTTP/Response/FileHashCodeTest.php | 72 - tests/HTTP/Response/HashCodeTest.php | 62 - tests/HTTP/Response/TestFile | 1 - tests/HTTP/URLTest.php | 137 -- tests/Logger/LoggerAppenderFileTest.php | 24 - tests/Logger/LoggerTest.php | 33 - tests/Mail/PlufMailTest.php | 46 - tests/Mail/check_on.png | Bin 767 -> 0 bytes tests/Mail/encodedbody1.txt | 32 - tests/Migration/InitTest.php | 132 -- tests/Migration/MtInitTest.php | 139 -- tests/ObjectMappertTest.php | 64 - tests/OptionsTest.php | 114 - tests/PlufCryptTest.php | 38 - tests/PlufResultPrinter.php | 29 - tests/PlufTest.php | 141 -- tests/PlufTestCase.php | 75 - tests/PlufUtilsTest.php | 178 -- tests/Process/Http/AccessLogTest.php | 86 + tests/Process/Http/FileToResponseTest.php | 47 + tests/Process/Http/IfMethodIsTest.php | 214 ++ tests/Process/Http/IfPathAndMethodIsTest.php | 122 ++ tests/Process/Http/IfPathIsTest.php | 98 + .../Http/Mock/ReturnRequestParsedBody.php | 14 + tests/Process/Http/RequestBodyParserTest.php | 109 + tests/Process/Http/assets/sample-1.jpeg | Bin 0 -> 110738 bytes tests/Processors/TenantSingleTest.php | 69 - tests/Processors/TenantTest.php | 141 -- .../Compiler/CompilerBlockTransTest.php | 69 - .../Template/Compiler/MethodCompile2Test.php | 59 - tests/Template/Compiler/MethodCompileTest.php | 115 -- tests/Template/Compiler/tpl-base.html | 5 - .../Compiler/tpl-extends.compiled.html | 8 - tests/Template/Compiler/tpl-extends.html | 6 - tests/Template/Compiler/tpl-test1.html | 1 - tests/Template/Compiler/tpl-test2.html | 1 - .../Template/Compiler/tpl-test3.compiled.html | 5 - tests/Template/Compiler/tpl-test3.html | 3 - tests/Template/Compiler/tpl-test4.html | 3 - tests/Template/Compiler/tpl-test5.html | 1 - .../Compiler/tpl-teststring-modifier.html | 1 - tests/Template/Compiler/tpl-teststring.html | 1 - tests/Template/Compiler/tpl1/tpl-extends.html | 6 - tests/Template/Compiler/tpl2/tpl-base.html | 5 - tests/Template/PlufTemplateTest.php | 55 - tests/Template/Tenant/PlufTemplateTest.php | 99 - tests/Template/Tenant/tpl-domain.html | 1 - tests/Template/Tenant/tpl-id.html | 1 - tests/Template/Tenant/tpl-title.html | 1 - tests/Template/tpl-test1.html | 1 - tests/Text/TextHTMLFilterTest.php | 311 --- tests/conf/config.php | 199 -- tests/conf/views.php | 103 - tests/templates/User/Mail/mail-token.html | 4 - tests/templates/User/Mail/pass-token.html | 4 - 293 files changed, 1583 insertions(+), 38329 deletions(-) delete mode 100644 examples/HelloWord/Module.php delete mode 100644 examples/HelloWord/Processors/HelloWordProcessor.php delete mode 100644 examples/HelloWord/module.json delete mode 100644 examples/HelloWord/urls.php delete mode 100644 examples/NoteBook/Book.php delete mode 100644 examples/NoteBook/Item.php delete mode 100644 examples/NoteBook/Library.php delete mode 100644 examples/NoteBook/Module.php delete mode 100644 examples/NoteBook/Tag.php delete mode 100644 examples/NoteBook/module.json delete mode 100644 examples/NoteBook/url.php delete mode 100644 examples/README delete mode 100755 examples/Relation/ManyToManyOne.php delete mode 100644 examples/Relation/ManyToManyTwo.php delete mode 100755 examples/Relation/Model.php delete mode 100755 examples/Relation/ModelCount.php delete mode 100755 examples/Relation/ModelRecurse.php delete mode 100644 examples/Relation/Module.php delete mode 100755 examples/Relation/RelatedToTestModel.php delete mode 100755 examples/Relation/RelatedToTestModel2.php delete mode 100644 examples/Relation/module.json delete mode 100644 examples/Smallest/Module.php delete mode 100644 examples/Smallest/module.json delete mode 100644 examples/Smallest/url.php delete mode 100755 src/Cache.php delete mode 100755 src/Cache/Apcu.php delete mode 100644 src/Cache/ArrayCache.php delete mode 100755 src/Cache/File.php delete mode 100755 src/Cache/Memcached.php delete mode 100644 src/ConfigTrait.php delete mode 100755 src/Crypt.php delete mode 100644 src/Data/Encoder/JsonEncoder.php delete mode 100644 src/Data/Exception.php delete mode 100644 src/Data/InvalidRelationKeyException.php delete mode 100644 src/Data/Model.php delete mode 100644 src/Data/ModelDescription.php delete mode 100644 src/Data/ModelEncoder.php delete mode 100644 src/Data/ModelProperty.php delete mode 100644 src/Data/ModelUtils.php delete mode 100644 src/Data/Query.php delete mode 100644 src/Data/QueryBuilder.php delete mode 100644 src/Data/QueryBuilder/RequestQueryBuilder.php delete mode 100644 src/Data/Repository.php delete mode 100644 src/Data/Repository/ModelRepository.php delete mode 100644 src/Data/Repository/RelationRepository.php delete mode 100644 src/Data/Schema.php delete mode 100644 src/Data/Schema/MySQLSchema.php delete mode 100644 src/Data/Schema/SQLiteSchema.php delete mode 100755 src/Date.php delete mode 100644 src/Db/Connection.php delete mode 100644 src/Db/Connection/Counter.php delete mode 100644 src/Db/Connection/Dumper.php delete mode 100644 src/Db/Connection/Oracle.php delete mode 100644 src/Db/Connection/Oracle12.php delete mode 100644 src/Db/Connection/PgSQL.php delete mode 100644 src/Db/Connection/Proxy.php delete mode 100644 src/Db/Engine.php delete mode 100644 src/Db/Exception.php delete mode 100644 src/Db/Expression.php delete mode 100644 src/Db/Expression/MySQL.php delete mode 100644 src/Db/Expressionable.php delete mode 100644 src/Db/Query.php delete mode 100644 src/Db/Query/MySQL.php delete mode 100644 src/Db/Query/Oracle.php delete mode 100644 src/Db/Query/Oracle12c.php delete mode 100644 src/Db/Query/OracleAbstract.php delete mode 100644 src/Db/Query/PgSQL.php delete mode 100644 src/Db/Query/SQLite.php delete mode 100644 src/Db/ResultSet.php delete mode 100644 src/DiContainerTrait.php delete mode 100755 src/Dispatcher.php delete mode 100755 src/Encoder.php create mode 100644 src/ExceptionBuilder.php delete mode 100644 src/ExceptionRenderer/Console.php delete mode 100644 src/ExceptionRenderer/HTML.php delete mode 100644 src/ExceptionRenderer/HTMLText.php delete mode 100644 src/ExceptionRenderer/JSON.php delete mode 100644 src/ExceptionRenderer/RendererAbstract.php delete mode 100644 src/FileUtil.php delete mode 100644 src/Graphql.php delete mode 100644 src/Graphql/Compiler.php delete mode 100755 src/HTTP.php delete mode 100644 src/HTTP/Error403.php delete mode 100755 src/HTTP/Error404.php delete mode 100755 src/HTTP/Error500.php delete mode 100755 src/HTTP/Request.php delete mode 100755 src/HTTP/Response.php delete mode 100755 src/HTTP/Response/CommandPassThru.php delete mode 100755 src/HTTP/Response/File.php delete mode 100755 src/HTTP/Response/Forbidden.php delete mode 100755 src/HTTP/Response/Json.php delete mode 100755 src/HTTP/Response/NotAvailable.php delete mode 100755 src/HTTP/Response/NotFound.php delete mode 100644 src/HTTP/Response/PlainText.php delete mode 100755 src/HTTP/Response/Redirect.php delete mode 100755 src/HTTP/Response/RedirectToLogin.php delete mode 100644 src/HTTP/Response/ResumableFile.php delete mode 100755 src/HTTP/Response/ServerError.php delete mode 100644 src/HTTP/ResponseFileBuilder.php delete mode 100755 src/HTTP/URL.php delete mode 100644 src/Logger.php delete mode 100755 src/LoggerAppender/File.php delete mode 100755 src/LoggerAppender/Remote.php delete mode 100644 src/LoggerFormatter.php delete mode 100644 src/LoggerFormatter/Plain.php delete mode 100644 src/LoggerManager.php delete mode 100755 src/Mail.php delete mode 100755 src/Mail/Batch.php delete mode 100755 src/Migration.php delete mode 100644 src/Module.php delete mode 100644 src/ObjectMapper.php delete mode 100644 src/ObjectMapper/ArrayMapper.php delete mode 100644 src/ObjectMapper/RequestMapper.php delete mode 100644 src/ObjectStorage.php delete mode 100644 src/ObjectValidator.php delete mode 100644 src/Options.php delete mode 100644 src/Pluf/Module.php delete mode 100755 src/Pluf/Queue.php delete mode 100755 src/Pluf/Queue/Processor.php delete mode 100644 src/Pluf/README delete mode 100755 src/Pluf/Search.php delete mode 100755 src/Pluf/Search/Occ.php delete mode 100755 src/Pluf/Search/ResultSet.php delete mode 100755 src/Pluf/Search/Stats.php delete mode 100755 src/Pluf/Search/Word.php delete mode 100755 src/Pluf/Session.php delete mode 100644 src/Pluf/Tenant.php delete mode 100644 src/Pluf/module.json delete mode 100755 src/Precondition.php create mode 100644 src/Process/Http/AccessLog.php create mode 100644 src/Process/Http/Exception/InvalidBodyContentException.php create mode 100644 src/Process/Http/FileToResponse.php create mode 100644 src/Process/Http/IfMethodIs.php rename src/{LoggerAppender/Console.php => Process/Http/IfMethodIsDelete.php} (62%) rename src/{Processors/ItemBinaryDownload.php => Process/Http/IfMethodIsGet.php} (63%) rename src/{Template/Tag/RmediaUrl.php => Process/Http/IfMethodIsPost.php} (61%) mode change 100755 => 100644 rename src/{LoggerAppender.php => Process/Http/IfMethodIsPut.php} (63%) create mode 100644 src/Process/Http/IfPathAndMethodIs.php create mode 100644 src/Process/Http/IfPathIs.php create mode 100644 src/Process/Http/RequestBodyParser.php rename src/{Template/Tag/TenantTag.php => Process/Http/ResourceNotFoundException.php} (58%) create mode 100644 src/Process/Http/ResponseBodyEncoder.php delete mode 100644 src/Processor.php delete mode 100644 src/ProcessorAdaptor.php delete mode 100644 src/Processors/DispatcherSignals.php delete mode 100644 src/Processors/ExceptionProcessor.php delete mode 100644 src/Processors/GraphqlRender.php delete mode 100644 src/Processors/HttpResponseEncoder.php delete mode 100644 src/Processors/ItemBinaryUpload.php delete mode 100644 src/Processors/ItemCollectionCreate.php delete mode 100644 src/Processors/ItemCollectionDelete.php delete mode 100644 src/Processors/ItemCollectionRead.php delete mode 100644 src/Processors/ItemCollectionUpdate.php delete mode 100644 src/Processors/ItemCreate.php delete mode 100644 src/Processors/ItemDelete.php delete mode 100644 src/Processors/ItemRead.php delete mode 100644 src/Processors/ItemUpdate.php delete mode 100755 src/Processors/ReadOnly.php delete mode 100755 src/Processors/Session.php delete mode 100644 src/Processors/TenantProcessor.php delete mode 100755 src/SQL.php delete mode 100755 src/Shortcuts.php delete mode 100755 src/Signal.php delete mode 100755 src/Template.php delete mode 100755 src/Template/Compiler.php delete mode 100755 src/Template/Context.php delete mode 100755 src/Template/Context/Request.php delete mode 100755 src/Template/ContextVars.php delete mode 100755 src/Template/SafeString.php delete mode 100755 src/Template/Tag.php delete mode 100755 src/Template/Tag/APerm.php delete mode 100755 src/Template/Tag/Cfg.php delete mode 100755 src/Template/Tag/Cycle.php delete mode 100755 src/Template/Tag/Firstof.php delete mode 100755 src/Template/Tag/MediaUrl.php delete mode 100755 src/Template/Tag/Messages.php delete mode 100644 src/Template/Tag/Mytag.php delete mode 100755 src/Template/Tag/Now.php delete mode 100755 src/Template/Tag/Regroup.php delete mode 100755 src/Template/Tag/Rurl.php delete mode 100755 src/Template/Tag/UrlTag.php delete mode 100755 src/Text.php delete mode 100755 src/Text/HTML/Filter.php delete mode 100755 src/Text/Lang.php delete mode 100755 src/Text/UTF8.php delete mode 100644 src/TranslatableTrait.php delete mode 100644 src/Translator/Adapter/Generic.php delete mode 100644 src/Translator/ITranslatorAdapter.php delete mode 100644 src/Translator/Translator.php delete mode 100755 src/Utils.php delete mode 100755 src/Views.php delete mode 100644 src/Views/AbstractCollectionView.php delete mode 100644 src/Views/ItemBinary.php delete mode 100644 src/Views/ItemCollectionView.php delete mode 100644 src/Views/ItemView.php delete mode 100644 src/Views/ManyToManyCollectionView.php delete mode 100644 src/Views/ManyToOneCollectionView.php delete mode 100755 src/bootstrap.php delete mode 100755 tests/Cache/ApcuTest.php delete mode 100644 tests/Cache/BasicTest.php delete mode 100755 tests/Cache/FileTest.php delete mode 100644 tests/Data/OldPlufModelTest.php delete mode 100755 tests/Data/PlufModelMTTest.php delete mode 100755 tests/Data/PlufModelRestAPITest.php delete mode 100755 tests/Data/PlufModelTest.php delete mode 100755 tests/Data/QueryBuilder/GeoPaginatorTest.php delete mode 100755 tests/Data/QueryBuilder/PaginatorTest.php delete mode 100755 tests/Data/QueryBuilder/RequestBuilderTest.php delete mode 100644 tests/Data/RepositoryTest.php delete mode 100755 tests/Data/SchemaSQLiteTest.php delete mode 100644 tests/Db/ConnectionTest.php delete mode 100644 tests/Db/ExceptionTest.php delete mode 100644 tests/Db/ExpressionTest.php delete mode 100644 tests/Db/OracleTest.php delete mode 100755 tests/Db/PlufDBTest.php delete mode 100644 tests/Db/QueryTest.php delete mode 100644 tests/Db/RandomTest.php delete mode 100644 tests/Db/db/ConnectionTest.php delete mode 100644 tests/Db/db/PdoSelectTest.php delete mode 100644 tests/Db/db/SelectTest.php delete mode 100644 tests/Db/db/SelectTest.xml delete mode 100644 tests/Db/db/TransactionTest.php delete mode 100644 tests/Dispatcher/CounterProcessor.php delete mode 100755 tests/Dispatcher/DispatcherTest.php delete mode 100755 tests/Dispatcher/PlufDispatcherTest.php delete mode 100644 tests/Dispatcher/RedirectProcessor.php delete mode 100644 tests/Dispatcher/SimpleTextProcessor.php delete mode 100755 tests/Encoder/PlufEncoderTest.php delete mode 100644 tests/ExceptionWrapper.php delete mode 100755 tests/Graphql/Compiler/ModelRelationTest.php delete mode 100755 tests/Graphql/Compiler/ModelTest.php delete mode 100755 tests/Graphql/Compiler/PaginatorTest.php delete mode 100755 tests/Graphql/RenderTest.php delete mode 100644 tests/HTTP/Response/ExceptionTest.php delete mode 100644 tests/HTTP/Response/FileHashCodeTest.php delete mode 100755 tests/HTTP/Response/HashCodeTest.php delete mode 100644 tests/HTTP/Response/TestFile delete mode 100755 tests/HTTP/URLTest.php delete mode 100644 tests/Logger/LoggerAppenderFileTest.php delete mode 100644 tests/Logger/LoggerTest.php delete mode 100755 tests/Mail/PlufMailTest.php delete mode 100755 tests/Mail/check_on.png delete mode 100755 tests/Mail/encodedbody1.txt delete mode 100644 tests/Migration/InitTest.php delete mode 100644 tests/Migration/MtInitTest.php delete mode 100644 tests/ObjectMappertTest.php delete mode 100644 tests/OptionsTest.php delete mode 100755 tests/PlufCryptTest.php delete mode 100644 tests/PlufResultPrinter.php delete mode 100755 tests/PlufTest.php delete mode 100644 tests/PlufTestCase.php delete mode 100755 tests/PlufUtilsTest.php create mode 100644 tests/Process/Http/AccessLogTest.php create mode 100644 tests/Process/Http/FileToResponseTest.php create mode 100644 tests/Process/Http/IfMethodIsTest.php create mode 100644 tests/Process/Http/IfPathAndMethodIsTest.php create mode 100644 tests/Process/Http/IfPathIsTest.php create mode 100644 tests/Process/Http/Mock/ReturnRequestParsedBody.php create mode 100644 tests/Process/Http/RequestBodyParserTest.php create mode 100644 tests/Process/Http/assets/sample-1.jpeg delete mode 100644 tests/Processors/TenantSingleTest.php delete mode 100644 tests/Processors/TenantTest.php delete mode 100755 tests/Template/Compiler/CompilerBlockTransTest.php delete mode 100755 tests/Template/Compiler/MethodCompile2Test.php delete mode 100755 tests/Template/Compiler/MethodCompileTest.php delete mode 100755 tests/Template/Compiler/tpl-base.html delete mode 100755 tests/Template/Compiler/tpl-extends.compiled.html delete mode 100755 tests/Template/Compiler/tpl-extends.html delete mode 100755 tests/Template/Compiler/tpl-test1.html delete mode 100755 tests/Template/Compiler/tpl-test2.html delete mode 100755 tests/Template/Compiler/tpl-test3.compiled.html delete mode 100755 tests/Template/Compiler/tpl-test3.html delete mode 100755 tests/Template/Compiler/tpl-test4.html delete mode 100755 tests/Template/Compiler/tpl-test5.html delete mode 100755 tests/Template/Compiler/tpl-teststring-modifier.html delete mode 100755 tests/Template/Compiler/tpl-teststring.html delete mode 100755 tests/Template/Compiler/tpl1/tpl-extends.html delete mode 100755 tests/Template/Compiler/tpl2/tpl-base.html delete mode 100755 tests/Template/PlufTemplateTest.php delete mode 100755 tests/Template/Tenant/PlufTemplateTest.php delete mode 100644 tests/Template/Tenant/tpl-domain.html delete mode 100755 tests/Template/Tenant/tpl-id.html delete mode 100644 tests/Template/Tenant/tpl-title.html delete mode 100755 tests/Template/tpl-test1.html delete mode 100755 tests/Text/TextHTMLFilterTest.php delete mode 100644 tests/conf/config.php delete mode 100755 tests/conf/views.php delete mode 100644 tests/templates/User/Mail/mail-token.html delete mode 100644 tests/templates/User/Mail/pass-token.html diff --git a/.gitignore b/.gitignore index f46b2670..f2c64e96 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,5 @@ clover.xml /build/ # netbeans -/nbproject/private/ \ No newline at end of file +/nbproject/private/ +/.build/ diff --git a/composer.json b/composer.json index b12de91e..c61958d3 100644 --- a/composer.json +++ b/composer.json @@ -17,22 +17,13 @@ ], "minimum-stability" : "stable", "require" : { - "pluf/http2" : "6.x", - "pear/mail" : "^1.3", - "pear/mail_mime" : "^1.10.0", - "guzzlehttp/guzzle" : "^6.2", - "webonyx/graphql-php" : "^0.12.5", - "phayes/geophp" : "^1.2", - "psr/log" : "~1.1" + "pluf/http2" : "7.x", + "pluf/scion" : "7.x", + "pluf/log" : "7.x", + "pluf/orm" : "7.x" }, "require-dev" : { - "phpunit/phpunit" : "~7.5", - "ockcyp/covers-validator" : "~1.0", - "squizlabs/php_codesniffer" : "~3.3", - "slevomat/coding-standard" : "~4.5", - "mediawiki/mediawiki-codesniffer" : "~23.0", - "phpstan/phpstan" : "~0.10.6", - "phpunit/dbunit" : "*" + "phpunit/phpunit" : "^9" }, "support" : { "wiki" : "https://github.com/pluf/core/wiki", @@ -51,18 +42,11 @@ "autoload" : { "psr-4" : { "Pluf\\" : "src/" - }, - "files" : [ - "src/bootstrap.php" - ] + } }, "autoload-dev" : { "psr-4" : { - "Pluf\\NoteBook\\" : "examples/NoteBook", - "Pluf\\Smallest\\" : "examples/Smallest", - "Pluf\\HelloWord\\" : "examples/HelloWord", - "Pluf\\Relation\\" : "examples/Relation", - "Pluf\\Test\\" : "tests/" + "Pluf\\Tests\\" : "tests/" }, "psr-0" : { "test" : "" diff --git a/examples/HelloWord/Module.php b/examples/HelloWord/Module.php deleted file mode 100644 index 4e65a2e2..00000000 --- a/examples/HelloWord/Module.php +++ /dev/null @@ -1,17 +0,0 @@ -setBody('Hello Word'); - return $response; - } -} - diff --git a/examples/HelloWord/module.json b/examples/HelloWord/module.json deleted file mode 100644 index 44715bb1..00000000 --- a/examples/HelloWord/module.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "Smallest", - "title": "An empty module" -} \ No newline at end of file diff --git a/examples/HelloWord/urls.php b/examples/HelloWord/urls.php deleted file mode 100644 index 043864a8..00000000 --- a/examples/HelloWord/urls.php +++ /dev/null @@ -1,11 +0,0 @@ - '#^/HelloWord$#', - 'processors' => [ - HelloWordProcessor::class - ] - ] -]; \ No newline at end of file diff --git a/examples/NoteBook/Book.php b/examples/NoteBook/Book.php deleted file mode 100644 index a966f769..00000000 --- a/examples/NoteBook/Book.php +++ /dev/null @@ -1,140 +0,0 @@ -. - */ -namespace Pluf\NoteBook; - -use Pluf\Data\Schema; -use Pluf\Db\Expression; - -/** - * Data model of a book - * - * @author maso - * @Model( - * table='notebook_book', - * title='Book', - * multitinant=true, - * mapped=false, - * ) - */ -class Book extends \Pluf\Data\Model -{ - - /** - * - * {@inheritdoc} - * @see \Pluf\Data\Model::init() - */ - function init() - { - $this->_a['table'] = 'notebook_book'; - $this->_a['verbose'] = 'Note book'; - $this->_a['cols'] = [ - // It is mandatory to have an "id" column. - 'id' => [ - // DB - 'type' => Schema::SEQUENCE, - 'primary' => true, - 'blank' => true, - // View - 'editable' => false, - 'readable' => true - ], - 'title' => [ - 'type' => Schema::VARCHAR, - 'size' => 100, - 'blank' => false, - 'readable' => true - ], - 'description' => [ - 'type' => 'Text', - 'blank' => false, - 'editable' => true, - 'readable' => true - ], - 'creation_dtime' => [ - 'type' => 'Datetime', - 'blank' => true, - 'editable' => false, - 'readable' => true - ], - 'items' => [ - 'type' => Schema::ONE_TO_MANY, - // 'joinProperty' => 'id', - 'inverseJoinModel' => Item::class, - 'inverseJoinProperty' => 'book_id' - ], - 'tags' => [ - 'type' => Schema::MANY_TO_MANY, - 'joinProperty' => 'id', - 'inverseJoinModel' => Tag::class, - 'inverseJoinProperty' => 'id' - ] - ]; - } - - /** - * - * {@inheritdoc} - * @see \Pluf\Data\Model::preSave() - */ - function preSave($create = false) - { - if ($this->id == '') { - $this->creation_dtime = gmdate('Y-m-d H:i:s'); - } - } - - public function loadViews(): array - { - return [ - 'nonEmpty' => [ - 'join' => [ - [ - 'joinProperty' => 'id', - 'inverseJoinModel' => Item::class, - 'inverseJoinProperty' => 'book_id', - 'alias' => 'item', - 'type' => Schema::INNER_JOIN - ] - ], - 'group' => [ - 'item.book_id' - ], - 'having' => [ - new Expression('count(*) > 0') - ] - ], - 'empty' => [ - 'join' => [ - [ - 'joinProperty' => 'items', - 'alias' => 'item' - ] - ], - 'where' => [ - [ - 'item.book_id', - null - ] - ] - ] - ]; - } -} diff --git a/examples/NoteBook/Item.php b/examples/NoteBook/Item.php deleted file mode 100644 index cbfffd6b..00000000 --- a/examples/NoteBook/Item.php +++ /dev/null @@ -1,92 +0,0 @@ -. - */ - -/** - * - * @author maso - * - */ -class Item extends \Pluf\Data\Model -{ - - function init() - { - $this->_a['table'] = 'notebook_item'; - $this->_a['verbose'] = 'Note item'; - $this->_a['cols'] = [ - // It is mandatory to have an "id" column. - 'id' => [ - 'type' => 'Sequence', - // It is automatically added. - 'blank' => true, - 'editable' => false, - 'readable' => true - ], - 'title' => [ - 'type' => 'Text', - 'blank' => false, - 'editable' => true, - 'readable' => true - ], - 'body' => [ - 'type' => 'Text', - 'blank' => false, - 'editable' => true, - 'readable' => true - ], - 'creation_dtime' => [ - 'type' => 'Datetime', - 'blank' => true, - 'editable' => false, - 'readable' => true - ], - 'book_id' => [ - 'type' => Schema::FOREIGNKEY, - 'columne' => 'book_id', - 'blank' => true, - 'editable' => true, - 'readable' => true - ], - 'book' => [ - 'type' => Schema::MANY_TO_ONE, - 'joinProperty' => 'book_id', - 'inverseJoinModel' => Book::class, - 'inverseJoinProperty' => 'id', - // do not create columne - 'mapped' => true - ] - ]; - } - - /** - * - * {@inheritdoc} - * @see \Pluf\Data\Model::preSave() - */ - function preSave($create = false) - { - if ($this->id == '') { - $this->creation_dtime = gmdate('Y-m-d H:i:s'); - } - } -} diff --git a/examples/NoteBook/Library.php b/examples/NoteBook/Library.php deleted file mode 100644 index ecde9f97..00000000 --- a/examples/NoteBook/Library.php +++ /dev/null @@ -1,58 +0,0 @@ -. - */ -namespace Pluf\NoteBook; - -use Pluf\Data\Model; -use Pluf\Data\Schema; - -/** - * Saves library location - * - * @author maso - * - */ -class Library extends Model -{ - - function init() - { - $this->_a['table'] = 'notebook_library'; - $this->_a['cols'] = array( - 'id' => array( - 'type' => Schema::SEQUENCE, - 'blank' => true - ), // It is automatically added. - 'title' => array( - 'type' => Schema::VARCHAR, - 'blank' => false, - 'size' => 100, - 'verbose' => 'Title of the item' - ), - 'location' => array( - 'type' => Schema::GEOMETRY, - 'blank' => true, - 'is_null' => false, - 'editable' => false, - 'readable' => true - ) - ); - } -} - - diff --git a/examples/NoteBook/Module.php b/examples/NoteBook/Module.php deleted file mode 100644 index 93cf4355..00000000 --- a/examples/NoteBook/Module.php +++ /dev/null @@ -1,17 +0,0 @@ -_a['table'] = 'notebook_tags'; - $this->_a['verbose'] = 'Note tag'; - $this->_a['cols'] = [ - // It is mandatory to have an "id" column. - 'id' => [ - 'type' => Schema::SEQUENCE, - 'primary' => true, - // It is automatically added. - 'blank' => true, - 'editable' => false, - 'readable' => true - ], - 'title' => [ - 'type' => Schema::VARCHAR, - 'size' => 100, - 'blank' => false, - 'editable' => false, - 'readable' => true - ], - 'books' => [ - 'type' => Schema::MANY_TO_MANY, - 'joinProperty' => 'id', - 'inverseJoinModel' => Book::class, - 'inverseJoinProperty' => 'id' - ] - ]; - } -} - diff --git a/examples/NoteBook/module.json b/examples/NoteBook/module.json deleted file mode 100644 index 6d7b380e..00000000 --- a/examples/NoteBook/module.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Note", - "title": "A test module with model", - "model": [ - "\\Pluf\\NoteBook\\Book", - "\\Pluf\\NoteBook\\Item", - "\\Pluf\\NoteBook\\Tag", - "\\Pluf\\NoteBook\\Library" - ], - "init": { - "\\Pluf\\NoteBook\\Book": [ - { - "title": "first book" - }, - { - "title": "second book" - } - ] - } -} \ No newline at end of file diff --git a/examples/NoteBook/url.php b/examples/NoteBook/url.php deleted file mode 100644 index b9938715..00000000 --- a/examples/NoteBook/url.php +++ /dev/null @@ -1,3 +0,0 @@ - \Pluf\XXX - -For exmaple: - - NoteBook => \Pluf\NoteBook - Smallest => \Pluf\Smallest - - -NOTE: thies applications will be loaded in test and debug environment automatically. \ No newline at end of file diff --git a/examples/Relation/ManyToManyOne.php b/examples/Relation/ManyToManyOne.php deleted file mode 100755 index e321711e..00000000 --- a/examples/Relation/ManyToManyOne.php +++ /dev/null @@ -1,60 +0,0 @@ -. - */ -namespace Pluf\Relation; - -use Pluf\Data\Schema; - -/** - * - * @author maso - * - */ -class ManyToManyOne extends \Pluf\Data\Model -{ - - /** - * - * {@inheritdoc} - * @see \Pluf\Data\Model::init() - */ - function init() - { - $this->_a['table'] = 'test_manytomanyone'; - $this->_a['cols'] = [ - 'id' => [ - 'type' => Schema::SEQUENCE, - 'nullable' => true - ], - 'one' => [ - 'type' => Schema::VARCHAR, - 'nullable' => false, - 'size' => 100 - ], - /* - * Relations: - * There is n*n relation with ManyToManyTwo - */ - 'twos' => [ - 'type' => Schema::MANY_TO_MANY, - 'inverseJoinModel' => ManyToManyTwo::class, - ], - ]; - } -} diff --git a/examples/Relation/ManyToManyTwo.php b/examples/Relation/ManyToManyTwo.php deleted file mode 100644 index af183ece..00000000 --- a/examples/Relation/ManyToManyTwo.php +++ /dev/null @@ -1,59 +0,0 @@ -. - */ -namespace Pluf\Relation; - -use Pluf\Data\Schema; - -/** - * - * @author maso - * - */ -class ManyToManyTwo extends \Pluf\Data\Model -{ - - /** - * - * {@inheritdoc} - * @see \Pluf\Data\Model::init() - */ - function init() - { - $this->_a['table'] = 'test_manytomanytwo'; - $this->_a['cols'] = [ - 'id' => [ - 'type' => Schema::SEQUENCE, - 'nullable' => true - ], - 'two' => [ - 'type' => Schema::VARCHAR, - 'nullable' => false, - 'size' => 100 - ], - /* - * Relations: - * There is n*n relation with ManyToManyOne - */ - 'ones' => [ - 'type' => Schema::MANY_TO_MANY, - 'inverseJoinModel' => ManyToManyOne::class - ] - ]; - } -} diff --git a/examples/Relation/Model.php b/examples/Relation/Model.php deleted file mode 100755 index 67d55bbe..00000000 --- a/examples/Relation/Model.php +++ /dev/null @@ -1,118 +0,0 @@ -. - */ -namespace Pluf\Relation; - -use Pluf\Data\Schema; - -/** - * - * @author maso - * - */ -class Model extends \Pluf\Data\Model -{ - - /** - * - * {@inheritdoc} - * @see \Pluf\Data\Model::init() - */ - function init() - { - $this->_a['table'] = 'test_model'; - $this->_a['cols'] = [ - 'id' => [ - 'type' => Schema::SEQUENCE, - 'nullable' => true - ], - 'title' => [ - 'type' => Schema::VARCHAR, - 'nullable' => false, - 'size' => 100 - ], - 'description' => [ - 'type' => Schema::TEXT, - 'nullable' => true - ], - 'degr' => [ - 'type' => Schema::FLOAT, - 'nullable' => true - ], - - /* - * Relations - */ - 'related' => array( - 'type' => Schema::ONE_TO_MANY, - 'inverseJoinModel' => RelatedToTestModel::class, - 'inverseJoinProperty' => 'testmodel_id' - ), - - 'first_rttm' => array( - 'type' => Schema::ONE_TO_MANY, - 'inverseJoinModel' => RelatedToTestModel2::class, - 'inverseJoinProperty' => 'testmodel_1' - ), - 'second_rttm' => array( - 'type' => Schema::ONE_TO_MANY, - 'inverseJoinModel' => RelatedToTestModel2::class, - 'inverseJoinProperty' => 'testmodel_2' - ), - - 'testmodel' => array( - 'type' => Schema::ONE_TO_MANY, - 'inverseJoinModel' => RelatedToTestModel::class, - 'inverseJoinProperty' => 'testmodel', - ) - ]; - } - - /** - * - * {@inheritdoc} - * @see \Pluf\Data\Model::loadViews() - */ - public function loadViews(): array - { - return [ - 'simple' => [ - 'select' => 'testmodel_id, title, description' - ], - '__unique__' => [ - 'select' => 'testmodel_id' - ] - ]; - } - - /** - * - * {@inheritdoc} - * @see \Pluf\Data\Model::loadIndexes() - */ - public function loadIndexes(): array - { - return [ - 'title' => [ - 'col' => 'title', - 'type' => 'normal' - ] - ]; - } -} - diff --git a/examples/Relation/ModelCount.php b/examples/Relation/ModelCount.php deleted file mode 100755 index 20826860..00000000 --- a/examples/Relation/ModelCount.php +++ /dev/null @@ -1,60 +0,0 @@ -. - */ -namespace Pluf\Relation; - -use Pluf\Data\Schema; - -/** - * - * @author maso - * - */ -class ModelCount extends \Pluf\Data\Model -{ - - /** - * - * {@inheritdoc} - * @see \Pluf\Data\Model::init() - */ - function init() - { - $this->_a['table'] = 'test_model_count'; - $this->_a['cols'] = array( - 'id' => [ - 'type' => Schema::SEQUENCE, - 'nullable' => true - ], - 'title' => [ - 'type' => Schema::VARCHAR, - 'nullable' => false, - 'size' => 100 - ], - 'description' => [ - 'type' => Schema::TEXT, - 'nullable' => true - ], - 'degr' => [ - 'type' => Schema::FLOAT, - 'nullable' => true - ] - ); - } -} - diff --git a/examples/Relation/ModelRecurse.php b/examples/Relation/ModelRecurse.php deleted file mode 100755 index e3bcbb67..00000000 --- a/examples/Relation/ModelRecurse.php +++ /dev/null @@ -1,59 +0,0 @@ -. - */ -namespace Pluf\Relation; - -use Pluf\Data\Schema; - -class ModelRecurse extends \Pluf\Data\Model -{ - - function init() - { - $this->_a['table'] = 'testmodelrecurse'; - $this->_a['cols'] = [ - 'id' => [ - 'type' => Schema::SEQUENCE, - 'blank' => true - ], - 'title' => [ - 'type' => Schema::VARCHAR, - 'nullable' => false, - 'size' => 100 - ], - 'parent_id' => [ - 'type' => Schema::FOREIGNKEY, - 'nullable' => true, - 'columne' => 'parent_id' - ], - 'parent' => [ - 'type' => Schema::MANY_TO_ONE, - 'joinColumne' => 'parent_id', - 'inverseJoinModel' => ModelRecurse::class, - 'inverseJoinProperty' => 'id' - ], - 'children' => [ - 'type' => Schema::ONE_TO_MANY, - 'mapped' => true, - 'joinProperty' => 'id', - 'inverseJoinModel' => ModelRecurse::class, - 'inverseJoinProperty' => 'parent_id' - ] - ]; - } -} diff --git a/examples/Relation/Module.php b/examples/Relation/Module.php deleted file mode 100644 index e3d9bc27..00000000 --- a/examples/Relation/Module.php +++ /dev/null @@ -1,40 +0,0 @@ -. - */ -namespace Pluf\Relation; - -use Pluf; - -class Module extends \Pluf\Module -{ - - const moduleJsonPath = __DIR__ . '/module.json'; - - const urlsPath = __DIR__ . '/urls.php'; - - /** - * Load relation test module - * - * {@inheritdoc} - * @see \Pluf\Module::init() - */ - public function init(Pluf $bootstrap): void - { - // Do nothing - } -} \ No newline at end of file diff --git a/examples/Relation/RelatedToTestModel.php b/examples/Relation/RelatedToTestModel.php deleted file mode 100755 index 4e972352..00000000 --- a/examples/Relation/RelatedToTestModel.php +++ /dev/null @@ -1,89 +0,0 @@ -. - */ -namespace Pluf\Relation; - -use Pluf\Data\Schema; - -/** - * - * @author maso - * - */ -class RelatedToTestModel extends \Pluf\Data\Model -{ - - /** - * load data model - * - * {@inheritdoc} - * @see \Pluf\Data\Model::init() - */ - function init() - { - $this->_a['table'] = 'relatedtotestmodel'; - $this->_a['cols'] = array( - 'id' => array( - 'type' => Schema::SEQUENCE, - 'nullable' => true - ), // It is automatically added. - 'dummy' => array( - 'type' => Schema::VARCHAR, - 'nullable' => false, - 'size' => 100 - ), - /* - * Relations: - * - * We want to access the foreign key value and the object at - * the same time. So, We add a foreign key and a many to one - * relation on one colemne. - */ - 'testmodel_id' => array( - 'type' => Schema::FOREIGNKEY, - 'nullable' => false, - 'inverseJoinModel' => Model::class, - 'columne' => 'testmodel_id' - ), - 'testmodel' => array( - 'type' => Schema::MANY_TO_ONE, - 'mapped' => true, - 'nullable' => false, - 'joinProperty' => 'testmodel_id', - 'inverseJoinModel' => Model::class, - 'columne' => 'testmodel_id' - ) - ); - } - - /** - * - * {@inheritdoc} - * @see \Pluf\Data\Model::loadIndexes() - */ - public function loadIndexes(): array - { - return array( - 'testmodel_id' => array( - 'type' => 'normal', - 'col' => 'testmodel_id' - ) - ); - } -} - diff --git a/examples/Relation/RelatedToTestModel2.php b/examples/Relation/RelatedToTestModel2.php deleted file mode 100755 index 20336f19..00000000 --- a/examples/Relation/RelatedToTestModel2.php +++ /dev/null @@ -1,60 +0,0 @@ -. - */ -namespace Pluf\Relation; - -use Pluf\Data\Schema; - -class RelatedToTestModel2 extends \Pluf\Data\Model -{ - - /** - * Load data model - * - * {@inheritdoc} - * @see \Pluf\Data\Model::init() - */ - function init() - { - $this->_a['table'] = 'relatedtotestmodel2'; - $this->_a['cols'] = [ - 'id' => [ - 'type' => Schema::SEQUENCE, - 'nullable' => true - ], - 'dummy' => [ - 'type' => Schema::VARCHAR, - 'nullable' => false, - 'size' => 100 - ], - 'testmodel_1' => [ - 'type' => Schema::MANY_TO_ONE, - 'nullable' => false, - 'inverseJoinModel' => Model::class, - 'columne' => 'testmodel_1' - ], - 'testmodel_2' => [ - 'type' => Schema::MANY_TO_ONE, - 'nullable' => false, - 'inverseJoinModel' => Model::class, - 'columne' => 'testmodel_2' - ] - ]; - } -} - diff --git a/examples/Relation/module.json b/examples/Relation/module.json deleted file mode 100644 index 7a1f8a57..00000000 --- a/examples/Relation/module.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "Relation", - "title": "A test module with model", - "model": [ - "\\Pluf\\Relation\\Model", - "\\Pluf\\Relation\\ModelRecurse", - "\\Pluf\\Relation\\ModelCount", - "\\Pluf\\Relation\\RelatedToTestModel", - "\\Pluf\\Relation\\RelatedToTestModel2", - "\\Pluf\\Relation\\ManyToManyOne", - "\\Pluf\\Relation\\ManyToManyTwo" - ] -} \ No newline at end of file diff --git a/examples/Smallest/Module.php b/examples/Smallest/Module.php deleted file mode 100644 index 61fa6f0d..00000000 --- a/examples/Smallest/Module.php +++ /dev/null @@ -1,17 +0,0 @@ - + + + + + src + + + + + + + - - tests/Cache/ - tests/Logger/ - tests/PlufTest.php - tests/OptionsTest.php - tests/PlufUtilsTest.php - tests/PlufCryptTest.php - - - tests/Db/ - - - tests/Data/ - - tests/ObjectMappertTest.php - - - tests/Dispatcher/ - tests/HTTP - tests/Middleware/ - tests/Encoder/ - tests/Template/ - tests/Processors/ - - - tests/Text/ - tests/Mail/ - tests/Migration - tests/Graphql/ + + tests/Process/Http/ - - - - - ./src - - - - - + - - - - - - - - - - - - - diff --git a/src/Cache.php b/src/Cache.php deleted file mode 100755 index f1acf332..00000000 --- a/src/Cache.php +++ /dev/null @@ -1,151 +0,0 @@ -. - */ -namespace Pluf; - -use Pluf; - -/** - * Pluf Cach - * - * مهم‌ترین نیاز در سیستم‌ها کش کردن داده‌هایی است که با استفاده از پردازش - * در سیستم ایجاد می‌شوند. این کار باعث بهبود کارایی سیستم خواهد شد. این - * کلاس ساختار کلی کش را در سیستم تعیین می‌کند. - * - * نکته: شما نباید به صورت مستقیم از این کلاس نمونه ایجاد کنید اما نمونه‌های - * متفاوتی از این کلاس وجود دارد که با روش‌های متفاوتی عمل کش کردن در سیستم - * را پیاده سازی کرده اند. - * - * تعیین مولد مدیریت کش به صورت زیر انجام می‌شود: - * - *

- * cfg['cache_engine'] = 'Apc';
- * 
- * - * There are many types of cache engine implemented by Pluf Cache. Here is - * list of them: - * - * - array - * - apc - * - file - * - memcached - * - * هر داده‌ای که در کش قرار می‌گیرد در یک بازه زمانی معتبر است و بعد از آن - * دور ریخته می‌شود این بازه زمانی به صورت زیر تعیین می‌شود (زمان بر اساس - * ثانیه تعیین می‌شود): - * - *

- * cfg['cache_file_timeout'] = 300;
- * 
- * - * نمونه کد زیر یک کش را گرفته و یک مقدار را در آن ذخیره می‌کند. این - * مقدار در فراخوانی‌های بعد قابل استفاده است. - * - *
- * $cache = new Pluf_Cache::getInstance($options);
- * if (null === ($foo=$cache->get('my-key'))) {
- * $foo = run_complex_operation();
- * $cache->set('my-key', $foo);
- * }
- * return $foo;
- * 
- * - * نکته: مقداری که در کش قرار می‌گیرد باید قابل سریال شده باشد در غیر این صورت - * خطا ایجاد خواهد شد. - * - * NOTE: It is not possible to push a non serialable object into the cache system. - * - * @see http://www.php.net/serialize - */ -abstract class Cache -{ - - use \Pluf\DiContainerTrait; - - protected $timeout = null; - - /** - * Creates new instance of Cache - * - * @return Cache Cache object - */ - public static function getInstance(?Options $options = null): Cache - { - if (! isset($options)) { - $options = Pluf::getConfigByPrifix('cache_', true); - } - $type = $options->engine; - if (! isset($type)) { - $type = 'array'; - } - switch ($type) { - case 'array': - $engine = new Cache\ArrayCache($options->startsWith('array_', true)); - break; - case 'apcu': - $engine = new Cache\Apcu($options->startsWith('apcu_', true)); - break; - case 'file': - $engine = new Cache\File($options->startsWith('file_', true)); - break; - case 'memcached': - $engine = new Cache\Memcached($options->startsWith('memcached_', true)); - break; - default: - throw new Exception('Unsupported cache engine: ' . $options->engine); - } - - return $engine; - } - - /** - * Returns timeout to this class - * - * @return mixed - */ - public function getTimeout() - { - return $this->timeout; - } - - /** - * Set a value in the cache. - * - * @param - * string Key to store the information - * @param - * mixed Value to store - * @param - * int Timeout in seconds (null) - * @return bool Success - */ - public abstract function set($key, $value, $timeout = null); - - /** - * Get value from the cache. - * - * @param - * string Key to get the information - * @param - * mixed Default value to return if cache miss (null) - * @param - * mixed Stored value or default - */ - public abstract function get($key, $default = null); -} diff --git a/src/Cache/Apcu.php b/src/Cache/Apcu.php deleted file mode 100755 index 64135b04..00000000 --- a/src/Cache/Apcu.php +++ /dev/null @@ -1,113 +0,0 @@ -. - */ -namespace Pluf\Cache; - -use Pluf\Options; - -/** - * APC cache - * - * Warning: This extension is considered unmaintained and dead. However, - * the source code for this extension is still available within PECL - * GIT here: http://git.php.net/?p=pecl/caching/apc.git. - * - * You need APC installed on your server for this cache system to - * work. You can install APC with $ sudo pecl install apc - * on most systems. - * - * A special 'cache_apc_keyprefix' can be set to use APC for different - * applications and avoid conflict. Compression is performed at the - * PHP level using the gz(in|de)flate functions. - * - * Example of configuration: - * - *
- * $cfg['cache_engine'] = 'Pluf_Cache_Apc';
- * $cfg['cache_timeout'] = 300;
- * $cfg['cache_apc_keyprefix'] = 'uniqueforapp';
- * $cfg['cache_apc_compress'] = true;
- * 
- * - * @see Pluf_Cache - * @see http://www.php.net/gzdeflate - * @see http://www.php.net/gzinflate - */ -class Apcu extends \Pluf\Cache -{ - - /** - * پیشوندی که به تمام کلیدهای کش اضافه می‌شود. - * این کلید در تنظیم‌های - * سیستم تعیین می‌شود. - */ - private $keyprefix = ''; - - /** - * فشرده کردن داده‌ها را تعیین می‌کند. - * در صورتی که فشرده سازی فعال شود - * یک مقدار سربار محاسباتی داریم اما حجم استفاده شده کاهش پیدا می‌کنه. - */ - private $compress = false; - - /** - * Creates new instance of the class - * - * نمونه ایجاد شده با استفاده از تنظیم‌هایی که در تنظیم‌های سرور تعیین شده\ - * است مقدار دهی و راه اندازی می‌شود. - */ - public function __construct(?Options $options = null) - { - $this->setDefaults($options); - } - - /** - * - * {@inheritdoc} - * @see \Pluf\Cache::set() - */ - public function set($key, $value, $timeout = null) - { - if ($timeout == null) { - $timeout = $this->timeout; - } - $value = serialize($value); - if ($this->compress) { - $value = gzdeflate($value, 9); - } - return apcu_store($this->keyprefix . $key, $value, $timeout); - } - - /** - * - * {@inheritdoc} - * @see \Pluf\Cache::get() - */ - public function get($key, $default = null) - { - $value = apcu_fetch($this->keyprefix . $key); - if ($value === FALSE) { - return $default; - } - if ($this->compress) { - $value = gzinflate($value); - } - return unserialize($value); - } -} diff --git a/src/Cache/ArrayCache.php b/src/Cache/ArrayCache.php deleted file mode 100644 index a7eed087..00000000 --- a/src/Cache/ArrayCache.php +++ /dev/null @@ -1,47 +0,0 @@ -setDefaults($options); - } - - /** - * - * {@inheritdoc} - * @see \Pluf\Cache::set() - */ - public function set($key, $value, $timeout = null) - { - $this->cache[$key] = $value; - } - - /** - * - * {@inheritdoc} - * @see \Pluf\Cache::get() - */ - public function get($key, $default = null) - { - if (! array_key_exists($key, $this->cache)) { - return $default; - } - return $this->cache[$key]; - } -} - diff --git a/src/Cache/File.php b/src/Cache/File.php deleted file mode 100755 index 74bcf5e0..00000000 --- a/src/Cache/File.php +++ /dev/null @@ -1,124 +0,0 @@ -. - */ -namespace Pluf\Cache; - -use Pluf\Options; -use Pluf; - -/** - * A file based cache - * - * در این مدل تمام داده‌هایی که کش می شود در پرونده‌هایی در یک پوشه قرار می‌گیرد. - * این پوشه نیز در تنظیم‌ها به صورت زیر تعیین می‌شود: - * - *

- * cfg['cache_file_folder'] = 'path';
- * 
- * - * تمام زیر پوشه‌هایی که در این مسیر ایجاد می‌شود با استفاده MD5 تعیین نام خواهد شد. - */ -class File extends \Pluf\Cache -{ - - /** - * Mapping key => md5. - * - * @var array - */ - private $keymap = array(); - - private $folder = '/tmp'; - - /** - * Creates new instance of the file cache - * - * @param Options $options - */ - public function __construct(?Options $options = null) - { - $this->setDefaults($options); - } - - /** - * - * {@inheritdoc} - * @see \Pluf\Cache::set() - */ - public function set($key, $value, $timeout = null) - { - $fname = $this->_keyToFile($key); - $dir = dirname($fname); - if (!isset($timeout)) { - $timeout = $this->timeout; - } - if (!isset($timeout)) { - $timeout = 3000; - } - if (! file_exists($dir)) { - mkdir($dir, 0777, true); - } - $expire = $_SERVER['REQUEST_TIME'] + $timeout; - $success = file_put_contents($fname, $expire . "\n" . serialize($value), LOCK_EX); - chmod($fname, 0777); - - return (false === $success) ? false : true; - } - - /** - * - * {@inheritdoc} - * @see \Pluf\Cache::get() - */ - public function get($key, $default = null) - { - $fname = $this->_keyToFile($key); - if (! file_exists($fname)) { - return $default; - } - - $data = file_get_contents($fname); - list ($time, $content) = explode("\n", $data, 2); - - if (isset($this->timeout) && $this->timeout > $_SERVER['REQUEST_TIME'] - $time) { - @unlink($fname); - return $default; - } - - return unserialize($content); - } - - /** - * Convert a key into a path to a file. - * - * @param - * string Key - * @return string Path to file - */ - private function _keyToFile($key) - { - if (isset($this->_keymap[$key])) { - $md5 = $this->_keymap[$key]; - } else { - $md5 = md5($key); - $this->_keymap[$key] = $md5; - } - - return $this->folder . '/' . substr($md5, 0, 2) . '/' . substr($md5, 2, 2) . '/' . substr($md5, 4); - } -} diff --git a/src/Cache/Memcached.php b/src/Cache/Memcached.php deleted file mode 100755 index ac82b9eb..00000000 --- a/src/Cache/Memcached.php +++ /dev/null @@ -1,95 +0,0 @@ -. - */ -namespace Pluf\Cache; - -use Pluf\Options; - -/** - * Cache in memory - * - * A special 'cache_memcached_keyprefix' can be set to use one - * memcached for different applications and avoid conflict. - * - * Example of configuration: - * - *
- * $cfg['cache_engine'] = 'memcached';
- *
- * $cfg['cache_memcached_timeout'] = 300;
- * $cfg['cache_memcached_keyprefix'] = 'uniqueforapp';
- * $cfg['cache_memcached_server'] = 'localhost';
- * $cfg['cache_memcached_port'] = 11211;
- * $cfg['cache_memcached_compress'] = 0; (or MEMCACHE_COMPRESSED)
- * 
- */ -class Memcached extends \Pluf\Cache -{ - private $memcache = null; - - private $keyprefix = ''; - - private $server = 'localhost'; - - private $port = 11211; - - private $compress = 0; - - /** - * Creates new instance of the cache - */ - public function __construct(?Options $options = null) - { - $this->setDefaults($options); - } - - /** - * - * {@inheritdoc} - * @see \Pluf\Cache::set() - */ - public function set($key, $value, $timeout = null) - { - if ($this->memcache) { - if ($timeout == null) { - $timeout = $this->timeout; - } - $this->memcache->set($this->keyprefix . $key, serialize($value), $this->compress, $timeout); - } - } - - /** - * - * {@inheritdoc} - * @see \Pluf\Cache::get() - */ - public function get($key, $default = null) - { - if ($this->memcache) { - $val = $this->memcache->get($this->keyprefix . $key); - if (false === $val) { - return $default; - } else { - return unserialize($val); - } - } else { - return $default; - } - } -} diff --git a/src/ConfigTrait.php b/src/ConfigTrait.php deleted file mode 100644 index 73c7d850..00000000 --- a/src/ConfigTrait.php +++ /dev/null @@ -1,209 +0,0 @@ -readConfig(); - * before using config. - */ -trait ConfigTrait -{ - - /** - * Check this property to see if trait is present in the object. - * - * @var bool - */ - public $_configTrait = true; - - /** - * This property stores config values. - * Use getConfig() method to access its values. - * - * @var array - */ - protected $config = []; - - /** - * Read config file or files and store it in $config property. - * - * Supported formats: - * php - PHP file with $config['foo'] = 'bar' structure - * php-inline - PHP file with return ['foo' => 'bar'] structure - * json - JSON file with {'foo':'bar'} structure - * yaml - YAML file with yaml structure - * - * @param string|array $files - * One or more filenames - * @param string $format - * Optional format for config files - * - * @throws Exception - * - * @return $this - */ - public function readConfig($files = [ - 'config.php' - ], $format = 'php') - { - if (! is_array($files)) { - $files = [ - $files - ]; - } - - $configs = []; - foreach ($files as $file) { - if (! is_readable($file)) { - throw new Exception([ - 'Can not read config file', - 'file' => $file, - 'format' => $format - ]); - } - - $tempConfig = []; - - switch (strtolower($format)) { - case 'php': - $config = null; - require $file; // fills $config variable - $tempConfig = $config; - break; - - case 'php-inline': - $tempConfig = require $file; - break; - - case 'json': - $tempConfig = json_decode(file_get_contents($file), true); - break; - - // case 'yaml': - // // @codeCoverageIgnoreStart - // if (!class_exists(\Symfony\Component\Yaml\Yaml::class)) { - // throw new Exception(['You need Symfony\Yaml repository if you want to parse YAML files']); - // } - // $tempConfig = \Symfony\Component\Yaml\Yaml::parseFile($file); - // // @codeCoverageIgnoreEnd - // break; - } - - if (! is_array($tempConfig)) { - throw new Exception([ - 'File was read but has a bad format', - 'file' => $file, - 'format' => $format - ]); - } - - $configs[] = $tempConfig; - } - - $this->config = array_replace_recursive($this->config, ...$configs); - - return $this; - } - - /** - * Manually set configuration option. - * - * @param string|array $paths - * Path to configuration element to set or array of [path=>value] - * @param mixed $value - * Value to set - * - * @return $this - */ - public function setConfig($paths = [], $value = null) - { - if (! is_array($paths)) { - $paths = [ - $paths => $value - ]; - } - - foreach ($paths as $path => $value) { - $pos = &$this->_lookupConfigElement($path, true); - - if (is_array($pos) && ! empty($pos) && is_array($value)) { - // special treatment for arrays - merge them - $pos = array_merge($pos, $value); - } else { - // otherwise just assign value - $pos = $value; - } - } - - return $this; - } - - /** - * Get configuration element. - * - * @param string $path - * Path to configuration element. - * @param mixed $default_value - * Default value returned if element don't exist - * - * @return mixed - */ - public function getConfig($path, $default_value = null) - { - $pos = &$this->_lookupConfigElement($path, false); - - // path element don't exist - return default value - if ($pos === false) { - return $default_value; - } - - return $pos; - } - - /** - * Internal method to lookup config element by given path. - * - * @param string $path - * Path to navigate to - * @param bool $create_elements - * Should we create elements it they don't exist - * - * @return &pos|false Pointer to element in $this->config or false is element don't exist and $create_elements===false - * Returns false if element don't exist and $create_elements===false - */ - protected function &_lookupConfigElement($path, $create_elements = false) - { - // trick to return false because we need reference here - $false = false; - - $path = explode('/', $path); - $pos = &$this->config; - foreach ($path as $el) { - - // need to return if not is array - // before call array_key_exists and throw error - if (! is_array($pos)) { - return $false; - } - - // create empty element if it doesn't exist - if (! array_key_exists($el, $pos) && $create_elements) { - $pos[$el] = []; - } - // if it still doesn't exist, then just return false (no error) - if (! array_key_exists($el, $pos) && ! $create_elements) { - return $false; - } - - $pos = &$pos[$el]; - } - - return $pos; - } -} diff --git a/src/Crypt.php b/src/Crypt.php deleted file mode 100755 index 8a5edaa8..00000000 --- a/src/Crypt.php +++ /dev/null @@ -1,262 +0,0 @@ -. - */ -namespace Pluf; - -use Pluf; - -/** - * A simple call used in cryptography - * - * Credit Anonymous on http://www.php.net/mcrypt - */ -class Crypt -{ - - public $key = ''; - - /** - * Construct the encryption object. - * - * @param - * string The encryption key ('') - */ - function __construct($key = '') - { - $this->key = $key; - } - - /** - * Encrypt a string with a key. - * - * If the key is not given, $this->key is used. If $this->key is - * empty an exception is raised. - * - * @param - * string String to encode - * @param - * string Encryption key ('') - * @return string Encoded string - */ - function encrypt($string, $key = '') - { - if ($key == '') { - $key = $this->key; - } - if ($key == '') { - throw new Exception('No encryption key provided.'); - } - $result = ''; - $strlen = strlen($string); - $keylen = strlen($key); - for ($i = 0; $i < $strlen; $i ++) { - $char = substr($string, $i, 1); - $keychar = substr($key, ($i % $keylen) - 1, 1); - $char = chr(ord($char) + ord($keychar)); - $result .= $char; - } - $result = base64_encode($result); - return str_replace(array( - '+', - '/', - '=' - ), array( - '-', - '_', - '~' - ), $result); - } - - /** - * Decrypt a string with a key. - * - * If the key is not given, $this->key is used. If $this->key is - * empty an exception is raised. - * - * @param - * string String to decode - * @param - * string Encryption key ('') - * @return string Decoded string - */ - function decrypt($string, $key = '') - { - if ($key == '') { - $key = $this->key; - } - if ($key == '') { - throw new Exception('No encryption key provided.'); - } - $result = ''; - $string = str_replace(array( - '-', - '_', - '~' - ), array( - '+', - '/', - '=' - ), $string); - $string = base64_decode($string); - $strlen = strlen($string); - $keylen = strlen($key); - for ($i = 0; $i < $strlen; $i ++) { - $char = substr($string, $i, 1); - $keychar = substr($key, ($i % $keylen) - 1, 1); - $char = chr(ord($char) - ord($keychar)); - $result .= $char; - } - return $result; - } - - /** - * Dump and sign an object. - * - * If you want to sign a small string, use directly the - * sign/unsign function as compression will not help and you will - * save the overhead of the serialize call. - * - * @param - * mixed Object - * @param - * string Key (null) - * @param - * bool Compress with gzdeflate (false) - * @param - * string Extra key not to use only the secret_key ('') - * @return string Signed string - */ - public static function dumps($obj, $key = null, $compress = false, $extra_key = '') - { - $serialized = serialize($obj); - $is_compressed = false; // Flag for if it's been compressed or not - if ($compress) { - $compressed = gzdeflate($serialized, 9); - if (strlen($compressed) < (strlen($serialized) - 1)) { - $serialized = $compressed; - $is_compressed = true; - } - } - $base64d = Utils::urlsafe_b64encode($serialized); - if ($is_compressed) { - $base64d = '.' . $base64d; - } - if ($key === null) { - $key = Pluf::f('secret_key'); - } - return self::sign($base64d, $key . $extra_key); - } - - /** - * Reverse of dumps, throw an Exception in case of bad signature. - * - * @param - * string Signed key - * @param - * string Key (null) - * @param - * string Extra key ('') - * @return mixed The dumped signed object - */ - public static function loads($s, $key = null, $extra_key = '') - { - if ($key === null) { - $key = Pluf::f('secret_key'); - } - $base64d = self::unsign($s, $key . $extra_key); - $decompress = false; - if ($base64d[0] == '.') { - // It's compressed; uncompress it first - $base64d = substr($base64d, 1); - $decompress = true; - } - $serialized = Utils::urlsafe_b64decode($base64d); - if ($decompress) { - $serialized = gzinflate($serialized); - } - return unserialize($serialized); - } - - /** - * Sign a string. - * - * If the key is not provided, it will use the secret_key - * available in the configuration file. - * - * The signature string is safe to use in URLs. So if the string to - * sign is too, you can use the signed string in URLs. - * - * @param - * string The string to sign - * @param - * string Optional key (null) - * @return string Signed string - */ - public static function sign($value, $key = null) - { - if ($key === null) { - $key = Pluf::f('secret_key'); - } - return $value . '.' . self::base64_hmac($value, $key); - } - - /** - * Unsign a value. - * - * It will throw an exception in case of error in the process. - * - * @return string Signed string - * @param - * string Optional key (null) - * @param - * string The string - */ - public static function unsign($signed_value, $key = null) - { - if ($key === null) { - $key = Pluf::f('secret_key'); - } - $compressed = ($signed_value[0] == '.') ? '.' : ''; - if ($compressed) { - $signed_value = substr($signed_value, 1); - } - if (false === strpos($signed_value, '.')) { - throw new Exception('Missing signature (no . found in value).'); - } - list ($value, $sig) = explode('.', $signed_value, 2); - if (self::base64_hmac($compressed . $value, $key) == $sig) { - return $compressed . $value; - } else { - throw new Exception(sprintf('Signature failed: "%s".', $sig)); - } - } - - /** - * Calculate the URL safe base64 encoded SHA1 hmac of a string. - * - * @param - * string The string to sign - * @param - * string The key - * @return string The signature - */ - public static function base64_hmac($value, $key) - { - return Utils::urlsafe_b64encode(hash_hmac('sha1', $value, $key, true)); - } -} diff --git a/src/Data/Encoder/JsonEncoder.php b/src/Data/Encoder/JsonEncoder.php deleted file mode 100644 index fbed509c..00000000 --- a/src/Data/Encoder/JsonEncoder.php +++ /dev/null @@ -1,15 +0,0 @@ -. - */ -namespace Pluf\Data; - -use Pluf\Exception; - -/** - * Invalid Relation Key - * - * If you try to get a related object with wrong key/name, then this exception - * will be thrown. - * - * @author maso - * - */ -class InvalidRelationKeyException extends Exception -{ - - // TODO: add error code - public function __construct($from, $to, $relationName) - { - parent::__construct('Invalid relation name from:' . $from . ', to:' . $to . ', relation:' . $relationName); - $this->from = $from; - $this->to = $to; - $this->relation = $relationName; - } -} - diff --git a/src/Data/Model.php b/src/Data/Model.php deleted file mode 100644 index dccf517c..00000000 --- a/src/Data/Model.php +++ /dev/null @@ -1,1232 +0,0 @@ -. - */ -namespace Pluf\Data; - -use JsonSerializable; -use Pluf; - -class Model implements JsonSerializable -{ - - // function __construct($pk = null, $values = array()){} - - // //----------------------------------------------------------- - // // Old API - // //----------------------------------------------------------- - // public function init(): void {} - // public function getRelationKeysToModel($model, $type): array {} - // public function getData(): array {} - // public function setAssoc(Model $model, ?string $assocName = null) {} - // public function delAssoc(Model $model, ?string $assocName = null) {} - // public function batchAssoc($model_name, $ids) {} - // public function getOne($p = array()): ?Model {} - // public function getList($p = array()) : array{} - // public function getCount($p = array()) {} - // public function getRelated($model, $method = null, $p = array()) {} - // public function update($where = '') {} - // public function create($raw = false) {} - // public function delete() {} - // public function setFromFormData($cleaned_values) {} - // public function isAnonymous() {} - // public function getSchema() {} - - // public function getView(string $name): array {} - // public function setView(string $name, array $view): void {} - // public function hasView(?string $name = null): bool {} - // public function getIndexes(): array {} - - /** - * Tenant field - * - * This field is added to model in the multi-tenancy mode automaticlly. - * - * @var array - */ - protected $tenant_field = array( - 'type' => Schema::FOREIGNKEY, - 'model' => '\\Pluf\\Pluf\\Tenant', - 'blank' => false, - 'relate_name' => 'tenant', - 'editable' => false, - 'readable' => false, - 'graphql_field' => false - ); - - public $_model = __CLASS__; - - /** - * Store the attributes of the model. - * To minimize pollution of the - * property space, all the attributes are stored in this array. - * - * Description of the keys: - * 'multitenant: Determines possibility of define the model in each tenant separately - * 'mapped': Determines that the model is a mapped model of another model. A mapped model have not a separate table. - * 'table': The table in which the model is stored. - * 'model': The name of the model. - * 'cols': The definition of the columns. - * 'idx': The definition of the indexes. - * 'views': The definition of the views. - * 'verbose': The verbose name of the model. - */ - public $_a = array( - 'multitenant' => true, - 'mapped' => false, - 'table' => 'model', - 'model' => '\Pluf\Data\Model', - 'cols' => array(), - 'idx' => array(), - 'views' => array() - ); - - /** - * Storage of the data. - * - * The object data are stored in an associative array. Each key - * corresponds to a column and stores a Pluf_DB_Field_* variable. - */ - public $_data = array(); - - /** - * Storage cached data for methods_get - */ - public $_cache = array(); - - // We should use a global cache. - - /** - * List of the foreign keys. - * - * Set by the init() method from the definition of the columns. - */ - public $_fk = array(); - - /** - * Methods available, this array is dynamically populated by init - * method. - */ - public $_m = array( - 'list' => array(), // get_*_list methods - 'many' => array(), // many to many - 'get' => array(), // foreign keys - 'extra' => array() - ); - - // added by some fields - function __construct($pk = null, $values = array()) - { - // --> - $this->_model = get_class($this); - $this->_a['model'] = $this->_model; - - $this->_a['multitenant'] = true; - $this->_init((int) $pk > 0); - if ((int) $pk > 0) { - $this->get($pk); // Should not have a side effect - } - } - - /** - * Load and init the model - */ - function _init() - { - // $engine = $this->getEngine(); - if (ModelUtils::loadFromCache($this)) { - return; - } - - // put in catch temprory - $this->init(); - $this->_setupMultitenantFields(); - - foreach ($this->_a['cols'] as $col => $description) { - - // $field = new $val['type']('', $col); - // $col_lower = strtolower($col); - - // if ($description['type'] === Engine::FOREIGNKEY) { - // $this->_m['get']['get_' . $col_lower] = array( - // $description['model'], - // $col - // ); - // $this->_cache['fk'][$col] = Engine::FOREIGNKEY; - // $this->_fk[$col] = Engine::FOREIGNKEY; - // /* - // * TODO: maso, 2018: this model will replace the old one in the - // * next major version - // */ - // if (array_key_exists('name', $description)) { - // $this->_m['get']['get_' . $description['name']] = $this->_m['get']['get_' . $col_lower]; - // } - // } - - // if ($description['type'] === Engine::MANY_TO_MANY) { - // $this->_m['list']['get_' . $col_lower . '_list'] = $description['model']; - // $this->_m['many'][$description['model']] = Engine::MANY_TO_MANY; - // /* - // * TODO: maso, 2018: this model will replace the old one in the - // * next major version - // */ - // if (array_key_exists('name', $description)) { - // $this->_m['list']['get_' . $description['name'] . '_list'] = $description['model']; - // } - // } - - if (array_key_exists('defaultValue', $description)) { - $this->_data[$col] = $description['defaultValue']; - } - } - - // $this->_setupAutomaticListMethods(Engine::FOREIGNKEY); - // $this->_setupAutomaticListMethods(Engine::MANY_TO_MANY); - - ModelUtils::putModelToCache($this); - } - - // /** - // * Retrieve key relationships of a given model. - // * - // * @param string $model - // * @param string $type - // * Relation Engine::FOREIGNKEY or Engine::MANY_TO_MANY - // * @return array Key relationships. - // */ - // public function getRelationKeysToModel($model, $type) - // { - // $keys = array(); - // foreach ($this->_a['cols'] as $col => $description) { - // if (isset($description['model']) && $model === $description['model']) { - // // $field = new $val['type'](); - // if ($type === $description['type']) { - // $keys[$col] = $description; - // } - // } - // } - // return $keys; - // } - - // /** - // * Get the foreign keys relating to a given model. - // * - // * @deprecated Use {@link self::getRelationKeysToModel()} instead. - // * @param - // * string Model - // * @return array Foreign keys - // */ - // function getForeignKeysToModel($model) - // { - // return $this->getRelationKeysToModel($model, Engine::FOREIGNKEY); - // } - - /** - * Get the raw data of the object. - * - * For the many to many relations, the value is an array of ids. - * - * @return array Associative array of the data. - */ - function getData() - { - foreach ($this->_a['cols'] as $col => $description) { - // $field = new $val['type'](); - if ($description['type'] == Schema::MANY_TO_MANY) { - $this->_data[$col] = []; - // XXX: maso, 2018: do not load many to many relation if is not required - $method = 'get_' . strtolower($col) . '_list'; - foreach ($this->$method() as $item) { - $this->_data[$col][] = $item->id; - } - } - } - return $this->_data; - } - - // /** - // * Bulk association of models to the current one. - // * - // * @param - // * string Model name - // * @param - // * array Ids of Model name - // * @return bool Success - // */ - // function batchAssoc($model_name, $ids) - // { - // $currents = $this->getRelated($model_name); - // foreach ($currents as $cur) { - // $this->delAssoc($cur); - // } - // foreach ($ids as $id) { - // $m = new $model_name($id); - // if ($m->id == $id) { - // $this->setAssoc($m); - // } - // } - // return true; - // } - - // /** - // * Get the table of the model. - // * - // * Avoid doing the concatenation of the prefix and the table - // * manually. - // * - // * @deprecated - // */ - // function getSqlTable() - // { - // return $this->getEngine() - // ->getSchema() - // ->getTableName($this); - // } - - /** - * Overloading of the get method. - * - * @param - * string Property to get - */ - function __get($prop) - { - if (array_key_exists($prop, $this->_a['cols'])) { - if (isset($this->_data[$prop])) { - return $this->_data[$prop]; - } - return null; - } - - return $this->__call($prop, array()); - } - - /** - * Overloading of the set method. - * - * @param - * string Property to set - * @param - * mixed Value to set - */ - function __set($prop, $val) - { - $md = ModelDescription::getInstance($this); - $property = $md->$prop; - if (isset($property)) { - $this->_data[$prop] = $val; - // Set reference attribute - if ($property->type == Schema::MANY_TO_ONE) { - if (isset($property->joinProperty)) { - $jp = $property->joinProperty; - $this->_data[$jp] = $val; - } - } - return; - } - throw new Exception([ - 'message' => 'Property {name} not found in model {model}.', - 'name' => $prop, - 'model' => get_class($this) - ]); - } - - /** - * Overloading of the method call. - * - * @param - * string Method - * @param - * array Arguments - */ - function __call($method, $args) - { - $match = []; - // Schema::MANY_TO_MANY - // SCHEMA::ONE_TO_MANY - if (preg_match('#^get_(?P.+)_list$#', $method, $match) || // - preg_match('#^get_(?P.+)$#', $method, $match)) { - $propertyName = $match['property']; - $md = ModelDescription::getInstance($this); - $property = $md->$propertyName; - - // create query from args - if (isset($args[0])) { - $queryValue = $args[0]; - } else { - $queryValue = []; - } - if ($queryValue instanceof \Pluf\Db\Query) { - $query = $queryValue; - } else { - $query = new Query($queryValue); - } - - if (isset($property)) { - switch ($property->type) { - case Schema::MANY_TO_ONE: - // FK Value - $fkvName = $property->joinProperty; - if (! isset($fkvName)) { - $fkvName = $property->name; - } - $fkValue = $this->$fkvName; - - // FK - $fkModel = ModelDescription::getInstance($property->inverseJoinModel); - $fkName = $property->inverseJoinProperty; - if (! isset($fkName)) { - $fkName = 'id'; - } - $fk = $fkModel->$fkName; - - $query->addFilter([ - $fk->name, - '=', - $fkValue - ]); - $repo = Pluf::getDataRepository([ - 'model' => $property->inverseJoinModel - ]); - return $repo->getOne($query); - case Schema::ONE_TO_MANY: - // FK Value - $fkvName = $property->joinProperty; - if (! isset($fkvName)) { - $fkvName = 'id'; - } - $fkValue = $this->$fkvName; - - // FK - $fkModel = ModelDescription::getInstance($property->inverseJoinModel); - $fkName = $property->inverseJoinProperty; - if (! isset($fkName)) { - $fkName = 'id'; - } - $fk = $fkModel->$fkName; - - $query->addFilter([ - $fk->name, - '=', - $fkValue - ]); - $repo = Pluf::getDataRepository([ - 'model' => $property->inverseJoinModel - ]); - return $repo->get($query); - case Schema::MANY_TO_MANY: - - $smd = ModelDescription::getInstance($this); - $tmd = ModelDescription::getInstance($property->inverseJoinModel); - $relation = $property; - - // FK Value - $fkvName = $relation->joinProperty; - if (! isset($fkvName)) { - $fkvName = 'id'; - } - $fkValue = $this->$fkvName; - $tmdFk = $relation->inverseJoinProperty; - if (! isset($tmdFk)) { - $tmdFk = 'id'; - } - - // Repository - $repo = Pluf::getDataRepository([ - 'model' => $property->inverseJoinModel - ]); - $schmea = $repo->getSchema(); - $query->setView([ - 'filter' => [ - [ - '__px_relation__.' . $schmea->getRelationSourceField($smd, $tmd, $relation), - '=', - $fkValue - ] - ], - 'join' => [ - [ - 'joinTable' => $schmea->getRelationTable($smd, $tmd, $relation), - 'joinColumne' => $schmea->getFieldName($tmd, $tmd->$tmdFk), - 'inverseJoinColumne' => $schmea->getRelationTargetField($smd, $tmd, $relation, false), - 'alias' => '__px_relation__' - ] - ] - ]); - - return $repo->get($query); - default: - throw new Exception([ - 'message' => 'the property {name} in model {model} is not relation', - 'name' => $property->name, - 'model' => $md->type - ]); - } - } - } - // no slousion found - throw new \Pluf\Exception([ - 'message' => 'Method "{method}" not available in model "{model}".', - 'method' => $method, - 'model' => get_class($this) - ]); - } - - /** - * Get a given item. - * - * @param - * int Id of the item. - * @return mixed Item or false if not found. - * @deprecated use \Pluf\Model\Repository::getList - */ - function get($id) - { - $model = Pluf::getDataRepository($this)->getById($id); - $this->_data = $model->_data; - } - - /** - * Get one item. - * - * The parameters are the same as the ones of the getList method, - * but, the return value is either: - * - * - The object - * - null if no match - * - Exception if the match results in more than one item. - * - * Usage: - * - *
-     * $m = Pluf::factory(My_Model::class)->getOne(array('filter' => 'id=1'));
-     * 
- *
-     * $m = Pluf::factory(My_Model::class)->getOne('id=1');
-     * 
- * - * @param array|string $pFilter - * string or array given to getList - * @see self::getList - * @return \Pluf\Data\Model|null find model - */ - public function getOne($p = array()) - { - if (! is_array($p)) { - $p = array( - 'filter' => $p - ); - } - $items = $this->getList($p); - if (count($items) == 1) { - return $items[0]; - } - if (count($items) == 0) { - return null; - } - throw new Exception([ - 'message' => 'More than one matching item found.' - ]); - } - - /** - * Gets list of model with the parameters - * - * The filter should be used only for simple filtering. If you want - * a complex query, you should create a new view. - * Both filter and order accept an array or a string in case of multiple - * parameters: - * Filter: - * array('col1=toto', 'col2=titi') will be used in a AND query - * or simply 'col1=toto' - * Order: - * array('col1 ASC', 'col2 DESC') or 'col1 ASC' - * - * This is modelled on the DB_Table pear module interface. - * - * keys: - * - * - 'view': The view to use - * - 'filter': The where clause to use - * - 'order': The ordering of the result set - * - 'start': The number of skipped rows in the result set - * - 'limit': The number of items to get in the result set - * - 'count': Run a count query and not a select if set to true - * - * @param - * array Associative array with the possible following - * @return \ArrayObject of items or through an exception if - * database failure - */ - function getList($p = array()) - { - $query = new Query($p); - return Pluf::getDataRepository($this)->get($query); - } - - /** - * Get the number of items. - * - * @see getList() for definition of the keys - * - * @param - * array with associative keys 'view' and 'filter' - * @return int The number of items - * @see \Pluf\Data\Repository#getCount - */ - function getCount($p = array()): int - { - $p['count'] = true; - $count = $this->getList($p); - if (! isset($count)) { - return 0; - } else { - return (int) $count; - } - } - - // /** - // * Get a list of related items. - // * - // * See the getList() method for usage of the view and filters. - // * - // * @param - // * string Class of the related items - // * @param - // * string Method call in a many to many related - // * @param - // * array Parameters, see getList() for the definition of - // * the keys - // * @return array Array of items - // */ - // private function getRelated(string $modelClass, $relationName, $p = array()) - // { - // if ($this->isAnonymous()) { - // return new ArrayObject(); - // } - - // $default = array( - // 'view' => null, - // 'filter' => null, - // 'order' => null, - // 'start' => null, - // 'nb' => null, - // 'count' => false - // ); - // $query = new Query(array_merge($default, $p)); - - // // $toMd = ModelDescription::getInstance($modelClass); - // // $fromMd = ModelDescription::getInstance($this); - - // $rep = Pluf::getDataRepository([ - // 'owner' => $this, - // 'target' => $modelClass, - // 'name' => $relationName - // ]); - - // return $repo->get([]); - - // // $property = $fromMd->$relationName; - // // switch ($property->type) { - // // case Schema::MANY_TO_ONE: - - // // case Schema::MANY_TO_MANY: - // // case Schema::ONE_TO_MANY: - // // $view = '__px__view_' . $relationName; - // // $query->view - // // default: - // // throw new Exception([ - // // 'message' => 'Property {relation} is not a relation field', - // // 'relation' => $relationName, - // // 'model' => get_class($this) - // // ]); - // // } - - // // return Pluf::getDataRepository($modelClass) - // // ->getRelations(, $relationName, $query); - // } - - /** - * Update the model into the database. - * - * If no where clause is provided, the index definition is used to - * find the sequence. These are used to limit the update - * to the current model. - * - * @param - * string Where clause to update specific items. ('') - * @return bool Success - */ - function update($where = null) - { - if (isset($where)) { - throw new Exception([ - 'message' => 'Bulky update is not supported in old Pluf Model.' - ]); - } - - $this->preSave(); - Pluf::getDataRepository($this)->update($this); - $this->postSave(); - return true; - } - - /** - * Create the model into the database. - * - * If raw insert is requested, the preSave/postSave methods are - * not called and the current id of the object is directly - * used. This is particularily used when doing backup/restore of - * data. - * - * @param - * bool Raw insert (false) - * @return bool Success - */ - function create($raw = false) - { - if (! $raw) { - $this->preSave(true); - } - - Pluf::getDataRepository([ - 'type' => 'model', - 'model' => get_class($this) - ])->create($this); - - if (! $raw) { - $this->postSave(true); - } - - return true; - } - - /** - * Delete the current model from the database. - * - * If another model link to the current model through a foreign - * key, the DB is responsible to mange relations. - */ - function delete() - { - if ($this->isAnonymous()) { - return false; - } - $this->preDelete(); - Pluf::getDataRepository($this)->delete($this); - $this->_reset(); - return true; - } - - /** - * Set the association of a model to another in many to many. - * - * @param - * object Object to associate to the current object - */ - function setAssoc(\Pluf\Data\Model $model, ?string $assocName = null) - { - if (! isset($assocName)) { - $property = ModelUtils::getRelationProperty(ModelDescription::getInstance($this), ModelDescription::getInstance($model)); - $assocName = $property->name; - } - Pluf::getDataRepository([ - 'relation' => $assocName, - 'source' => get_class($this), - 'target' => get_class($model) - ])->create($this, $model); - return true; - } - - /** - * Set the association of a model to another in many to many. - * - * @param - * object Object to associate to the current object - */ - function delAssoc(\Pluf\Data\Model $model, ?string $assocName = null) - { - if (! isset($assocName)) { - $property = Pluf::getDataSchema()->getRelationProperty($this, $model); - $assocName = $property->name; - } - Pluf::getDataRepository([ - 'relation' => $assocName, - 'source' => get_class($this), - 'target' => get_class($model) - ])->delete($this, $model); - return true; - } - - /** - * Reset the fields to default values. - */ - private function _reset() - { - foreach ($this->_a['cols'] as $col => $val) { - if (isset($val['default'])) { - $this->_data[$col] = $val['default']; - } elseif (isset($val['is_null'])) { - $this->_data[$col] = null; - } else { - $this->_data[$col] = ''; - } - } - } - - /** - * Represents the model in auto generated lists. - * - * You need to overwrite this method to have a nice display of - * your objects in the select boxes, logs. - * - * @return string reperesentation of the current object - */ - function __toString() - { - return $this->_a['model'] . '(' . $this->_data['id'] . ')'; - } - - /** - * مقادیر مدل را بر اساس یک فرم تعیین می‌کند - * - * این مقادیر به صورت یک آرایه به عنوان ورودی دریافت شده و بر اساس - * آن داده‌های مورد نیاز مدل تعیین می‌شود. - */ - function setFromFormData($cleaned_values) - { - foreach ($cleaned_values as $key => $val) { - $this->_data[$key] = $val; - } - } - - /** - * بررسی می‌کند که آیا مدل داده‌ای وجود دارد یا نه - * - * در صورتی که مدل داده‌ای ذخیره نشده باشد به عنوان داده بی نام و نشان در - * نظر - * گرفته می‌شود. در مورد کاربران این تابع کاربرد فراوان دارد. - * - * @return bool True if the user is anonymous. - */ - function isAnonymous() - { - return ($this->id == '' || 0 === (int) $this->id); - } - - /** - * مدل داده‌ای را تعیین می‌کند - * - * هر مدل داده‌ای یک نام دارد. - * - * این فراخوانی نام مدل داده‌ای را تعیین می‌کند که معادل با نام کلاس است. - * - * @return string - */ - public function getClass() - { - return $this->_a['model']; - } - - /** - * شناسه را تعیین می‌کند. - * - * @return integer id - */ - public function getId() - { - return $this->id; - } - - // /** - // * (non-PHPdoc) - // * - // * @see JsonSerializable::jsonSerialize() - // */ - // public function jsonSerialize() - // { - // $coded = array(); - // foreach ($this->_data as $col => $val) { - // /* - // * خصوصیت‌هایی که قابل خواندن نیستن سریال نخواهند شد - // */ - // if (array_key_exists($col, $this->_a['cols']) && array_key_exists('readable', $this->_a['cols'][$col]) && ! $this->_a['cols'][$col]['readable']) - // continue; - // /* - // * If parameter ins null, zero, empty, ... we will not encode - // */ - // if ($val) - // $coded[$col] = $val; - // } - // return $coded; - // } - public function getName() - { - return array_key_exists('verbose', $this->_a) ? $this->_a['verbose'] : $this->getClass(); - } - - // /** - // * Gets engine where this model is managed - // */ - // public function getRepository(): ?Repository - // { - // return Pluf::getDataRepository($this); - // } - - // /** - // * - // * @deprecated - // * @param string $name - // * @return array - // */ - // public function getView(string $name): array - // { - // $md = ModelDescription::getInstance($this); - // return $md->getView($name); - // } - - // /** - // * Set a view. - // * - // * @deprecated - // * @param string $name - // * Name of the view. - // * @param array $view - // * Definition of the view. - // */ - // public function setView(string $name, array $view): void - // { - // $md = ModelDescription::getInstance($this); - // $md->setView($name, $view); - // } - - // /** - // * - // * @deprecated - // * @param string $name - // * @return bool - // */ - // public function hasView(?string $name = null): bool - // { - // $md = ModelDescription::getInstance($this); - // return $md->hasView($name); - // } - - /** - * Loads and return views - */ - public function loadViews(): array - { - return []; - } - - public function getIndexes(): array - { - $indexes = $this->loadIndexes(); - if (Pluf::f('multitenant', false) && $this->_a['multitenant']) { - foreach ($indexes as $col => $idx) { - $indexes[$col]['col'] = 'tenant,' . $idx['col']; - } - } - return $indexes; - } - - /** - * Load indexes of the model - * - * @return array - */ - public function loadIndexes(): array - { - if (isset($this->_a['idx'])) { - return $this->_a['idx']; - } - return []; - } - - // /** - // * Generate the SQL select from the columns - // */ - // function getSelect() - // { - // if (isset($this->_cache['getSelect'])) { - // return $this->_cache['getSelect']; - // } - // $schema = $this->getEngine()->getSchema(); - // $select = array(); - // $table = $schema->getTableName($this); - // foreach ($this->_a['cols'] as $col => $val) { - // if ($val['type'] != Engine::MANY_TO_MANY) { - // $select[] = $table . '.' . $schema->qn($col) . ' AS ' . $schema->qn($col); - // } - // } - // $this->_cache['getSelect'] = implode(', ', $select); - // return $this->_cache['getSelect']; - // } - - // /** - // * Get models affected by delete. - // * - // * @return array Models deleted if deleting current model. - // */ - // function getDeleteSideEffect() - // { - // $affected = array(); - // foreach ($this->_m['list'] as $method => $details) { - // if (is_array($details)) { - // // foreignkey - // $related = $this->$method(); - // $affected = array_merge($affected, (array) $related); - // foreach ($related as $rel) { - // if ($details[0] == $this->_a['model'] and $rel->id == $this->_data['id']) { - // continue; // $rel == $this - // } - // $affected = array_merge($affected, (array) $rel->getDeleteSideEffect()); - // } - // } - // } - // return Pluf_Model_RemoveDuplicates($affected); - // } - - // public function getSchema() - // { - // $mainInfo = array( - // "type" => $this->getclass(), - // "unit" => null, - // "name" => $this->getname(), - // "title" => $this->getname(), - // "description" => null, - // "defaultvalue" => null, - // "required" => false, - // "visible" => false, - // "editable" => false, - // "priority" => 0, - // "validators" => [], - // "tags" => [], - // "children" => [] - // ); - // foreach ($this->_a['cols'] as $name => $field) { - // $fieldInfo = $this->getFieldInfo($name, $field); - // array_push($mainInfo['children'], $fieldInfo); - // } - // return $mainInfo; - // } - // - // private function getFieldInfo($name, $field) - // { - // return array( - // "type" => $field['type'], - // "unit" => null, - // "name" => $name, - // "title" => $name, - // "description" => null, - // "defaultValue" => array_key_exists('default', $field) ? $field['default'] : null, - // "required" => array_key_exists('is_null', $field) ? $field['is_null'] : true, - // "visible" => array_key_exists('readable', $field) ? $field['readable'] : true, - // "editable" => array_key_exists('editable', $field) ? $field['editable'] : false, - // "priority" => 0, - // "validators" => [], - // "tags" => [], - // "children" => [] - // ); - // } - - // /** - // * Prepare the value to be put in the DB. - // * - // * @param - // * mixed Value. - // * @param - // * string Column name. - // * @return string SQL ready string. - // */ - // function _toDb($val, $col) - // { - // return $this->getEngine()->toDb($val, $this->_a['cols'][$col]['type']); - // } - - // /** - // * Get the value from the DB. - // * - // * Create DB field and returns. The field type is used as the output - // * value type. - // * - // * @param - // * mixed Value. - // * @param - // * string Column name. - // * @return mixed Value. - // */ - // function _fromDb($val, $col) - // { - // return $this->getEngine()->_fromDb($val, $this->_a['cols'][$col]['type']); - // } - - // /** - // * Display value. - // * - // * When you have a list of choices for a field and you want to get - // * the display value of the current stored value. - // * - // * @param - // * string Field to display the value. - // * @return mixed Display value, if not available default to the value. - // */ - // function displayVal($col) - // { - // if (! isset($this->_a['cols'][$col]['choices'])) { - // return $this->_data[$col]; // will on purposed failed if not set - // } - // $val = array_search($this->_data[$col], $this->_a['cols'][$col]['choices']); - // if ($val !== false) { - // return $val; - // } - // return $this->_data[$col]; - // } - - // ------------------------------------------------------------------------ - // Function and automated part - // ------------------------------------------------------------------------ - // /** - // * متدهای اتوماتیک را برای مدل ورودی ایجاد می‌کند. - // * - // * Adds the get_xx_list method when the methods of the model - // * contains custom names. - // * - // * @param string $type - // * Relation type: Engine::FORINKEY or ENGINE::MANY_TO_MANY - // */ - // protected function _setupAutomaticListMethods($type) - // { - // $current_model = ModelUtils::getModelCacheKey($this); - // $relations = ModelUtils::getRelatedModels($this, $type); - - // foreach ($relations as $related) { - // if ($related != $current_model) { - // $model = new $related(); - // } else { - // $model = clone $this; - // } - // $fkeys = $model->getRelationKeysToModel($current_model, $type); - // foreach ($fkeys as $fkey => $val) { - // $mname = (isset($val['relate_name'])) ? $val['relate_name'] : $related; - // $mname = 'get_' . strtolower($mname) . '_list'; - // if (Engine::FOREIGNKEY === $type) { - // $this->_m['list'][$mname] = array( - // $related, - // $fkey - // ); - // } else { - // $this->_m['list'][$mname] = $related; - // $this->_m['many'][$related] = $type; - // } - // } - // } - // } - - /** - * Add tenant required fields - * - * Adds extra fields if multi-tenant is enabled - */ - protected function _setupMultitenantFields() - { - if (Pluf::f('multitenant', false) && $this->_a['multitenant']) { - $this->_a['cols']['tenant'] = $this->tenant_field; - } - } - - // ------------------------------------------------------------------------ - // TO override - // ------------------------------------------------------------------------ - - /** - * Hook run just after loading a model from the database. - * - * Just overwrite it into your model to perform custom actions. - */ - function restore() - {} - - /** - * دستگیره‌ای که درست قبل از ذخیره شدن در پایگاه داده اجرا می‌شود. - * - * در صورتی که نیاز به انجام پردازش‌هایی قبل از ذخیره شدن مدل داده‌ای دارید، - * این فراخوانی - * را بازنویسی کنید. - * - * @param - * bool Create. - */ - function preSave($create = false) - { - // TODO: maso, 1395: بررسی داده‌های پیش فرض و به روز رسانی آنها - // - // برخی داده‌ها در تمام مدلهای داده‌ای به صورت تکراری استفاده می‌شود. - // بهتر است که - // وجود این داده‌ها بررسی شود و در صورت وجود همین جا به روز رسانی انجام - // شود. - // - // - creation_dtime - // - modif_dtime - } - - /** - * فراخوانی پس از ذخیره شدن - * - * @param string $create - */ - function postSave($create = false) - {} - - /** - * Hook run just before deleting a model from the database. - * - * Just overwrite it into your model to perform custom actions. - */ - function preDelete() - {} - - /** - * ساختار داده‌ای را ایجاد می‌کند. - * - * این فراخوانی تمام ساختارهای داده‌ای اصلی را ایجاد می‌کند. تمام زیر - * کلاس‌ها - * باید این کلاس را پیاده سازی کنند و ساختارهای داده‌ای خود را ایجاد کنند. - */ - function init() - { - // Define it yourself. - } - - /** - * Traditional JSON Encoding - * - * In Pluf V5 supports JsonSerializable for each model, This is a new implementation - * to support old Data Model. - * - * @see JsonSerializable::jsonSerialize() - */ - public function jsonSerialize() - { - return ModelEncoder::getInstance(ModelEncoder::JSON)-> // - setProperties(Pluf::getConfigurationPrifix('data_', true)) - ->setModel($this) - ->encode($this); - } -} - diff --git a/src/Data/ModelDescription.php b/src/Data/ModelDescription.php deleted file mode 100644 index e56f22c0..00000000 --- a/src/Data/ModelDescription.php +++ /dev/null @@ -1,184 +0,0 @@ -mapped; - } - - /** - * Creates new instance of the model - * - * @return mixed new created model - */ - public function newInstance() - { - return new $this->type(); - } - - private function loadFromOldModel(\Pluf\Data\Model $model) - { - // load properties - foreach ($model->_a['cols'] as $col => $description) { - if (! array_key_exists('name', $description)) { - $description['name'] = $col; - } - if (! array_key_exists('editable', $description)) { - $description['editable'] = true; - } - $this->$col = new ModelProperty($description); - } - - // load descriptions - $this->setDefaults($model->_a); - $this->type = get_class($model); - $this->views = $model->loadViews(); - $this->multitinant = $model->_a['multitenant']; - - // Set identifier -// $identifier = $this->id; -// $this->identifier = $identifier; - } - - public static function getInstance($model): ModelDescription - { - // XXX: maso, 2020: use pluf cache to improve performance - if (is_string($model)) { - $model = new $model(); - } - /* - * Support old - */ - if (is_a($model, '\\Pluf\Data\Model', true)) { - $modelDescription = new ModelDescription(); - if (is_string($model)) { - $model = new $model(); - } - $modelDescription->loadFromOldModel($model); - } else { - // XXX: maso, 2020: create model description based on generic object - throw new Exception('Generic PHP Object is not supported'); - } - - return $modelDescription; - } - - /** - * Checks if the model is anonymous or not - * - * @param ModelDescription $md - * @param mixed $model - * @return bool - */ - public function isAnonymous($model): bool - { - if ($model instanceof \Pluf\Data\Model) { - return $model->isAnonymous(); - } - - $idProp = $this->getIdentifier(); - $id = $model->$idProp; - return isset($id); - } - - /** - * Gets identifier property - * - * @return ModelProperty identifier - */ - public function getIdentifier(): ModelProperty - { - return $this->identifier; - } - - /** - * Checks if the view with $name defines - * - * @param string $name - * @return bool - */ - public function hasView(string $name): bool - { - return array_key_exists($name, $this->views); - } - - /** - * Gets named view from the model - * - * @param string $name - * @return array - */ - public function getView(string $name): array - { - if (! $this->hasView($name)) { - throw new Exception([ - 'message' => 'View [name] does not exist in [model].', - 'model' => get_class($this), - 'name' => $name - ]); - } - return $this->views[$name]; - } - - /** - * Adds new view to the model description - * - * @param string $name - * Name of the view - * @param array $view - * Definition of the view - */ - public function setView(string $name, array $view = []) - { - $this->views[$name] = $view; - // XXX: maso, 2020: update cache - } - - /** - * Checks if the $target is insance of this description - * - * @param - * Object | ModelDescription | string $target - * @return boolean - */ - public function isInstanceOf($target): bool - { - $model = $target; - if ($target instanceof ModelDescription) { - $model = $target->type; - } - return is_a($model, $this->type, true); - } -} - diff --git a/src/Data/ModelEncoder.php b/src/Data/ModelEncoder.php deleted file mode 100644 index 4b06421a..00000000 --- a/src/Data/ModelEncoder.php +++ /dev/null @@ -1,14 +0,0 @@ - null, - // "defaultValue" => null, - // "required" => false, - // "visible" => false, - // "priority" => 0, - public bool $graphql_field = true; - - public ?string $graphql_name = null; - - // public array $validators" => ['NotNull', 'MaxSize:20', 'MinSize:2'], - // public array $tags => [], - public bool $editable = true; - - public bool $nullable = true; - - public bool $readable = true; - - public int $decimal_places = 8; - - public int $max_digits = 32; - - public ?int $size = 256; - - public bool $unique = false; - - public ?string $columne = null; - - /** - * Relation properties - * - * These are used to define a relation property for a model - * { - */ - public ?string $joinProperty = null; - - /** - * Defines a model which is related to the current one - * - * @var string - */ - public ?string $inverseJoinModel = null; - - /** - * Defines the property of the related model. - * - * NOTE: property must be defined, and the inverse of the property must be current one. - * - * @var string - */ - public ?string $inverseJoinProperty = null; - - /** - * } - */ - - /** - * Relation DB field - * { - */ - public ?string $joinTable = null; - - public ?string $joinColumne = null; - - public ?string $inverseJoinColumne = null; - - /** - * } - */ - /** - * Creates new instance of model property - * - * @param array|Options $options - */ - public function __construct($options) - { - $this->setDefaults($options); - } - - /** - * Checks if the property is mapped one. - * - * @return bool true if the property is mapped. - */ - public function isMapped(): bool - { - return isset($this->mapped) && $this->mapped; - } - - /** - * Check if the property is a relation - * - * @return bool true if the property is relation - */ - public function isRelation(): bool - { - return in_array($this->type, [ - Schema::ONE_TO_MANY, - Schema::MANY_TO_MANY, - Schema::MANY_TO_ONE - ]); - } -} - diff --git a/src/Data/ModelUtils.php b/src/Data/ModelUtils.php deleted file mode 100644 index aa02c97e..00000000 --- a/src/Data/ModelUtils.php +++ /dev/null @@ -1,293 +0,0 @@ -getName(); - } - if (strpos($model, '\\')) { - $model = '\\' . $model; - } - return $model; - } - - public static function loadFromCache(\Pluf\Data\Model $model): bool - { - $key = self::getModelCacheKey($model); - if (isset($GLOBALS[self::MODEL_CACHE_KEY][$key])) { - $init_cache = $GLOBALS[self::MODEL_CACHE_KEY][$key]; - - $model->_cache = $init_cache['cache']; - $model->_m = $init_cache['m']; - $model->_a = $init_cache['a']; - $model->_fk = $init_cache['fk']; - $model->_data = $init_cache['data']; - - return true; - } - return false; - } - - public static function putModelToCache(\Pluf\Data\Model $model): void - { - $key = self::getModelCacheKey($model); - if (isset($GLOBALS[self::MODEL_CACHE_KEY][$key])) { - return; - } - $GLOBALS[self::MODEL_CACHE_KEY][$key] = array( - 'cache' => $model->_cache, - 'm' => $model->_m, - 'a' => $model->_a, - 'fk' => $model->_fk, - 'data' => $model->_data - ); - } - - /** - * Gets list of related model to the type - * - * @param ModelDescription|string $type - * @return array - */ - public static function getRelatedModels($type): array - { - if ($type instanceof ModelDescription) { - $modelDescription = $type; - } else { - $modelDescription = ModelDescription::getInstance($type); - } - $preModels = []; - foreach ($modelDescription as $property) { - if ($property->isRelation()) { - $name = ModelUtils::getModelCacheKey($property->inverseJoinModel); - if (! in_array($name, $preModels)) { - array_push($preModels, $name); - } - } - } - return $preModels; - } - - /** - * Gets relation property from $smd to $tmd with name $relation - * - * @param ModelDescription $smd - * @param ModelDescription $tmd - * @param string $relation - * @throws Exception - * @return ModelProperty - */ - public static function getRelationProperty(ModelDescription $smd, ModelDescription $tmd, ?string $relation = null): ModelProperty - { - if (! isset($relation)) { - foreach ($smd as $property) { - if ($property->isRelation() && $tmd->isInstanceOf($property->inverseJoinModel)) { - return $property; - } - } - } - - // check relation - $relationProperty = $smd->$relation; - if (! isset($relationProperty)) { - throw new Exception([ - 'message' => 'The property wtih name {name} does not exist in type {type}', - 'type' => $smd->type, - 'name' => $relation->name - ]); - } - // check type - if (! $relationProperty->isRelation()) { - throw new Exception([ - 'message' => 'The property wtih name {name} is not a relation type in {type}', - 'type' => $smd->type, - 'name' => $relation->name - ]); - } - // check target model - if (! $tmd->isInstanceOf($relationProperty->inverseJoinModel)) { - throw new Exception([ - 'message' => 'The type {target} does not match with relation type {type} from {source}', - 'type' => $relationProperty->inverseJoinModel, - 'source' => $smd->type, - 'target' => $tmd->type - ]); - } - - return $relationProperty; - } - - // /** - // * Get the model relations and signals. - // * - // * If not in debug mode, it will automatically cache the - // * information. This allows one include file when many - // * applications and thus many includes are needed. - // * - // * Signals and relations are cached in the same file as the way to - // * go for signals is to put them in the relations.php file. - // * - // * @param - // * bool Use the cache (true) - // */ - // public static function loadRelations($usecache = true) - // { - // $GLOBALS[ModelUtils::MODEL_KEY] = array(); - // $GLOBALS[ModelUtils::MODEL_CACHE_KEY] = array(); - - // $apps = Pluf::f('installed_apps', array()); - // $cache = Pluf::f('tmp_folder', '/tmp') . '/Pluf_relations_cache_' . md5(serialize($apps)) . '.phps'; - - // if ($usecache and file_exists($cache)) { - // list ($GLOBALS[ModelUtils::MODEL_KEY], $GLOBALS['_PX_models_related'], $GLOBALS['_PX_signal']) = include $cache; - // return; - // } - - // $m = $GLOBALS[ModelUtils::MODEL_KEY]; - // foreach ($apps as $app) { - // $moduleName = "\\Pluf\\" . $app . "\\Module"; - // if (class_exists($moduleName)) { - // // Load PSR4 modules - // $m = array_merge_recursive($m, $moduleName::relations); - // } else { - // // Load PSR1 modules - // $m = array_merge_recursive($m, require $app . '/relations.php'); - // } - // } - // $GLOBALS[ModelUtils::MODEL_KEY] = $m; - - // $_r = array( - // 'relate_to' => array(), - // 'relate_to_many' => array() - // ); - // foreach ($GLOBALS[ModelUtils::MODEL_KEY] as $model => $relations) { - // foreach ($relations as $type => $related) { - // foreach ($related as $related_model) { - // if (! isset($_r[$type][$related_model])) { - // $_r[$type][$related_model] = array(); - // } - // $_r[$type][$related_model][] = $model; - // } - // } - // } - // $_r[Engine::FOREIGNKEY] = $_r['relate_to']; - // $_r[Engine::MANY_TO_MANY] = $_r['relate_to_many']; - // $GLOBALS['_PX_models_related'] = $_r; - - // // $GLOBALS['_PX_signal'] is automatically set by the require - // // statement and possibly in the configuration file. - // if ($usecache) { - // $s = var_export(array( - // $GLOBALS[ModelUtils::MODEL_KEY], - // $GLOBALS['_PX_models_related'], - // $GLOBALS['_PX_signal'] - // ), true); - // if (@file_put_contents($cache, 'getFileName()) . '/module.json'; - } - - if (false == ($file = Pluf::fileExists($path))) { - return []; - } - $myfile = fopen($file, "r") or die("Unable to open module.json!"); - $json = fread($myfile, filesize($file)); - fclose($myfile); - $moduel = json_decode($json, true); - if (! array_key_exists('model', $moduel)) { - return []; - } - return $moduel['model']; - } - - public static function loadViewsFromCache(\Pluf\Data\Model $model) - { - $key = self::getModelCacheKey($model); - if (isset($GLOBALS[self::MODEL_VIEW_CACHE_KEY][$key])) { - return $GLOBALS[self::MODEL_VIEW_CACHE_KEY][$key]; - } - return false; - } - - public static function putViewsToCache(\Pluf\Data\Model $model, array $views) - { - $key = self::getModelCacheKey($model); - $GLOBALS[self::MODEL_VIEW_CACHE_KEY][$key] = $views; - } - - public static function getModelName($model): String - { - $modelName = $model->_a['model']; - - return $modelName; - } - - /** - * - * @deprecated - */ - public static function getAssocTable(\Pluf\Data\Model $from, \Pluf\Data\Model $to): String - { - $hay = array( - strtolower($from->_a['model']), - strtolower($to->_a['model']) - ); - sort($hay); - $prefix = $from->getEngine() - ->getSchema() - ->getPrefix(); - return self::skipeName($prefix . $hay[0] . '_' . $hay[1] . '_assoc'); - } - - public static function getTable($model): String - { - $table = $model->_con->pfx . $model->_a['table']; - $name = self::skipeName($table); - return $name; - } - - public static function getAssocField($model): String - { - $name = self::skipeName(strtolower($model->_a['model']) . '_id'); - $name = $model->getEngine() - ->getSchema() - ->qn($name); - return $name; - } - - public static function skipeName(String $name): String - { - $name = str_replace('\\', '_', $name); - return $name; - } -} - diff --git a/src/Data/Query.php b/src/Data/Query.php deleted file mode 100644 index d5ce4b13..00000000 --- a/src/Data/Query.php +++ /dev/null @@ -1,270 +0,0 @@ -. - */ -namespace Pluf\Data; - -/** - * - * Here is list of attributes to build a query - * - * - * - view - * - filter - * - order - * - start - * - limit - * - select - * - count - * - * - * ## View - * - * View is named Model View which must be defined in the model descriptions. - * For example suppose there is a view 'viewName' in the model and you are - * about to run a query: - * - * ```PHP - * $query = new Query([ - * 'view' => 'viewName', - * ... - * ]); - * ``` - * - * @author maso - * - */ -class Query -{ - - const ORDER_ASC = 'asc'; - - const ORDER_DESC = 'desc'; - - const MAX_RESULT_SIZE = 300; - - use \Pluf\DiContainerTrait; - - /** - * A view to used in query. - * - * You are free to create a new view or call an existed view from the repository. - * - * @var array|string - */ - private $view = null; - - private ?array $filter = null; - - private ?array $order = null; - - /** - * Start position of the table to query - * - * @var int - */ - private int $start = 0; - - /** - * Maximum result list size - * - * @var int - */ - private ?int $limit = null; - - private bool $count = false; - - private ?string $select = null; - - /** - * Creates new instance of the Query - * - * To build a query: - * - * - view: a view to apply - * - filter: list of where clouse - * - order: list of order by - * - start: start index of results - * - limit: Limit of resoult count - * - select: a query to search - * - count: returns counts of items - * - * @param array $param - */ - function __construct(?array $param = []) - { - $param = array_merge(array( - 'view' => null, - 'filter' => [], - 'order' => [], - 'select' => null, - 'start' => 0, - 'limit' => - 1, - 'count' => false - ), $param); - $this->setDefaults($param); - } - - /** - * Gets select query - * - * @return string - */ - public function getSelect(): string - { - return $this->select; - } - - /** - * Get current view of the query - * - * @return string view - */ - public function getView() - { - return $this->view; - } - - /** - * - * @return array of filters - */ - public function getFilter(): array - { - return $this->filter; - } - - /** - * - * @return array order - */ - public function getOrder(): array - { - return $this->order; - } - - /** - * - * @return number - */ - public function getStart(): int - { - return $this->start; - } - - /** - * - * @return number - */ - public function getLimit(): int - { - if ($this->limit < 1) { - return Query::MAX_RESULT_SIZE; - } - return $this->limit; - } - - /** - * - * @return number of item - */ - public function getCount(): bool - { - return $this->count; - } - - /** - * Checks if there is a view in the query - * - * @return bool true if there is a veiw - */ - public function hasView(): bool - { - if (is_array($this->view)) { - return true; - } - return isset($this->view) && strlen($this->view) > 0; - } - - /** - * - * @param mixed $view - */ - public function setView($view) - { - $this->view = $view; - } - - /** - * - * @param mixed $filter - */ - public function setFilter($filter) - { - $this->filter = $filter; - } - - /** - * Adds filter into the filter list - * - * @param mixed $filter - */ - public function addFilter($filter) - { - if (! isset($this->filter)) { - $this->filter = []; - } - $this->filter[] = $filter; - } - - /** - * - * @param mixed $order - */ - public function setOrder($order) - { - $this->order = $order; - } - - /** - * - * @param number $start - */ - public function setStart($start) - { - $this->start = $start; - } - - /** - * - * @param number $limit - */ - public function setLimit($limit) - { - $this->limit = $limit; - } - - /** - * - * @param boolean $count - */ - public function setCount($count) - { - $this->count = $count; - } -} - diff --git a/src/Data/QueryBuilder.php b/src/Data/QueryBuilder.php deleted file mode 100644 index 0e2f7f3d..00000000 --- a/src/Data/QueryBuilder.php +++ /dev/null @@ -1,218 +0,0 @@ -setDefaults([ - 'view' => $config->getView(), - 'count' => $config->getCount(), - 'select' => $config->getSelect(), - 'start' => $config->getStart(), - 'limit' => $config->getLimit(), - 'filters' => $config->getFilter(), - 'orders' => $config->getOrder() - ]); - return $qb; - } - return new QueryBuilder(); - } - - /** - * Sets query view - * - * @param array $view - * @return QueryBuilder - */ - public function setView($view): QueryBuilder - { - $this->view = $view; - return $this; - } - - /** - * Start index of the result - * - * @param int $start - * @return QueryBuilder - */ - public function setStart(int $start): QueryBuilder - { - if ($start < 0) { - throw new Error500('Start must be positive.'); - } - $this->start = $start; - return $this; - } - - public function setLimit($limit): QueryBuilder - { - if ($limit < 0) { - throw new Error500('Query limit must be positive.'); - } - $this->limit = $limit; - return $this; - } - - public function setOrder($key, $order): QueryBuilder - { - foreach ($this->orders as $order) { - if ($order[0] === $key) { - throw new Error500('Dublicated order key'); - } - } - $this->orders[] = [ - $key, - $order - ]; - return $this; - } - - public function addFilter(): QueryBuilder - { - $args = func_get_args(); - switch (count($args)) { - case 0: - throw new Error500('A parameter is required.'); - case 1: - $this->filters[] = $args[0]; - break; - case 2: - $this->filters[] = [ - $args[0], - '=', - $args[1] - ]; - break; - case 3: - $this->filters[] = $args; - break; - default: - throw new Error500('Maximum allowed paramiter is 3'); - } - return $this; - } - - /** - * Sets select query - * - * @param string $select - * A query to perform on object. - * @return QueryBuilder current builder - */ - public function setSelect(?string $select = null): QueryBuilder - { - $this->select = $select; - return $this; - } - - public function optimize(): QueryBuilder - { - return $this->optimizeFilters(); - } - - public function optimizeFilters(): QueryBuilder - { - $categories = []; - $other = []; - foreach ($this->filters as $filter) { - if (! is_array($filter)) { - $other[] = $filter; - continue; - } - $key = $filter[0]; - $opr = $filter[1]; - $val = $filter[2]; - switch ($opr) { - case '=': - if (! array_key_exists($key, $categories)) { - $categories[$key] = [ - $key, - 'in', - [] - ]; - } - $categories[$key][2][] = $val; - break; - case 'in': - if (! array_key_exists($key, $categories)) { - $categories[$key] = [ - $key, - 'in', - [] - ]; - } - if (! is_array($val)) { - throw new Error500('Invalid value for in operation'); - } - $categories[$key][2] = array_merge($categories[$key][2], $val); - break; - default: - $other[] = $filter; - } - } - $this->filters = array_merge($other, $categories); - return $this; - } - - /** - * Builds a query based on configurations. - * - * @return Query - */ - public function build(): Query - { - // optimize query - if (Pluf::getConfig('data.query.optimization.enable', true)) { - $this->optimize(); - } - return new Query([ - 'view' => $this->view, - 'filter' => $this->filters, - 'order' => $this->orders, - 'select' => $this->select, - 'start' => $this->start, - 'limit' => $this->limit, - 'count' => $this->count - ]); - } -} - diff --git a/src/Data/QueryBuilder/RequestQueryBuilder.php b/src/Data/QueryBuilder/RequestQueryBuilder.php deleted file mode 100644 index 152298ca..00000000 --- a/src/Data/QueryBuilder/RequestQueryBuilder.php +++ /dev/null @@ -1,165 +0,0 @@ - - *
  • _px_q : Query string to search.
  • - *
  • _px_p : Current page.
  • - *
  • _px_sk : Sort key.
  • - *
  • _px_so : Sort order.
  • - *
  • _px_fk : Filter key.
  • - *
  • _px_fv : Filter value.
  • - *
      - * - * @param - * \Pluf\HTTP\Request The request - */ - function __construct(Request $request) - { - $this->loadPage($request) - ->loadSorts($request) - ->loadFilters($request) - ->loadQuery($request); - } - - /* - * load current page - */ - private function loadPage(Request $request): RequestQueryBuilder - { - $page = 0; - $pageSize = 30; - if (isset($request->REQUEST[self::CURRENT_PAGE_KEY])) { - // >> Page number - $page = $request->REQUEST[self::CURRENT_PAGE_KEY]; - if (! isset($page)) { - $page = 0; - } - $page = (int) $page; - } - if (isset($request->REQUEST[self::PAGE_SIZE_KEY])) { - // Page size - $pageSize = $request->REQUEST[self::PAGE_SIZE_KEY]; - if (! isset($pageSize)) { - $pageSize = 30; - } - $pageSize = (int) $pageSize; - } - // >> set query - $this->setStart($page * $pageSize)->setLimit($pageSize); - return $this; - } - - /* - * load current page - */ - private function loadSorts(Request $request): RequestQueryBuilder - { - if (! isset($request->REQUEST[self::SORT_KEY_KEY])) { - $this->sort_order = []; - return $this; - } - // Sort orders - $keys = $request->REQUEST[self::SORT_KEY_KEY]; - $vals = $request->REQUEST[self::SORT_ORDER_KEY]; - - if (! is_array($keys)) { - $keys = [ - $keys - ]; - } - if (! is_array($vals)) { - $vals = [ - $vals - ]; - } - - for ($i = 0; $i < sizeof($keys); $i ++) { - $key = $keys[$i]; - $order = 'ASC'; - if ($vals[$i] === 'd') { - $order = 'DESC'; - } - $this->setOrder($key, $order); - } - - return $this; - } - - /* - * load current page - */ - private function loadFilters(Request $request): RequestQueryBuilder - { - - // check filter option - if (! array_key_exists(self::FILTER_KEY_KEY, $request->REQUEST)) { - return $this; - } - - $keys = $request->REQUEST[self::FILTER_KEY_KEY]; - $vals = $request->REQUEST[self::FILTER_VALUE_KEY]; - if (! is_array($keys)) { - $keys = [ - $keys - ]; - } - if (! is_array($vals)) { - $vals = [ - $vals - ]; - } - - // categorize filters - for ($i = 0; $i < sizeof($keys); $i ++) { - $key = $keys[$i]; - $value = $vals[$i]; - $this->addFilter($key, $value); - } - - return $this; - } - - /* - * load current page - */ - private function loadQuery(Request $request): RequestQueryBuilder - { - // load query - $query = null; - if (isset($request->REQUEST[self::SEARCH_QUERY_KEY])) { - $query = $request->REQUEST[self::SEARCH_QUERY_KEY]; - } - return $this->setSelect($query); - } -} - -// XXX: maso, 2020: support Geometry in core of data model -// $feild = $this->model->_a['cols'][$key]; -// if (strcmp($feild['type'], 'Geometry') === 0) { -// $str = "Contains(GeometryFromText('" . implode("')," . $key . ") OR Contains(GeometryFromText('", $vals) . "')," . $key . ")"; -// $sql = new Pluf_SQL($str); -// } else diff --git a/src/Data/Repository.php b/src/Data/Repository.php deleted file mode 100644 index a69f18e3..00000000 --- a/src/Data/Repository.php +++ /dev/null @@ -1,246 +0,0 @@ -. - */ -namespace Pluf\Data; - -use Pluf\Options; -use Pluf\Data\Repository\ModelRepository; -use Pluf\Data\Repository\RelationRepository; -use Pluf\Db\Connection; -use PDOStatement; - -/** - * Repositories are object or components that encapsulate the logic required to access data sources. - * - * They centralize common data access functionality, providing better maintainability and decoupling the - * infrastructure or technology used to access databases from the domain model layer. If you use an - * Object-Relational Mapper (ORM) like Entity Framework, the code that must be implemented is simplified. - * This lets you focus on the data persistence logic rather than on data access plumbing. - * - * The Repository pattern is a well-documented way of working with a data source. In the book Patterns - * of Enterprise Application Architecture, Martin Fowler describes a repository as follows: - * - * A repository performs the tasks of an intermediary between the domain model layers and data mapping, acting - * in a similar way to a set of domain objects in memory. Client objects declaratively build queries and send - * them to the repositories for answers. Conceptually, a repository encapsulates a set of objects stored in - * the database and operations that can be performed on them, providing a way that is closer to the persistence - * layer. Repositories, also, support the purpose of separating, clearly and in one direction, the dependency - * between the work domain and the data allocation or mapping. - * - * Basically, a repository allows you to populate data in memory that comes from the database in the form of the - * domain entities. Once the entities are in memory, they can be changed and then persisted back to the - * database through transaction. - * - * @author maso - * @see https://martinfowler.com/eaaCatalog/repository.html - * @see https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ff649690(v=pandp.10) - * - */ -abstract class Repository -{ - - use \Pluf\DiContainerTrait; - - private static array $repositoryMap = []; - - public ?Connection $connection = null; - - public ?Schema $schema = null; - - /** - * Defines one repository per aggregate - * - * For each aggregate or aggregate root, you should create one repository object. In a microservice based on - * Domain-Driven Design (DDD) patterns, the only channel you should use to update the database should be the - * repositories. This is because they have a one-to-one relationship with the aggregate root, which controls - * the aggregate’s invariants and transactional consistency. It's okay to query the database through other - * channels (as you can do following a CQRS approach), because queries don't change the state of the database. - * However, the transactional area (that is, the updates) must always be controlled by the repositories and - * the aggregate roots. - * - * It can be valuable to create your repository in such a way that it enforces the rule that only - * aggregate roots should have repositories. This utility functions create new instance of such a repository - * for a given $modelType. Forexample: - * - * $repo = Repository::getInstance(Book::class); - * - * Creates a repository to mange Books. - */ - public static function getInstance($options): Repository - { - // TODO: maso, 2019: catche the repository in ArrayCache - if (is_array($options)) { - $options = new Options($options); - } - - // model repository - $model = $options->model; - if (isset($model)) { - $repo = new ModelRepository($options); - } - - // realtion repository - $relation = $options->relation; - if (isset($relation)) { - $repo = new RelationRepository($options); - } - - if (isset($repo)) { - return $repo; - } - - throw new Exception([ - 'message' => 'Unsupported repository type.' - ]); - } - - /** - * Creates new instance of the repository - * - * @param string $modelType - */ - public function __construct(Options $options) - { - $this->setDefaults($options); - $this->clean(); - } - - /** - * Sets repository schema - * - * @param Schema $schema - * to use in the repository. - * @return Repository current repository - */ - public function setSchema(Schema $schema): Repository - { - $this->schema = $schema; - $this->clean(); - return $this; - } - - /** - * Sets new DB connection into the repository - * - * DB connection is used to store or fetch data from DB. It is easy to replace - * current DB Connection with new one. - * - * @param Connection $connection - * @return Repository - */ - public function setConnection(Connection $connection): Repository - { - $this->connection = $connection; - return $this; - } - - /** - * Gets current connection - * - * @return \Pluf\Db\Connection - */ - public function getConnection() - { - return $this->connection; - } - - /** - * Gets current schema - * - * @return \Pluf\Data\Schema - */ - public function getSchema(): ?Schema - { - return $this->schema; - } - - /** - * Gets list of model matches with query. - * - * If the query is not given, then all items will match with it. - * - * @param Query $query - * to match - * @return array|int|mixed The count, list or a selected item - */ - public abstract function get(?Query $query = null); - - /** - * Delete the model - * - * If the $modal is a query, then all matched items will be removed - * - * @param mixed|\\Pluf\Data\Model|Query $model - */ - public abstract function delete($model); - - /** - * Crates the model in repository - * - * @param mixed|array|\\Pluf\Data\Model $model - * to create into the db - */ - public abstract function create($model); - - /** - * Updates the model in repository - * - * @param mixed|array|\\Pluf\Data\Model $model - * to create into the db - */ - public abstract function update($model); - - /** - * If a fundemental attribute of a repository changed then this function is - * called to clean the virtual attributes. - */ - protected abstract function clean(); - - // --------------------------------------------------------------------------------------- - // Utility functions - // --------------------------------------------------------------------------------------- - /** - * Checks if the last statement is executed successfully - * - * @param PDOStatement $stm - * @throws Exception - */ - protected function checkStatement(PDOStatement $stm): void - { - $info = $stm->errorInfo(); - if ($info[0] != 0) { - throw new Exception([ - 'code' => $info[0], - 'driverCode' => $info[1], - 'message' => $info[2] - ]); - } - } - - /** - * Gets related type - * - * @param string $type - * @return ModelDescription - */ - protected function getModelDescription(string $type): ModelDescription - { - return ModelDescription::getInstance($type); - } -} - diff --git a/src/Data/Repository/ModelRepository.php b/src/Data/Repository/ModelRepository.php deleted file mode 100644 index 87ed37b1..00000000 --- a/src/Data/Repository/ModelRepository.php +++ /dev/null @@ -1,413 +0,0 @@ -getListByQuery($query); - } - - /** - * Get a given item. - * - * @param - * int Id of the item. - * @return mixed Item or false if not found. - */ - public function getById($id) - { - $connection = $this->getConnection(); - $schema = $this->getSchema(); - $md = $this->getModelDescription($this->model); - - $stm = $connection->query() - ->table($schema->getTableName($md)) - ->where('id', '=', $id) - ->select(); - - $this->checkStatement($stm); - return $schema->newInstance($md, $stm->fetch()); - } - - /** - * Get one item. - * - * The parameters are the same as the ones of the getList method, - * but, the return value is either: - * - * - The object - * - null if no match - * - Exception if the match results in more than one item. - * - * @see self::getList - * @param Query $query) - * A query to run on db - * @return mixed|null find model - */ - public function getOne(Query $query) - { - $items = $this->get($query); - if (count($items) == 1) { - return $items[0]; - } - if (count($items) == 0) { - return null; - } - throw new \Pluf\Exception([ - 'message' => 'More than one matching item found.' - ]); - } - - /** - * Creates the model and update the id - * - * @param mixed $model - * @return mixed updated model - */ - public function create($model) - { - if (! is_a($model, $this->model)) { - throw new Exception([ - 'message' => 'Trying to store model {model} in reposiotory type {type}.', - 'model' => get_class($model), - 'type' => $this->model - ]); - } - // TODO: maso, 2019: pre create - $md = $this->getModelDescription($this->model); - $schema = $this->getSchema(); - $connection = $this->getConnection(); - - $stm = $connection->query() - ->mode('insert') - ->table($schema->getTableName($md)) - ->set($schema->getValues($md, $model)) - ->execute(); - - $this->checkStatement($stm); - // TODO: maso, 2019: post create - // Setting ID must move into post create from \Pluf\Data\Model - $model->id = $connection->lastInsertID(); - return $model; - } - - /** - * Updates the model and return it as result - * - * @param mixed $model - * updated model - * @throws Exception if the model is anonymous - * @return mixed updated model - */ - public function update($model) - { - $md = ModelDescription::getInstance($model); - $schema = $this->getSchema(); - $connection = $this->getConnection(); - - if ($md->isAnonymous($model)) { - throw new Exception([ - 'message' => 'Impossible to update anonymous object' - ]); - } - - // TODO: maso, 2019: pre update - - $query = new Query([ - 'filter' => [ - [ - 'id', - '=', - $model->id - ] - ] - ]); - - $stm = $connection->query() - ->mode('update') - ->table($schema->getTableName($md)) - ->set($schema->getValues($md, $model)) - ->where($schema->getQueryFilters($md, $query)) - ->execute(); - - $this->checkStatement($stm); - // TODO: maso, 2019: post update - return $model; - } - - /** - * Removes the model - * - * @param mixed $model - * A model to delete from repository - * @throws Exception if the model is anonymous - * @return mixed deleted model - */ - public function delete($model) - { - $md = ModelDescription::getInstance($model); - $schema = $this->getSchema(); - $connection = $this->getConnection(); - - if ($md->isAnonymous($model)) { - throw new Exception([ - 'message' => 'Impossible to update anonymous object' - ]); - } - - $query = new Query([ - 'filter' => [ - [ - 'id', - $model->id - ] - ] - ]); - - $stm = $connection->query() - ->mode('delete') - ->table($schema->getTableName($md)) - ->set($schema->getValues($md, $model)) - ->where($schema->getQueryFilters($md, $query)) - ->execute(); - - $this->checkStatement($stm); - return $model; - } - - /** - * Gets list of object - * - * Note: all object will be fetched in the memory - * - * The filter should be used only for simple filtering. If you want - * a complex query, you should create a new view. - * Both filter and order accept an array or a string in case of multiple - * - * @param Query $query - * to run on db - * @return array|int The result of items or through an exception if - * database failure - */ - private function getListByQuery(Query $query) - { - // TODO: maso, 2020: support cache - $schema = $this->getSchema(); - $md = $this->getModelDescription($this->model); - $connection = $this->getConnection(); - - // fields - $view = [ - 'filter' => [], - 'field' => [], - 'order' => [], - 'join' => [], - 'group' => [], - 'having' => [] - ]; - - if ($query->hasView()) { - // load the view - $viewName = $query->getView(); - if (is_string($viewName)) { - $dataView = $md->getView($query->getView()); - } else { - $dataView = $viewName; - } - - // Filter - if (array_key_exists('filter', $dataView)) { - $view['filter'] = array_merge( // - $schema->getViewFilters($md, $dataView), // - $schema->getQueryFilters($md, $query) // - ); - } else { - $view['filter'] = $schema->getQueryFilters($md, $query); - } - - // Field - if (array_key_exists('field', $dataView)) { - $view['field'] = $schema->getViewFields($md, $dataView); - } else { - $view['field'] = $schema->getFields($md); - } - - // Order - $allDataOrders = $query->getOrder(); - if (isset($dataView['order'])) { - $allDataOrders = array_merge($allDataOrders, $dataView['order']); - } - foreach ($allDataOrders as $name => $order) { - $view['order'][] = [ - $schema->getField($md, $name), - $order == Query::ORDER_DESC - ]; - } - - // Join - if (isset($dataView['join'])) { - $view['join'] = $schema->getViewJoins($md, $dataView); - } - - // Group - $view['group'] = $schema->getViewGroupBy($md, $dataView); - $view['having'] = $schema->getViewHaving($md, $dataView); - } else { - $view['field'] = $schema->getFields($md); - $view['filter'] = $schema->getQueryFilters($md, $query); - - // Order - foreach ($query->getOrder() as $name => $order) { - $view['order'][] = [ - $schema->getField($md, $name), - $order == Query::ORDER_DESC - ]; - } - } - - $dbQuery = $connection->query() - ->mode('select') - ->table($schema->getTableName($md)) - ->group($view['group']); - // add where - foreach ($view['filter'] as $filter) { - if ($filter instanceof Expression) { - $dbQuery->where($filter); - continue; - } - call_user_func_array([ - $dbQuery, - 'where' - ], $filter); - } - // add having - foreach ($view['having'] as $filter) { - if ($filter instanceof Expression) { - $dbQuery->having($filter); - continue; - } - call_user_func_array([ - $dbQuery, - 'having' - ], $filter); - } - - // add join - foreach ($view['join'] as $join) { - call_user_func_array([ - $dbQuery, - 'join' - ], $join); - } - - if ($query->getCount()) { - $dbQuery->field('count(*)', 'count'); - } else { - // add order - foreach ($view['order'] as $order) { - call_user_func_array([ - $dbQuery, - 'order' - ], $order); - } - // limit - $dbQuery->field($view['field'])->limit($query->getLimit(), $query->getStart()); - } - - // Checks the query statement - $stm = $dbQuery->execute(); - $this->checkStatement($stm); - - if ($query->getCount()) { - return $stm->fetch()['count']; - } - // Convert to new items - $result = $stm->fetchAll(); - $items = []; - foreach ($result as $data) { - $items[] = $schema->newInstance($md, $data); - } - return $items; - } - - public function deleteListByQuery(Query $query): array - { - $res = $this->sourceModel->getList($query->toArray()); - if ($res instanceof \ArrayObject) { - return $res->getArrayCopy(); - } - return []; - } - - /** - * Get the number of items. - * - * @see getList() for definition of the keys - * - * @param Query $query - * with associative keys 'view' and 'filter' - * @return int The number of items - */ - public function getCount(Query $query): int - { - return $this->sourceModel->getCount($query->toArray()); - } - - /** - * - * {@inheritdoc} - * @see \Pluf\Data\Repository::clean() - */ - protected function clean() - { - $this->modelDescription = null; - } -} - diff --git a/src/Data/Repository/RelationRepository.php b/src/Data/Repository/RelationRepository.php deleted file mode 100644 index effa7fc0..00000000 --- a/src/Data/Repository/RelationRepository.php +++ /dev/null @@ -1,286 +0,0 @@ -relation)) { - throw new Exception([ - 'message' => 'Relation name is required' - ]); - } - - // cheach relation property - $relationName = $this->relation; - $smd = $this->getModelDescription($this->source); - $this->relationPd = $smd->$relationName; - if (! isset($this->relationPd)) { - throw new Exception([ - 'message' => 'Relation property is not defined in srouce relation' - ]); - } - } - - /** - * Get a list of targets - * - * See the get() method for usage of the view and filters. - * - * For example, to find a book which is related to a item from NoteBook application - * you have to get relation repository as follow: - * - * ```php - * '\Pluf\NoteBook\Item', - * 'target' => '\Pluf\NoteBook\Book', - * 'relation' => 'items', - * ])); - * - * $query->setFilter('id', $item->id); - * $books = $repository->get($query); - * ``` - * As you can see, the get is responsible to list all targets item which is - * joined to the source model based on query. - * - * In the other hand to get list of items from a book: - * - * ```php - * '\Pluf\NoteBook\Book', - * 'target' => '\Pluf\NoteBook\Item', - * 'relation' => 'items', - * ])); - * - * $query->setFilter('book', $book); - * $items = $repository->get($query); - * ``` - * - * ## Query - * - * You are free to use both target and source attributes in the query at - * the same time. How ever there may be same attributes in both source and - * target. To cover this condition, the relation name will be assuemd as - * alias for all target objects. - * - * For example, here is a code to get all chapters of a book which is published: - * - * ```php - * '\Pluf\NoteBook\Book', - * 'target' => '\Pluf\NoteBook\Item', - * 'relation' => 'items', // is used as alias - * ])); - * - * $query->setFilter('book', $book); - * $query->setFilter('items.status', 'published'); - * $items = $repository->get($query); - * ``` - * - * - * - * {@inheritdoc} - * @see \Pluf\Data\Repository::get() - */ - public function get(?Query $query = null) - { - if (! isset($query)) { - $query = new Query(); - } - if ($query->hasView()) { - throw new Exception([ - 'message' => 'Query view is not supported in relation repository' - ]); - } - - $query->setView([ - 'join' => [ - [ - 'joinProperty' => $this->relation, - 'alias' => $this->relation - ] - ] - ]); - - $repo = Repository::getInstance([ - 'model' => $this->source, - 'connection' => $this->getConnection(), - 'schema' => $this->getSchema() - ]); - return $repo->get($query); - } - - /** - * Creates new relation from $srouce to $target - * - * {@inheritdoc} - * @see \Pluf\Data\Repository::create() - */ - public function create($source, $target = null) - { - if (! isset($target)) { - throw new Exception([ - 'message' => 'It is not possible to put null object into relation repository' - ]); - } - $schema = $this->getSchema(); - - $smd = $this->getModelDescription($this->source); - $tmd = $this->getModelDescription($this->target); - $relation = ModelUtils::getRelationProperty($smd, $tmd, $this->relation); - - switch ($relation->type) { - case Schema::MANY_TO_MANY: - // 1. define table: - $relationTable = $schema->getRelationTable($smd, $tmd, $relation); - - // 2. get relation fields - $relationSourceField = $schema->getRelationSourceField($smd, $tmd, $relation, false); - $relationTargetField = $schema->getRelationTargetField($smd, $tmd, $relation, false); - - $connection = $this->getConnection(); - // NOTE: suppose the id is unique for source - // NOTE: suppose the id is unique for target - $stm = $connection->query() - ->mode('insert') - ->table($relationTable) - ->set($relationSourceField, $source->id) - ->set($relationTargetField, $target->id) - ->execute(); - - $this->checkStatement($stm); - break; - default: - throw new Exception([ - 'message' => 'Relation type {type} is not supported in Create from relation repository.' - ]); - } - } - - /** - * Deletes the relation from $srouce to $target - * - * {@inheritdoc} - * @see \Pluf\Data\Repository::delete() - */ - public function delete($source, $target = null) - { - if (! isset($target)) { - throw new Exception([ - 'message' => 'It is not possible to put null object into relation repository' - ]); - } - - $smd = $this->getModelDescription($this->source); - $tmd = $this->getModelDescription($this->target); - $schema = $this->getSchema(); - - $rp = ModelUtils::getRelationProperty($smd, $tmd, $this->relation); - - // 1. define table: - $relationTable = $schema->getRelationTable($smd, $tmd, $rp); - - // 2. get relation fields - $relationSourceField = $schema->getRelationSourceField($smd, $tmd, $rp); - $relationTargetField = $schema->getRelationTargetField($smd, $tmd, $rp); - - $connection = $this->getConnection(); - // NOTE: suppose the id is unique for source - // NOTE: suppose the id is unique for target - $stm = $connection->query() - ->mode('delete') - ->table($relationTable) - ->where($relationSourceField, '=', $source->id) - ->where($relationTargetField, '=', $target->id) - ->execute(); - - $this->checkStatement($stm); - } - - /** - * Updates the relation. - * - * NOTE: it is not common fro relations - * - * {@inheritdoc} - * @see \Pluf\Data\Repository::update() - */ - public function update($source, $target = null) - { - throw new Exception([ - 'message' => 'It is not possible to update a relation' - ]); - } - - /** - * - * {@inheritdoc} - * @see \Pluf\Data\Repository::clean() - */ - protected function clean() - { - // load model description - $this->sourceMd = null; - $this->targetMd = null; - $this->relationPd = null; - } - -} - - - diff --git a/src/Data/Schema.php b/src/Data/Schema.php deleted file mode 100644 index f4ccb7a1..00000000 --- a/src/Data/Schema.php +++ /dev/null @@ -1,1195 +0,0 @@ - array( - '\Pluf\Data\Schema::booleanFromDb', - '\Pluf\Data\Schema::booleanToDb' - ), - self::DATE => array( - '\Pluf\Data\Schema::identityFromDb', - '\Pluf\Data\Schema::identityToDb' - ), - self::DATETIME => array( - '\Pluf\Data\Schema::identityFromDb', - '\Pluf\Data\Schema::identityToDb' - ), - self::EMAIL => array( - '\Pluf\Data\Schema::identityFromDb', - '\Pluf\Data\Schema::identityToDb' - ), - self::FILE => array( - '\Pluf\Data\Schema::identityFromDb', - '\Pluf\Data\Schema::identityToDb' - ), - self::FLOAT => array( - '\Pluf\Data\Schema::floatFromDb', - '\Pluf\Data\Schema::floatToDb' - ), - self::MANY_TO_ONE => array( - '\Pluf\Data\Schema::sequenceFromDb', - '\Pluf\Data\Schema::sequenceToDb' - ), - self::FOREIGNKEY => array( - '\Pluf\Data\Schema::sequenceFromDb', - '\Pluf\Data\Schema::sequenceToDb' - ), - self::INTEGER => array( - '\Pluf\Data\Schema::integerFromDb', - '\Pluf\Data\Schema::integerToDb' - ), - self::PASSWORD => array( - '\Pluf\Data\Schema::identityFromDb', - '\Pluf\Data\Schema::passwordToDb' - ), - self::SEQUENCE => array( - '\Pluf\Data\Schema::sequenceFromDb', - '\Pluf\Data\Schema::sequenceToDb' - ), - self::SLUG => array( - '\Pluf\Data\Schema::identityFromDb', - '\Pluf\Data\Schema::slugToDb' - ), - self::TEXT => array( - '\Pluf\Data\Schema::identityFromDb', - '\Pluf\Data\Schema::identityToDb' - ), - self::VARCHAR => array( - '\Pluf\Data\Schema::identityFromDb', - '\Pluf\Data\Schema::identityToDb' - ), - self::SERIALIZED => array( - '\Pluf\Data\Schema::serializedFromDb', - '\Pluf\Data\Schema::serializedToDb' - ), - self::COMPRESSED => array( - '\Pluf\Data\Schema::compressedFromDb', - '\Pluf\Data\Schema::compressedToDb' - ), - self::GEOMETRY => array( - '\Pluf\Data\Schema::geometryFromDb', - '\Pluf\Data\Schema::geometryToDb' - ) - ); - - protected string $prefix = ''; - - function __construct(?Options $options = null) - { - $this->setDefaults($options); - } - - /** - * - * @return string - */ - public function getPrefix() - { - return $this->prefix; - } - - /** - * - * @param string $prefix - */ - public function setPrefix($prefix) - { - $this->prefix = $prefix; - } - - /** - * Create the tables and indexes for the current model. - * - * If the model is a mapped model ($model->_a['mapped'] == true) then only tables for its - * many to many relations will be created and table for the model will not be created. - * - * A mapped model is a model which have not a separate table. In other word, a mapped model is - * a specific view to another model and is not a real model. - * - * A mapped model may defines some new many to many relations which was not defined in the main model. - * - * @return mixed True if success or database error. - */ - public function createTables(Connection $connection, ModelDescription $model): bool - { - $sql = $this->createTableQueries($model); - // Note: hadi, 2019: If model is a mapped model, its table is created or will be created by a none mapped model. - if ($model->isMapped()) { - $modelTableName = $this->getTableName($model); - // remove sql to create main table - $sql = array_diff_key($sql, array( - $modelTableName => '' - )); - } - - foreach ($sql as $query) { - $connection->expr($query)->execute(); - } - - if (! $model->isMapped()) { - $sql = $this->createIndexQueries($model); - foreach ($sql as $query) { - $connection->expr($query)->execute(); - } - } - return true; - } - - /** - * Drop the tables and indexes for the current model. - * - * @return mixed True if success or database error. - */ - public function dropTables(Connection $connection, ModelDescription $model): bool - { - $sql = $this->dropTableQueries($model); - // Note: hadi, 2019: If model is a mapped model, its table is created or will be created by a none mapped model. - if ($model->isMapped()) { - $modelTableName = $this->getTableName($model); - // remove sql to create main table - $sql = array_diff_key($sql, array( - $modelTableName => '' - )); - } - foreach ($sql as $query) { - $connection->expr($query)->execute(); - } - return true; - } - - /** - * Fetchs joine table for Many To Many relations - * - * @param ModelDescription $smd - * @param ModelDescription $tmd - * @param ModelProperty $relation - * @throws Exception - * @return string - */ - public function getRelationTable(ModelDescription $smd, ModelDescription $tmd, ModelProperty $relation): string - { - if ($relation->type != self::MANY_TO_MANY) { - throw new Exception([ - 'message' => 'The relation {name} from {srouce} is not assigned to a table.', - 'source' => $smd->type, - 'name' => $relation->name - ]); - } - - // // joineModel - // $joineModel = $relation->inverseJoinModel; - // if (isset($joineModel)) { - // $joineModelDescription = ModelDescription::getInstance($joineModel); - // return $this->getTableName($joineModelDescription); - // } - - // joineTable - $joineTable = $relation->joinTable; - if (isset($joineTable)) { - return $this->prefix . $joineTable; - } - - // crate default table name - $hay = array( - strtolower($smd->type), - strtolower($tmd->type) - ); - sort($hay); - return self::skipeName($this->prefix . $hay[0] . '_' . $hay[1] . '_assoc'); - } - - /** - * Gets source relation columne name - * - * @param ModelDescription $smd - * Source model in relation - * @param ModelDescription $tmd - * Target model in relation - * @param ModelProperty $relation - * The relation description - * @return string name of the columne in the relation - */ - public function getRelationSourceField(ModelDescription $smd, ModelDescription $tmd, ModelProperty $relation, ?bool $qn = true): string - { - $name = $relation->joinColumne; - if (! isset($name)) { - $name = self::skipeName(strtolower($smd->type) . '_id'); - } - if ($qn) { - $name = $this->qn($name); - } - return $name; - } - - /** - * Gets target relation columne name - * - * @param ModelDescription $smd - * Source model in relation - * @param ModelDescription $tmd - * Target model in relation - * @param ModelProperty $relation - * The relation description - * @return string name of the columne in the relation - */ - public function getRelationTargetField(ModelDescription $smd, ModelDescription $tmd, ModelProperty $relation, ?bool $qut = true): string - { - $name = $relation->inverseJoinColumne; - if (! isset($name)) { - $name = self::skipeName(strtolower($tmd->type) . '_id'); - } - if ($qut) { - $name = $this->qn($name); - } - return $name; - } - - public function getAssocField(ModelDescription $model, ?string $relationName = null): String - { - $name = self::skipeName(strtolower($model->model) . '_id'); - $name = $this->qn($name); - return $name; - } - - /** - * Generate real table name for model - * - * @param ModelDescription $modelDescription - * to fetch table name for - * @return string real table name - */ - public function getTableName(ModelDescription $modelDescription): string - { - return str_replace('\\', '_', $this->prefix . $modelDescription->table); - } - - /** - * Gets attributes to put into the DB - * - * @param ModelDescription $md - * @param mixed $alias - * to be used for all properties - */ - public function getFields(ModelDescription $md, ?string $alias = null) - { - $field = []; - $autoPrefix = ! isset($alias); - if ($autoPrefix) { - $alias = ''; - } else { - $alias = $alias . '.'; - } - foreach ($md as $name => $property) { - if ($property->type == self::MANY_TO_MANY || $property->type == self::MANY_TO_ONE || $property->type == self::ONE_TO_MANY) { - continue; - } - $field[$name] = $alias . $this->getFieldName($md, $property, $autoPrefix); - } - return $field; - } - - /** - * Get db field name for the $name attribute of the model - * - * The name may contains the alias (e.g. account.login) or a property name. - * The result is a valid DB name contians the alias. - * - * @param ModelDescription $md - * @param string $name - * @return string - */ - public function getField(ModelDescription $md, string $name): string - { - $names = explode('.', $name); - if (count($names) == 1) { - return $this->getFieldName($md, $md->$name, true); - } - $name = $names[1]; - return $names[0] . '.' . $this->getFieldName($md, $md->$name, false); - } - - /** - * Convert property name to DB name - * - * @param ModelDescription $md - * @param ModelProperty $property - * @param bool $joinTable - * The table name is added to property if it is ture otherwise the porperty name will be returned - */ - public function getFieldName(ModelDescription $md, ModelProperty $property, ?bool $joinTable = true): string - { - $name = $property->columne; - if (! isset($name)) { - $name = $property->name; - } - if ($joinTable) { - return $this->getTableName($md) . '.' . $name; - } - return $name; - } - - /** - * Converts $view fields into valid DB fields - * - * @param ModelDescription $md - * Reference data model description - * @param array $view - * A view based on Pluf/Data specification - */ - public function getViewFields(ModelDescription $md, array $view): array - { - if (! isset($view['field'])) { - return []; - } - $fields = []; - foreach ($view['field'] as $name => $field) { - if ($field instanceof Expression) { - $fields[$name] = $field; - continue; - } - - // extract alias - $names = explode('.', $field); - - // get model description - $cmd = $md; - if (count($names) > 1) { - // find model in joins - foreach ($view['join'] as $join) { - if (isset($join['alias']) && $join['alias'] == $names[0]) { - if (isset($join['inverseJoinModel'])) { - $cmd = ModelDescription::getInstance($join['inverseJoinModel']); - break; - } - $jproname = $join['joinProperty']; - $joinProperty = $md->$jproname; - $cmd = ModelDescription::getInstance($joinProperty->inverseJoinModel); - break; - } - } - $fields[$name] = $names[0] . '.' . $this->getField($cmd, $field, false); - } else { - $fields[$name] = $this->getField($md, $field, true); - } - } - return $fields; - } - - /** - * Converts data join in $view to DB join - * - * @param ModelDescription $ref - * The reference data model description - * @param array $view - * Data view to convert - * @return array an array of db views - */ - public function getViewJoins(ModelDescription $joinModel, array $view): array - { - $joins = []; - if (! isset($view['join'])) { - return $joins; - } - foreach ($view['join'] as $join) { - $type = self::LEFT_JOIN; - $alias = null; - if (isset($join['type'])) { - $type = $join['type']; - } - if (isset($join['alias'])) { - $alias = $join['alias']; - } - - /* - * Native joing - * - * TODO: maso, 2020: support joinProperty - */ - if (isset($join['joinTable'])) { - $ftable = $join['joinTable'] . '.' . $join['inverseJoinColumne']; - if (isset($alias)) { - $ftable = $ftable . ' ' . $alias; - } - $joins[] = [ - $ftable, - $join['joinColumne'], - $type - ]; - continue; - } - - /* - * Data Join - */ - $jproName = $join['joinProperty']; - if (! isset($jproName)) { - $jproName = 'id'; - } - /* - * required data - */ - $joinProperty = $joinModel->$jproName; - $inverseJoinModel = null; - $inverseJoinProperty = null; - - /* - * Fetch data from model - */ - switch ($joinProperty->type) { - case self::ONE_TO_MANY: - case self::MANY_TO_ONE: - case self::MANY_TO_MANY: - // Forign key - $inverseJoinModel = ModelDescription::getInstance($joinProperty->inverseJoinModel); - $inverseJoinPropertyName = $joinProperty->inverseJoinProperty; - if (! isset($inverseJoinPropertyName)) { - $inverseJoinPropertyName = 'id'; - } - $inverseJoinProperty = $inverseJoinModel->$inverseJoinPropertyName; - break; - default: - $inverseJoinModel = ModelDescription::getInstance($join['inverseJoinModel']); - $inverseJoinPropertyName = $join['inverseJoinProperty']; - if (! isset($inverseJoinPropertyName)) { - $inverseJoinPropertyName = 'id'; - } - $inverseJoinProperty = $inverseJoinModel->$inverseJoinPropertyName; - break; - } - - $dbJoins = $this->createDbJoin($joinModel, $joinProperty, $inverseJoinModel, $inverseJoinProperty, $type, $alias); - $joins = array_merge($joins, $dbJoins); - } - return $joins; - } - - private function createDbJoin(ModelDescription $joinModel, ModelProperty $joinProperty, // - ModelDescription $inverseJoinModel, ModelProperty $inverseJoinProperty, // - string $type, ?string $alias = ''): array - { - $joins = []; - - /* - * Fetch data from model - */ - switch ($joinProperty->type) { - case self::MANY_TO_MANY: - // First - $rtable = $this->getRelationTable($joinModel, $inverseJoinModel, $joinProperty); - - $propertyName = $joinProperty->joinProperty; - if (! isset($propertyName)) { - $propertyName = 'id'; - } - $joinProperty = $joinModel->$propertyName; - - $joins[] = [ - $rtable . '.' . $this->getRelationSourceField($joinModel, $inverseJoinModel, $joinProperty), - $this->getFieldName($joinModel, $joinProperty, true), - $type - ]; - - // second - $ttableField = $this->getRelationTargetField($joinModel, $inverseJoinModel, $joinProperty); - $joins[] = [ - $rtable . '.' . $ttableField . ' ' . $alias, - $this->getFieldName($inverseJoinModel, $inverseJoinProperty, true), - $type - ]; - break; - case self::ONE_TO_MANY: - $ftable = $this->getFieldName($inverseJoinModel, $inverseJoinProperty, true); - if (isset($alias)) { - $ftable .= ' ' . $alias; - } - // get real property - $propertyName = $joinProperty->joinProperty; - if (! isset($propertyName)) { - $propertyName = 'id'; - } - $rjp = $joinModel->$propertyName; - $joins[] = [ - $ftable, - $this->getFieldName($joinModel, $rjp), - $type - ]; - break; - case self::MANY_TO_ONE: - if ($inverseJoinProperty->type == self::ONE_TO_MANY) { - $propertyName = $inverseJoinModel->joinProperty; - if (! isset($propertyName)) { - $propertyName = 'id'; - } - $inverseJoinProperty = $inverseJoinModel->$propertyName; - } - $ftable = $this->getFieldName($inverseJoinModel, $inverseJoinProperty, true); - if (isset($alias)) { - $ftable .= ' ' . $alias; - } - $joins[] = [ - $ftable, - $this->getFieldName($joinModel, $joinProperty), - $type - ]; - break; - default: - $ftable = $this->getFieldName($inverseJoinModel, $inverseJoinProperty, true); - if (isset($alias)) { - $ftable .= ' ' . $alias; - } - $joins[] = [ - $ftable, - $this->getFieldName($joinModel, $joinProperty), - $type - ]; - break; - } - return $joins; - } - - /** - * Converts filters form Data view into DB filter - * - * @param ModelDescription $md - * @param array $view - * @return array - */ - public function getViewFilters(ModelDescription $md, $view) - { - if (! isset($view['filter'])) { - return []; - } - return $this->convertViewDataFilters($md, $view, $view['filter']); - } - - public function getViewHaving(ModelDescription $md, $view) - { - if (! isset($view['having'])) { - return []; - } - return $this->convertViewDataFilters($md, $view, $view['having']); - } - - public function getViewGroupBy(ModelDescription $md, $view) - { - if (! isset($view['group'])) { - return null; - } - $groups = []; - foreach ($view['group'] as $group) { - if ($group instanceof Expression) { - $groups[] = $group; - continue; - } - - $names = explode('.', $group); - - // get model description - $cmd = $md; - if (count($names) > 1) { - // find model in joins - foreach ($view['join'] as $join) { - if (isset($join['alias']) && $join['alias'] == $names[0]) { - if (isset($join['inverseJoinModel'])) { - $cmd = ModelDescription::getInstance($join['inverseJoinModel']); - break; - } - $jproname = $join['joinProperty']; - $joinProperty = $md->$jproname; - $cmd = ModelDescription::getInstance($joinProperty->inverseJoinModel); - break; - } - } - $groups[] = $this->getField($cmd, $group); - } else { - $groups[] = $this->getField($md, $group); - } - } - if (count($groups) == 0) { - return null; - } - return $groups; - } - - /** - * Gets model values to put into the DB - * - * @param ModelDescription $md - * @param mixed $model - */ - public function getValues(ModelDescription $md, $model) - { - $values = []; - foreach ($md as $name => $property) { - if ($name == 'id') { - // DB is responsible for ID - continue; - } - if ($property->type == self::MANY_TO_MANY || $property->type == self::ONE_TO_MANY || $property->isMapped()) { - // Virtural attributes - continue; - } - if ($property->type == self::MANY_TO_ONE) { - // XXX: maso, 2020: check forenkey - } - $values[$name] = $this->toDb($property, $model->$name); - } - return $values; - } - - /** - * Converts Data filter from query into DB filter - * - * @param ModelDescription $md - * @param Query $query - * @return array - */ - public function getQueryFilters(ModelDescription $md, Query $query) - { - return $this->convertDataFilters($md, $query->getFilter()); - } - - /** - * Retrieve key relationships of a given model. - * - * @param string $model - * @param string $type - * Relation Schema::MANY_TO_ONE or Schema::MANY_TO_MANY - * @return array Key relationships. - */ - public function getRelationKeysTo(ModelDescription $from, ModelDescription $to, $type) - { - $properies = []; - foreach ($from as $name => $property) { - if ($property->type === $type && $property->model === $to->type) { - $properies[] = $name; - } - } - return $properies; - } - - /** - * Creates new model and fill with data - * - * @param ModelDescription $md - * @param mixed $data - * @return mixed - */ - public function newInstance(ModelDescription $md, $data) - { - $model = $md->newInstance(); - return $this->fillModel($md, $model, $data); - } - - /** - * Fills the model with data from DB - * - * @param ModelDescription $md - * @param mixed $model - */ - public function fillModel(ModelDescription $md, $model, $data) - { - foreach ($md as $property) { - if ($property->type == self::MANY_TO_MANY) { - continue; - } - if ($property->type == self::ONE_TO_MANY) { - continue; - } - $name = $property->name; - if (isset($data[$name])) { - $model->$name = $this->fromDb($property, $data[$name]); - } - } - return $model; - } - - /** - * Converts a data value into valid DB value - * - * @param ModelProperty $property - * @param mixed $value - * @return mixed - */ - public function toDb(ModelProperty $property, $value) - { - $map = $this->type_cast[$property->type]; - return call_user_func_array($map[1], [ - $value, - $property - ]); - } - - /** - * Converts a DB value into a valid data value - * - * @param ModelProperty $property - * @param mixed $value - * @return mixed - */ - public function fromDb(ModelProperty $property, $value) - { - $map = $this->type_cast[$property->type]; - return call_user_func_array($map[0], [ - $value, - $property - ]); - } - - // XXX: maso, 2020: check if this is usefull anymoer - public function skipeName(String $name): String - { - $name = str_replace('\\', '_', $name); - return $name; - } - - private function convertViewDataFilters(ModelDescription $md, $view, $dataFilter) - { - $filters = []; - if (! isset($dataFilter) || count($dataFilter) == 0) { - return $filters; - } - foreach ($dataFilter as $filter) { - if ($filter instanceof Expression) { - $filters[] = $filter; - continue; - } - // or expression - if (is_array($filter[0])) { - $orFilters = []; - foreach ($filter as $orFilter) { - $orFilters[] = $this->convertViewDataWhereToDb($md, $view, $orFilter); - } - $filters[] = $orFilters; - continue; - } - $filters[] = $this->convertViewDataWhereToDb($md, $view, $filter); - } - return $filters; - } - - /* - * converts list of data filter - */ - private function convertDataFilters(ModelDescription $md, $dataFilter) - { - $filters = []; - if (! isset($dataFilter) || count($dataFilter) == 0) { - return $filters; - } - foreach ($dataFilter as $filter) { - if ($filter instanceof Expression) { - $filters[] = $filter; - continue; - } - // or expression - if (is_array($filter[0])) { - $orFilters = []; - foreach ($filter as $orFilter) { - $orFilters[] = $this->convertDataWhereToDb($md, $orFilter); - } - $filters[] = $orFilters; - continue; - } - $filters[] = $this->convertDataWhereToDb($md, $filter); - } - return $filters; - } - - /* - * converts a filter to db filter - * - * NOTE: all attribute are considerd to be from $md - */ - private function convertDataWhereToDb(ModelDescription $md, $filter) - { - if ($filter instanceof Expression) { - return $filter; - } - - $dfilter = []; - $names = explode('.', $filter[0]); - if (count($names) == 1) { - $name = $filter[0]; - $property = $md->$name; - $dfilter[] = $this->getFieldName($md, $property); - } else { - $name = $names[1]; - $property = $md->$name; - $dfilter[] = $names[0] . '.' . $this->getFieldName($md, $property, false); - } - - if (count($filter) == 2) { - $value = $filter[1]; - } else { - $value = $filter[2]; - $dfilter[] = $filter[1]; // operation = > < in , .. - } - - if ($value instanceof Expression) { - $dfilter[] = $value; - } else { - $dfilter[] = $this->toDb($property, $value); - } - return $dfilter; - } - - /* - * converts a filter to db filter - * - * Note: search alias in view joins - */ - private function convertViewDataWhereToDb(ModelDescription $md, $view, $filter) - { - if ($filter instanceof Expression) { - return $filter; - } - - $dfilter = []; - $names = explode('.', $filter[0]); - if (count($names) == 1) { - $name = $names[0]; - $property = $md->$name; - $dfilter[] = $this->getFieldName($md, $property); - } else { - $name = $names[1]; - foreach ($view['join'] as $join) { - if (isset($join['alias']) && $join['alias'] == $names[0]) { - /* - * Native Join - */ - if (isset($join['joinTable'])) { - return $filter; - } - /* - * Data Join - */ - if (isset($join['inverseJoinModel'])) { - $cmd = ModelDescription::getInstance($join['inverseJoinModel']); - break; - } - $jproname = $join['joinProperty']; - $joinProperty = $md->$jproname; - $cmd = ModelDescription::getInstance($joinProperty->inverseJoinModel); - break; - } - } - $property = $cmd->$name; - $dfilter[] = $names[0] . '.' . $this->getFieldName($cmd, $property, false); - } - - if (count($filter) == 2) { - $value = $filter[1]; - } else { - $value = $filter[2]; - $dfilter[] = $filter[1]; // operation = > < in , .. - } - - if ($value instanceof Expression) { - $dfilter[] = $value; - } else { - $dfilter[] = $this->toDb($property, $value); - } - return $dfilter; - } - - /* - * ----------------------------------------------------------------- - * Abstract Part - * ----------------------------------------------------------------- - */ - - /** - * Quote the column name. - * - * @param - * string Name of the column - * @return string Escaped name - */ - public abstract function qn(string $name): string; - - public abstract function createTableQueries(ModelDescription $model): array; - - /** - * Get the SQL to drop the tables corresponding to the model. - * - * @param ModelDescription $model - * Model to create sql for - * @return array SQL strings ready to execute. - */ - public abstract function dropTableQueries(ModelDescription $model): array; - - public abstract function createIndexQueries(ModelDescription $model): array; - - public abstract function createConstraintQueries(ModelDescription $model): array; - - public abstract function dropConstraintQueries(ModelDescription $model): array; - - /** - * Creates new instance of the schema - * - * @param Options $options - * @throws Exception - * @return Schema - */ - public static function getInstance(Options $options): Schema - { - $type = $options->engine; - if (! isset($type)) { - $type = 'sqlite'; - } - switch ($type) { - case 'mysql': - $engine = new Schema\MySQLSchema($options->startsWith('mysql_', true)); - break; - case 'sqlite': - $engine = new Schema\SQLiteSchema($options->startsWith('sqlite_', true)); - break; - default: - throw new Exception('Engine type "' . $type . '" is not supported with Pluf Data Schema.'); - } - return $engine; - } - - /** - * Identity function. - * - * @params - * mixed Value - * @return mixed Value - */ - public static function identityFromDb($val) - { - return $val; - } - - /** - * Identity function. - * - * @param - * mixed Value. - * @param - * object Database handler. - * @return string Ready to use for SQL. - */ - public static function identityToDb($val) - { - if (null === $val) { - return null; - } - return $val; - } - - public static function serializedFromDb($val) - { - if ($val) { - return unserialize($val); - } - return $val; - } - - public static function serializedToDb($val) - { - if (null === $val) { - return null; - } - return serialize($val); - } - - public static function compressedFromDb($val) - { - return ($val) ? gzinflate($val) : $val; - } - - public static function compressedToDb($val) - { - return (null === $val) ? null : gzdeflate($val, 9); - } - - public static function booleanFromDb($val) - { - if ($val) { - return true; - } - return false; - } - - public static function booleanToDb($val) - { - if (null === $val) { - return null; - } - if ($val) { - return '1'; - } - return '0'; - } - - public static function sequenceFromDb($val, ModelProperty $property) - { - return $val; - } - - public static function sequenceToDb($val, ModelProperty $property) - { - if (! isset($val)) { - return null; - } - switch ($property->type) { - case self::SEQUENCE: - case self::MANY_TO_ONE: - case self::FOREIGNKEY: - if ($val instanceof \Pluf\Data\Model) { - return $val->id; - } - if (is_numeric($val)) { - return $val; - } - default: - throw new Exception([ - 'message' => 'Property value is not convertable to db' - ]); - } - } - - public static function integerFromDb($val) - { - return (null === $val) ? null : (int) $val; - } - - public static function integerToDb($val) - { - return (null === $val) ? null : (string) (int) $val; - } - - public static function floatFromDb($val) - { - return (null === $val) ? null : (float) $val; - } - - public static function floatToDb($val) - { - return (null === $val) ? null : (string) (float) $val; - } - - public static function passwordToDb($val) - { - $exp = explode(':', $val); - if (in_array($exp[0], array( - 'sha1', - 'md5', - 'crc32' - ))) { - return $val; - } - // We need to hash the value. - $salt = Utils::getRandomString(5); - return 'sha1:' . $salt . ':' . sha1($salt . $val); - } - - public static function slugFromDB($val) - { - return $val; - } - - public static function slugToDB($val) - { - return $val; - } -} - - diff --git a/src/Data/Schema/MySQLSchema.php b/src/Data/Schema/MySQLSchema.php deleted file mode 100644 index 11e80e7e..00000000 --- a/src/Data/Schema/MySQLSchema.php +++ /dev/null @@ -1,386 +0,0 @@ - 'varchar(%s)', - self::SEQUENCE => 'mediumint(9) unsigned not null auto_increment', - self::BOOLEAN => 'bool', - self::DATE => 'date', - self::DATETIME => 'datetime', - self::FILE => 'varchar(250)', - self::MANY_TO_MANY => null, - self::ONE_TO_MANY => null, - self::MANY_TO_ONE => 'mediumint(9) unsigned', - self::FOREIGNKEY => 'mediumint(9) unsigned', - self::TEXT => 'longtext', - self::HTML => 'longtext', - self::TIME => 'time', - self::INTEGER => 'integer', - self::EMAIL => 'varchar(150)', - self::PASSWORD => 'varchar(150)', - self::FLOAT => 'numeric(%s, %s)', - self::BLOB => 'blob', - self::GEOMETRY => 'GEOMETRY' - ); - - public $defaults = array( - self::VARCHAR => "''", - self::SEQUENCE => null, - self::SEQUENCE => 1, - self::DATE => 0, - self::DATETIME => 0, - self::FILE => "''", - self::MANY_TO_MANY => null, - self::MANY_TO_ONE => 0, - self::ONE_TO_MANY => null, - self::FOREIGNKEY => 0, - self::TEXT => "''", - self::HTML => "''", - self::TIME => 0, - self::INTEGER => 0, - self::EMAIL => "''", - self::PASSWORD => "''", - self::FLOAT => 0.0, - self::BLOB => "''", - self::GEOMETRY => null - ); - - private $con = null; - - /** - * Creates new instance of the schema - * - * @param Options $options - */ - public function __construct(?Options $options = null) - { - parent::__construct($options); - } - - /** - * Workaround for which limits the - * length of foreign key identifiers to 64 characters. - * - * @param - * string - * @return string - */ - function getShortenedFKeyName($name) - { - if (strlen($name) <= 64) { - return $name; - } - return substr($name, 0, 55) . '_' . substr(md5($name), 0, 8); - } - - /** - * Get the SQL to create the constraints for the given model - * - * @param - * Object Model - * @return array Array of SQL strings ready to execute. - */ - public function createConstraintQueries(\Pluf\Data\Model $model): array - { - $smd = ModelDescription::getInstance($model); - $table = $this->getTableName($smd); - $alter_tbl = 'ALTER TABLE ' . $table; - // $cols = $model->_a['cols']; - $constraints = []; - $manytomany = []; - $manytooen = []; - - foreach ($smd as $property) { - if ($property->type == self::MANY_TO_MANY) { - $manytomany[] = $property; - } - if ($property->type == self::MANY_TO_ONE) { - $manytooen[] = $property; - } - } - - // Forigne Keys - foreach ($manytooen as $property) { - $tmd = ModelDescription::getInstance($property->model); - $referto = $this->getTableName($tmd); - // Add the foreignkey constraints - $constraints[] = $alter_tbl . ' ADD CONSTRAINT ' . $this->getShortenedFKeyName($table . '_' . $property->name . '_fkey') . ' - FOREIGN KEY (' . $this->qn($property->name) . ') - REFERENCES ' . $this->getTableName($referto) . ' (id) - ON DELETE NO ACTION ON UPDATE NO ACTION'; - } - - // Now for the many to many - foreach ($manytomany as $relation) { - $tmd = ModelDescription::getInstance($relation->model); - $table = $this->getRelationTable($smd, $tmd, $relation); - $alter_tbl = 'ALTER TABLE ' . $table; - $constraints[] = $alter_tbl . ' ADD CONSTRAINT ' . $this->getShortenedFKeyName($table . '_fkey1') . ' - FOREIGN KEY (' . $this->getRelationSourceField($smd, $tmd, $relation) . ') - REFERENCES ' . $this->getTableName($smd) . ' (id) - ON DELETE NO ACTION ON UPDATE NO ACTION'; - $constraints[] = $alter_tbl . ' ADD CONSTRAINT ' . $this->getShortenedFKeyName($table . '_fkey2') . ' - FOREIGN KEY (' . $this->getRelationTargetField($smd, $tmd, $relation) . ') - REFERENCES ' . $this->getTableName($tmd) . ' (id) - ON DELETE NO ACTION ON UPDATE NO ACTION'; - } - return $constraints; - } - - /** - * Get the SQL to drop the constraints for the given model - * - * @param - * Object Model - * @return array Array of SQL strings ready to execute. - */ - public function dropConstraintQueries(\Pluf\Data\Model $model): array - { - $table = $this->prefix . $model->_a['table']; - $constraints = array(); - $alter_tbl = 'ALTER TABLE ' . $table; - $cols = $model->_a['cols']; - $manytomany = array(); - - foreach ($cols as $col => $val) { - // remember these for later - $type = $val['type']; - if ($type == self::MANY_TO_MANY) { - $manytomany[] = $col; - } - if ($type == self::FOREIGNKEY) { - // Add the foreignkey constraints - // $referto = new $val['model'](); - $constraints[] = $alter_tbl . ' DROP CONSTRAINT ' . $this->getShortenedFKeyName($table . '_' . $col . '_fkey'); - } - } - - // Now for the many to many - foreach ($manytomany as $many) { - $omodel = new $cols[$many]['model'](); - $table = ModelUtils::getAssocTable($model, $omodel); - $alter_tbl = 'ALTER TABLE ' . $table; - $constraints[] = $alter_tbl . ' DROP CONSTRAINT ' . $this->getShortenedFKeyName($table . '_fkey1'); - $constraints[] = $alter_tbl . ' DROP CONSTRAINT ' . $this->getShortenedFKeyName($table . '_fkey2'); - } - return $constraints; - } - - /** - * Quote the column name. - * - * @param - * string Name of the column - * @return string Escaped name - */ - function qn(string $col): string - { - $cols = explode(',', $col); - // $colArray = array(); - foreach ($cols as $myCol) { - $colsArray[] = '`' . trim($myCol) . '`'; - } - return implode(',', $colsArray); - } - - /** - * - * {@inheritdoc} - * @see \Pluf\Data\Schema::dropTableQueries() - */ - public function dropTableQueries(\Pluf\Data\Model $model): array - { - $cols = $model->_a['cols']; - $modelTable = $this->getTableName($model); - $manytomany = array(); - $sql = 'DROP TABLE IF EXISTS `' . $modelTable . '`'; - - foreach ($cols as $col => $val) { - if ($val['type'] == self::MANY_TO_MANY) { - $manytomany[] = $col; - } - } - - // Now for the many to many - foreach ($manytomany as $many) { - $omodel = new $cols[$many]['model'](); - $table = $this->getRelationTable($model, $omodel); - $sql .= ', `' . $table . '`'; - } - return array( - $sql - ); - } - - /** - * - * {@inheritdoc} - * @see \Pluf\Data\Schema::createTableQueries() - */ - public function createTableQueries(\Pluf\Data\Model $model): array - { - $tables = array(); - $cols = $model->_a['cols']; - $manytomany = array(); - $sql = 'CREATE TABLE `' . $this->prefix . $model->_a['table'] . '` ('; - - foreach ($cols as $col => $val) { - // $field = new $val['type'](); - $type = $val['type']; - if ($type != self::MANY_TO_MANY) { - $sql .= $this->qn($col) . ' '; - $_tmp = $this->mappings[$type]; - if ($type == self::VARCHAR) { - if (isset($val['size'])) { - $_tmp = sprintf($this->mappings[$type], $val['size']); - } else { - $_tmp = sprintf($this->mappings[$type], '150'); - } - } - if ($type == self::FLOAT) { - if (! isset($val['max_digits'])) { - $val['max_digits'] = 32; - } - if (! isset($val['decimal_places'])) { - $val['decimal_places'] = 8; - } - $_tmp = sprintf($this->mappings[$type], $val['max_digits'], $val['decimal_places']); - } - $sql .= $_tmp; - if (empty($val['is_null'])) { - $sql .= ' NOT NULL'; - } - if ($type != self::TEXT && $type != self::BLOB && $type != self::GEOMETRY) { - if (isset($val['default'])) { - $sql .= ' default '; - $sql .= $model->_toDb($val['default'], $col); - } elseif ($type != self::SEQUENCE) { - $sql .= ' default ' . $this->defaults[$type]; - } - } - $sql .= ','; - } else { - $manytomany[] = $col; - } - } - $sql .= ' PRIMARY KEY (`id`))'; - $engine = 'InnoDB'; - if (key_exists('engine', $model->_a)) { - $engine = $model->_a['engine']; - } - $sql .= 'ENGINE=' . $engine . ' DEFAULT CHARSET=utf8;'; - $tables[$this->prefix . $model->_a['table']] = $sql; - - // Now for the many to many - foreach ($manytomany as $many) { - $omodel = new $cols[$many]['model'](); - $table = ModelUtils::getAssocTable($model, $omodel); - - $ra = ModelUtils::getAssocField($model); - $rb = ModelUtils::getAssocField($omodel); - - $sql = 'CREATE TABLE `' . $table . '` ('; - $sql .= $ra . ' ' . $this->mappings[self::FOREIGNKEY] . ' default 0,'; - $sql .= $rb . ' ' . $this->mappings[self::FOREIGNKEY] . ' default 0,'; - $sql .= 'PRIMARY KEY (' . $ra . ', ' . $rb . ')'; - $sql .= ') ENGINE=InnoDB'; - $sql .= ' DEFAULT CHARSET=utf8;'; - $tables[$table] = $sql; - } - return $tables; - } - - public function createIndexQueries(\Pluf\Data\Model $model): array - { - $index = array(); - $indexes = $model->getIndexes(); - $modelTable = $this->getTableName($model); - foreach ($indexes as $idx => $val) { - if (! isset($val['col'])) { - $val['col'] = $idx; - } - $type = ''; - if (isset($val['type']) && strcasecmp($val['type'], 'normal') != 0) { - $type = $val['type']; - } - $index[$modelTable . '_' . $idx] = sprintf('CREATE %s INDEX `%s` ON `%s` (%s);', $type, $idx, $modelTable, $this->qn($val['col'])); - } - foreach ($model->_a['cols'] as $col => $val) { - $type = $val['type']; - if ($type == self::FOREIGNKEY) { - $index[$modelTable . '_' . $col . '_foreignkey'] = sprintf('CREATE INDEX `%s` ON `%s` (`%s`);', $col . '_foreignkey_idx', $modelTable, $col); - } - if (isset($val['unique']) && $val['unique'] == true) { - // Add tenant column to index if config and table are multitenant. - $columns = (Pluf::f('multitenant', false) && $model->_a['multitenant']) ? 'tenant,' . $col : $col; - $index[$modelTable . '_' . $col . '_unique'] = sprintf('CREATE UNIQUE INDEX `%s` ON `%s` (%s);', $col . '_unique_idx', $modelTable, $this->qn($columns)); - } - } - return $index; - } - - /** - * - * @param Object $val - * @return string - */ - public static function geometryFromDb($val) - { - // TODO: maso, 2018: check if we need to use geoPHP::load to load data - // SEE: https://github.com/phayes/geoPHP - /* - * maso, 1395: convert $val (from BLOB) to WKT - * - * 1- SRID - * 2- WKB - * - * See: - * https://dev.mysql.com/doc/refman/5.7/en/gis-data-formats.html#gis-internal-format - */ - if ($val == null) { - return null; - } - $data = unpack("lsrid/H*wkb", $val); - $geometry = geoPHP::load($data['wkb'], 'wkb', TRUE); - $wkt_writer = new WKT(); - $wkt = $wkt_writer->write($geometry); - return $wkt; - } - - /** - * Convert text to geometry - * - * @return string - */ - public static function geometryToDb($val, $db) - { - // TODO: maso, 2018: check if we need to use geoPHP::load to load data - // SEE: https://github.com/phayes/geoPHP - // TODO: hadi 1397-06-16: Here $val should be encoded - // if($db->engine === 'SQLite'){ - // return (null === $val || empty($val)) ? null : "'" . $val . "'"; - // } - if (! isset($val)) { - return null; - } - return new Expression('GeometryFromText("[]")', [ - $val - ]); - } -} - - diff --git a/src/Data/Schema/SQLiteSchema.php b/src/Data/Schema/SQLiteSchema.php deleted file mode 100644 index ab1b9f8f..00000000 --- a/src/Data/Schema/SQLiteSchema.php +++ /dev/null @@ -1,273 +0,0 @@ - 'varchar(%s)', - self::SEQUENCE => 'integer primary key autoincrement', - self::BOOLEAN => 'bool', - self::DATE => 'date', - self::DATETIME => 'datetime', - self::FILE => 'varchar(250)', - self::MANY_TO_MANY => null, - self::MANY_TO_ONE => 'integer', - self::ONE_TO_MANY => null, - self::FOREIGNKEY => 'integer', - self::TEXT => 'text', - self::HTML => 'text', - self::TIME => 'time', - self::INTEGER => 'integer', - self::EMAIL => 'varchar(150)', - self::PASSWORD => 'varchar(150)', - self::FLOAT => 'real', - self::BLOB => 'blob', - self::GEOMETRY => 'text' - ); - - public $defaults = array( - self::VARCHAR => "''", - self::SEQUENCE => null, - self::BOOLEAN => 1, - self::DATE => 0, - self::DATETIME => 0, - self::FILE => "''", - self::MANY_TO_MANY => null, - self::MANY_TO_ONE => 0, - self::ONE_TO_MANY => null, - self::FOREIGNKEY => 0, - self::TEXT => "''", - self::HTML => "''", - self::TIME => 0, - self::INTEGER => 0, - self::EMAIL => "''", - self::PASSWORD => "''", - self::FLOAT => 0.0, - self::BLOB => "''", - self::GEOMETRY => "''" - ); - - private $con = null; - - /** - * Creates new instance of the schema - * - * @param Options $options - */ - function __construct(?Options $options = null) - { - parent::__construct($options); - - // TODO: maso, 2020: load options - - $this->type_cast[self::COMPRESSED] = $this->type_cast['Compressed'] = array( - '\Pluf\Db\SQLiteself::compressedFromDb', - '\Pluf\Db\SQLiteself::compressedToDb' - ); - $this->type_cast[self::GEOMETRY] = $this->type_cast['Compressed'] = array( - '\Pluf\Db\SQLiteself::geometryFromDb', - '\Pluf\Db\SQLiteself::geometryToDb' - ); - } - - public function createTableQueries(ModelDescription $model): array - { - $tables = array(); - // $cols = $model->_a['cols']; - $manytomany = array(); - - $table = $this->getTableName($model); - - $sql_col = array(); - - foreach ($model as $property) { - $type = $property->type; - $name = $property->name; - - if ($property->isMapped()) { - continue; - } - if ($type == self::MANY_TO_ONE && isset($property->joinProperty)) { - continue; - } - if ($type == self::ONE_TO_MANY) { - // will be created on other side - continue; - } - if ($type == self::MANY_TO_MANY) { - $manytomany[] = $property; - continue; - } - $sql = $this->qn($name) . ' '; - $_tmp = $this->mappings[$type]; - switch ($type) { - case self::VARCHAR: - $size = $property->size; - if (! isset($size)) { - $size = 150; - } - $_tmp = sprintf($this->mappings[$type], $size); - break; - case self::FLOAT: - $max_digits = 32; - $decimal_places = 8; - if (isset($property->max_digits)) { - $max_digits = $property->max_digits; - } - if (isset($property->decimal_places)) { - $decimal_places = $property->decimal_places; - } - $_tmp = sprintf($this->mappings[$type], $max_digits, $decimal_places); - break; - } - $sql .= $_tmp; - if (! $property->nullable) { - $sql .= ' not null'; - } - if (isset($property->defaultValue)) { - $sql .= ' default ' . $property->defaultValue; - } elseif ($type != self::SEQUENCE) { - $sql .= ' default ' . $this->defaults[$type]; - } - $sql_col[] = $sql; - } - - $tables[$table] = 'CREATE TABLE ' . $table . ' (' . implode(",", $sql_col) . ');'; - - // Now for the many to many - foreach ($manytomany as $relation) { - $tmd = ModelDescription::getInstance($relation->inverseJoinModel); - - $table = $this->getRelationTable($model, $tmd, $relation); - $ra = $this->getRelationSourceField($model, $tmd, $relation); - $rb = $this->getRelationTargetField($model, $tmd, $relation); - - $sql = 'CREATE TABLE IF NOT EXISTS ' . $table . ' ('; - $sql .= $ra . $this->mappings[self::FOREIGNKEY] . ' default 0,'; - $sql .= $rb . $this->mappings[self::FOREIGNKEY] . ' default 0,'; - $sql .= 'primary key (' . $ra . ', ' . $rb . ')'; - $sql .= ');'; - $tables[$table] = $sql; - } - return $tables; - } - - /** - * Get the SQL to generate the indexes of the given model. - * - * @param - * Object Model - * @return array Array of SQL strings ready to execute. - */ - public function createIndexQueries(ModelDescription $model): array - { - $index = array(); - $idxs = $this->getIndexes($model); - $table = $this->getTableName($model); - foreach ($idxs as $idx => $val) { - if (! isset($val['col'])) { - $val['col'] = $idx; - } - $unique = (isset($val['type']) && ($val['type'] == 'unique')) ? 'UNIQUE ' : ''; - $index[$table . '_' . $idx] = sprintf('CREATE %sINDEX %s ON %s (%s);', $unique, $table . '_' . $idx, $table, self::qn($val['col'])); - } - foreach ($model as $col => $property) { - // $field = new $val['type'](); - $type = $property->type; - if ($type == self::FOREIGNKEY) { - $index[$table . '_' . $col . '_foreignkey'] = sprintf('CREATE INDEX %s ON %s (%s);', $table . '_' . $col . '_foreignkey_idx', $table, self::qn($col)); - } - if ($property->unique) { - // Add tenant column to index if config and table are multitenant. - $columns = (Pluf::getConfig('multitenant', false) && $model->multitinant) ? 'tenant,' . $col : $col; - $index[$table . '_' . $col . '_unique'] = sprintf('CREATE UNIQUE INDEX %s ON %s (%s);', $table . '_' . $col . '_unique_idx', $table, self::qn($columns)); - } - } - return $index; - } - - function getIndexes(ModelDescription $model) - { - return []; - } - - /** - * Get the SQL to drop the tables corresponding to the model. - * - * @param - * Object Model - * @return string SQL string ready to execute. - */ - public function dropTableQueries(ModelDescription $model): array - { - $manytomany = array(); - $sql = array(); - $sql[] = 'DROP TABLE IF EXISTS ' . $this->getTableName($model); - foreach ($model as $property) { - if ($property->type == self::MANY_TO_MANY) { - $manytomany[] = $property; - } - } - - // Now for the many to many - foreach ($manytomany as $many) { - $omodel = ModelDescription::getInstance($many->inverseJoinModel); - $table = $this->getRelationTable($model, $omodel, $many); - $sql[] = 'DROP TABLE IF EXISTS ' . $table; - } - return $sql; - } - - /** - * Quote the column name. - * - * @param - * string Name of the column - * @return string Escaped name - */ - public function qn($col): string - { - return '"' . $col . '"'; - } - - /** - * - * {@inheritdoc} - * @see \Pluf\Data\Schema::createConstraintQueries() - */ - public function createConstraintQueries(ModelDescription $model): array - { - return []; - } - - /** - * SQLite cannot drop foreign keys from existing tables, - * so we skip their deletion completely. - * - * {@inheritdoc} - * @see \Pluf\Data\Schema::dropConstraintQueries() - */ - public function dropConstraintQueries(ModelDescription $model): array - { - return []; - } -} - - diff --git a/src/Date.php b/src/Date.php deleted file mode 100755 index c35674b8..00000000 --- a/src/Date.php +++ /dev/null @@ -1,251 +0,0 @@ -. - */ -namespace Pluf; - -/** - * کلاسی کاربردی برای کار با تاریخ - * - * استفاده از تاریخ در سیستم‌های شبکه بسیار مرسوم است. در پیاده سازی‌های متفاوت - * به دسته‌ای از عملیات‌ها نیاز داریم که تاریخ را دستکاری کنند. در اینجا تمام فراخوانی‌های - * مورد نیاز برای کار با تاریخ آورده شده است. - * - * @author maso - * - */ -class Date -{ - - /** - * Get a GM Date in the format YYYY-MM-DD HH:MM:SS and returns a - * string with the given format in the current timezone. - * - * @param - * string GMDate - * @param - * string Format to be given to strftime ('%Y-%m-%d %H:%M:%S') - * @return string Formated GMDate into the local time - */ - public static function gmDateToString($gmdate, $format = '%Y-%m-%d %H:%M:%S') - { - $time = strtotime($gmdate . 'Z'); - return strftime($format, $time); - } - - /** - * Get a GM Date in the format YYYY-MM-DD HH:MM:SS and returns a - * string with the given format in GMT. - * - * @param - * string GMDate - * @param - * string Format to be given to date ('c') - * @return string Formated GMDate into GMT - */ - public static function gmDateToGmString($gmdate, $format = 'c') - { - $time = strtotime($gmdate . 'Z'); - return date($format, $time); - } - - /** - * Day compare. - * - * Compare if the first date is before or after the second date. - * Returns: - * 0 if the days are the same. - * 1 if the first date is before the second. - * -1 if the first date is after the second. - * - * @param - * string YYYY-MM-DD date. - * @param - * string YYYY-MM-DD date (today local time). - * @return int - */ - public static function dayCompare($date1, $date2 = null) - { - $date2 = (is_null($date2)) ? date('Y-m-d') : $date2; - if ($date2 == $date1) - return 0; - if ($date1 > $date2) - return - 1; - return 1; - } - - /** - * مقایسه دو تاریخ با یکدیگر - * - * Compare two date and returns the number of seconds between the - * first and the second. - * If only the date is given without time, the - * end of the day is used (23:59:59). - * - * @param - * string Date to compare for ex: '2006-09-17 18:42:00' - * @param - * string Second date to compare if null use now (null) - * @return int Number of seconds between the two dates. Negative - * value if the second date is before the first. - */ - public static function compare($date1, $date2 = null) - { - if (strlen($date1) == 10) { - $date1 .= ' 23:59:59'; - } - if (is_null($date2)) { - $date2 = time(); - } else { - if (strlen($date2) == 10) { - $date2 .= ' 23:59:59'; - } - $date2 = strtotime(str_replace('-', '/', $date2)); - } - $date1 = strtotime(str_replace('-', '/', $date1)); - return $date2 - $date1; - } - - /** - * نمایش تاریخ به فرمت دلخواه - * - * Display a date in the format: - * X days Y hours ago - * X hours Y minutes ago - * X hours Y minutes left - * - * "resolution" is year, month, day, hour, minute. - * - * If not time is given, only the day, the end of the day is - * used: 23:59:59. - * - * @param - * string Date to compare with ex: '2006-09-17 18:42:00' - * @param - * string Reference date to compare with by default now (null) - * @param - * int Maximum number of elements to show (2) - * @param - * string If no delay between the two dates display ('now') - * @param - * bool Show ago/left suffix - * @return string Formatted date - */ - public static function easy($date, $ref = null, $blocks = 2, $notime = 'now', $show = true) - { - if (strlen($date) == 10) { - $date .= ' 23:59:59'; - } - if (is_null($ref)) { - $ref = date('Y-m-d H:i:s'); - $tref = time(); - } else { - if (strlen($ref) == 10) { - $ref .= ' 23:59:59'; - } - $tref = strtotime(str_replace('-', '/', $ref)); - } - $tdate = strtotime(str_replace('-', '/', $date)); - $past = true; - if ($tref < $tdate) { - // date in the past - $past = false; - $_tmp = $ref; - $ref = $date; - $date = $_tmp; - } - $ref = str_replace(array( - ' ', - ':' - ), '-', $ref); - $date = str_replace(array( - ' ', - ':' - ), '-', $date); - $refs = explode('-', $ref); - $dates = explode('-', $date); - // Modulo on the month is dynamically calculated after - $modulos = array( - 365, - 12, - 31, - 24, - 60, - 60 - ); - // day in month - $month = $refs[1] - 1; - $modulos[2] = date('t', mktime(0, 0, 0, $month, 1, $refs[0])); - $diffs = array(); - for ($i = 0; $i < 6; $i ++) { - $diffs[$i] = $refs[$i] - $dates[$i]; - } - $retain = 0; - for ($i = 5; $i > - 1; $i --) { - $diffs[$i] = $diffs[$i] - $retain; - $retain = 0; - if ($diffs[$i] < 0) { - $diffs[$i] = $modulos[$i] + $diffs[$i]; - $retain = 1; - } - } - $res = ''; - $total = 0; - for ($i = 0; $i < 5; $i ++) { - if ($diffs[$i] > 0) { - $total ++; - $res .= $diffs[$i] . ' '; - switch ($i) { - case 0: - $res .= _n('year', 'years', $diffs[$i]); - break; - case 1: - $res .= _n('month', 'months', $diffs[$i]); - break; - case 2: - $res .= _n('day', 'days', $diffs[$i]); - break; - case 3: - $res .= _n('hour', 'hours', $diffs[$i]); - break; - case 4: - $res .= _n('minute', 'minutes', $diffs[$i]); - break; - case 5: - $res .= _n('second', 'seconds', $diffs[$i]); - break; - } - $res .= ' '; - } - if ($total >= $blocks) - break; - } - if (strlen($res) == 0) { - return $notime; - } - if ($show) { - if ($past) { - $res = sprintf(('%s ago'), $res); - } else { - $res = sprintf(('%s left'), $res); - } - } - return $res; - } -} - diff --git a/src/Db/Connection.php b/src/Db/Connection.php deleted file mode 100644 index efd3a846..00000000 --- a/src/Db/Connection.php +++ /dev/null @@ -1,427 +0,0 @@ - $properties - ]); - } - - $this->setDefaults($properties); - } - - /** - * Normalize DSN connection string. - * - * Returns normalized DSN as array ['dsn', 'user', 'pass', 'driver', 'rest']. - * - * @param array|string $dsn - * DSN string - * @param string $user - * Optional username, this takes precedence over dsn string - * @param string $pass - * Optional password, this takes precedence over dsn string - * - * @return array - */ - public static function normalizeDSN($dsn, $user = null, $pass = null) - { - // Try to dissect DSN into parts - $parts = is_array($dsn) ? $dsn : parse_url($dsn); - - // If parts are usable, convert DSN format - if ($parts !== false && isset($parts['host'], $parts['path'])) { - // DSN is using URL-like format, so we need to convert it - $dsn = $parts['scheme'] . ':host=' . $parts['host'] . (isset($parts['port']) ? ';port=' . $parts['port'] : '') . ';dbname=' . substr($parts['path'], 1); - $user = $user !== null ? $user : (isset($parts['user']) ? $parts['user'] : null); - $pass = $pass !== null ? $pass : (isset($parts['pass']) ? $parts['pass'] : null); - } - - // If it's still array, then simply use it - if (is_array($dsn)) { - return $dsn; - } - - // If it's string, then find driver - if (is_string($dsn)) { - if (strpos($dsn, ':') === false) { - throw new Exception([ - "Your DSN format is invalid. Must be in 'driver:host;options' format", - 'dsn' => $dsn - ]); - } - list ($driver, $rest) = explode(':', $dsn, 2); - $driver = strtolower($driver); - } else { - // currently impossible to be like this, but we don't want ugly exceptions here - $driver = $rest = null; - } - - return [ - 'dsn' => $dsn, - 'user' => $user, - 'pass' => $pass, - 'driver' => $driver, - 'rest' => $rest - ]; - } - - /** - * Connect database. - * - * @param string|\PDO $dsn - * @param null|string $user - * @param null|string $password - * @param array $args - * - * @return Connection - */ - public static function connect($dsn, $user = null, $password = null, $args = []): Connection - { - // If it's already PDO object, then we simply use it - if ($dsn instanceof \PDO) { - $driver = $dsn->getAttribute(\PDO::ATTR_DRIVER_NAME); - $connectionClass = self::class; - $queryClass = null; - $expressionClass = null; - switch ($driver) { - case 'pgsql': - $connectionClass = Connection\PgSQL::class; - $queryClass = Query\PgSQL::class; - break; - case 'oci': - $connectionClass = Connection\Oracle::class; - break; - case 'sqlite': - $queryClass = Query\SQLite::class; - break; - case 'mysql': - $expressionClass = Expression\MySQL::class; - default: - // Default, for backwards compatibility - $queryClass = Query\MySQL::class; - break; - } - - return new $connectionClass(array_merge([ - 'connection' => $dsn, - 'query_class' => $queryClass, - 'expression_class' => $expressionClass, - 'driver' => $driver - ], $args)); - } - - // If it's some other object, then we simply use it trough proxy connection - if (is_object($dsn)) { - return new Connection\Proxy(array_merge([ - 'connection' => $dsn - ], $args)); - } - - // Process DSN string - $dsn = static::normalizeDSN($dsn, $user, $password); - - // Create driver specific connection - switch ($dsn['driver']) { - case 'mysql': - $c = new static(array_merge([ - 'connection' => new \PDO($dsn['dsn'], $dsn['user'], $dsn['pass']), - 'expression_class' => Expression\MySQL::class, - 'query_class' => Query\MySQL::class, - 'driver' => $dsn['driver'] - ], $args)); - break; - - case 'sqlite': - $c = new static(array_merge([ - 'connection' => new \PDO($dsn['dsn'], $dsn['user'], $dsn['pass']), - 'query_class' => Query\SQLite::class, - 'driver' => $dsn['driver'] - ], $args)); - break; - - case 'oci': - $c = new Connection\Oracle(array_merge([ - 'connection' => new \PDO($dsn['dsn'], $dsn['user'], $dsn['pass']), - 'driver' => $dsn['driver'] - ], $args)); - break; - - case 'oci12': - $dsn['dsn'] = str_replace('oci12:', 'oci:', $dsn['dsn']); - $c = new Connection\Oracle12(array_merge([ - 'connection' => new \PDO($dsn['dsn'], $dsn['user'], $dsn['pass']), - 'driver' => $dsn['driver'] - ], $args)); - break; - - case 'pgsql': - $c = new Connection\PgSQL(array_merge([ - 'connection' => new \PDO($dsn['dsn'], $dsn['user'], $dsn['pass']), - 'driver' => $dsn['driver'] - ], $args)); - break; - - case 'dumper': - $c = new Connection\Dumper(array_merge([ - 'connection' => static::connect($dsn['rest'], $dsn['user'], $dsn['pass']) - ], $args)); - break; - - case 'counter': - $c = new Connection\Counter(array_merge([ - 'connection' => static::connect($dsn['rest'], $dsn['user'], $dsn['pass']) - ], $args)); - break; - - // let PDO handle the rest - default: - $c = new static(array_merge([ - 'connection' => static::connect(new \PDO($dsn['dsn'], $dsn['user'], $dsn['pass'])) - ], $args)); - } - - return $c; - } - - /** - * Returns new Query object with connection already set. - * - * @param array $properties - * - * @return Query - */ - public function query($properties = []): Query - { - $c = $this->query_class; - $q = new $c($properties); - $q->connection = $this; - - return $q; - } - - /** - * - * @deprecated use query - * @param array $properties - * @return \Pluf\Db\Query - * - * @deprecated use query - */ - public function dsql($properties = []) - { - return $this->query($properties); - } - - /** - * Returns Expression object with connection already set. - * - * @param array $properties - * @param array $arguments - * - * @return Expression - */ - public function expr($properties = [], $arguments = null) - { - $c = $this->expression_class; - $e = new $c($properties, $arguments); - $e->connection = $this->connection ?: $this; - - return $e; - } - - /** - * Returns Connection or PDO object. - * - * @return Connection|\PDO - */ - public function connection() - { - return $this->connection; - } - - /** - * Execute Expression by using this connection. - * - * @param Expression $expr - * - * @return \PDOStatement - */ - public function execute(Expression $expr) - { - // If custom connection is set, execute again using that - if ($this->connection && $this->connection !== $this) { - return $expr->execute($this->connection); - } - - throw new Exception('Queries cannot be executed through this connection'); - } - - /** - * Atomic executes operations within one begin/end transaction, so if - * the code inside callback will fail, then all of the transaction - * will be also rolled back. - */ - public function atomic($f) - { - $this->beginTransaction(); - - try { - $res = call_user_func($f); - $this->commit(); - - return $res; - } catch (\Exception $e) { - $this->rollBack(); - - throw $e; - } - } - - /** - * Starts new transaction. - * - * Database driver supports statements for starting and committing - * transactions. Unfortunately most of them don't allow to nest - * transactions and commit gradually. - * With this method you have some implementation of nested transactions. - * - * When you call it for the first time it will begin transaction. If you - * call it more times, it will do nothing but will increase depth counter. - * You will need to call commit() for each execution of beginTransactions() - * and only the last commit will perform actual commit in database. - * - * So, if you have been working with the database and got un-handled - * exception in the middle of your code, everything will be rolled back. - * - * @return mixed Don't rely on any meaningful return - */ - public function beginTransaction() - { - // transaction starts only if it was not started before - $r = $this->inTransaction() ? false : $this->connection->beginTransaction(); - - $this->transaction_depth ++; - - return $r; - } - - /** - * Will return true if currently running inside a transaction. - * This is useful if you are logging anything into a database. If you are - * inside a transaction, don't log or it may be rolled back. - * Perhaps use a hook for this? - * - * @see beginTransaction() - * - * @return bool if in transaction - */ - public function inTransaction() - { - return $this->transaction_depth > 0; - } - - /** - * Commits transaction. - * - * Each occurrence of beginTransaction() must be matched with commit(). - * Only when same amount of commits are executed, the actual commit will be - * issued to the database. - * - * @see beginTransaction() - * - * @return mixed Don't rely on any meaningful return - */ - public function commit() - { - // check if transaction is actually started - if (! $this->inTransaction()) { - throw new Exception('Using commit() when no transaction has started'); - } - - $this->transaction_depth --; - - if ($this->transaction_depth == 0) { - return $this->connection->commit(); - } - - return false; - } - - /** - * Rollbacks queries since beginTransaction and resets transaction depth. - * - * @see beginTransaction() - * - * @return mixed Don't rely on any meaningful return - */ - public function rollBack() - { - // check if transaction is actually started - if (! $this->inTransaction()) { - throw new Exception('Using rollBack() when no transaction has started'); - } - - $this->transaction_depth --; - - if ($this->transaction_depth == 0) { - return $this->connection->rollBack(); - } - - return false; - } - - /** - * Return last inserted ID value. - * - * Few Connection drivers need to receive Model to get ID because PDO doesn't support this method. - * - * @param - * \atk4\data\Model Optional data model from which to return last ID - * - * @return mixed - */ - public function lastInsertID($m = null) - { - return $this->connection()->lastInsertID(); - } -} diff --git a/src/Db/Connection/Counter.php b/src/Db/Connection/Counter.php deleted file mode 100644 index 4651dd76..00000000 --- a/src/Db/Connection/Counter.php +++ /dev/null @@ -1,101 +0,0 @@ - $row) { - $this->rows ++; - yield $key => $row; - } - } - - /** - * Execute expression. - * - * @param Expression $expr - * - * @return mixed - */ - public function execute(Expression $expr) - { - if ($expr instanceof Query) { - $this->queries ++; - if ($expr->mode === 'select' || $expr->mode === null) { - $this->selects ++; - } - } else { - $this->expressions ++; - } - - try { - $ret = parent::execute($expr); - } catch (\Exception $e) { - if ($this->callback && is_callable($this->callback)) { - call_user_func($this->callback, $this->queries, $this->selects, $this->rows, $this->expressions, true); - } else { - printf("[ERROR] Queries: %3d, Selects: %3d, Rows fetched: %4d, Expressions %3d\n", $this->queries, $this->selects, $this->rows, $this->expressions); - } - - throw $e; - } - - return $this->iterate($ret); - } - - /** - * Log results when destructing. - */ - public function __destruct() - { - if ($this->callback && is_callable($this->callback)) { - call_user_func($this->callback, $this->queries, $this->selects, $this->rows, $this->expressions, false); - } else { - printf("Queries: %3d, Selects: %3d, Rows fetched: %4d, Expressions %3d\n", $this->queries, $this->selects, $this->rows, $this->expressions); - } - } -} diff --git a/src/Db/Connection/Dumper.php b/src/Db/Connection/Dumper.php deleted file mode 100644 index 92a4aa9a..00000000 --- a/src/Db/Connection/Dumper.php +++ /dev/null @@ -1,67 +0,0 @@ -start_time = microtime(true); - - try { - $ret = parent::execute($expr); - $took = microtime(true) - $this->start_time; - - if ($this->callback && is_callable($this->callback)) { - call_user_func($this->callback, $expr, $took, false); - } else { - printf("[%02.6f] %s\n", $took, $expr->getDebugQuery()); - } - } catch (\Exception $e) { - $took = microtime(true) - $this->start_time; - - if ($this->callback && is_callable($this->callback)) { - call_user_func($this->callback, $expr, $took, true); - } else { - printf("[ERROR %02.6f] %s\n", $took, $expr->getDebugQuery()); - } - - throw $e; - } - - return $ret; - } -} diff --git a/src/Db/Connection/Oracle.php b/src/Db/Connection/Oracle.php deleted file mode 100644 index 3b84c636..00000000 --- a/src/Db/Connection/Oracle.php +++ /dev/null @@ -1,68 +0,0 @@ -expr('ALTER SESSION SET NLS_TIMESTAMP_FORMAT={datetime_format} NLS_DATE_FORMAT={date_format} NLS_NUMERIC_CHARACTERS={dec_char}', [ - 'datetime_format' => 'YYYY-MM-DD HH24:MI:SS', // datetime format - 'date_format' => 'YYYY-MM-DD', // date format - 'dec_char' => '. ' // decimal separator, no thousands separator - ])->execute(); - } - - /** - * Return last inserted ID value. - * - * Few Connection drivers need to receive Model to get ID because PDO doesn't support this method. - * - * @param - * \atk4\data\Model Optional data model from which to return last ID - * - * @return mixed - */ - public function lastInsertID($m = null) - { - if ($m instanceof \Pluf\Data\Model) { - // if we use sequence, then we can easily get current value - if (isset($m->sequence)) { - return $this->dsql() - ->mode('seq_currval') - ->sequence($m->sequence) - ->getOne(); - } - - // otherwise we have to select max(id_field) - this can be bad for performance !!! - // Imants: Disabled for now because otherwise this will work even if database use triggers or - // any other mechanism to automatically increment ID and we can't tell this line to not execute. - // return $this->expr('SELECT max([field]) FROM [table]', ['field'=>$m->id_field, 'table'=>$m->table])->getOne(); - } - - // fallback - return parent::lastInsertID($m); - } -} diff --git a/src/Db/Connection/Oracle12.php b/src/Db/Connection/Oracle12.php deleted file mode 100644 index ff1221e7..00000000 --- a/src/Db/Connection/Oracle12.php +++ /dev/null @@ -1,18 +0,0 @@ -connection()->lastInsertID($m->sequence ?: $m->table . '_' . $m->id_field . '_seq'); - } -} diff --git a/src/Db/Connection/Proxy.php b/src/Db/Connection/Proxy.php deleted file mode 100644 index 894c678b..00000000 --- a/src/Db/Connection/Proxy.php +++ /dev/null @@ -1,55 +0,0 @@ -connection instanceof \Pluf\Db\Connection && $this->connection->driver) { - $this->driver = $this->connection->driver; - } - } - - public function connection() - { - return $this->connection->connection(); - } - - public function dsql($properties = []) - { - $dsql = $this->connection->dsql($properties); - $dsql->connection = $this; - - return $dsql; - } - - public function expr($properties = [], $arguments = null) - { - $expr = $this->connection->expr($properties, $arguments); - $expr->connection = $this; - - return $expr; - } - - public function execute(Expression $expr) - { - return $this->connection->execute($expr); - } -} diff --git a/src/Db/Engine.php b/src/Db/Engine.php deleted file mode 100644 index d75b03e7..00000000 --- a/src/Db/Engine.php +++ /dev/null @@ -1,394 +0,0 @@ - array( - '\Pluf\Db\Engine::booleanFromDb', - '\Pluf\Db\Engine::booleanToDb' - ), - self::DATE => array( - '\Pluf\Db\Engine::identityFromDb', - '\Pluf\Db\Engine::identityToDb' - ), - self::DATETIME => array( - '\Pluf\Db\Engine::identityFromDb', - '\Pluf\Db\Engine::identityToDb' - ), - self::EMAIL => array( - '\Pluf\Db\Engine::identityFromDb', - '\Pluf\Db\Engine::identityToDb' - ), - self::FILE => array( - '\Pluf\Db\Engine::identityFromDb', - '\Pluf\Db\Engine::identityToDb' - ), - self::FLOAT => array( - '\Pluf\Db\Engine::floatFromDb', - '\Pluf\Db\Engine::floatToDb' - ), - self::FOREIGNKEY => array( - '\Pluf\Db\Engine::integerFromDb', - '\Pluf\Db\Engine::integerToDb' - ), - self::INTEGER => array( - '\Pluf\Db\Engine::integerFromDb', - '\Pluf\Db\Engine::integerToDb' - ), - self::PASSWORD => array( - '\Pluf\Db\Engine::identityFromDb', - '\Pluf\Db\Engine::passwordToDb' - ), - self::SEQUENCE => array( - '\Pluf\Db\Engine::integerFromDb', - '\Pluf\Db\Engine::integerToDb' - ), - self::SLUG => array( - '\Pluf\Db\Engine::identityFromDb', - '\Pluf\Db\Engine::slugToDb' - ), - self::TEXT => array( - '\Pluf\Db\Engine::identityFromDb', - '\Pluf\Db\Engine::identityToDb' - ), - self::VARCHAR => array( - '\Pluf\Db\Engine::identityFromDb', - '\Pluf\Db\Engine::identityToDb' - ), - self::SERIALIZED => array( - '\Pluf\Db\Engine::serializedFromDb', - '\Pluf\Db\Engine::serializedToDb' - ), - self::COMPRESSED => array( - '\Pluf\Db\Engine::compressedFromDb', - '\Pluf\Db\Engine::compressedToDb' - ), - self::GEOMETRY => array( - '\Pluf\Db\Engine::geometryFromDb', - '\Pluf\Db\Engine::geometryToDb' - ) - ); - - /** - * Creates new instance of an engine - */ - function __construct(Options $options) - { - // set local options - $this->options = $options; - } - - /** - * - * @return Options which is used to create - */ - public function getOptions(): ?Options - { - return $this->options; - } - - /** - * - * @return mixed - */ - public function getSchema(): ?Schema - { - return $this->schema; - } - - /** - * Prepare the value to be put in the DB. - * - * @param - * mixed Value. - * @param - * string Column name. - * @return string SQL ready string. - */ - function toDb($val, $type) - { - $m = $this->type_cast[$type][1]; - return $m($val, $this); - } - - /** - * Get the value from the DB. - * - * Create DB field and returns. The field type is used as the output - * value type. - * - * @param - * mixed Value. - * @param - * string Column name. - * @return mixed Value. - */ - function fromDb($val, $type) - { - $m = $this->type_cast[$type][0]; - return ($m == 'Pluf_DB_IdentityFromDb') ? $val : $m($val); - } - - public abstract function isLive(): bool; - - public abstract function quote(string $string, int $parameter_type = null); - - public abstract function execute($query); - - /** - * Gets last created Model ID - * - * In insert query, last created model id will be saved in engine. - * - * @return int last created model id - */ - public abstract function getLastID(): int; - - public static function getInstance(Options $options): Engine - { - $engine = $options->engine; - $con = new $engine($options); - - // Create schema - $schemaName = $options->schema; - $schemaOption = $options->startsWith('schema_', true); - $con->schema = new $schemaName($con, $schemaOption); - - return $con; - } - - /** - * Identity function. - * - * @params - * mixed Value - * @return mixed Value - */ - public static function identityFromDb($val) - { - return $val; - } - - /** - * Identity function. - * - * @param - * mixed Value. - * @param - * object Database handler. - * @return string Ready to use for SQL. - */ - public static function identityToDb($val, $db) - { - if (null === $val) { - return null; - } - return $val; - } - - public static function serializedFromDb($val) - { - if ($val) { - return unserialize($val); - } - return $val; - } - - public static function serializedToDb($val, $db) - { - if (null === $val) { - return null; - } - return serialize($val); - } - - public static function compressedFromDb($val) - { - return ($val) ? gzinflate($val) : $val; - } - - public static function compressedToDb($val, $db) - { - return (null === $val) ? null : gzdeflate($val, 9); - } - - public static function booleanFromDb($val) - { - if ($val) { - return true; - } - return false; - } - - public static function booleanToDb($val, $db) - { - if (null === $val) { - return null; - } - if ($val) { - return '1'; - } - return '0'; - } - - public static function integerFromDb($val) - { - return (null === $val) ? null : (int) $val; - } - - public static function integerToDb($val, $db) - { - return (null === $val) ? null : (string) (int) $val; - } - - public static function floatFromDb($val) - { - return (null === $val) ? null : (float) $val; - } - - public static function floatToDb($val, $db) - { - return (null === $val) ? null : (string) (float) $val; - } - - public static function passwordToDb($val, $db) - { - $exp = explode(':', $val); - if (in_array($exp[0], array( - 'sha1', - 'md5', - 'crc32' - ))) { - return $val; - } - // We need to hash the value. - $salt = \Pluf\Utils::getRandomString(5); - return 'sha1:' . $salt . ':' . sha1($salt . $val); - } - - public static function slugFromDB($val) - {} - - public static function slugToDB($val, $db) - { - // return $db->esc(Pluf_DB_Field_Slug::slugify($val)); - } - - /** - * - * @param Object $val - * @return string - */ - public static function geometryFromDb($val) - { - // TODO: maso, 2018: check if we need to use geoPHP::load to load data - // SEE: https://github.com/phayes/geoPHP - /* - * maso, 1395: convert $val (from BLOB) to WKT - * - * 1- SRID - * 2- WKB - * - * See: - * https://dev.mysql.com/doc/refman/5.7/en/gis-data-formats.html#gis-internal-format - */ - if ($val == null) - return null; - $data = unpack("lsrid/H*wkb", $val); - $geometry = geoPHP::load($data['wkb'], 'wkb', TRUE); - $wkt_writer = new WKT(); - $wkt = $wkt_writer->write($geometry); - return $wkt; - } - - /** - * Convert text to geometry - * - * @return string - */ - public static function geometryToDb($val, $db) - { - // TODO: maso, 2018: check if we need to use geoPHP::load to load data - // SEE: https://github.com/phayes/geoPHP - // TODO: hadi 1397-06-16: Here $val should be encoded - // if($db->engine === 'SQLite'){ - // return (null === $val || empty($val)) ? null : "'" . $val . "'"; - // } - return (null === $val || empty($val)) ? null : (string) "GeometryFromText('" . $val . "')"; - } -} - diff --git a/src/Db/Exception.php b/src/Db/Exception.php deleted file mode 100644 index 7a1441e2..00000000 --- a/src/Db/Exception.php +++ /dev/null @@ -1,9 +0,0 @@ - [] - ]; - - /** - * As per PDO, _param() will convert value into :a, :b, :c .. - * :aa .. etc. - * - * @var string - */ - protected $paramBase = 'a'; - - /** - * Field, table and alias name escaping symbol. - * By SQL Standard it's double quote, but MySQL uses backtick. - * - * @var string - */ - protected $escape_char = '"'; - - /** - * Used for Linking. - * - * @var string - */ - public $_paramBase = null; - - /** - * Will be populated with actual values by _param(). - * - * @var array - */ - public $params = []; - - /** - * When you are willing to execute the query, connection needs to be specified. - * By default this is PDO object. - * - * @var \PDO|Connection - */ - public $connection = null; - - /** - * Holds references to bound parameter values. - * - * This is needed to use bindParam instead of bindValue and to be able to use 4th parameter of bindParam. - * - * @var array - */ - private $boundValues = []; - - /** - * Specifying options to constructors will override default - * attribute values of this class. - * - * If $properties is passed as string, then it's treated as template. - * - * @param string|array $properties - * @param array $arguments - */ - public function __construct($properties = [], $arguments = null) - { - // save template - if (is_string($properties)) { - $properties = [ - 'template' => $properties - ]; - } elseif (! is_array($properties)) { - throw new Exception([ - 'Incorrect use of Expression constructor', - 'properties' => $properties, - 'arguments' => $arguments - ]); - } - - // supports passing template as property value without key 'template' - if (isset($properties[0])) { - $properties['template'] = $properties[0]; - unset($properties[0]); - } - - // save arguments - if ($arguments !== null) { - if (! is_array($arguments)) { - throw new Exception([ - 'Expression arguments must be an array', - 'properties' => $properties, - 'arguments' => $arguments - ]); - } - $this->args['custom'] = $arguments; - } - - // deal with remaining properties - foreach ($properties as $key => $val) { - $this->$key = $val; - } - } - - /** - * Casting to string will execute expression and return getOne() value. - * - * @return string - */ - public function __toString() - { - return (string) $this->getOne(); - } - - /** - * Assigns a value to the specified offset. - * - * @param - * string The offset to assign the value to - * @param - * mixed The value to set - * @abstracting ArrayAccess - */ - public function offsetSet($offset, $value) - { - if ($offset === null) { - $this->args['custom'][] = $value; - } else { - $this->args['custom'][$offset] = $value; - } - } - - /** - * Whether or not an offset exists. - * - * @param - * string An offset to check for - * - * @return bool - * @abstracting ArrayAccess - */ - public function offsetExists($offset) - { - return array_key_exists($offset, $this->args['custom']); - } - - /** - * Unsets an offset. - * - * @param - * string The offset to unset - * @abstracting ArrayAccess - */ - public function offsetUnset($offset) - { - unset($this->args['custom'][$offset]); - } - - /** - * Returns the value at specified offset. - * - * @param - * string The offset to retrieve - * - * @return mixed - * @abstracting ArrayAccess - */ - public function offsetGet($offset) - { - return $this->args['custom'][$offset]; - } - - /** - * Use this instead of "new Expression()" if you want to automatically bind - * new expression to the same connection as the parent. - * - * @param array|string $properties - * @param array $arguments - * - * @return Expression - */ - public function expr($properties = [], $arguments = null) - { - // If we use DSQL Connection, then we should call expr() from there. - // Connection->expr() will return correct, connection specific Expression class. - if ($this->connection instanceof Connection) { - return $this->connection->expr($properties, $arguments); - } - - // Otherwise, connection is probably PDO and we don't know which Expression - // class to use, so we make a smart guess :) - if ($this instanceof Query) { - $e = new self($properties, $arguments); - } else { - $e = new static($properties, $arguments); - } - - $e->escape_char = $this->escape_char; - $e->connection = $this->connection; - - return $e; - } - - /** - * Resets arguments. - * - * @param string $tag - * - * @return $this - */ - public function reset($tag = null) - { - // unset all arguments - if ($tag === null) { - $this->args = [ - 'custom' => [] - ]; - - return $this; - } - - if (! is_string($tag)) { - throw new Exception([ - 'Tag should be string', - 'tag' => $tag - ]); - } - - // unset custom/argument or argument if such exists - if ($this->offsetExists($tag)) { - $this->offsetUnset($tag); - } elseif (isset($this->args[$tag])) { - unset($this->args[$tag]); - } - - return $this; - } - - /** - * Recursively renders sub-query or expression, combining parameters. - * - * @param mixed $sql_code - * Expression - * @param string $escape_mode - * Fall-back escaping mode - param|escape|none - * - * @return string|array Quoted expression or array of param names - */ - protected function _consume($sql_code, $escape_mode = 'param') - { - if (! is_object($sql_code)) { - switch ($escape_mode) { - case 'param': - return $this->_param($sql_code); - case 'escape': - return $this->_escape($sql_code); - case 'soft-escape': - return $this->_escapeSoft($sql_code); - case 'none': - return $sql_code; - } - - throw new Exception([ - '$escape_mode value is incorrect', - 'escape_mode' => $escape_mode - ]); - } - - // User may add Expressionable trait to any class, then pass it's objects - if ($sql_code instanceof Expressionable) { - $sql_code = $sql_code->getDSQLExpression($this); - } - - if (! $sql_code instanceof self) { - throw new Exception([ - 'Only Expressions or Expressionable objects may be used in Expression', - 'object' => $sql_code - ]); - } - - // at this point $sql_code is instance of Expression - $sql_code->params = &$this->params; - $sql_code->_paramBase = &$this->_paramBase; - $ret = $sql_code->render(); - - // Queries should be wrapped in parentheses in most cases - if ($sql_code instanceof Query) { - $ret = '(' . $ret . ')'; - } - - // unset is needed here because ->params=&$othervar->params=foo will also change $othervar. - // if we unset() first, we’re safe. - unset($sql_code->params); - $sql_code->params = []; - - return $ret; - } - - /** - * Given the string parameter, it will detect some "deal-breaker" for our - * soft escaping, such as "*" or "(". - * Those will typically indicate that expression is passed and shouldn't - * be escaped. - */ - protected function isUnescapablePattern($value) - { - return is_object($value) || $value === '*' || strpos($value, '(') !== false || strpos($value, $this->escape_char) !== false; - } - - /** - * Soft-escaping SQL identifier. - * This method will attempt to put - * escaping char around the identifier, however will not do so if you - * are using special characters like ".", "(" or escaping char. - * - * It will smartly escape table.field type of strings resulting - * in "table"."field". - * - * @param mixed $value - * Any string or array of strings - * - * @return string|array Escaped string or array of strings - */ - protected function _escapeSoft($value) - { - // supports array - if (is_array($value)) { - return array_map(__METHOD__, $value); - } - - // in some cases we should not escape - if ($this->isUnescapablePattern($value)) { - return $value; - } - - if (is_string($value) && strpos($value, '.') !== false) { - return implode('.', array_map(__METHOD__, explode('.', $value))); - } - - return $this->escape_char . trim($value) . $this->escape_char; - } - - /** - * Creates new expression where $sql_code appears escaped. - * Use this - * method as a conventional means of specifying arguments when you - * think they might have a nasty back-ticks or commas in the field - * names. - * - * @param string $value - * - * @return Expression - */ - public function escape($value) - { - return $this->expr('{}', [ - $value - ]); - } - - /** - * Escapes argument by adding backticks around it. - * This will allow you to use reserved SQL words as table or field - * names such as "table" as well as other characters that SQL - * permits in the identifiers (e.g. spaces or equation signs). - * - * @param mixed $value - * Any string or array of strings - * - * @return string|array Escaped string or array of strings - */ - protected function _escape($value) - { - // supports array - if (is_array($value)) { - return array_map(__METHOD__, $value); - } - - // in all other cases we should escape - $c = $this->escape_char; - - return $c . str_replace($c, $c . $c, $value) . $c; - } - - /** - * Converts value into parameter and returns reference. - * Use only during - * query rendering. Consider using `_consume()` instead, which will - * also handle nested expressions properly. - * - * @param string|array $value - * String literal or array of strings containing input data - * - * @return string|array Name of parameter or array of names - */ - protected function _param($value) - { - // supports array - if (is_array($value)) { - return array_map(__METHOD__, $value); - } - - $name = ':' . $this->_paramBase; - $this->_paramBase ++; - $this->params[$name] = $value; - - return $name; - } - - /** - * Render expression and return it as string. - * - * @return string Rendered query - */ - public function render() - { - $nameless_count = 0; - if (! isset($this->_paramBase)) { - $this->_paramBase = $this->paramBase; - } - - if ($this->template === null) { - throw new Exception('Template is not defined for Expression'); - } - - $res = preg_replace_callback( - // param | escape | escapeSoft - '/\[[a-z0-9_]*\]|{[a-z0-9_]*}|{{[a-z0-9_]*}}/i', function ($matches) use (&$nameless_count) { - $identifier = substr($matches[0], 1, - 1); - - if ($matches[0][0] == '[') { - $escaping = 'param'; - } elseif ($matches[0][0] == '{') { - if ($matches[0][1] == '{') { - $escaping = 'soft-escape'; - $identifier = substr($identifier, 1, - 1); - } else { - $escaping = 'escape'; - } - } - - // Allow template to contain [] - if ($identifier === '') { - $identifier = $nameless_count ++; - - // use rendering only with named tags - } - $fx = '_render_' . $identifier; - - // [foo] will attempt to call $this->_render_foo() - - if (array_key_exists($identifier, $this->args['custom'])) { - $value = $this->_consume($this->args['custom'][$identifier], $escaping); - } elseif (method_exists($this, $fx)) { - $value = $this->$fx(); - } else { - throw new Exception([ - 'Expression could not render tag', - 'tag' => $identifier - ]); - } - - return is_array($value) ? '(' . implode(',', $value) . ')' : $value; - }, $this->template); - unset($this->_paramBase); - - return trim($res); - } - - /** - * Return formatted debug output. - * - * Ignore false positive warnings of PHPMD. - * - * @SuppressWarnings(PHPMD.StaticAccess) - * - * @param bool $html - * Show as HTML? - * - * @return string SQL syntax of query - */ - public function getDebugQuery($html = null) - { - $d = $this->render(); -// $pp = []; - foreach (array_reverse($this->params) as $key => $val) { - if (is_numeric($val)) { - $d = preg_replace('/' . $key . '([^_]|$)/', $val . '\1', $d); - } elseif (is_string($val)) { - $d = preg_replace('/' . $key . '([^_]|$)/', "'" . addslashes($val) . "'\\1", $d); - } elseif ($val === null) { - $d = preg_replace('/' . $key . '([^_]|$)/', 'NULL\1', $d); - } else { - $d = preg_replace('/' . $key . '([^_]|$)/', $val . '\\1', $d); - } -// $pp[] = $key; - } -// if (class_exists('SqlFormatter')) { -// if ($html) { -// $result = \SqlFormatter::format($d); -// } else { -// $result = \SqlFormatter::format($d, false); -// } -// } else { - $result = $d; // output as-is -// } - if (! $html) { - return str_replace('#lte#', '<=', strip_tags(str_replace('<=', '#lte#', $result), '<>')); - } - - return $result; - } - - public function __debugInfo() - { - $arr = [ - 'R' => false, - 'template' => $this->template, - 'params' => $this->params, - // 'connection' => $this->connection, - 'args' => $this->args - ]; - - try { - $arr['R'] = $this->getDebugQuery(); - } catch (\Exception $e) { - $arr['R'] = $e->getMessage(); - } - - return $arr; - } - - /** - * Execute expression. - * - * @param \PDO|Connection $connection - * - * @return \PDOStatement - */ - public function execute($connection = null) - { - if ($connection === null) { - $connection = $this->connection; - } - - // If it's a PDO connection, we're cool - if ($connection instanceof \PDO) { - $connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); - // We support PDO - $query = $this->render(); - try { - $statement = $connection->prepare($query); - } catch (\Exception $e) { - $new = new Exception([ - 'Pluf got Exception when preparing this {query}', - 'error' => $e->getMessage(), - 'query' => $this->getDebugQuery() - ]); - $new->by_exception = $e; - throw $new; - } - foreach ($this->params as $key => $val) { - if (is_int($val)) { - $type = \PDO::PARAM_INT; - } elseif (is_bool($val)) { - // SQL does not like booleans at all, so convert them INT - $type = \PDO::PARAM_INT; - $val = (int) $val; - } elseif ($val === null) { - $type = \PDO::PARAM_NULL; - } elseif (is_string($val) || is_float($val)) { - $type = \PDO::PARAM_STR; - } elseif (is_resource($val)) { - $type = \PDO::PARAM_LOB; - } else { - throw new Exception([ - 'Incorrect param type', - 'key' => $key, - 'value' => $val, - 'type' => gettype($val) - ]); - } - - // Workaround to support LOB data type. See https://github.com/doctrine/dbal/pull/2434 - $this->boundValues[$key] = $val; - if ($type === \PDO::PARAM_STR) { - $bind = $statement->bindParam($key, $this->boundValues[$key], $type, strlen($val)); - } else { - $bind = $statement->bindParam($key, $this->boundValues[$key], $type); - } - - if (! $bind) { - throw new Exception([ - 'Unable to bind parameter', - 'param' => $key, - 'value' => $val, - 'type' => $type - ]); - } - } - - $statement->setFetchMode(\PDO::FETCH_ASSOC); - - try { - $statement->execute(); - } catch (\Exception $e) { - $new = new Exception([ - 'Pluf got Exception when executing this query', - 'error' => $e->getMessage(), - 'query' => $this->getDebugQuery() - ]); - $new->by_exception = $e; - throw $new; - } - - return $statement; - } else { - return $connection->execute($this); - } - } - - /** - * Returns ArrayIterator, for example PDOStatement. - * - * @return \PDOStatement - * @abstracting \IteratorAggregate - */ - public function getIterator() - { - return $this->execute(); - } - - // {{{ Result Querying - - /** - * Executes expression and return whole result-set in form of array of hashes. - * - * @return array - */ - public function get() - { - $stmt = $this->execute(); - - if ($stmt instanceof \Generator) { - return iterator_to_array($stmt); - } - - return $stmt->fetchAll(); - } - - /** - * Executes expression and return first value of first row of data from result-set. - * - * @return string - */ - public function getOne() - { - $data = $this->getRow(); - if (! $data) { - throw new Exception([ - 'Unable to fetch single cell of data for getOne from this query', - 'result' => $data, - 'query' => $this->getDebugQuery() - ]); - } - $one = array_shift($data); - - return $one; - } - - /** - * Executes expression and returns first row of data from result-set as a hash. - * - * @return array - */ - public function getRow() - { - $stmt = $this->execute(); - - if ($stmt instanceof \Generator) { - return $stmt->current(); - } - - return $stmt->fetch(); - } - - // }}} -} diff --git a/src/Db/Expression/MySQL.php b/src/Db/Expression/MySQL.php deleted file mode 100644 index a57b0f5d..00000000 --- a/src/Db/Expression/MySQL.php +++ /dev/null @@ -1,17 +0,0 @@ -field('name'); - * - * You can use a dot to prepend table name to the field: - * $q->field('user.name'); - * $q->field('user.name')->field('address.line1'); - * - * Array as a first argument will specify multiple fields, same as calling field() multiple times - * $q->field(['name', 'surname', 'address.line1']); - * - * You can pass first argument as Expression or Query - * $q->field( $q->expr('2+2'), 'alias'); // must always use alias - * - * You can use $q->dsql() for subqueries. Subqueries will be wrapped in - * brackets. - * $q->field( $q->dsql()->table('x')... , 'alias'); - * - * Associative array will assume that "key" holds the field alias. - * Value may be field name, Expression or Query. - * $q->field(['alias' => 'name', 'alias2' => 'mother.surname']); - * $q->field(['alias' => $q->expr(..), 'alias2' => $q->dsql()->.. ]); - * - * If you need to use funky name for the field (e.g, one containing - * a dot or a space), you should wrap it into expression: - * $q->field($q->expr('{}', ['fun...ky.field']), 'f'); - * - * @param mixed $field - * Specifies field to select - * @param string $alias - * Specify alias for this field - * - * @return $this - */ - public function field($field, $alias = null) - { - // field is passed as string, may contain commas - if (is_string($field) && strpos($field, ',') !== false) { - $field = explode(',', $field); - } - - // recursively add array fields - if (is_array($field)) { - if ($alias !== null) { - throw new Exception([ - 'Alias must not be specified when $field is an array', - 'alias' => $alias - ]); - } - - foreach ($field as $alias => $f) { - $this->field($f, is_numeric($alias) ? null : $alias); - } - - return $this; - } - - // save field in args - $this->_set_args('field', $alias, $field); - - return $this; - } - - /** - * Returns template component for [field]. - * - * @param bool $add_alias - * Should we add aliases, see _render_field_noalias() - * - * @return string Parsed template chunk - */ - protected function _render_field($add_alias = true) - { - // will be joined for output - $ret = []; - - // If no fields were defined, use defaultField - if (empty($this->args['field'])) { - if ($this->defaultField instanceof Expression) { - return $this->_consume($this->defaultField); - } - - return (string) $this->defaultField; - } - - // process each defined field - foreach ($this->args['field'] as $alias => $field) { - // Do not add alias, if: - // - we don't want aliases OR - // - alias is the same as field OR - // - alias is numeric - if ($add_alias === false || (is_string($field) && $alias === $field) || is_numeric($alias)) { - $alias = null; - } - - // Will parameterize the value and escape if necessary - $field = $this->_consume($field, 'soft-escape'); - - if ($alias) { - // field alias cannot be expression, so simply escape it - $field .= ' ' . $this->_escape($alias); - } - - $ret[] = $field; - } - - return implode(',', $ret); - } - - /** - * Renders part of the template: [field_noalias] - * Do not call directly. - * - * @return string Parsed template chunk - */ - protected function _render_field_noalias() - { - return $this->_render_field(false); - } - - // }}} - - // {{{ Table specification and rendering - - /** - * Specify a table to be used in a query. - * - * @param mixed $table - * Specifies table - * @param string $alias - * Specify alias for this table - * - * @return $this - */ - public function table($table, $alias = null) - { - // comma-separated table names - if (is_string($table) && strpos($table, ',') !== false) { - $table = explode(',', $table); - } - - // array of tables - recursively process each - if (is_array($table)) { - if ($alias !== null) { - throw new Exception([ - 'You cannot use single alias with multiple tables', - 'alias' => $alias - ]); - } - - foreach ($table as $alias => $t) { - if (is_numeric($alias)) { - $alias = null; - } - $this->table($t, $alias); - } - - return $this; - } - - // if table is set as sub-Query, then alias is mandatory - if ($table instanceof self && $alias === null) { - throw new Exception('If table is set as Query, then table alias is mandatory'); - } - - if (is_string($table) && $alias === null) { - $alias = $table; - } - - // main_table will be set only if table() is called once. - // it's used as "default table" when joining with other tables, see join(). - // on multiple calls, main_table will be false and we won't - // be able to join easily anymore. - $this->main_table = ($this->main_table === null && $alias !== null ? $alias : false); - - // save table in args - $this->_set_args('table', $alias, $table); - - return $this; - } - - /** - * Renders part of the template: [table] - * Do not call directly. - * - * @param bool $add_alias - * Should we add aliases, see _render_table_noalias() - * - * @return string Parsed template chunk - */ - protected function _render_table($add_alias = true) - { - // will be joined for output - $ret = []; - - if (empty($this->args['table'])) { - return ''; - } - - // process tables one by one - foreach ($this->args['table'] as $alias => $table) { - - // throw exception if we don't want to add alias and table is defined as Expression - if ($add_alias === false && $table instanceof self) { - throw new Exception('Table cannot be Query in UPDATE, INSERT etc. query modes'); - } - - // Do not add alias, if: - // - we don't want aliases OR - // - alias is the same as table name OR - // - alias is numeric - if ($add_alias === false || (is_string($table) && $alias === $table) || is_numeric($alias)) { - $alias = null; - } - - // consume or escape table - $table = $this->_consume($table, 'soft-escape'); - - // add alias if needed - if ($alias) { - $table .= ' ' . $this->_escape($alias); - } - - $ret[] = $table; - } - - return implode(',', $ret); - } - - /** - * Renders part of the template: [table_noalias] - * Do not call directly. - * - * @return string Parsed template chunk - */ - protected function _render_table_noalias() - { - return $this->_render_table(false); - } - - /** - * Renders part of the template: [from] - * Do not call directly. - * - * @return string Parsed template chunk - */ - protected function _render_from() - { - return empty($this->args['table']) ? '' : 'from'; - } - - // / }}} - - // {{{ with() - - /** - * Specify WITH query to be used. - * - * @param Query $cursor - * Specifies cursor query or array [alias=>query] for adding multiple - * @param string $alias - * Specify alias for this cursor - * @param array $fields - * Optional array of field names used in cursor - * @param bool $recursive - * Is it recursive? - * - * @return $this - */ - public function with(self $cursor, string $alias, ?array $fields = null, bool $recursive = false) - { - // save cursor in args - $this->_set_args('with', $alias, [ - 'cursor' => $cursor, - 'fields' => $fields, - 'recursive' => $recursive - ]); - - return $this; - } - - /** - * Recursive WITH query. - * - * @param Query|array $cursor - * Specifies cursor query or array [alias=>query] for adding multiple - * @param string $alias - * Specify alias for this cursor - * @param array $fields - * Optional array of field names used in cursor - * - * @return $this - */ - public function withRecursive(self $cursor, string $alias, ?array $fields = null) - { - return $this->with($cursor, $alias, $fields, true); - } - - /** - * Renders part of the template: [with] - * Do not call directly. - * - * @return string Parsed template chunk - */ - protected function _render_with() - { - // will be joined for output - $ret = []; - - if (empty($this->args['with'])) { - return ''; - } - - // process each defined cursor - $isRecursive = false; - foreach ($this->args['with'] as $alias => [ - 'cursor' => $cursor, - 'fields' => $fields, - 'recursive' => $recursive - ]) { - // cursor alias cannot be expression, so simply escape it - $s = $this->_escape($alias) . ' '; - - // set cursor fields - if ($fields !== null) { - $s .= '(' . implode(',', array_map([ - $this, - '_escape' - ], $fields)) . ') '; - } - - // will parameterize the value and escape if necessary - $s .= 'as ' . $this->_consume($cursor, 'soft-escape'); - - // is at least one recursive ? - $isRecursive = $isRecursive || $recursive; - - $ret[] = $s; - } - - return 'with ' . ($isRecursive ? 'recursive ' : '') . implode(',', $ret) . ' '; - } - - // / }}} - - // {{{ join() - - /** - * Joins your query with another table. - * Join will use $main_table - * to reference the main table, unless you specify it explicitly. - * - * Examples: - * $q->join('address'); // on user.address_id=address.id - * $q->join('address.user_id'); // on address.user_id=user.id - * $q->join('address a'); // With alias - * $q->join(array('a'=>'address')); // Also alias - * - * Second argument may specify the field of the master table - * $q->join('address', 'billing_id'); - * $q->join('address.code', 'code'); - * $q->join('address.code', 'user.code'); - * - * Third argument may specify which kind of join to use. - * $q->join('address', null, 'left'); - * $q->join('address.code', 'user.code', 'inner'); - * - * Using array syntax you can join multiple tables too - * $q->join(array('a'=>'address', 'p'=>'portfolio')); - * - * You can use expression for more complex joins - * $q->join('address', - * $q->orExpr() - * ->where('user.billing_id=address.id') - * ->where('user.technical_id=address.id') - * ) - * - * @param string|array $foreign_table - * Table to join with - * @param mixed $master_field - * Field in master table - * @param string $join_kind - * 'left' or 'inner', etc - * @param string $_foreign_alias - * Internal, don't use - * - * @return $this - */ - public function join($foreign_table, $master_field = null, $join_kind = null, $_foreign_alias = null) - { - // If array - add recursively - if (is_array($foreign_table)) { - foreach ($foreign_table as $alias => $foreign) { - if (is_numeric($alias)) { - $alias = null; - } - - $this->join($foreign, $master_field, $join_kind, $alias); - } - - return $this; - } - $j = []; - - // try to find alias in foreign table definition. this behaviour should be deprecated - if ($_foreign_alias === null) { - list ($foreign_table, $_foreign_alias) = array_pad(explode(' ', $foreign_table, 2), 2, null); - } - - // Split and deduce fields - // NOTE that this will not allow table names with dots in there !!! - list ($f1, $f2) = array_pad(explode('.', $foreign_table, 2), 2, null); - - if (is_object($master_field)) { - $j['expr'] = $master_field; - } else { - // Split and deduce primary table - if ($master_field === null) { - list ($m1, $m2) = [ - null, - null - ]; - } else { - list ($m1, $m2) = array_pad(explode('.', $master_field, 2), 2, null); - } - if ($m2 === null) { - $m2 = $m1; - $m1 = null; - } - if ($m1 === null) { - $m1 = $this->main_table; - } - - // Identify fields we use for joins - if ($f2 === null && $m2 === null) { - $m2 = $f1 . '_id'; - } - if ($m2 === null) { - $m2 = 'id'; - } - $j['m1'] = $m1; - $j['m2'] = $m2; - } - - $j['f1'] = $f1; - if ($f2 === null) { - $f2 = 'id'; - } - $j['f2'] = $f2; - - $j['t'] = $join_kind ?: 'left'; - $j['fa'] = $_foreign_alias; - - $this->args['join'][] = $j; - - return $this; - } - - /** - * Renders [join]. - * - * @return string rendered SQL chunk - */ - public function _render_join() - { - if (! isset($this->args['join'])) { - return ''; - } - $joins = []; - foreach ($this->args['join'] as $j) { - $jj = ''; - - $jj .= $j['t'] . ' join '; - - $jj .= $this->_escapeSoft($j['f1']); - - if ($j['fa'] !== null) { - $jj .= ' as ' . $this->_escape($j['fa']); - } - - $jj .= ' on '; - - if (isset($j['expr'])) { - $jj .= $this->_consume($j['expr']); - } else { - $jj .= $this->_escape($j['fa'] ?: $j['f1']) . '.' . $this->_escape($j['f2']) . ' = ' . ($j['m1'] === null ? '' : $this->_escape($j['m1']) . '.') . $this->_escape($j['m2']); - } - $joins[] = $jj; - } - - return ' ' . implode(' ', $joins); - } - - // }}} - - // {{{ where() and having() specification and rendering - - /** - * Adds condition to your query. - * - * Examples: - * $q->where('id',1); - * - * By default condition implies equality. You can specify a different comparison - * operator by either including it along with the field or using 3-argument - * format: - * $q->where('id>','1'); - * $q->where('id','>',1); - * - * You may use Expression as any part of the query. - * $q->where($q->expr('a=b')); - * $q->where('date>',$q->expr('now()')); - * $q->where($q->expr('length(password)'),'>',5); - * - * If you specify Query as an argument, it will be automatically - * surrounded by brackets: - * $q->where('user_id',$q->dsql()->table('users')->field('id')); - * - * You can specify OR conditions by passing single argument - array: - * $q->where([ - * ['a','is',null], - * ['b','is',null] - * ]); - * - * If entry of the OR condition is not an array, then it's assumed to - * be an expression; - * - * $q->where([ - * ['age',20], - * 'age is null' - * ]); - * - * The above use of OR conditions rely on orExpr() functionality. See - * that method for more information. - * - * To specify OR conditions - * $q->where($q->orExpr()->where('a',1)->where('b',1)); - * - * @param mixed $field - * Field, array for OR or Expression - * @param mixed $cond - * Condition such as '=', '>' or 'is not' - * @param mixed $value - * Value. Will be quoted unless you pass expression - * @param string $kind - * Do not use directly. Use having() - * @param string $num_args - * When $kind is passed, we can't determine number of - * actual arguments, so this argument must be specified. - * - * @return $this - */ - public function where($field, $cond = null, $value = null, $kind = 'where', $num_args = null) - { - // Number of passed arguments will be used to determine if arguments were specified or not - if ($num_args === null) { - $num_args = func_num_args(); - } - - // Array as first argument means we have to replace it with orExpr() - if (is_array($field)) { - // or conditions - $or = $this->orExpr(); - foreach ($field as $row) { - if (is_array($row)) { - call_user_func_array([ - $or, - 'where' - ], $row); - } else { - $or->where($row); - } - } - $field = $or; - } - - if ($num_args === 1 && is_string($field)) { - $this->args[$kind][] = [ - $this->expr($field) - ]; - - return $this; - } - - // first argument is string containing more than just a field name and no more than 2 - // arguments means that we either have a string expression or embedded condition. - if ($num_args === 2 && is_string($field) && ! preg_match('/^[.a-zA-Z0-9_]*$/', $field)) { - // field contains non-alphanumeric values. Look for condition - $matches = []; - preg_match('/^([^ <>!=]*)([>expr($field); - - $cond = '='; - } else { - $num_args ++; - } - - $field = $matches[1]; - } - - switch ($num_args) { - case 1: - $this->args[$kind][] = [ - $field - ]; - break; - case 2: - if (is_object($cond) && ! $cond instanceof Expressionable && ! $cond instanceof Expression) { - throw new Exception([ - 'Value cannot be converted to SQL-compatible expression', - 'field' => $field, - 'value' => $cond - ]); - } - - $this->args[$kind][] = [ - $field, - $cond - ]; - break; - case 3: - if (is_object($value) && ! $value instanceof Expressionable && ! $value instanceof Expression) { - throw new Exception([ - 'Value cannot be converted to SQL-compatible expression', - 'field' => $field, - 'cond' => $cond, - 'value' => $value - ]); - } - - $this->args[$kind][] = [ - $field, - $cond, - $value - ]; - break; - } - - return $this; - } - - /** - * Same syntax as where(). - * - * @param mixed $field - * Field, array for OR or Expression - * @param string $cond - * Condition such as '=', '>' or 'is not' - * @param string $value - * Value. Will be quoted unless you pass expression - * - * @return $this - */ - public function having($field, $cond = null, $value = null) - { - $num_args = func_num_args(); - - return $this->where($field, $cond, $value, 'having', $num_args); - } - - /** - * Subroutine which renders either [where] or [having]. - * - * @param string $kind - * 'where' or 'having' - * - * @return array Parsed chunks of query - */ - protected function _sub_render_where($kind) - { - // will be joined for output - $ret = []; - - // where() might have been called multiple times. Collect all conditions, - // then join them with AND keyword - foreach ($this->args[$kind] as $row) { - $ret[] = $this->_sub_render_condition($row); - } - - return $ret; - } - - /** - * Renders one condition. - * - * @param array $row - * Condition - * - * @return string - */ - protected function _sub_render_condition($row) - { - if (count($row) === 3) { - list ($field, $cond, $value) = $row; - } elseif (count($row) === 2) { - list ($field, $cond) = $row; - } elseif (count($row) === 1) { - list ($field) = $row; - } - - $field = $this->_consume($field, 'soft-escape'); - - if (count($row) == 1) { - // Only a single parameter was passed, so we simply include all - return $field; - } - - // below are only cases when 2 or 3 arguments are passed - - // if no condition defined - set default condition - if (count($row) == 2) { - $value = $cond; - - if (is_array($value)) { - $cond = 'in'; - } elseif ($value instanceof self && $value->mode === 'select') { - $cond = 'in'; - } else { - $cond = '='; - } - } else { - $cond = trim(strtolower($cond)); - } - - // below we can be sure that all 3 arguments has been passed - - // special conditions (IS | IS NOT) if value is null - if ($value === null) { - if ($cond === '=') { - $cond = 'is'; - } elseif (in_array($cond, [ - '!=', - '<>', - 'not' - ])) { - $cond = 'is not'; - } - } - - // value should be array for such conditions - if (in_array($cond, [ - 'in', - 'not in', - 'not' - ]) && is_string($value)) { - $value = array_map('trim', explode(',', $value)); - } - - // special conditions (IN | NOT IN) if value is array - if (is_array($value)) { - $cond = in_array($cond, [ - '!=', - '<>', - 'not', - 'not in' - ]) ? 'not in' : 'in'; - - // special treatment of empty array condition - if (empty($value)) { - if ($cond == 'in') { - return $field . '<>' . $field; // never true - } - - return '(' . $field . '=' . $field . ' or ' . $field . ' is null)'; // always true - } - - $value = '(' . implode(',', $this->_param($value)) . ')'; - - return $field . ' ' . $cond . ' ' . $value; - } - - // if value is object, then it should be Expression or Query itself - // otherwise just escape value - $value = $this->_consume($value, 'param'); - - return $field . ' ' . $cond . ' ' . $value; - } - - /** - * Renders [where]. - * - * @return string rendered SQL chunk - */ - protected function _render_where() - { - if (! isset($this->args['where'])) { - return; - } - - return ' where ' . implode(' and ', $this->_sub_render_where('where')); - } - - /** - * Renders [orwhere]. - * - * @return string rendered SQL chunk - */ - protected function _render_orwhere() - { - if (! isset($this->args['where'])) { - return; - } - - return implode(' or ', $this->_sub_render_where('where')); - } - - /** - * Renders [andwhere]. - * - * @return string rendered SQL chunk - */ - protected function _render_andwhere() - { - if (! isset($this->args['where'])) { - return; - } - - return implode(' and ', $this->_sub_render_where('where')); - } - - /** - * Renders [having]. - * - * @return string rendered SQL chunk - */ - protected function _render_having() - { - if (! isset($this->args['having'])) { - return; - } - - return ' having ' . implode(' and ', $this->_sub_render_where('having')); - } - - // }}} - - // {{{ group() - - /** - * Implements GROUP BY functionality. - * Simply pass either field name - * as string or expression. - * - * @param mixed $group - * Group by this - * - * @return $this - */ - public function group($group) - { - // Case with comma-separated fields - if (is_string($group) && ! $this->isUnescapablePattern($group) && strpos($group, ',') !== false) { - $group = explode(',', $group); - } - - if (is_array($group)) { - foreach ($group as $g) { - if (($g instanceof Expression) || (isset($g) && strlen($g) > 0)) { - $this->args['group'][] = $g; - } - } - return $this; - } - - if (($group instanceof Expression) || (isset($group) && strlen($group) > 0)) { - $this->args['group'][] = $group; - } - - return $this; - } - - /** - * Renders [group]. - * - * @return string rendered SQL chunk - */ - protected function _render_group() - { - if (! isset($this->args['group'])) { - return ''; - } - - $g = array_map(function ($a) { - return $this->_consume($a, 'soft-escape'); - }, $this->args['group']); - - return ' group by ' . implode(', ', $g); - } - - // }}} - - // {{{ Set field implementation - - /** - * Sets field value for INSERT or UPDATE statements. - * - * @param string|array $field - * Name of the field - * @param mixed $value - * Value of the field - * - * @return $this - */ - public function set($field, $value = null) - { - if ($value === false) { - throw new Exception([ - 'Value "false" is not supported by SQL', - 'field' => $field, - 'value' => $value - ]); - } - - if (is_array($value)) { - throw new Exception([ - 'Array values are not supported by SQL', - 'field' => $field, - 'value' => $value - ]); - } - - if (is_array($field)) { - foreach ($field as $key => $value) { - $this->set($key, $value); - } - - return $this; - } - - if (is_string($field) || $field instanceof Expression || $field instanceof Expressionable) { - $this->args['set'][] = [ - $field, - $value - ]; - } else { - throw new Exception([ - 'Field name should be string or Expressionable', - 'field' => $field - ]); - } - - return $this; - } - - /** - * Renders [set] for UPDATE query. - * - * @return string rendered SQL chunk - */ - protected function _render_set() - { - // will be joined for output - $ret = []; - - if (isset($this->args['set']) && $this->args['set']) { - foreach ($this->args['set'] as list ($field, $value)) { - $field = $this->_consume($field, 'escape'); - $value = $this->_consume($value, 'param'); - - $ret[] = $field . '=' . $value; - } - } - - return implode(', ', $ret); - } - - /** - * Renders [set_fields] for INSERT. - * - * @return string rendered SQL chunk - */ - protected function _render_set_fields() - { - // will be joined for output - $ret = []; - - if ($this->args['set']) { - foreach ($this->args['set'] as list ($field /* , $value */)) { - $field = $this->_consume($field, 'escape'); - - $ret[] = $field; - } - } - - return implode(',', $ret); - } - - /** - * Renders [set_values] for INSERT. - * - * @return string rendered SQL chunk - */ - protected function _render_set_values() - { - // will be joined for output - $ret = []; - - if ($this->args['set']) { - foreach ($this->args['set'] as list (/*$field*/, $value)) { - $value = $this->_consume($value, 'param'); - - $ret[] = $value; - } - } - - return implode(',', $ret); - } - - // }}} - - // {{{ Option - - /** - * Set options for particular mode. - * - * @param mixed $option - * @param string $mode - * select|insert|replace - * - * @return $this - */ - public function option($option, $mode = 'select') - { - // Case with comma-separated options - if (is_string($option) && strpos($option, ',') !== false) { - $option = explode(',', $option); - } - - if (is_array($option)) { - foreach ($option as $opt) { - $this->args['option'][$mode][] = $opt; - } - - return $this; - } - - $this->args['option'][$mode][] = $option; - - return $this; - } - - /** - * Renders [option]. - * - * @return string rendered SQL chunk - */ - protected function _render_option() - { - if (! isset($this->args['option'][$this->mode])) { - return ''; - } - - return ' ' . implode(' ', $this->args['option'][$this->mode]); - } - - // }}} - - // {{{ Query Modes - - /** - * Execute select statement. - * - * @return \PDOStatement - */ - public function select() - { - return $this->mode('select')->execute(); - } - - /** - * Execute insert statement. - * - * @return \PDOStatement - */ - public function insert() - { - return $this->mode('insert')->execute(); - } - - /** - * Execute update statement. - * - * @return \PDOStatement - */ - public function update() - { - return $this->mode('update')->execute(); - } - - /** - * Execute replace statement. - * - * @return \PDOStatement - */ - public function replace() - { - return $this->mode('replace')->execute(); - } - - /** - * Execute delete statement. - * - * @return \PDOStatement - */ - public function delete() - { - return $this->mode('delete')->execute(); - } - - /** - * Execute truncate statement. - * - * @return \PDOStatement - */ - public function truncate() - { - return $this->mode('truncate')->execute(); - } - - // }}} - - // {{{ Limit - - /** - * Limit how many rows will be returned. - * - * @param int $cnt - * Number of rows to return - * @param int $shift - * Offset, how many rows to skip - * - * @return $this - */ - public function limit($cnt, $shift = null) - { - $this->args['limit'] = [ - 'cnt' => $cnt, - 'shift' => $shift - ]; - - return $this; - } - - /** - * Renders [limit]. - * - * @return string rendered SQL chunk - */ - public function _render_limit() - { - if (isset($this->args['limit'])) { - return ' limit ' . (int) $this->args['limit']['shift'] . ', ' . (int) $this->args['limit']['cnt']; - } - } - - // }}} - - // {{{ Order - - /** - * Orders results by field or Expression. - * See documentation for full - * list of possible arguments. - * - * $q->order('name'); - * $q->order('name desc'); - * $q->order('name desc, id asc') - * $q->order('name',true); - * - * @param string|array $order - * Order by - * @param string|bool $desc - * true to sort descending - * - * @return $this - */ - public function order($order, $desc = null) - { - // Case with comma-separated fields or first argument being an array - if (is_string($order) && strpos($order, ',') !== false) { - $order = explode(',', $order); - } - - if (is_array($order)) { - if ($desc !== null) { - throw new Exception('If first argument is array, second argument must not be used'); - } - foreach (array_reverse($order) as $o) { - $this->order($o); - } - - return $this; - } - - // First argument may contain space, to divide field and ordering keyword. - // Explode string only if ordering keyword is 'desc' or 'asc'. - if ($desc === null && is_string($order) && strpos($order, ' ') !== false) { - $_chunks = explode(' ', $order); - $_desc = strtolower(array_pop($_chunks)); - if (in_array($_desc, [ - 'desc', - 'asc' - ])) { - $order = implode(' ', $_chunks); - $desc = $_desc; - } - } - - if (is_bool($desc)) { - $desc = $desc ? 'desc' : ''; - } elseif (strtolower($desc) === 'asc') { - $desc = ''; - } else { - // allows custom order like "order by name desc nulls last" for Oracle - } - - $this->args['order'][] = [ - $order, - $desc - ]; - - return $this; - } - - /** - * Renders [order]. - * - * @return string rendered SQL chunk - */ - public function _render_order() - { - if (! isset($this->args['order'])) { - return ''; - } - - $x = []; - foreach ($this->args['order'] as $tmp) { - list ($arg, $desc) = $tmp; - $x[] = $this->_consume($arg, 'soft-escape') . ($desc ? (' ' . $desc) : ''); - } - - return ' order by ' . implode(', ', array_reverse($x)); - } - - // }}} - public function __debugInfo() - { - $arr = [ - 'R' => false, - 'mode' => $this->mode - // 'template' => $this->template, - // 'params' => $this->params, - // 'connection' => $this->connection, - // 'main_table' => $this->main_table, - // 'args' => $this->args, - ]; - - try { - $arr['R'] = $this->getDebugQuery(); - } catch (\Exception $e) { - $arr['R'] = $e->getMessage(); - } - - return $arr; - } - - // {{{ Miscelanious - - /** - * Renders query template. - * If the template is not explicitly set will use "select" mode. - * - * @return string - */ - public function render() - { - if (! $this->template) { - $this->mode('select'); - } - - return parent::render(); - } - - /** - * Switch template for this query. - * Determines what would be done - * on execute. - * - * By default it is in SELECT mode - * - * @param string $mode - * - * @return $this - */ - public function mode($mode) - { - $prop = 'template_' . $mode; - - if (isset($this->{$prop})) { - $this->mode = $mode; - $this->template = $this->{$prop}; - } else { - throw new Exception([ - 'Query does not have this mode', - 'mode' => $mode - ]); - } - - return $this; - } - - /** - * Use this instead of "new Query()" if you want to automatically bind - * query to the same connection as the parent. - * - * @param array $properties - * - * @return Query - */ - public function dsql($properties = []) - { - $q = new static($properties); - $q->connection = $this->connection; - - return $q; - } - - /** - * Returns Expression object for the corresponding Query - * sub-class (e.g. - * Query_MySQL will return Expression_MySQL). - * - * Connection is not mandatory, but if set, will be preserved. This - * method should be used for building parts of the query internally. - * - * @param array $properties - * @param array $arguments - * - * @return Expression - */ - public function expr($properties = [], $arguments = null) - { - $c = $this->expression_class; - $e = new $c($properties, $arguments); - $e->connection = $this->connection; - - return $e; - } - - /** - * Returns Expression object for NOW() or CURRENT_TIMESTAMP() method. - * - * @param int $precision - * - * @return Expression - */ - public function exprNow($precision = null) - { - if ($precision !== null) { - return $this->expr('current_timestamp([])', [ - $precision - ]); - } - - return $this->expr('current_timestamp()'); - } - - /** - * Returns new Query object of [or] expression. - * - * @return Query - */ - public function orExpr() - { - return $this->dsql([ - 'template' => '[orwhere]' - ]); - } - - /** - * Returns new Query object of [and] expression. - * - * @return Query - */ - public function andExpr() - { - return $this->dsql([ - 'template' => '[andwhere]' - ]); - } - - /** - * Returns Query object of [case] expression. - * - * @param mixed $operand - * Optional operand for case expression. - * - * @return Query - */ - public function caseExpr($operand = null) - { - $q = $this->dsql([ - 'template' => '[case]' - ]); - - if ($operand !== null) { - $q->args['case_operand'] = $operand; - } - - return $q; - } - - /** - * Returns a query for a function, which can be used as part of the GROUP - * query which would concatenate all matching fields. - * - * MySQL, SQLite - group_concat - * PostgreSQL - string_agg - * Oracle - listagg - * - * @param mixed $field - * @param string $delimiter - * - * @return Expression - */ - public function groupConcat($field, $delimeter = ',') - { - throw new Exception('groupConcat() is SQL-dependent, so use a correct class'); - } - - /** - * Add when/then condition for [case] expression. - * - * @param mixed $when - * Condition as array for normal form [case] statement or just value in case of short form [case] statement - * @param mixed $then - * Then expression or value - * - * @return $this - */ - public function when($when, $then) - { - $this->args['case_when'][] = [ - $when, - $then - ]; - - return $this; - } - - /** - * Add else condition for [case] expression. - * - * @param mixed $else - * Else expression or value - * - * @return $this - */ - // public function else($else) // PHP 5.6 restricts to use such method name. PHP 7 is fine with it - public function otherwise($else) - { - $this->args['case_else'] = $else; - - return $this; - } - - /** - * Renders [case]. - * - * @return string rendered SQL chunk - */ - protected function _render_case() - { - if (! isset($this->args['case_when'])) { - return; - } - - $ret = ''; - - // operand - if ($short_form = isset($this->args['case_operand'])) { - $ret .= ' ' . $this->_consume($this->args['case_operand'], 'soft-escape'); - } - - // when, then - foreach ($this->args['case_when'] as $row) { - if (! array_key_exists(0, $row) || ! array_key_exists(1, $row)) { - throw new Exception([ - 'Incorrect use of "when" method parameters', - 'row' => $row - ]); - } - - $ret .= ' when '; - if ($short_form) { - // short-form - if (is_array($row[0])) { - throw new Exception([ - 'When using short form CASE statement, then you should not set array as when() method 1st parameter', - 'when' => $row[0] - ]); - } - $ret .= $this->_consume($row[0], 'param'); - } else { - $ret .= $this->_sub_render_condition($row[0]); - } - - // then - $ret .= ' then ' . $this->_consume($row[1], 'param'); - } - - // else - if (array_key_exists('case_else', $this->args)) { - $ret .= ' else ' . $this->_consume($this->args['case_else'], 'param'); - } - - return ' case' . $ret . ' end'; - } - - /** - * Sets value in args array. - * Doesn't allow duplicate aliases. - * - * @param string $what - * Where to set it - table|field - * @param string $alias - * Alias name - * @param mixed $value - * Value to set in args array - */ - protected function _set_args($what, $alias, $value) - { - // save value in args - if ($alias === null) { - $this->args[$what][] = $value; - } else { - - // don't allow multiple values with same alias - if (isset($this->args[$what][$alias])) { - throw new Exception([ - 'Alias should be unique', - 'what' => $what, - 'alias' => $alias - ]); - } - - $this->args[$what][$alias] = $value; - } - } - - // / }}} -} diff --git a/src/Db/Query/MySQL.php b/src/Db/Query/MySQL.php deleted file mode 100644 index 21d9587f..00000000 --- a/src/Db/Query/MySQL.php +++ /dev/null @@ -1,50 +0,0 @@ -expr('group_concat({} separator [])', [ - $field, - $delimeter - ]); - } -} diff --git a/src/Db/Query/Oracle.php b/src/Db/Query/Oracle.php deleted file mode 100644 index ec91978d..00000000 --- a/src/Db/Query/Oracle.php +++ /dev/null @@ -1,62 +0,0 @@ -[limit_start][and_limit_end]'; - - /** - * Limit how many rows will be returned. - * - * @param int $cnt Number of rows to return - * @param int $shift Offset, how many rows to skip - * - * @return $this - */ - public function limit($cnt, $shift = null) - { - // This is for pre- 12c version - $this->template_select = $this->template_select_limit; - - return parent::limit($cnt, $shift); - } - - /** - * Renders [limit_start]. - * - * @return string rendered SQL chunk - */ - public function _render_limit_start() - { - return (int) $this->args['limit']['shift']; - } - - /** - * Renders [and_limit_end]. - * - * @return string rendered SQL chunk - */ - public function _render_and_limit_end() - { - if (!$this->args['limit']['cnt']) { - return ''; - } - - return ' and "__dsql_rownum"<='. - ((int) ($this->args['limit']['cnt'] + $this->args['limit']['shift'])); - } -} diff --git a/src/Db/Query/Oracle12c.php b/src/Db/Query/Oracle12c.php deleted file mode 100644 index 3ef6fa8e..00000000 --- a/src/Db/Query/Oracle12c.php +++ /dev/null @@ -1,32 +0,0 @@ -args['limit'])) { - $cnt = (int) $this->args['limit']['cnt']; - $shift = (int) $this->args['limit']['shift']; - - return ' '.trim( - ($shift ? 'OFFSET '.$shift.' ROWS' : ''). - ' '. - // as per spec 'NEXT' is synonymous to 'FIRST', so not bothering with it. - // https://docs.oracle.com/javadb/10.8.3.0/ref/rrefsqljoffsetfetch.html - ($cnt ? 'FETCH NEXT '.$cnt.' ROWS ONLY' : '') - ); - } - } -} diff --git a/src/Db/Query/OracleAbstract.php b/src/Db/Query/OracleAbstract.php deleted file mode 100644 index e6dd8f0f..00000000 --- a/src/Db/Query/OracleAbstract.php +++ /dev/null @@ -1,76 +0,0 @@ -args['sequence'] = $sequence; - - return $this; - } - - /** - * Renders [sequence]. - * - * @return string rendered SQL chunk - */ - public function _render_sequence() - { - return $this->args['sequence']; - } - - /** - * Returns a query for a function, which can be used as part of the GROUP - * query which would concatenate all matching fields. - * - * MySQL, SQLite - group_concat - * PostgreSQL - string_agg - * Oracle - listagg - * - * NOTE: LISTAGG() is only supported starting from Oracle 11g and up - * https://stackoverflow.com/a/16771200/1466341 - * - * @param mixed $field - * @param string $delimiter - * - * @return Expression - */ - public function groupConcat($field, $delimeter = ',') - { - return $this->expr('listagg({}, [])', [ - $field, - $delimeter - ]); - } -} diff --git a/src/Db/Query/PgSQL.php b/src/Db/Query/PgSQL.php deleted file mode 100644 index b4683556..00000000 --- a/src/Db/Query/PgSQL.php +++ /dev/null @@ -1,66 +0,0 @@ -args['limit'])) { - return ' limit ' . (int) $this->args['limit']['cnt'] . ' offset ' . (int) $this->args['limit']['shift']; - } - } - - /** - * Returns a query for a function, which can be used as part of the GROUP - * query which would concatenate all matching fields. - * - * MySQL, SQLite - group_concat - * PostgreSQL - string_agg - * Oracle - listagg - * - * @param mixed $field - * @param string $delimiter - * - * @return Expression - */ - public function groupConcat($field, $delimeter = ',') : Expression - { - return $this->expr('string_agg({}, [])', [ - $field, - $delimeter - ]); - } -} diff --git a/src/Db/Query/SQLite.php b/src/Db/Query/SQLite.php deleted file mode 100644 index dcded869..00000000 --- a/src/Db/Query/SQLite.php +++ /dev/null @@ -1,42 +0,0 @@ -expr('group_concat({}, [])', [ - $field, - $delimeter - ]); - } -} diff --git a/src/Db/ResultSet.php b/src/Db/ResultSet.php deleted file mode 100644 index f5a3a83b..00000000 --- a/src/Db/ResultSet.php +++ /dev/null @@ -1,18 +0,0 @@ -setDefaults(['ui' => 'segment']); - * - * Typically you would want to do that inside your constructor. The - * default handling of the properties is: - * - * - only apply properties that are defined - * - only set property if it's current value is null - * - ignore defaults that have null value - * - if existing property and default have array, then both arrays will be merged - * - * Several classes may opt to extend setDefaults, for example in UI - * setDefaults is extended to support classes and content: - * - * $segment->setDefaults(['Hello There', 'red', 'ui'=>'segment']); - * - * WARNING: Do not use this trait unless you have a lot of properties - * to inject. Also follow the guidelines on - * - * https://github.com/atk4/ui/wiki/Object-Constructors - * - * Relying on this trait excessively may cause anger management issues to - * some code reviewers. - */ -trait DiContainerTrait -{ - /** - * Check this property to see if trait is present in the object. - * - * @var bool - */ - public $_DIContainerTrait = true; - - /** - * Call from __construct() to initialize the properties allowing - * developer to pass Dependency Injector Container. - * - * @param array $properties - * @param bool $passively if true, existing non-null argument values will be kept - */ - public function setDefaults($properties = [], $passively = false) - { - if ($properties === null) { - $properties = []; - } - - if(is_array($properties)){ - $properties = new Options($properties); - } - $vars = get_object_vars($this); - foreach ($vars as $key => $type) { - $value = $properties->$key; - if(isset($value)){ - $this->$key = $value; - } - } - } - - /** - * Sets object property. - * Throws exception. - * - * @param mixed $key - * @param mixed $value - * @param bool $strict - */ - protected function setMissingProperty($key, $value) - { - // ignore numeric properties by default - if (is_numeric($key)) { - return; - } - - throw new Exception([ - 'Property for specified object is not defined', - 'object' => $this, - 'property'=> $key, - 'value' => $value, - ]); - } -} diff --git a/src/Dispatcher.php b/src/Dispatcher.php deleted file mode 100755 index 7ac17708..00000000 --- a/src/Dispatcher.php +++ /dev/null @@ -1,227 +0,0 @@ -. - */ -namespace Pluf; - -use Pluf\HTTP\Error404; -use Pluf\HTTP\Request; -use Pluf\HTTP\Response; -use Pluf; - -/** - * Pluf Request Dispatcher - * - * Dispatchs the input request in turned it into a response - * - * @author maso - * - */ -class Dispatcher -{ - - private array $views = []; - - /** - * Creates new instance of the dispatcher - * - * @return Dispatcher - */ - public static function getInstance(): Dispatcher - { - Logger::debug('New instance of Pluf Dispatcher is created.'); - return new Dispatcher(); - } - - /** - * - * @param Request $request - * @return Response - */ - public function dispatch(Request $request): Response - { - $response = new Response(null); - $appliedProcessors = []; - $processors = $this->loadConfigProcessors(); - $views = $this->getViews(); - - while (true) { - /* - * 1. Apply all ready to run processor - */ - if (! $this->applyRequestProcessors($request, $response, $processors, $appliedProcessors)) { - $processors = []; - break; - } - - /* - * 2. Find the next loop view - * - * Search for appropriate view and set the current view - */ - $to_match = $request->query; - $match = []; - $view = null; - for ($i = 0; $i < count($views); $i ++) { - if ($this->isMethodSupported($request, $views[$i]) && // - preg_match($views[$i]['regex'], $to_match, $match)) { - $view = $views[$i]; - break; - } - } - - if (! isset($view)) { - $response->setStatusCode(404)->setBody(new Error404('Requested View not found.')); - break; - } - - // If this is root, follow childrens - // 0. load processors - $processors = $this->loadViewProcessors($view); - - // 1. update match - $request->match = array_merge($match, $request->match); - // 2. update params - if (array_key_exists('params', $view) && is_array($view['params'])) { - $request->params = array_merge($view['params'], $request->params); - } - if (isset($view['sub'])) { - // 1. update view - $views = $view['sub']; - if (is_string($views)) { - $views = include $views; - } - // 2. update query - $request->query = substr($to_match, strlen($match[0])); - continue; - } - // This is end of the tree - break; - } - $this->applyRequestProcessors($request, $response, $processors, $appliedProcessors); - return $this->applyResponseProcessors($request, $response, $appliedProcessors); - } - - /** - * Loads Ctrl layer - * - * @param - * string File including the views. - * @return bool Success. - */ - public function setViews($file): Dispatcher - { - if (is_array($file)) { - $this->views = $file; - Logger::debug('An array of contollers is set'); - } elseif (file_exists($file)) { - $this->views = include $file; - Logger::debug('Views are loaded from {}', $file); - } - return $this; - } - - /* - * Gets list of views - */ - private function getViews(): array - { - return $this->views; - } - - /** - * Checks if the method of the view is matche with the request - * - * @param Request $request - * to match - * @param array $view - * view to match with request - * @return bool true if the request match - */ - private function isMethodSupported(Request $request, array $view): bool - { - if (! isset($view['http-method'])) { - return true; - } - $methods = $view['http-method']; - - return (! is_array($methods) && $methods !== $request->method) || // - (is_array($methods) && ! in_array($request->method, $methods)); - } - - private function applyResponseProcessors(Request $request, Response $response, $processors): Response - { - /* - * Apply all processor in reverse - */ - foreach ($processors as $processor) { - try { - $response = $processor->response($request, $response); - } catch (\Exception $ex) { - Logger::debug('Fail to apply response processor {}. Continue to run rest of the chain.', $processor); - } - } - return $response; - } - - private function applyRequestProcessors(Request $request, Response $response, $processors, &$applyedProcessors): bool - { - /* - * Apply all processor in reverse - */ - foreach ($processors as $processor) { - try { - array_unshift($applyedProcessors, $processor); - $processor->request($request); - } catch (\Exception $ex) { - $response->setBody($ex) - ->setStatusCode(500); - Logger::debug('Fail to apply request processor {}. The chain is stoped.', $processor); - return false; - } - } - return true; - } - - private function loadViewProcessors($view) - { - $processors = []; - if (array_key_exists('processors', $view) && is_array($view['processors'])) { - foreach ($view['processors'] as $processorName) { - $processors[] = new $processorName(); - } - } - return $processors; - } - - private function loadConfigProcessors() - { - $processors = []; - /* - * # Load processors - * - * Load all processors based on configuration and then run them on - * request. and response - */ - foreach (Pluf::getConfig('processors', []) as $processorName) { - $processors[] = new $processorName(); - } - return $processors; - } -} - diff --git a/src/Encoder.php b/src/Encoder.php deleted file mode 100755 index f1b9d214..00000000 --- a/src/Encoder.php +++ /dev/null @@ -1,177 +0,0 @@ -. - */ -namespace Pluf; - -use Pluf\HTTP\Error403; - -/** - * Validators are functions used to validate user/program input. - * - * A validator signature is: - * my_validator($field_data, $params=array()) - * with $params an associative array of parameters. - * - * - * A validator must fail on an empty string by raising an - * Pluf_Form_Invalid Exception or return the data in the right format - * (string, bool, whatever). - * - * FIXME: Escape the strings when bad strings are sent in the error message. - */ -class Encoder -{ - - /** - * Store the complete form data if validation is coming from a form. - */ - protected $form = array(); - - /** - * Set the form data. - * - * @param - * &array Reference to the form data - */ - function setFormData(&$form) - { - $this->form = $form; - } - - /** - * Check if could be empty or not. - */ - function checkEmpty($data, $form = array(), $p = array()) - { - if (strlen($data) == 0 and isset($p['blank']) and false == $p['blank']) { - throw new Error403('The value must not be empty.'); - } - return true; - } - - /** - * Validate an url. - * - * Only the structure is checked, no check of availability of the - * url is performed. It is a really basic validation. - */ - static function url($url, $form = array(), $p = array()) - { - $ip = '(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.' . '(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}'; - $dom = '([a-z0-9\.\-]+)'; - if (preg_match('!^(http|https|ftp|gopher)\://(' . $ip . '|' . $dom . ')!i', $url)) { - return $url; - } else { - throw new Error403(sprintf(('The URL %s is not valid.'), htmlspecialchars($url))); - } - } - - static function varchar($string, $form = array(), $p = array()) - { - if (isset($p['size']) && strlen($string) > $p['size']) { - throw new Error403(sprintf(('The value should not be more than %s characters long.'), $p['size'])); - } - return $string; - } - - static function password($string, $form = array(), $p = array()) - { - if (strlen($string) < 6) { - throw new Error403(sprintf(('The password must be at least %s characters long.'), '6')); - } - return $string; - } - - static function email($string, $form = array(), $p = array()) - { - if (preg_match('/^[A-Z0-9._%-][+A-Z0-9._%-]*@(?:[A-Z0-9-]+\.)+[A-Z]{2,4}$/i', $string)) { - return $string; - } else { - throw new Error403(sprintf(('The email address "%s" is not valid.'), $string)); - } - } - - static function text($string, $form = array(), $p = array()) - { - return Encoder::varchar($string, $form, $p); - } - - static function sequence($id, $form = array(), $p = array()) - { - return Encoder::integer($id, $p); - } - - static function boolean($bool, $form = array(), $p = array()) - { - if (in_array($bool, array( - 'on', - 'y', - '1', - 1, - true - ))) { - return true; - } - return false; - } - - static function foreignkey($id, $form = array(), $p = array()) - { - return Encoder::integer($id, $p); - } - - static function integer($int, $form = array(), $p = array()) - { - if (! preg_match('/[0-9]+/', $int)) { - throw new Error403(('The value must be an integer.')); - } - return (int) $int; - } - - static function datetime($datetime, $form = array(), $p = array()) - { - if (false === ($stamp = strtotime($datetime))) { - throw new Error403(sprintf(('The date and time %s are not valid.'), htmlspecialchars($datetime))); - } - // convert to GMT - return gmdate('Y-m-d H:i:s', $stamp); - } - - static function date($date, $form = array(), $p = array()) - { - $ymd = explode('-', $date); - if (count($ymd) != 3 or strlen($ymd[0]) != 4 or false === checkdate($ymd[1], $ymd[2], $ymd[0])) { - throw new Error403(sprintf(('The date %s is not valid.'), htmlspecialchars($date))); - } - return $date; - } - - static function manytomany($vals, $form = array(), $p = array()) - { - $res = array(); - foreach ($vals as $val) { - $res[] = Encoder::integer($val); - } - return $res; - } - - static function float($val, $form = array(), $p = array()) - { - return (float) $val; - } -} diff --git a/src/Exception.php b/src/Exception.php index 941843b5..313005bf 100755 --- a/src/Exception.php +++ b/src/Exception.php @@ -18,18 +18,14 @@ */ namespace Pluf; -use Pluf\ExceptionRenderer\Console; -use Pluf\ExceptionRenderer\HTML; -use Pluf\ExceptionRenderer\HTMLText; -use Pluf\ExceptionRenderer\JSON; -use Pluf\ExceptionRenderer\RendererAbstract; -use Pluf\Translator\ITranslatorAdapter; use Throwable; +use JsonSerializable; +use RuntimeException; /** * Pluf root exception type * - * All pluf application exceptions are subclass of the \Pluf\Exception. If any exeption throw + * All pluf application exceptions are subclass of the \Pluf\Data\Exception. If any exeption throw * which is not subclass of it, the framework will consider as non expected exception. * * @@ -37,147 +33,43 @@ * @since Pluf6 * */ -class Exception extends \Exception /* implements \JsonSerializable */ +class Exception extends RuntimeException implements JsonSerializable { - /** @var array */ - public $params = []; + private array $solutions = []; - /** @var string */ - protected $custom_exception_title = 'Critical Error'; + private array $params = []; - /** @var string The name of the Exception for custom naming */ - protected $custom_exception_name = null; + private int $status = 500; /** - * Most exceptions would be a cause by some other exception, Agile - * Core will encapsulate them and allow you to access them anyway. + * Crates new instance of the exception * - * @var array - */ - private $trace2; - - // because PHP's use of final() sucks! - - /** @var string[] */ - private $solutions = []; - - // store solutions - - /** @var ITranslatorAdapter */ - private $adapter; - - /** - * Constructor. - * - * @param string|array $message + * @param string $message + * the message to show * @param int $code + * the error code * @param Throwable $previous - */ - public function __construct($message = '', ?int $code = null, Throwable $previous = null) + * the cause root of the error + * @param int $status + * the status code based on HTTP status for example 500 for internal error and 400 for user errors. + * @param array $params + * parameters that is used in message and solutions + * @param array $solutions + * list of common way to solve the problem + */ + public function __construct($message = '', ?int $code = null, ?Throwable $previous = null, ?int $status = 500, ?array $params = [], ?array $solutions = []) { + if (is_array($message)) { // message contain additional parameters - $this->params = $message; + $params = $message; $message = array_shift($this->params); } - parent::__construct($message, $code ?? 0, $previous); - $this->trace2 = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT); - } - - /** - * Change message (subject) of a current exception. - * Primary use is - * for localization purposes. - * - * @param string $message - * - * @return $this - */ - public function setMessage($message): self - { - $this->message = $message; - - return $this; - } - - /** - * Return trace array. - * - * @return array - */ - public function getMyTrace() - { - return $this->trace2; - } - - /** - * Return exception message using color sequences. - * - * : - * - * - * trace - * - * -- - * - * - * @return string - */ - public function getColorfulText(): string - { - return (string) new Console($this, $this->adapter); - } - - /** - * Similar to getColorfulText() but will use raw HTML for outputting colors. - * - * @return string - */ - public function getHTMLText(): string - { - return (string) new HTMLText($this, $this->adapter); - } - - /** - * Return exception message using HTML block and Semantic UI formatting. - * It's your job - * to put it inside boilerplate HTML and output, e.g:. - * - * $l = new \atk4\ui\App(); - * $l->initLayout('Centered'); - * $l->layout->template->setHTML('Content', $e->getHTML()); - * $l->run(); - * exit; - * - * @return string - */ - public function getHTML(): string - { - return (string) new HTML($this, $this->adapter); - } - - /** - * Return exception in JSON Format. - * - * @return string - */ - public function getJSON(): string - { - return (string) new JSON($this, $this->adapter); - } - - /** - * Safely converts some value to string. - * - * @param mixed $val - * - * @return string - */ - public function toString($val): string - { - return RendererAbstract::toSafeString($val); + $this->status = $status; + $this->params = $params; + $this->solutions = $solutions; } /** @@ -185,44 +77,15 @@ public function toString($val): string * * @return array */ - public function getParams() + public function getParams(): array { return $this->params; } /** - * Augment existing exception with more info. - * - * @param string $param - * @param mixed $value - * - * @return $this - */ - public function addMoreInfo($param, $value): self - { - $this->params[$param] = $value; - - return $this; - } - - /** - * Add a suggested/possible solution to the exception. - * - * @todo can be added more features? usually we are out of App - * - * @param string $solution + * Gets solustions * - * @return Exception - */ - public function addSolution(string $solution) - { - $this->solutions[] = $solution; - - return $this; - } - - /** - * Get the solutions array. + * @return array */ public function getSolutions(): array { @@ -230,98 +93,53 @@ public function getSolutions(): array } /** - * Get the custom Exception name, if defined in $custom_exception_name. + * Get status of the error * - * @return string + * @return int */ - public function getCustomExceptionName(): string + public function getStatus(): int { - return $this->custom_exception_name ?? get_class($this); + return $this->status; } /** - * Get the custom Exception title, if defined in $custom_exception_title. * - * @return string + * {@inheritdoc} + * @see JsonSerializable::jsonSerialize() */ - public function getCustomExceptionTitle(): string + public function jsonSerialize() { - return $this->custom_exception_title; + return [ + 'code' => $this->code, + 'status' => $this->status, + 'message' => $this->message, + 'parmas' => $this->params, + 'solutions' => $this->getSolutions() + ]; } - + + public function jsonSerializeDebug(){ + return [ + 'code' => $this->getCode(), + 'status' => $this->getStatus(), + 'message' => $this->getMessage(), + 'parmas' => $this->getParams(), + 'solutions' => $this->getSolutions(), + 'file' => $this->getFile(), + 'line' => $this->getLine(), + 'stack' => $this->getTrace() + ]; + } + /** - * Set Custom Translator adapter. - * - * @param ITranslatorAdapter|null $adapter * - * @return Exception + * {@inheritdoc} + * @see RuntimeException::__toString() */ - public function setTranslatorAdapter(?ITranslatorAdapter $adapter = null): self + public function __toString(): string { - $this->adapter = $adapter; - - return $this; + return json_encode($this->jsonSerialize(), JSON_PRETTY_PRINT); } - - // use DiContainerTrait; - - // protected $status; - - // protected $link; - - // protected $developerMessage; - - // protected $data; - - // /** - // * یک نمونه از این کلاس ایجاد می‌کند. - // * - // * @param string $message - // * @param string $code - // * @param string $previous - // */ - // public function __construct($options) - // { - // $this->setDefaults($options); - // } - - // public function getDeveloperMessage() - // { - // return $this->developerMessage; - // } - - // public function getStatus() - // { - // return $this->status; - // } - - // public function setData($data) - // { - // $this->data = $data; - // } - - // public function jsonSerialize() - // { - // if (Pluf::f('debug', false)) { - // return array( - // 'code' => $this->code, - // 'status' => $this->status, - // 'link' => $this->link, - // 'message' => $this->message, - // 'data' => $this->data, - // 'developerMessage' => $this->developerMessage, - // 'stack' => $this->getTrace() - // ); - // } else { - // return array( - // 'code' => $this->code, - // 'status' => $this->status, - // 'link' => $this->link, - // 'message' => $this->message, - // 'data' => $this->data - // ); - // } - // } } diff --git a/src/ExceptionBuilder.php b/src/ExceptionBuilder.php new file mode 100644 index 00000000..398a74e2 --- /dev/null +++ b/src/ExceptionBuilder.php @@ -0,0 +1,120 @@ +message = $message; + return $this; + } + + /** + * + * @param multitype: $solutions + */ + public function setSolutions($solutions): self + { + $this->solutions = $solutions; + return $this; + } + + /** + * Adds new solution + * + * @param mixed $solution + * @return self + */ + public function addSolution($solution): self + { + $this->solutions[] = $solution; + return $this; + } + + /** + * + * @param multitype: $params + */ + public function setParams($params): self + { + $this->params = $params; + return $this; + } + + /** + * + * @param multitype: $params + */ + public function setParam($key, $param): self + { + $this->params[$key] = $param; + return $this; + } + + /** + * + * @param number $status + */ + public function setStatus($status): self + { + $this->status = $status; + return $this; + } + + /** + * + * @param number $code + */ + public function setCode($code): self + { + $this->code = $code; + return $this; + } + + /** + * + * @param mixed $previous + */ + public function setPrevious($previous): self + { + $this->previous = $previous; + return $this; + } + + /** + * Build a new instance of exception. + * + * @return Exception + */ + public function build(): Exception + { + $ex = new Exception($this->message, $this->code, $this->previous, $this->params, $this->solutions); + return $ex; + } +} + diff --git a/src/ExceptionRenderer/Console.php b/src/ExceptionRenderer/Console.php deleted file mode 100644 index 55d7081a..00000000 --- a/src/ExceptionRenderer/Console.php +++ /dev/null @@ -1,133 +0,0 @@ -getExceptionTitle(); - $class = $this->getExceptionName(); - - $tokens = [ - '{TITLE}' => $title, - '{CLASS}' => $class, - '{MESSAGE}' => $this->_($this->exception->getMessage()), - '{CODE}' => $this->exception->getCode() ? ' [code: ' . $this->exception->getCode() . ']' : '' - ]; - - $this->output .= $this->replaceTokens($tokens, <<is_atk_exception) { - return; - } - - /** @var Exception $exception */ - $exception = $this->exception; - - if (0 === count($exception->getParams())) { - return; - } - - foreach ($exception->getParams() as $key => $val) { - $key = str_pad((string) $key, 19, ' ', STR_PAD_LEFT); - $this->output .= PHP_EOL . "\e[91m" . $key . ': ' . static::toSafeString($val) . "\e[0m"; - } - } - - protected function processSolutions(): void - { - if (false === $this->is_atk_exception) { - return; - } - - /** @var Exception $exception */ - $exception = $this->exception; - - if (0 === count($exception->getSolutions())) { - return; - } - - foreach ($exception->getSolutions() as $key => $val) { - $this->output .= PHP_EOL . "\e[92mSolution: " . $val . "\e[0m"; - } - } - - protected function processStackTrace(): void - { - $this->output .= <<processStackTraceInternal(); - } - - protected function processStackTraceInternal(): void - { - $text = <<is_atk_exception ? $this->exception->getMyTrace() : $this->exception->getTrace(); - $trace_count = count($trace); - foreach ($trace as $index => $call) { - $call = $this->parseCallTraceObject($call); - - if ($in_atk && ! preg_match('/atk4\/.*\/src\//', $call['file'])) { - $escape_frame = true; - $in_atk = false; - } - - $tokens['{FILE}'] = $call['file_formatted']; - $tokens['{LINE}'] = $call['line_formatted']; - $tokens['{OBJECT}'] = null !== $call['object_formatted'] ? " - \e[0;32m" . $call['object_formatted'] . "\e[0m" : ''; - $tokens['{CLASS}'] = null !== $call['class'] ? "\e[0;32m" . $call['class'] . "::\e[0m" : ''; - - $tokens['{FUNCTION_COLOR}'] = $escape_frame ? "\e[0;31m" : "\e[0;33m"; - $tokens['{FUNCTION}'] = $call['function']; - $tokens['{FUNCTION_ARGS}'] = '()'; - - if ($escape_frame) { - $escape_frame = false; - $args = []; - foreach ($call['args'] as $arg) { - $args[] = static::toSafeString($arg); - } - - $tokens['{FUNCTION_ARGS}'] = PHP_EOL . str_repeat(' ', 40) . "\e[0;31m(" . implode(', ', $args) . ')'; - } - - $this->output .= $this->replaceTokens($tokens, $text); - } - } - - protected function processPreviousException(): void - { - if (! $this->exception->getPrevious()) { - return; - } - - $this->output .= PHP_EOL . "\e[1;45mCaused by Previous Exception:\e[0m" . PHP_EOL; - - $this->output .= (string) (new static($this->exception->getPrevious())); - $this->output .= <<getExceptionTitle(); - $class = $this->getExceptionName(); - - $tokens = [ - '{TITLE}' => $title, - '{CLASS}' => $class, - '{MESSAGE}' => $this->_($this->exception->getMessage()), - '{CODE}' => $this->exception->getCode() ? ' [code: ' . $this->exception->getCode() . ']' : '' - ]; - - $this->output .= $this->replaceTokens($tokens, ' -
      - -
      -
      {TITLE}
      - {CLASS} {CODE} - {MESSAGE} -
      -
      - '); - } - - protected function processParams(): void - { - if (false === $this->is_atk_exception) { - return; - } - - /** @var Exception $exception */ - $exception = $this->exception; - - if (0 === count($exception->getParams())) { - return; - } - - $text = ' -
      -
      Exception Parameters
      - {PARAMS} -
      - '; - - $tokens = [ - '{PARAMS}' => '' - ]; - $text_inner = '
      {KEY}:{VAL}
      '; - foreach ($exception->getParams() as $key => $val) { - $key = str_pad((string) $key, 19, ' ', STR_PAD_LEFT); - $key = htmlentities($key); - $val = htmlentities(static::toSafeString($val)); - - $tokens['{PARAMS}'] .= $this->replaceTokens([ - '{KEY}' => $key, - '{VAL}' => $val - ], $text_inner); - } - - $this->output .= $this->replaceTokens($tokens, $text); - } - - protected function processSolutions(): void - { - if (false === $this->is_atk_exception) { - return; - } - - /** @var Exception $exception */ - $exception = $this->exception; - - if (0 === count($exception->getSolutions())) { - return; - } - - $text = ' -
      -
      Suggested solutions
      - {SOLUTIONS} -
      - '; - - $tokens = [ - '{SOLUTIONS}' => '' - ]; - $text_inner = '
      {VAL}
      '; - foreach ($exception->getSolutions() as $key => $val) { - $tokens['{SOLUTIONS}'] .= $this->replaceTokens([ - '{VAL}' => htmlentities($val) - ], $text_inner); - } - - $this->output .= $this->replaceTokens($tokens, $text); - } - - protected function processStackTrace(): void - { - $this->output .= ' - - - - - '; - - $this->processStackTraceInternal(); - - $this->output .= ' - -
      Stack Trace
      #FileObjectMethod
      - '; - } - - protected function processStackTraceInternal(): void - { - $text = ' - - {INDEX} - {FILE_LINE} - {OBJECT} - {FUNCTION}{FUNCTION_ARGS} - - '; - - $in_atk = true; - $escape_frame = false; - $tokens_trace = []; - $trace = $this->is_atk_exception ? $this->exception->getMyTrace() : $this->exception->getTrace(); - $trace_count = count($trace); - foreach ($trace as $index => $call) { - $call = $this->parseCallTraceObject($call); - - if ($in_atk && ! preg_match('/atk4\/.*\/src\//', $call['file'])) { - $escape_frame = true; - $in_atk = false; - } - - $tokens_trace['{INDEX}'] = $trace_count - $index; - $tokens_trace['{FILE_LINE}'] = empty(trim($call['file_formatted'])) ? '' : $call['file_formatted'] . ':' . $call['line_formatted']; - $tokens_trace['{OBJECT}'] = false !== $call['object'] ? $call['object_formatted'] : '-'; - $tokens_trace['{CLASS}'] = false !== $call['class'] ? $call['class'] . '::' : ''; - $tokens_trace['{CSS_CLASS}'] = $escape_frame ? 'negative' : ''; - - $tokens_trace['{FUNCTION}'] = $call['function']; - $tokens_trace['{FUNCTION_ARGS}'] = '()'; - - if ($escape_frame) { - $escape_frame = false; - - $args = []; - foreach ($call['args'] as $arg) { - $args[] = static::toSafeString($arg); - } - - if (! empty($args)) { - $tokens_trace['{FUNCTION_ARGS}'] = " - - - - - (" . str_repeat(' ', 20) . implode(', ', $args) . ') - - '; - } - } - - $this->output .= $this->replaceTokens($tokens_trace, $text); - } - } - - protected function processPreviousException(): void - { - if (! $this->exception->getPrevious()) { - return; - } - - $this->output .= ' -
      -
      Caused by Previous Exception:
      -
      - '; - - $this->output .= (string) (new self($this->exception->getPrevious())); - } -} diff --git a/src/ExceptionRenderer/HTMLText.php b/src/ExceptionRenderer/HTMLText.php deleted file mode 100644 index 4db26e3b..00000000 --- a/src/ExceptionRenderer/HTMLText.php +++ /dev/null @@ -1,137 +0,0 @@ -getExceptionTitle(); - $class = $this->getExceptionName(); - - $tokens = [ - '{TITLE}' => $title, - '{CLASS}' => $class, - '{MESSAGE}' => $this->_($this->exception->getMessage()), - '{CODE}' => $this->exception->getCode() ? ' [code: ' . $this->exception->getCode() . ']' : '' - ]; - - $this->output .= $this->replaceTokens($tokens, <<<'HTML' - --[ {TITLE} ]--------------------------- - {CLASS}: {MESSAGE} {CODE} - - HTML); - } - - protected function processParams(): void - { - if (false === $this->is_atk_exception) { - return; - } - - /** @var Exception $exception */ - $exception = $this->exception; - - if (0 === count($exception->getParams())) { - return; - } - - $this->output .= PHP_EOL . 'Exception params: '; - - foreach ($exception->getParams() as $key => $val) { - $key = str_pad((string) $key, 19, ' ', STR_PAD_LEFT); - $key = htmlentities($key); - $val = htmlentities(static::toSafeString($val)); - - $this->output .= PHP_EOL . ' - ' . $key . ': ' . $val; - } - } - - protected function processSolutions(): void - { - if (false === $this->is_atk_exception) { - return; - } - - /** @var Exception $exception */ - $exception = $this->exception; - - if (0 === count($exception->getSolutions())) { - return; - } - - $this->output .= PHP_EOL . PHP_EOL . 'Suggested solutions:'; - foreach ($exception->getSolutions() as $key => $val) { - $this->output .= PHP_EOL . ' - ' . htmlentities($val); - } - } - - protected function processStackTrace(): void - { - $this->output .= <<<'HTML' - - Stack Trace: - - HTML; - - $this->processStackTraceInternal(); - } - - protected function processStackTraceInternal(): void - { - $text = <<<'HTML' - {FILE}:{LINE}{OBJECT} {CLASS}{FUNCTION}{FUNCTION_ARGS} - - HTML; - - $in_atk = true; - $escape_frame = false; - $tokens_trace = []; - $trace = $this->is_atk_exception ? $this->exception->getMyTrace() : $this->exception->getTrace(); - $trace_count = count($trace); - foreach ($trace as $index => $call) { - $call = $this->parseCallTraceObject($call); - - if ($in_atk && ! preg_match('/atk4\/.*\/src\//', $call['file'])) { - $escape_frame = true; - $in_atk = false; - } - - $tokens_trace['{FILE}'] = $call['file_formatted']; - $tokens_trace['{LINE}'] = $call['line_formatted']; - $tokens_trace['{OBJECT}'] = null !== $call['object'] ? " - " . $call['object_formatted'] . '' : ''; - $tokens_trace['{CLASS}'] = null !== $call['class'] ? $call['class'] . '::' : ''; - - $tokens_trace['{FUNCTION_COLOR}'] = $escape_frame ? 'pink' : 'gray'; - $tokens_trace['{FUNCTION}'] = $call['function']; - $tokens_trace['{FUNCTION_ARGS}'] = '()'; - - if ($escape_frame) { - $escape_frame = false; - - $args = []; - foreach ($call['args'] as $arg) { - $args[] = static::toSafeString($arg); - } - - $tokens_trace['{FUNCTION_ARGS}'] = PHP_EOL . str_repeat(' ', 40) . '(' . implode(', ', $args) . ')'; - } - - $this->output .= $this->replaceTokens($tokens_trace, $text); - } - } - - protected function processPreviousException(): void - { - if (! $this->exception->getPrevious()) { - return; - } - - $this->output .= PHP_EOL . 'Caused by Previous Exception:' . PHP_EOL; - - $this->output .= (string) (new static($this->exception->getPrevious())); - } -} diff --git a/src/ExceptionRenderer/JSON.php b/src/ExceptionRenderer/JSON.php deleted file mode 100644 index c3f7f23f..00000000 --- a/src/ExceptionRenderer/JSON.php +++ /dev/null @@ -1,162 +0,0 @@ - false, - 'code' => 0, - 'message' => '', - 'title' => '', - 'class' => '', - 'params' => [], - 'solution' => [], - 'trace' => [], - 'previous' => [], - ]; - - protected function processHeader(): void - { - $title = $this->getExceptionTitle(); - $class = $this->getExceptionName(); - - $this->json['code'] = $this->exception->getCode(); - $this->json['message'] = $this->_($this->exception->getMessage()); - $this->json['title'] = $title; - $this->json['class'] = $class; - } - - protected function processParams(): void - { - if (false === $this->is_atk_exception) { - return; - } - - /** @var Exception $exception */ - $exception = $this->exception; - - if (0 === count($exception->getParams())) { - return; - } - - foreach ($exception->getParams() as $key => $val) { - $this->json['params'][$key] = static::toSafeString($val); - } - } - - protected function processSolutions(): void - { - if (false === $this->is_atk_exception) { - return; - } - - /** @var Exception $exception */ - $exception = $this->exception; - - if (0 === count($exception->getSolutions())) { - return; - } - - foreach ($exception->getSolutions() as $key => $val) { - $this->json['solution'][$key] = $val; - } - } - - protected function processStackTrace(): void - { - $this->output .= <<<'HTML' -Stack Trace: - -HTML; - - $this->processStackTraceInternal(); - } - - protected function processStackTraceInternal(): void - { - $in_atk = true; - $escape_frame = false; - $tokens_trace = []; - $trace = $this->is_atk_exception ? $this->exception->getMyTrace() : $this->exception->getTrace(); - $trace_count = count($trace); - foreach ($trace as $index => $call) { - $call = $this->parseCallTraceObject($call); - - if ($in_atk && !preg_match('/atk4\/.*\/src\//', $call['file'])) { - $escape_frame = true; - $in_atk = false; - } - - if ($escape_frame) { - $escape_frame = false; - - $args = []; - foreach ($call['args'] as $arg) { - $args[] = static::toSafeString($arg); - } - - $call['args'] = $args; - } - - $this->json['stack'][] = $call; - } - } - - protected function processPreviousException(): void - { - if (!$this->exception->getPrevious()) { - return; - } - - $previous = new static($this->exception->getPrevious()); - $text = (string) $previous; // need to trigger processAll; - - $this->json['previous'] = $previous->json; - } - - protected function parseCallTraceObject($call): array - { - return [ - 'line' => $call['line'] ?? '', - 'file' => $call['file'] ?? '', - 'class' => $call['class'] ?? null, - 'object' => ($call['object'] ?? null) !== null ? ($call['object']->name ?? get_class($call['object'])) : null, - 'function' => $call['function'] ?? null, - 'args' => $call['args'] ?? [], - ]; - } - - public function __toString(): string - { - try { - $this->processAll(); - } catch (\Throwable $e) { - // fallback if error occur - $this->json = [ - 'success' => false, - 'code' => $this->exception->getCode(), - 'message' => 'Error during JSON renderer : '.$this->exception->getMessage(), - // avoid translation - //'message' => $this->_($this->exception->getMessage()), - 'title' => get_class($this->exception), - 'class' => get_class($this->exception), - 'params' => [], - 'solution' => [], - 'trace' => [], - 'previous' => [ - 'title' => get_class($e), - 'class' => get_class($e), - 'code' => $e->getCode(), - 'message' => $e->getMessage(), - ], - ]; - } - - return (string) json_encode($this->json, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); - } -} diff --git a/src/ExceptionRenderer/RendererAbstract.php b/src/ExceptionRenderer/RendererAbstract.php deleted file mode 100644 index 335eab83..00000000 --- a/src/ExceptionRenderer/RendererAbstract.php +++ /dev/null @@ -1,139 +0,0 @@ -adapter = $adapter; - $this->exception = $exception; - $this->is_atk_exception = $exception instanceof Exception; - } - - abstract protected function processHeader(): void; - - abstract protected function processParams(): void; - - abstract protected function processSolutions(): void; - - abstract protected function processStackTrace(): void; - - abstract protected function processStackTraceInternal(): void; - - abstract protected function processPreviousException(): void; - - protected function processAll(): void - { - $this->processHeader(); - $this->processParams(); - $this->processSolutions(); - $this->processStackTrace(); - $this->processPreviousException(); - } - - public function __toString(): string - { - try { - $this->processAll(); - - return $this->output; - } catch (\Throwable $e) { - // fallback if Exception occur in renderer - return get_class($this->exception).' ['.$this->exception->getCode().'] Error:'.$this->_($this->exception->getMessage()); - } - } - - protected function replaceTokens(array $tokens, string $text): string - { - return str_replace(array_keys($tokens), array_values($tokens), $text); - } - - protected function parseCallTraceObject($call): array - { - $parsed = [ - 'line' => (string) ($call['line'] ?? ''), - 'file' => (string) ($call['file'] ?? ''), - 'class' => $call['class'] ?? null, - 'object' => $call['object'] ?? null, - 'function' => $call['function'] ?? null, - 'args' => $call['args'] ?? [], - 'object_formatted' => null, - 'file_formatted' => null, - 'line_formatted' => null, - ]; - - $parsed['file_formatted'] = str_pad(substr($parsed['file'], -40), 40, ' ', STR_PAD_LEFT); - $parsed['line_formatted'] = str_pad($parsed['line'] ?? '', 4, ' ', STR_PAD_LEFT); - - if (null !== $parsed['object']) { - $parsed['object_formatted'] = $parsed['object']->name ?? get_class($parsed['object']); - } - - return $parsed; - } - - public static function toSafeString($val): string - { - if (is_object($val) && !$val instanceof \Closure) { - return isset($val->_trackableTrait) - ? get_class($val).' ('.$val->name.')' - : 'Object '.get_class($val); - } - - return (string) json_encode($val); - } - - protected static function getClassShortName(\Throwable $exception): string - { - return preg_replace('/.*\\\\/', '', get_class($exception)); - } - - /** - * @return string - */ - protected function getExceptionTitle(): string - { - return $this->is_atk_exception - ? $this->exception->getCustomExceptionTitle() - : static::getClassShortName($this->exception).' Error'; - } - - /** - * @return string - */ - protected function getExceptionName(): string - { - return $this->is_atk_exception - ? $this->exception->getCustomExceptionName() - : get_class($this->exception); - } - - public function _($message, array $parameters = [], ?string $domain = null, ?string $locale = null): string - { - return $this->adapter - ? $this->adapter->_($message, $parameters, $domain, $locale) - : Translator::instance()->_($message, $parameters, $domain, $locale); - } -} diff --git a/src/FileUtil.php b/src/FileUtil.php deleted file mode 100644 index 04a3d3d9..00000000 --- a/src/FileUtil.php +++ /dev/null @@ -1,293 +0,0 @@ -open($zipFilePath, ZIPARCHIVE::CREATE)) { - return false; - } - $source = str_replace('\\', '/', realpath($folder)); - if (is_dir($source) === true) { - $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST); - foreach ($files as $file) { - $file = str_replace('\\', '/', $file); - // Ignore "." and ".." folders - if (in_array(substr($file, strrpos($file, '/') + 1), array( - '.', - '..' - ))) { - continue; - } - $file = realpath($file); - if (is_dir($file) === true) { - $zip->addEmptyDir(str_replace($source . '/', '', $file . '/')); - } else if (is_file($file) === true) { - $zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file)); - } - } - } else if (is_file($source) === true) { - $zip->addFromString(basename($source), file_get_contents($source)); - } - return $zip->close(); - } - - public static function unzipToFolder($folder, $zipFilePath) - { - $zip = new ZipArchive(); - if ($zip->open($zipFilePath) === TRUE) { - $zip->extractTo($folder); - $zip->close(); - } else { - throw new \Pluf\Exception('Unable to unzip file.'); - } - } - - public static function createTempFolder($pre = '') - { - $key = $pre . md5(microtime() . rand(0, 123456789)); - $folder = Pluf::f('tmp_folder', '/tmp') . '/' . $key; - if (! mkdir($folder, 0777, true)) { - throw new \Pluf\Exception('Failed to create folder in temp'); - } - return $folder; - } -} diff --git a/src/Graphql.php b/src/Graphql.php deleted file mode 100644 index a949b715..00000000 --- a/src/Graphql.php +++ /dev/null @@ -1,127 +0,0 @@ -. - */ -namespace Pluf; - -use Pluf\Data\ModelUtils; -use Pluf\Graphql\Compiler; -use Pluf; - -/** - * Render a result based on GraphQl - * - * @author maso - * @since 4.0.0 - */ -class Graphql -{ - - private $cache = null; - - /** - * Creates new instance of the engine - * - * If the schema file name is not provided, it will default to - * - * Pluf::f('graphql_schema') - * - * If the cache folder name is not provided, it will default to - * - * Pluf::f('tmp_folder') - * - * - * @param Object $rootObject - * to render as result - * @param string $schema - * name of schema file - * @param array $cache - * a folder path to store the schema file - */ - function __construct($cache = null) - { - if (null == $cache) { - $this->cache = Pluf::f('tmp_folder'); - } else { - $this->cache = $cache; - } - } - - /** - * Render the template with the given context and return the content. - * - * @param $c Object - * Context. - * @param $rootValue string - * GraphQl query for example {id, items{id}} - * @return - */ - function render($c, $query) - { - // 1. root type - $itemType = null; - // if ($c instanceof Pluf_Paginator) { - // $rootType = 'Pluf_Paginator'; - // $itemType = ModelUtils::getModelCacheKey($c->model); - // $schema = 'Pluf_GraphQl_Schema__Pluf_Paginator_' . $itemType; - // } else { - $rootType = ModelUtils::getModelCacheKey($c); - $schema = 'Pluf_GraphQl_Schema_' . ModelUtils::skipeName($rootType); - // } - - // 2. load schema - $this->loadSchema($schema, $rootType, $itemType); - - // render result - return $this->generateResult($schema, $c, $query); - } - - private function loadSchema($schema, $rootType, $itemType) - { - if (class_exists($schema, false)) { - return; - } - $compiled_schema = $this->cache . '/' . $schema . '.phps'; - if (! file_exists($compiled_schema) or Pluf::f('debug')) { - $compiler = new Compiler($rootType, $itemType); - $compiler->write($schema, $compiled_schema); - } - include $compiled_schema; - } - - private function generateResult($schema, $c, $query) - { - $compiler = new $schema(); - $result = $compiler->render($c, $query); - if (array_key_exists('errors', $result)) { - throw new \Pluf\Exception('Fail to run GraphQl query: ' . $this->beautifyErrorMessage($result['errors'])); - } - return $result['data']; - } - - private function beautifyErrorMessage($errors) - { - // return print_r($errors, TRUE); - $messages = array(); - foreach ($errors as $error) { - $msg = '[line: ' . $error['locations'][0]['line'] . ', column: ' . $error['locations'][0]['column'] . ': ' . $error['message'] . ']'; - array_push($messages, $msg); - } - return implode(',', $messages); - } -} - diff --git a/src/Graphql/Compiler.php b/src/Graphql/Compiler.php deleted file mode 100644 index a53467d0..00000000 --- a/src/Graphql/Compiler.php +++ /dev/null @@ -1,407 +0,0 @@ -. - */ -namespace Pluf\Graphql; - -use Pluf\Data\ModelDescription; -use Pluf\Data\ModelProperty; -use Pluf\Data\ModelUtils; -use Pluf\Data\Schema; -use Pluf\HTTP\Error500; - -/** - * Create a compilre render - * - * @author maso - * - */ -class Compiler -{ - - private $rootType; - - private $itemType; - - private $compiledTypes; - - /** - * Creates new instance - * - * @param string $rootType - * the main data model - * @param string $itemType - * the list model if is paginated - */ - function __construct($rootType, $itemType = null) - { - $this->rootType = ModelUtils::getModelCacheKey($rootType); - $this->itemType = $itemType; - } - - /** - * Write graphql compiler - * - * @param string $outputFile - */ - public function write($className, $outputFile) - { - $this->compiledTypes = array(); - $renderCode = ''; - // if ($this->rootType === 'Pluf_Paginator') { - // $renderCode .= $this->createModelType($this->itemType); - // $renderCode .= '$itemType =' . $this->getNameOf($this->itemType) . ';'; - // $renderCode .= '$rootType =' . $this->createPaginatorType(); - // } else { - $renderCode .= $this->createModelType($this->rootType); - $renderCode .= '$rootType = $' . ModelUtils::skipeName($this->rootType) . ';'; - // } - $this->_write($className, $outputFile, $renderCode); - } - - /** - * Write the compiled template in the cache folder. - * Throw an exception if it cannot write it. - * - * @return bool Success in writing - */ - private function _write($className, $fileName, $renderCode) - { - $schema_content = 'compiledTypes as $item) { - $schema_content .= ' - $' . ModelUtils::skipeName($item) . ' = null;'; - } - $schema_content .= ' - // render code - ' . $renderCode . ' - try { - $schema = new Schema([ - \'query\' => $rootType - ]); - $result = GraphQL::executeQuery($schema, $query, $rootValue); - return $result->toArray(); - } catch (Exception $e) { - var_dump($e); - throw new \Pluf\HTTP\Error500($e->getMessage()); - } - } -} -'; - // mode "a" to not truncate before getting the lock - $fp = @fopen($fileName, 'a'); - if ($fp !== false) { - // Exclusive lock on writing - flock($fp, LOCK_EX); - // We have the unique pointeur, we truncate - ftruncate($fp, 0); - // Go back to the start of the file like a +w - rewind($fp); - fwrite($fp, $schema_content, strlen($schema_content)); - // Lock released, read access is possible - flock($fp, LOCK_UN); - fclose($fp); - @chmod($fileName, 0777); - return true; - } - throw new Error500(sprintf('Cannot write the GraphQl render function: %s', $fileName)); - } - - private static function createPaginatorType() - { - return 'new ObjectType([ - \'name\' => \'Pluf_paginator\', - \'fields\' => function () use (&$itemType){ - return [ - \'counts\' => [ - \'type\' => Type::int(), - \'resolve\' => function ($root) { - return $root->fetchItemsCount(); - } - ], - \'current_page\' => [ - \'type\' => Type::int(), - \'resolve\' => function ($root) { - return $root->current_page; - } - ], - \'items_per_page\' => [ - \'type\' => Type::int(), - \'resolve\' => function ($root) { - return $root->items_per_page; - } - ], - \'page_number\' => [ - \'type\' => Type::int(), - \'resolve\' => function ($root) { - return $root->getNumberOfPages(); - } - ], - \'items\' => [ - \'type\' => Type::listOf($itemType), - \'resolve\' => function ($root) { - return $root->fetchItems(); - } - ], - ]; - } - ]);'; - } - - /** - * - * @param string $type - * model name - * @return string - */ - private function createModelType($type) - { - // $model = new $type(); - $name = ModelUtils::getModelCacheKey($type); - // $orginName = $name; - - if (in_array($name, $this->compiledTypes)) { - // type is compiled before - return ''; - } - array_push($this->compiledTypes, $name); - - $md = ModelDescription::getInstance($type); - if (isset($md->graphql_name)) { - $name = $md->graphql_name; - } - - $preModels = ModelUtils::getRelatedModels($md); - $requiredModel = ''; - if (sizeof($preModels) > 0) { - $names = array(); - foreach ($preModels as $pm) { - $names[] = ModelUtils::skipeName($pm); - } - $requiredModel = 'use (&$' . implode(', &$', $names) . ')'; - } - - // compile the model - $result = ' // - $' . ModelUtils::skipeName($name) . ' = new ObjectType([ - \'name\' => \'' . $name . '\', - \'fields\' => function () ' . $requiredModel . '{ - return ' . $this->compileFields($md) . '; - } - ]);'; - - // compile related models - foreach ($preModels as $typeName) { - $result .= $this->createModelType($typeName); - } - - return $result; - } - - private function getNameOf($type) - { - return '$' . ModelUtils::skipeName(ModelUtils::getModelCacheKey($type)); - } - - private function compileFields(ModelDescription $model): string - { - $fields = ''; - foreach ($model as $field) { - // Check if it is graphql_field - if (! $field->graphql_field || ! $field->readable) { - continue; - } - switch ($field->type) { - case Schema::ONE_TO_MANY: - case Schema::MANY_TO_MANY: - $fields .= $this->compileFieldManytomany($field); - break; - case Schema::MANY_TO_ONE: - $fields .= $this->compileFieldForeignkey($field); - break; - default: - // set field name - $name = $field->name; - if (isset($field->graphql_name)) { - $name = $field->graphql_name; - } - - $fields .= ' - //' . $this->fieldComment($field) . ' - \'' . $name . '\' => [ - ' . $this->compileField($field) . ' - ],'; - break; - } - } - return '[' . $fields . ']'; - } - - // /** - // * Compile and add OneToMany or ManyToMany relations - // * - // * These relations are created automatically and ther is no related field - // * in the model definitions. - // * - // * @param string $type - // * Relation type: Engine::FOREIGNKEY Engine::Many_To_many - // * @param \Pluf\Data\Model $mainModel - // * main model wihch is the target of compile - // */ - // private function compileRelationFields($type, $mainModel) - // { - // $res = ''; - // $current_model = ModelUtils::getModelCacheKey($mainModel); - // $relations = ModelUtils::getRelatedModels($mainModel, $type); - // foreach ($relations as $related) { - // if ($related != $current_model) { - // $model = new $related(); - // } else { - // $model = clone $mainModel; - // } - // $fkeys = $model->getRelationKeysToModel($current_model, $type); - // foreach ($fkeys as $fkey => $val) { - // $name = (isset($val['relate_name'])) ? $val['relate_name'] : $related; - // $res .= ' - // //Foreinkey list-' . $this->fieldComment($fkey, []) . ' - // \'' . $name . '\' => [ - // \'type\' => Type::listOf(' . $this->getNameOf($related) . '), - // \'resolve\' => function ($root) { - // return $root->get_' . $name . '_list(); - // }, - // ],'; - // } - // } - // return $res; - // } - private function compileField(ModelProperty $field) - { - // set type - switch ($field->type) { - case Schema::SEQUENCE: - case Schema::FOREIGNKEY: - $res = 'Type::int()'; - break; - case Schema::DATE: - case Schema::DATETIME: - case Schema::EMAIL: - case Schema::FILE: - case Schema::SERIALIZED: - case Schema::SLUG: - case Schema::TEXT: - case Schema::VARCHAR: - case Schema::GEOMETRY: - $res = 'Type::string()'; - break; - case Schema::INTEGER: - $res = 'Type::int()'; - break; - case Schema::FLOAT: - $res = 'Type::float()'; - break; - case Schema::BOOLEAN: - $res = 'Type::boolean()'; - break; - - case Schema::MANY_TO_ONE: - case Schema::MANY_TO_MANY: - case Schema::ONE_TO_MANY: - default: - throw new Error500('Unsupported data tyep'); - } - - // for primetives - return '\'type\' => ' . $res . ', - \'resolve\' => function ($root) { - return $root->' . $field->name . '; - },'; - } - - /* - * Converts a field to a string command - */ - private function compileFieldForeignkey(ModelProperty $field): string - { - $name = $field->name; - if (isset($field->graphql_name)) { - $name = $field->graphql_name; - } - $typeVar = $this->getNameOf($field->inverseJoinModel); - - $functionName = $field->name; - $res = ' - //Foreinkey object-' . $this->fieldComment($field) . ' - \'' . $name . '\' => [ - \'type\' => ' . $typeVar . ', - \'resolve\' => function ($root) { - return $root->get_' . $functionName . '(); - }, - ],'; - return $res; - } - - private function compileFieldManytomany(ModelProperty $field): string - { - $type = $this->getNameOf($field->inverseJoinModel); - $functionName = $field->name; - $name = $field->name; - if (isset($field->graphql_name)) { - $name = $field->graphql_name; - } - /* - * TODO: maso, 2018: support for pagination in list function - * XXX: maso, 2018: check for security access - * - * if(parent is accessable) then - * All children are too - */ - return ' - //relation value-' . $this->fieldComment($field) . ' - \'' . $name . '\' => [ - \'type\' => Type::listOf(' . $type . '), - \'resolve\' => function ($root) { - return $root->get_' . $functionName . '_list(); - }, - ],'; - } - - /* - * Converts field into a single line string as comment - */ - private function fieldComment(ModelProperty $field): string - { - // return $field->name . ': ' . str_replace([ - // "\r\n", - // "\n", - // "\r" - // ], "", print_r($field, true)); - return ''; - } -} - - diff --git a/src/HTTP.php b/src/HTTP.php deleted file mode 100755 index d5c1663a..00000000 --- a/src/HTTP.php +++ /dev/null @@ -1,105 +0,0 @@ -. - */ -namespace Pluf; -/** - * Some basic HTTP management tools - * - * @author maso - * - */ -class HTTP -{ - - /** - * Break magic_quotes - * - * @credit Olivier Meunier - */ - function removeTheMagic() - { - // if (get_magic_quotes_gpc()) { - // if (!empty($_GET)) { - // array_walk($_GET, '\\Pluf\\HTTP::magicStrip'); - // } - // if (!empty($_POST)) { - // array_walk($_POST, 'Pluf_HTTP_magicStrip'); - // } - // if (!empty($_REQUEST)) { - // array_walk($_REQUEST, 'Pluf_HTTP_magicStrip'); - // } - // if (!empty($_COOKIE)) { - // array_walk($_COOKIE, 'Pluf_HTTP_magicStrip'); - // } - // } - if (function_exists('ini_set')) { - @ini_set('session.use_cookies', '1'); - @ini_set('session.use_only_cookies', '1'); - @ini_set('session.use_trans_sid', '0'); - @ini_set('url_rewriter.tags', ''); - } - } - - /** - * Break magic_quotes - * - * @credit Olivier Meunier - */ - public static function magicStrip(&$k, $key) - { - $k = HTTP::handleMagicQuotes($k); - } - - /** - * Break magic_quotes - * - * @credit Olivier Meunier - */ - public static function handleMagicQuotes(&$value) - { - if (is_array($value)) { - $result = array(); - foreach ($value as $k => $v) { - if (is_array($v)) { - $result[$k] = HTTP::handleMagicQuotes($v); - } else { - $result[$k] = stripslashes($v); - } - } - return $result; - } else { - return stripslashes($value); - } - } - - //----------------------Build reauest - -// /* -// * # Create q request -// * -// * TODO: maso, 2020: remove from dispatcher -// * - It limits tests -// * - It bind the dispatcher to a specific environment -// */ -// $query = preg_replace('#^(/)+#', '/', '/' . $query); -// $req = new Pluf\HTTP\Request($query); -// Pluf\HTTP\Request::setCurrent($req); -} - - diff --git a/src/HTTP/Error403.php b/src/HTTP/Error403.php deleted file mode 100644 index f41d653d..00000000 --- a/src/HTTP/Error403.php +++ /dev/null @@ -1,24 +0,0 @@ -. - */ -namespace Pluf\HTTP; - -use Pluf\DiContainerTrait; -use ArrayAccess; -use Iterator; -use Serializable; - -/* - * The request object. - * - * It is given as first arguments to the view as first argument. - * - * @author maso - * - */ -class Request implements ArrayAccess, Iterator, Serializable -{ - - use DiContainerTrait; - - static ?Request $current = null; - - public $POST = array(); - - public $GET = array(); - - public $PUT = array(); - - public $REQUEST = array(); - - public $COOKIE = array(); - - public $FILES = array(); - - public $HEADERS = array(); - - public $query = ''; - - public $query_string = ''; - - public $method = ''; - - public $uri = ''; - - public $view = ''; - - public $remote_addr = ''; - - public $http_host = ''; - - public $SERVER = array(); - - public $uid = ''; - - public $time = ''; - - public $agent = ''; - - /** - * Protocol - * - * @see $_SERVER['HTTP_HOST'] - * @var boolean - */ - public $https = false; - - /* - * TODO: maso, 2020: put the following items into the context - */ - public $user = null; - - public $match = []; - - public $params = []; - - // public ?Tenant $tenant = null; - function __construct($query) - { - $http = new \Pluf\HTTP(); - $http->removeTheMagic(); - - $this->POST = &$_POST; - $this->GET = &$_GET; - $this->REQUEST = &$_REQUEST; - $this->COOKIE = &$_COOKIE; - $this->FILES = &$_FILES; - $this->query = $query; - $this->query_string = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : ''; - $this->method = (isset($_SERVER['REQUEST_METHOD'])) ? $_SERVER['REQUEST_METHOD'] : 'GET'; - $this->uri = (isset($_SERVER['REQUEST_URI'])) ? $_SERVER['REQUEST_URI'] : '/'; - $this->path_info = (isset($_SERVER['PATH_INFO'])) ? $_SERVER['PATH_INFO'] : '/'; - $this->remote_addr = (isset($_SERVER['REMOTE_ADDR'])) ? $_SERVER['REMOTE_ADDR'] : 'localhost'; - $this->http_host = (isset($_SERVER['HTTP_HOST'])) ? $_SERVER['HTTP_HOST'] : ''; - $this->SERVER = &$_SERVER; - $this->uid = uniqid(microtime(true), true); - // request time - $this->time = (isset($_SERVER['REQUEST_TIME'])) ? $_SERVER['REQUEST_TIME'] : time(); - // XXX: maso, 2019: check the documents - $this->microtime = (isset($_SERVER['REQUEST_TIME_FLOAT'])) ? $_SERVER['REQUEST_TIME_FLOAT'] : microtime(true); - - $this->https = isset($_SERVER['HTTPS']); - $this->agent = (isset($_SERVER['HTTP_USER_AGENT'])) ? $_SERVER['HTTP_USER_AGENT'] : ''; - - /* - * Load PUT parameters and merge with POST - */ - if ($this->method == 'PUT') { - $put_vars = array(); - parse_str(file_get_contents("php://input"), $put_vars); - $this->PUT = $put_vars; - $this->POST = array_merge($this->POST, $put_vars); - $this->REQUEST = array_merge($this->REQUEST, $put_vars); - } - /* - * Load request header - */ - $this->HEADERS = array(); - if (function_exists('apache_request_headers')) { - $this->HEADERS = apache_request_headers(); - } - $headers = new Header2(); - foreach ($this->HEADERS as $key => $value){ - $headers->setHeader($key, $value); - } - $this->headers = $headers; - } - - /** - * Gets time of the request - * - * NOTE: returns the current time in the number of seconds since the Unix Epoch (January 1 1970 00:00:00 GMT). - * - * @return int - */ - public function getTime() - { - return $this->time; - } - - /** - * The timestamp of the start of the request, with microsecond precision. - * - * @return float - */ - public function getMicrotime() - { - return $this->microtime; - } - - /** - * Calculates the size of the request - * - * @return int size of the request - */ - public function getSize() - { - $size = 0; - // TODO: maso, 2019: file size - // Note: hadi, 2019: base on this: https://www.geeksforgeeks.org/php-_files-array-http-file-upload-variables/ - foreach ($this->FILES as $file) { - $size += array_key_exists('size', $file) ? $file['size'] : 0; - } - // Parameter size - $size += strlen(serialize($this->REQUEST)); - // Header size - $size += strlen(serialize($this->HEADERS)); - return $size; - } - - public function isGet(): bool - { - return $this->method == 'GET'; - } - - public function isPost(): bool - { - return $this->method == 'POST'; - } - - public function isPut(): bool - { - return $this->method == 'PUT'; - } - - public function isDelete(): bool - { - return $this->method == 'DELETE'; - } - - public function setHeader($key, $value): Request - { - $this->HEADERS[$key] = $value; - return $this; - } - - public static function getCurrent(): ?Request - { - return self::$current; - } - - public static function setCurrent(?Request $request = null): void - { - // Legacy model, - $GLOBALS['_PX_request'] = $request; - self::$current = $request; - } - - // ------------------------------------------------------------------------- - // Context Manager - // ------------------------------------------------------------------------- - public Header2 $headers; - private array $context = []; - - private $position = 0; - - function __get($key) - { - // get local value - if (isset($this->context[$key])) { - return $this->context[$key]; - } - return null; - } - - function __set($key, $value) - { - // set local value - $this->context[$key] = $value; - } - - public function next() - { - ++ $this->position; - } - - public function valid() - { - return isset($this->context[$this->position]); - } - - public function offsetGet($offset) - { - return isset($this->context[$offset]) ? $this->context[$offset] : null; - } - - public function serialize() - { - return serialize($this->context); - } - - public function current() - { - return $this->context[$this->position]; - } - - public function unserialize($serialized) - { - $this->context = unserialize($serialized); - } - - public function offsetExists($offset) - { - unset($this->context[$offset]); - } - - public function rewind() - { - $this->position = 0; - } - - public function offsetUnset($offset) - { - unset($this->context[$offset]); - } - - public function offsetSet($offset, $value) - { - return isset($this->context[$offset]) ? $this->context[$offset] : null; - } - - public function key() - { - return $this->position; - } -} diff --git a/src/HTTP/Response.php b/src/HTTP/Response.php deleted file mode 100755 index 5c7e74f6..00000000 --- a/src/HTTP/Response.php +++ /dev/null @@ -1,257 +0,0 @@ -. - */ -namespace Pluf\HTTP; - -use Pluf; -use Pluf\DiContainerTrait; - -/** - * Response object to be constructed by the views. - * - * When constructing a view, the response object must be populated and - * returned. The response is then displayed to the visitor. - * The interest of using a response object is that we can run a post - * filter action on the response. For example you can run a filter that - * is checking that all the output is valid HTML and write a logfile if - * this is not the case. - */ -class Response -{ - - use DiContainerTrait; - - /** - * Content of the response. - */ - public $content = ''; - - /** - * Array of the headers to add. - * - * For example $this->headers['Content-Type'] = 'text/html; charset=utf-8'; - */ - public $headers = array(); - - /** - * Status code of the answer. - */ - public $status_code = 200; - - /** - * Cookies to send. - * - * $this->cookies['my_cookie'] = 'content of the cookie'; - */ - public $cookies = array(); - - /** - * Status code list. - * - * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html - */ - public $status_code_list = array( - '100' => 'CONTINUE', - '101' => 'SWITCHING PROTOCOLS', - '200' => 'OK', - '201' => 'CREATED', - '202' => 'ACCEPTED', - '203' => 'NON-AUTHORITATIVE INFORMATION', - '204' => 'NO CONTENT', - '205' => 'RESET CONTENT', - '206' => 'PARTIAL CONTENT', - '300' => 'MULTIPLE CHOICES', - '301' => 'MOVED PERMANENTLY', - '302' => 'FOUND', - '303' => 'SEE OTHER', - '304' => 'NOT MODIFIED', - '305' => 'USE PROXY', - '306' => 'RESERVED', - '307' => 'TEMPORARY REDIRECT', - '400' => 'BAD REQUEST', - '401' => 'UNAUTHORIZED', - '402' => 'PAYMENT REQUIRED', - '403' => 'FORBIDDEN', - '404' => 'NOT FOUND', - '405' => 'METHOD NOT ALLOWED', - '406' => 'NOT ACCEPTABLE', - '407' => 'PROXY AUTHENTICATION REQUIRED', - '408' => 'REQUEST TIMEOUT', - '409' => 'CONFLICT', - '410' => 'GONE', - '411' => 'LENGTH REQUIRED', - '412' => 'PRECONDITION FAILED', - '413' => 'REQUEST ENTITY TOO LARGE', - '414' => 'REQUEST-URI TOO LONG', - '415' => 'UNSUPPORTED MEDIA TYPE', - '416' => 'REQUESTED RANGE NOT SATISFIABLE', - '417' => 'EXPECTATION FAILED', - '500' => 'INTERNAL SERVER ERROR', - '501' => 'NOT IMPLEMENTED', - '502' => 'BAD GATEWAY', - '503' => 'SERVICE UNAVAILABLE', - '504' => 'GATEWAY TIMEOUT', - '505' => 'HTTP VERSION NOT SUPPORTED' - ); - - /** - * Constructor of the response. - * - * @param - * string Content of the response ('') - * @param - * string MimeType of the response (null) if not given will - * default to the one given in the configuration 'mimetype' - */ - function __construct($content = '', $mimetype = null) - { - if (is_null($mimetype)) { - $mimetype = Pluf::getConfig('mimetype', 'text/html') . '; charset=utf-8'; - } - $this->content = $content; - - if (is_array($mimetype)) { - $mimetype = $mimetype[0]; - } - - $this->headers['Content-Type'] = $mimetype; - $this->headers['X-Powered-By'] = 'Pluf (Phoenix Scholars Co.) - http://pluf.ir'; - $this->status_code = 200; - $this->cookies = array(); - } - - /** - * Render a response object. - */ - function render($output_body = true) - { - if (defined('IN_UNIT_TESTS')) { - return; - } - if ($this->status_code >= 200 && $this->status_code != 204 && $this->status_code != 304) { - $this->headers['Content-Length'] = strlen($this->content); - } - $this->outputHeaders(); - if ($output_body) { - echo $this->content; - } - } - - /** - * Output headers. - */ - function outputHeaders() - { - if (! defined('IN_UNIT_TESTS')) { - // header('HTTP/1.0 '.$this->status_code.' ' - // .$this->status_code_list[$this->status_code], - // true, $this->status_code); - // foreach ($this->headers as $header => $ch) { - // header($header.': '.$ch); - // } - /* XXX: maso, 2019: Our header is not working well */ - $HTTP = new \Pluf\HTTP\Header2(); - $HTTP->sendStatusCode($this->status_code); - foreach ($this->headers as $header => $value) { - $HTTP->setHeader($header, $value); - } - $HTTP->sendHeaders(); - - foreach ($this->cookies as $cookie => $data) { - // name, data, expiration, path, domain, secure, http only - $expire = (null == $data) ? time() - 31536000 : time() + 31536000; - $data = (null == $data) ? '' : $data; - setcookie($cookie, $data, $expire, // - Pluf::f('cookie_path', '/'), // - Pluf::f('cookie_domain', null), // - Pluf::f('cookie_secure', false), // - Pluf::f('cookie_httponly', true)); - } - } else { - $_COOKIE = array(); - foreach ($this->cookies as $cookie => $data) { - $_COOKIE[$cookie] = $data; - } - } - } - - /** - * Return hash code of the object - */ - public function hashCode() - { - if (isset($this->content)) { - if (! isset($this->contentHash)) { - $this->contentHash = md5($this->content); - } - return $this->contentHash; - } - return '0000'; - } - - public function getStatusCode() - { - return $this->status_code; - } - - public function setStatusCode($code): Response - { - $this->status_code = $code; - return $this; - } - - public function getBody() - { - return $this->content; - } - - public function setBody($body): Response - { - $this->content = $body; - return $this; - } - - /** - * Checks if the body is set - * - * @return bool true if the body is set - */ - public function hasBody(): bool - { - return isset($this->content); - } - - public function getHeader($key) - { - if (array_key_exists($key, $this->headers)) { - return $this->headers[$key]; - } - return null; - } - - public function setHeader($key, $value): Response - { - $this->headers[$key] = $value; - return $this; - } - - public function isOk(): bool - { - return $this->status_code == 200; - } -} diff --git a/src/HTTP/Response/CommandPassThru.php b/src/HTTP/Response/CommandPassThru.php deleted file mode 100755 index b43acac0..00000000 --- a/src/HTTP/Response/CommandPassThru.php +++ /dev/null @@ -1,54 +0,0 @@ -. - */ -namespace Pluf\HTTP\Response; - -/** - * Special response object to output the return value of a command. - * - * You need to use escapeshellarg() and escapeshellcmd() to provide a - * "clean" command. The Content-Length will not be set as it is not - * possible to predict it. - */ -class CommandPassThru extends \Pluf\HTTP\Response -{ - - /** - * The command argument must be a safe string! - * - * @param - * string Command to run. - * @param - * string Mimetype (null) - */ - function __construct($command, $mimetype = null) - { - parent::__construct($command, $mimetype); - } - - /** - * Render a response object. - */ - function render($output_body = true) - { - $this->outputHeaders(); - if ($output_body) { - passthru($this->content); - } - } -} diff --git a/src/HTTP/Response/File.php b/src/HTTP/Response/File.php deleted file mode 100755 index a7b6b828..00000000 --- a/src/HTTP/Response/File.php +++ /dev/null @@ -1,90 +0,0 @@ -. - */ -namespace Pluf\HTTP\Response; - -use Pluf\HTTP\Error404; - -/** - * Render file as response - * - * @author maso - * - */ -class File extends \Pluf\HTTP\Response -{ - - public $delete_file = false; - - /** - * Creates new instance of File response - * - * @param string $filepath - * @param string $mimetype - * @param boolean $delete_file - */ - function __construct($filepath, $mimetype = null, $delete_file = false) - { - parent::__construct($filepath, $mimetype); - $this->delete_file = $delete_file; - } - - /** - * Render the file - * - * {@inheritdoc} - * @see \Pluf\HTTP\Response::render() - */ - function render($output_body = true) - { - if (! file_exists($this->content)) { - throw new Error404('Requested resource not found'); - } - if (defined('IN_UNIT_TESTS')) { - parent::render($output_body); - return; - } - $dl = new \Pluf\HTTP\Download2(array( - 'file' => $this->content, - 'contenttype' => $this->headers['Content-Type'], - 'gzip' => false, - 'cache' => true - )); - foreach ($this->headers as $key => $value) { - $dl->headers[$key] = $value; - } - $dl->send(false); - } - - /** - * Genereate hash code of file - * - * {@inheritdoc} - * @see \Pluf\HTTP\Response::hashCode() - */ - public function hashCode() - { - if (isset($this->content) && file_exists($this->content)) { - if (! isset($this->contentHash)) { - $this->contentHash = md5_file($this->content); - } - return $this->contentHash; - } - return '0000'; - } -} diff --git a/src/HTTP/Response/Forbidden.php b/src/HTTP/Response/Forbidden.php deleted file mode 100755 index 7a7cde82..00000000 --- a/src/HTTP/Response/Forbidden.php +++ /dev/null @@ -1,48 +0,0 @@ -. - */ -namespace Pluf\HTTP\Response; - -use Pluf\Template; -use Exception; - -/** - * - * @deprecated - */ -class Forbidden extends \Pluf\HTTP\Response -{ - - function __construct($request) - { - $content = ''; - try { - $context = new Template\Context(array( - 'query' => $request->query - )); - $tmpl = new Template('403.html'); - $content = $tmpl->render($context); - $mimetype = null; - } catch (Exception $e) { - $mimetype = 'text/plain'; - $content = 'You are not authorized to view this page. You do not have permission' . "\n" . 'to view the requested directory or page using the credentials supplied.' . "\n\n" . '403 - Forbidden'; - } - parent::__construct($content, $mimetype); - $this->status_code = 403; - } -} \ No newline at end of file diff --git a/src/HTTP/Response/Json.php b/src/HTTP/Response/Json.php deleted file mode 100755 index 7905c406..00000000 --- a/src/HTTP/Response/Json.php +++ /dev/null @@ -1,35 +0,0 @@ -. - */ -namespace Pluf\HTTP\Response; - -use Pluf; - -class Json extends \Pluf\HTTP\Response -{ - /** - * @param mixed Values, will be encoded using json_encode - */ - function __construct($data, $mimetype=null) - { - if (null == $mimetype) { - $mimetype = Pluf::f('mimetype_json', 'application/json').'; charset=utf-8'; - } - parent::__construct(json_encode($data), $mimetype); - } -} diff --git a/src/HTTP/Response/NotAvailable.php b/src/HTTP/Response/NotAvailable.php deleted file mode 100755 index ac632bf0..00000000 --- a/src/HTTP/Response/NotAvailable.php +++ /dev/null @@ -1,63 +0,0 @@ -. - */ -namespace Pluf\HTTP\Response; - -use Exception; -use Pluf\Template; - -/** - * - * @deprecated - */ -class NotAvailable extends \Pluf\HTTP\Response -{ - - /** - * یک نمونه جدید از این شئی ایجاد می‌کند - * - * در فرآیند ساخت تلاش می‌شو که الگویی برای خطای 503 بازیابی شده و به عنوان نتیجه - * برگردانده شود. - * در صورتی که خطایی رخ دهد، یک متن پیش فرض به عنوان خطای نتیجه نمایش داده خواهد شد. - * - * @param \Pluf\HTTP\Response $request - */ - function __construct($request) - { - $content = ''; - try { - $tmpl = new Template('503.html'); - $params = array( - 'query' => $request->query - ); - if (is_null($request)) { - $context = new Template\Context($params); - } else { - $context = new Template\Context\Request($request, $params); - } - $content = $tmpl->render($context); - $mimetype = null; - } catch (Exception $e) { - $mimetype = 'text/plain'; - $content = sprintf('The requested URL %s is not available at the moment.' . "\n" . 'Please try again later.' . "\n\n" . '503 - Service Unavailable', Pluf_esc($request->query)); - } - parent::__construct($content, $mimetype); - $this->status_code = 503; - $this->headers['Retry-After'] = 300; // retry after 5 minutes - } -} diff --git a/src/HTTP/Response/NotFound.php b/src/HTTP/Response/NotFound.php deleted file mode 100755 index 70d8b8a8..00000000 --- a/src/HTTP/Response/NotFound.php +++ /dev/null @@ -1,71 +0,0 @@ -. - */ -namespace Pluf\HTTP\Response; - -use Pluf\Template; -use Pluf\HTTP\Error404; -use Pluf; - -/** - * - * @deprecated - */ -class NotFound extends \Pluf\HTTP\Response -{ - - /** - * یک نمونه جدید از این شئی ایجاد می‌کند - * - * در فرآیند ساخت تلاش می‌شو که الگویی برای خطای 404 بازیابی شده و به عنوان نتیجه - * برگردانده شود. - * در صورتی که خطایی رخ دهد، یک متن پیش فرض به عنوان خطای نتیجه نمایش داده خواهد شد. - * - * @param \Pluf\HTTP\Request $request - */ - function __construct($request) - { - if (Pluf::f('rest', false)) { - $mimetype = Pluf::f('mimetype_json', 'application/json') . '; charset=utf-8'; - $exception = new Error404(); - parent::__construct(json_encode($exception), $mimetype); - $this->status_code = 404; - return; - } - - $content = ''; - try { - $tmpl = new Template('404.html'); - $params = array( - 'query' => $request->query - ); - if (is_null($request)) { - $context = new Template\Context($params); - } else { - $context = new Template\Context\Request($request, $params); - } - $content = $tmpl->render($context); - $mimetype = null; - } catch (\Exception $e) { - $mimetype = 'text/plain'; - $content = sprintf('The requested URL %s was not found on this server.' . "\n" . 'Please check the URL and try again.' . "\n\n" . '404 - Not Found', Pluf_esc($request->query)); - } - parent::__construct($content, $mimetype); - $this->status_code = 404; - } -} diff --git a/src/HTTP/Response/PlainText.php b/src/HTTP/Response/PlainText.php deleted file mode 100644 index 138f1779..00000000 --- a/src/HTTP/Response/PlainText.php +++ /dev/null @@ -1,34 +0,0 @@ -. - */ -namespace Pluf\HTTP\Response; - -class PlainText extends \Pluf\HTTP\Response -{ - - /** - * - * @param - * mixed Values, will be encoded using json_encode - */ - function __construct($data, $mimetype = 'text/plain; charset=utf-8') - { - parent::__construct(print_r($data, true), $mimetype); - } -} - diff --git a/src/HTTP/Response/Redirect.php b/src/HTTP/Response/Redirect.php deleted file mode 100755 index 8123c83b..00000000 --- a/src/HTTP/Response/Redirect.php +++ /dev/null @@ -1,39 +0,0 @@ -. - */ -namespace Pluf\HTTP\Response; - -class Redirect extends \Pluf\HTTP\Response -{ - - /** - * Redirect response to a given URL. - * - * @param - * string URL - * @param - * int Redirect code (302) or 301 for permanent - */ - function __construct($url, $code = 302) - { - $content = sprintf('Please, click here to be redirected.', $url); - parent::__construct($content); - $this->headers['Location'] = $url; - $this->status_code = $code; - } -} diff --git a/src/HTTP/Response/RedirectToLogin.php b/src/HTTP/Response/RedirectToLogin.php deleted file mode 100755 index e8b716e0..00000000 --- a/src/HTTP/Response/RedirectToLogin.php +++ /dev/null @@ -1,69 +0,0 @@ -. - */ -namespace Pluf\HTTP\Response; - -use Pluf\HTTP\URL; -use Pluf; - -/** - * Can be used as a response to return when a user must be logged to - * access a page. - * - * @deprecated - */ -class RedirectToLogin extends \Pluf\HTTP\Response -{ - - /** - * The $request object is used to know what the post login - * redirect url should be. - * - * If the action url of the login page is not set, it will try to - * get the url from the login view from the 'login_view' - * configuration key. - * - * @param - * Request The request object of the current page. - * @param - * string The full url of the login page (null) - */ - function __construct($request, $loginurl = null) - { - if ($loginurl !== null) { - $murl = new URL(); - $url = $murl->generate($loginurl, array( - '_redirect_after' => $request->uri - ), false); - $encoded = $murl->generate($loginurl, array( - '_redirect_after' => $request->uri - )); - } else { - $url = URL::urlForView(Pluf::f('login_view', 'login_view'), array(), array( - '_redirect_after' => $request->uri - ), false); - $encoded = URL::urlForView(Pluf::f('login_view', 'login_view'), array(), array( - '_redirect_after' => $request->uri - )); - } - $content = sprintf('Please, click here to be redirected.', $encoded); - parent::__construct($content); - $this->headers['Location'] = $url; - $this->status_code = 302; - } -} diff --git a/src/HTTP/Response/ResumableFile.php b/src/HTTP/Response/ResumableFile.php deleted file mode 100644 index 6dce433a..00000000 --- a/src/HTTP/Response/ResumableFile.php +++ /dev/null @@ -1,200 +0,0 @@ -. - */ -namespace Pluf\HTTP\Response; - -use Pluf\HTTP\Error404; - -/** - * TODO: maso, 1395: document - */ -class ResumableFile extends \Pluf\HTTP\Response -{ - - private $file; - - private $name; - - private $boundary; - - private $delay = 0; - - private $totalSize = 0; - - private $httpRange; - - function __construct($filepath, $httpRange, $fileName, $mimetype = null, $delay = 0) - { - parent::__construct($filepath, $mimetype); - - if (! is_file($filepath)) { - throw new Error404(); - } - $this->httpRange = $httpRange; - $this->totalSize = filesize($filepath); - $this->file = fopen($filepath, "r"); - $this->boundary = md5($filepath); - $this->delay = $delay; - if ($fileName) - $this->name = $fileName; - else - $this->name = basename($filepath); - } - - /** - * Render a response object. - */ - function render($output_body = true) - { - // $this->process (); - if ($this->status_code >= 200 && $this->status_code != 204 && $this->status_code != 304) { - $this->headers['Content-Length'] = strlen($this->content); - } - // } - // public function process() { - $ranges = NULL; - $t = 0; - if ($this->httpRange && $range = stristr(trim($this->httpRange), 'bytes=')) { - $range = substr($range, 6); - $ranges = explode(',', $range); - $t = count($ranges); - } - $this->headers['Accept-Ranges'] = 'bytes'; - $this->headers['Content-Type'] = 'application/octet-stream'; - $this->headers['Content-Transfer-Encoding'] = 'binary'; - // $this->headers ['Content-Disposition'] = sprintf ( 'attachment; filename="%s"', $this->name ); - $this->headers['Content-Disposition'] = 'attachment; ' . sprintf('filename="%s"; ', rawurlencode($this->name)) . sprintf("filename*=utf-8''%s", rawurlencode($this->name)); - if ($t > 0) { - $this->status_code = 206; - $t === 1 ? $this->pushSingle($range) : $this->pushMulti($ranges); - } else { - $this->headers['Content-Length'] = $this->totalSize; - $this->outputHeaders(); - $this->readFile(); - } - flush(); - } - - private function pushSingle($range) - { - $start = $end = 0; - $this->getRange($range, $start, $end); - $this->headers['Content-Length'] = $end - $start + 1; - $this->headers['Content-Range'] = sprintf(' bytes %d-%d/%d', $start, $end, $this->totalSize); - $this->outputHeaders(); - fseek($this->file, $start); - $this->readBuffer($end - $start + 1); - $this->readFile(); - } - - private function pushMulti($ranges) - { - $length = $start = $end = 0; - // $output = ""; - $tl = "Content-type: application/octet-stream\r\n"; - $formatRange = "Content-range: bytes %d-%d/%d\r\n\r\n"; - foreach ($ranges as $range) { - $this->getRange($range, $start, $end); - $length += strlen("\r\n--$this->boundary\r\n"); - $length += strlen($tl); - $length += strlen(sprintf($formatRange, $start, $end, $this->totalSize)); - $length += $end - $start + 1; - } - $length += strlen("\r\n--$this->boundary--\r\n"); - $this->headers['Content-Length'] = $length; - $this->headers['Content-Type'] = 'multipart/x-byteranges; boundary=' . $this->boundary; - $this->outputHeaders(); - foreach ($ranges as $range) { - $this->getRange($range, $start, $end); - echo "\r\n--$this->boundary\r\n"; - echo $tl; - echo sprintf($formatRange, $start, $end, $this->totalSize); - fseek($this->file, $start); - $this->readBuffer($end - $start + 1); - } - echo "\r\n--$this->boundary--\r\n"; - } - - private function getRange($range, &$start, &$end) - { - list ($start, $end) = explode('-', $range); - $fileSize = $this->totalSize; - if ($start == '') { - $tmp = $end; - $end = $fileSize - 1; - $start = $fileSize - $tmp; - if ($start < 0) - $start = 0; - } else { - if ($end == '' || $end > $fileSize - 1) - $end = $fileSize - 1; - } - if ($start > $end) { - header("Status: 416 Requested range not satisfiable"); - header("Content-Range: */" . $fileSize); - exit(); - } - return array( - $start, - $end - ); - } - - private function readFile() - { - while (! feof($this->file)) { - echo fgets($this->file); - flush(); - usleep($this->delay); - } - } - - private function readBuffer($bytes, $size = 1024) - { - $bytesLeft = $bytes; - while ($bytesLeft > 0 && ! feof($this->file)) { - $bytesRead = $bytesLeft > $size ? $size : $bytesLeft; - $bytesLeft -= $bytesRead; - echo fread($this->file, $bytesRead); - flush(); - usleep($this->delay); - } - } - - public function computeSize() - { - $t = 0; - if (! $this->httpRange && $range = stristr(trim($this->httpRange), 'bytes=')) { - $range = substr($range, 6); - $ranges = explode(',', $range); - $t = count($ranges); - } - if ($t <= 0) - return $this->totalSize; - $myStart = $myEnd = 0; - if ($t == 1) { - $this->getRange($range, $myStart, $myEnd); - return $myEnd - $myStart + 1; - } else { - $mySize = 0; - foreach ($ranges as $rg) - $mySize += $this->getRange($rg, $myStart, $myEnd); - return $mySize; - } - } -} diff --git a/src/HTTP/Response/ServerError.php b/src/HTTP/Response/ServerError.php deleted file mode 100755 index 9fd2002e..00000000 --- a/src/HTTP/Response/ServerError.php +++ /dev/null @@ -1,64 +0,0 @@ -. - */ -namespace Pluf\HTTP\Response; - -use Pluf; - -/** - * Error response - */ -class ServerError extends \Pluf\HTTP\Response -{ - - /** - * یک نمونه جدید از این شئی ایجاد می‌کند - * - * در فرآیند ساخت تلاش می‌شو که الگویی برای خطای 500 بازیابی شده و به عنوان - * نتیجه - * برگردانده شود. - * در صورتی که خطایی رخ دهد، یک متن پیش فرض به عنوان خطای نتیجه نمایش داده - * خواهد شد. - * - * @param \Pluf\HTTP\Request $request - */ - function __construct($exception, $mimetype = null, $request = null) - { - /* - * ایجاد پیام مناسب برای کاربر - */ - $mimetype = Pluf::f('mimetype_json', 'application/json') . '; charset=utf-8'; - if (! ($exception instanceof \Pluf\Exception)) { - parent::__construct(json_encode([ - 'code' => 5000, - 'status' => 500, - // 'link' => $this->link, - 'message' => $exception->getMessage(), - // 'data' => $this->data, - // 'developerMessage' => $this->developerMessage, - 'stack' => Pluf::f('debug', false) ? $exception->getTrace() : array() - ]), $mimetype); - $this->status_code = 500; - } else { - parent::__construct(json_encode($exception), $mimetype); - // XXX: maso, 2020: convert pluf exception into response - $this->status_code = $exception->getCode(); - } - return; - } -} diff --git a/src/HTTP/ResponseFileBuilder.php b/src/HTTP/ResponseFileBuilder.php deleted file mode 100644 index f526ed98..00000000 --- a/src/HTTP/ResponseFileBuilder.php +++ /dev/null @@ -1,70 +0,0 @@ -abspath, $this->mimetype, $this->delete); - // TODO: maso, 2020: support resumable file too - if (isset($this->filename)) { - $response->headers['Content-Disposition'] = sprintf('attachment; filename="%s"', $this->filename); - } - return $response; - } - - public function setFilename(string $filename): ResponseFileBuilder - { - $this->filename = $filename; - return $this; - } - - public function setMimetype(string $mimetype): ResponseFileBuilder - { - $this->mimetype = $mimetype; - return $this; - } - - public function setAbsloutPath(string $abspath): ResponseFileBuilder - { - $this->abspath = $abspath; - return $this; - } - - public function setResumable(bool $resumable): ResponseFileBuilder - { - $this->resumable = $resumable; - return $this; - } - - public function setRequest(Request $request): ResponseFileBuilder - { - $this->request = $request; - return $this; - } - - public function deleteOnEnd(bool $delete): ResponseFileBuilder - { - $this->delete = $delete; - return $this; - } -} - diff --git a/src/HTTP/URL.php b/src/HTTP/URL.php deleted file mode 100755 index ef183c34..00000000 --- a/src/HTTP/URL.php +++ /dev/null @@ -1,231 +0,0 @@ -. - */ -namespace Pluf\HTTP; - -use Exception; -use Pluf; - -/** - * Generate a ready to use URL to be used in location/redirect or forms. - * - * When redirecting a user, depending of the format of the url with - * mod_rewrite or not, the parameters must all go in the GET or - * everything but the action. This class provide a convinient way to - * generate those url and parse the results for the dispatcher. - */ -class URL -{ - - /** - * Defines type ot the url - * - * @var string - */ - var string $type = 'simple'; - - function __construct(string $type = 'simple') - { - $this->type = $type; - } - - /** - * Generate the URL. - * - * The & is encoded as & in the url. - * - * @param - * string Action url - * @param - * array Associative array of the parameters (array()) - * @param - * bool Encode the & in the url (true) - * @return string Ready to use URL. - */ - public function generate(string $action, $params = array()) - { - $url = ''; - switch ($this->type) { - case 'simple': - $params['_px_action'] = $action; - $url = '?' . http_build_query($params); - break; - case 'mod_rewrite': - $url = $action; - if (count($params)) { - $url .= '?' . http_build_query($params); - } - break; - default: - throw new Exception('Unsupported URL type: "' . $this->type . '"'); - } - return $url; - } - - /** - * Get the action of the request. - * - * We directly get the PATH_INFO OR ORIG_PATH_INFO variable or return '/' - * - * @return string Action - */ - public function getAction() - { - switch ($this->type) { - case 'simple': - return $_GET['_px_action']; - case 'mod_rewrite': - $request_uri = ''; - if (isset($_SERVER['PATH_INFO'])) { - $request_uri = trim($_SERVER['PATH_INFO']); - } else if (isset($_SERVER['ORIG_PATH_INFO'])) { - $request_uri = trim(str_replace($_SERVER['SCRIPT_NAME'], '', $_SERVER['ORIG_PATH_INFO']), '/'); - } - return $request_uri; - default: - throw new Exception('Unsupported URL type: "' . $this->type . '"'); - } - } - - /** - * Provide the full URL (without domain) to a view. - * - * @param - * string View. - * @param - * array Parameters for the view (array()). - * @param - * array Extra GET parameters for the view (array()). - * @param - * bool Should the URL be encoded (true). - * @return string URL. - */ - public static function urlForView($view, $params = array(), $get_params = array()) - { - $url = new URL(Pluf::f('url_format', 'mod_rewrite')); - return $url->generate(self::reverse($view, $params), $get_params); - } - -// /** -// * Reverse an URL. -// * -// * @param -// * string View in the form 'class::method' or string of the name. -// * @param -// * array Possible parameters for the view (array()). -// * @return string URL. -// */ -// public static function reverse($view, $params = array()) -// { -// $model = ''; -// $method = ''; -// if (false !== strpos($view, '::')) { -// list ($model, $method) = explode('::', $view); -// } -// $vdef = array( -// $model, -// $method, -// $view -// ); -// $regbase = array( -// '', -// array() -// ); -// $regbase = self::find($GLOBALS['_PX_views'], $vdef, $regbase); -// if ($regbase === false) { -// throw new Exception(sprintf('Error, the view: %s has not been found.', $view)); -// } -// $url = ''; -// foreach ($regbase[1] as $regex) { -// if ($regex == '#^#') -// continue; -// $url .= self::buildReverseUrl($regex, $params); -// } -// if (! defined('IN_UNIT_TESTS')) { -// $url = $regbase[0] . $url; -// } - -// return $url; -// } - - /** - * Go in the list of views to find the matching one. - * - * @param - * array Views - * @param - * array View definition array(model, method, name) - * @param - * array Regex of the view up to now and base - * @return mixed Regex of the view or false - */ - public static function find($views, $vdef, $regbase) - { - foreach ($views as $dview) { - if (isset($dview['sub'])) { - $regbase2 = $regbase; - if (empty($regbase2[0])) { - $regbase2[0] = $dview['base']; - } - $regbase2[1][] = $dview['regex']; - $res = self::find($dview['sub'], $vdef, $regbase2); - if ($res) { - return $res; - } - continue; - } - if ((isset($dview['name']) && $dview['name'] == $vdef[2]) or ($dview['model'] == $vdef[0] && $dview['method'] == $vdef[1])) { - $regbase[1][] = $dview['regex']; - if (! empty($dview['base'])) { - $regbase[0] = $dview['base']; - } - return $regbase; - } - } - return false; - } - - /** - * Build the reverse URL without the path base. - * - * Credits to Django, again... - * - * @param - * string Regex for the URL. - * @param - * array Parameters - * @return string URL filled with the parameters. - */ - public static function buildReverseUrl($url_regex, $params = array()) - { - $url = str_replace(array( - '\\.', - '\\-' - ), array( - '.', - '-' - ), $url_regex); - if (count($params)) { - $groups = array_fill(0, count($params), '#\(([^)]+)\)#'); - $url = preg_replace($groups, $params, $url, 1); - } - $matches = array(); - preg_match('/^#\^?([^#\$]+)/', $url, $matches); - return $matches[1]; - } -} diff --git a/src/Logger.php b/src/Logger.php deleted file mode 100644 index 26e4dd5e..00000000 --- a/src/Logger.php +++ /dev/null @@ -1,215 +0,0 @@ -. - */ -namespace Pluf; - -use Psr\Log\LoggerInterface; -use Pluf; - -/** - * Internal Pluf Logger - * - * @author maso - * - */ -class Logger -{ - - /** - * The log stack. - * - * A logger function is just pushing the data in the log stack, - * the writers are then called to write the data later. - */ - public static $stack = array(); - - /** - * A simple storage to track stats. - * - * A good example is to store stats and at the end of the request, - * push the info back in the log. You can for example store the - * total time doing SQL or other things like that. - */ - public static $store = array(); - - /** - * Different log levels. - */ - const ALL = 1; - - const DEBUG = 1; - - const INFO = 2; - - const PERF = 3; - - const EVENT = 3; - - const WARN = 4; - - const ERROR = 5; - - const FATAL = 6; - - const ALERT = 7; - - const EMERGENCY = 8; - - const OFF = 0; - - /** - * Used to reverse the log level to the string. - */ - private static $reverse = array( - 0 => 'all', - 1 => 'debug', - 2 => 'info', - 3 => 'notice', - 4 => 'warning', - 5 => 'error', - 6 => 'critical', - 7 => 'alert', - 8 => 'emergency', - 9 => 'off' - ); - - private static $direct = array( - 'all' => 0, - 'debug' => 1, - 'info' => 2, - 'notice' => 3, - 'warning' => 4, - 'error' => 5, - 'critical' => 6, - 'alert' => 7, - 'emergency' => 8, - 'off' => 9 - ); - - private static array $loggerManagers = []; - - private static ?LoggerFormatter $loggerFormater = null; - - private static ?LoggerAppender $loggerAppender = null; - - private static function _log(int $level, $message) - { - $loggerManager = self::getLogger('default'); - $loggerManager->log(self::$reverse[$level], $message); - } - - /** - * Log at the ALL level. - */ - public static function log($message) - { - return self::_log(self::ALL, $message); - } - - /** - * Log at the DEBUG level. - */ - public static function debug($message) - { - self::_log(self::DEBUG, $message); - } - - public static function info($message) - { - self::_log(self::INFO, $message); - } - - public static function perf($message) - { - self::_log(self::PERF, $message); - } - - public static function event($message) - { - self::_log(self::EVENT, $message); - } - - public static function warn($message) - { - self::_log(self::WARN, $message); - } - - public static function error($message) - { - self::_log(self::ERROR, $message); - } - - public static function fatal($message) - { - self::_log(self::FATAL, $message); - } - - /** - * Creates and return an instance of the logger with the given key - * - * @param string $key - * @return LoggerInterface - */ - public static function getLogger(string $key): LoggerInterface - { - if (array_key_exists($key, self::$loggerManagers)) { - return self::$loggerManagers[$key]; - } - if (! isset(self::$loggerFormater)) { - $className = Pluf::getConfig('log_formater', LoggerFormatter\Plain::class); - self::$loggerFormater = new $className(); - } - if (! isset(self::$loggerAppender)) { - $className = Pluf::getConfig('log_appender', LoggerAppender\Console::class); - self::$loggerAppender = new $className(); - } - $loggerManager = new LoggerManager($key, self::$loggerFormater, self::$loggerAppender); - self::$loggerManagers[$key] = $loggerManager; - return $loggerManager; - } - - /** - * Signal handler to flush the log. - * - * The name of the signal and the parameters are not used. - */ - public static function flush() - { - foreach (self::$loggerManagers as $loggerManager) { - $loggerManager->flush(); - } - } - - public static function toLevelMarker(string $level): int - { - return self::$direct[$level]; - } - - /** - * Signal handler to flush the log. - * - * The name of the signal and the parameters are not used. - */ - public static function flushHandler($signal, &$params) - { - self::flush(); - } -} - - - diff --git a/src/LoggerAppender/File.php b/src/LoggerAppender/File.php deleted file mode 100755 index 276a008a..00000000 --- a/src/LoggerAppender/File.php +++ /dev/null @@ -1,45 +0,0 @@ -. - */ -namespace Pluf\LoggerAppender; - -use Pluf; - -/** - * ذخیره کردن لاگ‌ها در فایل - * - * This is the simplest logger. You can use it as a base to create - * more complex loggers. The logger interface is really simple and use - * some helper functions from the main Logger class. - * - * The only required static method of a log writer is - * write, which takes the stack to write as parameter. - * - * The only configuration variable of the file writer is the path to - * the log file 'pluf_log_file'. By default it creates a - * pluf.log in the configured tmp folder. - */ -class File implements \Pluf\LoggerAppender -{ - - public function write($message): void - { - $file = Pluf::f('pluf_log_file', Pluf::f('tmp_folder', '/tmp') . '/pluf.log'); - file_put_contents($file, $message . PHP_EOL, FILE_APPEND); - } -} diff --git a/src/LoggerAppender/Remote.php b/src/LoggerAppender/Remote.php deleted file mode 100755 index 973dd53c..00000000 --- a/src/LoggerAppender/Remote.php +++ /dev/null @@ -1,62 +0,0 @@ -. - */ -namespace Pluf\LoggerAppender; - -use Pluf; - -/** - * Posts loggs to remote server - * - * Fire a POST request agains a server with the payload being the - * content of the log. The log is serialized as JSON. It is always - * containing the current stack, so an array of log "lines". - * - * The configuration keys are: - * - * - 'log_remote_server' (localhost) - * - 'log_remote_path' (/) - * - 'log_remote_port' (8000) - * - 'log_remote_headers' (array()) - */ -class Remote implements \Pluf\LoggerAppender -{ - - /** - * Flush the stack to the remote server. - * - * @param $stack Array - */ - public function write($message): void - { - $out = 'POST ' . Pluf::f('log_remote_path', '/') . ' HTTP/1.1' . "\r\n"; - $out .= 'Host: ' . Pluf::f('log_remote_server', 'localhost') . "\r\n"; - $out .= 'Host: localhost' . "\r\n"; - $out .= 'Content-Length: ' . strlen($message) . "\r\n"; - foreach (Pluf::f('log_remote_headers', array()) as $key => $val) { - $out .= $key . ': ' . $val . "\r\n"; - } - $out .= 'Connection: Close' . "\r\n\r\n"; - $out .= $message; - $errno = 0; - $errstr = ''; - $fp = fsockopen(Pluf::f('log_remote_server', 'localhost'), Pluf::f('log_remote_port', 8000), $errno, $errstr, 5); - fwrite($fp, $out); - fclose($fp); - } -} diff --git a/src/LoggerFormatter.php b/src/LoggerFormatter.php deleted file mode 100644 index 0cb47393..00000000 --- a/src/LoggerFormatter.php +++ /dev/null @@ -1,14 +0,0 @@ -getKey() . '] [' . $level . '] ' . $message; - foreach ($context as $key => $value) { - $payload .= $key . ':' . serialize($value); - } - return $payload; - } -} - diff --git a/src/LoggerManager.php b/src/LoggerManager.php deleted file mode 100644 index 12e6421a..00000000 --- a/src/LoggerManager.php +++ /dev/null @@ -1,113 +0,0 @@ -key = $key; - $this->loggerFormater = $loggerFormater; - $this->loggerAppender = $loggerAppender; - - $this->levelMark = Logger::toLevelMarker(Pluf::f('log_level', 'off')); - } - - /** - * Gets key of the logger - * - * @return string - */ - public function getKey() - { - return $this->key; - } - - /** - * write a log - * - * {@inheritdoc} - * @see \Psr\Log\LoggerInterface::log() - */ - public function log($level, $message, array $context = []) - { - // check log leverl - if ($this->levelMark > Logger::toLevelMarker($level)) { - return; - } - - // generate new message - $strMessage = $this->loggerFormater->format($this, $level, $message, $context); - - // check if should write now - if (! Pluf::f('log_delayed', false)) { - $this->loggerAppender->write($strMessage); - } - - // push message to buffer - $this->stack[] = $strMessage; - } - - /** - * Flush the data to the writer. - * - * This reset the stack. - */ - public function flush() - { - if (count($this->stack) == 0) { - return; - } - - // write all messages - foreach ($this->stack as $strMessage) { - $this->loggerAppender->write($strMessage); - } - $this->stack = array(); - } -} \ No newline at end of file diff --git a/src/Mail.php b/src/Mail.php deleted file mode 100755 index 337ce313..00000000 --- a/src/Mail.php +++ /dev/null @@ -1,229 +0,0 @@ -. - */ - -/** - * Generate and send multipart emails. - * - * This class is just a small wrapper around the PEAR packages Mail - * and Mail/mime. - * - * Class to easily generate multipart emails. It supports embedded - * images within the email. It can be used to send a text with - * possible attachments. - * - * The encoding of the message is utf-8 by default. - * - * Usage example: - * - * $email = new Pluf_Mail('from_email@example.com', 'to_email@example.com', - * 'Subject of the message'); - * $img_id = $email->addAttachment('/var/www/html/img/pic.jpg', 'image/jpg'); - * $email->addTextMessage('Hello world!'); - * $email->addHtmlMessage('Hello world!'); - * $email->sendMail(); - * - * - * The configuration parameters are the one for Mail::factory with the - * 'mail_' prefix not to conflict with the other parameters. - * - * @see http://pear.php.net/manual/en/package.mail.mail.factory.php - * - * 'mail_backend' - 'mail', 'smtp' or 'sendmail' (default 'mail'). - * - * List of parameter for the backends: - * - * mail backend - * -------------- - * - * If safe mode is disabled, an array with all the 'mail_*' parameters - * excluding 'mail_backend' will be passed as the fifth argument to - * the PHP mail() function. The elements will be joined as a - * space-delimited string. - * - * sendmail backend - * ------------------ - * - * 'mail_sendmail_path' - The location of the sendmail program on the - * filesystem. Default is /usr/bin/sendmail - * 'sendmail_args' - Additional parameters to pass to the - * sendmail. Default is -i - * - * smtp backend - * -------------- - * - * 'mail_host' - The server to connect. Default is localhost - * 'mail_port' - The port to connect. Default is 25 - * 'mail_auth' - Whether or not to use SMTP authentication. Default is - * FALSE - * - * 'mail_username' - The username to use for SMTP authentication. - * 'mail_password' - The password to use for SMTP authentication. - * 'mail_localhost' - The value to give when sending EHLO or - * HELO. Default is localhost - * 'mail_timeout' - The SMTP connection timeout. Default is NULL (no - * timeout) - * 'mail_verp' - Whether to use VERP or not. Default is FALSE - * 'mail_debug' - Whether to enable SMTP debug mode or not. Default is - * FALSE - * 'mail_persist' - Indicates whether or not the SMTP connection - * should persist over multiple calls to the send() - * method. - * - * If you are doing some testing, you should use the smtp backend - * together with fakemail: http://www.lastcraft.com/fakemail.php - */ -namespace Pluf; - -use Mail_mime; -use Pluf; - -class Mail -{ - - public $headers = array(); - - public $message; - - public $encoding = 'utf-8'; - - public $to_address = ''; - - /** - * Construct the base email. - * - * @param - * string The email of the sender. - * @param - * string The destination email. - * @param - * string The subject of the message. - * @param - * string Encoding of the message ('UTF-8') - * @param - * string End of line type ("\n") - */ - function __construct ($src, $dest, $subject, $encoding = 'UTF-8', $crlf = "\n") - { - // Note that the Pluf autoloader will correctly load this PEAR - // object. - $this->message = new Mail_mime($crlf); - $this->message->_build_params['html_charset'] = $encoding; - $this->message->_build_params['text_charset'] = $encoding; - $this->message->_build_params['head_charset'] = $encoding; - $this->message->_build_params['ignore-iconv'] = true; - $this->message->setContentType('text/html; charset=UTF-8'); - - - $this->message->setContentType('text/html; charset=UTF-8'); - $this->to_address = $dest; - $this->headers = array( - 'From' => $src, - 'To' => $dest, - 'Date' => date(DATE_RFC2822), - 'Subject' => $subject, - 'Content-Type' => 'text/html; charset=UTF-8' - ); - } - - /** - * Add the base plain text message to the email. - * - * @param - * string The message - */ - function addTextMessage ($msg) - { - $this->message->setTXTBody($msg); - } - - /** - * Set the return path for the email. - * - * @param - * string Email - */ - function setReturnPath ($email) - { - $this->headers['Return-Path'] = $email; - } - - /** - * Add headers to an email. - * - * @param - * array Array of headers - */ - function addHeaders ($hdrs) - { - $this->headers = array_merge($this->headers, $hdrs); - } - - /** - * Add the alternate HTML message to the email. - * - * @param - * string The HTML message - */ - function addHtmlMessage ($msg) - { - $this->message->setHTMLBody($msg); - } - - /** - * Add an attachment to the message. - * - * The file to attach must be available on disk and you need to - * provide the mimetype of the attachment manually. - * - * @param - * string Path to the file to be added. - * @param - * string Mimetype of the file to be added ('text/plain'). - * @return bool True. - */ - function addAttachment ($file, $ctype = 'text/plain') - { - $this->message->addAttachment($file, $ctype); - } - - /** - * Effectively sends the email. - */ - function sendMail () - { - $body = $this->message->get(); - $hdrs = $this->message->headers($this->headers); - - $params = Pluf::pf('mail_', true); // strip the prefix 'mail_' - unset($params['backend']); - $gmail = new Mail(); - $mail = $gmail->factory(Pluf::f('mail_backend', 'mail'), $params); - if (Pluf::f('send_emails', true)) { - $ret = $mail->send($this->to_address, $hdrs, $body); - } - if (defined('IN_UNIT_TESTS')) { - $GLOBALS['_PX_UNIT_TESTS']['emails'][] = array( - $this->to_address, - $hdrs, - $body - ); - } - return $ret; - } -} diff --git a/src/Mail/Batch.php b/src/Mail/Batch.php deleted file mode 100755 index 5a83a2de..00000000 --- a/src/Mail/Batch.php +++ /dev/null @@ -1,252 +0,0 @@ -. - */ -namespace Pluf\Mail; - -use Mail; -use Mail_mime; -use Pluf; - -/** - * Generate and send multipart emails in batch mode. - * - * This class is just a small wrapper around the PEAR packages Mail - * and Mail/mime. - * - * Class to easily generate multipart emails. It supports embedded - * images within the email. It can be used to send a text with - * possible attachments. - * - * The encoding of the message is utf-8 by default. - * - * Usage example: - * - * $email = new Pluf_Mail_Batch('from_email@example.com'); - * foreach($emails as $m) { - * $email->setSubject($m['subject']); - * $email->setTo($m['to']); - * $img_id = $email->addAttachment('/var/www/html/img/pic.jpg', 'image/jpg'); - * $email->addTextMessage($m['content']); - * $email->sendMail(); - * } - * $email->close(); - * - * - * The configuration parameters are the one for Mail::factory with the - * 'mail_' prefix not to conflict with the other parameters. - * - * @see http://pear.php.net/manual/en/package.mail.mail.factory.php 'mail_backend' - 'mail', 'smtp' or 'sendmail' (default 'mail'). - * - * List of parameter for the backends: - * - * mail backend - * -------------- - * - * If safe mode is disabled, an array with all the 'mail_*' parameters - * excluding 'mail_backend' will be passed as the fifth argument to - * the PHP mail() function. The elements will be joined as a - * space-delimited string. - * - * sendmail backend - * ------------------ - * - * 'mail_sendmail_path' - The location of the sendmail program on the - * filesystem. Default is /usr/bin/sendmail - * 'sendmail_args' - Additional parameters to pass to the - * sendmail. Default is -i - * - * smtp backend - * -------------- - * - * 'mail_host' - The server to connect. Default is localhost - * 'mail_port' - The port to connect. Default is 25 - * 'mail_auth' - Whether or not to use SMTP authentication. Default is - * FALSE - * - * 'mail_username' - The username to use for SMTP authentication. - * 'mail_password' - The password to use for SMTP authentication. - * 'mail_localhost' - The value to give when sending EHLO or - * HELO. Default is localhost - * 'mail_timeout' - The SMTP connection timeout. Default is NULL (no - * timeout) - * 'mail_verp' - Whether to use VERP or not. Default is FALSE - * 'mail_debug' - Whether to enable SMTP debug mode or not. Default is - * FALSE - * 'mail_persist' - Will automatically be set to true. - * - * If you are doing some testing, you should use the smtp backend - * together with fakemail: http://www.lastcraft.com/fakemail.php - */ -class Batch -{ - - public $headers = array(); - - public $message; - - public $encoding = 'utf-8'; - - public $crlf = "\n"; - - public $from = ''; - - protected $backend = null; - - /** - * Construct the base email. - * - * @param - * string The email of the sender. - * @param - * string Encoding of the message ('UTF-8') - * @param - * string End of line type ("\n") - */ - function __construct($src, $encoding = 'UTF-8', $crlf = "\n") - { - // Note that the Pluf autoloader will correctly load this PEAR - // object. - $this->message = new Mail_mime($crlf); - $this->message->_build_params['html_charset'] = $encoding; - $this->message->_build_params['text_charset'] = $encoding; - $this->message->_build_params['head_charset'] = $encoding; - $this->headers = array( - 'From' => $src - ); - $this->encoding = $encoding; - $this->crlf = $crlf; - $this->from = $src; - } - - /** - * Set the subject of the email. - * - * @param - * string Subject - */ - function setSubject($subject) - { - $this->headers['Subject'] = $subject; - } - - /** - * Set the recipient of the email. - * - * @param - * string Recipient email - */ - function setTo($email) - { - $this->headers['To'] = $email; - } - - /** - * Add the base plain text message to the email. - * - * @param - * string The message - */ - function addTextMessage($msg) - { - $this->message->setTXTBody($msg); - } - - /** - * Set the return path for the email. - * - * @param - * string Email - */ - function setReturnPath($email) - { - $this->headers['Return-Path'] = $email; - } - - /** - * Add headers to an email. - * - * @param - * array Array of headers - */ - function addHeaders($hdrs) - { - $this->headers = array_merge($this->headers, $hdrs); - } - - /** - * Add the alternate HTML message to the email. - * - * @param - * string The HTML message - */ - function addHtmlMessage($msg) - { - $this->message->setHTMLBody($msg); - } - - /** - * Add an attachment to the message. - * - * The file to attach must be available on disk and you need to - * provide the mimetype of the attachment manually. - * - * @param - * string Path to the file to be added. - * @param - * string Mimetype of the file to be added ('text/plain'). - * @return bool True. - */ - function addAttachment($file, $ctype = 'text/plain') - { - $this->message->addAttachment($file, $ctype); - } - - /** - * Effectively sends the email. - */ - function sendMail() - { - if ($this->backend === null) { - $params = Pluf::pf('mail_', true); // strip the prefix 'mail_' - unset($params['backend']); - $gmail = new Mail(); - if (Pluf::f('mail_backend') == 'smtp') { - $params['persist'] = true; - } - $this->backend = $gmail->factory(Pluf::f('mail_backend', 'mail'), $params); - } - $body = $this->message->get(); - $hdrs = $this->message->headers($this->headers); - if (Pluf::f('send_emails', true)) { - $this->backend->send($this->headers['To'], $hdrs, $body); - } - $this->message = new Mail_mime($this->crlf); - $this->message->_build_params['html_charset'] = $this->encoding; - $this->message->_build_params['text_charset'] = $this->encoding; - $this->message->_build_params['head_charset'] = $this->encoding; - $this->headers = array( - 'From' => $this->from - ); - } - - function close() - { - unset($this->backend); - $this->backend = null; - } -} diff --git a/src/Migration.php b/src/Migration.php deleted file mode 100755 index 0bb0a0aa..00000000 --- a/src/Migration.php +++ /dev/null @@ -1,553 +0,0 @@ -. - */ -namespace Pluf; - -/** - * A class to manage the migration of the code from one version to - * another, upward or downward. - * - * You can directly use the migrate.php script. - * - * Simple example usage: - * - *
      - * $m = new \Pluf\Migration('MyApp');
      - * $m->migrate();
      - *
      - * // Install the application MyApp
      - * $m = new \Pluf\Migration('MyApp');
      - * $m->install();
      - * // Uninstall the application MyApp
      - * $m->uninstall();
      - *
      - * $m = new \Pluf\Migration();
      - * $m->migrate(); // migrate all the installed app to the newest version.
      - *
      - * $m = new \Pluf\Migration();
      - * $m->migrate(3); // migrate (upgrade or downgrade) to version 3
      - * 
      - */ -use Pluf\Data\ModelDescription; -use Pluf\Data\Schema; -use Pluf\Pluf\Tenant; -use Pluf; - -class Migration -{ - - protected $app = ''; - - /** - * < Application beeing migrated. - */ - public $apps = array(); - - /** - * < Applications which are going to be migrated. - */ - public $to_version = null; - - /** - * < Target version for the migration. - */ - public $dry_run = false; - - /** - * < Set to true to not act. - */ - public $display = false; - - /** - * < Display on the console what is done. - */ - - /** - * Create a new migration. - * - * @param - * mixed Application or array of applications to migrate. - */ - public function __construct($app = null) - { - if (! is_null($app)) { - if (is_array($app)) { - $this->apps = $app; - } else { - $this->apps = array( - $app - ); - } - } else { - $this->apps = Pluf::f('installed_apps'); - } - } - - /** - * Install the application. - * - * Basically run the base install function for each application - * and then set the version to the latest migration. - */ - public function install() - { - foreach ($this->apps as $app) { - $this->installAppFromConfig($app); - } - return true; - } - - /** - * Init app from data - * - * @return boolean - */ - public function init(?Tenant $tenant = null): bool - { - $current = Tenant::getCurrent(); - try { - Tenant::setCurrent($tenant); - foreach ($this->apps as $app) { - $this->initAppFromConfig($app); - } - } finally { - Tenant::setCurrent($current); - } - return true; - } - - /** - * Uninstall the application. - */ - public function uninstall(): bool - { - $apps = array_reverse($this->apps); - foreach ($apps as $app) { - $this->uninstallAppFromConfig($app); - } - return true; - } - - // /** - // * Backup the application. - // * - // * @param - // * string Path to the backup folder - // * @param - // * string Backup name (null) - // */ - // public function backup($path, $name = null) - // { - // foreach ($this->apps as $app) { - // $func = $app . '_Migrations_Backup_run'; - // if ($this->display) { - // echo ($func . "\n"); - // } - // if (! $this->dry_run) { - // /* $ret = */ - // $func($path, $name); - // } - // } - // return true; - // } - - // /** - // * Restore the application. - // * - // * @param - // * string Path to the backup folder - // * @param - // * string Backup name - // */ - // public function restore($path, $name) - // { - // foreach ($this->apps as $app) { - // $func = $app . '_Migrations_Backup_restore'; - // if ($this->display) { - // echo ($func . "\n"); - // } - // if (! $this->dry_run) { - // /* $ret = */ - // $func($path, $name); - // } - // } - // return true; - // } - - // /** - // * Run the migration. - // */ - // public function migrate($to_version = null) - // { - // $this->to_version = $to_version; - // foreach ($this->apps as $app) { - // $this->app = $app; - // $migrations = $this->findMigrations(); - // // The run will throw an exception in case of error. - // $this->runMigrations($migrations); - // } - // return true; - // } - - // /** - // * Un/Install the given application. - // * - // * @param - // * string Application to install. - // * @param - // * bool Uninstall (false) - // */ - // public function installApp($app) - // { - // // if ($uninstall) { - // // return $this->uninstallAppFromConfig($app); - // // } - // return $this->installAppFromConfig($app); - // } - - // /** - // * Find the migrations for the current app. - // * - // * @return array Migrations names indexed by order. - // */ - // public function findMigrations() - // { - // $migrations = array(); - // if (false !== ($mdir = Pluf::fileExists($this->app . '/Migrations'))) { - // $dir = new DirectoryIterator($mdir); - // foreach ($dir as $file) { - // $matches = array(); - // if (! $file->isDot() && ! $file->isDir() && preg_match('#^(\d+)#', $file->getFilename(), $matches)) { - // $info = pathinfo($file->getFilename()); - // $migrations[(int) $matches[1]] = $info['filename']; - // } - // } - // } - // return $migrations; - // } - - /** - * Install the application based on application configuration - * - * @param string $app - * The application key - * @return boolean true if the installation is successful - */ - private function installAppFromConfig($app) - { - $module = self::getModuleConfig($app); - if ($module === false) { - throw new Exception('Module file not found in path'); - } - - $connection = Pluf::db(); - $schema = Schema::getInstance(Pluf::getConfigByPrefix('db_schema_', true)); - // Create modules - if (array_key_exists('model', $module)) { - $models = $module['model']; - foreach ($models as $model) { - $schema->createTables($connection, ModelDescription::getInstance($model)); - } - } - return true; - } - - // /** - // * Creates the constraints for the current model. - // * This should be done _after_ all tables of all models have been created. - // * - // * @throws \Pluf\Exception - // */ - // public static function createConstraints(Engine $engine, Schema $schema, \Pluf\Data\Model $model): bool - // { - // $sql = $schema->createConstraintQueries($model); - // foreach ($sql as $query) { - // if (false === $engine->execute($query)) { - // throw new Exception($$engine->getError()); - // } - // } - // } - - // /** - // * Drops the constraints for the current model. - // * This should be done _before_ all tables of all models are dropped. - // * - // * @throws \Pluf\Exception - // * @return boolean - // */ - // public static function dropConstraints(Engine $engine, Schema $schema, \Pluf\Data\Model $model): bool - // { - // $sql = $schema->dropConstraintQueries($model); - // foreach ($sql as $query) { - // if (false === $engine->execute($query)) { - // throw new Exception($engine->getError()); - // } - // } - // return true; - // } - - /** - * Load initial data if exist - * - * @param string $app - */ - public function initAppFromConfig($app) - { - $module = self::getModuleConfig($app); - if ($module === false) { - throw new \Pluf\Exception('Module file not found in path'); - } - - // Load models - if (array_key_exists('init', $module)) { - $models = $module['init']; - foreach ($models as $model => $values) { - foreach ($values as $value) { - $p = new $model(); - if (method_exists($p, 'initFromFormData')) { - $p->initFromFormData($value); - } else { - $p->setFromFormData($value); - } - if (! $p->create()) { - throw new \Pluf\Exception('Impossible to load init modules: ' . $model); - } - } - } - } - - // // Init Releations - // if (array_key_exists('init_assoc', $module)) { - // $relations = $module['init_assoc']; - // foreach ($relations as $models => $relates) { - // $model = explode('|', $models); - // $model0 = trim($model[0]); - // $model1 = trim($model[1]); - // $p0 = new $model0(); - // $p1 = new $model1(); - // foreach ($relates as $rel) { - // $p0 = $p0->getOne($rel['from']); - // $p1 = $p1->getOne($rel['to']); - // $p0->setAssoc($p1); - // } - // } - // } - } - - /** - * Delete application - * - * @param string $app - */ - public function uninstallAppFromConfig($app) - { - $module = self::getModuleConfig($app); - if ($module === false) { - throw new Exception('Module file not found in path'); - } - - $schema = Pluf::getDataSchema(); - $connection = Pluf::db(); - - // Delete modules - if (array_key_exists('model', $module)) { - $models = $module['model']; - // foreach ($models as $model) { - // self::dropConstraints($engine, $schema, new $model()); - // } - foreach ($models as $model) { - $schema->dropTables($connection, ModelDescription::getInstance($model)); - } - } - // TODO: delete permissions - // TODO: delete monitors - return true; - } - - /** - * Load module configuration - * - * @param string $app - * @return boolean|mixed - */ - public static function getModuleConfig($app) - { - $moduleName = "Pluf\\" . $app . "\\Module"; - if (class_exists($moduleName)) { - $file = $moduleName::moduleJsonPath; - } else if (false == ($file = Pluf::fileExists($app . '/module.json'))) { - return false; - } - $myfile = fopen($file, "r") or die("Unable to open module.json!"); - $json = fread($myfile, filesize($file)); - fclose($myfile); - return json_decode($json, true); - } - - // /** - // * Run the migrations. - // * - // * From an array of possible migrations, it will first get the - // * current version of the app and then based on $this->to_version - // * will run the migrations in the right order or do nothing if - // * nothing to be done. - // * - // * @param - // * array Possible migrations. - // */ - // public function runMigrations($migrations) - // { - // if (empty($migrations)) { - // return; - // } - // $current = $this->getAppVersion($this->app); - // if ($this->to_version === null) { - // $to_version = max(array_keys($migrations)); - // } else { - // $to_version = $this->to_version; - // } - // if ($to_version == $current) { - // return; // Nothing to do - // } - // $the_way = 'up'; // Tribute to Pat Metheny - // if ($to_version > $current) { - // // upgrade - // $min = $current + 1; - // $max = $to_version; - // } else { - // // downgrade - // $the_way = 'do'; - // $max = $current; - // $min = $to_version + 1; - // } - // // Filter the migrations - // $to_run = array(); - // foreach ($migrations as $order => $name) { - // if ($order < $min or $order > $max) { - // continue; - // } - // if ($the_way == 'up') { - // $to_run[] = array( - // $order, - // $name - // ); - // } else { - // array_unshift($to_run, array( - // $order, - // $name - // )); - // } - // } - // asort($to_run); - // // Run the migrations - // foreach ($to_run as $migration) { - // $this->runMigration($migration, $the_way); - // } - // } - - // /** - // * Run the given migration. - // */ - // public function runMigration($migration, $the_way = 'up') - // { - // $target_version = ($the_way == 'up') ? $migration[0] : $migration[0] - 1; - // if ($this->display) { - // echo ($migration[0] . ' ' . $migration[1] . ' ' . $the_way . "\n"); - // } - // if (! $this->dry_run) { - // if ($the_way == 'up') { - // $func = $this->app . '_Migrations_' . $migration[1] . '_up'; - // } else { - // $func = $this->app . '_Migrations_' . $migration[1] . '_down'; - // } - // $func(); // Real migration run - // $this->setAppVersion($this->app, $target_version); - // } - // } - - // /** - // * Set the application version. - // * - // * @param - // * string Application - // * @param - // * int Version - // * @return true - // */ - // public function setAppVersion($app, $version) - // { - // $gschema = new Pluf_DB_SchemaInfo(); - // $sql = new Query(['filters' => ['application', '=', $app]]); - // $appinfo = $gschema->getList(array( - // 'filter' => $sql->gen() - // )); - // if ($appinfo->count() == 1) { - // $appinfo[0]->version = $version; - // $appinfo[0]->update(); - // } else { - // $schema = new Pluf_DB_SchemaInfo(); - // $schema->application = $app; - // $schema->version = $version; - // $schema->create(); - // } - // return true; - // } - - // /** - // * Remove the application information. - // * - // * @param - // * string Application - // * @return true - // */ - // public function delAppInfo($app) - // { - // $gschema = new Pluf_DB_SchemaInfo(); - // $sql = new Query(['filters' => ['application', '=', $app]]); - // $appinfo = $gschema->getList(array( - // 'filter' => $sql->gen() - // )); - // if ($appinfo->count() == 1) { - // $appinfo[0]->delete(); - // } - // return true; - // } - - // /** - // * Get the current version of the app. - // * - // * @param - // * string Application. - // * @return int Version. - // */ - // public function getAppVersion($app) - // { - // try { - // $db = &Pluf::db(); - // $res = $db->select('SELECT version FROM ' . $db->pfx . 'schema_info WHERE application=' . $db->esc($app)); - // return (int) $res[0]['version']; - // } catch (Exception $e) { - // // We should not be here, only in the case of nothing - // // installed. I am not sure if this is a good way to - // // handle this border case anyway. Maybe better to have an - // // 'install' method to run all the migrations in order. - // return 0; - // } - // } -} \ No newline at end of file diff --git a/src/Module.php b/src/Module.php deleted file mode 100644 index 0126dd30..00000000 --- a/src/Module.php +++ /dev/null @@ -1,183 +0,0 @@ -. - */ -namespace Pluf; - -use Pluf\HTTP\Request; - -/** - * Abstract definistion of modules - * - * @author maso - * - */ -abstract class Module -{ - - const MODULE_DEFAULT_URL_PATH = 'urls.php'; - - /** - * Module key - * - * @var string - */ - private ?string $key = null; - - /** - * Flag to show state of the module - * - * If the module is loaded, then this flag is true. - * - * @var boolean - */ - private bool $loaded = false; - - /** - * Creates new instance of a module with the given key - * - * @param string $key - * of the module - */ - public function __construct(string $key) - { - $this->key = $key; - // TODO: maso, 2020: load if key is empty - } - - /** - * Checks if the module is loaded. - * - * @return boolean - */ - public function isLoaded() - { - return $this->loaded; - } - - /** - * Set the module as loaded. - * - * @param bool $loaded - * state of the module - */ - private function setLoaded(bool $loaded) - { - $this->loaded = $loaded; - } - - /** - * Gets the key of the module - * - * @return string - */ - public function getKey(): string - { - return $this->key; - } - - /** - * Load and return default modul views - * - * @return array of views - */ - public function getViews() - { - $apiPrefix = \Pluf::f('view_api_prefix', ''); - $apiBase = \Pluf::f('view_api_base', ''); - - $reflection = new \ReflectionObject($this); - $directory = dirname($reflection->getFileName()); - if (is_readable($directory . '/' . self::MODULE_DEFAULT_URL_PATH)) { - return [ - array( - 'app' => $reflection->getNamespaceName(), - 'regex' => '#^' . $apiPrefix . '/' . strtolower($this->getKey()) . '#', - 'base' => $apiBase, - 'sub' => require $directory . '/' . self::MODULE_DEFAULT_URL_PATH - ) - ]; - } - return array(); - } - - /** - * Initialize a module - * - * @param \Pluf $bootstrap - */ - public abstract function init(\Pluf $bootstrap): void; - - /** - * Gets list of modules - * - * @return Module[] list of modules - */ - public static function getModules(): array - { - $apps = \Pluf::f('installed_apps', array()); - $modules = array(); - foreach ($apps as $app) { - $moduleName = "Pluf\\" . $app . "\\Module"; - if (class_exists($moduleName)) { - $modules[] = new $moduleName($app); - } - } - - // TODO: maos, 2020: catch the modules - return $modules; - } - - /** - * Loads all module controllers - * - * @param Request $request - * @return array - */ - public static function loadControllers(?Request $request = null): array - { - $modules = self::getModules(); - $views = array(); - foreach ($modules as $module) { - $mviews = $module->getViews(); - $views = array_merge_recursive($views, $mviews); - } - return $views; - } - - /** - * Load all modules - * - * NOTE: This method is not intend to called directlly. - */ - public static function loadModules(): void - { - $modules = self::getModules(); - $bootstrap = new \Pluf(); - foreach ($modules as $module) { - $loaded = false; - try { - $module->init($bootstrap); - $loaded = true; - } catch (\Exception $ex) { - Logger::error('Fail to load module ' . $module->getKey(), $ex); - } - $module->setLoaded($loaded); - } - } -} - diff --git a/src/ObjectMapper.php b/src/ObjectMapper.php deleted file mode 100644 index b640294e..00000000 --- a/src/ObjectMapper.php +++ /dev/null @@ -1,63 +0,0 @@ -objectName = $objectName; - } - - /** - * Fetchs and maps next object - * - * @return Object - */ - public abstract function next($object): Object; - - /** - * Checks if there is more item - * - * @return bool - */ - public abstract function hasMore(): bool; - - public static function getInstance($input): ObjectMapper - { - if ($input instanceof HTTP\Request) { - return new RequestMapper($input); - } - if (is_array($input)) { - return new ArrayMapper($input); - } - throw new Error500('No suitable object mapper fount for input: ' . get_class($input)); - } - - protected function fillObject($object, $values) - { - $md = ModelDescription::getInstance($object); - if (! is_object($object)) { - $object = $md->newInstance(); - } - foreach ($md as $name => $property) { - if ($property->editable && array_key_exists($name, $values)) { - $object->$name = $values[$name]; - } - } - return $object; - } -} - diff --git a/src/ObjectMapper/ArrayMapper.php b/src/ObjectMapper/ArrayMapper.php deleted file mode 100644 index c06e0358..00000000 --- a/src/ObjectMapper/ArrayMapper.php +++ /dev/null @@ -1,62 +0,0 @@ -values = $data; - } else { - $this->values[] = $data; - } - $this->pointer = 0; - } - - /** - * - * {@inheritdoc} - * @see \Pluf\ObjectMapper::next() - */ - public function next($object): Object - { - if (! $this->hasMore()) { - throw new Error500('No more item exist in the array mapper.'); - } - $values = $this->values[$this->pointer ++]; - return $this->fillObject($object, $values); - } - - /** - * - * {@inheritdoc} - * @see \Pluf\ObjectMapper::hasMore() - */ - public function hasMore(): bool - { - return $this->pointer < count($this->values); - } -} - diff --git a/src/ObjectMapper/RequestMapper.php b/src/ObjectMapper/RequestMapper.php deleted file mode 100644 index 98559b02..00000000 --- a/src/ObjectMapper/RequestMapper.php +++ /dev/null @@ -1,78 +0,0 @@ -request = $request; - $values = []; - switch ($request->method) { - case 'GET': - $values = $this->loadGetParams(); - break; - case 'POST': - $values = $this->loadPostParams(); - break; - case 'PUT': - case 'HEAD': - default: - $values = $this->loadEmptyParams(); - break; - } - parent::__construct($values); - } - - private function loadGetParams() - { - return [ - $this->request->REQUEST - ]; - } - - private function loadPostParams() - { - $request = $this->request; - $type = $request->headers->getHeader('Content-Type'); - switch ($type) { - /* - * An associative array of variables passed to the current script - * via the HTTP POST method. - * - * HTTP Content-Type in the request: - * - * - application/x-www-form-urlencoded - * - multipart/form-data - */ - case 'application/x-www-form-urlencoded': - case 'multipart/form-data': - return [ - $request->REQUEST - ]; - break; - default: - return $this->loadEmptyParams(); - break; - } - } - - private function loadEmptyParams() - { - return []; - } -} - diff --git a/src/ObjectStorage.php b/src/ObjectStorage.php deleted file mode 100644 index 89d2b54b..00000000 --- a/src/ObjectStorage.php +++ /dev/null @@ -1,134 +0,0 @@ -required) { -// return ''; // no file -// } elseif (is_null($value) and $this->required) { -// throw new Pluf_Form_Invalid('No files were uploaded. Please try to send the file again.'); -// } -// switch ($value['error']) { -// case UPLOAD_ERR_OK: -// break; -// case UPLOAD_ERR_INI_SIZE: -// throw new Pluf_Form_Invalid(sprintf(('The uploaded file is too large. Reduce the size of the file to %s and send it again.'), -// Pluf_Utils::prettySize(ini_get('upload_max_filesize')))); -// break; -// case UPLOAD_ERR_FORM_SIZE: -// throw new Pluf_Form_Invalid(sprintf(('The uploaded file is too large. Reduce the size of the file to %s and send it again.'), -// Pluf_Utils::prettySize($_REQUEST['MAX_FILE_SIZE']))); -// break; -// case UPLOAD_ERR_PARTIAL: -// throw new Pluf_Form_Invalid(('The upload did not complete. Please try to send the file again.')); -// break; -// case UPLOAD_ERR_NO_FILE: -// if ($this->required) { -// throw new Pluf_Form_Invalid(('No files were uploaded. Please try to send the file again.')); -// } else { -// return ''; // no file -// } -// break; -// case UPLOAD_ERR_NO_TMP_DIR: -// case UPLOAD_ERR_CANT_WRITE: -// throw new Pluf_Form_Invalid(('The server has no temporary folder correctly configured to store the uploaded file.')); -// break; -// case UPLOAD_ERR_EXTENSION: -// throw new Pluf_Form_Invalid(('The uploaded file has been stopped by an extension.')); -// break; -// default: -// throw new Pluf_Form_Invalid(('An error occured when upload the file. Please try to send the file again.')); -// } -// if ($value['size'] > $this->max_size) { -// throw new Pluf_Form_Invalid(sprintf(('The uploaded file is to big (%1$s). Reduce the size to less than %2$s and try again.'), -// Pluf_Utils::prettySize($value['size']), -// Pluf_Utils::prettySize($this->max_size))); -// } -// // copy the file to the final destination and updated $value -// // with the final path name. 'final_name' is relative to -// // Pluf::f('upload_path') -// Pluf::loadFunction($this->move_function); -// // Should throw a Pluf_Form_Invalid exception if error or the -// // value to be stored in the database. -// return call_user_func($this->move_function, $value, -// $this->move_function_params); -// } -// } - -// /** -// * Default move function. The file name is sanitized. -// * -// * In the extra parameters, options can be used so that this function is -// * matching most of the needs: -// * -// * * 'upload_path': The path in which the uploaded file will be -// * stored. -// * * 'upload_path_create': If set to true, try to create the -// * upload path if not existing. -// * -// * * 'upload_overwrite': Set it to true if you want to allow overwritting. -// * -// * * 'file_name': Force the file name to this name and do not use the -// * original file name. If this name contains '%s' for -// * example 'myid-%s', '%s' will be replaced by the -// * original filename. This can be used when for -// * example, you want to prefix with the id of an -// * article all the files attached to this article. -// * -// * If you combine those options, you can dynamically generate the path -// * name in your form (for example date base) and let this upload -// * function create it on demand. -// * -// * @param array Upload value of the form. -// * @param array Extra parameters. If upload_path key is set, use it. (array()) -// * @return string Name relative to the upload path. -// */ -// function Pluf_Form_Field_File_moveToUploadFolder($value, $params=array()) -// { -// $name = Pluf_Utils::cleanFileName($value['name']); -// $upload_path = Pluf::f('upload_path', '/tmp'); -// if (isset($params['file_name'])) { -// if (false !== strpos($params['file_name'], '%s')) { -// $name = sprintf($params['file_name'], $name); -// } else { -// $name = $params['file_name']; -// } -// } -// if (isset($params['upload_path'])) { -// $upload_path = $params['upload_path']; -// } -// $dest = $upload_path.'/'.$name; -// if (isset($params['upload_path_create']) -// and !is_dir(dirname($dest))) { -// if (false == @mkdir(dirname($dest), 0777, true)) { -// throw new Pluf_Form_Invalid(('An error occured when creating the upload path. Please try to send the file again.')); -// } -// } -// if ((!isset($params['upload_overwrite']) or $params['upload_overwrite'] == false) and file_exists($dest)) { -// throw new Pluf_Form_Invalid(sprintf(('A file with the name "%s" has already been uploaded.'), $name)); -// } -// if (@!move_uploaded_file($value['tmp_name'], $dest)) { -// throw new Pluf_Form_Invalid(('An error occured when uploading the file. Please try to send the file again.')); -// } -// @chmod($dest, 0666); -// return $name; -// } \ No newline at end of file diff --git a/src/ObjectValidator.php b/src/ObjectValidator.php deleted file mode 100644 index a5949e28..00000000 --- a/src/ObjectValidator.php +++ /dev/null @@ -1,24 +0,0 @@ -defaultValues = $defaultValues->values; - } elseif (is_array($defaultValues)) { - $this->defaultValues = $defaultValues; - } else { - throw new Exception('Unsupported data type default values:' . get_class($defaultValues)); - } - } - } - - function __get($key) - { - // create key - if ($this->strip) { - $key = $this->prefix . $key; - } - - // get value from parent - if (isset($this->parent)) { - return $this->parent->$key; - } - - // get local value - if (isset($this->values[$key])) { - return $this->values[$key]; - } - if (isset($this->defaultValues[$key])) { - return $this->defaultValues[$key]; - } - return null; - } - - function __set($key, $value) - { - // create key - if ($this->strip) { - $key = $this->prefix . $key; - } - - // set value to parent - if (isset($this->parent)) { - $this->parent->$key = $value; - return; - } - - // set local value - $this->values[$key] = $value; - } - - /** - * Gets subset of options by prefix - * - * @param string $prefix - * A prefix key of all configs - * @param boolean $strip - * To cut key or not - * @return \Pluf\Options subset - */ - public function startsWith(string $prefix, $strip = false) - { - $options = new Options(); - $options->parent = $this; - $options->prefix = $prefix; - $options->strip = $strip; - return $options; - } - - /** - * - * {@inheritdoc} - * @see ArrayAccess::offsetSet() - */ - public function offsetSet($offset, $value) - { - if (is_null($offset)) { - $this->values[] = $value; - } else { - $this->values[$offset] = $value; - } - } - - /** - * - * {@inheritdoc} - * @see ArrayAccess::offsetExists() - */ - public function offsetExists($offset) - { - return isset($this->values[$offset]); - } - - /** - * - * {@inheritdoc} - * @see ArrayAccess::offsetUnset() - */ - public function offsetUnset($offset) - { - unset($this->values[$offset]); - } - - /** - * - * {@inheritdoc} - * @see ArrayAccess::offsetGet() - */ - public function offsetGet($offset) - { - return isset($this->values[$offset]) ? $this->values[$offset] : null; - } - - /** - * - * {@inheritdoc} - * @see Iterator::rewind() - */ - public function rewind() - { - $this->position = 0; - } - - /** - * - * {@inheritdoc} - * @see Iterator::current() - */ - public function current() - { - return $this->values[$this->position]; - } - - /** - * - * {@inheritdoc} - * @see Iterator::key() - */ - public function key() - { - return $this->position; - } - - /** - * - * {@inheritdoc} - * @see Iterator::next() - */ - public function next() - { - ++ $this->position; - } - - /** - * - * {@inheritdoc} - * @see Iterator::valid() - */ - public function valid() - { - return isset($this->values[$this->position]); - } - - /** - * - * {@inheritdoc} - * @see Serializable::serialize() - */ - public function serialize() - { - $data = [ - 'values' => $this->values, - 'default' => $this->defaultValues - ]; - return serialize($data); - } - - /** - * - * {@inheritdoc} - * @see Serializable::unserialize() - */ - public function unserialize($data) - { - $data = unserialize($data); - $this->defaultValues = $data['default']; - $this->values = $data['values']; - } -} - diff --git a/src/Pluf/Module.php b/src/Pluf/Module.php deleted file mode 100644 index d965f304..00000000 --- a/src/Pluf/Module.php +++ /dev/null @@ -1,52 +0,0 @@ -. - */ -namespace Pluf\Pluf; - -use Pluf\Signal; - -class Module extends \Pluf\Module -{ - - const moduleJsonPath = __DIR__ . '/module.json'; - - const relations = array( - 'Pluf_Search_Occ' => array( - 'relate_to' => array( - 'Pluf_Search_Word' - ) - ) - ); - - const urlsPath = __DIR__ . '/urls.php'; - - public function init(\Pluf $bootstrap): void - { - /** - * For each model having a Engine::FOREIGNKEY or a Engine::MANY_TO_MANY colum, details - * must be added here. - * These details are used to generated the methods - * to retrieve related models from each model. - */ - Signal::connect('Pluf_Dispatcher::postDispatch', array( - '\\Pluf\\Logger', - 'flushHandler' - ), 'Pluf_Dispatcher'); - } -} - diff --git a/src/Pluf/Queue.php b/src/Pluf/Queue.php deleted file mode 100755 index c621abc8..00000000 --- a/src/Pluf/Queue.php +++ /dev/null @@ -1,134 +0,0 @@ -. - */ -namespace Pluf\Pluf; - - -/** - * Simple queue system to delay the processing of tasks. - * - * What you do is that you push in the queue an object and what was - * done on the object. Then later one, a simple script will go through - * the queue and will check if something has to be done for the given - * object. - * - * For example, you have articles with for each articles a list of - * authors. You want to index the article with also the name of the - * authors. So it means that you need to update the index when an - * author is changed and when an article is changed. But you do not - * want to do the indexing of all the articles of an author when you - * update the author information (if this take 0.5s per article, with - * 100 articles, you would have to wait nearly 1 minute!). - * - * So when you update an author you push in the queue: "author x has - * been updated". Then you have a script that will go in the queue, - * find that the author has been updated and index each of his - * articles. - */ -class Queue extends \Pluf\Data\Model -{ - - function init() - { - $this->_a['verbose'] = 'message queue'; - $this->_a['table'] = 'pluf_queue'; - $this->_a['cols'] = array( - // It is mandatory to have an "id" column. - 'id' => array( - 'type' => 'Sequence', - // It is automatically added. - 'blank' => true - ), - 'version' => array( - 'type' => 'Integer', - 'blank' => true - ), - 'model_class' => array( - 'type' => 'Varchar', - 'blank' => false, - 'size' => 150, - 'verbose' => 'model class' - ), - 'model_id' => array( - 'type' => 'Integer', - 'blank' => false, - 'verbose' => 'model id' - ), - 'action' => array( - 'type' => 'Varchar', - 'blank' => false, - 'size' => 150, - 'verbose' => 'action' - ), - 'lock' => array( - 'type' => 'Integer', - 'blank' => false, - 'verbose' => 'lock status', - 'default' => 0, - 'choices' => array( - 'Free' => 0, - 'In progress' => 1, - 'Completed' => 2 - ) - ), - 'creation_dtime' => array( - 'type' => 'Datetime', - 'blank' => true, - 'verbose' => 'created at' - ), - 'modif_dtime' => array( - 'type' => 'Datetime', - 'blank' => true, - 'verbose' => 'modified at' - ) - ); - $this->_a['idx'] = array( - 'lock_idx' => array( - 'type' => 'normal', - 'col' => 'lock' - ) - ); - } - - function preSave($create = false) - { - if ($this->id == '') { - $this->creation_dtime = gmdate('Y-m-d H:i:s'); - } - $this->modif_dtime = gmdate('Y-m-d H:i:s'); - } - - /** - * Add an object to the queue. - * - * @param - * \Pluf\Data\Model Your model - * @param - * string Action for the object - */ - public static function addTo($object, $action = '') - { - $q = new Queue(); - $q->model_class = $object->_model; - $q->model_id = $object->id; - $q->lock = 0; - $q->action = $action; - $q->create(); - } -} - diff --git a/src/Pluf/Queue/Processor.php b/src/Pluf/Queue/Processor.php deleted file mode 100755 index 679abc02..00000000 --- a/src/Pluf/Queue/Processor.php +++ /dev/null @@ -1,98 +0,0 @@ -. - */ -namespace Pluf\Pluf\Queue; - -use Pluf\Signal; -use Pluf\Pluf\Queue; -use Pluf; - -/** - * Class to process a Pluf_Queue. - * - * This class is very simple as basically this is just a signal - * handler. It goes throught the queue, get a free item, lock it, - * process it by sending a signal then mark it as done. - */ -class Processor -{ - - /** - * Get an item to process. - * - * @return mixed False if no item to proceed. - */ - public static function getItem() - { - $item = false; - $db = Pluf::db(); - $db->begin(); - // In a transaction to not process the same item at - // the same time from to processes. - $gqueue = new Queue(); - $items = $gqueue->getList(array( - 'filter' => $db->qn('lock') . '=0', - 'order' => 'creation_dtime ASC' - )); - if ($items->count() > 0) { - $item = $items[0]; - $item->lock = 1; - $item->update(); - } - $db->commit(); - if ($item === false) - return false; - // try to get the corresponding object - $obj = Pluf::factory($item->model_class, $item->model_id); - if ($obj->id != $item->model_id) - $obj = null; - return array( - 'queue' => $item, - 'item' => $obj - ); - } - - public static function process() - { - while (false !== ($q = self::getItem())) { - /** - * [signal] - * - * Pluf_Queue_Processor::process - * - * [sender] - * - * Pluf_Queue_Processor - * - * [description] - * - * This signal allows an application to perform an action on a - * queue item. The item is set to null if none existing. - * - * You must not modify the 'queue' object. - * - * [parameters] - * - * array('item' => $item, 'queue' => $queue); - */ - Signal::send('Pluf_Queue_Processor::process', 'Pluf_Queue_Process', $q); - $q['queue']->lock = 2; - $q['queue']->update(); - } - } -} diff --git a/src/Pluf/README b/src/Pluf/README deleted file mode 100644 index 89b6876a..00000000 --- a/src/Pluf/README +++ /dev/null @@ -1,3 +0,0 @@ -Pluf Module - -The Pluf module contains basics of a website. \ No newline at end of file diff --git a/src/Pluf/Search.php b/src/Pluf/Search.php deleted file mode 100755 index 11a31ca6..00000000 --- a/src/Pluf/Search.php +++ /dev/null @@ -1,256 +0,0 @@ -. - */ -namespace Pluf\Pluf; - -use Pluf\SQL; -use Pluf\Text; -use Pluf\Pluf\Search\Occ; -use Pluf\Pluf\Search\Stats; -use Pluf\Pluf\Search\Word; -use Exception; -use Pluf; - -/** - * Class implementing a small search engine. - * - * Ideal for a small website with up to 100,000 documents. - */ -class Search -{ - - /** - * Search. - * - * Returns an array of array with model_class, model_id and - * score. The list is already sorted by score descending. - * - * You can then filter the list as you wish with another set of - * weights. - * - * @param - * string Query string. - * @return array Results. - */ - public static function search($query, $stemmer = 'Pluf_Text_Stemmer_Porter') - { - $query = Text::cleanString(html_entity_decode($query, ENT_QUOTES, 'UTF-8')); - $words = Text::tokenize($query); - if ($stemmer != null) { - $words = self::stem($words, $stemmer); - } - $words_flat = array(); - foreach ($words as $word => $c) { - $words_flat[] = $word; - } - $word_ids = self::getWordIds($words_flat); - if (in_array(null, $word_ids)) { - return array(); - } - return self::searchDocuments($word_ids); - } - - /** - * Stem the words with the given stemmer. - */ - public static function stem($words, $stemmer) - { - $nwords = array(); - foreach ($words as $word => $occ) { - $word = call_user_func(array( - $stemmer, - 'stem' - ), $word); - if (isset($nwords[$word])) { - $nwords[$word] += $occ; - } else { - $nwords[$word] = $occ; - } - } - return $nwords; - } - - /** - * Search documents. - * - * Only the total of the ponderated occurences is used to sort the - * results. - * - * @param - * array Ids. - * @return array Sorted by score, returns model_class, model_id and score. - */ - public static function searchDocuments($wids) - { - $db = &Pluf::db(); - $gocc = new Occ(); - $where = array(); - foreach ($wids as $id) { - $where[] = $db->qn('word') . '=' . (int) $id; - } - $select = 'SELECT model_class, model_id, SUM(pondocc) AS score FROM ' . $gocc->getSqlTable() . ' WHERE ' . implode(' OR ', $where) . ' GROUP BY model_class, model_id HAVING COUNT(*)=' . count($wids) . ' ORDER BY score DESC'; - return $db->select($select); - } - - /** - * Get the id of each word. - * - * @param - * array Words - * @return array Ids, null if no matching word. - */ - public static function getWordIds($words) - { - $ids = array(); - $gword = new Word(); - foreach ($words as $word) { - $l = $gword->getList(array( - 'filter' => [ - [ - 'word', - '=', - $word - ] - ] - )); - if ($l->count() > 0) { - $ids[] = $l[0]->id; - } else { - $ids[] = null; - } - } - return $ids; - } - - /** - * Index a document. - * - * The document must provide a method _toIndex() returning the - * document as a string for indexation. The string must be clean - * and will simply be tokenized by Pluf_Text::tokenize(). - * - * So a recommended way to clean it at the end is to remove all - * the HTML tags and then run the following on it: - * - * return Pluf_Text::cleanString(html_entity_decode($string, - * ENT_QUOTES, 'UTF-8')); - * - * Indexing is resource intensive so it is recommanded to run the - * indexing in an asynchronous way. When you save a resource to be - * indexed, just write a log "need to index resource x" and then - * you can every few minutes index the resources. Nobody care if - * your index is not perfectly fresh, but your end users care if - * it takes 0.6s to get back the page instead of 0.1s. - * - * Take 500 average documents, index them while counting the total - * time it takes to index. Divide by 500 and if the result is more - * than 0.1s, use a log/queue. - * - * FIXME: Concurrency problem if you index at the same time the same doc. - * - * @param - * \Pluf\Data\Model Document to index. - * @param - * Stemmer used. ('Pluf_Text_Stemmer_Porter') - * @return array Statistics. - */ - public static function index($doc, $stemmer = 'Pluf_Text_Stemmer_Porter') - { - $words = Text::tokenize($doc->_toIndex()); - if ($stemmer != null) { - $words = self::stem($words, $stemmer); - } - // Get the total number of words. - $total = 0.0; - $words_flat = array(); - foreach ($words as $word => $occ) { - $total += (float) $occ; - $words_flat[] = $word; - } - // Drop the last indexation. - $gocc = new Occ(); - $sql = new SQL('DELETE FROM ' . $gocc->getSqlTable() . ' WHERE model_class=%s AND model_id=%s', array( - $doc->_model, - $doc->id - )); - $db = &Pluf::db(); - $db->execute($sql->gen()); - // Get the ids for each word. - $ids = self::getWordIds($words_flat); - // Insert a new word for the missing words and add the occ. - $n = count($ids); - $new_words = 0; - $done = array(); - for ($i = 0; $i < $n; $i ++) { - if ($ids[$i] === null) { - $word = new Word(); - $word->word = $words_flat[$i]; - try { - $word->create(); - $ids[$i] = $word->id; - } catch (Exception $e) { - // most likely concurrent addition of a word, try - // to read it. - $_ids = self::getWordIds(array( - $words_flat[$i] - )); - if ($_ids[0] !== null) { - // if we miss it here, just forget about it - $ids[$i] = $_ids[0]; - } - } - $new_words ++; - } - if (isset($done[$ids[$i]])) { - continue; - } - $done[$ids[$i]] = true; - $occ = new Occ(); - $occ->word = new Word($ids[$i]); - $occ->model_class = $doc->_model; - $occ->model_id = $doc->id; - $occ->occ = $words[$words_flat[$i]]; - $occ->pondocc = $words[$words_flat[$i]] / $total; - $occ->create(); - } - // update the stats - $sql = new SQL('model_class=%s AND model_id=%s', array( - $doc->_model, - $doc->id - )); - $last_index = Pluf::factory(Stats::class)->getList(array( - 'filter' => $sql->gen() - )); - if ($last_index->count() == 0) { - $stats = new Stats(); - $stats->model_class = $doc->_model; - $stats->model_id = $doc->id; - $stats->indexations = 1; - $stats->create(); - } else { - $last_index[0]->indexations += 1; - $last_index[0]->update(); - } - return array( - 'total' => $total, - 'new' => $new_words, - 'unique' => $n - ); - } -} \ No newline at end of file diff --git a/src/Pluf/Search/Occ.php b/src/Pluf/Search/Occ.php deleted file mode 100755 index 93019f34..00000000 --- a/src/Pluf/Search/Occ.php +++ /dev/null @@ -1,81 +0,0 @@ -. - */ -namespace Pluf\Pluf\Search; - - -/** - * Storage of the occurence of the words. - */ -class Occ extends \Pluf\Data\Model -{ - - function init() - { - $this->_a['verbose'] = 'occurence'; - $this->_a['table'] = 'pluf_search_occs'; - $this->_a['cols'] = array( - // It is mandatory to have an "id" column. - 'id' => array( - 'type' => 'Sequence', - // It is automatically added. - 'blank' => true - ), - 'word' => array( - 'type' => 'Foreignkey', - 'model' => 'Pluf_Search_Word', - 'blank' => false, - 'verbose' => 'word' - ), - 'model_class' => array( - 'type' => 'Varchar', - 'blank' => false, - 'size' => 150, - 'verbose' => 'model class' - ), - 'model_id' => array( - 'type' => 'Integer', - 'blank' => false, - 'verbose' => 'model id' - ), - 'occ' => array( - 'type' => 'Integer', - 'blank' => false, - 'verbose' => 'occurences' - ), - 'pondocc' => array( - 'type' => 'Float', - 'blank' => false, - 'verbose' => 'weighted occurence' - ) - ); - $this->_a['idx'] = array( - 'model_class_id_combo_word_idx' => array( - 'type' => 'unique', - 'col' => 'model_class, model_id, word' - ) - ); - } - - function __toString() - { - return $this->word; - } -} - diff --git a/src/Pluf/Search/ResultSet.php b/src/Pluf/Search/ResultSet.php deleted file mode 100755 index c301fd78..00000000 --- a/src/Pluf/Search/ResultSet.php +++ /dev/null @@ -1,77 +0,0 @@ -. - */ -namespace Pluf\Pluf\Search; - -use Iterator; -use Pluf; - -/** - * ResultSet class to iterate over a search result. - */ -class ResultSet implements Iterator -{ - - protected $results = array(); - - public function __construct($search_res) - { - $this->results = $search_res; - reset($this->results); - } - - /** - * Get the current item. - */ - public function current() - { - $i = current($this->results); - $doc = Pluf::factory($i['model_class'], $i['model_id']); - $doc->_searchScore = $i['score']; - return $doc; - } - - public function key() - { - return key($this->results); - } - - public function next() - { - next($this->results); - } - - public function rewind() - { - reset($this->results); - } - - public function valid() - { - // We know that the boolean false will not be stored as a - // field, so we can test against false to check if valid or - // not. - return (false !== current($this->results)); - } - - public function count() - { - return count($this->results); - } -} \ No newline at end of file diff --git a/src/Pluf/Search/Stats.php b/src/Pluf/Search/Stats.php deleted file mode 100755 index 0e7d3b76..00000000 --- a/src/Pluf/Search/Stats.php +++ /dev/null @@ -1,85 +0,0 @@ -. - */ -namespace Pluf\Pluf\Search; - - -/** - * Keep track of when a document has been last indexed and the number - * of indexations. - */ -class Stats extends \Pluf\Data\Model -{ - - function init () - { - $this->_a['verbose'] = 'search stats'; - $this->_a['table'] = 'pluf_search_stats'; - $this->_a['cols'] = array( - // It is mandatory to have an "id" column. - 'id' => array( - 'type' => 'Sequence', - // It is automatically added. - 'blank' => true - ), - 'model_class' => array( - 'type' => 'Varchar', - 'blank' => false, - 'size' => 150, - 'verbose' => 'model class' - ), - 'model_id' => array( - 'type' => 'Integer', - 'blank' => false, - 'verbose' => 'model id' - ), - 'indexations' => array( - 'type' => 'Integer', - 'blank' => false, - 'verbose' => 'number of indexations', - 'default' => 0 - ), - 'creation_dtime' => array( - 'type' => 'Datetime', - 'blank' => true, - 'verbose' => 'created at' - ), - 'modif_dtime' => array( - 'type' => 'Datetime', - 'blank' => true, - 'verbose' => 'modified at' - ) - ); - $this->_a['idx'] = array( - 'model_class_id_combo_idx' => array( - 'type' => 'unique', - 'col' => 'model_class, model_id' - ) - ); - } - - function preSave ($create = false) - { - if ($this->id == '') { - $this->creation_dtime = gmdate('Y-m-d H:i:s'); - } - $this->modif_dtime = gmdate('Y-m-d H:i:s'); - } -} - diff --git a/src/Pluf/Search/Word.php b/src/Pluf/Search/Word.php deleted file mode 100755 index 47f7fe31..00000000 --- a/src/Pluf/Search/Word.php +++ /dev/null @@ -1,55 +0,0 @@ -. - */ -namespace Pluf\Pluf\Search; - - -/** - * Storage of the words. - */ -class Word extends \Pluf\Data\Model -{ - - function init() - { - $this->_a['verbose'] = 'word'; - $this->_a['table'] = 'pluf_search_words'; - $this->_a['cols'] = array( - // It is mandatory to have an "id" column. - 'id' => array( - 'type' => 'Sequence', - // It is automatically added. - 'blank' => true - ), - 'word' => array( - 'type' => 'Varchar', - 'blank' => false, - 'unique' => true, - 'size' => 150, - 'verbose' => 'word' - ) - ); - } - - function __toString() - { - return $this->word; - } -} - diff --git a/src/Pluf/Session.php b/src/Pluf/Session.php deleted file mode 100755 index 06cc57fc..00000000 --- a/src/Pluf/Session.php +++ /dev/null @@ -1,222 +0,0 @@ -. - */ -namespace Pluf\Pluf; - -use Pluf\Data\Schema; -use Pluf; - -/** - * ساختار داده‌ای یک نشست را تعیین می‌کند - * - * @author maso - * - */ -class Session extends \Pluf\Data\Model -{ - - public $data = array(); - - public $cookie_name = 'sessionid'; - - public $touched = false; - - public $test_cookie_name = 'testcookie'; - - public $test_cookie_value = 'worked'; - - public $set_test_cookie = false; - - public $test_cookie = null; - - /** - * یک نمونه جدید از این کلاس ایجاد می‌کند. - * - * @see \\Pluf\Data\Model::_init() - */ - function _init() - { - $this->cookie_name = Pluf::f('session_cookie_id', 'sessionid'); - parent::_init(); - } - - /** - * ساختارهای داده‌ای مورد نیاز برای نشت را تعیین می‌کند. - * - * @see \\Pluf\Data\Model::init() - */ - function init() - { - $this->_a['table'] = 'sessions'; - $this->_a['verbose'] = 'sessions'; - $this->_a['cols'] = array( - // It is mandatory to have an "id" column. - 'id' => array( - 'type' => Schema::SEQUENCE, - 'blank' => true - ), - 'version' => array( - 'type' => 'Integer', - 'blank' => true - ), - 'session_key' => array( - 'type' => 'Varchar', - 'blank' => false, - 'size' => 100 - ), - 'session_data' => array( - 'type' => 'Text', - 'blank' => false - ), - 'expire' => array( - 'type' => 'Datetime', - 'blank' => false - ) - ); - $this->_a['idx'] = array( - 'session_key_idx' => array( - 'type' => 'unique', - 'col' => 'session_key' - ) - ); - // $this->_admin = array(); - } - - /** - * تعیین یک داده در نشست - * - * با استفاده از این فراخوانی می‌توان یک داده با کلید جدید در نشست ایجاد - * کرد. این کلید برای دستیابی‌های - * بعد مورد استفاده قرار خواهد گرفت. - * - * @param - * کلید داده - * @param - * داده مورد نظر. در صورتی که مقدار آن تهی باشد به معنی - * حذف است. - */ - function setData($key, $value = null) - { - if (is_null($value)) { - unset($this->data[$key]); - } else { - $this->data[$key] = $value; - } - $this->touched = true; - } - - /** - * داده معادل با کلید تعیین شده را برمی‌گرداند - * - * در صورتی که داده تعیین نشده بود مقدار پیش فرض تعیین شده به عنوان نتیجه - * این فراخوانی - * برگردانده خواهد شد. - */ - function getData($key = null, $default = '') - { - if (is_null($key)) { - return parent::getData(); - } - if (isset($this->data[$key])) { - return $this->data[$key]; - } else { - return $default; - } - } - - /** - * Check if value is set in session - * - * @param string $key - * @return boolean - */ - public function containsKey($key) - { - return isset($this->data[$key]); - } - - /** - * تمام داده‌های موجود در نشت را پاک می‌کند. - * - * تمام داده‌های ذخیره شده در نشست را پاک می‌کند. - */ - function clear() - { - $this->data = array(); - $this->touched = true; - } - - /** - * یک کلید نشت جدید را ایجاد می‌کند. - * - * از این کلید برای دستیابی به داده‌ها استفاده می‌شود. - */ - function getNewSessionKey() - { - while (1) { - $key = md5(microtime() . rand(0, 123456789) . rand(0, 123456789) . Pluf::f('secret_key')); - $sess = $this->getList(array( - 'filter' => 'session_key=\'' . $key . '\'' - )); - if (count($sess) == 0) { - break; - } - } - return $key; - } - - /** - * Presave/create function to encode data into session_data. - */ - function preSave($create = false) - { - $this->session_data = serialize($this->data); - if ($this->session_key == '') { - $this->session_key = $this->getNewSessionKey(); - } - $this->expire = gmdate('Y-m-d H:i:s', time() + 31536000); - } - - /** - * Restore function to decode the session_data into $this->data. - */ - function restore() - { - $this->data = unserialize($this->session_data); - } - - /** - * Create a test cookie. - */ - public function createTestCookie() - { - $this->set_test_cookie = true; - } - - public function getTestCookie() - { - return ($this->test_cookie == $this->test_cookie_value); - } - - public function deleteTestCookie() - { - $this->set_test_cookie = true; - $this->test_cookie_value = null; - } -} diff --git a/src/Pluf/Tenant.php b/src/Pluf/Tenant.php deleted file mode 100644 index 1de26c81..00000000 --- a/src/Pluf/Tenant.php +++ /dev/null @@ -1,278 +0,0 @@ - - * - */ -class Tenant extends \Pluf\Data\Model -{ - - /** - * Current tenant - * - * @var Tenant - */ - static ?Tenant $currentTenant = NULL; - - /** - * یک نگاشت به صورت کلید-مقدار که تنظیمات ملک را نگه می دارد. - * تنظیمات ملک خصوصیاتی است که توسط مالک ملک قابل تعیین است - * - * @var array $setting - */ - protected $settingData = array(); - - protected $settingChanged = false; - - /** - * یک نگاشت به صورت کلید-مقدار که پیکره‌بندی ملک را نگه می دارد. - * پیکره بندی ملک خصوصیاتی است که توسط ادمین کل پلتفورم قابل تعیین است - * - * @var array $configData - */ - protected $configData = array(); - - protected $configChanged = false; - - /** - * - * @brief مدل داده‌ای را بارگذاری می‌کند. - * - * @see \Pluf\Data\Model::init() - */ - function init() - { - $this->_a['table'] = 'tenants'; - $this->_a['multitenant'] = false; - $this->_a['cols'] = array( - 'id' => array( - 'type' => Schema::SEQUENCE, - 'blank' => true, - 'editable' => false - ), - 'level' => array( - 'type' => 'Integer', - 'blank' => true, - 'default' => 0, - 'editable' => false - ), - 'title' => array( - 'type' => 'Varchar', - 'blank' => true, - 'size' => 100 - ), - 'description' => array( - 'type' => 'Varchar', - 'blank' => true, - 'is_null' => true, - 'size' => 1024, - 'editable' => true - ), - 'domain' => array( - 'type' => 'Varchar', - 'blank' => true, - 'is_null' => true, - 'unique' => true, - 'size' => 63, - 'editable' => true - ), - 'subdomain' => array( - 'type' => 'Varchar', - 'is_null' => false, - 'unique' => true, - 'size' => 63, - 'editable' => true - ), - 'validate' => array( - 'type' => 'Boolean', - 'default' => false, - 'is_null' => true, - 'editable' => false - ), - 'email' => array( - 'type' => 'Email', - 'is_null' => true, - 'verbose' => 'Owner email', - 'editable' => true, - 'readable' => true - ), - 'phone' => array( - 'type' => 'Varchar', - 'blank' => true, - 'verbose' => 'Owner phone', - 'editable' => true, - 'readable' => true - ), - 'creation_dtime' => array( - 'type' => 'Datetime', - 'blank' => true, - 'editable' => false - ), - 'modif_dtime' => array( - 'type' => 'Datetime', - 'blank' => true, - 'editable' => false - ), - /* - * Relations - */ - 'parent_id' => array( - 'type' => 'Foreignkey', - 'model' => Tenant::class, - 'blank' => true, - // 'name' => 'parent', // Used to override columen name - 'graphql_name' => 'parent', - 'relate_name' => 'children', - 'editable' => true, - 'readable' => true - ) - ); - } - - /** - * \brief پیش ذخیره را انجام می‌دهد - * - * @param boolean $create - * حالت - * ساخت یا به روز رسانی را تعیین می‌کند - */ - function preSave($create = false) - { - if ($this->id == '') { - $this->creation_dtime = gmdate('Y-m-d H:i:s'); - } - $this->modif_dtime = gmdate('Y-m-d H:i:s'); - } - - /** - * ملک تعیین شده با زیردامنه تعیین شده را برمی‌گرداند - * - * @param string $subdomain - * @return Tenant - */ - public static function bySubDomain($subdomain) - { - $list = Pluf::getDataRepository(Tenant::class)->get(new Query([ - 'filter' => [ - [ - 'subdomain', - '=', - $subdomain - ] - ] - ])); - if (sizeof($list) > 0) { - return $list[0]; - } - return NULL; - } - - /** - * ملک با دامنه تعیین شده را برمی‌گرداند. - * - * @param string $domain - */ - public static function byDomain($domain) - { - $repo = Pluf::getDataRepository(Tenant::class); - $list = $repo->get(new Query([ - 'filter' => [ - [ - 'domain', - '=', - $domain - ] - ] - ])); - if (sizeof($list) > 0) { - return $list[0]; - } - return NULL; - } - - public static function setCurrent(?Tenant $tenant = null): void - { - self::$currentTenant = $tenant; - // Change current request tenant - $request = Request::getCurrent(); - if (isset($request)) { - $request->setTenant($tenant); - } - } - - /** - * - * @deprecated See Tenant::getCurrent() - * @return Tenant current tentant - */ - public static function current(): ?Tenant - { - return self::getCurrent(); - } - - /** - * Gets current tenant - * - * @return Tenant - */ - public static function getCurrent(): ?Tenant - { - // check current value - if (isset(self::$currentTenant)) { - return self::$currentTenant; - } - - // create for single tinant - if (! Pluf::f('multitenant', false)) { - $tenant = new Tenant(); - $tenant->setFromFormData(Pluf::f('multitenant_default', array( - 'level' => 10, - 'title' => 'Tenant title', - 'description' => 'Default tenant in single mode', - 'domain' => 'pluf.ir', - 'subdomain' => 'www', - 'validate' => 1 - ))); - $tenant->id = 0; - return self::$currentTenant = $tenant; - } - - // fetch from request - $request = Request::getCurrent(); - if (isset($request)) { - return self::$currentTenant = $request->tenant; - } - return null; - } - - /** - * Gets tenant storage path - * - * @return string - */ - public static function storagePath() - { - return self::getCurrent()->getStoragePath(); - } - - /** - * Gets tenant storage path - * - * @return string path of the storage - */ - public function getStoragePath() - { - if ($this->isAnonymous()) { - return Pluf::f('upload_path'); - } - return Pluf::f('upload_path') . '/' . $this->id; - } -} diff --git a/src/Pluf/module.json b/src/Pluf/module.json deleted file mode 100644 index b819011b..00000000 --- a/src/Pluf/module.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "Pluf", - "version": "4.0.0", - "title": "Pluf core module", - "model": [ - "\\Pluf\\Pluf\\Session", - "\\Pluf\\Pluf\\Search\\Word", - "\\Pluf\\Pluf\\Search\\Occ", - "\\Pluf\\Pluf\\Search\\Stats", - "\\Pluf\\Pluf\\Queue", - "\\Pluf\\Pluf\\Tenant" - ] -} \ No newline at end of file diff --git a/src/Precondition.php b/src/Precondition.php deleted file mode 100755 index e2685393..00000000 --- a/src/Precondition.php +++ /dev/null @@ -1,72 +0,0 @@ -. - */ -namespace Pluf; - -/** - * Global system preconditions - * - * @author maso - */ -use Pluf\HTTP\Error404; -use Pluf\HTTP\Response; - -/** - * - * @deprecated use processing model - * @author maso - * - */ -class Precondition -{ - - /** - * Requires SSL to access the view. - * - * It will redirect the user to the same URL but over SSL if the - * user is not using SSL, if POST request, the data are lost, so - * handle it with care. - * - * @param - * \Pluf\HTTP\Request - * @return mixed - */ - static public function sslRequired($request) - { - if (empty($_SERVER['HTTPS']) or $_SERVER['HTTPS'] == 'off') { - return new Response\Redirect('https://' . $request->http_host . $request->uri); - } - return true; - } - - /** - * Checks if given name is matched with patterin [a-zA-Z_][0-9a-zA-Z_]* - * - * Throws bad request exception if given name does not match. - * - * @param string $name - * @throws Error404 - */ - public static function assertKeyIsValid($name) - { - if (preg_match('/^[a-zA-Z_][0-9a-zA-Z_]*$/', $name) !== 1) { - throw new Error404('Invalid parameter: <' . $name . '>'); - } - } -} \ No newline at end of file diff --git a/src/Process/Http/AccessLog.php b/src/Process/Http/AccessLog.php new file mode 100644 index 00000000..846ea3d4 --- /dev/null +++ b/src/Process/Http/AccessLog.php @@ -0,0 +1,113 @@ + + * Copyright (C) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +namespace Pluf\Process\Http; + +use Pluf\Scion\UnitTrackerInterface; +use Psr\Http\Message\RequestInterface; +use Psr\Log\LoggerInterface; +use Psr\Http\Message\ServerRequestInterface; + +/** + * Logs all HTTP accesses + * + * The access log dose not handle exception. Be sure that the process flow handle the + * exceptions and convert them into a valid respons. + * + * The response of the next unit must be a http response too + * + * @author Mostafa Barmshory (mostafa.barmshory@gmail.com) + */ +class AccessLog +{ + + /** + * following parameters may be used into the message format. + * + * - {Remote} – Remote host (client IP address) + * - {UserId} – User identity, or dash, if none (often not used) + * - {UserName} – Username, via HTTP authentication, or dash if not used + * - {Timestamp} – Timestamp of when Apache received the HTTP request + * - {Request} – The actual request itself from the client + * - {Status} – The status code Apache returns in response to the request + * - {RequestSize} – The size of the request in bytes. + * - {Referrer} – Referrer header, or dash if not used (In other words, did they click a URL on another site to come to your site) + * - {UserAgent} – User agent (contains information about the requester’s browser/OS/etc) + */ + private string $format = "{Remote} {UserId} {UserName} {Timestamp} \"{Request}\" %>{Status} {RequestSize} \"{Referrer}\" \"{UserAgent}\""; + + public function __invoke(RequestInterface $request, UnitTrackerInterface $unitTracker, LoggerInterface $logger, $user = null) + { + $result = $unitTracker->next(); + $logger->info($this->format, [ + "Remote" => self::getIpAddress($request), + "User" => $user, + "Timestamp" => time(), + "Request" => $request, + "Status" => $result->getStatusCode(), + "RequestSize" => $request->getHeader("Content-Length"), + "Referrer" => $request->getHeader("Referrer"), + "UserAgent" => $request->getHeader("User-Agent") + ]); + return $result; + } + + /** + * Gets parameter form the incoming request + * + * @param RequestInterface $request + * @return string + */ + public static function getIpAddress(RequestInterface $request): string + { + if (! ($request instanceof ServerRequestInterface)) { + return "-"; + } + + $server = $request->getServerParams(); + + // Check for shared Internet/ISP IP + if (array_key_exists('HTTP_CLIENT_IP', $server)) { + return $server['HTTP_CLIENT_IP']; + } + + // Check for IP addresses passing through proxies + if (array_key_exists('HTTP_X_FORWARDED_FOR', $server)) { + return $server['HTTP_X_FORWARDED_FOR']; + } + + if (array_key_exists('HTTP_X_FORWARDED', $server)) { + return $server['HTTP_X_FORWARDED']; + } + + if (array_key_exists('HTTP_X_CLUSTER_CLIENT_IP', $server)) { + return $server['HTTP_X_CLUSTER_CLIENT_IP']; + } + + if (array_key_exists('HTTP_FORWARDED_FOR', $server)) { + return $server['HTTP_FORWARDED_FOR']; + } + + if (array_key_exists('HTTP_FORWARDED', $server)) { + return $server['HTTP_FORWARDED']; + } + + // Return unreliable IP address since all else failed + return $server['REMOTE_ADDR']; + } +} diff --git a/src/Process/Http/Exception/InvalidBodyContentException.php b/src/Process/Http/Exception/InvalidBodyContentException.php new file mode 100644 index 00000000..f4689d03 --- /dev/null +++ b/src/Process/Http/Exception/InvalidBodyContentException.php @@ -0,0 +1,7 @@ + + * Copyright (C) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +namespace Pluf\Process\Http; + +use Pluf\Scion\UnitTrackerInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Exception; + +/** + * Converts a file to a http response + * + * + * + * $fileToResponse = new FileToHttpResponse(); + * ... + * $response = $fileToResponse($request, $response, $unitTracker); + * + * @author maso + * @author hadi + */ +class FileToResponse +{ + + /** + * Defines response cache police + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control + * @var string + */ + private string $cacheControl; + + /** + * Mimetype of the response + * + * The mimpeTypes is a file whre list of all mimetype and extensions are saved. If the + * path is not set of find, then the file extensions are used directly. + * + * @var string + */ + private string $mimeTypes; + + /** + * Creates new instance of the process + * + * It is possible to change process options. All options passed as a parameter. + * + * @param string $cacheControl + * sets the cache policy + */ + public function __construct(string $cacheControl = 'public,immutable', string $mimeTypes = '/etc/mime.types') + { + $this->cacheControl = $cacheControl; + $this->mimeTypes = $mimeTypes; + } + + /** + * Convert file to a reponse + * + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @param UnitTrackerInterface $processTracker + * @throws Exception + * @return \Psr\Http\Message\ResponseInterface + */ + function __invoke(ServerRequestInterface $request, ResponseInterface $response, UnitTrackerInterface $unitTracker) + { + $filePath = $unitTracker->next(); + $fileName = basename($filePath); + + if (! file_exists($filePath)) { + throw new Exception("File not found: $filePath"); + } + + // default action is to send the entire file + $byteOffset = 0; + $byteLength = $fileSize = filesize($filePath); + $fileName = self::cleanFileName($fileName); + + // remove headers hat might unnecessarily clutter up the output + $server = $request->getServerParams(); + + $match = []; + if (isset($server['HTTP_RANGE']) && preg_match('%bytes=(\d+)-(\d+)?%i', $server['HTTP_RANGE'], $match)) { + $byteOffset = (int) $match[1]; + + if (isset($match[2])) { + $finishBytes = (int) $match[2]; + $byteLength = $finishBytes + 1; + } else { + $finishBytes = $fileSize - 1; + } + $response = $response->withStatus(206, 'Partial Content')->withHeader('Content-Range', "bytes {$byteOffset}-{$finishBytes}/{$fileSize}"); + } + + $byteRange = $byteLength - $byteOffset; + + $bufferSize = 512 * 16; + $bytePool = $byteRange; + + if (! $fh = fopen($filePath, 'r')) { + throw new Exception("Could not get filehandler for reading: $filePath"); + } + + if (fseek($fh, $byteOffset, SEEK_SET) == - 1) { + throw new Exception("Could not seek to offset $byteOffset in file: $filePath"); + } + + while ($bytePool > 0) { + $chunkSizeRequested = min($bufferSize, $bytePool); + $buffer = fread($fh, $chunkSizeRequested); + $chunkSizeActual = strlen($buffer); + if ($chunkSizeActual == 0) { + throw new \Exception("Chunksize became 0"); + } + $bytePool -= $chunkSizeActual; + $response->getBody()->write($buffer); + } + + return $response->withHeader('Cache-Control', $this->cacheControl) + ->withoutHeader('Pragma') + ->withHeader('Content-Type', $this->getMimeType($fileName)) + ->withHeader('Accept-Ranges', 'bytes') + ->withHeader('Content-Disposition', "attachment; filename=\"{$fileName}\"") + ->withHeader('Content-Length', $byteRange); + } + + private static function cleanFileName($fileName) + { + // clean up filename + $invalidChars = array( + '<', + '>', + '?', + '"', + ':', + '|', + '\\', + '/', + '*', + '&' + ); + $fileName = str_replace($invalidChars, '', $fileName); + // normalize to prevent utf8 problems + // $fileName = preg_replace('/\p{Mn}/u', '', Normalizer::normalize($fileName, Normalizer::FORM_KD)); + return $fileName; + } + + /** + * Find the mime type of a file. + * + * Use /etc/mime.types to find the type. + * + * @param + * string Filename/Filepath + * @param + * array Mime type found or 'application/octet-stream', basename, + * extension + */ + public function getMimeType($file) + { + static $mimes = null; + $info = pathinfo($file); + if (isset($info['extension'])) { + // load mimes + if ($mimes == null) { + $mimes = array(); + $filecontent = @file_get_contents($this->mimeTypes); + if ($filecontent !== false) { + $mimes = preg_split("/\015\012|\015|\012/", $filecontent); + } + } + foreach ($mimes as $mime) { + if ('#' != substr($mime, 0, 1)) { + $elts = preg_split('/ |\t/', $mime, - 1, PREG_SPLIT_NO_EMPTY); + if (in_array($info['extension'], $elts)) { + return $elts[0]; + } + } + } + } + return 'application/octet-stream'; + } +} diff --git a/src/Process/Http/IfMethodIs.php b/src/Process/Http/IfMethodIs.php new file mode 100644 index 00000000..85c38e79 --- /dev/null +++ b/src/Process/Http/IfMethodIs.php @@ -0,0 +1,43 @@ + + * Copyright (C) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +namespace Pluf\Process\Http; + +use Pluf\Scion\UnitTrackerInterface; +use Psr\Http\Message\RequestInterface; + +class IfMethodIs +{ + + private string $method; + + public function __construct(string $method) + { + $this->method = $method; + } + + public function __invoke(RequestInterface $request, UnitTrackerInterface $unitTracker) + { + if ($request->getMethod() == $this->method) { + return $unitTracker->next(); + } + // Jump to the end of unit + return $unitTracker->jump(); + } +} + diff --git a/src/LoggerAppender/Console.php b/src/Process/Http/IfMethodIsDelete.php similarity index 62% rename from src/LoggerAppender/Console.php rename to src/Process/Http/IfMethodIsDelete.php index d8500eb9..737355b3 100644 --- a/src/LoggerAppender/Console.php +++ b/src/Process/Http/IfMethodIsDelete.php @@ -1,7 +1,7 @@ + * Copyright (C) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -14,17 +14,16 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * along with this program. If not, see . */ -namespace Pluf\LoggerAppender; +namespace Pluf\Process\Http; - -class Console implements \Pluf\LoggerAppender +class IfMethodIsDelete extends IfMethodIs { - public function write($message): void + public function __construct() { - print($message . PHP_EOL); + parent::__construct("DELETE"); } } diff --git a/src/Processors/ItemBinaryDownload.php b/src/Process/Http/IfMethodIsGet.php similarity index 63% rename from src/Processors/ItemBinaryDownload.php rename to src/Process/Http/IfMethodIsGet.php index df16fb9b..51cd605c 100644 --- a/src/Processors/ItemBinaryDownload.php +++ b/src/Process/Http/IfMethodIsGet.php @@ -1,7 +1,7 @@ + * Copyright (C) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -14,11 +14,15 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * along with this program. If not, see . */ -namespace Pluf\Processors; +namespace Pluf\Process\Http; -class ItemBinaryDownload +class IfMethodIsGet extends IfMethodIs { -} + public function __construct() + { + parent::__construct("GET"); + } +} diff --git a/src/Template/Tag/RmediaUrl.php b/src/Process/Http/IfMethodIsPost.php old mode 100755 new mode 100644 similarity index 61% rename from src/Template/Tag/RmediaUrl.php rename to src/Process/Http/IfMethodIsPost.php index dd8931a5..c6a124d0 --- a/src/Template/Tag/RmediaUrl.php +++ b/src/Process/Http/IfMethodIsPost.php @@ -1,8 +1,7 @@ + * Copyright (C) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,16 +14,15 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * along with this program. If not, see . */ -namespace Pluf\Template\Tag; +namespace Pluf\Process\Http; -class RmediaUrl extends \Pluf\Template\Tag +class IfMethodIsPost extends IfMethodIs { - function start($var, $file = '') + public function __construct() { - $this->context->set($var, MediaUrl::url($file)); + parent::__construct("POST"); } } - diff --git a/src/LoggerAppender.php b/src/Process/Http/IfMethodIsPut.php similarity index 63% rename from src/LoggerAppender.php rename to src/Process/Http/IfMethodIsPut.php index 994b7cd4..4ea2d768 100644 --- a/src/LoggerAppender.php +++ b/src/Process/Http/IfMethodIsPut.php @@ -1,7 +1,7 @@ + * Copyright (C) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -14,13 +14,16 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * along with this program. If not, see . */ -namespace Pluf; +namespace Pluf\Process\Http; -interface LoggerAppender +class IfMethodIsPut extends IfMethodIs { - public function write(string $message): void; + public function __construct() + { + parent::__construct("PUT"); + } } diff --git a/src/Process/Http/IfPathAndMethodIs.php b/src/Process/Http/IfPathAndMethodIs.php new file mode 100644 index 00000000..8256778c --- /dev/null +++ b/src/Process/Http/IfPathAndMethodIs.php @@ -0,0 +1,61 @@ + + * Copyright (C) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +namespace Pluf\Process\Http; + +use Pluf\Scion\UnitTrackerInterface; +use Psr\Http\Message\RequestInterface; + +class IfPathAndMethodIs +{ + + private string $regex; + + private array $methods; + + private bool $removePrefix = true; + + public function __construct(string $regex, array $methods = [ + 'GET', + 'POST', + 'DELETE' + ], bool $removePrefix = true) + { + $this->regex = $regex; + $this->methods = $methods; + $this->removePrefix = $removePrefix; + } + + public function __invoke(RequestInterface $request, UnitTrackerInterface $unitTracker) + { + $uri = $request->getUri(); + $requestPath = $uri->getPath(); + $method = $request->getMethod(); + $match = []; + if (! in_array($method, $this->methods) || ! preg_match($this->regex, $requestPath, $match)) { + return $unitTracker->jump(); + } + if ($this->removePrefix) { + $match = array_merge($match, [ + 'request' => $request->withUri($uri->withPath(substr($requestPath, strlen($match[0])))) + ]); + } + return $unitTracker->next($match); + } +} + diff --git a/src/Process/Http/IfPathIs.php b/src/Process/Http/IfPathIs.php new file mode 100644 index 00000000..ab8c24d0 --- /dev/null +++ b/src/Process/Http/IfPathIs.php @@ -0,0 +1,64 @@ + + * Copyright (C) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +namespace Pluf\Process\Http; + +use Pluf\Scion\UnitTrackerInterface; +use Psr\Http\Message\RequestInterface; + +/** + * Checks if the request path match with the given one. + * + * @author maso + */ +class IfPathIs +{ + + private string $regex; + + private bool $removePrefix = true; + + /** + * Create new instance of the process + * + * @param string $regex to match with request path + * @param bool $removePrefix should remove the pattern from the path + */ + public function __construct(string $regex, bool $removePrefix = true) + { + $this->regex = $regex; + $this->removePrefix = $removePrefix; + } + + public function __invoke(RequestInterface $request, UnitTrackerInterface $unitTracker) + { + $uri = $request->getUri(); + $requestPath = $uri->getPath(); + $match = []; + if (! preg_match($this->regex, $requestPath, $match)) { + return $unitTracker->jump(); + } + if ($this->removePrefix) { + $match = array_merge($match, [ + 'request' => $request->withUri($uri->withPath(substr($requestPath, strlen($match[0])))) + ]); + } + return $unitTracker->next($match); + } +} + diff --git a/src/Process/Http/RequestBodyParser.php b/src/Process/Http/RequestBodyParser.php new file mode 100644 index 00000000..92d17aac --- /dev/null +++ b/src/Process/Http/RequestBodyParser.php @@ -0,0 +1,93 @@ + + * Copyright (C) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +namespace Pluf\Process\Http; + +use Pluf\Scion\UnitTrackerInterface; +use Pluf\Scion\Process\Http\Exception\InvalidBodyContentException; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ServerRequestInterface; + +/** + * Pars request body and convert it into a PHP Object + * + * + * @author maso (mostafa.barmshory@gmail.com) + * + */ +class RequestBodyParser +{ + + /** + * Read the content value and convert it into a parsed one. + * + * For example reads the application/json body and set a equivalent map to the + * request. + * + * @param RequestInterface $request + * to pars + * @param UnitTrackerInterface $unitTracker + * tracker to manage the processing flow + * @throws InvalidBodyContentException if the body mistmach with the content type + * @return mixed the result of chain + */ + public function __invoke(ServerRequestInterface $request, UnitTrackerInterface $unitTracker) + { + // Decode body + $method = $request->getMethod(); + if ($method === 'POST') { + $contentTypes = $request->getHeader('Content-Type') ?? []; + + $parsedContentType = ''; + foreach ($contentTypes as $contentType) { + $fragments = explode(';', $contentType); + $parsedContentType = current($fragments); + } + + $contentTypesWithParsedBodies = [ + 'application/json', + 'application/xml', + 'application/yml' + ]; + + if (in_array($parsedContentType, $contentTypesWithParsedBodies)) { + $bodyString = $request->getBody()->getContents(); + switch ($parsedContentType) { + case 'application/json': + $bodyJson = json_decode($bodyString); + if (empty($bodyJson)) { + throw new InvalidBodyContentException("Fail to decode body as " . $parsedContentType); + } + $request = $request->withParsedBody($bodyJson); + break; + case 'application/xml': + case 'application/yml': + default: + throw new InvalidBodyContentException("Not supported content type: " . $parsedContentType); + } + } + + var_dump($request->getParsedBody()); + } + // run pipeline + return $unitTracker->next([ + 'request' => $request + ]); + } +} + diff --git a/src/Template/Tag/TenantTag.php b/src/Process/Http/ResourceNotFoundException.php similarity index 58% rename from src/Template/Tag/TenantTag.php rename to src/Process/Http/ResourceNotFoundException.php index de521992..b85a5c3d 100644 --- a/src/Template/Tag/TenantTag.php +++ b/src/Process/Http/ResourceNotFoundException.php @@ -1,7 +1,7 @@ + * Copyright (C) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -14,22 +14,23 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * along with this program. If not, see . */ -namespace Pluf\Template\Tag; +namespace Pluf\Process\Http; /** - * Display a tenant attribute. + * Throw exception and break to path + * + * + * @author maso (mostafa.barmshory@gmail.com) + * */ -use Pluf\Pluf\Tenant; - -class TenantTag extends \Pluf\Template\Tag +class ResourceNotFoundException { - function start($attr = 'title') + public function __invoke() { - $tenant = Tenant::current(); - echo $tenant->$attr; + throw new \Exception("Resouce not found"); } } diff --git a/src/Process/Http/ResponseBodyEncoder.php b/src/Process/Http/ResponseBodyEncoder.php new file mode 100644 index 00000000..08b4ee01 --- /dev/null +++ b/src/Process/Http/ResponseBodyEncoder.php @@ -0,0 +1,48 @@ + + * Copyright (C) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +namespace Pluf\Process\Http; + +use Pluf\Scion\UnitTrackerInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Throwable; +use Psr\Http\Message\StreamFactoryInterface; +use Psr\Log\LoggerInterface; + +class ResponseBodyEncoder +{ + + // + public function __invoke(ServerRequestInterface $request, ResponseInterface $response, StreamFactoryInterface $streamFactory, LoggerInterface $logger, UnitTrackerInterface $unitTracker) + { + $result = ""; + try { + $result = $unitTracker->next(); + } catch (Throwable $t) { + $response = $response->withStatus(500); + $result = [ + 'message' => $t->getMessage() + ]; + } + + $resultEncode = json_encode($result); + return $response->withBody($streamFactory->createStream($resultEncode)); + } +} + diff --git a/src/Processor.php b/src/Processor.php deleted file mode 100644 index 40b7d593..00000000 --- a/src/Processor.php +++ /dev/null @@ -1,50 +0,0 @@ -. - */ -namespace Pluf; - -use Pluf\HTTP\Request; -use Pluf\HTTP\Response; - -/** - * Pluf general Processor - * - * @author maso - * - */ -interface Processor -{ - - /** - * Process the request - * - * @param Request $request - * @return boolean false if ther is no problem otherwize ther is an error - */ - public function request(Request &$request); - - /** - * Process the response - * - * @param Request $request - * @param Response $response - */ - public function response(Request $request, Response $response): Response; -} - diff --git a/src/ProcessorAdaptor.php b/src/ProcessorAdaptor.php deleted file mode 100644 index d1eacc12..00000000 --- a/src/ProcessorAdaptor.php +++ /dev/null @@ -1,54 +0,0 @@ -. - */ -namespace Pluf; - -use Pluf\HTTP\Request; -use Pluf\HTTP\Response; - -/** - * Pluf general Processor - * - * @author maso - * - */ -class ProcessorAdaptor implements Processor -{ - - /** - * Process the request - * - * @param Request $request - * @return boolean false if ther is no problem otherwize ther is an error - */ - public function request(Request &$request) - {} - - /** - * Process the response - * - * @param Request $request - * @param Response $response - */ - public function response(Request $request, Response $response): Response - { - return $response; - } -} - diff --git a/src/Processors/DispatcherSignals.php b/src/Processors/DispatcherSignals.php deleted file mode 100644 index de575f25..00000000 --- a/src/Processors/DispatcherSignals.php +++ /dev/null @@ -1,56 +0,0 @@ - $request, - * 'response' => $response) - */ - Signal::send('Pluf_Dispatcher::postDispatch', 'Pluf_Dispatcher', [ - 'request' => $request, - 'response' => $response - ]); - return $response; - } -} - diff --git a/src/Processors/ExceptionProcessor.php b/src/Processors/ExceptionProcessor.php deleted file mode 100644 index 661f47d1..00000000 --- a/src/Processors/ExceptionProcessor.php +++ /dev/null @@ -1,33 +0,0 @@ -isOk()) { - return $response; - } - $body = $response->getBody(); - // XXX: maso, 2020: convert body exception into a serializable object - // XXX: maso, 2020: check response header (status code) - return $response->setBody($body); - } -} - diff --git a/src/Processors/GraphqlRender.php b/src/Processors/GraphqlRender.php deleted file mode 100644 index 8d5683bd..00000000 --- a/src/Processors/GraphqlRender.php +++ /dev/null @@ -1,36 +0,0 @@ -REQUEST)) { - $gl = new Pluf_Graphql(); - $newBody = $gl->render($response->getBody(), $request->REQUEST['graphql']); - $response->setBody($newBody); - } - return $response; - } -} - diff --git a/src/Processors/HttpResponseEncoder.php b/src/Processors/HttpResponseEncoder.php deleted file mode 100644 index b62eeb0e..00000000 --- a/src/Processors/HttpResponseEncoder.php +++ /dev/null @@ -1,46 +0,0 @@ -negotiateMimeType($contentType, $contentType[0]); - if ($mime === false) { - throw new \Pluf\Exception("You don't want any of the content types I have to offer\n"); - } - switch ($mime) { - case 'application/json': - $response = new Response\Json($response); - break; - case 'text/plain': - $response = new Response\PlainText($response); - break; - } - - return $response; - } -} - diff --git a/src/Processors/ItemBinaryUpload.php b/src/Processors/ItemBinaryUpload.php deleted file mode 100644 index 6e1443bf..00000000 --- a/src/Processors/ItemBinaryUpload.php +++ /dev/null @@ -1,35 +0,0 @@ -. - */ -namespace Pluf\Processors; - -use Pluf\Processor; -use Pluf\HTTP\Request; -use Pluf\HTTP\Response; - -class ItemBinaryUpload implements Processor -{ - - public function request(Request $request) - {} - - public function response(Request $request, Response $response) - {} -} - - diff --git a/src/Processors/ItemCollectionCreate.php b/src/Processors/ItemCollectionCreate.php deleted file mode 100644 index abf1ae3e..00000000 --- a/src/Processors/ItemCollectionCreate.php +++ /dev/null @@ -1,34 +0,0 @@ -. - */ -namespace Pluf\Processors; - -use Pluf\Processor; -use Pluf\HTTP\Request; -use Pluf\HTTP\Response; - -class ItemCollectionCreate implements Processor -{ - - public function request(Request $request) - {} - - public function response(Request $request, Response $response) - {} -} - diff --git a/src/Processors/ItemCollectionDelete.php b/src/Processors/ItemCollectionDelete.php deleted file mode 100644 index 44787bb0..00000000 --- a/src/Processors/ItemCollectionDelete.php +++ /dev/null @@ -1,34 +0,0 @@ -. - */ -namespace Pluf\Processors; - -use Pluf\Processor; -use Pluf\HTTP\Request; -use Pluf\HTTP\Response; - -class ItemCollectionDelete implements Processor -{ - - public function request(Request $request) - {} - - public function response(Request $request, Response $response) - {} -} - diff --git a/src/Processors/ItemCollectionRead.php b/src/Processors/ItemCollectionRead.php deleted file mode 100644 index 656ac0fe..00000000 --- a/src/Processors/ItemCollectionRead.php +++ /dev/null @@ -1,34 +0,0 @@ -. - */ -namespace Pluf\Processors; - -use Pluf\Processor; -use Pluf\HTTP\Request; -use Pluf\HTTP\Response; - -class ItemCollectionRead implements Processor -{ - - public function request(Request $request) - {} - - public function response(Request $request, Response $response): Response - {} -} - diff --git a/src/Processors/ItemCollectionUpdate.php b/src/Processors/ItemCollectionUpdate.php deleted file mode 100644 index 097fe146..00000000 --- a/src/Processors/ItemCollectionUpdate.php +++ /dev/null @@ -1,34 +0,0 @@ -. - */ -namespace Pluf\Processors; - -use Pluf\Processor; -use Pluf\HTTP\Request; -use Pluf\HTTP\Response; - -class ItemCollectionUpdate implements Processor -{ - - public function request(Request $request) - {} - - public function response(Request $request, Response $response): Response - {} -} - diff --git a/src/Processors/ItemCreate.php b/src/Processors/ItemCreate.php deleted file mode 100644 index 490c5d82..00000000 --- a/src/Processors/ItemCreate.php +++ /dev/null @@ -1,87 +0,0 @@ -. - */ -namespace Pluf\Processors; - -use Pluf\ObjectMapper; -use Pluf\ObjectValidator; -use Pluf\ProcessorAdaptor; -use Pluf\HTTP\Error403; -use Pluf\HTTP\Error500; -use Pluf\HTTP\Request; -use Pluf\HTTP\Response; -use Pluf; - -/** - * Creates new item - * - * @author maso - * - */ -class ItemCreate extends ProcessorAdaptor -{ - - /** - * - * {@inheritdoc} - * @see \Pluf\Processor::request() - */ - public function request(Request $request) - { - $modelName = $this->getModelName($request); - $mapper = ObjectMapper::getInstance($request); - if (! $mapper->hasMore()) { - throw new Error403('No item in request to update'); - } - $item = $mapper->mapNext($modelName); - ObjectValidator::getInstance()->check($item); - $item = Pluf::getDataRepository($modelName)->create($item); - $request->item = $item; - } - - /** - * Put created item into the request - * - * {@inheritdoc} - * @see \Pluf\Processor::response() - */ - public function response(Request $request, Response $response): Response - { - if ($response->isOk()) { - if (! $response->hasBody()) { - $response->setBody($request->item); - } - } - return $response; - } - - /* - * Fetchs model name from inputs - * - * 1. from params - */ - protected function getModelName(Request $request): string - { - if (isset($request->params['model'])) { - return $request->params['model']; - } - // TODO: maso, 2020: search in match and request form model name - throw new Error500('The model class was not provided in the parameters.'); - } -} - diff --git a/src/Processors/ItemDelete.php b/src/Processors/ItemDelete.php deleted file mode 100644 index b16c991f..00000000 --- a/src/Processors/ItemDelete.php +++ /dev/null @@ -1,52 +0,0 @@ -. - */ -namespace Pluf\Processors; - -use Pluf\ProcessorAdaptor; -use Pluf\HTTP\Error500; -use Pluf\HTTP\Request; - -/** - * Deletes Model Item - * - * Required processors: - * - * - ItemRead - * - * @author maso - * - */ -class ItemDelete extends ProcessorAdaptor -{ - - /** - * Deletes item (loaded in request) from repository - * - * {@inheritdoc} - * @see \Pluf\Processor::request() - */ - public function request(Request $request) - { - if (! isset($request->item)) { - throw new Error500("Item is not loaded. Check the ItemRead processor is placed in stack."); - } - $request->item->delete(); - } -} - diff --git a/src/Processors/ItemRead.php b/src/Processors/ItemRead.php deleted file mode 100644 index 15b80ba2..00000000 --- a/src/Processors/ItemRead.php +++ /dev/null @@ -1,109 +0,0 @@ -. - */ -namespace Pluf\Processors; - -use Pluf\ProcessorAdaptor; -use Pluf\Data\Query; -use Pluf\HTTP\Error404; -use Pluf\HTTP\Error500; -use Pluf\HTTP\Request; -use Pluf\HTTP\Response; -use Pluf; - -class ItemRead extends ProcessorAdaptor -{ - - /** - * Reqd the item from model repositories and put it into the request. - * - * @param Request $request - */ - public function request(Request $request) - { - // Set the default - $modelName = $this->getModelName($request); - $modelId = $this->getModelId($request); - ; - $item = $this->getObjectOr404($modelName, $modelId); - $request->item = $item; - } - - /** - * If there is no body, the loaded item will be set as the response body. - * - * @param Request $request - * @param Response $response - * @return Response - */ - public function response(Request $request, Response $response): Response - { - // If the response is not OK we do anything - if ($response->isOk()) { - // If the body is not set we use the item as body - if (! $response->isBodySet()) { - $response->setBody($request->item); - } - } - - return $response; - } - - /* - * Fetchs model name from inputs - * - * 1. from params - */ - protected function getModelName(Request $request): string - { - if (isset($request->params['model'])) { - return $request->params['model']; - } - // TODO: maso, 2020: search in match and request form model name - throw new Error500('The model class was not provided in the parameters.'); - } - - /* - * Gets model id from the request - */ - protected function getModelId(Request $request) - { - return $request->match['modelId']; - } - - /* - * Finds item with the $modelId - */ - protected function getObjectOr404(string $modelName, $modelId) - { - $items = Pluf::getDataRepository($modelName)->get(new Query([ - 'filter' => [ - [ - 'id', - '=', - $modelId - ] - ] - ])); - if (sizeof($items) == 0) { - throw new Error404('Request resource with ID:' . $modelId . ' not found'); - } - return $items[0]; - } -} - diff --git a/src/Processors/ItemUpdate.php b/src/Processors/ItemUpdate.php deleted file mode 100644 index bd32fe27..00000000 --- a/src/Processors/ItemUpdate.php +++ /dev/null @@ -1,46 +0,0 @@ -. - */ -namespace Pluf\Processors; - -use Pluf\ObjectMapper; -use Pluf\ObjectValidator; -use Pluf\ProcessorAdaptor; -use Pluf\HTTP\Error403; -use Pluf\HTTP\Request; -use Pluf; - -class ItemUpdate extends ProcessorAdaptor -{ - - public function request(Request $request) - { - $item = $request->item; - $modelName = get_class($item); - $mapper = ObjectMapper::getInstance($request); - if (! $mapper->hasMore()) { - throw new Error403('No item in request to update'); - } - $newItem = $mapper->mapNext($modelName); - $newItem->id = $item->id; - ObjectValidator::getInstance()->check($newItem); - $item = Pluf::getDataRepository(get_class($item))->update($newItem); - $request->item = $item; - } -} - diff --git a/src/Processors/ReadOnly.php b/src/Processors/ReadOnly.php deleted file mode 100755 index ab4d8de0..00000000 --- a/src/Processors/ReadOnly.php +++ /dev/null @@ -1,123 +0,0 @@ -. - */ -namespace Pluf\Processors; - -use Pluf\Processor; -use Pluf\HTTP\Request; -use Pluf\HTTP\Response; -use Pluf; - -/** - * Readonly middleware. - * - * It is intercepting all the POST requests with a message telling - * that the website is in read only mode. - * - * Optionally, a message at the top of the page is added to inform - * that the website is in read only mode. - * - * Add the middleware at the top of your middleware list and - * optionally add a message to be displayed in your configuration - * file. - * - * Example: - * - *
      - * $cfg['middleware_classes'] = array(
      - * 'Pluf_Middleware_ReadOnly',
      - * 'Pluf_Middleware_Csrf',
      - * 'Pluf_Middleware_Session',
      - * 'Pluf_Middleware_Translation',
      - * );
      - * $cfg['read_only_mode_message'] = 'The server is in read only mode the '
      - * .'time to be migrated on another host.'
      - * .'Thank you for your patience.';
      - * 
      - * - * You can put HTML in your message. - */ -class ReadOnly implements Processor -{ - - /** - * Process the request. - * - * @param - * Request The request - * @return bool false - */ - function request(Request &$request) - { - if ($request->method == 'POST') { - $res = new Response('Server in read only mode' . "\n\n" . 'We are upgrading the system to make it better for you, please try again later...', 'text/plain'); - $res->status_code = 503; - return $res; - } - return false; - } - - /** - * Process the response of a view. - * - * If configured, add the message to inform that the website is in - * read only mode. - * - * @param - * Request The request - * @param - * Response The response - * @return Response The response - */ - public function response(Request $request, Response $response): Response - { - if (! Pluf::f('read_only_mode_message', false)) { - return $response; - } - if (! in_array($response->status_code, array( - 200, - 201, - 202, - 203, - 204, - 205, - 206, - 404, - 501 - ))) { - return $response; - } - $ok = false; - $cts = array( - 'text/html', - 'application/xhtml+xml' - ); - foreach ($cts as $ct) { - if (false !== strripos($response->headers['Content-Type'], $ct)) { - $ok = true; - break; - } - } - if ($ok == false) { - return $response; - } - $message = Pluf::f('read_only_mode_message'); - $response->content = str_replace('', '
      ' . $message . '
      ', $response->content); - return $response; - } -} diff --git a/src/Processors/Session.php b/src/Processors/Session.php deleted file mode 100755 index 55187800..00000000 --- a/src/Processors/Session.php +++ /dev/null @@ -1,171 +0,0 @@ -. - */ -namespace Pluf\Processors; - -use Pluf\Exception; -use Pluf\Processor; -use Pluf\Signal; -use Pluf\Data\Query; -use Pluf\HTTP\Request; -use Pluf\HTTP\Response; -use Pluf; - -/** - * Allow a session object in the request. - */ -class Session implements Processor -{ - - /** - * Process the request. - * - * FIXME: We should logout everybody when the session table is emptied. - * - * @param - * Request The request - * @return bool false - */ - function request(Request &$request) - { - $repo = Pluf::getDataRepository([ - 'type' => 'model', - 'model' => '\Pluf_Session' - ]); - - $session = new Session(); - if (! isset($request->COOKIE[$session->cookie_name])) { - // No session is defined. We set empty session. - $request->session = $session; - if (isset($request->COOKIE[$request->session->test_cookie_name])) { - $request->session->test_cookie = $request->COOKIE[$request->session->test_cookie_name]; - } - return false; - } - try { - $data = self::_decodeData($request->COOKIE[$session->cookie_name]); - } catch (Exception $e) { - $request->session = $session; - if (isset($request->COOKIE[$request->session->test_cookie_name])) { - $request->session->test_cookie = $request->COOKIE[$request->session->test_cookie_name]; - } - return false; - } - if (isset($data['Pluf_Session_key'])) { - $found_session = $repo->getList(new Query([ - 'filter' => [ - [ - 'session_key', - $data['Pluf_Session_key'] - ] - ] - ])); - - if (isset($found_session[0])) { - $request->session = $found_session[0]; - } else { - $request->session = $session; - } - } else { - $request->session = $session; - } - // if ($set_lang and - // false == $request->session->getData('pluf_language', false)) { - // $request->session->setData('pluf_language', $set_lang); - // } - if (isset($request->COOKIE[$request->session->test_cookie_name])) { - $request->session->test_cookie = $request->COOKIE[$request->session->test_cookie_name]; - } - return false; - } - - /** - * Process the response of a view. - * - * If the session has been modified save it into the database. - * Add the session cookie to the response. - * - * @param - * Request The request - * @param - * Response The response - */ - function response(Request $request, Response $response): Response - { - if ($request->session->touched) { - if ($request->session->isAnonymous()) { - $request->session->create(); - } else { - $request->session->update(); - } - $data = array(); - $data['Pluf_Session_key'] = $request->session->session_key; - $response->cookies[$request->session->cookie_name] = self::_encodeData($data); - } - if ($request->session->set_test_cookie != false) { - $response->cookies[$request->session->test_cookie_name] = $request->session->test_cookie_value; - } - return $response; - } - - /** - * Encode the cookie data and create a check with the secret key. - * - * @param - * mixed Data to encode - * @return string Encoded data ready for the cookie - */ - public static function _encodeData($data) - { - if ('' == ($key = Pluf::f('secret_key'))) { - throw new Exception('Security error: "secret_key" is not set in the configuration file.'); - } - $data = serialize($data); - return base64_encode($data) . md5(base64_encode($data) . $key); - } - - /** - * Decode the data and check that the data have not been tampered. - * - * If the data have been tampered an exception is raised. - * - * @param - * string Encoded data - * @return mixed Decoded data - */ - public static function _decodeData($encoded_data) - { - $check = substr($encoded_data, - 32); - $base64_data = substr($encoded_data, 0, strlen($encoded_data) - 32); - if (md5($base64_data . Pluf::f('secret_key')) != $check) { - throw new Exception('The session data may have been tampered.'); - } - return unserialize(base64_decode($base64_data)); - } - - public static function processContext($signal, &$params) - { - $params['context'] = array_merge($params['context'], Pluf_Middleware_Session_ContextPreProcessor($params['request'])); - } -} - -// TODO: maso, 2020: move to module initials -Signal::connect('Pluf_Template_Context_Request::construct', array( - 'Pluf_Middleware_Session', - 'processContext' -)); \ No newline at end of file diff --git a/src/Processors/TenantProcessor.php b/src/Processors/TenantProcessor.php deleted file mode 100644 index 8ce2c886..00000000 --- a/src/Processors/TenantProcessor.php +++ /dev/null @@ -1,161 +0,0 @@ -. - */ -namespace Pluf\Processors; - -use Pluf\Exception; -use Pluf\Logger; -use Pluf\ProcessorAdaptor; -use Pluf\HTTP\Request; -use Pluf\HTTP\Response; -use Pluf\Pluf\Tenant; -use Pluf; -use Pluf\HTTP\Error404; - -/** - * - * @author maso - * - */ -class TenantProcessor extends ProcessorAdaptor -{ - - /** - * - * {@inheritdoc} - */ - public function request(Request &$request) - { - /* - * In single tenant mode we do not check anything - */ - if (! Pluf::getConfig('multitenant', false)) { - $request->tenant = Tenant::getCurrent(); - return; - } - - /* - * Try to figure out tenant from domain - */ - try { - $domain = $request->http_host; - $tenant = Tenant::byDomain($domain); - if ($tenant) { - $request->tenant = $tenant; - return; - } - } catch (Exception $e) { - Logger::debug('Fail to get tenant from domain address', $e); - } - - /* - * trye to get from subdomain - */ - try { - $subdomain = self::extract_subdomains($request->http_host); - $tenant = Tenant::bySubDomain($subdomain); - if ($tenant) { - $request->tenant = $tenant; - return; - } - } catch (Exception $e) { - Logger::debug('Fail to get tenant from domain address', $e); - } - - /* - * Fetchs tenant from header - */ - try { - if (array_key_exists('_PX_tenant', $request->HEADERS)) { - $tenant = new Tenant($request->HEADERS['_PX_tenant']); - if ($tenant) { - $request->tenant = $tenant; - return; - } - } - } catch (Exception $e) { - Logger::debug('Fail to get tenant from header', $e); - } - - throw new Error404('Tenant not found'); - } - - /** - * - * @param Request $request - * @param Response $response - * @return Response - */ - public function response(Request $request, Response $response): Response - { - if (! Pluf::getConfig('multitenant', false) || $response->isOk() || isset($request->tenant)) { - return $response; - } - - /* - * If no tenant found, then we redirect to the error url - */ - $redirectUrl = Pluf::getConfig('tenant_notfound_url', 'https://pluf.ir/wb/blog/page/how-config-notfound-tenant'); - return new Response\Redirect($redirectUrl, 302); - } - - /** - * **************************************************************************************************** - * Note: hadi, 1395: برای استخراج زیر دامنه یا زیردامنه‌ها از یک آدرس از دو متد زیر استفاده کرده‌ایم. - * خوبی این روش این است که برای پسوندهای چند بخشی مثل co.ir و مانند آن نیز تا حد قابل قبولی کار می‌کند. - * البته ایراداتی هم دارد. برای اطاعات بیشتر به پیوند زیر مراجعه شود: - * - * http://stackoverflow.com/a/12372310 - * - * پسوندهای قابل قبول برای دامنه‌های عمومی اینترنتی در پیوند زیر قابل مشاهده است: - * https://publicsuffix.org/list/ - * - * ***************************************************************************************************** - */ - - /** - * دامنه اصلی را از رشته داده شده استخراج می‌کند - * - * @param string $str - * @return string - */ - private static function extract_domain(string $str): string - { - $matches = array(); - if (preg_match("/(?P[a-z0-9][a-z0-9\-]{1,63}(\.[a-z\.]{2,6})?(:[0-9]+)?)$/i", $str, $matches)) { - return $matches['domain']; - } else { - return $str; - } - } - - /** - * زیر دامنه یا زیردامنه‌ها را از رشته داده شده استخراج می‌کند. - * - * @param string $str - * @return string - */ - private static function extract_subdomains(string $str): string - { - $dom = self::extract_domain($str); - - $subdomains = rtrim(strstr($str, $dom, true), '.'); - - return $subdomains; - } -} diff --git a/src/SQL.php b/src/SQL.php deleted file mode 100755 index d14b9324..00000000 --- a/src/SQL.php +++ /dev/null @@ -1,173 +0,0 @@ -. - */ -namespace Pluf; - -use Pluf; - -/** - * Generate the WHERE SQL clause in an easy and SQL proof way. - * - * @deprecated will remove from project - */ -class SQL -{ - - protected $db; - - protected $where = ''; - - public $ands = array(); - - /** - * Construct the constructor with a default condition. - */ - function __construct($base = '', $args = array()) - { - $this->db = Pluf::db(); - if (strlen($base) > 0) { - $this->Q($base, $args); - } - } - - /** - * Returns the where clause. - * - * @return string Where clause without the WHERE - */ - function gen() - { - return implode(' AND ', $this->ands); - } - - /** - * Add a condition. - * - * @param - * string String to 'interpolate' - * @param - * mixed String or array of parameters (array()) - */ - function Q($base, $args = array()) - { - if (! is_array($args)) { - $args = array( - $args - ); - } - $this->ands[] = vsprintf($base, $this->escapeParams($args)); - return $this; - } - - public function escapeParams(array $params) - { - $escaped = []; - foreach ($params as $param) { - if (is_array($param)) { - $escaped[] = implode(',', $this->escapeParams($param)); - } else { - if (! isset($param)) { - $escaped[] = 'NULL'; - } else if (is_string($param)) { - $escaped[] = $this->db->quote($param); - } else { - $escaped[] = $param; - } - } - } - return $escaped; - } - - /** - * Add another SQL as a AND. - * - * @param - * Pluf_SQL Other object to add to the current. - */ - function SAnd($sql) - { - return $this->SDef($sql); - } - - /** - * Add another SQL as a OR - * - * @param - * Pluf_SQL Other object to add to the current. - */ - function SOr($sql) - { - return $this->SDef($sql, 'OR'); - } - - /** - * Add another SQL to the current - * - * @param - * Pluf_SQL Other object to add to the current. - * @param - * string Type of addition - */ - function SDef($sql, $k = 'AND') - { - if (empty($this->ands)) { - $this->ands = $sql->ands; - } else { - $othersql = $sql->gen(); - $current = $this->gen(); - if (strlen($othersql)) { - $this->ands = array(); - $this->ands[] = '(' . $current . ') ' . $k . ' (' . $othersql . ')'; - } - } - return $this; - } - - /** - * Get keywords. - * - * Considering a query string, explode the query string in - * keywords given a defined delimiter. - * - * @param - * string Query string - * @param - * string delimiter (' ') - * @return array Array of keywords - */ - function keywords($string, $del = ' ') - { - $keys = array(); - $args = explode($del, $string); - foreach ($args as $arg) { - $arg = trim($arg); - if (strlen($arg) > 0) { - $keys[] = $arg; - } - } - return $keys; - } - - public static function cleanString(string $str): string - { - return str_replace('%', '%%', $str); - ; - } -} - diff --git a/src/Shortcuts.php b/src/Shortcuts.php deleted file mode 100755 index f547dca7..00000000 --- a/src/Shortcuts.php +++ /dev/null @@ -1,148 +0,0 @@ -. - */ -namespace Pluf; - -use Pluf\HTTP\Response; - -// Pluf_Shortcuts_RenderToResponse -// Pluf_Shortcuts_GetFormForUpdateModel -// Pluf_Shortcuts_GetFormForUpdateModel -// Pluf_Shortcuts_folderSize -// Pluf_Shortcuts_GetAssociationTableName -// Pluf_Shortcuts_GetListCount -// Pluf_Shortcuts_GetForeignKeyName -// Pluf_Shortcuts_GetForeignKeyName -class Shortcuts -{ - - public static function GetRequestParam($request, $id) - { - if (array_key_exists($id, $request->REQUEST)) { - return $request->REQUEST[$id]; - } - return null; - } - - /** - * Get an object by id or raise a 404 error. - * - * @param - * string Model - * @param - * int Id of the model to get - */ - public static function GetObjectOr404($object, $id) - { - $item = new $object($id); - if ((int) $id > 0 && $item->id == $id) { - return $item; - } - throw new \Pluf\HTTP\Error404("Object not found (" . $object . "," . $id . ")"); - } - - /** - * Render a template file and an array as a reponse. - * - * If a none null request object is given, the context used will - * automatically be a Pluf_Template_Context_Request context and thus - * the context will be populated using the - * 'template_context_processors' functions. - * - * @param - * string Template file name - * @param - * array Associative array for the context - * @param - * \Pluf\HTTP\Request Request object (null) - * @return \Pluf\HTTP\Response The response with the rendered template - */ - public static function RenderToResponse($tplfile, $params, $request = null) - { - $tmpl = new Template($tplfile); - if (is_null($request)) { - $context = new Template\Context($params); - } else { - $context = new Template\Context\Request($request, $params); - } - return new Response($tmpl->render($context)); - } - - /** - * Compute folder size - * - * @param string $dir - * @return number - */ - function folderSize($dir) - { - $count_size = 0; - $count = 0; - $dir_array = scandir($dir); - foreach ($dir_array as /* $key => */$filename) { - if ($filename != ".." && $filename != ".") { - if (is_dir($dir . "/" . $filename)) { - $new_foldersize = Pluf_Shortcuts_folderSize($dir . "/" . $filename); - $count_size = $count_size + $new_foldersize; - } else if (is_file($dir . "/" . $filename)) { - $count_size = $count_size + filesize($dir . "/" . $filename); - $count ++; - } - } - } - return $count_size; - } - - /** - * Returns column name for given model as foreign key in an association table. - * - * @param string $modelName - * name of model (of type \Pluf\Data\Model) - * @return string column name for given model as foreign key in an association table. - * @deprecated use Schema::getAssocField($model); - */ - public static function GetForeignKeyName($modelName) - { - return strtolower($modelName) . '_id'; - } - - /** - * Returns list count for given request. - * - * If count is not set in request or count is more than a threshold (50) returns a default value (50). - * - * @param \Pluf\HTTP\Request $request - * @return number - */ - public static function GetListCount($request) - { - $count = 50; - if (array_key_exists('_px_ps', $request->GET)) { - $count = $request->GET['_px_ps']; - if ($count == 0 || $count > 50) { - $count = 50; - } - } - return $count; - } -} - - - - diff --git a/src/Signal.php b/src/Signal.php deleted file mode 100755 index 67d79d8e..00000000 --- a/src/Signal.php +++ /dev/null @@ -1,78 +0,0 @@ -. - */ -namespace Pluf; - -/** - * Signal system. - */ -class Signal -{ - - /** - * Send a signal. - * - * @param - * string Signal to be sent. - * @param - * string Sender. - * @param - * array Parameters - * @return void - */ - public static function send($signal, $sender, &$params = array()) - { - if (! empty($GLOBALS['_PX_signal'][$signal])) { - foreach ($GLOBALS['_PX_signal'][$signal] as /* $key=> */$val) { - if ($val[2] === null or $sender == $val[2]) { - call_user_func_array(array( - $val[0], - $val[1] - ), array( - $signal, - &$params - )); - } - } - } - } - - /** - * Connect to a signal. - * - * @param - * string Name of the signal. - * @param - * array array('class', 'method') handling the signal. - * @param - * string Optional sender filtering. - */ - public static function connect($signal, $who, $sender = null) - { - if (! isset($GLOBALS['_PX_signal'][$signal])) { - $GLOBALS['_PX_signal'][$signal] = array(); - } - $GLOBALS['_PX_signal'][$signal][] = array( - $who[0], - $who[1], - $sender - ); - } -} - diff --git a/src/Template.php b/src/Template.php deleted file mode 100755 index d7a9240f..00000000 --- a/src/Template.php +++ /dev/null @@ -1,365 +0,0 @@ -. - */ -namespace Pluf; - -use Pluf\Template\Compiler; -use Pluf\Template\Context; -use Pluf\Template\SafeString; -use Pluf; - -/** - * Render a template file. - */ -class Template -{ - - public $tpl = ''; - - public $folders = array(); - - public $cache = ''; - - public $compiled_template = ''; - - public $template_content = ''; - - public $context = null; - - public $class = ''; - - /** - * Constructor. - * - * If the folder name is not provided, it will default to - * Pluf::f('template_folders') - * If the cache folder name is not provided, it will default to - * Pluf::f('tmp_folder') - * - * @param - * string Template name. - * @param - * string Template folder paths (null) - * @param - * string Cache folder name (null) - */ - function __construct($template, $folders = null, $cache = null) - { - $this->tpl = $template; - if (null == $folders) { - $this->folders = Pluf::f('template_folders'); - } else { - $this->folders = $folders; - } - if (null == $cache) { - $this->cache = Pluf::f('tmp_folder'); - } else { - $this->cache = $cache; - } - if (defined('IN_UNIT_TESTS')) { - if (! isset($GLOBALS['_PX_tests_templates'])) { - $GLOBALS['_PX_tests_templates'] = array(); - } - } - $this->compiled_template = $this->getCompiledTemplateName(); - $b = $this->compiled_template[1]; - $this->class = 'Pluf_Template_' . $b; - $this->compiled_template = $this->compiled_template[0]; - if (! class_exists($this->class, false)) { - if (! file_exists($this->compiled_template) or Pluf::f('debug')) { - $compiler = new Compiler($this->tpl, $this->folders); - $this->template_content = $compiler->getCompiledTemplate(); - $this->write($b); - } - include $this->compiled_template; - } - } - - /** - * Render the template with the given context and return the content. - * - * @param - * Object Context. - */ - function render($c = null) - { - if (defined('IN_UNIT_TESTS')) { - $GLOBALS['_PX_tests_templates'][] = $this; - } - if (null == $c) { - $c = new Context(); - } - $this->context = $c; - ob_start(); - $t = $c; - try { - call_user_func(array( - $this->class, - 'render' - ), $t); - // include $this->compiled_template; - } catch (Exception $e) { - ob_clean(); - throw $e; - } - $a = ob_get_contents(); - ob_end_clean(); - return $a; - } - - /** - * Get the full name of the compiled template. - * - * Ends with .phps to prevent execution from outside if the cache folder - * is not secured but to still have the syntax higlightings by the tools - * for debugging. - * - * @return string Full path to the compiled template - */ - function getCompiledTemplateName() - { - // The compiled template not only depends on the file but also - // on the possible folders in which it can be found. - $_tmp = var_export($this->folders, true); - return array( - $this->cache . '/Pluf_Template-' . md5($_tmp . $this->tpl) . '.phps', - md5($_tmp . $this->tpl) - ); - } - - /** - * Write the compiled template in the cache folder. - * Throw an exception if it cannot write it. - * - * @return bool Success in writing - */ - function write($name) - { - $this->template_content = '' . $this->template_content . 'compiled_template, 'a'); - if ($fp !== false) { - // Exclusive lock on writing - flock($fp, LOCK_EX); - // We have the unique pointeur, we truncate - ftruncate($fp, 0); - // Go back to the start of the file like a +w - rewind($fp); - fwrite($fp, $this->template_content, strlen($this->template_content)); - // Lock released, read access is possible - flock($fp, LOCK_UN); - fclose($fp); - @chmod($this->compiled_template, 0777); - return true; - } - throw new Exception(sprintf('Cannot write the compiled template: %s', $this->compiled_template)); - } - - public static function markSafe($string) - { - return new SafeString($string, true); - } - - /** - * Set a string to be safe for display. - * - * @param - * string String to be safe for display. - * @return string Pluf_Template_SafeString - */ - public static function unsafe($string) - { - return new SafeString($string, true); - } - - /** - * Special htmlspecialchars that can handle the objects. - * - * @param - * string String proceeded by htmlspecialchars - * @return string String like if htmlspecialchars was not applied - */ - public static function htmlspecialchars($string) - { - return htmlspecialchars((string) $string, ENT_COMPAT, 'UTF-8'); - } - - /** - * Modifier plugin: Convert the date from GMT to local and format it. - * - * This is used as all the datetime are stored in GMT in the database. - * - * @param string $date - * input date string considered GMT - * @param string $format - * strftime format for output ('%b %e, %Y') - * @return string date in localtime - */ - public static function dateFormat($date, $format = '%b %e, %Y') - { - if (substr(PHP_OS, 0, 3) == 'WIN') { - $_win_from = array( - '%e', - '%T', - '%D' - ); - $_win_to = array( - '%#d', - '%H:%M:%S', - '%m/%d/%y' - ); - $format = str_replace($_win_from, $_win_to, $format); - } - $date = date('Y-m-d H:i:s', strtotime($date . ' GMT')); - return strftime($format, strtotime($date)); - } - - /** - * Modifier plugin: Format a unix time. - * - * Warning: date format is directly to be used, not consideration of - * GMT or local time. - * - * @param int $time - * input date string considered GMT - * @param string $format - * strftime format for output ('Y-m-d H:i:s') - * @return string formated time - */ - public static function timeFormat($time, $format = 'Y-m-d H:i:s') - { - return date($format, $time); - } - - /** - * Special echo function that checks if the string to output is safe - * or not, if not it is escaped. - * - * @param - * mixed Input - * @return string Safe to display in HTML. - */ - public static function safeEcho($mixed, $echo = true) - { - if ($echo) { - echo (! is_object($mixed) or 'Pluf_Template_SafeString' != get_class($mixed)) ? htmlspecialchars($mixed, ENT_COMPAT, 'UTF-8') : $mixed->value; - } else { - return (! is_object($mixed) or 'Pluf_Template_SafeString' != get_class($mixed)) ? htmlspecialchars($mixed, ENT_COMPAT, 'UTF-8') : $mixed->value; - } - } - - /** - * New line to
      returning a safe string. - * - * @param - * mixed Input - * @return string Safe to display in HTML. - */ - public static function nl2br($mixed) - { - if (! is_object($mixed) or 'Pluf_Template_SafeString' !== get_class($mixed)) { - return Template::markSafe(nl2br(htmlspecialchars((string) $mixed, ENT_COMPAT, 'UTF-8'))); - } else { - return Template::markSafe(nl2br($mixed->value)); - } - } - - /** - * Var export returning a safe string. - * - * @param - * mixed Input - * @return string Safe to display in HTML. - */ - public static function varExport($mixed) - { - return self::unsafe('
      ' . Pluf_esc(var_export($mixed, true)) . '
      '); - } - - /** - * Display the date in a "6 days, 23 hours ago" style. - */ - public static function dateAgo($date, $f = 'withal') - { - $date = Template::dateFormat($date, '%Y-%m-%d %H:%M:%S'); - if ($f == 'withal') { - return \Pluf\Date::easy($date, null, 2, ('now')); - } else { - return \Pluf\Date::easy($date, null, 2, ('now'), false); - } - } - - /** - * Display the time in a "6 days, 23 hours ago" style. - */ - public static function timeAgo($date, $f = "withal") - { - $date = self::timeFormat($date); - if ($f == 'withal') { - return \Pluf\Date::easy($date, null, 2, ('now')); - } else { - return \Pluf\Date::easy($date, null, 2, ('now'), false); - } - } - - /** - * Hex encode an email excluding the "mailto:". - */ - public static function safeEmail($email) - { - $email = chunk_split(bin2hex($email), 2, '%'); - $email = '%' . substr($email, 0, strlen($email) - 1); - return Template::markSafe($email); - } - - /** - * Returns the first item in the given array. - * - * @param array $array - * @return mixed An empty string if $array is not an array. - */ - public static function first($array) - { - $array = (array) $array; - $result = array_shift($array); - if (null === $result) { - return ''; - } - - return $result; - } - - /** - * Returns the last item in the given array. - * - * @param array $array - * @return mixed An empty string if $array is not an array. - */ - public static function last($array) - { - $array = (array) $array; - $result = array_pop($array); - if (null === $result) { - return ''; - } - - return $result; - } -} diff --git a/src/Template/Compiler.php b/src/Template/Compiler.php deleted file mode 100755 index 5f545030..00000000 --- a/src/Template/Compiler.php +++ /dev/null @@ -1,730 +0,0 @@ - 'strtoupper', - 'lower' => 'strtolower', - 'count' => 'count', - 'md5' => 'md5', - 'sha1' => 'sha1', - 'escxml' => 'htmlspecialchars', - 'strip_tags' => 'strip_tags', - 'escurl' => 'rawurlencode', - 'capitalize' => 'ucwords', - // Not var_export because of recursive issues. - 'debug' => 'print_r', - 'fulldebug' => 'var_export', - 'trim' => 'trim', - 'ltrim' => 'ltrim', - 'rtrim' => 'rtrim', - 'escape' => '\\Pluf\\Template::htmlspecialchars', - 'dump' => '\\Pluf\\Template::varExport', - 'nl2br' => '\\Pluf\\Template::nl2br', - 'unsafe' => '\\Pluf\\Template::unsafe', - 'safe' => '\\Pluf\\Template::unsafe', - 'date' => '\\Pluf\\Template::dateFormat', - 'time' => '\\Pluf\\Template::timeFormat', - 'dateago' => '\\Pluf\\Template::dateAgo', - 'timeago' => '\\Pluf\\Template::timeAgo', - 'email' => '\\Pluf\\Template::safeEmail', - 'first' => '\\Pluf\\Template::first', - 'last' => '\\Pluf\\Template::last' - ); - - /** - * After the compilation is completed, this contains the list of - * modifiers used in the template. - * The GetCompiledTemplate method - * these modifiers. - */ - public $_usedModifiers = array(); - - /** - * Default allowed extra tags/functions. - * - * - * These default tags are merged with the 'template_tags' defined - * in the configuration of the application. - */ - protected $_allowedTags = array( - 'url' => '\\Pluf\\Template\\Tag\\UrlTag', - 'aurl' => '\\Pluf\\Template\\Tag\\Rurl', - 'media' => '\\Pluf\\Template\\Tag\\MediaUrl', - 'amedia' => '\\Pluf\\Template\\Tag\\RmediaUrl', - 'aperm' => '\\Pluf\\Template\\Tag\\APerm', - 'getmsgs' => '\\Pluf\\Template\\Tag\\Messages' - ); - - /** - * During compilation, all the tags are created once so to query - * their interface easily. - */ - protected $_extraTags = array(); - - /** - * The block stack to see if the blocks are correctly closed. - */ - protected $_blockStack = array(); - - /** - * Special stack for the translation handling in blocktrans. - */ - protected $_transStack = array(); - - protected $_transPlural = false; - - /** - * Current template source file. - */ - protected $_sourceFile; - - /** - * Current tag. - */ - protected $_currentTag; - - /** - * Template folders. - */ - public $templateFolders = array(); - - /** - * Template content. - * It can be set directly from a string. - */ - public $templateContent = ''; - - /** - * The extend blocks. - */ - public $_extendBlocks = array(); - - /** - * The extended template. - */ - public $_extendedTemplate = ''; - - /** - * Construct the compiler. - * - * @param - * string Basename of the template file. - * @param - * array Base folders in which the templates files - * should be found. (array()) - * @param - * bool Load directly the template content. (true) - */ - function __construct($template_file, $folders = array(), $load = true) - { - /** - * [signal] - * - * Pluf_Template_Compiler::construct_template_tags_modifiers - * - * [sender] - * - * Pluf_Template_Compiler - * - * [description] - * - * This signal allows an application to dynamically modify the - * allowed template tags. The order of the merge with the ones - * configured in the configuration files and the default one - * is: default -> signal -> configuration file. - * That is, the configuration file is the highest authority. - * - * [parameters] - * - * array('tags' => array(), - * 'modifiers' => array()); - */ - $params = array( - 'tags' => array(), - 'modifiers' => array() - ); - Signal::send('Pluf_Template_Compiler::construct_template_tags_modifiers', 'Pluf_Template_Compiler', $params); - $this->_allowedTags = array_merge($this->_allowedTags, $params['tags'], Pluf::f('template_tags', array())); - $this->_modifier = array_merge($this->_modifier, $params['modifiers'], Pluf::f('template_modifiers', array())); - foreach ($this->_allowedTags as $name => $model) { - $this->_extraTags[$name] = new $model(); - } - $this->_sourceFile = $template_file; - $this->_allowedInVar = array_merge($this->_vartype, $this->_op); - $this->_allowedInExpr = array_merge($this->_vartype, $this->_op); - $this->_allowedAssign = array_merge($this->_vartype, $this->_assignOp, $this->_op); - $this->templateFolders = $folders; - if ($load) { - $this->loadTemplateFile($template_file); - } - } - - /** - * Compile the template into a PHP code. - * - * @return string PHP code of the compiled template. - */ - function compile() - { - $this->compileBlocks(); - $tplcontent = $this->templateContent; - // Remove the template comments - $tplcontent = preg_replace('!{\*(.*?)\*}!s', '', $tplcontent); - // Remove PHP code - $tplcontent = preg_replace('!<\?php(.*?)\?>!s', '', $tplcontent); - // Catch the litteral blocks and put them in the - // $this->_literals stack - $_match = []; - preg_match_all('!{literal}(.*?){/literal}!s', $tplcontent, $_match); - $this->_literals = $_match[1]; - $tplcontent = preg_replace("!{literal}(.*?){/literal}!s", '{literal}', $tplcontent); - // Core regex to parse the template - $result = preg_replace_callback('/{((.).*?)}/s', array( - $this, - '_callback' - ), $tplcontent); - if (count($this->_blockStack)) { - trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR); - } - return $result; - } - - /** - * Get a cleaned compile template. - */ - function getCompiledTemplate() - { - $result = $this->compile(); - // if (count($this->_usedModifiers)) { - // $code = array(); - // // foreach ($this->_usedModifiers as $modifier) { - // // $code[] = 'Pluf::loadFunction(\'' . $modifier . '\'); '; - // // } - // $result = '' . $result; - // } - // Clean the output - $result = str_replace(array( - '?>', - '' - ), '', $result); - // To avoid the triming of the \n after a php closing tag. - $result = str_replace("?>\n", "?>\n\n", $result); - return $result; - } - - /** - * Parse the extend blocks. - * - * If the current template extends another, it finds the extended - * template and grabs the defined blocks and compile them. - */ - function compileBlocks() - { - $tplcontent = $this->templateContent; - $this->_extendedTemplate = ''; - // Match extends on the first line of the template - $_match = []; - if (preg_match("!{extends\s['\"](.*?)['\"]}!", $tplcontent, $_match)) { - $this->_extendedTemplate = $_match[1]; - } - // Get the blocks in the current template - $cnt = preg_match_all("!{block\s(\S+?)}(.*?){/block}!s", $tplcontent, $_match); - // Compile the blocks - for ($i = 0; $i < $cnt; $i ++) { - if (! isset($this->_extendBlocks[$_match[1][$i]]) or false !== strpos($this->_extendBlocks[$_match[1][$i]], '~~{~~superblock~~}~~')) { - $compiler = clone ($this); - $compiler->templateContent = $_match[2][$i]; - $_tmp = $compiler->compile(); - $this->updateModifierStack($compiler); - if (! isset($this->_extendBlocks[$_match[1][$i]])) { - $this->_extendBlocks[$_match[1][$i]] = $_tmp; - } else { - $this->_extendBlocks[$_match[1][$i]] = str_replace('~~{~~superblock~~}~~', $_tmp, $this->_extendBlocks[$_match[1][$i]]); - } - } - } - if (strlen($this->_extendedTemplate) > 0) { - // The template of interest is now the extended template - // as we are not in a base template - $this->loadTemplateFile($this->_extendedTemplate); - $this->_sourceFile = $this->_extendedTemplate; - $this->compileBlocks(); // It will recurse to the base template. - } else { - // Replace the current blocks by a place holder - if ($cnt) { - $this->templateContent = preg_replace("!{block\s(\S+?)}(.*?){/block}!s", "{block $1}", $tplcontent, - 1); - } - } - } - - /** - * Load a template file. - * - * The path to the file to load is relative and the file is found - * in one of the $templateFolders array of folders. - * - * @param - * string Relative path of the file to load. - */ - function loadTemplateFile($file) - { - // FIXME: Very small security check, could be better. - if (strpos($file, '..') !== false) { - throw new Exception(sprintf(('Template file contains invalid characters: %s'), $file)); - } - foreach ($this->templateFolders as $folder) { - if (file_exists($folder . '/' . $file)) { - $this->templateContent = file_get_contents($folder . '/' . $file); - return; - } - } - // File not found in all the folders. - throw new Exception(sprintf(('Template file not found: %s'), $file)); - } - - function _callback($matches) - { - list (, $tag, $firstcar) = $matches; - if (! preg_match('/^\$|[\'"]|[a-zA-Z\/]$/', $firstcar)) { - trigger_error(sprintf(('Invalid tag syntax: %s'), $tag), E_USER_ERROR); - return ''; - } - $this->_currentTag = $tag; - if (in_array($firstcar, array( - '$', - '\'', - '"' - ))) { - if ('blocktrans' !== end($this->_blockStack)) { - return '_parseVariable($tag) . '); ?>'; - } else { - $tok = explode('|', $tag); - $this->_transStack[substr($tok[0], 1)] = $this->_parseVariable($tag); - return '%%' . substr($tok[0], 1) . '%%'; - } - } else { - $m = []; - if (! preg_match('/^(\/?[a-zA-Z0-9_]+)(?:(?:\s+(.*))|(?:\((.*)\)))?$/', $tag, $m)) { - trigger_error(sprintf(('Invalid function syntax: %s'), $tag), E_USER_ERROR); - return ''; - } - if (count($m) == 4) { - $m[2] = $m[3]; - } - if (! isset($m[2])) - $m[2] = ''; - if ($m[1] == 'ldelim') - return '{'; - if ($m[1] == 'rdelim') - return '}'; - if ($m[1] != 'include') { - return '_parseFunction($m[1], $m[2]) . '?>'; - } else { - return $this->_parseFunction($m[1], $m[2]); - } - } - } - - function _parseVariable($expr) - { - $tok = explode('|', $expr); - $res = $this->_parseFinal(array_shift($tok), $this->_allowedInVar); - $m = []; - foreach ($tok as $modifier) { - if (! preg_match('/^(\w+)(?:\:(.*))?$/', $modifier, $m)) { - trigger_error(sprintf(('Invalid modifier syntax: (%s) %s'), $this->_currentTag, $modifier), E_USER_ERROR); - return ''; - } - // $targs = array( - // $res - // ); - if (isset($m[2])) { - $res = $this->_modifier[$m[1]] . '(' . $res . ',' . $m[2] . ')'; - } else if (isset($this->_modifier[$m[1]])) { - $res = $this->_modifier[$m[1]] . '(' . $res . ')'; - } else { - trigger_error(sprintf(('Unknown modifier: (%s) %s'), $this->_currentTag, $m[1]), E_USER_ERROR); - return ''; - } - if (! in_array($this->_modifier[$m[1]], $this->_usedModifiers)) { - $this->_usedModifiers[] = $this->_modifier[$m[1]]; - } - } - return $res; - } - - function _parseFunction($name, $args) - { - switch ($name) { - case 'if': - $res = 'if (' . $this->_parseFinal($args, $this->_allowedInExpr) . '): '; - array_push($this->_blockStack, 'if'); - break; - case 'else': - if (end($this->_blockStack) != 'if') { - trigger_error(sprintf(('End tag of a block missing: %s'), end($this->_blockStack)), E_USER_ERROR); - } - $res = 'else: '; - break; - case 'elseif': - if (end($this->_blockStack) != 'if') { - trigger_error(sprintf(('End tag of a block missing: %s'), end($this->_blockStack)), E_USER_ERROR); - } - $res = 'elseif(' . $this->_parseFinal($args, $this->_allowedInExpr) . '):'; - break; - case 'foreach': - $res = 'foreach (' . $this->_parseFinal($args, array_merge(array( - T_AS, - T_DOUBLE_ARROW, - T_STRING, - T_OBJECT_OPERATOR, - T_LIST, - $this->_allowedAssign, - '[', - ']' - )), array( - ';', - '!' - )) . '): '; - array_push($this->_blockStack, 'foreach'); - break; - case 'while': - $res = 'while(' . $this->_parseFinal($args, $this->_allowedInExpr) . '):'; - array_push($this->_blockStack, 'while'); - break; - case '/foreach': - case '/if': - case '/while': - $short = substr($name, 1); - if (end($this->_blockStack) != $short) { - trigger_error(sprintf(('End tag of a block missing: %s'), end($this->_blockStack)), E_USER_ERROR); - } - array_pop($this->_blockStack); - $res = 'end' . $short . '; '; - break; - case 'assign': - $res = $this->_parseFinal($args, $this->_allowedAssign) . '; '; - break; - case 'literal': - if (count($this->_literals)) { - $res = '?>' . array_shift($this->_literals) . '' . $this->_extendBlocks[$args] . '~~{~~superblock~~}~~_parseFinal($args, $this->_allowedAssign); - $res = 'echo((' . $argfct . '));'; - break; - case 'blocktrans': - array_push($this->_blockStack, 'blocktrans'); - $res = ''; - $this->_transStack = array(); - if ($args) { - $this->_transPlural = true; - $_args = $this->_parseFinal($args, $this->_allowedAssign, array( - ';', - '[', - ']' - ), true); - $res .= '$_b_t_c=' . trim(array_shift($_args)) . '; '; - } - $res .= 'ob_start(); '; - break; - case '/blocktrans': - $short = substr($name, 1); - if (end($this->_blockStack) != $short) { - trigger_error(sprintf(('End tag of a block missing: %s'), end($this->_blockStack)), E_USER_ERROR); - } - $res = ''; - if ($this->_transPlural) { - $res .= '$_b_t_p=ob_get_contents(); ob_end_clean(); echo('; - $res .= 'Pluf_Translation::sprintf(_n($_b_t_s, $_b_t_p, $_b_t_c), array('; - $_tmp = array(); - foreach ($this->_transStack as $key => $_trans) { - $_tmp[] = '\'' . addslashes($key) . '\' => \Pluf\Template::safeEcho(' . $_trans . ', false)'; - } - $res .= implode(', ', $_tmp); - unset($_trans, $_tmp); - $res .= ')));'; - $this->_transStack = array(); - } else { - $res .= '$_b_t_s=ob_get_contents(); ob_end_clean(); '; - if (count($this->_transStack) == 0) { - $res .= 'echo(($_b_t_s)); '; - } else { - $res .= 'echo(Pluf_Translation::sprintf(($_b_t_s), array('; - $_tmp = array(); - foreach ($this->_transStack as $key => $_trans) { - $_tmp[] = '\'' . addslashes($key) . '\' => \Pluf\Template::safeEcho(' . $_trans . ', false)'; - } - $res .= implode(', ', $_tmp); - unset($_trans, $_tmp); - $res .= '))); '; - $this->_transStack = array(); - } - } - $this->_transPlural = false; - array_pop($this->_blockStack); - break; - case 'plural': - $res = '$_b_t_s=ob_get_contents(); ob_end_clean(); ob_start(); '; - break; - case 'include': - // XXX fixme: Will need some security check, when online - // editing. - $argfct = preg_replace('!^[\'"](.*)[\'"]$!', '$1', $args); - $_comp = new Compiler($argfct, $this->templateFolders); - $res = $_comp->compile(); - $this->updateModifierStack($_comp); - break; - default: - $_end = false; - $oname = $name; - if (substr($name, 0, 1) == '/') { - $_end = true; - $name = substr($name, 1); - } - // Here we should allow custom blocks. - - // Here we start the template tag calls at the template tag - // {tag ...} is not a block, so it must be a function. - if (! isset($this->_allowedTags[$name])) { - trigger_error(sprintf(('The function tag "%s" is not allowed.'), $name), E_USER_ERROR); - } - $argfct = $this->_parseFinal($args, $this->_allowedAssign); - // $argfct is a string that can be copy/pasted in the PHP code - // but we need the array of args. - $res = ''; - if (isset($this->_extraTags[$name])) { - if (false == $_end) { - if (method_exists($this->_extraTags[$name], 'start')) { - $res .= '$_extra_tag = Pluf::factory(\'' . $this->_allowedTags[$name] . '\', $t); $_extra_tag->start(' . $argfct . '); '; - } - if (method_exists($this->_extraTags[$name], 'genStart')) { - $res .= $this->_extraTags[$name]->genStart(); - } - } else { - if (method_exists($this->_extraTags[$name], 'end')) { - $res .= '$_extra_tag = Pluf::factory(\'' . $this->_allowedTags[$name] . '\', $t); $_extra_tag->end(' . $argfct . '); '; - } - if (method_exists($this->_extraTags[$name], 'genEnd')) { - $res .= $this->_extraTags[$name]->genEnd(); - } - } - } - if ($res == '') { - trigger_error(sprintf(('The function tag "{%s ...}" is not supported.'), $oname), E_USER_ERROR); - } - } - return $res; - } - - /* - * - * ------- - * if: op, autre, var - * foreach: T_AS, T_DOUBLE_ARROW, T_VARIABLE, @locale@ - * for: autre, fin_instruction - * while: op, autre, var - * assign: T_VARIABLE puis assign puis autre, ponctuation, T_STRING - * echo: T_VARIABLE/@locale@ puis autre + ponctuation - * modificateur: serie de autre séparé par une virgule - * - * tous : T_VARIABLE, @locale@ - * - */ - function _parseFinal($string, $allowed = array(), $exceptchar = array( - ';' - ), $getAsArray = false) - { - $tokens = token_get_all(''); - $result = ''; - $first = true; - $inDot = false; - $firstok = array_shift($tokens); - $afterAs = false; - $f_key = ''; - $f_val = ''; - $results = array(); - - // il y a un bug, parfois le premier token n'est pas T_OPEN_TAG... - if ($firstok == '<' && $tokens[0] == '?' && is_array($tokens[1]) && $tokens[1][0] == T_STRING && $tokens[1][1] == 'php') { - array_shift($tokens); - array_shift($tokens); - } - foreach ($tokens as $tok) { - if (is_array($tok)) { - list ($type, $str) = $tok; - $first = false; - if ($type == T_CLOSE_TAG) { - continue; - } - if ($type == T_AS) { - $afterAs = true; - } - if ($type == T_STRING && $inDot) { - $result .= $str; - } elseif ($type == T_VARIABLE) { - // $result .= '$t->_vars[\''.substr($str, 1).'\']'; - $result .= '$t->_vars->' . substr($str, 1); - } elseif ($type == T_WHITESPACE || in_array($type, $allowed)) { - $result .= $str; - } else { - trigger_error(sprintf(('Invalid syntax: (%s) %s.'), $this->_currentTag, $str . ' tokens' . var_export($tokens, true)), E_USER_ERROR); - return ''; - } - } else { - if (in_array($tok, $exceptchar)) { - trigger_error(sprintf(('Invalid character: (%s) %s.'), $this->_currentTag, $tok), E_USER_ERROR); - } elseif ($tok == '.') { - $inDot = true; - $result .= '->'; - } elseif ($tok == '~') { - $result .= '.'; - } elseif ($tok == '[') { - $result .= $tok; - } elseif ($tok == ']') { - $result .= $tok; - } elseif ($getAsArray && $tok == ',') { - $results[] = $result; - $result = ''; - } else { - $result .= $tok; - } - $first = false; - } - } - if (! $getAsArray) { - return $result; - } else { - if ($result != '') { - $results[] = $result; - } - return $results; - } - } - - /** - * Update the current stack of modifiers from another compiler. - */ - protected function updateModifierStack($compiler) - { - foreach ($compiler->_usedModifiers as $_um) { - if (! in_array($_um, $this->_usedModifiers)) { - $this->_usedModifiers[] = $_um; - } - } - } -} diff --git a/src/Template/Context.php b/src/Template/Context.php deleted file mode 100755 index e0750988..00000000 --- a/src/Template/Context.php +++ /dev/null @@ -1,49 +0,0 @@ -. - */ -namespace Pluf\Template; - -/** - * داده‌های مورد نیاز در الگو را نگهداری می‌کند. - * - * تمام داده‌هایی که از لایه نمایش به لایه الگو ارسال می‌شود در این کلاس - * نگهداری شده و در اختیار الگوها قرار می‌گیرد. - */ -class Context -{ - - public $_vars; - - function __construct($vars = array()) - { - $this->_vars = new ContextVars($vars); - } - - function get($var) - { - if (isset($this->_vars[$var])) { - return $this->_vars[$var]; - } - return ''; - } - - function set($var, $value) - { - $this->_vars[$var] = $value; - } -} diff --git a/src/Template/Context/Request.php b/src/Template/Context/Request.php deleted file mode 100755 index 9e2c2c59..00000000 --- a/src/Template/Context/Request.php +++ /dev/null @@ -1,68 +0,0 @@ -. - */ -namespace Pluf\Template\Context; - -use Pluf\Signal; -use Pluf\Template\ContextVars; -use Pluf; - -/** - * Class storing the data that are then used in the template. - * - * This class automatically set the 'request' key with the current - * request and use and add more keys based on the processors. - */ -class Request extends \Pluf\Template\Context -{ - - function __construct($request, $vars = array()) - { - $vars = array_merge(array( - 'request' => $request - ), $vars); - foreach (Pluf::f('template_context_processors', array()) as $proc) { - $vars = array_merge($proc($request), $vars); - } - $params = array( - 'request' => $request, - 'context' => $vars - ); - /** - * [signal] - * - * Pluf_Template_Context_Request::construct - * - * [sender] - * - * Pluf_Template_Context_Request - * - * [description] - * - * This signal allows an application to dynamically modify the - * context array. - * - * [parameters] - * - * array('request' => $request, - * 'context' => array()); - */ - Signal::send('Pluf_Template_Context_Request::construct', 'Pluf_Template_Context_Request', $params); - $this->_vars = new ContextVars($params['context']); - } -} diff --git a/src/Template/ContextVars.php b/src/Template/ContextVars.php deleted file mode 100755 index 98a26397..00000000 --- a/src/Template/ContextVars.php +++ /dev/null @@ -1,43 +0,0 @@ -. - */ -namespace Pluf\Template; - -use ArrayObject; - -/** - * Special array where the keyed indexes can be accessed as properties. - */ -class ContextVars extends ArrayObject -{ - - function __get($prop) - { - return (isset($this[$prop])) ? $this[$prop] : ''; - } - - function __set($prop, $value) - { - $this[$prop] = $value; - } - - function __toString() - { - return var_export($this, true); - } -} diff --git a/src/Template/SafeString.php b/src/Template/SafeString.php deleted file mode 100755 index 460cc6cb..00000000 --- a/src/Template/SafeString.php +++ /dev/null @@ -1,47 +0,0 @@ -. - */ -namespace Pluf\Template; - -/** - * A string already escaped to display in a template. - */ -class SafeString -{ - - public $value = ''; - - function __construct($mixed, $safe = false) - { - if (is_object($mixed) and 'Pluf_Template_SafeString' == get_class($mixed)) { - $this->value = $mixed->value; - } else { - $this->value = ($safe) ? $mixed : htmlspecialchars($mixed, ENT_COMPAT, 'UTF-8'); - } - } - - function __toString() - { - return $this->value; - } - - public static function markSafe($string) - { - return new SafeString($string, true); - } -} \ No newline at end of file diff --git a/src/Template/Tag.php b/src/Template/Tag.php deleted file mode 100755 index 509668f6..00000000 --- a/src/Template/Tag.php +++ /dev/null @@ -1,32 +0,0 @@ -context = $context; - } -} diff --git a/src/Template/Tag/APerm.php b/src/Template/Tag/APerm.php deleted file mode 100755 index 8a75d2c3..00000000 --- a/src/Template/Tag/APerm.php +++ /dev/null @@ -1,51 +0,0 @@ -. - */ -namespace Pluf\Template\Tag; - -/** - * Assign a permission to a template variable. - * - * This template tag is available by default under the name - * aperm. Example of usage: - * - * - * {aperm 'can_drive', $user, 'MyApp.can_drive'} - * {aperm 'can_drive_big_car', $user, 'MyApp.can_drive', $bigcar} - * {if $can_drive}Can drive!{/if} - * - */ -class APerm extends \Pluf\Template\Tag -{ - - /** - * - * @param - * string Variable to get the permission - * @param - * User - * @param - * string Permission string - * @param - * mixed Optional \Pluf\Data\Model if using row level permission (null) - */ - function start($var, $user, $perm, $object = null) - { - $this->context->set($var, $user->hasPerm($perm, $object)); - } -} diff --git a/src/Template/Tag/Cfg.php b/src/Template/Tag/Cfg.php deleted file mode 100755 index 7991e73e..00000000 --- a/src/Template/Tag/Cfg.php +++ /dev/null @@ -1,57 +0,0 @@ -. - */ -namespace Pluf\Template\Tag; - -use Pluf; - -/** - * دسترسی به تنظیمان نرم‌افزار - * - * در لایه الگو ممکن است نیاز به تنظیم‌هایی شود که برای سیستم در نظر گرفته شده است. این کلاس یک برچسب را به - * وجود می‌آورد تا به تنظیم‌ها دسترسی پیدا کرد. - * - * نکته اینکه این کلاس امکان دسترسی به داده‌های امنیتی از تنظیم‌ها را نمی‌دهد. - */ -class Cfg extends \Pluf\Template\Tag -{ - - /** - * Display the configuration variable. - * - * @param - * string Configuration variable. - * @param - * mixed Default value to return display (''). - * @param - * bool Display the value (true). - * @param - * string Prefix to set to the variable if not displayed - * ('cfg_'). - */ - function start($cfg, $default = '', $display = true, $prefix = 'cfg_') - { - if (0 !== strpos($cfg, 'db_') or 0 !== strpos($cfg, 'secret_')) { - if ($display) { - echo Pluf::f($cfg, $default); - } else { - $this->context->set($prefix . $cfg, Pluf::f($cfg, $default)); - } - } - } -} diff --git a/src/Template/Tag/Cycle.php b/src/Template/Tag/Cycle.php deleted file mode 100755 index ed77c915..00000000 --- a/src/Template/Tag/Cycle.php +++ /dev/null @@ -1,157 +0,0 @@ -. - */ -namespace Pluf\Template\Tag; - -use Pluf\Template; -use Pluf\Utils; - -/** - * Template tag cycle. - * - * Cycle among the given strings or variables each time this tag is - * encountered. - * - * Within a loop, cycles among the given strings each time through the loop: - * - * - * {foreach $some_list as $obj} - * - * ... - * - * {/foreach} - * - * - * You can use variables, too. For example, if you have two - * template variables, $rowvalue1 and $rowvalue2, you can - * cycle between their values like this: - * - * - * {foreach $some_list as $obj} - * - * ... - * - * {/foreach} - * - * - * You can mix variables and strings: - * - * - * {foreach $some_list as $obj} - * - * ... - * - * {/foreach} - * - * - * In some cases you might want to refer to the next value of a cycle - * from outside of a loop. To do this, just group the arguments into - * an array and give the {cycle} tag name last, like this: - * - * - * {cycle array('row1', 'row2'), 'rowcolors'} - * - * - * From then on, you can insert the current value of the cycle - * wherever you'd like in your template: - * - * - * ... - * ... - * - * Based on concepts from the Django cycle template tag. - */ -class Cycle extends \Pluf\Template\Tag -{ - - /** - * - * @see \Pluf\Template\Tag::start() - * @throws \InvalidArgumentException If no argument is provided. - */ - public function start() - { - $nargs = func_num_args(); - if (1 > $nargs) { - throw new \InvalidArgumentException('`cycle` tag requires at least one argument'); - } - - $result = ''; - list ($key, $index) = $this->_computeIndex(func_get_args()); - - switch ($nargs) { - // (array or mixed) argument - case 1: - $arg = func_get_arg(0); - if (is_array($arg)) { - $result = $arg[$index % count($arg)]; - } else { - $result = $arg; - } - break; - - // (array) arguments, (string) assign - case 2: - $args = func_get_args(); - if (is_array($args[0])) { - $last = array_pop($args); - if (is_string($last) && '' === $this->context->get($last)) { - $value = Utils::flattenArray($args[0]); - $this->context->set($last, $value); - - list ($assign_key, $assign_index) = $this->_computeIndex(array( - $value - )); - $result = $value[0]; - } - break; - } - - // considers all the arguments as a value to use in the cycle - default: - $args = Utils::flattenArray(func_get_args()); - $result = $args[$index % count($args)]; - break; - } - - echo Template::markSafe((string) $result); - } - - /** - * Compute an index for the given array. - * - * @param - * array - * @return array A array of two elements: key and index. - */ - protected function _computeIndex($args) - { - if (! isset($this->context->__cycle_stack)) { - $this->context->__cycle_stack = array(); - } - - $key = serialize($args); - $this->context->__cycle_stack[$key] = (array_key_exists($key, $this->context->__cycle_stack)) ? 1 + $this->context->__cycle_stack[$key] : 0; - $index = $this->context->__cycle_stack[$key]; - - return array( - $key, - $index - ); - } -} diff --git a/src/Template/Tag/Firstof.php b/src/Template/Tag/Firstof.php deleted file mode 100755 index 5c744689..00000000 --- a/src/Template/Tag/Firstof.php +++ /dev/null @@ -1,80 +0,0 @@ -. - */ -namespace Pluf\Template\Tag; - -use Pluf\Template; - -/** - * Template tag firstof. - * - * Outputs the first variable passed that is not false, without escaping. - * Outputs nothing if all the passed variables are false. - * - * Sample usage: - * - * {firstof array($var1, $var2, $var3)} - * - * This is equivalent to: - * - * - * {if $var1} - * {$var1|safe} - * {elseif $var2} - * {$var2|safe} - * {elseif $var3} - * {$var3|safe} - * {/if} - * - * - * You can also use a literal string as a fallback value in case all - * passed variables are false: - * - * {firstof array($var1, $var2, $var3), "fallback value"} - * - * Based on concepts from the Django firstof template tag. - */ -class Firstof extends \Pluf\Template\Tag -{ - - /** - * - * @see \Pluf\Template\Tag::start() - * @param string $token - * Variables to test. - * @param string $fallback - * Literal string to used when all passed variables are false. - * @throws \InvalidArgumentException If no argument is provided. - */ - public function start($tokens = array(), $fallback = null) - { - if (! is_array($tokens) || 0 === count($tokens)) { - throw new \InvalidArgumentException('`firstof` tag requires at least one array as argument'); - } - $result = (string) $fallback; - - foreach ($tokens as $var) { - if ($var) { - $result = Template::markSafe((string) $var); - break; - } - } - - echo $result; - } -} diff --git a/src/Template/Tag/MediaUrl.php b/src/Template/Tag/MediaUrl.php deleted file mode 100755 index 6c5c576e..00000000 --- a/src/Template/Tag/MediaUrl.php +++ /dev/null @@ -1,39 +0,0 @@ -. - */ -namespace Pluf\Template\Tag; - -use Pluf; - -class MediaUrl extends \Pluf\Template\Tag -{ - - function start($file = '') - { - echo MediaUrl::url($file); - } - - public static function url($file = '') - { - if ($file !== '' && Pluf::f('last_update_file', false) && false !== ($last_update = Pluf::fileExists(Pluf::f('last_update_file')))) { - $file = $file . '?' . substr(md5(filemtime($last_update)), 0, 5); - } - return Pluf::f('url_media', Pluf::f('app_base') . '/media') . $file; - } -} - diff --git a/src/Template/Tag/Messages.php b/src/Template/Tag/Messages.php deleted file mode 100755 index 5576a1c5..00000000 --- a/src/Template/Tag/Messages.php +++ /dev/null @@ -1,40 +0,0 @@ -. - */ -namespace Pluf\Template\Tag; - -/** - * Display the messages for the current user. - */ -class Messages extends \Pluf\Template\Tag -{ - - function start($user) - { - if (is_object($user) && ! $user->isAnonymous()) { - $messages = $user->getAndDeleteMessages(); - if (count($messages) > 0) { - echo '
      ' . "\n" . '
        ' . "\n"; - foreach ($messages as $m) { - echo '
      • ' . $m . '
      • '; - } - echo '
      '; - } - } - } -} diff --git a/src/Template/Tag/Mytag.php b/src/Template/Tag/Mytag.php deleted file mode 100644 index 687c953a..00000000 --- a/src/Template/Tag/Mytag.php +++ /dev/null @@ -1,83 +0,0 @@ -. - */ -namespace Pluf\Template\Tag; - -/** - * Custom tag example. - */ -class Mytag extends \Pluf\Template\Tag -{ - - /** - * Perform some operations at the opening of the - * tag {mytag 'param1', 'param2'}. - * - * You can access the template context through - * $this->context - */ - function start($param1, $param2) - { - echo 'start of tag mytag:
      '; - echo '$this->context[mytag]: ' . $this->context->get('mytag') . '
      '; - } - - /** - * Generate some valid PHP code to be put in the template. - * - * The code generated by this function is included in the - * template just after the call to $this->start() if the start() - * method is implemented. - * - * You cannot access the template context but you can write - * code that is modifying it through $t - */ - function genStart() - { - return '$t->set(\'mytag\', \'hello world!\');'; - } - - /** - * Perform some operations at the closing of the - * tag {/mytag 'param1'}. - * - * You can access the template context through - * $this->context - */ - function end($param1) - { - echo 'end of tag mytag:
      '; - echo '$this->context[mytag]: ' . $this->context->get('mytag') . '
      '; - } - - /** - * Generate some valid PHP code to be put in the template. - * - * The code generated by this function is included in the - * template just after the call to $this->end() if the end() - * method is implemented. - * - * You cannot access the template context but you can write - * code that is modifying it through $t - */ - function genEnd() - { - // Cleaning :) - return '$t->set(\'mytag\', \'\');'; - } -} \ No newline at end of file diff --git a/src/Template/Tag/Now.php b/src/Template/Tag/Now.php deleted file mode 100755 index 4899fff6..00000000 --- a/src/Template/Tag/Now.php +++ /dev/null @@ -1,47 +0,0 @@ -. - */ -namespace Pluf\Template\Tag; - -/** - * Template tag now. - * - * Displays the date, formatted according to the given string. - * - * Sample usage: - * It is {now "jS F Y H:i"} - * - * Based on concepts from the Django now template tag. - * - * @link http://php.net/date for all the possible values. - */ -class Now extends \Pluf\Template\Tag -{ - - /** - * - * @see \Pluf\Template\Tag::start() - * @param string $token - * Format to be applied. - */ - public function start($token) - { - echo date($token); - } -} diff --git a/src/Template/Tag/Regroup.php b/src/Template/Tag/Regroup.php deleted file mode 100755 index 792ef629..00000000 --- a/src/Template/Tag/Regroup.php +++ /dev/null @@ -1,171 +0,0 @@ -. - */ -namespace Pluf\Template\Tag; - -use ArrayObject; -use ReflectionObject; - -/** - * Template tag regroup. - * - * Regroup a list of alike objects by a common attribute. - * - * This complex tag is best illustrated by use of an example: - * say that people is a list of people represented by arrays with - * first_name, last_name, and gender keys: - * - * - * $people = array( - * array('first_name' => 'George', - * 'last_name' => 'Bush', - * 'gender' => 'Male'), - * array('first_name' => 'Bill', - * 'last_name' => 'Clinton', - * 'gender' => 'Male'), - * array('first_name' => 'Margaret', - * 'last_name' => 'Thatcher', - * 'gender' => 'Female'), - * array('first_name' => 'Condoleezza', - * 'last_name' => 'Rice', - * 'gender' => 'Female'), - * array('first_name' => 'Pat', - * 'last_name' => 'Smith', - * 'gender' => 'Unknow'), - * ); - * - * - * ...and you'd like to display a hierarchical list that is ordered by - * gender, like this: - * - *
        - *
      • Male: - *
          - *
        • George Bush
        • - *
        • Bill Clinton
        • - *
        - *
      • - *
      • Female: - *
          - *
        • Margaret Thatcher
        • - *
        • Condoleezza Rice
        • - *
        - *
      • - *
      • Unknown: - *
          - *
        • Pat Smith
        • - *
        - *
      • - *
      - * - * You can use the {regroup} tag to group the list of people by - * gender. The following snippet of template code would accomplish this: - * - * - * {regroup $people, 'gender', 'gender_list'} - *
        - * {foreach $gender_list as $gender} - *
      • {$gender.grouper}: - *
          - * {foreach $gender.list as $item} - *
        • {$item.first_name} {$item.last_name}
        • - * {/foreach} - *
        - *
      • - * {/foreach} - *
      - *
      - * - * Let's walk through this example. {regroup} takes three arguments: - * the object (array or instance of \Pluf\Data\Model or any object) - * you want to regroup, the attribute to group by,and the name of the - * resulting object. Here, we're regrouping the people list by the - * gender attribute and calling the result gender_list. The result is - * assigned in a context varible of the same name $gender_list. - * - * {regroup} produces a instance of ArrayObject (in this case, $gender_list) - * of group objects. Each group object has two attributes: - * - *
        - *
      • grouper -- the item that was grouped by - * (e.g., the string "Male" or "Female").
      • - *
      • list -- an ArrayObject of all items in this group - * (e.g., an ArrayObject of all people with gender='Male').
      • - *
      - * - * Note that {regroup} does not order its input! - * - * Based on concepts from the Django regroup template tag. - */ -class Regroup extends \Pluf\Template\Tag -{ - - /** - * - * @see \Pluf\Template\Tag::start() - * @param mixed $data - * The object to group. - * @param string $by - * The attribute ti group by. - * @param string $assign - * The name of the resulting object. - * @throws \InvalidArgumentException If no argument is provided. - */ - public function start($data, $by, $assign) - { - $grouped = array(); - $tmp = array(); - - foreach ($data as $group) { - if (is_object($group)) { - if (is_subclass_of($group, '\Pluf\Data\Model')) { - $raw = $group->getData(); - if (! array_key_exists($by, $raw)) { - continue; - } - } else { - $ref = new ReflectionObject($group); - if (! $ref->hasProperty($by)) { - continue; - } - } - $key = $group->$by; - $list = $group; - } else { - if (! array_key_exists($by, $group)) { - continue; - } - $key = $group[$by]; - $list = new ArrayObject($group, ArrayObject::ARRAY_AS_PROPS); - } - - if (! array_key_exists($key, $tmp)) { - $tmp[$key] = array(); - } - $tmp[$key][] = $list; - } - - foreach ($tmp as $key => $list) { - $grouped[] = new ArrayObject(array( - 'grouper' => $key, - 'list' => $list - ), ArrayObject::ARRAY_AS_PROPS); - } - $this->context->set(trim($assign), $grouped); - } -} diff --git a/src/Template/Tag/Rurl.php b/src/Template/Tag/Rurl.php deleted file mode 100755 index f3e2b058..00000000 --- a/src/Template/Tag/Rurl.php +++ /dev/null @@ -1,33 +0,0 @@ -. - */ -namespace Pluf\Template\Tag; - -/** - * Assign the url to a template variable. - */ -use Pluf\HTTP\URL; - -class Rurl extends \Pluf\Template\Tag -{ - - function start($var, $view, $params = array(), $get_params = array()) - { - $this->context->set($var, URL::urlForView($view, $params, $get_params)); - } -} diff --git a/src/Template/Tag/UrlTag.php b/src/Template/Tag/UrlTag.php deleted file mode 100755 index 0cb4a912..00000000 --- a/src/Template/Tag/UrlTag.php +++ /dev/null @@ -1,30 +0,0 @@ -. - */ -namespace Pluf\Template\Tag; - -use Pluf\HTTP\URL; - -class UrlTag extends \Pluf\Template\Tag -{ - - function start($view, $params = array(), $get_params = array()) - { - echo URL::urlForView($view, $params, $get_params); - } -} diff --git a/src/Text.php b/src/Text.php deleted file mode 100755 index 69d95c71..00000000 --- a/src/Text.php +++ /dev/null @@ -1,420 +0,0 @@ -. - */ -namespace Pluf; - -/** - * Utility class to clean/manipulate strings. - */ -class Text -{ - - /** - * Wrap a string containing HTML code. - * - * The HTML is not broken, words are broken only if very long. - * - * Improved from a version available on php.net - * - * @see http://www.php.net/manual/en/function.wordwrap.php#89782 - * - * @param - * string The string to wrap - * @param - * int The maximal length of a string (45) - * @param - * string Wrap string ("\n") - * @return string Wrapped string - */ - public static function wrapHtml($string, $length = 45, $wrapString = "\n") - { - $wrapped = ''; - $word = ''; - $html = false; - $line_len = 0; - $n = mb_strlen($string); - for ($i = 0; $i < $n; $i ++) { - $char = mb_substr($string, $i, 1); - /** - * HTML Begins - */ - if ($char === '<') { - if (! empty($word)) { - $line_len += mb_strlen($word); - $wrapped .= $word; - $word = ''; - } - $html = true; - $wrapped .= $char; - continue; - } - if ($char === '>') { - /** - * HTML ends - */ - $html = false; - $wrapped .= $char; - continue; - } - if ($html) { - /** - * If this is inside HTML -> append to the wrapped string - */ - $wrapped .= $char; - continue; - } - if ($char === $wrapString) { - /** - * Whitespace characted / new line - */ - $wrapped .= $word . $char; - $word = ''; - $line_len = 0; - continue; - } - if (in_array($char, array( - ' ', - "\t" - ))) { - // Word delimiter, check if split before it needed - $word .= $char; - if (mb_strlen($word) + $line_len <= $length) { - $line_len += mb_strlen($word); - $wrapped .= $word; - $word = ''; - } else { - // If we add the word, it will be above the limit - $line_len = mb_strlen($word); - $wrapped .= $wrapString . $word; - $word = ''; - } - continue; - } - /** - * Check chars - */ - - $word .= $char; - if (mb_strlen($word) + $line_len > $length) { - $wrapped .= $wrapString; - $line_len = 0; - continue; - } - if (mb_strlen($word) >= $length) { - $wrapped .= $word . $wrapString; - $word = ''; - $line_len = 0; - continue; - } - } - if ($word !== '') { - $wrapped .= $word; - } - return $wrapped; - } - - /** - * Given a string, cleaned from the not interesting characters, - * returns an array with the words as index and the number of - * times it was in the text as the value. - * - * @credits Tokenizer of DokuWiki to handle Thai and CJK words. - * http://www.splitbrain.org/projects/dokuwiki - * - * @param - * string Cleaned, lowercased and utf-8 encoded string. - * @param - * bool Remove the accents (True) - * @return array Word and number of occurences. - */ - public static function tokenize($string, $remove_accents = True) - { - if ($remove_accents) { - $string = self::removeAccents($string); - } - $asian1 = '[\x{0E00}-\x{0E7F}]'; // Thai - $asian2 = '[' . '\x{2E80}-\x{3040}' . // CJK -> Hangul - '\x{309D}-\x{30A0}' . '\x{30FD}-\x{31EF}\x{3200}-\x{D7AF}' . '\x{F900}-\x{FAFF}' . // CJK Compatibility Ideographs - '\x{FE30}-\x{FE4F}' . // CJK Compatibility Forms - ']'; - $asian3 = '[' . // Hiragana/Katakana (can be two characters) - '\x{3042}\x{3044}\x{3046}\x{3048}' . '\x{304A}-\x{3062}\x{3064}-\x{3082}' . '\x{3084}\x{3086}\x{3088}-\x{308D}' . '\x{308F}-\x{3094}' . '\x{30A2}\x{30A4}\x{30A6}\x{30A8}' . '\x{30AA}-\x{30C2}\x{30C4}-\x{30E2}' . '\x{30E4}\x{30E6}\x{30E8}-\x{30ED}' . '\x{30EF}-\x{30F4}\x{30F7}-\x{30FA}' . '][' . '\x{3041}\x{3043}\x{3045}\x{3047}\x{3049}' . '\x{3063}\x{3083}\x{3085}\x{3087}\x{308E}\x{3095}-\x{309C}' . '\x{30A1}\x{30A3}\x{30A5}\x{30A7}\x{30A9}' . '\x{30C3}\x{30E3}\x{30E5}\x{30E7}\x{30EE}\x{30F5}\x{30F6}\x{30FB}\x{30FC}' . '\x{31F0}-\x{31FF}' . ']?'; - $asian = '(?:' . $asian1 . '|' . $asian2 . '|' . $asian3 . ')'; - $words = array(); - // handle asian chars as single words. - $asia = @preg_replace('/(' . $asian . ')/u', ' \1 ', $string); - if (! is_null($asia)) { - // will not be called if regexp failure - $string = $asia; - } - $arr = preg_split('/\s+/', $string, - 1, PREG_SPLIT_NO_EMPTY); - foreach ($arr as $w) { - $w = trim($w); - if (isset($words[$w])) { - $words[$w] ++; - } else { - $words[$w] = 1; - } - } - return $words; - } - - /** - * Clean a string from the HTML and the unnecessary - * punctuation. - * Convert the string to lowercase. - * - * @info Require mbstring extension. - * - * @param - * string String. - * @return string Cleaned lowercase string. - */ - public static function cleanString($string) - { - $string = html_entity_decode($string, ENT_QUOTES, 'utf-8'); - $string = str_replace(',;:(){}[]\\|*@!?^_=/\'~`%$#', ' '); - return mb_strtolower($string, 'UTF-8'); - } - - /** - * Remove the accentuated characters. - * - * Requires a string in lowercase, the removal is not perfect but - * is better than nothing. - * - * @param - * string Lowercased string in utf-8. - * @return string String with some of the accents removed. - */ - public static function removeAccents($string) - { - $map = array( - 'à' => 'a', - 'ô' => 'o', - 'ď' => 'd', - 'ḟ' => 'f', - 'ë' => 'e', - 'š' => 's', - 'ơ' => 'o', - 'ß' => 'ss', - 'ă' => 'a', - 'ř' => 'r', - 'ț' => 't', - 'ň' => 'n', - 'ā' => 'a', - 'ķ' => 'k', - 'ŝ' => 's', - 'ỳ' => 'y', - 'ņ' => 'n', - 'ĺ' => 'l', - 'ħ' => 'h', - 'ṗ' => 'p', - 'ó' => 'o', - 'ú' => 'u', - 'ě' => 'e', - 'é' => 'e', - 'ç' => 'c', - 'ẁ' => 'w', - 'ċ' => 'c', - 'õ' => 'o', - 'ṡ' => 's', - 'ø' => 'o', - 'ģ' => 'g', - 'ŧ' => 't', - 'ș' => 's', - 'ė' => 'e', - 'ĉ' => 'c', - 'ś' => 's', - 'î' => 'i', - 'ű' => 'u', - 'ć' => 'c', - 'ę' => 'e', - 'ŵ' => 'w', - 'ṫ' => 't', - 'ū' => 'u', - 'č' => 'c', - 'ö' => 'oe', - 'è' => 'e', - 'ŷ' => 'y', - 'ą' => 'a', - 'ł' => 'l', - 'ų' => 'u', - 'ů' => 'u', - 'ş' => 's', - 'ğ' => 'g', - 'ļ' => 'l', - 'ƒ' => 'f', - 'ž' => 'z', - 'ẃ' => 'w', - 'ḃ' => 'b', - 'å' => 'a', - 'ì' => 'i', - 'ï' => 'i', - 'ḋ' => 'd', - 'ť' => 't', - 'ŗ' => 'r', - 'ä' => 'ae', - 'í' => 'i', - 'ŕ' => 'r', - 'ê' => 'e', - 'ü' => 'ue', - 'ò' => 'o', - 'ē' => 'e', - 'ñ' => 'n', - 'ń' => 'n', - 'ĥ' => 'h', - 'ĝ' => 'g', - 'đ' => 'd', - 'ĵ' => 'j', - 'ÿ' => 'y', - 'ũ' => 'u', - 'ŭ' => 'u', - 'ư' => 'u', - 'ţ' => 't', - 'ý' => 'y', - 'ő' => 'o', - 'â' => 'a', - 'ľ' => 'l', - 'ẅ' => 'w', - 'ż' => 'z', - 'ī' => 'i', - 'ã' => 'a', - 'ġ' => 'g', - 'ṁ' => 'm', - 'ō' => 'o', - 'ĩ' => 'i', - 'ù' => 'u', - 'į' => 'i', - 'ź' => 'z', - 'á' => 'a', - 'û' => 'u', - 'þ' => 'th', - 'ð' => 'dh', - 'æ' => 'ae', - 'µ' => 'u', - 'ĕ' => 'e' - ); - return strtr($string, $map); - } - - /** - * Convert a string to a list of characters. - * - * @param - * string utf-8 encoded string. - * @return array Characters. - */ - public static function stringToChars($string) - { - $chars = array(); - $strlen = mb_strlen($string, 'UTF-8'); - for ($i = 0; $i < $strlen; $i ++) { - $chars[] = mb_substr($string, $i, 1, 'UTF-8'); - } - return $chars; - } - - /** - * Prevent a string to be all uppercase. - * - * If more than 50% of the words in the string are uppercases and - * if the string contains more than one word, the string is - * converted using the mb_convert_case. - * - * @see http://www.php.net/mb_convert_case - * - * @param - * string String to test. - * @param - * int Mode to convert the string (MB_CASE_TITLE) - * @return string Cleaned string. - */ - public static function preventUpperCase($string, $mode = MB_CASE_TITLE) - { - $elts = mb_split(' ', $string); - $n_elts = count($elts); - if ($n_elts > 1) { - $tot = 0; - foreach ($elts as $elt) { - if ($elt == '') { - $n_elts --; - continue; - } - if ($elt == mb_strtoupper($elt, 'UTF-8')) { - $tot ++; - } - } - if ((float) $tot / (float) $n_elts >= 0.5) { - return mb_convert_case(mb_strtolower($string, 'UTF-8'), $mode, 'UTF-8'); - } - } - return $string; - } - - /** - * Simple uppercase prevention. - * - * Contrary to self::preventUpperCase, this method will also - * prevent a single word to be uppercase. - * - * @param - * string String possibly in uppercase. - * @param - * int Mode to convert the string (MB_CASE_TITLE) - * @return string Mode cased if all uppercase in input. - */ - public static function simplePreventUpperCase($string, $mode = MB_CASE_TITLE) - { - if ($string == mb_strtoupper($string)) { - return mb_convert_case(mb_strtolower($string), $mode, 'UTF-8'); - } - return $string; - } - - - /** - * Shortcut needed all over the place. - * - * Note that in some cases, we need to escape strings not in UTF-8, so - * this is not possible to safely use a call to htmlspecialchars. This - * is why str_replace is used. - * - * @param - * string Raw string - * @return string HTML escaped string - */ - public static function esc($string) - { - return str_replace(array( - '&', - '"', - '<', - '>' - ), array( - '&', - '"', - '<', - '>' - ), (string) $string); - } -} diff --git a/src/Text/HTML/Filter.php b/src/Text/HTML/Filter.php deleted file mode 100755 index 771d09d1..00000000 --- a/src/Text/HTML/Filter.php +++ /dev/null @@ -1,446 +0,0 @@ -. - */ -namespace Pluf\Text\HTML; - -/** - * A PHP HTML filtering library - * - * lib_filter.php,v 1.15 2006/09/28 22:44:31 cal - * - * http://iamcal.com/publish/articles/php/processing_html/ - * http://iamcal.com/publish/articles/php/processing_html_part_2/ - * - * By Cal Henderson - * - * This code is licensed under a Creative Commons - * Attribution-ShareAlike 2.5 License - * http://creativecommons.org/licenses/by-sa/2.5/ - * - * Thanks to Jang Kim for adding support for single quoted attributes. - * - * TODO: Add HTML check from: - * http://simonwillison.net/2003/Feb/23/safeHtmlChecker/ - */ -class Filter -{ - - public $tag_counts = array(); - - /** - * tags and attributes that are allowed - */ - public $allowed = array( - 'a' => array( - 'href', - 'target' - ), - 'b' => array(), - 'img' => array( - 'src', - 'width', - 'height', - 'alt' - ) - ); - - /** - * tags which should always be self-closing (e.g. - * "") - */ - public $no_close = array( - 'img' - ); - - /** - * tags which must always have seperate opening and closing tags - * (e.g. - * "") - */ - public $always_close = array( - 'a', - 'b' - ); - - /** - * attributes which should be checked for valid protocols - */ - public $protocol_attributes = array( - 'src', - 'href' - ); - - /** - * protocols which are allowed - */ - public $allowed_protocols = array( - 'http', - 'ftp', - 'mailto' - ); - - /** - * tags which should be removed if they contain no content - * (e.g. - * "" or "") - */ - public $remove_blanks = array( - 'a', - 'b' - ); - - /** - * should we remove comments? - */ - public $strip_comments = 1; - - /** - * should we try and make a b tag out of "b>" - */ - public $always_make_tags = 0; - - /** - * Allows decimal entities. - * - * An entity has to decimal format . - * For example, the entity @ is the @ character. - * - * @var int - */ - public $allow_numbered_entities = 1; - - /** - * Allows hexadecimal entities. - * - * An entity has to decimal format . - * For example, the entity @ is the @ character. - * - * @var int - */ - public $allow_hexadecimal_entities = 1; - - public $allowed_entities = array( - 'amp', - 'gt', - 'lt', - 'quot' - ); - - function go($data) - { - $this->tag_counts = array(); - $data = $this->escape_comments($data); - $data = $this->balance_html($data); - $data = $this->check_tags($data); - $data = $this->process_remove_blanks($data); - $data = $this->validate_entities($data); - return $data; - } - - function escape_comments($data) - { - $data = preg_replace_callback("//s", function ($matchs) { - return ''; - }, $data); - return $data; - } - - function balance_html($data) - { - if ($this->always_make_tags) { - // try and form html - $data = preg_replace("/>>+/", ">", $data); - $data = preg_replace("/<<+/", "<", $data); - $data = preg_replace("/^>/", "", $data); - $data = preg_replace("/<([^>]*?)(?=<|$)/", "<$1>", $data); - $data = preg_replace("/(^|>)([^<]*?)(?=>)/", "$1<$2", $data); - } else { - // escape stray brackets - $data = preg_replace("/<([^>]*?)(?=<|$)/", "<$1", $data); - $data = preg_replace("/(^|>)([^<]*?)(?=>)/", "$1$2><", $data); - // the last regexp causes '<>' entities to appear - // (we need to do a lookahead assertion so that the last bracket can - // be used in the next pass of the regexp) - $data = str_replace('<>', '', $data); - } - // echo "::".HtmlSpecialChars($data)."
      \n"; - return $data; - } - - function check_tags($data) - { - $data = preg_replace_callback("/<(.*?)>/s", function ($matchs) { - return $this->process_tag($this->StripSingle($matchs[1])); - }, $data); - foreach (array_keys($this->tag_counts) as $tag) { - for ($i = 0; $i < $this->tag_counts[$tag]; $i ++) { - $data .= ""; - } - } - return $data; - } - - function process_tag($data) - { - // ending tags - $matches = []; - if (preg_match("/^\/([a-z0-9]+)/si", $data, $matches)) { - $name = StrToLower($matches[1]); - if (in_array($name, array_keys($this->allowed))) { - if (! in_array($name, $this->no_close)) { - if (isset($this->tag_counts[$name]) and $this->tag_counts[$name]) { - $this->tag_counts[$name] --; - return ''; - } - } - } else { - return ''; - } - } - // starting tags - if (preg_match("/^([a-z0-9]+)(.*?)(\/?)$/si", $data, $matches)) { - $name = StrToLower($matches[1]); - $body = $matches[2]; - $ending = $matches[3]; - if (in_array($name, array_keys($this->allowed))) { - $params = ""; - $matches_1 = []; - $matches_2 = []; - $matches_3 = []; - preg_match_all("/([a-z0-9]+)=([\"'])(.*?)\\2/si", $body, $matches_2, PREG_SET_ORDER); // - preg_match_all("/([a-z0-9]+)(=)([^\"\s']+)/si", $body, $matches_1, PREG_SET_ORDER); // - preg_match_all("/([a-z0-9]+)=([\"'])([^\"']*?)\s*$/si", $body, $matches_3, PREG_SET_ORDER); // no_close)) { - $ending = ' /'; - } - if (in_array($name, $this->always_close)) { - $ending = ''; - } - if (! $ending) { - if (isset($this->tag_counts[$name])) { - $this->tag_counts[$name] ++; - } else { - $this->tag_counts[$name] = 1; - } - } - if ($ending) { - $ending = ' /'; - } - return '<' . $name . $params . $ending . '>'; - } else { - return ''; - } - } - // comments - if (preg_match("/^!--(.*)--$/si", $data)) { - if ($this->strip_comments) { - return ''; - } else { - return '<' . $data . '>'; - } - } - // garbage, ignore it - return ''; - } - - function process_param_protocol($data) - { - $data = $this->decode_entities($data); - $matches = []; - if (preg_match("/^([^:]+)\:/si", $data, $matches)) { - if (! in_array($matches[1], $this->allowed_protocols)) { - $data = '#' . substr($data, strlen($matches[1]) + 1); - } - } - return $data; - } - - function process_remove_blanks($data) - { - foreach ($this->remove_blanks as $tag) { - $data = preg_replace("/<{$tag}(\s[^>]*)?><\\/{$tag}>/", '', $data); - $data = preg_replace("/<{$tag}(\s[^>]*)?\\/>/", '', $data); - } - return $data; - } - - function fix_case($data) - { - $data_notags = Strip_Tags($data); - $data_notags = preg_replace('/[^a-zA-Z]/', '', $data_notags); - if (strlen($data_notags) < 5) { - return $data; - } - if (preg_match('/[a-z]/', $data_notags)) { - return $data; - } - return preg_replace_callback("/(>|^)([^<]+?)(<|$)/s", function ($matchs) { - return $this->StripSingle($matchs[1]) . $this->fix_case_inner($this->StripSingle($matchs[1])) . $this->StripSingle($matchs[1]); - }, $data); - } - - function fix_case_inner($data) - { - $data = StrToLower($data); - $data = preg_replace_callback('/(^|[^\w\s\';,\\-])(\s*)([a-z])/', function ($matchs) { - return $this->StripSingle($matchs[1] . $matchs[2]) . StrToUpper($this->StripSingle($matchs[1])); - }, $data); - return $data; - } - - function validate_entities($data) - { - // validate entities throughout the string - $data = preg_replace_callback('!&([^&;]*)(?=(;|&|$))!', function ($matchs) { - return $this->check_entity($this->StripSingle($matchs[1]), $this->StripSingle($matchs[2])); - }, $data); - // validate quotes outside of tags - $data = preg_replace_callback("/(>|^)([^<]+?)(<|$)/s", function ($matchs) { - return $this->StripSingle($matchs[1]) . str_replace('"', '"', $this->StripSingle($matchs[2])) . $this->StripSingle($matchs[3]); - }, $data); - return $data; - } - - function check_entity($preamble, $term) - { - if (';' === $term) { - if ($this->is_valid_entity($preamble)) { - return '&' . $preamble; - } - } - return '&' . $preamble; - } - - /** - * Determines if the string provided is a valid entity. - * - * @param string $entity - * String to test against. - * @return boolean - */ - function is_valid_entity($entity) - { - $m = []; - if (preg_match('#^\#([0-9]{2,}|x[0-9a-f]{2,})$#i', $entity, $m)) { - if (0 === strpos($m[1], 'x')) { - // hexadecimal entity - if ($this->allow_hexadecimal_entities && $this->not_control_caracter($m[1])) { - return true; - } - return false; - } else { - // decimal entity - if ($this->allow_numbered_entities && $this->not_control_caracter($m[1])) { - return true; - } - return false; - } - } - // HTML 4.0 character entity - return in_array($entity, $this->allowed_entities); - } - - /** - * Determines if the data provided is not a control character. - * - * @param string|int $data - * Data to test against like "64" or "x40". - * @return boolean - */ - function not_control_caracter($data) - { - if (0 === strpos($data, 'x')) { - $data = substr($data, 1); - $data = hexdec($data); - } else { - $data = intval($data); - } - return (31 < $data && (127 > $data || 159 < $data)); - } - - // within attributes, we want to convert all hex/dec/url escape - // sequences into their raw characters so that we can check we - // don't get stray quotes/brackets inside strings - function decode_entities($data) - { - $data = preg_replace_callback('!(&)#(\d+);?!', array( - $this, - 'decode_dec_entity' - ), $data); - $data = preg_replace_callback('!(&)#x([0-9a-f]+);?!i', array( - $this, - 'decode_hex_entity' - ), $data); - $data = preg_replace_callback('!(%)([0-9a-f]{2});?!i', array( - $this, - 'decode_hex_entity' - ), $data); - $data = $this->validate_entities($data); - return $data; - } - - function decode_hex_entity($m) - { - return $this->decode_num_entity($m[1], hexdec($m[2])); - } - - function decode_dec_entity($m) - { - return $this->decode_num_entity($m[1], intval($m[2])); - } - - function decode_num_entity($orig_type, $d) - { - if ($d < 0) { - $d = 32; - } // space - // don't mess with huigh chars - if ($this->not_control_caracter($d)) { - if ($orig_type == '%') { - return '%' . dechex($d); - } - if ($orig_type == '&') { - return "&#$d;"; - } - } - return HtmlSpecialChars(chr($d)); - } - - function StripSingle($data) - { - return str_replace(array( - '\\"', - "\\0" - ), array( - '"', - chr(0) - ), $data); - } -} diff --git a/src/Text/Lang.php b/src/Text/Lang.php deleted file mode 100755 index d5726ee2..00000000 --- a/src/Text/Lang.php +++ /dev/null @@ -1,182 +0,0 @@ -. - */ -namespace Pluf\Text; - -use Pluf\Text; - -/** - * Detect the language of a text. - * - * - * list($lang, $confid) = Pluf_Text_Lang::detect($string); - * - */ -class Lang -{ - - /** - * Given a string, returns the language. - * - * Algorithm by Cavnar et al. 94. - * - * @param - * string - * @param - * bool Is the string clean (false) - * @return array Language, Confidence - */ - public static function detect($string, $is_clean = false) - { - if (! $is_clean) { - $string = Text::cleanString($string); - } - } - - /** - * Returns the sorted n-grams of a document. - * - * FIXME: We should detect the proportion of thai/chinese/japanese - * characters and switch to unigram instead of n-grams if the - * proportion is greater than 50%. - * - * @param - * string The clean document. - * @param - * int Maximum size of the n grams (3) - * @return array N-Grams - */ - public static function docNgrams($string, $n = 3) - { - // do not remove the accents - $words = Text::tokenize($string, false); - $ngrams = array(); - for ($i = 2; $i <= $n; $i ++) { - foreach ($words as $word => $occ) { - foreach (self::makeNgrams($word, $i) as $ngram) { - $ngrams[] = array( - $ngram, - $occ - ); - } - } - } - $out = array(); - foreach ($ngrams as $ngram) { - if (! isset($out[$ngram[0]])) { - $out[$ngram[0]] = $ngram[1]; - } else { - $out[$ngram[0]] += $ngram[1]; - } - } - // split the ngrams by occurence. - $ngrams = array(); - foreach ($out as $ngram => $occ) { - if (isset($ngrams[$occ])) { - $ngrams[$occ][] = $ngram; - } else { - $ngrams[$occ] = array( - $ngram - ); - } - } - krsort($ngrams); - $res = array(); - foreach ($ngrams as $occ => $list) { - sort($list); - foreach ($list as $ngram) { - $res[] = $ngram; - } - } - return $res; - } - - /** - * Returns the n-grams of rank n of the word. - * - * @param - * string Word. - * @return array N-grams - */ - public static function makeNgrams($word, $n = 3) - { - $chars = array( - '_' - ); - $chars = $chars + Text::stringToChars($word); - $chars[] = '_'; - $l = count($chars); - $ngrams = array(); - for ($i = 0; $i < $l + 1 - $n; $i ++) { - $ngrams[$i] = array(); - } - // $n_ngrams = $l+1-$n; - for ($i = 0; $i < $l; $i ++) { - for ($j = 0; $j < $n; $j ++) { - if (isset($ngrams[$i - $j])) { - $ngrams[$i - $j][] = $chars[$i]; - } - } - } - $out = array(); - foreach ($ngrams as $ngram) { - $t = implode('', $ngram); - if ($t != '__') { - $out[] = $t; - } - } - return $out; - } - - /** - * Return the distance between two document ngrams. - * - * @param - * array n-gram - * @param - * array n-gram - * @return integer distance - */ - public static function ngramDistance($n1, $n2) - { - $res = 0; - $n_n1 = count($n1); - $n_n2 = count($n2); - if ($n_n1 > $n_n2) { - list ($n_n1, $n_n2) = array( - $n_n2, - $n_n1 - ); - list ($n1, $n2) = array( - $n2, - $n1 - ); - } - for ($i = 0; $i < $n_n1; $i ++) { - if (false !== ($index = array_search($n1[$i], $n2))) { - $offset = abs($index - $i); - $res += ($offset > 3) ? 3 : $offset; - } else { - $res += 3; - } - } - $res += ($n_n2 - $n_n1) * 3; - return $res; - } -} \ No newline at end of file diff --git a/src/Text/UTF8.php b/src/Text/UTF8.php deleted file mode 100755 index 9d9751f3..00000000 --- a/src/Text/UTF8.php +++ /dev/null @@ -1,1827 +0,0 @@ -. - */ -namespace Pluf\Text; - -/** - * UTF8 helper functions - * - * Original file coming from DokuWiki. Updated as we consider that the - * multibytes functions are always available and wrapped in a class. - * - * @license LGPL (http://www.gnu.org/copyleft/lesser.html) - * @author Andreas Gohr - */ -class UTF8 -{ - - /** - * URL-Encode a filename/URL to allow unicodecharacters - * - * Slashes are not encoded - * - * When the second parameter is true the string will - * be encoded only if non ASCII characters are detected - - * This makes it safe to run it multiple times on the - * same string (default is true) - * - * @author Andreas Gohr - */ - public static function filename($file, $safe = true) - { - if ($safe && preg_match('#^[a-zA-Z0-9/_\-.%]+$#', $file)) { - return $file; - } - return str_replace('%2F', '/', urlencode($file)); - } - - /** - * Checks if a string contains 7bit ASCII only - * - * @author Andreas Gohr - */ - public static function is_ascii($str) - { - $n = strlen($str); - for ($i = 0; $i < $n; $i ++) { - if (ord($str{$i}) > 127) - return false; - } - return true; - } - - /** - * Strips all highbyte chars - * - * Returns a pure ASCII7 string - * - * @author Andreas Gohr - */ - public static function strip($str) - { - $ascii = ''; - $n = strlen($str); - for ($i = 0; $i < $n; $i ++) { - if (ord($str{$i}) < 128) { - $ascii .= $str{$i}; - } - } - return $ascii; - } - - /** - * Tries to detect if a string is in Unicode encoding - * - * @author - * @link http://www.php.net/manual/en/function.utf8-encode.php - */ - public static function check($str) - { - $k = strlen($str); - for ($i = 0; $i < $k; $i ++) { - $b = ord($str[$i]); - if ($b < 0x80) - continue; // 0bbbbbbb - elseif (($b & 0xE0) == 0xC0) - $n = 1; // 110bbbbb - elseif (($b & 0xF0) == 0xE0) - $n = 2; // 1110bbbb - elseif (($b & 0xF8) == 0xF0) - $n = 3; // 11110bbb - elseif (($b & 0xFC) == 0xF8) - $n = 4; // 111110bb - elseif (($b & 0xFE) == 0xFC) - $n = 5; // 1111110b - else - return false; // Does not match any model - for ($j = 0; $j < $n; $j ++) { // n bytes matching 10bbbbbb follow ? - if ((++ $i == $k) || ((ord($str[$i]) & 0xC0) != 0x80)) { - return false; - } - } - } - return true; - } - - /** - * Detect if a string is in a Russian charset. - * - * This should be used when the mb_string detection encoding is - * failing. For example: - * - *
      -     * $encoding = mb_detect_encoding($string, mb_detect_order(), true);
      -     * if ($encoding == false) {
      -     * $encoding = Pluf_Text_UTF8::detect_cyr_charset($string);
      -     * }
      -     * 
      - * - * @link http://forum.php.su/topic.php?forum=1&topic=1346 - * - * @param - * string - * @return string Possible Russian encoding - */ - public static function detect_cyr_charset($str) - { - $charsets = array( - 'KOI8-R' => 0, - 'Windows-1251' => 0, - 'CP-866' => 0, - 'ISO-8859-5' => 0 - ); - $length = strlen($str); - for ($i = 0; $i < $length; $i ++) { - $char = ord($str[$i]); - // non-russian characters - if ($char < 128 || $char > 256) - continue; - - // CP866 - if (($char > 159 && $char < 176) || ($char > 223 && $char < 242)) - $charsets['CP-866'] += 3; - if (($char > 127 && $char < 160)) - $charsets['CP-866'] += 1; - - // KOI8-R - if (($char > 191 && $char < 223)) - $charsets['KOI8-R'] += 3; - if (($char > 222 && $char < 256)) - $charsets['KOI8-R'] += 1; - - // WIN-1251 - if ($char > 223 && $char < 256) - $charsets['Windows-1251'] += 3; - if ($char > 191 && $char < 224) - $charsets['Windows-1251'] += 1; - - // ISO-8859-5 - if ($char > 207 && $char < 240) - $charsets['ISO-8859-5'] += 3; - if ($char > 175 && $char < 208) - $charsets['ISO-8859-5'] += 1; - } - arsort($charsets); - return key($charsets); - } - - /** - * Replace accented UTF-8 characters by unaccented ASCII-7 equivalents. - * - * @author Andreas Gohr - */ - public static function deaccent($string) - { - return strtr($string, self::accents()); - } - - /** - * Romanize a non-latin string - * - * FIXME - * - * @author Andreas Gohr - */ - public static function romanize($string) - { - if (self::is_ascii($string)) - return $string; - return strtr($string, self::romanization()); - } - - /** - * Removes special characters (nonalphanumeric) from a UTF-8 string - * - * This function adds the controlchars 0x00 to 0x19 to the array of - * stripped chars (they are not included in $UTF8_SPECIAL_CHARS) - * - * @author Andreas Gohr - * @param string $string - * The UTF8 string to strip of special chars - * @param string $repl - * Replace special with this string - * @param string $additional - * Additional chars to strip (used in regexp char class) - */ - function stripspecials($string, $repl = '', $additional = '') - { - static $specials = null; - if (is_null($specials)) { - $specials = preg_quote(self::special_chars(), '/'); - } - return preg_replace('/[' . $additional . '\x00-\x19' . $specials . ']/u', $repl, $string); - } - - /** - * UTF-8 lookup table for lower case accented letters - * - * This lookuptable defines replacements for accented characters from the ASCII-7 - * range. This are lower case letters only. - * - * @author Andreas Gohr - * @see utf8_deaccent() - */ - public static function accents() - { - return array( - 'à' => 'a', - 'ô' => 'o', - 'ď' => 'd', - 'ḟ' => 'f', - 'ë' => 'e', - 'š' => 's', - 'ơ' => 'o', - 'ß' => 'ss', - 'ă' => 'a', - 'ř' => 'r', - 'ț' => 't', - 'ň' => 'n', - 'ā' => 'a', - 'ķ' => 'k', - 'ŝ' => 's', - 'ỳ' => 'y', - 'ņ' => 'n', - 'ĺ' => 'l', - 'ħ' => 'h', - 'ṗ' => 'p', - 'ó' => 'o', - 'ú' => 'u', - 'ě' => 'e', - 'é' => 'e', - 'ç' => 'c', - 'ẁ' => 'w', - 'ċ' => 'c', - 'õ' => 'o', - 'ṡ' => 's', - 'ø' => 'o', - 'ģ' => 'g', - 'ŧ' => 't', - 'ș' => 's', - 'ė' => 'e', - 'ĉ' => 'c', - 'ś' => 's', - 'î' => 'i', - 'ű' => 'u', - 'ć' => 'c', - 'ę' => 'e', - 'ŵ' => 'w', - 'ṫ' => 't', - 'ū' => 'u', - 'č' => 'c', - 'ö' => 'oe', - 'è' => 'e', - 'ŷ' => 'y', - 'ą' => 'a', - 'ł' => 'l', - 'ų' => 'u', - 'ů' => 'u', - 'ş' => 's', - 'ğ' => 'g', - 'ļ' => 'l', - 'ƒ' => 'f', - 'ž' => 'z', - 'ẃ' => 'w', - 'ḃ' => 'b', - 'å' => 'a', - 'ì' => 'i', - 'ï' => 'i', - 'ḋ' => 'd', - 'ť' => 't', - 'ŗ' => 'r', - 'ä' => 'ae', - 'í' => 'i', - 'ŕ' => 'r', - 'ê' => 'e', - 'ü' => 'ue', - 'ò' => 'o', - 'ē' => 'e', - 'ñ' => 'n', - 'ń' => 'n', - 'ĥ' => 'h', - 'ĝ' => 'g', - 'đ' => 'd', - 'ĵ' => 'j', - 'ÿ' => 'y', - 'ũ' => 'u', - 'ŭ' => 'u', - 'ư' => 'u', - 'ţ' => 't', - 'ý' => 'y', - 'ő' => 'o', - 'â' => 'a', - 'ľ' => 'l', - 'ẅ' => 'w', - 'ż' => 'z', - 'ī' => 'i', - 'ã' => 'a', - 'ġ' => 'g', - 'ṁ' => 'm', - 'ō' => 'o', - 'ĩ' => 'i', - 'ù' => 'u', - 'į' => 'i', - 'ź' => 'z', - 'á' => 'a', - 'û' => 'u', - 'þ' => 'th', - 'ð' => 'dh', - 'æ' => 'ae', - 'µ' => 'u', - 'ĕ' => 'e', - 'À' => 'A', - 'Ô' => 'O', - 'Ď' => 'D', - 'Ḟ' => 'F', - 'Ë' => 'E', - 'Š' => 'S', - 'Ơ' => 'O', - 'Ă' => 'A', - 'Ř' => 'R', - 'Ț' => 'T', - 'Ň' => 'N', - 'Ā' => 'A', - 'Ķ' => 'K', - 'Ŝ' => 'S', - 'Ỳ' => 'Y', - 'Ņ' => 'N', - 'Ĺ' => 'L', - 'Ħ' => 'H', - 'Ṗ' => 'P', - 'Ó' => 'O', - 'Ú' => 'U', - 'Ě' => 'E', - 'É' => 'E', - 'Ç' => 'C', - 'Ẁ' => 'W', - 'Ċ' => 'C', - 'Õ' => 'O', - 'Ṡ' => 'S', - 'Ø' => 'O', - 'Ģ' => 'G', - 'Ŧ' => 'T', - 'Ș' => 'S', - 'Ė' => 'E', - 'Ĉ' => 'C', - 'Ś' => 'S', - 'Î' => 'I', - 'Ű' => 'U', - 'Ć' => 'C', - 'Ę' => 'E', - 'Ŵ' => 'W', - 'Ṫ' => 'T', - 'Ū' => 'U', - 'Č' => 'C', - 'Ö' => 'Oe', - 'È' => 'E', - 'Ŷ' => 'Y', - 'Ą' => 'A', - 'Ł' => 'L', - 'Ų' => 'U', - 'Ů' => 'U', - 'Ş' => 'S', - 'Ğ' => 'G', - 'Ļ' => 'L', - 'Ƒ' => 'F', - 'Ž' => 'Z', - 'Ẃ' => 'W', - 'Ḃ' => 'B', - 'Å' => 'A', - 'Ì' => 'I', - 'Ï' => 'I', - 'Ḋ' => 'D', - 'Ť' => 'T', - 'Ŗ' => 'R', - 'Ä' => 'Ae', - 'Í' => 'I', - 'Ŕ' => 'R', - 'Ê' => 'E', - 'Ü' => 'Ue', - 'Ò' => 'O', - 'Ē' => 'E', - 'Ñ' => 'N', - 'Ń' => 'N', - 'Ĥ' => 'H', - 'Ĝ' => 'G', - 'Đ' => 'D', - 'Ĵ' => 'J', - 'Ÿ' => 'Y', - 'Ũ' => 'U', - 'Ŭ' => 'U', - 'Ư' => 'U', - 'Ţ' => 'T', - 'Ý' => 'Y', - 'Ő' => 'O', - 'Â' => 'A', - 'Ľ' => 'L', - 'Ẅ' => 'W', - 'Ż' => 'Z', - 'Ī' => 'I', - 'Ã' => 'A', - 'Ġ' => 'G', - 'Ṁ' => 'M', - 'Ō' => 'O', - 'Ĩ' => 'I', - 'Ù' => 'U', - 'Į' => 'I', - 'Ź' => 'Z', - 'Á' => 'A', - 'Û' => 'U', - 'Þ' => 'Th', - 'Ð' => 'Dh', - 'Æ' => 'Ae', - 'Ĕ' => 'E' - ); - } - - public static function special_chars() - { - return "\x1A" . ' !"#$%&\'()+,/;<=>?@[\]^`{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•�' . '�—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½�' . '�¿×÷ˇ˘˙˚˛˜˝̣̀́̃̉΄΅·βφϑϒϕϖְֱֲֳִֵֶַָֹֻּֽ־ֿ�' . '�ׁׂ׃׳״،؛؟ـًٌٍَُِّْ٪฿‌‍‎‏–—―‗‘’‚“”�' . '��†‡•…‰′″‹›⁄₧₪₫€№℘™Ωℵ←↑→↓↔↕↵' . '⇐⇑⇒⇓⇔∀∂∃∅∆∇∈∉∋∏∑−∕∗∙√∝∞∠∧∨�' . '�∪∫∴∼≅≈≠≡≤≥⊂⊃⊄⊆⊇⊕⊗⊥⋅⌐⌠⌡〈〉⑩─�' . '��┌┐└┘├┤┬┴┼═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟╠' . '╡╢╣╤╥╦╧╨╩╪╫╬▀▄█▌▐░▒▓■▲▼◆◊●�' . '�★☎☛☞♠♣♥♦✁✂✃✄✆✇✈✉✌✍✎✏✐✑✒✓✔✕�' . '��✗✘✙✚✛✜✝✞✟✠✡✢✣✤✥✦✧✩✪✫✬✭✮✯✰✱' . '✲✳✴✵✶✷✸✹✺✻✼✽✾✿❀❁❂❃❄❅❆❇❈❉❊❋�' . '�❏❐❑❒❖❘❙❚❛❜❝❞❡❢❣❤❥❦❧❿➉➓➔➘➙➚�' . '��➜➝➞➟➠➡➢➣➤➥➦➧➨➩➪➫➬➭➮➯➱➲➳➴➵➶' . '➷➸➹➺➻➼➽➾' . ' 、。〃〈〉《》「」『』【】〒〔〕〖〗〘〙〚〛〶' . '�' . '�ﹼﹽ' . '!"#$%&'()*+,-./:;<=>?@[\]^`{|}~' . '⦅⦆。「」、・¢£¬ ̄¦¥₩│←↑→↓■○'; - } - - /** - * Romanization lookup table - * - * This lookup tables provides a way to transform strings written - * in a language different from the ones based upon latin letters - * into plain ASCII. - * - * Please note: this is not a scientific transliteration table. It - * only works oneway from nonlatin to ASCII and it works by simple - * character replacement only. Specialities of each language are - * not supported. - * - * @author Andreas Gohr - * @author Vitaly Blokhin - * @link http://www.uconv.com/translit.htm - * @author Bisqwit - * @link http://kanjidict.stc.cx/hiragana.php?src=2 - * @link http://www.translatum.gr/converter/greek-transliteration.htm - * @link http://en.wikipedia.org/wiki/Royal_Thai_General_System_of_Transcription - * @link http://www.btranslations.com/resources/romanization/korean.asp - * @author Arthit Suriyawongkul - * @author Denis Scheither - */ - public static function romanization() - { - return array( - // russian cyrillic - 'а' => 'a', - 'А' => 'A', - 'б' => 'b', - 'Б' => 'B', - 'в' => 'v', - 'В' => 'V', - 'г' => 'g', - 'Г' => 'G', - 'д' => 'd', - 'Д' => 'D', - 'е' => 'e', - 'Е' => 'E', - 'ё' => 'jo', - 'Ё' => 'Jo', - 'ж' => 'zh', - 'Ж' => 'Zh', - 'з' => 'z', - 'З' => 'Z', - 'и' => 'i', - 'И' => 'I', - 'й' => 'j', - 'Й' => 'J', - 'к' => 'k', - 'К' => 'K', - 'л' => 'l', - 'Л' => 'L', - 'м' => 'm', - 'М' => 'M', - 'н' => 'n', - 'Н' => 'N', - 'о' => 'o', - 'О' => 'O', - 'п' => 'p', - 'П' => 'P', - 'р' => 'r', - 'Р' => 'R', - 'с' => 's', - 'С' => 'S', - 'т' => 't', - 'Т' => 'T', - 'у' => 'u', - 'У' => 'U', - 'ф' => 'f', - 'Ф' => 'F', - 'х' => 'x', - 'Х' => 'X', - 'ц' => 'c', - 'Ц' => 'C', - 'ч' => 'ch', - 'Ч' => 'Ch', - 'ш' => 'sh', - 'Ш' => 'Sh', - 'щ' => 'sch', - 'Щ' => 'Sch', - 'ъ' => '', - 'Ъ' => '', - 'ы' => 'y', - 'Ы' => 'Y', - 'ь' => '', - 'Ь' => '', - 'э' => 'eh', - 'Э' => 'Eh', - 'ю' => 'ju', - 'Ю' => 'Ju', - 'я' => 'ja', - 'Я' => 'Ja', - // Ukrainian cyrillic - 'Ґ' => 'Gh', - 'ґ' => 'gh', - 'Є' => 'Je', - 'є' => 'je', - 'І' => 'I', - 'і' => 'i', - 'Ї' => 'Ji', - 'ї' => 'ji', - // Georgian - 'ა' => 'a', - 'ბ' => 'b', - 'გ' => 'g', - 'დ' => 'd', - 'ე' => 'e', - 'ვ' => 'v', - 'ზ' => 'z', - 'თ' => 'th', - 'ი' => 'i', - 'კ' => 'p', - 'ლ' => 'l', - 'მ' => 'm', - 'ნ' => 'n', - 'ო' => 'o', - 'პ' => 'p', - 'ჟ' => 'zh', - 'რ' => 'r', - 'ს' => 's', - 'ტ' => 't', - 'უ' => 'u', - 'ფ' => 'ph', - 'ქ' => 'kh', - 'ღ' => 'gh', - 'ყ' => 'q', - 'შ' => 'sh', - 'ჩ' => 'ch', - 'ც' => 'c', - 'ძ' => 'dh', - 'წ' => 'w', - 'ჭ' => 'j', - 'ხ' => 'x', - 'ჯ' => 'jh', - 'ჰ' => 'xh', - // Sanskrit - 'अ' => 'a', - 'आ' => 'ah', - 'इ' => 'i', - 'ई' => 'ih', - 'उ' => 'u', - 'ऊ' => 'uh', - 'ऋ' => 'ry', - 'ॠ' => 'ryh', - 'ऌ' => 'ly', - 'ॡ' => 'lyh', - 'ए' => 'e', - 'ऐ' => 'ay', - 'ओ' => 'o', - 'औ' => 'aw', - 'अं' => 'amh', - 'अः' => 'aq', - 'क' => 'k', - 'ख' => 'kh', - 'ग' => 'g', - 'घ' => 'gh', - 'ङ' => 'nh', - 'च' => 'c', - 'छ' => 'ch', - 'ज' => 'j', - 'झ' => 'jh', - 'ञ' => 'ny', - 'ट' => 'tq', - 'ठ' => 'tqh', - 'ड' => 'dq', - 'ढ' => 'dqh', - 'ण' => 'nq', - 'त' => 't', - 'थ' => 'th', - 'द' => 'd', - 'ध' => 'dh', - 'न' => 'n', - 'प' => 'p', - 'फ' => 'ph', - 'ब' => 'b', - 'भ' => 'bh', - 'म' => 'm', - 'य' => 'z', - 'र' => 'r', - 'ल' => 'l', - 'व' => 'v', - 'श' => 'sh', - 'ष' => 'sqh', - 'स' => 's', - 'ह' => 'x', - // Hebrew - 'א' => 'a', - 'ב' => 'b', - 'ג' => 'g', - 'ד' => 'd', - 'ה' => 'h', - 'ו' => 'v', - 'ז' => 'z', - 'ח' => 'kh', - 'ט' => 'th', - 'י' => 'y', - 'ך' => 'h', - 'כ' => 'k', - 'ל' => 'l', - 'ם' => 'm', - 'מ' => 'm', - 'ן' => 'n', - 'נ' => 'n', - 'ס' => 's', - 'ע' => 'ah', - 'ף' => 'f', - 'פ' => 'p', - 'ץ' => 'c', - 'צ' => 'c', - 'ק' => 'q', - 'ר' => 'r', - 'ש' => 'sh', - 'ת' => 't', - // Arabic - 'ا' => 'a', - 'ب' => 'b', - 'ت' => 't', - 'ث' => 'th', - 'ج' => 'g', - 'ح' => 'xh', - 'خ' => 'x', - 'د' => 'd', - 'ذ' => 'dh', - 'ر' => 'r', - 'ز' => 'z', - 'س' => 's', - 'ش' => 'sh', - 'ص' => 's\'', - 'ض' => 'd\'', - 'ط' => 't\'', - 'ظ' => 'z\'', - 'ع' => 'y', - 'غ' => 'gh', - 'ف' => 'f', - 'ق' => 'q', - 'ك' => 'k', - 'ل' => 'l', - 'م' => 'm', - 'ن' => 'n', - 'ه' => 'x\'', - 'و' => 'u', - 'ي' => 'i', - // Japanese hiragana - // 3 character syllables, っ doubles the consonant after - 'っちゃ' => 'ccha', - 'っちぇ' => 'cche', - 'っちょ' => 'ccho', - 'っちゅ' => 'cchu', - 'っびゃ' => 'bya', - 'っびぇ' => 'bye', - 'っびぃ' => 'byi', - 'っびょ' => 'byo', - 'っびゅ' => 'byu', - 'っちゃ' => 'cha', - 'っちぇ' => 'che', - 'っち' => 'chi', - 'っちょ' => 'cho', - 'っちゅ' => 'chu', - 'っひゃ' => 'hya', - 'っひぇ' => 'hye', - 'っひぃ' => 'hyi', - 'っひょ' => 'hyo', - 'っひゅ' => 'hyu', - 'っきゃ' => 'kya', - 'っきぇ' => 'kye', - 'っきぃ' => 'kyi', - 'っきょ' => 'kyo', - 'っきゅ' => 'kyu', - 'っぎゃ' => 'gya', - 'っぎぇ' => 'gye', - 'っぎぃ' => 'gyi', - 'っぎょ' => 'gyo', - 'っぎゅ' => 'gyu', - 'っみゃ' => 'mya', - 'っみぇ' => 'mye', - 'っみぃ' => 'myi', - 'っみょ' => 'myo', - 'っみゅ' => 'myu', - 'っにゃ' => 'nya', - 'っにぇ' => 'nye', - 'っにぃ' => 'nyi', - 'っにょ' => 'nyo', - 'っにゅ' => 'nyu', - 'っりゃ' => 'rya', - 'っりぇ' => 'rye', - 'っりぃ' => 'ryi', - 'っりょ' => 'ryo', - 'っりゅ' => 'ryu', - 'っしゃ' => 'sha', - 'っしぇ' => 'she', - 'っし' => 'shi', - 'っしょ' => 'sho', - 'っしゅ' => 'shu', - // 2 character syllables - normal - 'ふぁ' => 'fa', - 'ふぇ' => 'fe', - 'ふぃ' => 'fi', - 'ふぉ' => 'fo', - 'ふ' => 'fu', - 'ヴぁ' => 'va', - 'ヴぇ' => 've', - 'ヴぃ' => 'vi', - 'ヴぉ' => 'vo', - 'ヴ' => 'vu', - 'びゃ' => 'bya', - 'びぇ' => 'bye', - 'びぃ' => 'byi', - 'びょ' => 'byo', - 'びゅ' => 'byu', - 'ちゃ' => 'cha', - 'ちぇ' => 'che', - 'ち' => 'chi', - 'ちょ' => 'cho', - 'ちゅ' => 'chu', - 'ひゃ' => 'hya', - 'ひぇ' => 'hye', - 'ひぃ' => 'hyi', - 'ひょ' => 'hyo', - 'ひゅ' => 'hyu', - 'きゃ' => 'kya', - 'きぇ' => 'kye', - 'きぃ' => 'kyi', - 'きょ' => 'kyo', - 'きゅ' => 'kyu', - 'ぎゃ' => 'gya', - 'ぎぇ' => 'gye', - 'ぎぃ' => 'gyi', - 'ぎょ' => 'gyo', - 'ぎゅ' => 'gyu', - 'みゃ' => 'mya', - 'みぇ' => 'mye', - 'みぃ' => 'myi', - 'みょ' => 'myo', - 'みゅ' => 'myu', - 'にゃ' => 'nya', - 'にぇ' => 'nye', - 'にぃ' => 'nyi', - 'にょ' => 'nyo', - 'にゅ' => 'nyu', - 'りゃ' => 'rya', - 'りぇ' => 'rye', - 'りぃ' => 'ryi', - 'りょ' => 'ryo', - 'りゅ' => 'ryu', - 'しゃ' => 'sha', - 'しぇ' => 'she', - 'し' => 'shi', - 'しょ' => 'sho', - 'しゅ' => 'shu', - 'じゃ' => 'ja', - 'じぇ' => 'je', - 'じ' => 'ji', - 'じょ' => 'jo', - 'じゅ' => 'ju', - - // 2 character syllables, っ doubles the consonant after - 'っば' => 'bba', - 'っべ' => 'bbe', - 'っび' => 'bbi', - 'っぼ' => 'bbo', - 'っぶ' => 'bbu', - 'っぱ' => 'ppa', - 'っぺ' => 'ppe', - 'っぴ' => 'ppi', - 'っぽ' => 'ppo', - 'っぷ' => 'ppu', - 'った' => 'tta', - 'って' => 'tte', - 'っち' => 'cchi', - 'っと' => 'tto', - 'っつ' => 'ttsu', - 'っだ' => 'dda', - 'っで' => 'dde', - 'っぢ' => 'ddi', - 'っど' => 'ddo', - 'っづ' => 'ddu', - 'っが' => 'gga', - 'っげ' => 'gge', - 'っぎ' => 'ggi', - 'っご' => 'ggo', - 'っぐ' => 'ggu', - 'っか' => 'kka', - 'っけ' => 'kke', - 'っき' => 'kki', - 'っこ' => 'kko', - 'っく' => 'kku', - 'っま' => 'mma', - 'っめ' => 'mme', - 'っみ' => 'mmi', - 'っも' => 'mmo', - 'っむ' => 'mmu', - 'っな' => 'nna', - 'っね' => 'nne', - 'っに' => 'nni', - 'っの' => 'nno', - 'っぬ' => 'nnu', - 'っら' => 'rra', - 'っれ' => 'rre', - 'っり' => 'rri', - 'っろ' => 'rro', - 'っる' => 'rru', - 'っさ' => 'ssa', - 'っせ' => 'sse', - 'っし' => 'sshi', - 'っそ' => 'sso', - 'っす' => 'ssu', - 'っざ' => 'zza', - 'っぜ' => 'zze', - 'っじ' => 'zzi', - 'っぞ' => 'zzo', - 'っず' => 'zzu', - - // 1 character syllabels - 'あ' => 'a', - 'え' => 'e', - 'い' => 'i', - 'お' => 'o', - 'う' => 'u', - 'ん' => 'n', - 'は' => 'ha', - 'へ' => 'he', - 'ひ' => 'hi', - 'ほ' => 'ho', - 'ふ' => 'hu', - 'ば' => 'ba', - 'べ' => 'be', - 'び' => 'bi', - 'ぼ' => 'bo', - 'ぶ' => 'bu', - 'ぱ' => 'pa', - 'ぺ' => 'pe', - 'ぴ' => 'pi', - 'ぽ' => 'po', - 'ぷ' => 'pu', - 'た' => 'ta', - 'て' => 'te', - 'ち' => 'ti', - 'と' => 'to', - 'つ' => 'tu', - 'だ' => 'da', - 'で' => 'de', - 'ぢ' => 'di', - 'ど' => 'do', - 'づ' => 'du', - 'が' => 'ga', - 'げ' => 'ge', - 'ぎ' => 'gi', - 'ご' => 'go', - 'ぐ' => 'gu', - 'か' => 'ka', - 'け' => 'ke', - 'き' => 'ki', - 'こ' => 'ko', - 'く' => 'ku', - 'ま' => 'ma', - 'め' => 'me', - 'み' => 'mi', - 'も' => 'mo', - 'む' => 'mu', - 'な' => 'na', - 'ね' => 'ne', - 'に' => 'ni', - 'の' => 'no', - 'ぬ' => 'nu', - 'ら' => 'ra', - 'れ' => 're', - 'り' => 'ri', - 'ろ' => 'ro', - 'る' => 'ru', - 'さ' => 'sa', - 'せ' => 'se', - 'し' => 'shi', - 'そ' => 'so', - 'す' => 'su', - 'わ' => 'wa', - 'うぇ' => 'we', - 'うぃ' => 'wi', - 'を' => 'wo', - 'ざ' => 'za', - 'ぜ' => 'ze', - 'じ' => 'zi', - 'ぞ' => 'zo', - 'ず' => 'zu', - 'や' => 'ya', - 'いぇ' => 'ye', - 'よ' => 'yo', - 'ゆ' => 'yu', - - // never seen one of those, but better save than sorry - 'でゃ' => 'dha', - 'でぇ' => 'dhe', - 'でぃ' => 'dhi', - 'でょ' => 'dho', - 'でゅ' => 'dhu', - 'どぁ' => 'dwa', - 'どぇ' => 'dwe', - 'どぃ' => 'dwi', - 'どぉ' => 'dwo', - 'どぅ' => 'dwu', - 'ぢゃ' => 'dya', - 'ぢぇ' => 'dye', - 'ぢぃ' => 'dyi', - 'ぢょ' => 'dyo', - 'ぢゅ' => 'dyu', - 'ふぁ' => 'fwa', - 'ふぇ' => 'fwe', - 'ふぃ' => 'fwi', - 'ふぉ' => 'fwo', - 'ふぅ' => 'fwu', - 'ふゃ' => 'fya', - 'ふぇ' => 'fye', - 'ふぃ' => 'fyi', - 'ふょ' => 'fyo', - 'ふゅ' => 'fyu', - 'ぴゃ' => 'pya', - 'ぴぇ' => 'pye', - 'ぴぃ' => 'pyi', - 'ぴょ' => 'pyo', - 'ぴゅ' => 'pyu', - 'すぁ' => 'swa', - 'すぇ' => 'swe', - 'すぃ' => 'swi', - 'すぉ' => 'swo', - 'すぅ' => 'swu', - 'てゃ' => 'tha', - 'てぇ' => 'the', - 'てぃ' => 'thi', - 'てょ' => 'tho', - 'てゅ' => 'thu', - 'つゃ' => 'tsa', - 'つぇ' => 'tse', - 'つぃ' => 'tsi', - 'つょ' => 'tso', - 'つ' => 'tsu', - 'とぁ' => 'twa', - 'とぇ' => 'twe', - 'とぃ' => 'twi', - 'とぉ' => 'two', - 'とぅ' => 'twu', - 'ヴゃ' => 'vya', - 'ヴぇ' => 'vye', - 'ヴぃ' => 'vyi', - 'ヴょ' => 'vyo', - 'ヴゅ' => 'vyu', - 'うぁ' => 'wha', - 'うぇ' => 'whe', - 'うぃ' => 'whi', - 'うぉ' => 'who', - 'うぅ' => 'whu', - 'ゑ' => 'wye', - 'ゐ' => 'wyi', - 'じゃ' => 'zha', - 'じぇ' => 'zhe', - 'じぃ' => 'zhi', - 'じょ' => 'zho', - 'じゅ' => 'zhu', - 'じゃ' => 'zya', - 'じぇ' => 'zye', - 'じぃ' => 'zyi', - 'じょ' => 'zyo', - 'じゅ' => 'zyu', - - // convert what's left (probably only kicks in when something's missing above - 'ぁ' => 'a', - 'ぇ' => 'e', - 'ぃ' => 'i', - 'ぉ' => 'o', - 'ぅ' => 'u', - 'ゃ' => 'ya', - 'ょ' => 'yo', - 'ゅ' => 'yu', - - // 'spare' characters from other romanization systems - // 'だ'=>'da','で'=>'de','ぢ'=>'di','ど'=>'do','づ'=>'du', - // 'ら'=>'la','れ'=>'le','り'=>'li','ろ'=>'lo','る'=>'lu', - // 'さ'=>'sa','せ'=>'se','し'=>'si','そ'=>'so','す'=>'su', - // 'ちゃ'=>'cya','ちぇ'=>'cye','ちぃ'=>'cyi','ちょ'=>'cyo','ちゅ'=>'cyu', - // 'じゃ'=>'jya','じぇ'=>'jye','じぃ'=>'jyi','じょ'=>'jyo','じゅ'=>'jyu', - // 'りゃ'=>'lya','りぇ'=>'lye','りぃ'=>'lyi','りょ'=>'lyo','りゅ'=>'lyu', - // 'しゃ'=>'sya','しぇ'=>'sye','しぃ'=>'syi','しょ'=>'syo','しゅ'=>'syu', - // 'ちゃ'=>'tya','ちぇ'=>'tye','ちぃ'=>'tyi','ちょ'=>'tyo','ちゅ'=>'tyu', - // 'し'=>'ci',,い'=>'yi','ぢ'=>'dzi', - // 'っじゃ'=>'jja','っじぇ'=>'jje','っじ'=>'jji','っじょ'=>'jjo','っじゅ'=>'jju', - - // Japanese katakana - - // 4 character syllables: ッ doubles the consonant after, ー doubles the vowel before (usualy written with macron, but we don't want that in our URLs) - 'ッビャー' => 'bbyaa', - 'ッビェー' => 'bbyee', - 'ッビィー' => 'bbyii', - 'ッビョー' => 'bbyoo', - 'ッビュー' => 'bbyuu', - 'ッピャー' => 'ppyaa', - 'ッピェー' => 'ppyee', - 'ッピィー' => 'ppyii', - 'ッピョー' => 'ppyoo', - 'ッピュー' => 'ppyuu', - 'ッキャー' => 'kkyaa', - 'ッキェー' => 'kkyee', - 'ッキィー' => 'kkyii', - 'ッキョー' => 'kkyoo', - 'ッキュー' => 'kkyuu', - 'ッギャー' => 'ggyaa', - 'ッギェー' => 'ggyee', - 'ッギィー' => 'ggyii', - 'ッギョー' => 'ggyoo', - 'ッギュー' => 'ggyuu', - 'ッミャー' => 'mmyaa', - 'ッミェー' => 'mmyee', - 'ッミィー' => 'mmyii', - 'ッミョー' => 'mmyoo', - 'ッミュー' => 'mmyuu', - 'ッニャー' => 'nnyaa', - 'ッニェー' => 'nnyee', - 'ッニィー' => 'nnyii', - 'ッニョー' => 'nnyoo', - 'ッニュー' => 'nnyuu', - 'ッリャー' => 'rryaa', - 'ッリェー' => 'rryee', - 'ッリィー' => 'rryii', - 'ッリョー' => 'rryoo', - 'ッリュー' => 'rryuu', - 'ッシャー' => 'sshaa', - 'ッシェー' => 'sshee', - 'ッシー' => 'sshii', - 'ッショー' => 'sshoo', - 'ッシュー' => 'sshuu', - 'ッチャー' => 'cchaa', - 'ッチェー' => 'cchee', - 'ッチー' => 'cchii', - 'ッチョー' => 'cchoo', - 'ッチュー' => 'cchuu', - - // 3 character syllables - doubled vowels - 'ファー' => 'faa', - 'フェー' => 'fee', - 'フィー' => 'fii', - 'フォー' => 'foo', - 'フャー' => 'fyaa', - 'フェー' => 'fyee', - 'フィー' => 'fyii', - 'フョー' => 'fyoo', - 'フュー' => 'fyuu', - 'ヒャー' => 'hyaa', - 'ヒェー' => 'hyee', - 'ヒィー' => 'hyii', - 'ヒョー' => 'hyoo', - 'ヒュー' => 'hyuu', - 'ビャー' => 'byaa', - 'ビェー' => 'byee', - 'ビィー' => 'byii', - 'ビョー' => 'byoo', - 'ビュー' => 'byuu', - 'ピャー' => 'pyaa', - 'ピェー' => 'pyee', - 'ピィー' => 'pyii', - 'ピョー' => 'pyoo', - 'ピュー' => 'pyuu', - 'キャー' => 'kyaa', - 'キェー' => 'kyee', - 'キィー' => 'kyii', - 'キョー' => 'kyoo', - 'キュー' => 'kyuu', - 'ギャー' => 'gyaa', - 'ギェー' => 'gyee', - 'ギィー' => 'gyii', - 'ギョー' => 'gyoo', - 'ギュー' => 'gyuu', - 'ミャー' => 'myaa', - 'ミェー' => 'myee', - 'ミィー' => 'myii', - 'ミョー' => 'myoo', - 'ミュー' => 'myuu', - 'ニャー' => 'nyaa', - 'ニェー' => 'nyee', - 'ニィー' => 'nyii', - 'ニョー' => 'nyoo', - 'ニュー' => 'nyuu', - 'リャー' => 'ryaa', - 'リェー' => 'ryee', - 'リィー' => 'ryii', - 'リョー' => 'ryoo', - 'リュー' => 'ryuu', - 'シャー' => 'shaa', - 'シェー' => 'shee', - 'シー' => 'shii', - 'ショー' => 'shoo', - 'シュー' => 'shuu', - 'ジャー' => 'jaa', - 'ジェー' => 'jee', - 'ジー' => 'jii', - 'ジョー' => 'joo', - 'ジュー' => 'juu', - 'スァー' => 'swaa', - 'スェー' => 'swee', - 'スィー' => 'swii', - 'スォー' => 'swoo', - 'スゥー' => 'swuu', - 'デァー' => 'daa', - 'デェー' => 'dee', - 'ディー' => 'dii', - 'デォー' => 'doo', - 'デゥー' => 'duu', - 'チャー' => 'chaa', - 'チェー' => 'chee', - 'チー' => 'chii', - 'チョー' => 'choo', - 'チュー' => 'chuu', - 'ヂャー' => 'dyaa', - 'ヂェー' => 'dyee', - 'ヂィー' => 'dyii', - 'ヂョー' => 'dyoo', - 'ヂュー' => 'dyuu', - 'ツャー' => 'tsaa', - 'ツェー' => 'tsee', - 'ツィー' => 'tsii', - 'ツョー' => 'tsoo', - 'ツー' => 'tsuu', - 'トァー' => 'twaa', - 'トェー' => 'twee', - 'トィー' => 'twii', - 'トォー' => 'twoo', - 'トゥー' => 'twuu', - 'ドァー' => 'dwaa', - 'ドェー' => 'dwee', - 'ドィー' => 'dwii', - 'ドォー' => 'dwoo', - 'ドゥー' => 'dwuu', - 'ウァー' => 'whaa', - 'ウェー' => 'whee', - 'ウィー' => 'whii', - 'ウォー' => 'whoo', - 'ウゥー' => 'whuu', - 'ヴャー' => 'vyaa', - 'ヴェー' => 'vyee', - 'ヴィー' => 'vyii', - 'ヴョー' => 'vyoo', - 'ヴュー' => 'vyuu', - 'ヴァー' => 'vaa', - 'ヴェー' => 'vee', - 'ヴィー' => 'vii', - 'ヴォー' => 'voo', - 'ヴー' => 'vuu', - 'ウェー' => 'wee', - 'ウィー' => 'wii', - 'イェー' => 'yee', - - // 3 character syllables - doubled consonants - 'ッビャ' => 'bbya', - 'ッビェ' => 'bbye', - 'ッビィ' => 'bbyi', - 'ッビョ' => 'bbyo', - 'ッビュ' => 'bbyu', - 'ッピャ' => 'ppya', - 'ッピェ' => 'ppye', - 'ッピィ' => 'ppyi', - 'ッピョ' => 'ppyo', - 'ッピュ' => 'ppyu', - 'ッキャ' => 'kkya', - 'ッキェ' => 'kkye', - 'ッキィ' => 'kkyi', - 'ッキョ' => 'kkyo', - 'ッキュ' => 'kkyu', - 'ッギャ' => 'ggya', - 'ッギェ' => 'ggye', - 'ッギィ' => 'ggyi', - 'ッギョ' => 'ggyo', - 'ッギュ' => 'ggyu', - 'ッミャ' => 'mmya', - 'ッミェ' => 'mmye', - 'ッミィ' => 'mmyi', - 'ッミョ' => 'mmyo', - 'ッミュ' => 'mmyu', - 'ッニャ' => 'nnya', - 'ッニェ' => 'nnye', - 'ッニィ' => 'nnyi', - 'ッニョ' => 'nnyo', - 'ッニュ' => 'nnyu', - 'ッリャ' => 'rrya', - 'ッリェ' => 'rrye', - 'ッリィ' => 'rryi', - 'ッリョ' => 'rryo', - 'ッリュ' => 'rryu', - 'ッシャ' => 'ssha', - 'ッシェ' => 'sshe', - 'ッシ' => 'sshi', - 'ッショ' => 'ssho', - 'ッシュ' => 'sshu', - 'ッチャ' => 'ccha', - 'ッチェ' => 'cche', - 'ッチ' => 'cchi', - 'ッチョ' => 'ccho', - 'ッチュ' => 'cchu', - - // 3 character syllables - doubled vowel and consonants - 'ッバー' => 'bbaa', - 'ッベー' => 'bbee', - 'ッビー' => 'bbii', - 'ッボー' => 'bboo', - 'ッブー' => 'bbuu', - 'ッパー' => 'ppaa', - 'ッペー' => 'ppee', - 'ッピー' => 'ppii', - 'ッポー' => 'ppoo', - 'ップー' => 'ppuu', - 'ッケー' => 'kkee', - 'ッキー' => 'kkii', - 'ッコー' => 'kkoo', - 'ックー' => 'kkuu', - 'ッカー' => 'kkaa', - 'ッガー' => 'ggaa', - 'ッゲー' => 'ggee', - 'ッギー' => 'ggii', - 'ッゴー' => 'ggoo', - 'ッグー' => 'gguu', - 'ッマー' => 'maa', - 'ッメー' => 'mee', - 'ッミー' => 'mii', - 'ッモー' => 'moo', - 'ッムー' => 'muu', - 'ッナー' => 'nnaa', - 'ッネー' => 'nnee', - 'ッニー' => 'nnii', - 'ッノー' => 'nnoo', - 'ッヌー' => 'nnuu', - 'ッラー' => 'rraa', - 'ッレー' => 'rree', - 'ッリー' => 'rrii', - 'ッロー' => 'rroo', - 'ッルー' => 'rruu', - 'ッサー' => 'ssaa', - 'ッセー' => 'ssee', - 'ッシー' => 'sshii', - 'ッソー' => 'ssoo', - 'ッスー' => 'ssuu', - 'ッザー' => 'zzaa', - 'ッゼー' => 'zzee', - 'ッジー' => 'zzii', - 'ッゾー' => 'zzoo', - 'ッズー' => 'zzuu', - 'ッター' => 'ttaa', - 'ッテー' => 'ttee', - 'ッチー' => 'chii', - 'ットー' => 'ttoo', - 'ッツー' => 'ttssuu', - 'ッダー' => 'ddaa', - 'ッデー' => 'ddee', - 'ッヂー' => 'ddii', - 'ッドー' => 'ddoo', - 'ッヅー' => 'dduu', - - // 2 character syllables - normal - 'ファ' => 'fa', - 'フェ' => 'fe', - 'フィ' => 'fi', - 'フォ' => 'fo', - 'フャ' => 'fya', - 'フェ' => 'fye', - 'フィ' => 'fyi', - 'フョ' => 'fyo', - 'フュ' => 'fyu', - 'ヒャ' => 'hya', - 'ヒェ' => 'hye', - 'ヒィ' => 'hyi', - 'ヒョ' => 'hyo', - 'ヒュ' => 'hyu', - 'ビャ' => 'bya', - 'ビェ' => 'bye', - 'ビィ' => 'byi', - 'ビョ' => 'byo', - 'ビュ' => 'byu', - 'ピャ' => 'pya', - 'ピェ' => 'pye', - 'ピィ' => 'pyi', - 'ピョ' => 'pyo', - 'ピュ' => 'pyu', - 'キャ' => 'kya', - 'キェ' => 'kye', - 'キィ' => 'kyi', - 'キョ' => 'kyo', - 'キュ' => 'kyu', - 'ギャ' => 'gya', - 'ギェ' => 'gye', - 'ギィ' => 'gyi', - 'ギョ' => 'gyo', - 'ギュ' => 'gyu', - 'ミャ' => 'mya', - 'ミェ' => 'mye', - 'ミィ' => 'myi', - 'ミョ' => 'myo', - 'ミュ' => 'myu', - 'ニャ' => 'nya', - 'ニェ' => 'nye', - 'ニィ' => 'nyi', - 'ニョ' => 'nyo', - 'ニュ' => 'nyu', - 'リャ' => 'rya', - 'リェ' => 'rye', - 'リィ' => 'ryi', - 'リョ' => 'ryo', - 'リュ' => 'ryu', - 'シャ' => 'sha', - 'シェ' => 'she', - 'シ' => 'shi', - 'ショ' => 'sho', - 'シュ' => 'shu', - 'ジャ' => 'ja', - 'ジェ' => 'je', - 'ジ' => 'ji', - 'ジョ' => 'jo', - 'ジュ' => 'ju', - 'スァ' => 'swa', - 'スェ' => 'swe', - 'スィ' => 'swi', - 'スォ' => 'swo', - 'スゥ' => 'swu', - 'デァ' => 'da', - 'デェ' => 'de', - 'ディ' => 'di', - 'デォ' => 'do', - 'デゥ' => 'du', - 'チャ' => 'cha', - 'チェ' => 'che', - 'チ' => 'chi', - 'チョ' => 'cho', - 'チュ' => 'chu', - 'ヂャ' => 'dya', - 'ヂェ' => 'dye', - 'ヂィ' => 'dyi', - 'ヂョ' => 'dyo', - 'ヂュ' => 'dyu', - 'ツャ' => 'tsa', - 'ツェ' => 'tse', - 'ツィ' => 'tsi', - 'ツョ' => 'tso', - 'ツ' => 'tsu', - 'トァ' => 'twa', - 'トェ' => 'twe', - 'トィ' => 'twi', - 'トォ' => 'two', - 'トゥ' => 'twu', - 'ドァ' => 'dwa', - 'ドェ' => 'dwe', - 'ドィ' => 'dwi', - 'ドォ' => 'dwo', - 'ドゥ' => 'dwu', - 'ウァ' => 'wha', - 'ウェ' => 'whe', - 'ウィ' => 'whi', - 'ウォ' => 'who', - 'ウゥ' => 'whu', - 'ヴャ' => 'vya', - 'ヴェ' => 'vye', - 'ヴィ' => 'vyi', - 'ヴョ' => 'vyo', - 'ヴュ' => 'vyu', - 'ヴァ' => 'va', - 'ヴェ' => 've', - 'ヴィ' => 'vi', - 'ヴォ' => 'vo', - 'ヴ' => 'vu', - 'ウェ' => 'we', - 'ウィ' => 'wi', - 'イェ' => 'ye', - - // 2 character syllables - doubled vocal - 'アー' => 'aa', - 'エー' => 'ee', - 'イー' => 'ii', - 'オー' => 'oo', - 'ウー' => 'uu', - 'ダー' => 'daa', - 'デー' => 'dee', - 'ヂー' => 'dii', - 'ドー' => 'doo', - 'ヅー' => 'duu', - 'ハー' => 'haa', - 'ヘー' => 'hee', - 'ヒー' => 'hii', - 'ホー' => 'hoo', - 'フー' => 'fuu', - 'バー' => 'baa', - 'ベー' => 'bee', - 'ビー' => 'bii', - 'ボー' => 'boo', - 'ブー' => 'buu', - 'パー' => 'paa', - 'ペー' => 'pee', - 'ピー' => 'pii', - 'ポー' => 'poo', - 'プー' => 'puu', - 'ケー' => 'kee', - 'キー' => 'kii', - 'コー' => 'koo', - 'クー' => 'kuu', - 'カー' => 'kaa', - 'ガー' => 'gaa', - 'ゲー' => 'gee', - 'ギー' => 'gii', - 'ゴー' => 'goo', - 'グー' => 'guu', - 'マー' => 'maa', - 'メー' => 'mee', - 'ミー' => 'mii', - 'モー' => 'moo', - 'ムー' => 'muu', - 'ナー' => 'naa', - 'ネー' => 'nee', - 'ニー' => 'nii', - 'ノー' => 'noo', - 'ヌー' => 'nuu', - 'ラー' => 'raa', - 'レー' => 'ree', - 'リー' => 'rii', - 'ロー' => 'roo', - 'ルー' => 'ruu', - 'サー' => 'saa', - 'セー' => 'see', - 'シー' => 'shii', - 'ソー' => 'soo', - 'スー' => 'suu', - 'ザー' => 'zaa', - 'ゼー' => 'zee', - 'ジー' => 'zii', - 'ゾー' => 'zoo', - 'ズー' => 'zuu', - 'ター' => 'taa', - 'テー' => 'tee', - 'チー' => 'chii', - 'トー' => 'too', - 'ツー' => 'tsuu', - 'ワー' => 'waa', - 'ヲー' => 'woo', - 'ヤー' => 'yaa', - 'ヨー' => 'yoo', - 'ユー' => 'yuu', - 'ヱー' => 'wyee', - 'ヰー' => 'wyii', - 'ヵー' => 'kaa', - 'ヶー' => 'kee', - - // 2 character syllables - doubled consonants - 'ッバ' => 'bba', - 'ッベ' => 'bbe', - 'ッビ' => 'bbi', - 'ッボ' => 'bbo', - 'ッブ' => 'bbu', - 'ッパ' => 'ppa', - 'ッペ' => 'ppe', - 'ッピ' => 'ppi', - 'ッポ' => 'ppo', - 'ップ' => 'ppu', - 'ッケ' => 'kke', - 'ッキ' => 'kki', - 'ッコ' => 'kko', - 'ック' => 'kku', - 'ッカ' => 'kka', - 'ッガ' => 'gga', - 'ッゲ' => 'gge', - 'ッギ' => 'ggi', - 'ッゴ' => 'ggo', - 'ッグ' => 'ggu', - 'ッマ' => 'ma', - 'ッメ' => 'me', - 'ッミ' => 'mi', - 'ッモ' => 'mo', - 'ッム' => 'mu', - 'ッナ' => 'nna', - 'ッネ' => 'nne', - 'ッニ' => 'nni', - 'ッノ' => 'nno', - 'ッヌ' => 'nnu', - 'ッラ' => 'rra', - 'ッレ' => 'rre', - 'ッリ' => 'rri', - 'ッロ' => 'rro', - 'ッル' => 'rru', - 'ッサ' => 'ssa', - 'ッセ' => 'sse', - 'ッシ' => 'sshi', - 'ッソ' => 'sso', - 'ッス' => 'ssu', - 'ッザ' => 'zza', - 'ッゼ' => 'zze', - 'ッジ' => 'zzi', - 'ッゾ' => 'zzo', - 'ッズ' => 'zzu', - 'ッタ' => 'tta', - 'ッテ' => 'tte', - 'ッチ' => 'chi', - 'ット' => 'tto', - 'ッツ' => 'ttssu', - 'ッダ' => 'dda', - 'ッデ' => 'dde', - 'ッヂ' => 'ddi', - 'ッド' => 'ddo', - 'ッヅ' => 'ddu', - - // 1 character syllables - 'ア' => 'a', - 'エ' => 'e', - 'イ' => 'i', - 'オ' => 'o', - 'ウ' => 'u', - 'ン' => 'n', - 'ハ' => 'ha', - 'ヘ' => 'he', - 'ヒ' => 'hi', - 'ホ' => 'ho', - 'フ' => 'fu', - 'バ' => 'ba', - 'ベ' => 'be', - 'ビ' => 'bi', - 'ボ' => 'bo', - 'ブ' => 'bu', - 'パ' => 'pa', - 'ペ' => 'pe', - 'ピ' => 'pi', - 'ポ' => 'po', - 'プ' => 'pu', - 'ケ' => 'ke', - 'キ' => 'ki', - 'コ' => 'ko', - 'ク' => 'ku', - 'カ' => 'ka', - 'ガ' => 'ga', - 'ゲ' => 'ge', - 'ギ' => 'gi', - 'ゴ' => 'go', - 'グ' => 'gu', - 'マ' => 'ma', - 'メ' => 'me', - 'ミ' => 'mi', - 'モ' => 'mo', - 'ム' => 'mu', - 'ナ' => 'na', - 'ネ' => 'ne', - 'ニ' => 'ni', - 'ノ' => 'no', - 'ヌ' => 'nu', - 'ラ' => 'ra', - 'レ' => 're', - 'リ' => 'ri', - 'ロ' => 'ro', - 'ル' => 'ru', - 'サ' => 'sa', - 'セ' => 'se', - 'シ' => 'shi', - 'ソ' => 'so', - 'ス' => 'su', - 'ザ' => 'za', - 'ゼ' => 'ze', - 'ジ' => 'zi', - 'ゾ' => 'zo', - 'ズ' => 'zu', - 'タ' => 'ta', - 'テ' => 'te', - 'チ' => 'chi', - 'ト' => 'to', - 'ツ' => 'tsu', - 'ダ' => 'da', - 'デ' => 'de', - 'ヂ' => 'di', - 'ド' => 'do', - 'ヅ' => 'du', - 'ワ' => 'wa', - 'ヲ' => 'wo', - 'ヤ' => 'ya', - 'ヨ' => 'yo', - 'ユ' => 'yu', - 'ヱ' => 'wye', - 'ヰ' => 'wyi', - 'ヵ' => 'ka', - 'ヶ' => 'ke', - - // convert what's left (probably only kicks in when something's missing above - 'ァ' => 'a', - 'ェ' => 'e', - 'ィ' => 'i', - 'ォ' => 'o', - 'ゥ' => 'u', - 'ャ' => 'ya', - 'ョ' => 'yo', - 'ュ' => 'yu', - - // 'ラ'=>'la','レ'=>'le','リ'=>'li','ロ'=>'lo','ル'=>'lu', - // 'チャ'=>'cya','チェ'=>'cye','チィ'=>'cyi','チョ'=>'cyo','チュ'=>'cyu', - // 'デャ'=>'dha','デェ'=>'dhe','ディ'=>'dhi','デョ'=>'dho','デュ'=>'dhu', - // 'リャ'=>'lya','リェ'=>'lye','リィ'=>'lyi','リョ'=>'lyo','リュ'=>'lyu', - // 'テャ'=>'tha','テェ'=>'the','ティ'=>'thi','テョ'=>'tho','テュ'=>'thu', - // 'ファ'=>'fwa','フェ'=>'fwe','フィ'=>'fwi','フォ'=>'fwo','フゥ'=>'fwu', - // 'チャ'=>'tya','チェ'=>'tye','チィ'=>'tyi','チョ'=>'tyo','チュ'=>'tyu', - // 'ジャ'=>'jya','ジェ'=>'jye','ジィ'=>'jyi','ジョ'=>'jyo','ジュ'=>'jyu', - // 'ジャ'=>'zha','ジェ'=>'zhe','ジィ'=>'zhi','ジョ'=>'zho','ジュ'=>'zhu', - // 'ジャ'=>'zya','ジェ'=>'zye','ジィ'=>'zyi','ジョ'=>'zyo','ジュ'=>'zyu', - // 'シャ'=>'sya','シェ'=>'sye','シィ'=>'syi','ショ'=>'syo','シュ'=>'syu', - // 'シ'=>'ci','フ'=>'hu',シ'=>'si','チ'=>'ti','ツ'=>'tu','イ'=>'yi','ヂ'=>'dzi', - - // "Greeklish" - 'Γ' => 'G', - 'Δ' => 'E', - 'Θ' => 'Th', - 'Λ' => 'L', - 'Ξ' => 'X', - 'Π' => 'P', - 'Σ' => 'S', - 'Φ' => 'F', - 'Ψ' => 'Ps', - 'γ' => 'g', - 'δ' => 'e', - 'θ' => 'th', - 'λ' => 'l', - 'ξ' => 'x', - 'π' => 'p', - 'σ' => 's', - 'φ' => 'f', - 'ψ' => 'ps', - - // Thai - 'ก' => 'k', - 'ข' => 'kh', - 'ฃ' => 'kh', - 'ค' => 'kh', - 'ฅ' => 'kh', - 'ฆ' => 'kh', - 'ง' => 'ng', - 'จ' => 'ch', - 'ฉ' => 'ch', - 'ช' => 'ch', - 'ซ' => 's', - 'ฌ' => 'ch', - 'ญ' => 'y', - 'ฎ' => 'd', - 'ฏ' => 't', - 'ฐ' => 'th', - 'ฑ' => 'd', - 'ฒ' => 'th', - 'ณ' => 'n', - 'ด' => 'd', - 'ต' => 't', - 'ถ' => 'th', - 'ท' => 'th', - 'ธ' => 'th', - 'น' => 'n', - 'บ' => 'b', - 'ป' => 'p', - 'ผ' => 'ph', - 'ฝ' => 'f', - 'พ' => 'ph', - 'ฟ' => 'f', - 'ภ' => 'ph', - 'ม' => 'm', - 'ย' => 'y', - 'ร' => 'r', - 'ฤ' => 'rue', - 'ฤๅ' => 'rue', - 'ล' => 'l', - 'ฦ' => 'lue', - 'ฦๅ' => 'lue', - 'ว' => 'w', - 'ศ' => 's', - 'ษ' => 's', - 'ส' => 's', - 'ห' => 'h', - 'ฬ' => 'l', - 'ฮ' => 'h', - 'ะ' => 'a', - 'ั' => 'a', - 'รร' => 'a', - 'า' => 'a', - 'ๅ' => 'a', - 'ำ' => 'am', - 'ํา' => 'am', - 'ิ' => 'i', - 'ี' => 'i', - 'ึ' => 'ue', - 'ี' => 'ue', - 'ุ' => 'u', - 'ู' => 'u', - 'เ' => 'e', - 'แ' => 'ae', - 'โ' => 'o', - 'อ' => 'o', - 'ียะ' => 'ia', - 'ีย' => 'ia', - 'ือะ' => 'uea', - 'ือ' => 'uea', - 'ัวะ' => 'ua', - 'ัว' => 'ua', - 'ใ' => 'ai', - 'ไ' => 'ai', - 'ัย' => 'ai', - 'าย' => 'ai', - 'าว' => 'ao', - 'ุย' => 'ui', - 'อย' => 'oi', - 'ือย' => 'ueai', - 'วย' => 'uai', - 'ิว' => 'io', - '็ว' => 'eo', - 'ียว' => 'iao', - '่' => '', - '้' => '', - '๊' => '', - '๋' => '', - '็' => '', - '์' => '', - '๎' => '', - 'ํ' => '', - 'ฺ' => '', - 'ๆ' => '2', - '๏' => 'o', - 'ฯ' => '-', - '๚' => '-', - '๛' => '-', - '๐' => '0', - '๑' => '1', - '๒' => '2', - '๓' => '3', - '๔' => '4', - '๕' => '5', - '๖' => '6', - '๗' => '7', - '๘' => '8', - '๙' => '9', - - // Korean - 'ㄱ' => 'k', - 'ㅋ' => 'kh', - 'ㄲ' => 'kk', - 'ㄷ' => 't', - 'ㅌ' => 'th', - 'ㄸ' => 'tt', - 'ㅂ' => 'p', - 'ㅍ' => 'ph', - 'ㅃ' => 'pp', - 'ㅈ' => 'c', - 'ㅊ' => 'ch', - 'ㅉ' => 'cc', - 'ㅅ' => 's', - 'ㅆ' => 'ss', - 'ㅎ' => 'h', - 'ㅇ' => 'ng', - 'ㄴ' => 'n', - 'ㄹ' => 'l', - 'ㅁ' => 'm', - 'ㅏ' => 'a', - 'ㅓ' => 'e', - 'ㅗ' => 'o', - 'ㅜ' => 'wu', - 'ㅡ' => 'u', - 'ㅣ' => 'i', - 'ㅐ' => 'ay', - 'ㅔ' => 'ey', - 'ㅚ' => 'oy', - 'ㅘ' => 'wa', - 'ㅝ' => 'we', - 'ㅟ' => 'wi', - 'ㅙ' => 'way', - 'ㅞ' => 'wey', - 'ㅢ' => 'uy', - 'ㅑ' => 'ya', - 'ㅕ' => 'ye', - 'ㅛ' => 'oy', - 'ㅠ' => 'yu', - 'ㅒ' => 'yay', - 'ㅖ' => 'yey' - ); - } -} \ No newline at end of file diff --git a/src/TranslatableTrait.php b/src/TranslatableTrait.php deleted file mode 100644 index 14422dc4..00000000 --- a/src/TranslatableTrait.php +++ /dev/null @@ -1,40 +0,0 @@ -app) && method_exists($this->app, '_')) { - return $this->app->_($message, $parameters, $domain, $locale); - } - - return Translator::instance()->_($message, $parameters, $domain, $locale); - } -} diff --git a/src/Translator/Adapter/Generic.php b/src/Translator/Adapter/Generic.php deleted file mode 100644 index 39ed37b2..00000000 --- a/src/Translator/Adapter/Generic.php +++ /dev/null @@ -1,155 +0,0 @@ -getDefinition($message, $domain ?? 'atk', $locale ?? 'en'); - - if (null === $definition) { - return $message; - } - - $count = $parameters['count'] ?? 1; - - return $this->processMessagePlural($definition, $parameters, $count); - } - - /** - * Return translated string. - * if parameters is not empty will replace tokens. - * - * @param array|string $definition - * @param array|null $parameters - * - * @return string - */ - protected function processMessage($definition, array $parameters = []): string - { - foreach ($parameters as $key => $val) { - $definition = str_replace('{{'.$key.'}}', $val, $definition); - } - - return $definition; - } - - /** - * @param array|string $definition A string of definitions separated by | - * @param array $parameters An array of parameters - * @param int $count Requested plural form - * - * @return string - */ - protected function processMessagePlural($definition, array $parameters = [], int $count = 1): string - { - $definitions_forms = is_array($definition) ? $definition : explode('|', $definition); - $found_definition = null; - switch ((int) $count) { - case 0: - $found_definition = $definitions_forms['zero'] ?? end($definitions_forms); - break; - case 1: - $found_definition = $definitions_forms['one'] ?? null; - break; - default: - $found_definition = $definitions_forms['other'] ?? null; - break; - } - - // if no definition found get the first from array - $definition = $found_definition ?? array_shift($definitions_forms); - - return $this->processMessage($definition, $parameters); - } - - protected function getDefinition(string $message, $domain, ?string $locale) - { - $this->loadDefinitionATK($locale); // need to be called before manual add - - return $this->definitions[$locale][$domain][$message] ?? null; - } - - protected function loadDefinitionATK(string $locale): void - { - if (isset($this->definitions[$locale]['atk'])) { - return; - } - - $this->definitions[$locale]['atk'] = []; - -// if (class_exists('\atk4\data\Locale')) { -// $path = Locale::getPath(); -// $this->addDefinitionFromFile($path.$locale.'/atk.php', $locale, 'atk', 'php-inline'); -// } - } - - /** - * Load definitions from file. - * - * @param string $file - * @param string $locale - * @param string $domain - * @param string $format - * - * @throws \Pluf\Exception - */ - public function addDefinitionFromFile(string $file, string $locale, string $domain, string $format): void - { - $this->loadDefinitionATK($locale); // need to be called before manual add - - $this->readConfig($file, $format); - - $this->definitions = array_replace_recursive( - $this->definitions, - [ - $locale => [ - $domain => $this->config, - ], - ] - ); - - $this->config = []; - } - - /** - * Set or Replace a single definition within a domain. - * - * @param string $key - * @param string|array $definition - * @param string $locale - * @param string $domain - * - * @return $this - */ - public function setDefinitionSingle(string $key, $definition, string $locale = 'en', string $domain = 'atk') - { - $this->loadDefinitionATK($locale); // need to be called before manual add - - if (is_string($definition)) { - $definition = [$definition]; - } - - $this->definitions[$locale][$domain][$key] = $definition; - - return $this; - } -} diff --git a/src/Translator/ITranslatorAdapter.php b/src/Translator/ITranslatorAdapter.php deleted file mode 100644 index 6f7de6e3..00000000 --- a/src/Translator/ITranslatorAdapter.php +++ /dev/null @@ -1,20 +0,0 @@ -_setDefaults($properties); - } - - /** - * @param string $locale - * - * @return Translator - */ - public function setDefaultLocale(string $locale): self - { - $this->default_locale = $locale; - - return $this; - } - - /** - * @param string $default_domain - * - * @return Translator - */ - public function setDefaultDomain(string $default_domain): self - { - $this->default_domain = $default_domain; - - return $this; - } - - /** - * No clone. - * - * @codeCoverageIgnore - */ - protected function __clone() - { - } - - /** - * No serialize. - * - * @codeCoverageIgnore - * - * @throws Exception - */ - public function __wakeup(): void - { - throw new Exception('Translator cannot be serialized'); - } - - /** - * is a singleton and need a method to access the instance. - */ - public static function instance(): self - { - if (null === self::$instance) { - self::$instance = new static(); - } - - return self::$instance; - } - - /** - * Set the Translator Adapter. - * - * @param ITranslatorAdapter $translator - * - * @return Translator - */ - public function setAdapter(ITranslatorAdapter $translator): self - { - $this->adapter = $translator; - - return $this; - } - - /** - * Get the adapter. - * - * @TODO should remain private? - * - * @return ITranslatorAdapter - */ - private function getAdapter(): ITranslatorAdapter - { - if (null === $this->adapter) { - $this->adapter = new Generic(); - } - - return $this->adapter; - } - - /** - * Translate the given message. - * - * @param string $message The message to be translated - * @param array $parameters Array of parameters used to translate message - * @param string|null $domain The domain for the message or null to use the default - * @param string|null $locale The locale or null to use the default - * - * @return string The translated string - */ - public function _($message, array $parameters = [], ?string $domain = null, ?string $locale = null): string - { - return $this->getAdapter()->_($message, $parameters, $domain ?? $this->default_domain, $locale ?? $this->default_locale); - } -} diff --git a/src/Utils.php b/src/Utils.php deleted file mode 100755 index 644a17ec..00000000 --- a/src/Utils.php +++ /dev/null @@ -1,379 +0,0 @@ -. - */ -namespace Pluf; - -/** - * ابزارها و متدهای پرکاربرد - * - * در این کلاس یک سری از متدهای پرکاربرد به صورت متدهای ایستا - * پیاده سازی شده است. - */ -class Utils -{ - - /** - * Produces a random string. - * - * @param - * int Length of the random string to be generated. - * @return string Random string - */ - static function getRandomString($len = 35) - { - $string = ''; - $chars = '0123456789abcdefghijklmnopqrstuvwxyz' . 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%&*()+=-_}{[]> (1000 * 1000 * 1000)) { - $mysize = sprintf('%01.2f', $size / (1000 * 1000 * 1000)) . ' ' . ('GB'); - } elseif ($size > (1000 * 1000)) { - $mysize = sprintf('%01.2f', $size / (1000 * 1000)) . ' ' . ('MB'); - } elseif ($size >= 1000) { - $mysize = sprintf('%01.2f', $size / 1000) . ' ' . ('kB'); - } else { - $mysize = sprintf(_n('%d byte', '%d bytes', $size), $size); - } - return $mysize; - } - - /** - * RFC(2)822 Email Parser - * - * By Cal Henderson - * This code is licensed under a Creative Commons - * Attribution-ShareAlike 2.5 License - * http://creativecommons.org/licenses/by-sa/2.5/ - * Revision 5 - http://www.iamcal.com/publish/articles/php/parsing_email/ - * - * Comments were stripped, check the source for the way this - * parser is built. It is a very interesting reading. - * - * @param - * string Email - * @return bool Is email - */ - static function isValidEmail($email) - { - $email = trim($email); - $n = explode(' ', $email); - if (count($n) > 1) { - return false; - } - $no_ws_ctl = "[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]"; - $alpha = "[\\x41-\\x5a\\x61-\\x7a]"; - $digit = "[\\x30-\\x39]"; - $cr = "\\x0d"; - $lf = "\\x0a"; - $crlf = "($cr$lf)"; - $obs_char = "[\\x00-\\x09\\x0b\\x0c\\x0e-\\x7f]"; - $obs_text = "($lf*$cr*($obs_char$lf*$cr*)*)"; - $text = "([\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f]|$obs_text)"; - $obs_qp = "(\\x5c[\\x00-\\x7f])"; - $quoted_pair = "(\\x5c$text|$obs_qp)"; - $wsp = "[\\x20\\x09]"; - $obs_fws = "($wsp+($crlf$wsp+)*)"; - $fws = "((($wsp*$crlf)?$wsp+)|$obs_fws)"; - $ctext = "($no_ws_ctl|[\\x21-\\x27\\x2A-\\x5b\\x5d-\\x7e])"; - $ccontent = "($ctext|$quoted_pair)"; - $comment = "(\\x28($fws?$ccontent)*$fws?\\x29)"; - $cfws = "(($fws?$comment)*($fws?$comment|$fws))"; - $cfws = "$fws*"; - $atext = "($alpha|$digit|[\\x21\\x23-\\x27\\x2a\\x2b\\x2d\\x2f\\x3d\\x3f\\x5e\\x5f\\x60\\x7b-\\x7e])"; - $atom = "($cfws?$atext+$cfws?)"; - $qtext = "($no_ws_ctl|[\\x21\\x23-\\x5b\\x5d-\\x7e])"; - $qcontent = "($qtext|$quoted_pair)"; - $quoted_string = "($cfws?\\x22($fws?$qcontent)*$fws?\\x22$cfws?)"; - $word = "($atom|$quoted_string)"; - $obs_local_part = "($word(\\x2e$word)*)"; - $obs_domain = "($atom(\\x2e$atom)*)"; - $dot_atom_text = "($atext+(\\x2e$atext+)*)"; - $dot_atom = "($cfws?$dot_atom_text$cfws?)"; - $dtext = "($no_ws_ctl|[\\x21-\\x5a\\x5e-\\x7e])"; - $dcontent = "($dtext|$quoted_pair)"; - $domain_literal = "($cfws?\\x5b($fws?$dcontent)*$fws?\\x5d$cfws?)"; - $local_part = "($dot_atom|$quoted_string|$obs_local_part)"; - $domain = "($dot_atom|$domain_literal|$obs_domain)"; - $addr_spec = "($local_part\\x40$domain)"; - - $done = 0; - while (! $done) { - $new = preg_replace("!$comment!", '', $email); - if (strlen($new) == strlen($email)) { - $done = 1; - } - $email = $new; - } - return preg_match("!^$addr_spec$!", $email) ? true : false; - } - - /** - * Validate an url. - * - * Only the structure is checked, no check of availability of the - * url is performed. It is a really basic validation. - */ - static function isValidUrl($url) - { - $ip = '(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.' . '(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}'; - $dom = '([a-z0-9\.\-]+)'; - return (preg_match('!^(http|https|ftp|gopher)\://(' . $ip . '|' . $dom . ')!i', $url)) ? true : false; - } - - /** - * Convert a whatever separated list of items and returns an array - * of them. - * - * @param - * string Items. - * @param - * string Separator (',') - * @return array Items. - */ - static function itemsToArray($items, $sep = ',') - { - $_t = explode($sep, $items); - $res = array(); - foreach ($_t as $item) { - $item = trim($item); - if (strlen($item) > 0) { - $res[] = $item; - } - } - return $res; - } - - /** - * Run an external program capturing both stdout and stderr. - * - * @credits dk at brightbyte dot de - * @source http://www.php.net/manual/en/function.shell-exec.php - * - * @param - * string Command to run (will be passed to proc_open) - * @param - * &int Return code of the command - * @return mixed false in case of error or output string - */ - public static function runExternal($cmd, &$code) - { - $descriptorspec = array( - // stdin is a pipe that the child will read from - 0 => array( - 'pipe', - 'r' - ), - // stdout is a pipe that the child will write to - 1 => array( - 'pipe', - 'w' - ), - // stderr is a file to write to - 2 => array( - 'pipe', - 'w' - ) - ); - $pipes = array(); - $process = proc_open($cmd, $descriptorspec, $pipes); - $output = ''; - if (! is_resource($process)) - return false; - fclose($pipes[0]); // close child's input imidiately - stream_set_blocking($pipes[1], false); - stream_set_blocking($pipes[2], false); - // $todo = array( - // $pipes[1], - // $pipes[2] - // ); - while (true) { - $read = array(); - if (! feof($pipes[1])) - $read[] = $pipes[1]; - if (! feof($pipes[2])) - $read[] = $pipes[2]; - if (! $read) - break; - $write = $ex = array(); - $ready = stream_select($read, $write, $ex, 2); - if ($ready === false) { - break; // should never happen - something died - } - foreach ($read as $r) { - $s = fread($r, 1024); - $output .= $s; - } - } - fclose($pipes[1]); - fclose($pipes[2]); - $code = proc_close($process); - return $output; - } - - /** - * URL safe base 64 encoding. - * - * Compatible with python base64's urlsafe methods. - * - * @link http://www.php.net/manual/en/function.base64-encode.php#63543 - */ - public static function urlsafe_b64encode($string) - { - return str_replace(array( - '+', - '/', - '=' - ), array( - '-', - '_', - '' - ), base64_encode($string)); - } - - /** - * URL safe base 64 decoding. - */ - public static function urlsafe_b64decode($string) - { - $data = str_replace(array( - '-', - '_' - ), array( - '+', - '/' - ), $string); - $mod4 = strlen($data) % 4; - if ($mod4) { - $data .= substr('====', $mod4); - } - return base64_decode($data); - } - - /** - * Flatten an array. - * - * @param array $array - * The array to flatten. - * @return array - */ - public static function flattenArray($array) - { - $result = array(); - foreach (new \RecursiveIteratorIterator(new \RecursiveArrayIterator($array)) as $value) { - $result[] = $value; - } - - return $result; - } -} diff --git a/src/Views.php b/src/Views.php deleted file mode 100755 index 340658bf..00000000 --- a/src/Views.php +++ /dev/null @@ -1,99 +0,0 @@ -. - */ -namespace Pluf; - -use Pluf\HTTP\Request; -use Pluf\HTTP\Response; -use Pluf\HTTP\Response\Redirect; - -/** - * Basic Pluf View - * - * To start a module as fast as possible, you need a basic views such as model - * CRUD and list. Here is a list of a basic views which are very common in your - * desing. This class is a collection of utilities to develop a new view. - */ -class Views -{ - - /** - * Simple redirection view. - * - * @param - * Request Request object - * @param - * array Match - * @param - * string Redirection URL (not a view) - */ - public function redirectTo(Request $request, array $match, string $url): Redirect - { - return new Response\Redirect($url); - } - - /** - * Simple content view. - * - * The content is a text and will be route to the client directly. - * - * @param - * Request Request object - * @param - * array Match - * @param - * string Content of the page - */ - function simpleContent(Request $request, array $match, $content): Response - { - return new Response($content); - } - - /* - * fetch model from input request - * - * 1. model is defined directly in parameters - * 2. ? - */ - private static function getItemModelName(Request $request, array $match, array $params): string - { - if (! isset($params['model'])) { - throw new Exception('The model class was not provided in the parameters.'); - } - return $params['model']; - } - - /** - * Creates a template and returns as result - * - * @param \Pluf\HTTP\Request $request - * @param array $match - * @return \Pluf\HTTP\Response - */ - function loadTemplate($request, $match) - { - $template = $match[1]; - $extra_context = array(); - // create and show a template - $context = new Template\Context\Request($request, $extra_context); - $tmpl = new Template($template); - return new Response($tmpl->render($context)); - } -} - - diff --git a/src/Views/AbstractCollectionView.php b/src/Views/AbstractCollectionView.php deleted file mode 100644 index 7763e687..00000000 --- a/src/Views/AbstractCollectionView.php +++ /dev/null @@ -1,7 +0,0 @@ -getItem($request, $match, $p); - // Do - $storage = ObjectStorage::getInstance(); - - return ResponseFileBuilder::getInstance()->setAbsloutPath($storage->getAbsloutPath($item)) - ->setFilename($storage->getMimeType($item)) - ->setMimetype($storage->getMimeType($item)) - ->setResumable(true) - ->deleteOnEnd(false) - ->build(); - } - - /** - * Upload a file as content - * - * @param Request $request - * @param array $match - * @return Object model - */ - public function upload($request, $match, $p) - { - $item = $this->getItem($request, $match, $p); - // // Do action - // if (array_key_exists('file', $request->FILES)) { - // $extra = array( - // 'model' => $object - // ); - // $form = new Pluf_Form_ModelBinaryUpdate(array_merge($request->REQUEST, $request->FILES), $extra); - // $item = $form->save(); - // return $item; - // } else { - // $myfile = fopen($item->getAbsloutPath(), "w") or die("Unable to open file!"); - // $entityBody = file_get_contents('php://input', 'r'); - // fwrite($myfile, $entityBody); - // fclose($myfile); - // $item->update(); - // } - return $item; - } -} - diff --git a/src/Views/ItemCollectionView.php b/src/Views/ItemCollectionView.php deleted file mode 100644 index 5b9a18ea..00000000 --- a/src/Views/ItemCollectionView.php +++ /dev/null @@ -1,108 +0,0 @@ -getSchema(); - } - - /** - * Create an object (Part of the CRUD series). - * - * The minimal extra parameter is the model class name. The list - * of extra parameters is: - * - * 'model' - Class name string, required. - * - * 'extra_context' - Array of key/values to be added to the - * context (array()) - * - * 'extra_form' - Array of key/values to be added to the - * form generation (array()) - * - * 'template' - Template to use ('"model class"_create_form.html') - * - * @param - * Request Request object - * @param - * array Match - * @param - * array Extra parameters - */ - public function putItems($request, $match, $p) - { - $default = array( - 'extra_context' => array(), - 'extra_form' => array() - ); - $p = array_merge($default, $p); - // Set the default - $model = self::CRUD_getModel($p); - $object = $model instanceof Model ? $model : new $model(); - // Read body of request - // $entityBody = file_get_contents('php://input', 'r'); - // Check if body is a json array - // Convert each item to an object model by using Form - // Save models - $form = Pluf_Shortcuts_GetFormForModel($object, $request->REQUEST, $p['extra_form']); - $object = $form->save(); - - return self::CRUD_response($request, $p, $object); - } - - /** - * List objects (Part of the CRUD series). - * - * @param Request $request - * @param array $match - */ - public function getItems($request, $match, $p = array()) - { -// // Create page -// $builder = new Pluf_Paginator_Builder(self::CRUD_getModelInstance($p)); -// if (array_key_exists('sql', $p)) { -// if ($p['sql'] instanceof SQL) { -// $builder->setWhereClause($p['sql']); -// } else { -// $builder->setWhereClause(new SQL($p['sql'])); -// } -// } -// if (array_key_exists('listFilters', $p)) { -// $builder->setDisplayList($p['listFilters']); -// } -// if (array_key_exists('searchFields', $p)) { -// $builder->setSearchFields($p['searchFields']); -// } -// if (array_key_exists('sortFields', $p)) { -// $builder->setSortFields($p['sortFields']); -// } -// if (array_key_exists('view', $p)) { -// $builder->setView($p['view']); -// } -// $builder->setRequest($request); -// return $builder->build(); - } -} - diff --git a/src/Views/ItemView.php b/src/Views/ItemView.php deleted file mode 100644 index de81cdc2..00000000 --- a/src/Views/ItemView.php +++ /dev/null @@ -1,207 +0,0 @@ - array( -// * 'My_Precondition_Class::precondFunc', -// * 'My_Precondition_Class::precondFunc2' -// * ) -// * ); -// * -// * Value of 'precond' could be array or a single item. -// * -// * Each precondition function will be called with three argument respectively as following: -// * - $request: Pluf_HTTp_Request -// * - $object: \Pluf\Data\Model -// * - $parent: \Pluf\Data\Model, which is a parent of $object model -// * -// */ -// protected function checkPreconditions(Request $request, array $match, array $params, Object $item): void -// { -// if (! isset($params['precond'])) { -// return; -// } -// $preconds = $params['precond']; -// if (! is_array($preconds)) { -// $preconds = array( -// $preconds -// ); -// } -// foreach ($preconds as $precond) { -// $res = call_user_func_array(explode('::', $precond), [ -// $request, -// $match, -// $params, -// $item -// ]); -// if ($res !== true) { -// // TODO: maso, 2020: throw error number -// throw new Error403('CRUD precondition is not satisfied.'); -// } -// } -// } - -// /* -// * Finds item with the $modelId -// */ -// protected function getObjectOr404(string $modelName, $modelId) -// { -// $items = Pluf::getDataRepository($modelName)->get(new Query([ -// 'filter' => [ -// [ -// 'id', -// '=', -// $modelId -// ] -// ] -// ])); -// if (sizeof($items) == 0) { -// throw new Error404('Request resource with ID:' . $modelId . ' not found'); -// } -// return $items[0]; -// } - -// /** -// * Access an item -// * -// * -// * 'model' - Class name string, required. -// * -// * -// * 'modelId' - Id of of the current model to update -// * -// * @param -// * Request Request object -// * @param -// * array Match -// * @param -// * array Extra parameters -// * @return object Response object (can be a redirect) -// */ -// public function getItem(Request $request, array $match, array $params) -// { -// // Set the default -// $modelName = self::getModelName($params); -// $modelId = $match['modelId']; -// $item = self::getObjectOr404($modelName, $modelId); -// self::checkPreconditions($request, $match, $params, $item); -// return $item; -// } - -// /** -// * Delete an object (Part of the CRUD series). -// * -// * The minimal extra parameter is the model class name. The list -// * of extra parameters is: -// * -// * 'model' - Class name string, required. -// * -// * 'post_delete_redirect' - View to redirect after saving, required. -// * -// * 'id' - Index in the match to fin the id of the object to delete (1) -// * -// * 'login_required' - Do we require login (false) -// * -// * 'template' - Template to use ('"model class"_confirm_delete.html') -// * -// * 'post_delete_redirect_keys' - Which keys of the model to pass to -// * the view (array()) -// * -// * 'extra_context' - Array of key/values to be added to the -// * context (array()) -// * -// * Other extra parameters may be as follow: -// * -// * 'permanently' - if is exist and its value is false the object will not be deleted permanently and -// * only the `deleted` field of that will be set to true. Note that this option assumes that the removing -// * object has a feild named `deleted` -// * -// * @param Request $request -// * object -// * @param array $match -// * @param array $p -// * parameters -// * @return Object deleted item -// */ -// public function deleteItem(Request $request, array $match, array $params) -// { -// $item = self::getItem($request, $match, $params); -// $item->delete(); -// return $item; -// } - -// /** -// * Update an object (Part of the CRUD series). -// * -// * The minimal extra parameter is the model class name. The list -// * of extra parameters is: -// * -// * 'model' - Class name string, required. -// * -// * 'model_id' - Id of of the current model to update -// * -// * 'extra_context' - Array of key/values to be added to the -// * context (array()) -// * -// * 'extra_form' - Array of key/values to be added to the -// * form generation (array()) -// * -// * 'template' - Template to use ('"model class"_update_form.html') -// * -// * @param Request $request -// * object -// * @param array $match -// * @param array $p -// * parameters -// * @return Object updated item -// */ -// public function updateItem(Request $request, array $match, array $params) -// { -// $item = self::getItem($request, $match, $params); -// $objectName = get_class($item); -// $mapper = ObjectMapper::getInstance($request); -// if (! $mapper->hasMore()) { -// throw new Error403('No item in request to update'); -// } -// $newItem = $mapper->next(new $objectName()); -// $newItem->id = $item->id; -// ObjectValidator::getInstance()->check($newItem); -// $item = Pluf::getDataRepository($objectName)->update($newItem); -// return $item; -// } -// } - diff --git a/src/Views/ManyToManyCollectionView.php b/src/Views/ManyToManyCollectionView.php deleted file mode 100644 index 6003f21e..00000000 --- a/src/Views/ManyToManyCollectionView.php +++ /dev/null @@ -1,7 +0,0 @@ -__get($key) !== $parent->id) { - // throw new \Pluf\HTTP\Error404('Invalid relation'); - // } - // } - public function getItems(Request $request, array $match, array $p) - { - if (array_key_exists('parentId', $request->REQUEST)) { - $parentId = $request->REQUEST['parentId']; - } else { - $parentId = $match['parentId']; - } - $sql = new SQL($p['parentKey'] . '=%s', $parentId); - if (isset($p['sql'])) { - $sqlMain = new SQL($p['sql']); - $sql = $sqlMain->SAnd($sql); - } - $p['sql'] = $sql; - return $this->findObject($request, $match, $p); - } - - /** - * Clear Many to on relations - * - * @param \Pluf\HTTP\Request $request - * @param array $match - * @param array $p - * @return NULL - */ - public function clearManyToOne(Request $request, $match = array(), $p = array()) - { - // XXX: clean list - return null; - } - - public function get($request, $match, $p) - { - // Set the default - if (array_key_exists('modelId', $request->REQUEST)) { - $modelId = $request->REQUEST['modelId']; - } else { - $modelId = $match['modelId']; - } - $object = Pluf_Shortcuts_GetObjectOr404(self::CRUD_getModel($p), $modelId); - if (array_key_exists('parentId', $request->REQUEST)) { - $parentId = $request->REQUEST['parentId']; - } else { - $parentId = $match['parentId']; - } - $parent = Pluf_Shortcuts_GetObjectOr404(self::CRUD_getParentModel($p), $parentId); - // TODO: maso, 2017: assert relation - self::CRUD_assertManyToOneRelation($parent, $object, $p); - self::CRUD_checkPreconditions($request, $p, $object, $parent); - return self::CRUD_response($request, $p, $object); - } - - public function createManyToOne($request, $match, $p) - { - if (array_key_exists('parentId', $request->REQUEST)) { - $parentId = $request->REQUEST['parentId']; - } else { - $parentId = $match['parentId']; - } - $parent = Pluf_Shortcuts_GetObjectOr404(self::CRUD_getParentModel($p), $parentId); - - $default = array( - 'extra_context' => array(), - 'extra_form' => array() - ); - $p = array_merge($default, $p); - // Set the default - $model = self::CRUD_getModel($p); - $object = $model instanceof Model ? $model : new $model(); - $form = Pluf_Shortcuts_GetFormForModel($object, $request->REQUEST, $p['extra_form']); - $object = $form->save(false); - $object->{$p['parentKey']} = $parent; - $object->create(); - - return self::CRUD_response($request, $p, $object); - } - - public function updateManyToOne($request, $match, $p) - { - if (array_key_exists('parentId', $request->REQUEST)) { - $parentId = $request->REQUEST['parentId']; - } else { - $parentId = $match['parentId']; - } - $parent = Pluf_Shortcuts_GetObjectOr404(self::CRUD_getParentModel($p), $parentId); - - $default = array( - 'extra_context' => array(), - 'extra_form' => array() - ); - $p = array_merge($default, $p); - // Set the default - $model = self::CRUD_getModel($p); - $object = Pluf_Shortcuts_GetObjectOr404($model, $match['modelId']); - // TODO: maso, 2017: check relateion - self::CRUD_assertManyToOneRelation($parent, $object, $p); - self::CRUD_checkPreconditions($request, $p, $object, $parent); - $form = Pluf_Shortcuts_GetFormForUpdateModel($object, $request->REQUEST, $p['extra_form']); - $object = $form->save(); - return self::CRUD_response($request, $p, $object); - } - - public function deleteManyToOne($request, $match, $p) - { - if (array_key_exists('parentId', $request->REQUEST)) { - $parentId = $request->REQUEST['parentId']; - } else { - $parentId = $match['parentId']; - } - $parent = Pluf_Shortcuts_GetObjectOr404(self::CRUD_getParentModel($p), $parentId); - - $default = array( - 'extra_context' => array(), - 'extra_form' => array() - ); - $p = array_merge($default, $p); - // Set the default - $model = self::CRUD_getModel($p); - $object = Pluf_Shortcuts_GetObjectOr404($model, $match['modelId']); - // TODO: maso, 2017: check relateion - self::CRUD_assertManyToOneRelation($parent, $object, $p); - $objectCopy = Pluf_Shortcuts_GetObjectOr404($model, $match['modelId']); - $objectCopy->id = 0; - self::CRUD_checkPreconditions($request, $p, $object, $parent); - $object->delete(); - return self::CRUD_response($request, $p, $objectCopy); - } -} - diff --git a/src/bootstrap.php b/src/bootstrap.php deleted file mode 100755 index 74dd81c6..00000000 --- a/src/bootstrap.php +++ /dev/null @@ -1,376 +0,0 @@ -. - */ -use Pluf\Cache; -use Pluf\Module; -use Pluf\Options; -use Pluf\Data\ModelDescription; -use Pluf\Data\Repository; -use Pluf\Data\Schema; -use Pluf\Db\Connection; -use Pluf\Db\Connection\Dumper; -use Pluf\HTTP\Request; - -/** - * The main class of the framework. - * From where all start. - * - * The __autoload function is automatically set. - */ -class Pluf -{ - - public static ?Options $options = null; - - public static ?Cache $cache = null; - - public static ?Connection $dbConnection = null; - - public static ?Schema $dataSchema = null; - - /** - * Start the framework - * - * @param - * string Configuration file to use - */ - public static function start($config) - { - self::$cache = null; - self::$dbConnection = null; - self::$options = null; - self::$dataSchema = null; - - // Load configurations - $GLOBALS['_PX_starttime'] = microtime(true); - $GLOBALS['_PX_uniqid'] = uniqid($GLOBALS['_PX_starttime'], true); - $GLOBALS['_PX_signal'] = array(); - $GLOBALS['_PX_locale'] = array(); - - // Load options - if (is_array($config)) { - $GLOBALS['_PX_config'] = $config; - } else if (false !== ($file = self::fileExists($config))) { - $GLOBALS['_PX_config'] = require $file; - } else { - throw new Exception('Configuration file does not exist: ' . $config); - } - self::$options = new Options($GLOBALS['_PX_config']); - - // Load the relations for each installed application. Each - // application folder must be in the include path. - // ModelUtils::loadRelations(! Pluf::getConfig('debug', false)); - - date_default_timezone_set(self::getConfig('time_zone', 'UTC')); - mb_internal_encoding(self::getConfig('encoding', 'UTF-8')); - mb_regex_encoding(self::getConfig('encoding', 'UTF-8')); - - // Load modules - Module::loadModules(); - } - - /** - * - * @deprecated - */ - public static function f($cfg, $default = '') - { - if (isset($GLOBALS['_PX_config'][$cfg])) { - return $GLOBALS['_PX_config'][$cfg]; - } - return $default; - } - - /** - * - * @deprecated - */ - public static function pf(string $prefix, $strip = false) - { - return self::getConfigByPrefix($prefix, $strip); - } - - /** - * Gets system configuration - * - * @param string $key - * Configuration key - * @param - * mixed Possible default value if value is not set ('') - * @return mixed Configuration variable or default value if not defined. - */ - public static function getConfig(string $key, $default = '') - { - if (! isset(self::$options)) { - return $default; - } - $val = self::$options->$key; - if (isset($val)) { - return $val; - } - return $default; - } - - /** - * Access an array of configuration variables having a given - * prefix. - * - * @param - * string Prefix. - * @param - * bool Strip the prefix from the keys (false). - * @return array Configuration variables. - */ - public static function getConfigByPrefix(string $prefix, bool $strip = false) - { - return self::$options->startsWith($prefix, $strip); - } - - /** - * Returns a given object. - * - * - * Loads automatically the corresponding class file if needed. - * If impossible to get the class $model, exception is thrown. - * - * @param - * string Model to load. - * @param - * mixed Extra parameters for the constructor of the model. - */ - public static function factory($model, $params = null) - { - if ($params !== null) { - return new $model($params); - } - return new $model(); - } - - /** - * Load a class depending on its name. - * - * Throw an exception if not possible to load the class. - * - * @param - * string Class to load. - */ - public static function loadClass($class) - { - if (class_exists($class, false)) { - return; - } - $file = str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php'; - if (! file_exists(stream_resolve_include_path($file))) { - return; - } - include_once $file; - if (class_exists($class, false) || interface_exists($class, false)) { - return; - } - $error = 'Impossible to load the class: ' . $class . "\n" . 'Tried to include: ' . $file . "\n" . 'Include path: ' . get_include_path(); - throw new Exception($error); - } - - /** - * Load a function depending on its name. - * - * The implementation file of the function - * MyApp_Youpla_Boum_Stuff() is MyApp/Youpla/Boum.php That way it - * is possible to group all the related function in one file. - * - * Throw an exception if not possible to load the function. - * - * @param - * string Function to load. - * - * @deprecated will ber removed in next version - */ - public static function loadFunction($function) - { - if (function_exists($function)) { - return; - } - $elts = explode('_', $function); - array_pop($elts); - $file = implode(DIRECTORY_SEPARATOR, $elts) . '.php'; - if (false !== ($file = Pluf::fileExists($file))) { - include $file; - } - if (! function_exists($function)) { - throw new Exception('Impossible to load the function: ' . $function); - } - } - - /** - * Hack for [[php file_exists()]] that checks the include_path. - * - * Use this to see if a file exists anywhere in the include_path. - * - * - * $file = 'path/to/file.php'; - * if (Pluf::fileExists('path/to/file.php')) { - * include $file; - * } - * - * - * @credits Paul M. Jones - * - * @param string $file - * Check for this file in the include_path. - * @return mixed Full path to the file if the file exists and - * is readable in the include_path, false if not. - */ - public static function fileExists($file) - { - $file = trim($file); - if (! $file) { - return false; - } - // using an absolute path for the file? - // dual check for Unix '/' and Windows '\', - // or Windows drive letter and a ':'. - $abs = ($file[0] == '/' || $file[0] == '\\' || $file[1] == ':'); - if ($abs && file_exists($file)) { - return $file; - } - // using a relative path on the file - $path = explode(PATH_SEPARATOR, get_include_path()); - foreach ($path as $dir) { - // strip Unix '/' and Windows '\' - $target = rtrim($dir, '\\/') . DIRECTORY_SEPARATOR . $file; - try { - if (file_exists($target)) { - return $target; - } - } catch (Exception $e) {} - } - // never found it - return false; - } - - /** - * Helper to load the default database connection. - * - * This method is just dispatching to the function define in the - * configuration by the 'db_get_connection' key or use the default - * 'Pluf_DB_getConnection'. If you want to use your own function, - * take a look at the Pluf_DB_getConnection function to use the - * same approach for your method. - * - * The extra parameters can be used to selectively connect to a - * given database. When the ORM is getting a connection, it is - * passing the current model as parameter. That way you could get - * different databases for different models. - * - * @param - * mixed Extra parameters. - * @return resource DB connection. - */ - /** - * Get the default DB connection. - * - * The default database connection is defined in the configuration file - * through the following configuration variables: - * - db_login : Login to connect to the database - * - db_password : Password to the database - * - db_server : Name of the server - * - db_database : Name of the database - * - db_table_prefix : Prefix for the table names - * - db_version : Version of the database engine - * - db_engine : Engine for exampe 'MySQL', 'SQLite' - * - * Once the first connection is created the following calls to Pluf::db() - * are getting the same connection. - */ - public static function &db($extra = null) - { - if (! isset(self::$dbConnection)) { - $options = self::getConfigByPrefix('db_', true); - self::$dbConnection = Connection::connect($options->dsn, $options->user, $options->passwd); - if ($options->dumper) { - $optionsDumper = $options->startsWith('dumber_', true); - $optionsDumper->connection = self::$dbConnection; - self::$dbConnection = new Dumper($optionsDumper); - } - } - return self::$dbConnection; - } - - /** - * Get Curretn request - * - * @deprecated This will removed in the next major version. Pleas use Request::getCurrent() - * @return Request|NULL - */ - public static function getCurrentRequest() - { - return Request::getCurrent(); - } - - public static function getCache() - { - if (! isset(self::$cache)) { - // load cache - self::$cache = Cache::getInstance(self::getConfigByPrefix('cache_', true)); - } - return self::$cache; - } - - public static function getDataSchema(): Schema - { - if (! isset(self::$dataSchema)) { - self::$dataSchema = Schema::getInstance(self::getConfigByPrefix('data_schema_', true)); - } - return self::$dataSchema; - } - - /** - * Gets new instance of repository - * - * @param mixed $option - * @return \Pluf\Data\Repository\ModelRepository | \Pluf\Data\Repository\RelationRepository - */ - public static function getDataRepository($option): Repository - { - // XXX: maso, 2020: adding cache manager for repository - if (is_array($option)) { - $options = new Options($option); - } else if ($option instanceof \Pluf\Options) { - $options = $option; - } else { - $options = new Options(); - } - - if (is_string($option)) { - $options->model = $option; - } - - if ($option instanceof ModelDescription) { - $options->model = $option->type; - } - if ($option instanceof \Pluf\Data\Model) { - $options->model = get_class($option); - } - - $options->connection = self::db(); - $options->schema = self::getDataSchema(); - - return Repository::getInstance($options); - } -} - diff --git a/tests/Cache/ApcuTest.php b/tests/Cache/ApcuTest.php deleted file mode 100755 index 4a7cdd86..00000000 --- a/tests/Cache/ApcuTest.php +++ /dev/null @@ -1,168 +0,0 @@ -. - */ -namespace Pluf\Test\Cache; - -use PHPUnit\Framework\TestCase; -use Pluf\Cache; -use Pluf\Options; -use stdClass; - -class ApceTest extends TestCase -{ - - /** - * - * @test - */ - public function createNewInstance() - { - $cache = Cache::getInstance(new Options([ - 'engine' => 'apcu' - ])); - $this->assertNotNull($cache); - } - - /** - * - * @test - */ - public function testBasic() - { - $options = new Options([ - 'timeout' => 300 - ]); - $cache = new Cache\Apcu($options); - $this->assertNotNull($cache); - - $var = 'foo1'; - $key = 'test1'; - - $cache->set($key, $var); - $this->assertEquals($var, $cache->get($key)); - } - - /** - * - * @test - */ - public function testGetUnknownKey() - { - $options = new Options([ - 'timeout' => 300 - ]); - $cache = new Cache\Apcu($options); - $this->assertNotNull($cache); - - $this->assertNull($cache->get('unknown')); - } - - /** - * - * @test - */ - public function testGetDefault() - { - $options = new Options([ - 'timeout' => 300 - ]); - $cache = new Cache\Apcu($options); - $this->assertNotNull($cache); - - $this->assertEquals('default', $cache->get('unknown', 'default')); - } - - /** - * - * @test - */ - public function testSerialized() - { - $arrayData = [ - 'hello' => 'world', - 'foo' => false, - 0 => array( - 'foo', - 'bar' - ) - ]; - $options = new Options([ - 'timeout' => 300 - ]); - $cache = new Cache\Apcu($options); - $this->assertNotNull($cache); - - $cache->set('array', $arrayData); - $this->assertEquals($arrayData, $cache->get('array')); - - $obj = new stdClass(); - $obj->foo = 'bar'; - $obj->hello = 'world'; - $success = $cache->set('object', $obj); - $this->assertTrue($success); - $this->assertEquals($obj, $cache->get('object')); - - unset($obj); - $this->assertInstanceOf(stdClass::class, $cache->get('object')); - $this->assertEquals('world', $cache->get('object')->hello); - } - - /** - * - * @test - */ - public function testSerializedObject() - { - $object = new MyObject(); - $options = new Options([ - 'timeout' => 300 - ]); - $cache = new Cache\Apcu($options); - $this->assertNotNull($cache); - - $cache->set('obj', $object); - $this->assertEquals($object, $cache->get('obj')); - } -} - -class MyObject -{ - - private $var1 = 'hi'; - - protected $var2 = 'me'; - - public $v3 = [ - 'hello' => 'world', - 'foo' => false, - 0 => array( - 'foo', - 'bar' - ) - ]; - - function getVar1() - { - return $this->var1; - } - - function getVar2() - { - return $this->var2; - } -} diff --git a/tests/Cache/BasicTest.php b/tests/Cache/BasicTest.php deleted file mode 100644 index 38600fcf..00000000 --- a/tests/Cache/BasicTest.php +++ /dev/null @@ -1,21 +0,0 @@ -assertNotNull(Pluf::getCache()); - $this->assertTrue(Pluf::getCache() instanceof Pluf\Cache); - } -} - diff --git a/tests/Cache/FileTest.php b/tests/Cache/FileTest.php deleted file mode 100755 index f7d36024..00000000 --- a/tests/Cache/FileTest.php +++ /dev/null @@ -1,153 +0,0 @@ -. - */ -namespace Pluf\Test\Cache; - -use PHPUnit\Framework\TestCase; -use Pluf\Cache; -use Pluf\Options; -use stdClass; - -class FileTest extends TestCase -{ - - private $_config; - - private $_arrayData = array( - 'hello' => 'world', - 'foo' => false, - 0 => array( - 'foo', - 'bar' - ) - ); - - /** - * - * @before - */ - public function setUpTest() - { - if (! array_key_exists('_PX_config', $GLOBALS)) { - $GLOBALS['_PX_config'] = array(); - } - $this->_config = $GLOBALS['_PX_config']; // backup - $GLOBALS['_PX_config']['cache_engine'] = 'Pluf_Cache_File'; - $GLOBALS['_PX_config']['cache_timeout'] = 5; - $GLOBALS['_PX_config']['cache_file_folder'] = '/tmp/pluf_unittest_cache'; - } - - /** - * - * @after - */ - public function tearDownTest() - { - $GLOBALS['_PX_config'] = $this->_config; - } - - /** - * - * @test - */ - public function testConstructor() - { - $options = new Options([ - 'engine' => 'file' - ]); - $cache = Cache::getInstance($options); - $this->assertNotNull($cache); - } - - /** - * - * @test - */ - public function testBasic() - { - $options = new Options([ - 'engine' => 'file' - ]); - $cache = Cache::getInstance($options); - $this->assertNotNull($cache); - - $var = 'foo1'; - $key = 'test1'; - - $cache->set($key, $var); - $this->assertEquals($var, $cache->get($key)); - } - - /** - * - * @test - */ - public function testGetUnknownKey() - { - $options = new Options([ - 'engine' => 'file' - ]); - $cache = Cache::getInstance($options); - $this->assertNotNull($cache); - - $this->assertNull($cache->get('unknown')); - } - - /** - * - * @test - */ - public function testGetDefault() - { - $options = new Options([ - 'engine' => 'file' - ]); - $cache = Cache::getInstance($options); - $this->assertNotNull($cache); - - $this->assertEquals('default', $cache->get('unknown', 'default')); - } - - /** - * - * @test - */ - public function testSerialized() - { - $options = new Options([ - 'engine' => 'file' - ]); - $cache = Cache::getInstance($options); - $this->assertNotNull($cache); - - $success = $cache->set('array', $this->_arrayData); - $this->assertTrue($success); - $this->assertEquals($this->_arrayData, $cache->get('array')); - - $obj = new stdClass(); - $obj->foo = 'bar'; - $obj->hello = 'world'; - $success = $cache->set('object', $obj); - $this->assertTrue($success); - $this->assertEquals($obj, $cache->get('object')); - - unset($obj); - $this->assertInstanceOf(stdClass::class, $cache->get('object')); - $this->assertEquals('world', $cache->get('object')->hello); - } -} diff --git a/tests/Data/OldPlufModelTest.php b/tests/Data/OldPlufModelTest.php deleted file mode 100644 index b5b87e7b..00000000 --- a/tests/Data/OldPlufModelTest.php +++ /dev/null @@ -1,655 +0,0 @@ - 'sqlite::memory:', - 'db_user' => null, - 'db_password' => null, - 'db_dumper' => false, - 'data_schema_engine' => 'sqlite', - 'data_schema_sqlite_prefix' => 'db' . rand() . '_' - ]); - } - - /** - * - * @test - */ - public function testCreateAModel() - { - $model = new \Pluf\NoteBook\Book(); - $model->title = 'title' . rand(); - - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($model)); - - $this->assertTrue($model->create()); - $this->assertFalse($model->isAnonymous()); - } - - /** - * - * @test - */ - public function testGetOneById() - { - $model = new \Pluf\NoteBook\Book(); - $model->title = 'title' . rand(); - - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($model)); - $this->assertTrue($model->create()); - - $model2 = new \Pluf\NoteBook\Book($model->id); - $this->assertFalse($model2->isAnonymous()); - $this->assertEquals($model->title, $model2->title); - } - - /** - * - * @test - */ - public function testGetModel() - { - $model = new \Pluf\NoteBook\Book(); - $model->title = 'title' . rand(); - - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($model)); - $this->assertTrue($model->create()); - - $model2 = new \Pluf\NoteBook\Book(); - $model2->get($model->id); - $this->assertFalse($model2->isAnonymous()); - $this->assertEquals($model->title, $model2->title); - } - - /** - * - * @test - */ - public function testGetOneModel() - { - $model = new \Pluf\NoteBook\Book(); - $model->title = 'title' . rand(); - - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($model)); - $this->assertTrue($model->create()); - - $model2 = new \Pluf\NoteBook\Book(); - $model2->get($model->id); - $this->assertFalse($model2->isAnonymous()); - $this->assertEquals($model->title, $model2->title); - } - - /** - * - * @test - */ - public function testGetItemByWhere() - { - $model = new \Pluf\NoteBook\Book(); - $model->title = 'title' . rand(); - - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($model)); - $this->assertTrue($model->create()); - - // 1 - $list = $model->getList([ - 'filter' => [ - [ - 'id', - $model->id - ] - ] - ]); - $this->assertEquals(1, sizeof($list)); - - $model2 = $list[0]; - $model2->get($model->id); - $this->assertFalse($model2->isAnonymous()); - $this->assertEquals($model->title, $model2->title); - - // 2 - $list = $model->getList([ - 'filter' => [ - [ - 'title', - $model->title - ] - ] - ]); - $this->assertEquals(1, sizeof($list)); - - $model2 = $list[0]; - $model2->get($model->id); - $this->assertFalse($model2->isAnonymous()); - $this->assertEquals($model->title, $model2->title); - - // 3 - $list = $model->getList([ - 'filter' => [ - [ - 'id', - $model->id - ], - [ - 'title', - $model->title - ] - ] - ]); - $this->assertEquals(1, sizeof($list)); - - $model2 = $list[0]; - $model2->get($model->id); - $this->assertFalse($model2->isAnonymous()); - $this->assertEquals($model->title, $model2->title); - } - - /** - * - * @test - */ - public function testGetItemByWhereAndOrder() - { - $model = new \Pluf\NoteBook\Book(); - $model->title = 'title' . rand(); - - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($model)); - $this->assertTrue($model->create()); - $this->assertTrue($model->create()); - - // 1 - $list = $model->getList([ - 'filter' => [ - [ - 'title', - $model->title - ] - ], - 'order' => [ - 'id' => Query::ORDER_ASC - ] - ]); - $this->assertEquals(2, sizeof($list)); - $this->assertTrue($list[0]->id < $list[1]->id); - - // 1 - $list = $model->getList([ - 'filter' => [ - [ - 'title', - $model->title - ] - ], - 'order' => [ - 'id' => Query::ORDER_DESC - ] - ]); - $this->assertEquals(2, sizeof($list)); - $this->assertTrue($list[0]->id > $list[1]->id); - } - - /** - * - * @test - */ - public function testGetItemByQuery() - { - $model = new \Pluf\NoteBook\Book(); - $model->title = 'title xxx' . rand(); - - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($model)); - $this->assertTrue($model->create()); - - // 1 - $list = $model->getList([ - 'filter' => [ - [ - 'title', - 'like', - '%xxx%' - ] - ] - ]); - $this->assertEquals(1, sizeof($list)); - - // 1 - $list = $model->getList([ - 'filter' => [ - [ - 'title', - 'like', - '%yyy%' - ] - ] - ]); - $this->assertEquals(0, sizeof($list)); - } - - /** - * - * @test - */ - public function testGetItemByQueryLimit() - { - $model = new \Pluf\NoteBook\Book(); - $model->title = 'title xxx' . rand(); - - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($model)); - // crate 5 item - $this->assertTrue($model->create()); - $this->assertTrue($model->create()); - $this->assertTrue($model->create()); - $this->assertTrue($model->create()); - $this->assertTrue($model->create()); - - // 1 - $list = $model->getList([ - 'start' => 0, - 'limit' => 2 - ]); - $this->assertEquals(2, sizeof($list)); - - // 2 - $list = $model->getList([ - 'start' => 4, - 'limit' => 2 - ]); - $this->assertEquals(1, sizeof($list)); - - // 3 - $list = $model->getList([ - 'start' => 0, - 'limit' => 10 - ]); - $this->assertEquals(5, sizeof($list)); - } - - /** - * - * @test - */ - public function testGetItemCount() - { - $model = new \Pluf\NoteBook\Book(); - $model->title = 'title xxx' . rand(); - - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($model)); - // crate 5 item - $this->assertTrue($model->create()); - $this->assertTrue($model->create()); - $this->assertTrue($model->create()); - $this->assertTrue($model->create()); - $this->assertTrue($model->create()); - - // 1 - $count = $model->getList([ - 'count' => true - ]); - $this->assertEquals(5, $count); - - // 3 - $count = $model->getList([ - 'filter' => [ - [ - 'id', - '<', - 2 - ] - ], - 'count' => true - ]); - $this->assertEquals(1, $count); - } - - /** - * - * @test - */ - public function testGetEmptyBooksByView() - { - $book = new \Pluf\NoteBook\Book(); - $item = new \Pluf\NoteBook\Item(); - - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($book)); - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($item)); - - // crate 5 item - $book->title = 'title xxx' . rand(); - $this->assertTrue($book->create()); - - // 1 - $count = $book->getList([ - 'count' => true - ]); - $this->assertEquals(1, $count); - - // 3 - // XXX: maso, - $list = $book->getList([ - 'view' => 'empty' - ]); - $this->assertEquals(1, count($list)); - - $list = $book->getList([ - 'view' => 'nonEmpty' - ]); - $this->assertEquals(0, count($list)); - } - - /** - * - * @test - */ - public function testGetOneItemByView() - { - $book = new \Pluf\NoteBook\Book(); - $item = new \Pluf\NoteBook\Item(); - - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($book)); - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($item)); - - // crate 5 item - $book->title = 'title xxx' . rand(); - $this->assertTrue($book->create()); - - // 1 - $itemOne = $book->getOne([ - // 'view' => 'empty', - 'filter' => [ - [ - 'title', - $book->title - ] - ] - ]); - $this->assertEquals($itemOne->title, $book->title); - } - - /** - * - * @test - */ - public function testGetCountFromModel() - { - $book = new \Pluf\NoteBook\Book(); - $item = new \Pluf\NoteBook\Item(); - - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($book)); - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($item)); - - // crate 5 item - $book->title = 'title xxx' . rand(); - $this->assertTrue($book->create()); - - $book->title = 'title xxx' . rand(); - $this->assertTrue($book->create()); - - // 1 - $count = $book->getCount([ - 'filter' => [ - [ - 'title', - $book->title - ] - ] - ]); - $this->assertEquals(1, $count); - } - - /** - * - * @test - */ - public function testUpdateModel() - { - $book = new \Pluf\NoteBook\Book(); - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($book)); - - // crate 5 item - $book->title = 'title xxx' . rand(); - $this->assertTrue($book->create()); - - $book->title = 'title yyy' . rand(); - $this->assertTrue($book->update()); - - $book2 = new \Pluf\NoteBook\Book($book->id); - - $this->assertEquals($book->title, $book2->title); - } - - /** - * - * @test - */ - public function testDeleteModel() - { - $book = new \Pluf\NoteBook\Book(); - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($book)); - - // crate 5 item - $book->title = 'title xxx' . rand(); - $this->assertTrue($book->create()); - $this->assertTrue($book->delete()); - - $bookList = $book->getList(); - - $this->assertEquals(0, count($bookList)); - } - - /** - * - * @test - */ - public function gettingBookFromItem() - { - $book = new \Pluf\NoteBook\Book(); - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($book)); - - $item = new \Pluf\NoteBook\Item(); - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($item)); - - // crate 5 item - $item->title = 'title xxx' . rand(); - $this->assertTrue($item->create()); - - $book = $item->get_book(); - $this->assertNull($book); - } - - /** - * - * @test - */ - public function gettingListOfItemsFromBook() - { - $book = new \Pluf\NoteBook\Book(); - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($book)); - - $item = new \Pluf\NoteBook\Item(); - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($item)); - - // crate 5 item - $book->title = 'title xxx' . rand(); - $this->assertTrue($book->create()); - - $items = $book->get_items_list(); - $this->assertTrue(is_array($items)); - $this->assertEquals(0, count($items)); - } - - /** - * - * @test - */ - public function gettingListOfTagsFromBook() - { - $book = new \Pluf\NoteBook\Book(); - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($book)); - - $item = new \Pluf\NoteBook\Item(); - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($item)); - - $tag = new \Pluf\NoteBook\Tag(); - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($tag)); - - // crate 5 item - $tag->title = 'tag xxx' . rand(); - $this->assertTrue($tag->create()); - - $items = $tag->get_books_list(); - $this->assertTrue(is_array($items)); - $this->assertEquals(0, count($items)); - } - - /** - * - * @test - */ - public function gettingSetAssocOfTagsFromBook() - { - $book = new \Pluf\NoteBook\Book(); - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($book)); - - $item = new \Pluf\NoteBook\Item(); - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($item)); - - $tag = new \Pluf\NoteBook\Tag(); - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($tag)); - - // crate 5 item - $tag->title = 'tag xxx' . rand(); - $this->assertTrue($tag->create()); - - $items = $tag->get_books_list(); - $this->assertTrue(is_array($items)); - $this->assertEquals(0, count($items)); - - $book = new \Pluf\NoteBook\Book(); - $book->title = 'book xxx' . rand(); - $this->assertTrue($book->create()); - - $tag->setAssoc($book, 'books'); - $items = $tag->get_books_list(); - $this->assertTrue(is_array($items)); - $this->assertEquals(1, count($items)); - - $book = new \Pluf\NoteBook\Book(); - $book->title = 'book xxx' . rand(); - $this->assertTrue($book->create()); - - $tag->setAssoc($book, 'books'); - $items = $tag->get_books_list(); - $this->assertTrue(is_array($items)); - $this->assertEquals(2, count($items)); - - $items = $book->get_tags_list(); - $this->assertTrue(is_array($items)); - $this->assertEquals(1, count($items)); - } - - /** - * - * @test - */ - public function gettingDelAssocOfTagsFromBook() - { - $book = new \Pluf\NoteBook\Book(); - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($book)); - - $item = new \Pluf\NoteBook\Item(); - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($item)); - - $tag = new \Pluf\NoteBook\Tag(); - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($tag)); - - // crate 5 item - $tag->title = 'tag xxx' . rand(); - $this->assertTrue($tag->create()); - - $book1 = new \Pluf\NoteBook\Book(); - $book1->title = 'book xxx' . rand(); - $this->assertTrue($book1->create()); - $tag->setAssoc($book1, 'books'); - - $book2 = new \Pluf\NoteBook\Book(); - $book2->title = 'book xxx' . rand(); - $this->assertTrue($book2->create()); - $tag->setAssoc($book2, 'books'); - - $items = $tag->get_books_list(); - $this->assertTrue(is_array($items)); - $this->assertEquals(2, count($items)); - - $tag->delAssoc($book1, 'books'); - $items = $tag->get_books_list(); - $this->assertTrue(is_array($items)); - $this->assertEquals(1, count($items)); - - $tag->delAssoc($book2, 'books'); - $items = $tag->get_books_list(); - $this->assertTrue(is_array($items)); - $this->assertEquals(0, count($items)); - } - - /** - * - * @test - */ - public function settingItemsBookWithObjectAndId() - { - $book = new \Pluf\NoteBook\Book(); - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($book)); - - $item = new \Pluf\NoteBook\Item(); - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($item)); - - // crate 5 item - $book->title = 'title xxx' . rand(); - $this->assertTrue($book->create()); - - // crate 5 item - $item->title = 'title xxx' . rand(); - $item->book = $book; - $this->assertTrue($item->create()); - - $items = $book->get_items_list(); - $this->assertTrue(is_array($items)); - $this->assertEquals(1, count($items)); - } -} - diff --git a/tests/Data/PlufModelMTTest.php b/tests/Data/PlufModelMTTest.php deleted file mode 100755 index ceddcd71..00000000 --- a/tests/Data/PlufModelMTTest.php +++ /dev/null @@ -1,356 +0,0 @@ -. - */ -namespace Pluf\Test\Data; - -use PHPUnit\Framework\TestCase; -use Pluf\Pluf\Tenant; -use Pluf\Relation\ManyToManyTwo; -use Pluf\Relation\Model; -use Pluf\Relation\ModelCount; -use Pluf\Relation\ModelRecurse; -use Pluf\Relation\RelatedToTestModel; -use Pluf\Relation\RelatedToTestModel2; -use Pluf; - -class PlufModelMTTest extends TestCase -{ - - /** - * - * @beforeClass - */ - public static function createDataBase() - { - $conf = include __DIR__ . '/../conf/config.php'; - $conf['multitenant'] = true; - Pluf::start($conf); - $m = new \Pluf\Migration(); - $m->install(); - - // Test tenant - $tenant = new Tenant(); - $tenant->domain = 'localhost'; - $tenant->subdomain = 'www'; - $tenant->validate = true; - $tenant->create(); - self::assertFalse($tenant->isAnonymous()); - $m->init($tenant); - - Tenant::setCurrent($tenant); - } - - /** - * - * @afterClass - */ - public static function removeDatabses() - { - $m = new \Pluf\Migration(); - $m->uninstall(); - } - - /** - * - * @test - */ - public function testSetModelField() - { - $model = new Model(); - $model->title = 'myvalue'; - $this->assertEquals('myvalue', $model->title); - } - - /** - * - * @test - */ - public function testTestModelRecurse() - { - $model = new ModelRecurse(); - $model->title = 'myvalue'; - $this->assertEquals('myvalue', $model->title); - $model->create(); - - $model2 = new ModelRecurse(); - $model2->title = 'child'; - $model2->parent_id = $model; - $model2->create(); - $this->assertFalse($model2->isAnonymous()); - - $a = $model->get_children_list(); - $this->assertEquals($a[0]->title, 'child'); - } - - /** - * - * @test - */ - public function testCreateTestModel() - { - $model = new Model(); - $model->title = 'my title'; - $model->description = 'A small desc.'; - $model->create(); - $this->assertFalse($model->isAnonymous()); - $this->assertEquals(1, (int) $model->id); - } - - /** - * - * @test - */ - public function testGetTestModel() - { - $model = new Model(); - $model->title = 'my title'; - $model->description = 'A small desc.'; - $model->create(); - $m = new Model(1); - $this->assertEquals('my title', $m->title); - } - - /** - * - * @test - */ - public function testUpdateTestModel() - { - $model = new Model(); - $model->title = 'my title'; - $model->description = 'A small desc.'; - $model->create(); - $model = new Model(1); - $model->description = 'A small desc 2.'; - $this->assertEquals(true, $model->update()); - $this->assertEquals('A small desc 2.', $model->description); - } - - /** - * - * @test - */ - public function testDeleteTestModel() - { - $model = new Model(); - $model->title = 'my title'; - $model->description = 'A small desc.'; - $model->create(); - $this->assertFalse($model->isAnonymous()); - $id = $model->id; - $this->assertEquals(true, $model->delete()); - $this->assertTrue($model->isAnonymous()); - - $model = new Model($id); - $this->assertTrue($model->isAnonymous()); - } - - /** - * - * @test - */ - public function testGetListTestModel() - { - for ($i = 0; $i < 10; $i ++) { - $model = new ModelCount(); - $model->title = 'title ' . $i; - $model->description = 'A small desc ' . $i; - $model->create(); - } - $model->title = 'update to have 11 records and 10 head'; - $model->update(); - $m = new ModelCount(); - $models = $m->getList(); - $this->assertEquals(10, count($models)); - $this->assertEquals('title 0', $models[0]->title); - $this->assertEquals('title 5', $models[5]->title); - - foreach ($models as $item) { - $this->assertTrue($item->delete()); - } - } - - /** - * - * @test - */ - public function testGetCountModel() - { - for ($i = 0; $i < 10; $i ++) { - $model = new ModelCount(); - $model->title = 'title ' . $i; - $model->description = 'A small desc ' . $i; - $model->create(); - } - $model->title = 'update to have 11 records and 10 head'; - $model->update(); - $m = new ModelCount(); - $this->assertEquals(10, $m->getCount()); - - $models = $m->getList(); - foreach ($models as $item) { - $this->assertTrue($item->delete()); - } - } - - /** - * - * @test - */ - public function testRelatedTestModel() - { - $model = new Model(); - $model->title = 'title'; - $model->description = 'A small desc '; - $this->assertEquals(true, $model->create()); - - $m = new RelatedToTestModel(); - $m->testmodel = $model; - $m->dummy = 'stupid values'; - $this->assertEquals(true, $m->create()); - - $rel = $model->get_testmodel_list(); - $this->assertEquals('stupid values', $rel[0]->dummy); - $mod = $m->get_testmodel(); - $this->assertEquals('title', $mod->title); - } - - /** - * - * @test - */ - public function testLimitRelatedTestModel() - { - $model = new Model(); - $model->title = 'title'; - $model->description = 'A small desc '; - $this->assertEquals(true, $model->create()); - - $m = new RelatedToTestModel(); - $m->testmodel = $model; - $m->dummy = 'stupid values'; - $this->assertEquals(true, $m->create()); - - $m = new RelatedToTestModel(); - $m->testmodel = $model; - $m->dummy = 'stupid values 2'; - $this->assertEquals(true, $m->create()); - - $m = new RelatedToTestModel(); - $m->testmodel = $model; - $m->dummy = 'stupid values 3'; - $this->assertEquals(true, $m->create()); - $rel = $model->get_testmodel_list(array( - 'filter' => [ - [ - "dummy", - "=", - "stupid values 2" - ] - ] - )); - $this->assertEquals('stupid values 2', $rel[0]->dummy); - $this->assertEquals(1, count($rel)); - $rel = $model->get_testmodel_list(); - $this->assertEquals(3, count($rel)); - } - - /** - * - * @test - */ - public function testManyRelatedTestModel() - { - $tm1 = new Model(); - $tm1->title = 'title tm1'; - $tm1->description = 'A small desc tm1'; - $tm1->create(); - $tm2 = new Model(); - $tm2->title = 'title tm2'; - $tm2->description = 'A small desc tm2'; - $tm2->create(); - $tm3 = new Model(); - $tm3->title = 'title tm3'; - $tm3->description = 'A small desc tm3'; - $tm3->create(); - - $rm1 = new RelatedToTestModel2(); - $rm1->testmodel_1 = $tm1; - $rm1->testmodel_2 = $tm2; - $rm1->dummy = 'stupid values rm1'; - $rm1->create(); - - $rm2 = new RelatedToTestModel2(); - $rm2->testmodel_1 = $tm1; - $rm2->testmodel_2 = $tm2; - $rm2->dummy = 'stupid values rm2'; - $rm2->create(); - - $rm3 = new RelatedToTestModel2(); - $rm3->testmodel_1 = $tm1; - $rm3->testmodel_2 = $tm3; - $rm3->dummy = 'stupid values rm3'; - $rm3->create(); - - $rel = $tm1->get_first_rttm_list(); - $this->assertEquals(3, count($rel)); - $this->assertEquals('stupid values rm1', $rel[0]->dummy); - - $rel = $tm2->get_first_rttm_list(); - $this->assertEquals(0, count($rel)); - - $rel = $tm2->get_second_rttm_list(); - $this->assertEquals(2, count($rel)); - $this->assertEquals('stupid values rm2', $rel[1]->dummy); - - $rel = $tm3->get_second_rttm_list(); - $this->assertEquals(1, count($rel)); - $this->assertEquals('stupid values rm3', $rel[0]->dummy); - - $tm1bis = $rm2->get_testmodel_1(); - $this->assertEquals('title tm1', $tm1bis->title); - } - - /** - * - * @test - */ - public function testRelatedToNotCreatedTestModel() - { - $m2 = new ManyToManyTwo(); - $m2->two = 'two is the best'; - $rel = $m2->get_ones_list(); - $this->assertNotEquals(false, $rel); - $this->assertEquals(0, count($rel)); - } - - // /** - // * XXX: maso, 2020: check if this process must fail - // * @expectedException Exception - // * @test - // */ - // public function testExceptionOnProperty() - // { - // $model = new Model(); - // $model->title = 'title'; - // $model->description = 'A small desc '; - // $this->assertEquals(true, $model->create()); - // // $rel = $model->should_fail; - // } -} - diff --git a/tests/Data/PlufModelRestAPITest.php b/tests/Data/PlufModelRestAPITest.php deleted file mode 100755 index 94cbf72b..00000000 --- a/tests/Data/PlufModelRestAPITest.php +++ /dev/null @@ -1,80 +0,0 @@ -. - */ -namespace Pluf\Test\Data; - -use PHPUnit\Framework\TestCase; -use Pluf\Dispatcher; -use Pluf\Module; -use Pluf\HTTP\Request; -use Pluf; - -class PlufModelRestAPITest extends TestCase -{ - - /** - * - * @beforeClass - */ - public static function createDataBase() - { - $conf = include __DIR__ . '/../conf/config.php'; - $conf['view_api_prefix'] = '/api/test/prefix' . rand(); - Pluf::start($conf); - $m = new \Pluf\Migration(); - $m->install(); - } - - /** - * - * @afterClass - */ - public static function removeDatabses() - { - $m = new \Pluf\Migration(); - $m->uninstall(); - } - - /** - * - * @test - */ - public function getInvalidRequestWithRandomPrefix() - { - $res = Dispatcher::getInstance()->setViews(Module::loadControllers())->dispatch(new Request('/helloword/HelloWord')); - $this->assertNotNull($res); - $this->assertEquals(404, $res->getStatusCode()); - } - - /** - * - * @test - */ - public function getValidRequestWithRandomPrefix() - { - $query = Pluf::getConfig('view_api_prefix') . '/helloword/HelloWord'; - - $this->assertNotNull(Dispatcher::getInstance()->setViews(Module::loadControllers()) - ->dispatch(new Request($query))); - - $this->assertEquals(200, Dispatcher::getInstance()->setViews(Module::loadControllers()) - ->dispatch(new Request($query)) - ->getStatusCode()); - } -} - diff --git a/tests/Data/PlufModelTest.php b/tests/Data/PlufModelTest.php deleted file mode 100755 index 4cf7c8d0..00000000 --- a/tests/Data/PlufModelTest.php +++ /dev/null @@ -1,358 +0,0 @@ -. - */ -namespace Pluf\Test\Data; - -use PHPUnit\Framework\TestCase; -use Pluf\Relation\ManyToManyTwo; -use Pluf\Relation\Model; -use Pluf\Relation\ModelCount; -use Pluf\Relation\ModelRecurse; -use Pluf\Relation\RelatedToTestModel; -use Pluf\Relation\RelatedToTestModel2; -use Pluf; - -class PlufModelTest extends TestCase -{ - - /** - * - * @beforeClass - */ - public static function createDataBase() - { - $conf = include __DIR__ . '/../conf/config.php'; - $conf['installed_apps'] = [ - 'Relation' - ]; - Pluf::start($conf); - - $m = new \Pluf\Migration(); - $m->install(); - } - - /** - * - * @afterClass - */ - public static function removeDatabses() - { - $m = new \Pluf\Migration(); - $m->uninstall(); - } - - /** - * - * @test - */ - public function testSetModelField() - { - $model = new Model(); - $model->title = 'myvalue'; - $this->assertEquals('myvalue', $model->title); - } - - /** - * - * @test - */ - public function testTestModelRecurse() - { - $model = new ModelRecurse(); - $model->title = 'myvalue'; - $this->assertEquals('myvalue', $model->title); - $model->create(); - - $model2 = new ModelRecurse(); - $model2->title = 'child'; - $model2->parent_id = $model; - $model2->create(); - $this->assertFalse($model2->isAnonymous()); - - $a = $model->get_children_list(); - $this->assertEquals($a[0]->title, 'child'); - } - - /** - * - * @test - */ - public function testCreateTestModel() - { - $model = new Model(); - $model->title = 'my title'; - $model->description = 'A small desc.'; - $model->create(); - $this->assertFalse($model->isAnonymous()); - } - - /** - * - * @test - */ - public function testGetTestModel() - { - $model = new Model(); - $model->title = 'my title'; - $model->description = 'A small desc.'; - $model->create(); - - $m = new Model($model->id); - $this->assertEquals($model->title, $m->title); - } - - /** - * - * @test - */ - public function testUpdateTestModel() - { - $model = new Model(); - $model->title = 'my title'; - $model->description = 'A small desc.'; - $model->create(); - $model = new Model(1); - $model->description = 'A small desc 2.'; - $this->assertEquals(true, $model->update()); - $this->assertEquals('A small desc 2.', $model->description); - } - - /** - * - * @test - */ - public function testDeleteTestModel() - { - $model = new Model(); - $model->title = 'my title'; - $model->description = 'A small desc.'; - $model->create(); - $this->assertFalse($model->isAnonymous()); - $id = $model->id; - $this->assertEquals(true, $model->delete()); - $this->assertTrue($model->isAnonymous()); - - $model = new Model($id); - $this->assertTrue($model->isAnonymous()); - } - - /** - * - * @test - */ - public function testGetListTestModel() - { - for ($i = 0; $i < 10; $i ++) { - $model = new ModelCount(); - $model->title = 'title ' . $i; - $model->description = 'A small desc ' . $i; - $model->create(); - } - $model->title = 'update to have 11 records and 10 head'; - $model->update(); - $m = new ModelCount(); - $models = $m->getList(); - $this->assertEquals(10, count($models)); - $this->assertEquals('title 0', $models[0]->title); - $this->assertEquals('title 5', $models[5]->title); - - foreach ($models as $item) { - $this->assertTrue($item->delete()); - } - } - - /** - * - * @test - */ - public function testGetCountModel() - { - for ($i = 0; $i < 10; $i ++) { - $model = new ModelCount(); - $model->title = 'title ' . $i; - $model->description = 'A small desc ' . $i; - $model->create(); - } - $model->title = 'update to have 11 records and 10 head'; - $model->update(); - $m = new ModelCount(); - $this->assertEquals(10, $m->getCount()); - - $models = $m->getList(); - foreach ($models as $item) { - $this->assertTrue($item->delete()); - } - } - - /** - * - * @test - */ - public function testRelatedTestModel() - { - $model = new Model(); - $model->title = 'title'; - $model->description = 'A small desc '; - $model->create(); - $this->assertFalse($model->isAnonymous()); - - $m = new RelatedToTestModel(); - $m->testmodel = $model; - $m->dummy = 'stupid values'; - $m->create(); - $this->assertFalse($m->isAnonymous()); - - $rel = $model->get_related_list(); - $this->assertEquals('stupid values', $rel[0]->dummy); - $mod = $m->get_testmodel(); - $this->assertEquals('title', $mod->title); - } - - /** - * - * @test - */ - public function testLimitRelatedTestModel() - { - $model = new Model(); - $model->title = 'title'; - $model->description = 'A small desc '; - $model->create(); - $this->assertFalse($model->isAnonymous()); - - $m = new RelatedToTestModel(); - $m->testmodel = $model; - $m->dummy = 'stupid values'; - $m->create(); - $this->assertFalse($m->isAnonymous()); - - $m = new RelatedToTestModel(); - $m->testmodel = $model; - $m->dummy = 'stupid values 2'; - $m->create(); - $this->assertFalse($m->isAnonymous()); - - $m = new RelatedToTestModel(); - $m->testmodel = $model; - $m->dummy = 'stupid values 3'; - $m->create(); - $this->assertFalse($m->isAnonymous()); - - $rel = $model->get_related_list([ - 'filter' => [ - [ - 'dummy', - '=', - 'stupid values 2' - ] - ] // "dummy='stupid values 2'" - ]); - $this->assertEquals('stupid values 2', $rel[0]->dummy); - $this->assertEquals(1, count($rel)); - $rel = $model->get_related_list(); - $this->assertEquals(3, count($rel)); - } - - /** - * - * @test - */ - public function testManyRelatedTestModel() - { - $tm1 = new Model(); - $tm1->title = 'title tm1'; - $tm1->description = 'A small desc tm1'; - $tm1->create(); - - $tm2 = new Model(); - $tm2->title = 'title tm2'; - $tm2->description = 'A small desc tm2'; - $tm2->create(); - - $tm3 = new Model(); - $tm3->title = 'title tm3'; - $tm3->description = 'A small desc tm3'; - $tm3->create(); - - $rm1 = new RelatedToTestModel2(); - $rm1->testmodel_1 = $tm1; - $rm1->testmodel_2 = $tm2; - $rm1->dummy = 'stupid values rm1'; - $rm1->create(); - - $rm2 = new RelatedToTestModel2(); - $rm2->testmodel_1 = $tm1; - $rm2->testmodel_2 = $tm2; - $rm2->dummy = 'stupid values rm2'; - $rm2->create(); - - $rm3 = new RelatedToTestModel2(); - $rm3->testmodel_1 = $tm1; - $rm3->testmodel_2 = $tm3; - $rm3->dummy = 'stupid values rm3'; - $rm3->create(); - - $rel = $tm1->get_first_rttm_list(); - $this->assertEquals(3, count($rel)); - $this->assertEquals('stupid values rm1', $rel[0]->dummy); - - $rel = $tm2->get_first_rttm_list(); - $this->assertEquals(0, count($rel)); - - $rel = $tm2->get_second_rttm_list(); - $this->assertEquals(2, count($rel)); - $this->assertEquals('stupid values rm2', $rel[1]->dummy); - - $rel = $tm3->get_second_rttm_list(); - $this->assertEquals(1, count($rel)); - $this->assertEquals('stupid values rm3', $rel[0]->dummy); - - $tm1_2 = new Model($tm1->id); - $this->assertEquals($tm1->id, $tm1_2->id); - - $tm1bis = $rm2->get_testmodel_1(); - $this->assertEquals('title tm1', $tm1bis->title); - } - - /** - * - * @test - */ - public function testRelatedToNotCreatedTestModel() - { - $m2 = new ManyToManyTwo(); - $m2->two = 'two is the best'; - $rel = $m2->get_ones_list(); - $this->assertNotEquals(false, $rel); - $this->assertEquals(0, count($rel)); - } - - /** - * - * @expectedException Exception - * @test - */ - public function testExceptionOnProperty() - { - $model = new Model(); - // $model->title = 'title'; - $model->description = 'A small desc '; - $this->assertEquals(true, $model->create()); - } -} - diff --git a/tests/Data/QueryBuilder/GeoPaginatorTest.php b/tests/Data/QueryBuilder/GeoPaginatorTest.php deleted file mode 100755 index d9fc8afe..00000000 --- a/tests/Data/QueryBuilder/GeoPaginatorTest.php +++ /dev/null @@ -1,100 +0,0 @@ -. -// */ -// use PHPUnit\Framework\TestCase; -// use Pluf\HTTP\Request; - -// require_once dirname(__FILE__) . '/MyGeoModel.php'; - -// /** -// * -// * @backupGlobals disabled -// * @backupStaticAttributes disabled -// */ -// class Pluf_Paginator_GeoPaginatorTest extends TestCase -// { - -// /** -// * -// * @before -// */ -// protected function setUpTest() -// { -// Pluf::start(__DIR__ . '/../conf/config.php'); -// $dbEngine = Pluf::f('db_engine'); -// if (strcasecmp($dbEngine, 'MySQL') !== 0) { -// $this->markTestSkipped('Test could be run only on MySql database'); -// } - -// $db = Pluf::db(); -// $schema = $db->getSchema(); -// $m1 = new Pluf_Paginator_MyGeoModel(); -// $schema->model = $m1; -// $schema->dropTables(); -// $schema->createTables(); -// for ($i = 0; $i < 11; $i ++) { -// $m = new Pluf_Paginator_MyGeoModel(); -// $m->title = 'My title ' . $i; -// $m->location = 'POINT(' . $i . ' ' . $i . ')'; -// $m->create(); -// } -// } - -// /** -// * -// * @after -// */ -// protected function tearDownTest() -// { -// $db = Pluf::db(); -// $schema = $db->getSchema(); -// $m1 = new Pluf_Paginator_MyGeoModel(); -// $schema->model = $m1; -// $schema->dropTables(); -// } - -// /** -// * Test filter from request -// * -// * @test -// */ -// public function testSetFromRequestFilter() -// { -// $_REQUEST = array( -// '_px_fk' => 'location', -// '_px_fv' => 'POLYGON ((0 0, 12 0, 12 12, 0 12, 0 0))' -// ); -// $request = new Request('/test'); - -// $pag = new Pluf_Paginator(new Pluf_Paginator_MyGeoModel()); -// $fields = array( -// 'id', -// 'title', -// 'location' -// ); -// $pag->configure($fields, $fields); -// $pag->list_filters = $fields; -// $pag->items_per_page = 30; -// $pag->setFromRequest($request); - -// $result = $pag->render_object(); -// $this->assertTrue(is_array($result)); -// $this->assertTrue(array_key_exists('items', $result)); -// $this->assertEquals(11, sizeof($result['items'])); -// } -// } diff --git a/tests/Data/QueryBuilder/PaginatorTest.php b/tests/Data/QueryBuilder/PaginatorTest.php deleted file mode 100755 index 0d2b51c4..00000000 --- a/tests/Data/QueryBuilder/PaginatorTest.php +++ /dev/null @@ -1,606 +0,0 @@ -. -// */ -// use PHPUnit\Framework\TestCase; -// use Pluf\HTTP\Request; - -// require_once dirname(__FILE__) . '/MyModel.php'; - -// /** -// * -// * @backupGlobals disabled -// * @backupStaticAttributes disabled -// */ -// class Pluf_Paginator_PaginatorTest extends TestCase -// { - -// /** -// * -// * @before -// */ -// protected function setUpTest() -// { -// Pluf::start(__DIR__ . '/../conf/config.php'); -// $engine = Pluf::db(); -// $schema = $engine->getSchema(); - -// $m1 = new Pluf_Paginator_MyModel(); - -// \Pluf\Migration::dropTables($engine, $schema, $m1); -// \Pluf\Migration::createTables($engine, $schema, $m1); - -// for ($i = 1; $i < 11; $i ++) { -// $m = new Pluf_Paginator_MyModel(); -// $m->title = 'My title ' . $i; -// $m->description = 'My description ' . $i; -// $m->int_field = $i; -// $m->float_field = $i; -// $m->create(); -// } -// } - -// /** -// * -// * @after -// */ -// protected function tearDownTest() -// { -// $engine = Pluf::db(); -// $schema = $engine->getSchema(); - -// $m1 = new Pluf_Paginator_MyModel(); - -// \Pluf\Migration::dropTables($engine, $schema, $m1); -// } - -// /** -// * Test simple pagination -// * -// * @test -// */ -// public function testSimplePaginate() -// { -// $pag = new Pluf_Paginator(new Pluf_Paginator_MyModel()); -// $pag->items_per_page = 5; -// $this->assertTrue(is_array($pag->render_object())); -// $this->assertTrue(array_key_exists('items', $pag->render_object())); -// } - -// /** -// * Test single sort order -// * -// * @test -// */ -// public function testSingleSortPaginate() -// { -// $pag = new Pluf_Paginator(new Pluf_Paginator_MyModel()); -// $fields = array( -// 'id', -// 'title', -// 'description' -// ); -// $pag->configure($fields, $fields); -// $pag->list_filters = $fields; -// $pag->items_per_page = 5; -// $pag->sort_order = array( -// 'id', -// 'ASC' -// ); - -// $this->assertTrue(is_array($pag->render_object())); -// $this->assertTrue(array_key_exists('items', $pag->render_object())); -// } - -// /** -// * Test multi sort order -// * -// * @test -// */ -// public function testMultiSortPaginate() -// { -// $pag = new Pluf_Paginator(new Pluf_Paginator_MyModel()); -// $fields = array( -// 'id', -// 'title', -// 'description' -// ); -// $pag->configure($fields, $fields); -// $pag->list_filters = $fields; -// $pag->items_per_page = 5; -// $pag->sort_order = array( -// array( -// 'id', -// 'ASC' -// ), -// array( -// 'title', -// 'ASC' -// ) -// ); - -// $this->assertTrue(is_array($pag->render_object())); -// $this->assertTrue(array_key_exists('items', $pag->render_object())); -// } - -// /** -// * Test multi sort order -// * -// * @test -// */ -// public function testSortOrderFunctionPaginate() -// { -// $item1 = new Pluf_Paginator_MyModel(); -// $item1->title = 'a'; -// $item1->description = 'description'; -// $item1->create(); - -// $item2 = new Pluf_Paginator_MyModel(); -// $item2->title = 'b'; -// $item2->description = 'description'; -// $item2->create(); - -// $pag = new Pluf_Paginator(new Pluf_Paginator_MyModel()); -// $fields = array( -// 'id', -// 'title', -// 'description' -// ); -// $pag->configure($fields, $fields); -// $pag->list_filters = $fields; -// $pag->items_per_page = 5; - -// $pag->sort_order = array( -// array( -// 'id', -// 'ASC' -// ) -// ); -// $result = $pag->render_object(); -// $this->assertTrue(is_array($result)); -// $this->assertTrue(array_key_exists('items', $result)); -// // check order -// for ($i = 1; $i < sizeof($result['items']); $i ++) { -// $a = $result['items'][$i]; -// $b = $result['items'][$i - 1]; -// $this->assertTrue($a->id > $b->id); -// } - -// $pag->sort_order = array( -// array( -// 'id', -// 'DESC' -// ) -// ); -// $result = $pag->render_object(); -// $this->assertTrue(is_array($result)); -// $this->assertTrue(array_key_exists('items', $result)); -// // check order -// for ($i = 1; $i < sizeof($result['items']); $i ++) { -// $a = $result['items'][$i]; -// $b = $result['items'][$i - 1]; -// $this->assertFalse($a->id > $b->id); -// } - -// $item1->delete(); -// $item2->delete(); -// } - -// /** -// * Load from request -// * -// * @test -// */ -// public function testSetFromRequestSort() -// { -// $_REQUEST = array( -// '_px_sk' => 'id', -// '_px_so' => 'd' -// ); -// $request = new Request('/test'); - -// $pag = new Pluf_Paginator(new Pluf_Paginator_MyModel()); -// $fields = array( -// 'id', -// 'title', -// 'description' -// ); -// $pag->configure($fields, $fields); -// $pag->list_filters = $fields; -// $pag->items_per_page = 5; -// $pag->setFromRequest($request); - -// $result = $pag->render_object(); -// $this->assertTrue(is_array($result)); -// $this->assertTrue(array_key_exists('items', $result)); -// } - -// /** -// * Load from request with multi sort -// * -// * @test -// */ -// public function testSetFromRequestMultiSort() -// { -// $_REQUEST = array( -// '_px_sk' => array( -// 'id', -// 'title' -// ), -// '_px_so' => array( -// 'd', -// 'a' -// ) -// ); -// $request = new Request('/test'); - -// $pag = new Pluf_Paginator(new Pluf_Paginator_MyModel()); -// $fields = array( -// 'id', -// 'title', -// 'description' -// ); -// $pag->configure($fields, $fields); -// $pag->list_filters = $fields; -// $pag->items_per_page = 5; -// $pag->setFromRequest($request); - -// $result = $pag->render_object(); -// $this->assertTrue(is_array($result)); -// $this->assertTrue(array_key_exists('items', $result)); -// } - -// /** -// * Test filter from request -// * -// * @test -// */ -// public function testSetFromRequestFilter() -// { -// $_REQUEST = array( -// '_px_fk' => 'id', -// '_px_fv' => '1' -// ); -// $request = new Request('/test'); - -// $pag = new Pluf_Paginator(new Pluf_Paginator_MyModel()); -// $fields = array( -// 'id', -// 'title', -// 'description' -// ); -// $pag->configure($fields, $fields); -// $pag->list_filters = $fields; -// $pag->items_per_page = 5; -// $pag->setFromRequest($request); - -// $result = $pag->render_object(); -// $this->assertTrue(is_array($result)); -// $this->assertTrue(array_key_exists('items', $result)); -// } - -// /** -// * Test filter function -// * -// * @test -// */ -// public function testSetFromRequestFilterFunction() -// { -// $item1 = new Pluf_Paginator_MyModel(); -// $item1->title = 'a'; -// $item1->description = 'description'; -// $item1->create(); - -// $_REQUEST = array( -// '_px_fk' => 'id', -// '_px_fv' => $item1->id -// ); -// $request = new Request('/test'); - -// $pag = new Pluf_Paginator(new Pluf_Paginator_MyModel()); -// $fields = array( -// 'id', -// 'title', -// 'description' -// ); -// $pag->configure($fields, $fields); -// $pag->list_filters = $fields; -// $pag->items_per_page = 5; -// $pag->setFromRequest($request); - -// $result = $pag->render_object(); -// $this->assertTrue(is_array($result)); -// $this->assertTrue(array_key_exists('items', $result)); -// $this->assertTrue(sizeof($result['items']) === 1); - -// $item1->delete(); -// } - -// /** -// * Test multi filter from request -// * -// * @test -// */ -// public function testSetFromRequestMultiFilter() -// { -// $item1 = new Pluf_Paginator_MyModel(); -// $item1->title = 'a'; -// $item1->description = 'description'; -// $item1->create(); - -// $item2 = new Pluf_Paginator_MyModel(); -// $item2->title = 'b'; -// $item2->description = 'description'; -// $item2->create(); - -// $_REQUEST = array( -// '_px_fk' => array( -// 'id', -// 'title' -// ), -// '_px_fv' => array( -// $item1->id, -// $item1->title -// ) -// ); -// $request = new Request('/test'); - -// $pag = new Pluf_Paginator(new Pluf_Paginator_MyModel()); -// $fields = array( -// 'id', -// 'title', -// 'description' -// ); -// $pag->configure($fields, $fields); -// $pag->list_filters = $fields; -// $pag->items_per_page = 5; -// $pag->setFromRequest($request); - -// $result = $pag->render_object(); -// $this->assertTrue(is_array($result)); -// $this->assertTrue(array_key_exists('items', $result)); -// $this->assertEquals(sizeof($result['items']), 1); - -// $item1->delete(); -// $item2->delete(); -// } - -// /** -// * Test same filter keys -// * -// * @test -// */ -// public function testSetFromRequestSameFilter() -// { -// $item1 = new Pluf_Paginator_MyModel(); -// $item1->title = 'a'; -// $item1->description = 'description'; -// $item1->create(); - -// $item2 = new Pluf_Paginator_MyModel(); -// $item2->title = 'b'; -// $item2->description = 'description'; -// $item2->create(); - -// $_REQUEST = array( -// '_px_fk' => array( -// 'id', -// 'id' -// ), -// '_px_fv' => array( -// $item1->id, -// $item2->id -// ) -// ); -// $request = new Request('/test'); - -// $pag = new Pluf_Paginator(new Pluf_Paginator_MyModel()); -// $fields = array( -// 'id', -// 'title', -// 'description' -// ); -// $pag->configure($fields, $fields); -// $pag->list_filters = $fields; -// $pag->items_per_page = 5; -// $pag->setFromRequest($request); - -// $result = $pag->render_object(); -// $this->assertTrue(is_array($result)); -// $this->assertTrue(array_key_exists('items', $result)); -// $this->assertTrue(sizeof($result['items']) === 2); - -// $item1->delete(); -// $item2->delete(); -// } - -// /** -// * Test search in items -// * -// * @test -// */ -// public function testSearchPaginate() -// { -// $pag = new Pluf_Paginator(new Pluf_Paginator_MyModel()); -// // $fields = array( -// // 'id', -// // 'title', -// // 'description' -// // ); -// // $pag->configure($fields, $fields); -// // $pag->list_filters = $fields; -// $pag->items_per_page = 5; -// $pag->search_string = 'test'; - -// $this->assertTrue(is_array($pag->render_object())); -// $this->assertTrue(array_key_exists('items', $pag->render_object())); -// } - -// /** -// * Test search function when query is set from request -// * -// * @test -// */ -// public function testSearchSetFromRequest() -// { -// $item1 = new Pluf_Paginator_MyModel(); -// $item1->title = 'my test title'; -// $item1->description = 'description about my test item'; -// $item1->int_field = 100; -// $item1->float_field = 200.0; -// $item1->create(); - -// $_REQUEST = array( -// '_px_q' => 'test' -// ); -// $request = new Request('/test'); - -// $pag = new Pluf_Paginator(new Pluf_Paginator_MyModel()); -// $fields = array( -// 'id', -// 'title', -// 'description', -// 'int_field', -// 'float_field' -// ); -// $pag->configure($fields, $fields); -// $pag->list_filters = $fields; -// $pag->items_per_page = 5; -// $pag->setFromRequest($request); - -// $result = $pag->render_object(); -// $this->assertTrue(is_array($result)); -// $this->assertTrue(array_key_exists('items', $result)); -// $this->assertTrue(sizeof($result['items']) === 1); - -// $item1->delete(); -// } - -// /** -// * Test filter key validation -// * -// * @test -// * @expectedException Error404 -// */ -// public function testValidationForFilterKeys() -// { -// $item1 = new Pluf_Paginator_MyModel(); -// $item1->title = 'a'; -// $item1->description = 'description'; -// $item1->create(); - -// $item2 = new Pluf_Paginator_MyModel(); -// $item2->title = 'b'; -// $item2->description = 'description'; -// $item2->create(); - -// $_REQUEST = array( -// '_px_fk' => array( -// 'id', -// 'title;' -// ), -// '_px_fv' => array( -// $item1->id, -// $item1->title -// ) -// ); -// $request = new Request('/test'); - -// $pag = new Pluf_Paginator(new Pluf_Paginator_MyModel()); -// $fields = array( -// 'id', -// 'title', -// 'description' -// ); -// $pag->configure($fields, $fields); -// $pag->list_filters = $fields; -// $pag->items_per_page = 5; -// $pag->setFromRequest($request); - -// $pag->render_object(); - -// $item1->delete(); -// $item2->delete(); -// } - -// /** -// * Test sort key validation -// * -// * @test -// * @expectedException Pluf_Exception_BadRequest -// */ -// public function testValidationForSortKeys() -// { -// $pag = new Pluf_Paginator(new Pluf_Paginator_MyModel()); -// $fields = array( -// 'id', -// 'title', -// 'description' -// ); -// $pag->configure($fields, $fields); -// $pag->list_filters = $fields; -// $pag->items_per_page = 5; -// $pag->sort_order = array( -// array( -// 'id', -// 'ASC' -// ), -// array( -// 'title', -// 'ASC' -// ) -// ); - -// $item1 = new Pluf_Paginator_MyModel(); -// $item1->title = 'a'; -// $item1->description = 'description'; -// $item1->create(); - -// $item2 = new Pluf_Paginator_MyModel(); -// $item2->title = 'b'; -// $item2->description = 'description'; -// $item2->create(); - -// $_REQUEST = array( -// '_px_sk' => array( -// 'id', -// 'title$' -// ), -// '_px_so' => array( -// 'a', -// 'a' -// ) -// ); -// $request = new Request('/test'); - -// $pag = new Pluf_Paginator(new Pluf_Paginator_MyModel()); -// $fields = array( -// 'id', -// 'title', -// 'description' -// ); -// $pag->configure($fields, $fields); -// $pag->list_filters = $fields; -// $pag->items_per_page = 5; -// $pag->setFromRequest($request); - -// $pag->render_object(); - -// $item1->delete(); -// $item2->delete(); -// } -// } diff --git a/tests/Data/QueryBuilder/RequestBuilderTest.php b/tests/Data/QueryBuilder/RequestBuilderTest.php deleted file mode 100755 index bd385bd9..00000000 --- a/tests/Data/QueryBuilder/RequestBuilderTest.php +++ /dev/null @@ -1,266 +0,0 @@ -. - */ -namespace Pluf\Test\Data\QueryBuilder; - -use PHPUnit\Framework\TestCase; -use Pluf; -use Pluf\HTTP\Request; -use Pluf\Data\QueryBuilder; -use Pluf\Data\Query; -use Pluf\ObjectMapper; -use Pluf\NoteBook\Book; - -/** - * Test paginator builder - * - * @backupGlobals disabled - * @backupStaticAttributes disabled - */ -class RequestBuilderTest extends TestCase -{ - - /** - * - * @beforeClass - */ - public static function createDataBase() - { - $conf = include __DIR__ . '/../../conf/config.php'; - Pluf::start($conf); - - $m = new \Pluf\Migration(); - $m->install(); - - $mapper = ObjectMapper::getInstance([ - [ - "title" => "a", - "description" => "b" - ], - [ - "title" => "c", - "description" => "d" - ], - [ - "title" => "e", - "description" => "f" - ], - [ - "title" => "g", - "description" => "h" - ] - ]); - $repo = Pluf::getDataRepository(Book::class); - while ($mapper->hasMore()) { - $item = $mapper->next(Book::class); - $repo->create($item); - } - } - - /** - * - * @afterClass - */ - public static function removeDatabses() - { - $m = new \Pluf\Migration(); - $m->uninstall(); - } - - /** - * - * @test - */ - public function testCreateSimplePaginator() - { - $request = new Request('/api/v2/nootbook/books'); - $request->REQUEST = []; - $builder = QueryBuilder::getInstance($request); - $this->assertNotNull($builder); - $this->assertTrue($builder instanceof QueryBuilder); - - $query = QueryBuilder::getInstance($request)->build(); - - $this->assertNotNull($query); - $this->assertTrue($query instanceof Query); - } - - /** - * - * @test - */ - public function testListWithLimit() - { - $request = new Request('/api/v2/nootbook/books'); - $request->REQUEST['_px_ps'] = '1'; - $query = QueryBuilder::getInstance($request)->build(); - $repo = Pluf::getDataRepository(Book::class); - $list = $repo->get($query); - $this->assertEquals(1, count($list)); - } - - /** - * - * @test - */ - public function testListWithPage() - { - $request = new Request('/api/v2/nootbook/books'); - $request->REQUEST['_px_p'] = '0'; - $request->REQUEST['_px_ps'] = '3'; - $query = QueryBuilder::getInstance($request)->build(); - $repo = Pluf::getDataRepository(Book::class); - $list = $repo->get($query); - $this->assertEquals(3, count($list)); - - $request = new Request('/api/v2/nootbook/books'); - $request->REQUEST['_px_p'] = '1'; - $request->REQUEST['_px_ps'] = '3'; - $query = QueryBuilder::getInstance($request)->build(); - $repo = Pluf::getDataRepository(Book::class); - $list = $repo->get($query); - $this->assertEquals(1, count($list)); - } - - /** - * - * @test - */ - public function testWithSearchFields() - { - $letters = [ - 'a', - 'b', - 'c', - 'd', - 'e', - 'f', - 'g', - 'h' - ]; - foreach ($letters as $letter) { - $request = new Request('/api/v2/nootbook/books'); - $request->REQUEST['q'] = $letter; - $request->REQUEST['_px_p'] = '0'; - $request->REQUEST['_px_ps'] = '30'; - $query = QueryBuilder::getInstance($request)->build(); - $repo = Pluf::getDataRepository(Book::class); - $list = $repo->get($query); - $this->assertEquals(1, count($list)); - } - } - - /** - * - * @test - */ - public function testWithFilter() - { - $request = new Request('/api/v2/nootbook/books'); - $request->REQUEST['_px_fk'] = 'title'; - $request->REQUEST['_px_fv'] = 'a'; - $request->REQUEST['_px_p'] = '0'; - $request->REQUEST['_px_ps'] = '30'; - $query = QueryBuilder::getInstance($request)->build(); - $repo = Pluf::getDataRepository(Book::class); - $list = $repo->get($query); - $this->assertEquals(1, count($list)); - } - - /** - * - * @test - */ - public function testWithDublicatedFilter() - { - $request = new Request('/api/v2/nootbook/books'); - $request->REQUEST['_px_fk'] = [ - 'title', - 'title', - 'title' - ]; - $request->REQUEST['_px_fv'] = [ - 'a', - 'b', - 'c' - ]; - $request->REQUEST['_px_p'] = '0'; - $request->REQUEST['_px_ps'] = '30'; - $query = QueryBuilder::getInstance($request)->optimize()->build(); - $repo = Pluf::getDataRepository(Book::class); - $list = $repo->get($query); - $this->assertEquals(2, count($list)); - } - - // /** - // * - // * @test - // */ - // public function testWithSearchFieldsAutomated() - // { - // $builder = new Pluf_Paginator_Builder(new Pluf_Paginator_MyModel()); - // $pag = $builder->build(); - // $this->assertTrue(isset($pag)); - // $this->assertTrue(in_array('id', $pag->search_fields), 'Id not found in search fields'); - // $this->assertTrue(in_array('title', $pag->search_fields), 'Title not found in search fields'); - // $this->assertTrue(in_array('description', $pag->search_fields), 'Description not found in search fields'); - // } - - // /** - // * - // * @test - // */ - // public function testWithSortFieldsAutomated() - // { - // $builder = new Pluf_Paginator_Builder(new Pluf_Paginator_MyModel()); - // $pag = $builder->build(); - // $this->assertTrue(isset($pag)); - // $this->assertTrue(in_array('id', $pag->sort_fields), 'Id not found in sort fields'); - // $this->assertTrue(in_array('title', $pag->sort_fields), 'Title not found in sort fields'); - // $this->assertTrue(in_array('description', $pag->sort_fields), 'Description not found in sort fields'); - // } - - // /** - // * - // * @test - // */ - // public function testModelView() - // { - // $builder = new Pluf_Paginator_Builder(new Pluf_Paginator_MyModel()); - // $pag = $builder->setView('test_view')->build(); - // $this->assertTrue(isset($pag)); - // $this->assertEquals('test_view', $pag->model_view, 'Id not found in sort fields'); - // } - - // /** - // * - // * @test - // */ - // public function testWhereClause() - // { - // $sql = new Pluf_SQL('id=%s', array( - // 'id' => '1' - // )); - // $builder = new Pluf_Paginator_Builder(new Pluf_Paginator_MyModel()); - // $pag = $builder->setView('test_view') - // ->setWhereClause($sql) - // ->build(); - // $this->assertTrue(isset($pag)); - // $this->assertEquals($sql->gen(), $pag->forced_where->gen(), 'Where clause dose not matsh'); - // } -} diff --git a/tests/Data/RepositoryTest.php b/tests/Data/RepositoryTest.php deleted file mode 100644 index 8b2197c3..00000000 --- a/tests/Data/RepositoryTest.php +++ /dev/null @@ -1,248 +0,0 @@ -. - */ -namespace Pluf\Test\Data; - -use PHPUnit\Framework\TestCase; -use Pluf\Options; -use Pluf\Data\Query; -use Pluf\NoteBook\Book; -use Pluf\NoteBook\Tag; -use Pluf; - -class RepositoryTest extends TestCase -{ - - /** - * - * @beforeClass - */ - public static function installApplication() - { - Pluf::start(__DIR__ . '/../conf/config.php'); - $m = new \Pluf\Migration(); - $m->install(); - } - - /** - * - * @afterClass - */ - public static function deleteApplication() - { - $m = new \Pluf\Migration(); - $m->uninstall(); - } - - /** - * Getting list of books with repository model - * - * @test - */ - public function getListOfBookByOptions() - { - $repo = Pluf::getDataRepository([ - 'model' => Book::class - ]); - $this->assertNotNull($repo); - - $query = new Query([ - 'filter' => [ - [ - 'title', - '=', - 'my title' - ], - [ - 'id', - '>', - 5 - ] - ] - ]); - - $items = $repo->get($query); - $this->assertNotNull($items); - } - - /** - * Getting list of books with repository model - * - * @test - */ - public function getListOfBookByClassName() - { - $repo = Pluf::getDataRepository(Book::class); - $this->assertNotNull($repo); - - $query = new Query([ - 'filter' => [ - [ - 'title', - '=', - 'my title' - ], - [ - 'id', - '>', - 5 - ] - ] - ]); - - $items = $repo->get($query); - $this->assertNotNull($items); - } - - /** - * Getting list of books with repository model - * - * @test - */ - public function getListOfBookByOptionsModel() - { - $repo = Pluf::getDataRepository(new Options([ - 'model' => Book::class - ])); - $this->assertNotNull($repo); - - $query = new Query([ - 'filter' => [ - [ - 'title', - '=', - 'my title' - ], - [ - 'id', - '>', - 5 - ] - ] - ]); - - $items = $repo->get($query); - $this->assertNotNull($items); - } - - /** - * Getting list of books with repository model - * - * @test - */ - public function getListOfTagsByOptionsModel() - { - $repo = Pluf::getDataRepository(new Options([ - 'model' => Tag::class - ])); - $this->assertNotNull($repo); - - $query = new Query([ - 'filter' => [ - [ - 'title', - '=', - 'my title' - ], - [ - 'id', - '>', - 5 - ] - ] - ]); - - $items = $repo->get($query); - $this->assertNotNull($items); - } - - /** - * - * @test - */ - public function putTagsByOptionsModel() - { - $repo = Pluf::getDataRepository(new Options([ - 'model' => Tag::class - ])); - $this->assertNotNull($repo); - - $tag = new Tag(); - $tag->title = 'Hi'; - $tag->create(); - $this->assertFalse($tag->isAnonymous()); - - $items = $repo->get(); - $this->assertNotNull($items); - $this->assertTrue(count($items) > 0); - } - - /** - * - * @test - */ - public function putTagsByOptionsModelByRepo() - { - $repo = Pluf::getDataRepository(new Options([ - 'model' => Tag::class - ])); - $this->assertNotNull($repo); - - $tag = new Tag(); - $tag->title = 'Hi'; - $repo->create($tag); - $this->assertFalse($tag->isAnonymous()); - - $items = $repo->get(); - $this->assertNotNull($items); - $this->assertTrue(count($items) > 0); - } - - /** - * - * @test - */ - public function updateTagsByOptionsModelByRepo() - { - $repo = Pluf::getDataRepository(new Options([ - 'model' => Tag::class - ])); - $this->assertNotNull($repo); - - $tag = new Tag(); - $tag->title = 'Hi'; - $repo->create($tag); - $this->assertFalse($tag->isAnonymous()); - - $items = $repo->get(); - $this->assertNotNull($items); - $this->assertTrue(count($items) > 0); - - $tag2 = new Tag($tag->id); - $this->assertFalse($tag2->isAnonymous()); - $this->assertEquals($tag->title, $tag2->title); - - $tag2->title = rand() . '-name'; - $repo->update($tag2); - - $tag3 = new Tag($tag2->id); - $this->assertFalse($tag3->isAnonymous()); - $this->assertEquals($tag2->title, $tag3->title); - } -} - diff --git a/tests/Data/SchemaSQLiteTest.php b/tests/Data/SchemaSQLiteTest.php deleted file mode 100755 index a33ca96d..00000000 --- a/tests/Data/SchemaSQLiteTest.php +++ /dev/null @@ -1,163 +0,0 @@ -. - */ -namespace Pluf\Test\Data; - -use PHPUnit\Framework\TestCase; -use Pluf\Options; -use Pluf\Data\ModelDescription; -use Pluf\Data\Schema\SQLiteSchema; -use Pluf; - -/** - * - * @backupGlobals disabled - * @backupStaticAttributes disabled - */ -class SchemaSQLiteTest extends TestCase -{ - - // /** - // * - // * @beforeClass - // */ - // public static function initTest() - // { - // Pluf::start(dirname(__FILE__) . '/../conf/config.php'); - // if (Pluf::db()->engine != 'SQLite') { - // self::markTestSkipped('Only to be run with the SQLite DB engine'); - // } - // } - - /** - * - * @test - */ - public function testGenerateSchema3() - { - Pluf::start([ - 'db_dsn' => 'sqlite::memory:', - 'db_user' => null, - 'db_password' => null - ]); - $model = new \Pluf\NoteBook\Book(); - $modelDes = ModelDescription::getInstance($model); - $schema = new SQLiteSchema(new Options([ - 'prefix' => 'sqlite_' . rand() . '_' - ])); - - $sql = $schema->createTableQueries($modelDes); - - // CREATE TABLE pluf_unit_tests_testmodel ( - // id integer primary key autoincrement, - // title varchar(100) default '', - // description text not null default '' - // ); - - $tablename = $schema->getTableName($modelDes); - $this->assertEquals(true, strpos($sql[$tablename], 'CREATE TABLE') !== false); - $this->assertEquals(true, strpos($sql[$tablename], 'integer') !== false); - $this->assertEquals(true, strpos($sql[$tablename], 'varchar(100)') !== false); - $this->assertEquals(true, strpos($sql[$tablename], 'text') !== false); - $this->assertEquals(true, strpos($sql[$tablename], $tablename) !== false); - } - - /** - * - * @test - */ - public function testDeleteSchemaTestModel() - { - Pluf::start([ - 'db_dsn' => 'sqlite::memory:', - 'db_user' => null, - 'db_password' => null - ]); - $model = new \Pluf\NoteBook\Book(); - $modelDes = ModelDescription::getInstance($model); - $schema = new SQLiteSchema(new Options([ - 'prefix' => 'sqlite_' . rand() . '_' - ])); - - $del = $schema->dropTableQueries($modelDes); - - $tablename = $schema->getTableName($modelDes); - $this->assertEquals('DROP TABLE IF EXISTS ' . $tablename, $del[0]); - } - - /** - * - * @test - */ - public function testGetValuesOfModelSchema() - { - Pluf::start([ - 'db_dsn' => 'sqlite::memory:', - 'db_user' => null, - 'db_password' => null, - - 'data_schema_engine' => 'sqlite', - 'data_schema_sqlite_prefix' => 'sqlite_' . rand() . '_' - ]); - - // Check if schema is loaded - $this->assertNotNull(Pluf::getDataSchema()); - - $model = new \Pluf\NoteBook\Book(); - $model->title = 'my title'; - $model->description = 'A small desc.'; - - $md = ModelDescription::getInstance($model); - - $vals = Pluf::getDataSchema()->getValues($md, $model); - $this->assertNotNull($vals); - - $this->assertFalse(isset($vals['id'])); - $this->assertFalse(array_key_exists('id', $vals)); - $this->assertTrue(isset($vals['title'])); - $this->assertTrue(isset($vals['description'])); - } - - /** - * - * @test - */ - public function testGenerateSchema() - { - Pluf::start([ - 'db_dsn' => 'sqlite::memory:', - 'db_user' => null, - 'db_password' => null, - - 'data_schema_engine' => 'sqlite', - 'data_schema_sqlite_prefix' => 'db' . rand() . '_' - ]); - $this->assertNotNull(Pluf::getDataSchema()); - - $model = new \Pluf\NoteBook\Book(); - - Pluf::getDataSchema()->createTables(Pluf::db(), ModelDescription::getInstance($model)); - - $model->title = 'my title'; - $model->description = 'A small desc.'; - $this->assertEquals(true, $model->create()); - $this->assertEquals(1, (int) $model->id); - - Pluf::getDataSchema()->dropTables(Pluf::db(), ModelDescription::getInstance($model)); - } -} diff --git a/tests/Db/ConnectionTest.php b/tests/Db/ConnectionTest.php deleted file mode 100644 index 37b571ee..00000000 --- a/tests/Db/ConnectionTest.php +++ /dev/null @@ -1,374 +0,0 @@ -. - */ -namespace Pluf\Test\Db; - -use PHPUnit\Framework\TestCase; -use Pluf\Db\Connection; - -class ConnectionTest extends TestCase -{ - - /** - * Test constructor. - */ - public function testInit() - { - $c = Connection::connect('sqlite::memory:'); - $this->assertEquals(4, $c->expr('select (2+2)') - ->getOne()); - } - - /** - * Test DSN normalize. - */ - public function testDSNNormalize() - { - // standard - $dsn = Connection::normalizeDSN('mysql://root:pass@localhost/db'); - $this->assertEquals([ - 'dsn' => 'mysql:host=localhost;dbname=db', - 'user' => 'root', - 'pass' => 'pass', - 'driver' => 'mysql', - 'rest' => 'host=localhost;dbname=db' - ], $dsn); - - $dsn = Connection::normalizeDSN('mysql:host=localhost;dbname=db'); - $this->assertEquals([ - 'dsn' => 'mysql:host=localhost;dbname=db', - 'user' => null, - 'pass' => null, - 'driver' => 'mysql', - 'rest' => 'host=localhost;dbname=db' - ], $dsn); - - $dsn = Connection::normalizeDSN('mysql:host=localhost;dbname=db', 'root', 'pass'); - $this->assertEquals([ - 'dsn' => 'mysql:host=localhost;dbname=db', - 'user' => 'root', - 'pass' => 'pass', - 'driver' => 'mysql', - 'rest' => 'host=localhost;dbname=db' - ], $dsn); - - // username and password should take precedence - $dsn = Connection::normalizeDSN('mysql://root:pass@localhost/db', 'foo', 'bar'); - $this->assertEquals([ - 'dsn' => 'mysql:host=localhost;dbname=db', - 'user' => 'foo', - 'pass' => 'bar', - 'driver' => 'mysql', - 'rest' => 'host=localhost;dbname=db' - ], $dsn); - - // more options - $dsn = Connection::normalizeDSN('mysql://root:pass@localhost/db;foo=bar'); - $this->assertEquals([ - 'dsn' => 'mysql:host=localhost;dbname=db;foo=bar', - 'user' => 'root', - 'pass' => 'pass', - 'driver' => 'mysql', - 'rest' => 'host=localhost;dbname=db;foo=bar' - ], $dsn); - - // no password - $dsn = Connection::normalizeDSN('mysql://root@localhost/db'); - $this->assertEquals([ - 'dsn' => 'mysql:host=localhost;dbname=db', - 'user' => 'root', - 'pass' => null, - 'driver' => 'mysql', - 'rest' => 'host=localhost;dbname=db' - ], $dsn); - $dsn = Connection::normalizeDSN('mysql://root:@localhost/db'); // see : after root - $this->assertEquals([ - 'dsn' => 'mysql:host=localhost;dbname=db', - 'user' => 'root', - 'pass' => null, - 'driver' => 'mysql', - 'rest' => 'host=localhost;dbname=db' - ], $dsn); - - // specific DSNs - $dsn = Connection::normalizeDSN('dumper:sqlite::memory'); - $this->assertEquals([ - 'dsn' => 'dumper:sqlite::memory', - 'user' => null, - 'pass' => null, - 'driver' => 'dumper', - 'rest' => 'sqlite::memory' - ], $dsn); - - $dsn = Connection::normalizeDSN('sqlite::memory'); - $this->assertEquals([ - 'dsn' => 'sqlite::memory', - 'user' => null, - 'pass' => null, - 'driver' => 'sqlite', - 'rest' => ':memory' - ], $dsn); // rest is unusable anyway in this context - - // with port number as URL, normalize port to ;port=1234 - $dsn = Connection::normalizeDSN('mysql://root:pass@localhost:1234/db'); - $this->assertEquals([ - 'dsn' => 'mysql:host=localhost;port=1234;dbname=db', - 'user' => 'root', - 'pass' => 'pass', - 'driver' => 'mysql', - 'rest' => 'host=localhost;port=1234;dbname=db' - ], $dsn); - - // with port number as DSN, leave port as :port - $dsn = Connection::normalizeDSN('mysql:host=localhost:1234;dbname=db'); - $this->assertEquals([ - 'dsn' => 'mysql:host=localhost:1234;dbname=db', - 'user' => null, - 'pass' => null, - 'driver' => 'mysql', - 'rest' => 'host=localhost:1234;dbname=db' - ], $dsn); - } - - /** - * Test driver property. - */ - public function testDriver() - { - $c = Connection::connect('sqlite::memory:'); - $this->assertEquals('sqlite', $c->driver); - - $c = Connection::connect('dumper:sqlite::memory:'); - $this->assertEquals('sqlite', $c->driver); - - $c = Connection::connect('counter:sqlite::memory:'); - $this->assertEquals('sqlite', $c->driver); - } - - /** - * Test Dumper connection. - */ - public function testDumper() - { - $c = Connection::connect('dumper:sqlite::memory:'); - - $result = false; - $c->callback = function ($expr, $time, $fail) use (&$result) { - $result = $expr->render(); - }; - - $this->assertEquals('PDO', get_class($c->connection())); - - $this->assertEquals(4, $c->expr('select (2+2)') - ->getOne()); - - $this->assertEquals('select (2+2)', $result); - } - - /** - * - * @expectedException Exception - */ - public function testMysqlFail() - { - Connection::connect('mysql:host=localhost;dbname=nosuchdb'); - } - - public function testDumperEcho() - { - $c = Connection::connect('dumper:sqlite::memory:'); - - $this->assertEquals(4, $c->expr('select (2+2)') - ->getOne()); - - $this->expectOutputRegex("/select \(2\+2\)/"); - } - - public function testCounter() - { - $c = Connection::connect('counter:sqlite::memory:'); - - $result = false; - $c->callback = function ($a, $b, $c, $d, $fail) use (&$result) { - $result = [ - $a, - $b, - $c, - $d - ]; - }; - - $this->assertEquals(4, $c->expr('select ([]+[])', [ - $c->expr('2'), - 2 - ]) - ->getOne()); - - unset($c); - $this->assertEquals([ - 0, - 0, - 1, - 1 - ], $result); - } - - public function testCounterEcho() - { - $c = Connection::connect('counter:sqlite::memory:'); - - $this->assertEquals(4, $c->expr('select ([]+[])', [ - $c->expr('2'), - 2 - ]) - ->getOne()); - - $this->expectOutputString("Queries: 0, Selects: 0, Rows fetched: 1, Expressions 1\n"); - - unset($c); - } - - public function testCounter2() - { - $c = Connection::connect('counter:sqlite::memory:'); - - $result = false; - $c->callback = function ($a, $b, $c, $d, $fail) use (&$result) { - $result = [ - $a, - $b, - $c, - $d - ]; - }; - - $this->assertEquals(4, $c->dsql() - ->field($c->expr('2+2')) - ->getOne()); - - unset($c); - $this->assertEquals([ - 1, - 1, - 1, - 0 - ], - // 1 query - // 1 select - // 1 result row - // 0 expressions - $result); - } - - public function testCounter3() - { - $c = Connection::connect('counter:sqlite::memory:'); - - $result = false; - $c->callback = function ($a, $b, $c, $d, $fail) use (&$result) { - $result = [ - $a, - $b, - $c, - $d - ]; - }; - - $c->expr('create table test (id int, name varchar(255))')->execute(); - $c->dsql() - ->table('test') - ->set('name', 'John') - ->insert(); - $c->dsql() - ->table('test') - ->set('name', 'Peter') - ->insert(); - $c->dsql() - ->table('test') - ->set('name', 'Joshua') - ->insert(); - $res = $c->dsql() - ->table('test') - ->where('name', 'like', 'J%') - ->field('name') - ->get(); - - $this->assertEquals([ - [ - 'name' => 'John' - ], - [ - 'name' => 'Joshua' - ] - ], $res); - - unset($c); - $this->assertEquals([ - 4, - 1, - 2, - 1 - ], - // 4 queries, 3 inserts and select - // 1 select - // 2 result row, john, joshua - // 1 expressions, create - $result); - } - - /** - * - * @expectedException \PDOException - */ - public function testException1() - { - Connection::connect(':'); - } - - /** - * - * @expectedException \Pluf\Db\Exception - */ - public function testException2() - { - Connection::connect(''); - } - - /** - * - * @expectedException \Pluf\Db\Exception - */ - public function testException3() - { - new Connection('sqlite::memory'); - } - - /** - * - * @expectedException \Pluf\Db\Exception - */ - public function testException4() - { - $c = new Connection(); - $q = $c->expr('select (2+2)'); - - $this->assertEquals('select (2+2)', $q->render()); - - $q->execute(); - } -} diff --git a/tests/Db/ExceptionTest.php b/tests/Db/ExceptionTest.php deleted file mode 100644 index 245bf2fd..00000000 --- a/tests/Db/ExceptionTest.php +++ /dev/null @@ -1,58 +0,0 @@ -. - */ -namespace Pluf\Test\Db; - -use Pluf\Db\Expression; -use PHPUnit\Framework\TestCase; -use Pluf\Db\Exception; - -class ExceptionTest extends TestCase -{ - - /** - * Test constructor. - * - * @expectedException Pluf\Exception - */ - public function testException1() - { - throw new Exception(); - } - - /** - * - * @expectedException \Pluf\Db\Exception - */ - public function testException2() - { - $e = new Expression('hello, [world]'); - $e->render(); - } - -// public function testException3() -// { -// try { -// $e = new Expression('hello, [world]'); -// $e->render(); -// } catch (Exception $e) { -// $this->assertEquals('Expression could not render tag', $e->getMessage()); -// $this->assertEquals('world', $e->getParams()['tag']); -// } -// } -} diff --git a/tests/Db/ExpressionTest.php b/tests/Db/ExpressionTest.php deleted file mode 100644 index 789d1271..00000000 --- a/tests/Db/ExpressionTest.php +++ /dev/null @@ -1,553 +0,0 @@ -e(null); - } - - /** - * Test constructor exception - wrong 1st parameter. - * - * @expectedException \Pluf\Db\Exception - */ - public function testConstructorException_1st_2() - { - $this->e(false); - } - - /** - * Test constructor exception - wrong 2nd parameter. - * - * @expectedException \Pluf\Db\Exception - */ - public function testConstructorException_2nd_1() - { - $this->e('hello, []', false); - } - - /** - * Test constructor exception - wrong 2nd parameter. - * - * @expectedException \Pluf\Db\Exception - */ - public function testConstructorException_2nd_2() - { - $this->e('hello, []', 'hello'); - } - - /** - * Test constructor exception - no arguments. - * - * @expectedException \Pluf\Db\Exception - */ - public function testConstructorException_0arg() - { - // Template is not defined for Expression - $this->e()->render(); - } - - /** - * Testing parameter edge cases - empty strings and arrays etc. - */ - public function testConstructor_1() - { - $this->assertEquals('', $this->e('') - ->render()); - } - - /** - * Testing simple template patterns without arguments. - * Testing different ways how to pass template to constructor. - */ - public function testConstructor_2() - { - // pass as string - $this->assertEquals('now()', $this->e('now()') - ->render()); - // pass as array without key - $this->assertEquals('now()', $this->e([ - 'now()' - ]) - ->render()); - // pass as array with template key - $this->assertEquals('now()', $this->e([ - 'template' => 'now()' - ]) - ->render()); - // pass as array without key - $this->assertEquals(':a Name', $this->e([ - '[] Name' - ], [ - 'First' - ]) - ->render()); - // pass as array with template key - $this->assertEquals(':a Name', $this->e([ - 'template' => '[] Name' - ], [ - 'Last' - ]) - ->render()); - } - - /** - * Testing template with simple arguments. - */ - public function testConstructor_3() - { - $e = $this->e('hello, [who]', [ - 'who' => 'world' - ]); - $this->assertEquals('hello, :a', $e->render()); - $this->assertEquals('world', $e->params[':a']); - - $e = $this->e('hello, {who}', [ - 'who' => 'world' - ]); - $this->assertEquals('hello, "world"', $e->render()); - $this->assertEquals([], $e->params); - } - - /** - * Testing template with complex arguments. - */ - public function testConstructor_4() - { - // argument = Expression - $this->assertEquals('hello, world', $this->e('hello, [who]', [ - 'who' => $this->e('world') - ]) - ->render()); - - // multiple arguments = Expression - $this->assertEquals('hello, world', $this->e('[what], [who]', [ - 'what' => $this->e('hello'), - 'who' => $this->e('world') - ]) - ->render()); - - // numeric argument = Expression - $this->assertEquals('testing "hello, world"', $this->e('testing "[]"', [ - $this->e('[what], [who]', [ - 'what' => $this->e('hello'), - 'who' => $this->e('world') - ]) - ]) - ->render()); - - // pass template as array - $this->assertEquals('hello, world', $this->e([ - 'template' => 'hello, [who]' - ], [ - 'who' => $this->e('world') - ]) - ->render()); - } - - /** - * Test nested parameters. - */ - public function testNestedParams() - { - // ++1 and --2 - $e1 = $this->e('[] and []', [ - $this->e('++[]', [ - 1 - ]), - $this->e('--[]', [ - 2 - ]) - ]); - - $this->assertEquals('++1 and --2', strip_tags($e1->getDebugQuery())); - - $e2 = $this->e('=== [foo] ===', [ - 'foo' => $e1 - ]); - - $this->assertEquals('=== ++1 and --2 ===', strip_tags($e2->getDebugQuery())); - - $this->assertEquals('++1 and --2', strip_tags($e1->getDebugQuery())); - } - - /** - * Tests where one expression with parameter is used within several other expressions. - */ - public function testNestedExpressions() - { - $e1 = $this->e('Hello [who]', [ - 'who' => 'world' - ]); - - $e2 = $this->e('[greeting]! How are you.', [ - 'greeting' => $e1 - ]); - $e3 = $this->e('It is me again. [greeting]', [ - 'greeting' => $e1 - ]); - - $s2 = $e2->render(); // Hello :a! How are you. - $s3 = $e3->render(); // It is me again. Hello :a - - $e4 = $this->e('[] and good night', [ - $e1 - ]); - $s4 = $e4->render(); // Hello :a and good night - - $this->assertEquals('Hello :a! How are you.', $s2); - $this->assertEquals('It is me again. Hello :a', $s3); - $this->assertEquals('Hello :a and good night', $s4); - } - - /** - * - * @expectedException \Exception - */ - /* - * public function testToStringException1() - * { - * $e = new MyBadExpression('Hello'); - * $s = (string)$e; - * } - */ - - /** - * expr() should return new Expression object and inherit connection from it. - */ - public function testExpr() - { - $e = $this->e([ - 'connection' => new \stdClass() - ]); - $this->assertEquals(true, $e->expr()->connection instanceof \stdClass); - } - - /** - * Fully covers _escape method. - */ - public function testEscape() - { - // escaping expressions - $this->assertEquals('"first_name"', $this->callProtected($this->e(), '_escape', [ - 'first_name' - ])); - $this->assertEquals('"123"', $this->callProtected($this->e(), '_escape', [ - 123 - ])); - $this->assertEquals('"he""llo"', $this->callProtected($this->e(), '_escape', [ - 'he"llo' - ])); - - // should not escape expressions - $this->assertEquals('*', $this->callProtected($this->e(), '_escapeSoft', [ - '*' - ])); - $this->assertEquals('"*"', $this->callProtected($this->e(), '_escape', [ - '*' - ])); - $this->assertEquals('(2+2) age', $this->callProtected($this->e(), '_escapeSoft', [ - '(2+2) age' - ])); - $this->assertEquals('"(2+2) age"', $this->callProtected($this->e(), '_escape', [ - '(2+2) age' - ])); - $this->assertEquals('"users"."first_name"', $this->callProtected($this->e(), '_escapeSoft', [ - 'users.first_name' - ])); - $this->assertEquals('"users".*', $this->callProtected($this->e(), '_escapeSoft', [ - 'users.*' - ])); - $this->assertEquals(true, $this->callProtected($this->e(), '_escapeSoft', [ - new \stdClass() - ]) instanceof \stdClass); - - // escaping array - escapes each of its elements using hard escape - $this->assertEquals([ - '"first_name"', - '*', - '"last_name"' - ], $this->callProtected($this->e(), '_escapeSoft', [ - [ - 'first_name', - '*', - 'last_name' - ] - ])); - - // escaping array - escapes each of its elements using hard escape - $this->assertEquals([ - '"first_name"', - '"*"', - '"last_name"' - ], $this->callProtected($this->e(), '_escape', [ - [ - 'first_name', - '*', - 'last_name' - ] - ])); - - $this->assertEquals('"first_name"', $this->e() - ->escape('first_name') - ->render()); - $this->assertEquals('"first""_name"', $this->e() - ->escape('first"_name') - ->render()); - $this->assertEquals('"first""_name {}"', $this->e() - ->escape('first"_name {}') - ->render()); - } - - /** - * Fully covers _param method. - */ - public function testParam() - { - $e = new Expression('hello, [who]', [ - 'who' => 'world' - ]); - $this->assertEquals('hello, :a', $e->render()); - $this->assertEquals([ - ':a' => 'world' - ], $e->params); - - // @todo Imants: allowing to pass value as array looks wrong. - // See test case in testParam() method. - // Maybe we should add implode(' ', array_map(...)) here ? - $e = new Expression('hello, [who]', [ - 'who' => [ - 'cruel', - 'world' - ] - ]); - $this->assertEquals('hello, (:a,:b)', $e->render()); - $this->assertEquals([ - ':a' => 'cruel', - ':b' => 'world' - ], $e->params); - } - - /** - * - * @test - */ - public function testConsume() - { - // few brief tests on _consume - $this->assertEquals('"123"', $this->callProtected($this->e(), '_consume', [ - 123, - 'escape' - ])); - $this->assertEquals(':x', $this->callProtected($this->e([ - '_paramBase' => 'x' - ]), '_consume', [ - 123, - 'param' - ])); - $this->assertEquals(123, $this->callProtected($this->e(), '_consume', [ - 123, - 'none' - ])); - $this->assertEquals('(select *)', $this->callProtected($this->e(), '_consume', [ - new Query() - ])); - - $this->assertEquals('hello, "myfield"', $this->e('hello, []', [ - new MyField() - ]) - ->render()); - } - - /** - * $escape_mode value is incorrect. - * - * @expectedException \Pluf\Db\Exception - */ - public function testConsumeException1() - { - $this->callProtected($this->e(), '_consume', [ - 123, - 'blahblah' - ]); - } - - /** - * Only Expressions or Expressionable objects may be used in Expression. - * - * @expectedException \Pluf\Db\Exception - */ - public function testConsumeException2() - { - $this->callProtected($this->e(), '_consume', [ - new \StdClass() - ]); - } - - /** - * - * @test - */ - public function testArrayAccess() - { - $e = $this->e('', [ - 'parrot' => 'red', - 'blue' - ]); - - // offsetGet - $this->assertEquals('red', $e['parrot']); - $this->assertEquals('blue', $e[0]); - - // offsetSet - $e['cat'] = 'black'; - $this->assertEquals('black', $e['cat']); - $e['cat'] = 'white'; - $this->assertEquals('white', $e['cat']); - - // offsetExists, offsetUnset - $this->assertEquals(true, isset($e['cat'])); - unset($e['cat']); - $this->assertEquals(false, isset($e['cat'])); - - // testing absence of specific key in asignment - $e = $this->e('[], []'); - $e[] = 'Hello'; - $e[] = 'World'; - $this->assertEquals("'Hello', 'World'", strip_tags($e->getDebugQuery())); - - // real-life example - $age = $this->e('coalesce([age], [default_age])'); - $age['age'] = $this->e('year(now()) - year(birth_date)'); - $age['default_age'] = 18; - $this->assertEquals('coalesce(year(now()) - year(birth_date), :a)', $age->render()); - } - - /** - * Test IteratorAggregate implementation. - */ - public function testIteratorAggregate() - { - // todo - can not test this without actual DB connection and executing expression - null; - } - - /** - * Test for vendors that rely on JavaScript expressions, instead of parameters. - * - * @coversNothing - */ - public function testJsonExpression() - { - $e = new JsonExpression('hello, [who]', [ - 'who' => 'world' - ]); - - $this->assertEquals('hello, "world"', $e->render()); - $this->assertEquals([], $e->params); - } - - /** - * Test reset exception if tag is not a string. - * - * @expectedException \Pluf\Db\Exception - */ - public function testResetException() - { - $this->e('test')->reset($this->e()); - } - - /** - * Test var-dump code for codecoverage. - */ - public function testVarDump() - { - $this->e('test')->__debugInfo(); - - $this->e(' [nosuchtag] ')->__debugInfo(); - } - - /** - * Test reset(). - */ - public function testReset() - { - // reset everything - $e = $this->e('hello, [name] [surname]', [ - 'name' => 'John', - 'surname' => 'Doe' - ]); - $e->reset(); - $this->assertAttributeEquals([ - 'custom' => [] - ], 'args', $e); - - // reset particular custom/tag - $e = $this->e('hello, [name] [surname]', [ - 'name' => 'John', - 'surname' => 'Doe' - ]); - $e->reset('surname'); - $this->assertAttributeEquals([ - 'custom' => [ - 'name' => 'John' - ] - ], 'args', $e); - } -} - -// @codingStandardsIgnoreStart -class JsonExpression extends Expression -{ - - public function _param($value) - { - return json_encode($value); - } -} - -class MyField implements Expressionable -{ - - public function getDSQLExpression($e) - { - return $e->expr('"myfield"'); - } -} -/* -class MyBadExpression extends Expression -{ - public function getOne() - { - // should return string, but for test case we return array to get \Exception - return array(); - } -} -*/ -// @codingStandardsIgnoreEnd diff --git a/tests/Db/OracleTest.php b/tests/Db/OracleTest.php deleted file mode 100644 index efef29d1..00000000 --- a/tests/Db/OracleTest.php +++ /dev/null @@ -1,145 +0,0 @@ -. - */ -namespace Pluf\Test\Db; - -use Pluf\Db\Connection; -use Pluf\Test\PlufTestCase; - -class OracleTest extends PlufTestCase -{ - - /** - * Test constructor. - */ - public function testDetection() - { - try { - $c = Connection::connect('oci:dbname=mydb'); - $this->assertEquals('select "baz" from "foo" where "bar" = :a', $c->dsql() - ->table('foo') - ->where('bar', 1) - ->field('baz') - ->render()); - } catch (\PDOException $e) { - if (! extension_loaded('oci8')) { - $this->markTestSkipped('The oci8 extension is not available.'); - } - - throw $e; - } - } - - public function connect($ver = '') - { - return new Connection(array_merge([ - 'connection' => new \PDO('sqlite::memory:'), - 'query_class' => 'Pluf\Db\Query\Oracle' . $ver, - 'expression_class' => 'atk4\dsql\Expression_Oracle' - ])); - } - - public function testOracleClass() - { - $c = $this->connect(); - $this->assertEquals('select "baz" from "foo" where "bar" = :a', $c->dsql() - ->table('foo') - ->where('bar', 1) - ->field('baz') - ->render()); - - $this->assertEquals('select "baz" "ali" from "foo" where "bar" = :a', $c->dsql() - ->table('foo') - ->where('bar', 1) - ->field('baz', 'ali') - ->render()); - } - - public function testClassicOracleLimit() - { - $c = $this->connect(); - $this->assertEquals('select * from (select rownum "__dsql_rownum","__t".* from (select "baz" from "foo" where "bar" = :a) "__t") where "__dsql_rownum">0 and "__dsql_rownum"<=10', $c->dsql() - ->table('foo') - ->where('bar', 1) - ->field('baz') - ->limit(10) - ->render()); - - $this->assertEquals('select * from (select rownum "__dsql_rownum","__t".* from (select "baz" "baz_alias" from "foo" where "bar" = :a) "__t") where "__dsql_rownum">0 and "__dsql_rownum"<=10', $c->dsql() - ->table('foo') - ->where('bar', 1) - ->field('baz', 'baz_alias') - ->limit(10) - ->render()); - } - - public function test12cOracleLimit() - { - $c = $this->connect('12c'); - $this->assertEquals('select "baz" from "foo" where "bar" = :a FETCH NEXT 10 ROWS ONLY', $c->dsql() - ->table('foo') - ->where('bar', 1) - ->field('baz') - ->limit(10) - ->render()); - } - - public function testClassicOracleSkip() - { - $c = $this->connect(); - $this->assertEquals('select * from (select rownum "__dsql_rownum","__t".* from (select "baz" from "foo" where "bar" = :a) "__t") where "__dsql_rownum">10', $c->dsql() - ->table('foo') - ->where('bar', 1) - ->field('baz') - ->limit(null, 10) - ->render()); - } - - public function test12cOracleSkip() - { - $c = $this->connect('12c'); - $this->assertEquals('select "baz" from "foo" where "bar" = :a OFFSET 10 ROWS', $c->dsql() - ->table('foo') - ->where('bar', 1) - ->field('baz') - ->limit(null, 10) - ->render()); - } - - public function testClassicOracleLimitSkip() - { - $c = $this->connect(); - $this->assertEquals('select * from (select rownum "__dsql_rownum","__t".* from (select "baz" from "foo" where "bar" = :a) "__t") where "__dsql_rownum">99 and "__dsql_rownum"<=109', $c->dsql() - ->table('foo') - ->where('bar', 1) - ->field('baz') - ->limit(10, 99) - ->render()); - } - - public function test12cOracleLimitSkip() - { - $c = $this->connect('12c'); - $this->assertEquals('select "baz" from "foo" where "bar" = :a OFFSET 99 ROWS FETCH NEXT 10 ROWS ONLY', $c->dsql() - ->table('foo') - ->where('bar', 1) - ->field('baz') - ->limit(10, 99) - ->render()); - } -} diff --git a/tests/Db/PlufDBTest.php b/tests/Db/PlufDBTest.php deleted file mode 100755 index 4f7ff4c4..00000000 --- a/tests/Db/PlufDBTest.php +++ /dev/null @@ -1,94 +0,0 @@ -. - */ -namespace Pluf\Test\Db; - -use Pluf\Test\PlufTestCase; -use Pluf; - -/** - * - * @backupGlobals disabled - * @backupStaticAttributes disabled - */ -class PlufDBTest extends PlufTestCase -{ - - public $db; - - /** - * - * @before - */ - public function setUpTest() - { - Pluf::start(__DIR__ . '/../conf/config.php'); - $this->db = &Pluf::db(); - } - - public function testEscapeInteger() - { - $tests = array( - '123', - 123, - 123.32, - 'qwe\\qwe', - '\'' - ); - $res = array( - '123', - '123', - '123', - '0', - '0' - ); - foreach ($tests as $test) { - $ok = current($res); - $this->assertEquals($ok, \Pluf\Db\Engine::integerToDb($test, $this->db)); - next($res); - } - } - - public function testEscapeBoolean() - { - $tests = array( - '123', - 123, - 123.32, - 'qwe\\qwe', - '\'', - false, - '0' - ); - $res = array( - '1', - '1', - '1', - '1', - '1', - '0', - '0' - ); - foreach ($tests as $test) { - $ok = current($res); - $this->assertEquals($ok, \Pluf\Db\Engine::booleanToDb($test, $this->db), 'Fail to convert :' . $test); - next($res); - } - } -} - diff --git a/tests/Db/QueryTest.php b/tests/Db/QueryTest.php deleted file mode 100644 index e151fd83..00000000 --- a/tests/Db/QueryTest.php +++ /dev/null @@ -1,1629 +0,0 @@ -. - */ -namespace Pluf\Test\Db; - -use Pluf\Db\Expression; -use Pluf\Db\Query; -use Pluf\Test\PlufTestCase; - -class QueryTest extends PlufTestCase -{ - - public function q() - { - $args = func_get_args(); - switch (count($args)) { - case 1: - return new Query($args[0]); - case 2: - return new Query($args[0], $args[1]); - } - - return new Query(); - } - - /** - * Test constructor. - */ - public function testConstruct() - { - // passing properties in constructor - $this->assertEquals('"q"', $this->callProtected($this->q(), '_escape', [ - 'q' - ])); - } - - /** - * dsql() should return new Query object and inherit connection from it. - */ - public function testDsql() - { - $q = $this->q([ - 'connection' => new \stdClass() - ]); - $this->assertEquals(true, $q->dsql()->connection instanceof \stdClass); - } - - /** - * field() should return $this Query for chaining. - */ - public function testFieldReturnValue() - { - $q = $this->q(); - $this->assertEquals($q, $q->field('first_name')); - } - - /** - * Testing field - basic cases. - */ - public function testFieldBasic() - { - $this->assertEquals('"first_name"', $this->callProtected($this->q() - ->field('first_name'), '_render_field')); - $this->assertEquals('"first_name","last_name"', $this->callProtected($this->q() - ->field('first_name,last_name'), '_render_field')); - $this->assertEquals('"first_name","last_name"', $this->callProtected($this->q() - ->field('first_name') - ->field('last_name'), '_render_field')); - $this->assertEquals('"last_name"', $this->callProtected($this->q() - ->field('first_name') - ->reset('field') - ->field('last_name'), '_render_field')); - $this->assertEquals('*', $this->callProtected($this->q() - ->field('first_name') - ->reset('field'), '_render_field')); - $this->assertEquals('*', $this->callProtected($this->q() - ->field('first_name') - ->reset(), '_render_field')); - $this->assertEquals('"employee"."first_name"', $this->callProtected($this->q() - ->field('employee.first_name'), '_render_field')); - $this->assertEquals('"first_name" "name"', $this->callProtected($this->q() - ->field('first_name', 'name'), '_render_field')); - $this->assertEquals('"first_name" "name"', $this->callProtected($this->q() - ->field([ - 'name' => 'first_name' - ]), '_render_field')); - $this->assertEquals('"name"', $this->callProtected($this->q() - ->field([ - 'name' => 'name' - ]), '_render_field')); - $this->assertEquals('"employee"."first_name" "name"', $this->callProtected($this->q() - ->field([ - 'name' => 'employee.first_name' - ]), '_render_field')); - $this->assertEquals('*', $this->callProtected($this->q() - ->field('*'), '_render_field')); - $this->assertEquals('"employee"."first_name"', $this->callProtected($this->q() - ->field('employee.first_name'), '_render_field')); - } - - /** - * Testing field - defaultField. - */ - public function testFieldDefaultField() - { - // default defaultField - $this->assertEquals('*', $this->callProtected($this->q(), '_render_field')); - // defaultField as custom string - not escaped - $this->assertEquals('id', $this->callProtected($this->q([ - 'defaultField' => 'id' - ]), '_render_field')); - // defaultField as custom string with dot - not escaped - $this->assertEquals('all.values', $this->callProtected($this->q([ - 'defaultField' => 'all.values' - ]), '_render_field')); - // defaultField as Expression object - not escaped - $this->assertEquals('values()', $this->callProtected($this->q([ - 'defaultField' => new Expression('values()') - ]), '_render_field')); - } - - /** - * Testing field - basic cases. - */ - public function testFieldExpression() - { - $this->assertEquals('"name"', $this->q('[field]') - ->field('name') - ->render()); - $this->assertEquals('"first name"', $this->q('[field]') - ->field('first name') - ->render()); - $this->assertEquals('"first"."name"', $this->q('[field]') - ->field('first.name') - ->render()); - $this->assertEquals('now()', $this->q('[field]') - ->field('now()') - ->render()); - $this->assertEquals('now()', $this->q('[field]') - ->field(new Expression('now()')) - ->render()); - // Usage of field aliases - $this->assertEquals('now() "time"', $this->q('[field]') - ->field('now()', 'time') - ->render()); - $this->assertEquals( // alias can be passed as 2nd argument - 'now() "time"', $this->q('[field]') - ->field(new Expression('now()'), 'time') - ->render()); - $this->assertEquals( // alias can be passed as 3nd argument - 'now() "time"', $this->q('[field]') - ->field([ - 'time' => new Expression('now()') - ]) - ->render()); - } - - /** - * Duplicate alias of field. - * - * @expectedException Pluf\Exception - */ - public function testFieldException1() - { - $this->q() - ->field('name', 'a') - ->field('surname', 'a'); - } - - /** - * There shouldn't be alias when passing fields as array. - * - * @expectedException Exception - */ - public function testFieldException2() - { - $this->q()->field([ - 'name', - 'surname' - ], 'a'); - } - - /** - * There shouldn't be alias when passing multiple tables. - * - * @expectedException Pluf\Db\Exception - */ - public function testTableException1() - { - $this->q()->table('employee,jobs', 'u'); - } - - /** - * There shouldn't be alias when passing multiple tables. - * - * @expectedException Pluf\Db\Exception - */ - public function testTableException2() - { - $this->q()->table([ - 'employee', - 'jobs' - ], 'u'); - } - - /** - * Alias is NOT mandatory when pass table as Expression. - */ - public function testTableException3() - { - $this->q()->table($this->q() - ->expr('test')); - } - - /** - * Alias is IS mandatory when pass table as Query. - * - * @expectedException Pluf\Db\Exception - */ - public function testTableException4() - { - $this->q()->table($this->q() - ->table('test')); - } - - /** - * Table aliases should be unique. - * - * @expectedException Pluf\Db\Exception - */ - public function testTableException5() - { - $this->q() - ->table('foo', 'a') - ->table('bar', 'a'); - } - - /** - * Table aliases should be unique. - * - * @expectedException Pluf\Db\Exception - */ - public function testTableException6() - { - $this->q() - ->table('foo', 'bar') - ->table('bar'); - } - - /** - * Table aliases should be unique. - * - * @expectedException Pluf\Db\Exception - */ - public function testTableException7() - { - $this->q() - ->table('foo') - ->table('foo'); - } - - /** - * Table aliases should be unique. - * - * @expectedException Pluf\Db\Exception - */ - public function testTableException8() - { - $this->q() - ->table($this->q() - ->table('test'), 'foo') - ->table('foo'); - } - - /** - * Table aliases should be unique. - * - * @expectedException Pluf\Db\Exception - */ - public function testTableException9() - { - $this->q() - ->table('foo') - ->table($this->q() - ->table('test'), 'foo'); - } - - /** - * Table can't be set as sub-Query in Update query mode. - * - * @expectedException Pluf\Db\Exception - */ - public function testTableException10() - { - $this->q() - ->mode('update') - ->table($this->q() - ->table('test'), 'foo') - ->field('name') - ->set('name', 1) - ->render(); - } - - /** - * Table can't be set as sub-Query in Insert query mode. - * - * @expectedException Pluf\Db\Exception - */ - public function testTableException11() - { - $this->q() - ->mode('insert') - ->table($this->q() - ->table('test'), 'foo') - ->field('name') - ->set('name', 1) - ->render(); - } - - /** - * Requesting non-existant query mode should throw exception. - * - * @expectedException Pluf\Db\Exception - */ - public function testModeException1() - { - $this->q()->mode('non_existant_mode'); - } - - /** - * table() should return $this Query for chaining. - */ - public function testTableReturnValue() - { - $q = $this->q(); - $this->assertEquals($q, $q->table('employee')); - } - - /** - */ - public function testTableRender1() - { - // no table defined - $this->assertEquals('select now()', $this->q() - ->field(new Expression('now()')) - ->render()); - - // one table - $this->assertEquals('select "name" from "employee"', $this->q() - ->field('name') - ->table('employee') - ->render()); - - $this->assertEquals('select "na#me" from "employee"', $this->q() - ->field('"na#me"') - ->table('employee') - ->render()); - $this->assertEquals('select "na""me" from "employee"', $this->q() - ->field(new Expression('{}', [ - 'na"me' - ])) - ->table('employee') - ->render()); - $this->assertEquals('select "жук" from "employee"', $this->q() - ->field(new Expression('{}', [ - 'жук' - ])) - ->table('employee') - ->render()); - $this->assertEquals('select "this is 💩" from "employee"', $this->q() - ->field(new Expression('{}', [ - 'this is 💩' - ])) - ->table('employee') - ->render()); - - $this->assertEquals('select "name" from "employee" "e"', $this->q() - ->field('name') - ->table('employee', 'e') - ->render()); - $this->assertEquals('select * from "employee" "e"', $this->q() - ->table('employee', 'e') - ->render()); - - // multiple tables - $this->assertEquals('select "employee"."name" from "employee","jobs"', $this->q() - ->field('employee.name') - ->table('employee') - ->table('jobs') - ->render()); - $this->assertEquals('select "name" from "employee","jobs"', $this->q() - ->field('name') - ->table('employee,jobs') - ->render()); - $this->assertEquals('select "name" from "employee","jobs"', $this->q() - ->field('name') - ->table(' employee , jobs ') - ->render()); - $this->assertEquals('select "name" from "employee","jobs"', $this->q() - ->field('name') - ->table([ - 'employee', - 'jobs' - ]) - ->render()); - $this->assertEquals('select "name" from "employee","jobs"', $this->q() - ->field('name') - ->table([ - 'employee ', - ' jobs' - ]) - ->render()); - - // multiple tables with aliases - $this->assertEquals('select "name" from "employee","jobs" "j"', $this->q() - ->field('name') - ->table([ - 'employee', - 'j' => 'jobs' - ]) - ->render()); - $this->assertEquals('select "name" from "employee" "e","jobs" "j"', $this->q() - ->field('name') - ->table([ - 'e' => 'employee', - 'j' => 'jobs' - ]) - ->render()); - // testing _render_table_noalias, shouldn't render table alias 'emp' - $this->assertEquals('insert into "employee" ("name") values (:a)', $this->q() - ->field('name') - ->table('employee', 'emp') - ->set('name', 1) - ->mode('insert') - ->render()); - $this->assertEquals('update "employee" set "name"=:a', $this->q() - ->field('name') - ->table('employee', 'emp') - ->set('name', 1) - ->mode('update') - ->render()); - } - - /** - */ - public function testTableRender2() - { - // pass table as expression or query - $q = $this->q()->table('employee'); - - $this->assertEquals('select "name" from (select * from "employee") "e"', $this->q() - ->field('name') - ->table($q, 'e') - ->render()); - - $this->assertEquals('select "name" from "myt""able"', $this->q() - ->field('name') - ->table(new Expression('{}', [ - 'myt"able' - ])) - ->render()); - - // test with multiple sub-queries as tables - $q1 = $this->q()->table('employee'); - $q2 = $this->q()->table('customer'); - - $this->assertEquals( - // this way it would be more correct: 'select "e"."name","c"."name" from (select * from "employee") "e",(select * from "customer") "c" where "e"."last_name" = "c"."last_name"', - 'select "e"."name","c"."name" from (select * from "employee") "e",(select * from "customer") "c" where "e"."last_name" = c.last_name', $this->q() - ->field('e.name') - ->field('c.name') - ->table($q1, 'e') - ->table($q2, 'c') - ->where('e.last_name', $this->q() - ->expr('c.last_name')) - ->render()); - } - - /** - * - * @test - */ - public function testBasicRenderSubquery() - { - $age = new Expression('coalesce([age], [default_age])'); - $age['age'] = new Expression('year(now()) - year(birth_date)'); - $age['default_age'] = 18; - - $q = $this->q() - ->table('user') - ->field($age, 'calculated_age'); - - $this->assertEquals('select coalesce(year(now()) - year(birth_date), :a) "calculated_age" from "user"', $q->render()); - } - - /** - * - * @test - */ - public function testgetDebugQuery() - { - $age = new Expression('coalesce([age], [default_age], [foo], [bar])'); - $age['age'] = new Expression('year(now()) - year(birth_date)'); - $age['default_age'] = 18; - $age['foo'] = 'foo'; - $age['bar'] = null; - - $q = $this->q() - ->table('user') - ->field($age, 'calculated_age'); - - $this->assertEquals("select coalesce(year(now()) - year(birth_date), 18, 'foo', NULL) \"calculated_age\" from \"user\"", strip_tags($q->getDebugQuery())); - } - - /** - * - * @requires PHP 5.6 - */ - public function testVarDump() - { - ini_set('xdebug.overload_var_dump', 'off'); - $this->expectOutputRegex('/.*select \* from "user".*/'); - var_dump($this->q()->table('user')); - } - -// public function testVarDump2() -// { -// ini_set('xdebug.overload_var_dump', 'off'); -// $this->expectOutputRegex('/.*Expression could not render tag.*/'); -// var_dump(new Expression('Hello [world]')); -// } - - public function testVarDump3() - { - ini_set('xdebug.overload_var_dump', 'off'); - $this->expectOutputRegex('/.*Hello \'php\'.*/'); - var_dump(new Expression('Hello [world]', [ - 'world' => 'php' - ])); - } - -// /** -// * -// * @requires PHP 5.6 -// */ -// public function testVarDump4() -// { -// ini_set('xdebug.overload_var_dump', 'off'); -// $this->expectOutputRegex('/.*Table cannot be Query.*/'); -// // should throw exception "Table cannot be Query in UPDATE, INSERT etc. query modes" -// var_dump($this->q() -// ->mode('update') -// ->table($this->q() -// ->table('test'), 'foo')); -// } - - /** - * - * @test - */ - public function testUnionQuery() - { - // 1st query - $q1 = $this->q() - ->table('sales') - ->field('date') - ->field('amount', 'debit') - ->field($this->q() - ->expr('0'), 'credit'); // simply 0 - - $this->assertEquals('select "date","amount" "debit",0 "credit" from "sales"', $q1->render()); - - // 2nd query - $q2 = $this->q() - ->table('purchases') - ->field('date') - ->field($this->q() - ->expr('0'), 'debit') - -> - // simply 0 - field('amount', 'credit'); - $this->assertEquals('select "date",0 "debit","amount" "credit" from "purchases"', $q2->render()); - - // $q1 union $q2 - $u = new Expression('[] union []', [ - $q1, - $q2 - ]); - $this->assertEquals('(select "date","amount" "debit",0 "credit" from "sales") union (select "date",0 "debit","amount" "credit" from "purchases")', $u->render()); - - // SELECT date,debit,credit FROM ($q1 union $q2) - $q = $this->q() - ->field('date,debit,credit') - ->table($u, 'derrivedTable'); - /* - * @see https://github.com/atk4/dsql/issues/33 - * @see https://github.com/atk4/dsql/issues/34 - */ - /* - * $this->assertEquals( - * 'select "date","debit","credit" from ((select "date","amount" "debit",0 "credit" from "sales") union (select "date",0 "debit","amount" "credit" from "purchases")) "derrivedTable"', - * $q->render() - * ); - */ - } - - /** - * where() should return $this Query for chaining. - */ - public function testWhereReturnValue() - { - $q = $this->q(); - $this->assertEquals($q, $q->where('id', 1)); - } - - /** - * having() should return $this Query for chaining. - */ - public function testHavingReturnValue() - { - $q = $this->q(); - $this->assertEquals($q, $q->having('id', 1)); - } - - /** - * Basic where() tests. - */ - public function testWhereBasic() - { - // one parameter as a string - treat as expression - $this->assertEquals('where now()', $this->q('[where]') - ->where('now()') - ->render()); - $this->assertEquals('where foo >= bar', $this->q('[where]') - ->where('foo >= bar') - ->render()); - - // two parameters - field, value - $this->assertEquals('where "id" = :a', $this->q('[where]') - ->where('id', 1) - ->render()); - $this->assertEquals('where "user"."id" = :a', $this->q('[where]') - ->where('user.id', 1) - ->render()); - $this->assertEquals('where "db"."user"."id" = :a', $this->q('[where]') - ->where('db.user.id', 1) - ->render()); - $this->assertEquals('where "id" is :a', $this->q('[where]') - ->where('id', null) - ->render()); - $this->assertEquals('where "id" is :a', $this->q('[where]') - ->where('id', null) - ->render()); - - // three parameters - field, condition, value - $this->assertEquals('where "id" > :a', $this->q('[where]') - ->where('id', '>', 1) - ->render()); - $this->assertEquals('where "id" < :a', $this->q('[where]') - ->where('id', '<', 1) - ->render()); - $this->assertEquals('where "id" = :a', $this->q('[where]') - ->where('id', '=', 1) - ->render()); - $this->assertEquals('where "id" in (:a,:b)', $this->q('[where]') - ->where('id', '=', [ - 1, - 2 - ]) - ->render()); - $this->assertEquals('where "id" in (:a,:b)', $this->q('[where]') - ->where('id', [ - 1, - 2 - ]) - ->render()); - $this->assertEquals('where "id" in (select * from "user")', $this->q('[where]') - ->where('id', $this->q() - ->table('user')) - ->render()); - - // two parameters - more_than_just_a_field, value - $this->assertEquals('where "id" = :a', $this->q('[where]') - ->where('id=', 1) - ->render()); - $this->assertEquals('where "id" != :a', $this->q('[where]') - ->where('id!=', 1) - ->render()); - $this->assertEquals('where "id" <> :a', $this->q('[where]') - ->where('id<>', 1) - ->render()); - - // field name with special symbols - not escape - $this->assertEquals('where now() = :a', $this->q('[where]') - ->where('now()', 1) - ->render()); - - // field name as expression - $this->assertEquals('where now = :a', $this->q('[where]') - ->where(new Expression('now'), 1) - ->render()); - - // more than one where condition - join with AND keyword - $this->assertEquals('where "a" = :a and "b" is :b', $this->q('[where]') - ->where('a', 1) - ->where('b', null) - ->render()); - } - - /** - * Verify that passing garbage to where throw exception. - * - * @expectedException Exception - */ - public function testWhereIncompatibleObject1() - { - $this->q('[where]') - ->where('a', new \DateTime()) - ->render(); - } - - /** - * Verify that passing garbage to where throw exception. - * - * @expectedException Exception - */ - public function testWhereIncompatibleObject2() - { - $this->q('[where]')->where('a', new \DateTime()); - } - - /** - * Verify that passing garbage to where throw exception. - * - * @expectedException Exception - */ - public function testWhereIncompatibleObject3() - { - $this->q('[where]')->where('a', '<>', new \DateTime()); - } - - /** - * Testing where() with special values - null, array, like. - */ - public function testWhereSpecialValues() - { - // in | not in - $this->assertEquals('where "id" in (:a,:b)', $this->q('[where]') - ->where('id', 'in', [ - 1, - 2 - ]) - ->render()); - $this->assertEquals('where "id" not in (:a,:b)', $this->q('[where]') - ->where('id', 'not in', [ - 1, - 2 - ]) - ->render()); - $this->assertEquals('where "id" not in (:a,:b)', $this->q('[where]') - ->where('id', 'not', [ - 1, - 2 - ]) - ->render()); - $this->assertEquals('where "id" in (:a,:b)', $this->q('[where]') - ->where('id', '=', [ - 1, - 2 - ]) - ->render()); - $this->assertEquals('where "id" not in (:a,:b)', $this->q('[where]') - ->where('id', '<>', [ - 1, - 2 - ]) - ->render()); - $this->assertEquals('where "id" not in (:a,:b)', $this->q('[where]') - ->where('id', '!=', [ - 1, - 2 - ]) - ->render()); - // speacial treatment for empty array values - $this->assertEquals('where "id"<>"id"', $this->q('[where]') - ->where('id', '=', []) - ->render()); - $this->assertEquals('where ("id"="id" or "id" is null)', $this->q('[where]') - ->where('id', '<>', []) - ->render()); - // pass array as CSV - $this->assertEquals('where "id" in (:a,:b)', $this->q('[where]') - ->where('id', 'in', '1,2') - ->render()); - $this->assertEquals('where "id" not in (:a,:b)', $this->q('[where]') - ->where('id', 'not in', '1, 2') - ->render()); - $this->assertEquals('where "id" not in (:a,:b)', $this->q('[where]') - ->where('id', 'not', '1,2') - ->render()); - - // is | is not - $this->assertEquals('where "id" is :a', $this->q('[where]') - ->where('id', 'is', null) - ->render()); - $this->assertEquals('where "id" is not :a', $this->q('[where]') - ->where('id', 'is not', null) - ->render()); - $this->assertEquals('where "id" is not :a', $this->q('[where]') - ->where('id', 'not', null) - ->render()); - $this->assertEquals('where "id" is :a', $this->q('[where]') - ->where('id', '=', null) - ->render()); - $this->assertEquals('where "id" is not :a', $this->q('[where]') - ->where('id', '<>', null) - ->render()); - $this->assertEquals('where "id" is not :a', $this->q('[where]') - ->where('id', '!=', null) - ->render()); - - // like | not like - $this->assertEquals('where "name" like :a', $this->q('[where]') - ->where('name', 'like', 'foo') - ->render()); - $this->assertEquals('where "name" not like :a', $this->q('[where]') - ->where('name', 'not like', 'foo') - ->render()); - - // two parameters - more_than_just_a_field, value - // is | is not - $this->assertEquals('where "id" is :a', $this->q('[where]') - ->where('id=', null) - ->render()); - $this->assertEquals('where "id" is not :a', $this->q('[where]') - ->where('id!=', null) - ->render()); - $this->assertEquals('where "id" is not :a', $this->q('[where]') - ->where('id<>', null) - ->render()); - - // in | not in - $this->assertEquals('where "id" in (:a,:b)', $this->q('[where]') - ->where('id=', [ - 1, - 2 - ]) - ->render()); - $this->assertEquals('where "id" not in (:a,:b)', $this->q('[where]') - ->where('id!=', [ - 1, - 2 - ]) - ->render()); - $this->assertEquals('where "id" not in (:a,:b)', $this->q('[where]') - ->where('id<>', [ - 1, - 2 - ]) - ->render()); - } - - /** - * Having basically is the same as where, so we can relax and trouhly test where() instead. - */ - public function testBasicHaving() - { - $this->assertEquals('having "id" = :a', $this->q('[having]') - ->having('id', 1) - ->render()); - $this->assertEquals('having "id" > :a', $this->q('[having]') - ->having('id', '>', 1) - ->render()); - $this->assertEquals('where "id" = :a having "id" > :b', $this->q('[where][having]') - ->where('id', 1) - ->having('id>', 1) - ->render()); - } - - /** - * Test Limit. - */ - public function testLimit() - { - $this->assertEquals('limit 0, 100', $this->q('[limit]') - ->limit(100) - ->render()); - $this->assertEquals('limit 200, 100', $this->q('[limit]') - ->limit(100, 200) - ->render()); - } - - /** - * Test Order. - */ - public function testOrder() - { - $this->assertEquals('order by "name"', $this->q('[order]') - ->order('name') - ->render()); - $this->assertEquals('order by "name", "surname"', $this->q('[order]') - ->order('name,surname') - ->render()); - $this->assertEquals('order by "name" desc, "surname" desc', $this->q('[order]') - ->order('name desc,surname desc') - ->render()); - $this->assertEquals('order by "name" desc, "surname"', $this->q('[order]') - ->order([ - 'name desc', - 'surname' - ]) - ->render()); - $this->assertEquals('order by "name" desc, "surname"', $this->q('[order]') - ->order('surname') - ->order('name desc') - ->render()); - $this->assertEquals('order by "name" desc, "surname"', $this->q('[order]') - ->order('surname', false) - ->order('name', true) - ->render()); - // table name|alias included - $this->assertEquals('order by "users"."name"', $this->q('[order]') - ->order('users.name') - ->render()); - // strange field names - $this->assertEquals('order by "my name" desc', $this->q('[order]') - ->order('"my name" desc') - ->render()); - $this->assertEquals('order by "жук"', $this->q('[order]') - ->order('жук asc') - ->render()); - $this->assertEquals('order by "this is 💩"', $this->q('[order]') - ->order('this is 💩') - ->render()); - $this->assertEquals('order by "this is жук" desc', $this->q('[order]') - ->order('this is жук desc') - ->render()); - $this->assertEquals('order by * desc', $this->q('[order]') - ->order([ - '* desc' - ]) - ->render()); - $this->assertEquals('order by "{}" desc', $this->q('[order]') - ->order([ - '{} desc' - ]) - ->render()); - $this->assertEquals('order by "* desc"', $this->q('[order]') - ->order(new Expression('"* desc"')) - ->render()); - $this->assertEquals('order by "* desc"', $this->q('[order]') - ->order($this->q() - ->escape('* desc')) - ->render()); - $this->assertEquals('order by "* desc {}"', $this->q('[order]') - ->order($this->q() - ->escape('* desc {}')) - ->render()); - // custom sort order - $this->assertEquals('order by "name" desc nulls last', $this->q('[order]') - ->order('name', 'desc nulls last') - ->render()); - $this->assertEquals('order by "name" nulls last', $this->q('[order]') - ->order('name', 'nulls last') - ->render()); - } - - /** - * If first argument is array, second argument must not be used. - * - * @expectedException Exception - */ - public function testOrderException1() - { - $this->q('[order]')->order([ - 'name', - 'surname' - ], 'desc'); - } - - /** - * Test Group. - */ - public function testGroup() - { - $this->assertEquals('group by "gender"', $this->q('[group]') - ->group('gender') - ->render()); - $this->assertEquals('group by "gender", "age"', $this->q('[group]') - ->group('gender,age') - ->render()); - $this->assertEquals('group by "gender", "age"', $this->q('[group]') - ->group([ - 'gender', - 'age' - ]) - ->render()); - $this->assertEquals('group by "gender", "age"', $this->q('[group]') - ->group('gender') - ->group('age') - ->render()); - // table name|alias included - $this->assertEquals('group by "users"."gender"', $this->q('[group]') - ->group('users.gender') - ->render()); - // strange field names - $this->assertEquals('group by "my name"', $this->q('[group]') - ->group('"my name"') - ->render()); - $this->assertEquals('group by "жук"', $this->q('[group]') - ->group('жук') - ->render()); - $this->assertEquals('group by "this is 💩"', $this->q('[group]') - ->group('this is 💩') - ->render()); - $this->assertEquals('group by "this is жук"', $this->q('[group]') - ->group('this is жук') - ->render()); - $this->assertEquals('group by date_format(dat, "%Y")', $this->q('[group]') - ->group(new Expression('date_format(dat, "%Y")')) - ->render()); - $this->assertEquals('group by date_format(dat, "%Y")', $this->q('[group]') - ->group('date_format(dat, "%Y")') - ->render()); - } - - /** - * Test groupConcat. - * - * @expectedException Exception - */ - public function testGroupConcatException() - { - // doesn't support groupConcat by default - $this->q()->groupConcat('foo'); - } - - /** - * Test groupConcat. - */ - public function testGroupConcat() - { - $q = new Query\MySQL(); - $this->assertEquals('group_concat(`foo` separator :a)', $q->groupConcat('foo', '-') - ->render()); - - $q = new Query\Oracle(); - $this->assertEquals('listagg("foo", :a)', $q->groupConcat('foo', '-') - ->render()); - - $q = new Query\Oracle12c(); - $this->assertEquals('listagg("foo", :a)', $q->groupConcat('foo', '-') - ->render()); - - $q = new Query\PgSQL(); - $this->assertEquals('string_agg("foo", :a)', $q->groupConcat('foo', '-') - ->render()); - - $q = new Query\SQLite(); - $this->assertEquals('group_concat("foo", :a)', $q->groupConcat('foo', '-') - ->render()); - } - - /** - * Test expr(). - */ - public function testExpr() - { - $this->assertEquals('Pluf\\Db\\Expression', get_class($this->q() - ->expr('foo'))); - - $q = new Query\MySQL(); - $this->assertEquals('Pluf\\Db\\Expression\\MySQL', get_class($q->expr('foo'))); - } - - /** - * Test Join. - */ - public function testJoin() - { - $this->assertEquals('left join "address" on "address"."id" = "address_id"', $this->q('[join]') - ->join('address') - ->render()); - $this->assertEquals('left join "address" as "a" on "a"."id" = "address_id"', $this->q('[join]') - ->join('address a') - ->render()); - $this->assertEquals('left join "address" as "a" on "a"."id" = "user"."address_id"', $this->q('[join]') - ->table('user') - ->join('address a') - ->render()); - $this->assertEquals('left join "address" as "a" on "a"."id" = "user"."my_address_id"', $this->q('[join]') - ->table('user') - ->join('address a', 'my_address_id') - ->render()); - $this->assertEquals('left join "address" as "a" on "a"."id" = "u"."address_id"', $this->q('[join]') - ->table('user', 'u') - ->join('address a') - ->render()); - $this->assertEquals('left join "address" as "a" on "a"."user_id" = "u"."id"', $this->q('[join]') - ->table('user', 'u') - ->join('address.user_id a') - ->render()); - $this->assertEquals('left join "address" as "a" on "a"."user_id" = "u"."id" ' . 'left join "bank" as "b" on "b"."id" = "u"."bank_id"', $this->q('[join]') - ->table('user', 'u') - ->join([ - 'a' => 'address.user_id', - 'b' => 'bank' - ]) - ->render()); - $this->assertEquals('left join "address" on "address"."user_id" = "u"."id" ' . 'left join "bank" on "bank"."id" = "u"."bank_id"', $this->q('[join]') - ->table('user', 'u') - ->join([ - 'address.user_id', - 'bank' - ]) - ->render()); - $this->assertEquals('left join "address" as "a" on "a"."user_id" = "u"."id" ' . 'left join "bank" as "b" on "b"."id" = "u"."bank_id" ' . 'left join "bank_details" on "bank_details"."id" = "bank"."details_id"', $this->q('[join]') - ->table('user', 'u') - ->join([ - 'a' => 'address.user_id', - 'b' => 'bank' - ]) - ->join('bank_details', 'bank.details_id') - ->render()); - - $this->assertEquals('left join "address" as "a" on a.name like u.pattern', $this->q('[join]') - ->table('user', 'u') - ->join('address a', new Expression('a.name like u.pattern')) - ->render()); - } - - /** - * Combined execution of where() clauses. - */ - public function testCombinedWhere() - { - $this->assertEquals('select "name" from "employee" where "a" = :a', $this->q() - ->field('name') - ->table('employee') - ->where('a', 1) - ->render()); - - $this->assertEquals('select "name" from "employee" where "employee"."a" = :a', $this->q() - ->field('name') - ->table('employee') - ->where('employee.a', 1) - ->render()); - - /* - * $this->assertEquals( - * 'select "name" from "db"."employee" where "db"."employee"."a" = :a', - * $this->q() - * ->field('name')->table('db.employee')->where('db.employee.a',1) - * ->render() - * ); - */ - - $this->assertEquals('delete from "employee" where "employee"."a" = :a', $this->q() - ->mode('delete') - ->field('name') - ->table('employee') - ->where('employee.a', 1) - ->render()); - - $user_ids = $this->q() - ->table('expired_users') - ->field('user_id'); - - $this->assertEquals('update "user" set "active"=:a where "id" in (select "user_id" from "expired_users")', $this->q() - ->table('user') - ->where('id', 'in', $user_ids) - ->set('active', 0) - ->mode('update') - ->render()); - } - - /** - * Test where() when $field is passed as array. - * Should create OR conditions. - */ - public function testOrWhere() - { - $this->assertEquals('select "name" from "employee" where ("a" = :a or "b" = :b)', $this->q() - ->field('name') - ->table('employee') - ->where([ - [ - 'a', - 1 - ], - [ - 'b', - 1 - ] - ]) - ->render()); - - $this->assertEquals('select "name" from "employee" where ("a" = :a or a=b)', $this->q() - ->field('name') - ->table('employee') - ->where([ - [ - 'a', - 1 - ], - 'a=b' - ]) - ->render()); - } - - /** - * Test OrWhere and AndWhere without where condition. - * Should ignore them. - */ - public function testEmptyOrAndWhere() - { - $this->assertEquals('', $this->q() - ->orExpr() - ->render()); - - $this->assertEquals('', $this->q() - ->andExpr() - ->render()); - } - - /** - * Test insert, update and delete templates. - */ - public function testInsertDeleteUpdate() - { - // delete template - $this->assertEquals('delete from "employee" where "name" = :a', $this->q() - ->field('name') - ->table('employee') - ->where('name', 1) - ->mode('delete') - ->render()); - - // update template - $this->assertEquals('update "employee" set "name"=:a', $this->q() - ->field('name') - ->table('employee') - ->set('name', 1) - ->mode('update') - ->render()); - - $this->assertEquals('update "employee" set "name"="name"+1', $this->q() - ->field('name') - ->table('employee') - ->set('name', new Expression('"name"+1')) - ->mode('update') - ->render()); - - // insert template - $this->assertEquals('insert into "employee" ("name") values (:a)', $this->q() - ->field('name') - ->table('employee') - ->set('name', 1) - ->mode('insert') - ->render()); - - // set multiple fields - $this->assertEquals('insert into "employee" ("time","name") values (now(),:a)', $this->q() - ->field('time') - ->field('name') - ->table('employee') - ->set('time', new Expression('now()')) - ->set('name', 'unknown') - ->mode('insert') - ->render()); - - // set as array - $this->assertEquals('insert into "employee" ("time","name") values (now(),:a)', $this->q() - ->field('time') - ->field('name') - ->table('employee') - ->set([ - 'time' => new Expression('now()'), - 'name' => 'unknown' - ]) - ->mode('insert') - ->render()); - } - - /** - * set() should return $this Query for chaining. - */ - public function testSetReturnValue() - { - $q = $this->q(); - $this->assertEquals($q, $q->set('id', 1)); - } - - /** - * Value [false] is not supported by SQL. - * - * @expectedException Exception - */ - public function testSetException1() - { - $this->q()->set('name', false); - } - - /** - * Field name can be expression. - * - * @covers ::set - */ - public function testSetException2() - { - $this->q()->set((new Expression('foo')), 1); - } - - /** - * Test nested OR and AND expressions. - */ - public function testNestedOrAnd() - { - // test 1 - $q = $this->q(); - $q->table('employee')->field('name'); - $q->where($q->orExpr() - ->where('a', 1) - ->where('b', 1)); - $this->assertEquals('select "name" from "employee" where ("a" = :a or "b" = :b)', $q->render()); - - // test 2 - $q = $this->q(); - $q->table('employee')->field('name'); - $q->where($q->orExpr() - ->where('a', 1) - ->where('b', 1) - ->where($q->andExpr() - ->where('true') - ->where('false'))); - $this->assertEquals('select "name" from "employee" where ("a" = :a or "b" = :b or (true and false))', $q->render()); - } - - /** - * Test reset(). - */ - public function testReset() - { - // reset everything - $q = $this->q() - ->table('user') - ->where('name', 'John'); - $q->reset(); - $this->assertEquals('select *', $q->render()); - - // reset particular tag - $q = $this->q() - ->table('user') - ->where('name', 'John') - ->reset('where') - ->where('surname', 'Doe'); - $this->assertEquals('select * from "user" where "surname" = :a', $q->render()); - } - - /** - * Test [option]. - */ - public function testOption() - { - // single option - $this->assertEquals('select calc_found_rows * from "test"', $this->q() - ->table('test') - ->option('calc_found_rows') - ->render()); - // multiple options - $this->assertEquals('select calc_found_rows ignore * from "test"', $this->q() - ->table('test') - ->option('calc_found_rows,ignore') - ->render()); - $this->assertEquals('select calc_found_rows ignore * from "test"', $this->q() - ->table('test') - ->option([ - 'calc_found_rows', - 'ignore' - ]) - ->render()); - // options for specific modes - $q = $this->q() - ->table('test') - ->field('name') - ->set('name', 1) - ->option('calc_found_rows', 'select') - -> - // for default select mode - option('ignore', 'insert'); // for insert mode - - $this->assertEquals('select calc_found_rows "name" from "test"', $q->mode('select') - ->render()); - $this->assertEquals('insert ignore into "test" ("name") values (:a)', $q->mode('insert') - ->render()); - $this->assertEquals('update "test" set "name"=:a', $q->mode('update') - ->render()); - } - - /** - * Test caseExpr (normal). - */ - public function testCaseExprNormal() - { - // Test normal form - $s = $this->q() - ->caseExpr() - ->when([ - 'status', - 'New' - ], 't2.expose_new') - ->when([ - 'status', - 'like', - '%Used%' - ], 't2.expose_used') - ->otherwise(null) - ->render(); - $this->assertEquals('case when "status" = :a then :b when "status" like :c then :d else :e end', $s); - - // with subqueries - $age = new Expression('year(now()) - year(birth_date)'); - $q = $this->q() - ->table('user') - ->field($age, 'calc_age'); - - $s = $this->q() - ->caseExpr() - ->when([ - 'age', - '>', - $q - ], 'Older') - ->otherwise('Younger') - ->render(); - $this->assertEquals('case when "age" > (select year(now()) - year(birth_date) "calc_age" from "user") then :a else :b end', $s); - } - - /** - * Test caseExpr (short form). - */ - public function testCaseExprShortForm() - { - $s = $this->q() - ->caseExpr('status') - ->when('New', 't2.expose_new') - ->when('Used', 't2.expose_used') - ->otherwise(null) - ->render(); - $this->assertEquals('case "status" when :a then :b when :c then :d else :e end', $s); - - // with subqueries - $age = new Expression('year(now()) - year(birth_date)'); - $q = $this->q() - ->table('user') - ->field($age, 'calc_age'); - - $s = $this->q() - ->caseExpr($q) - ->when(100, 'Very old') - ->otherwise('Younger') - ->render(); - $this->assertEquals('case (select year(now()) - year(birth_date) "calc_age" from "user") when :a then :b else :c end', $s); - } - - /** - * Incorrect use of "when" method parameters. - * - * @expected Exception Exception - */ - public function testCaseExprException1() - { - $this->q() - ->caseExpr() - ->when([ - 'status' - ], 't2.expose_new'); - } - - /** - * When using short form CASE statement, then you should not set array as when() method 1st parameter. - * - * @expected Exception Exception - */ - public function testCaseExprException2() - { - $this->q() - ->caseExpr('status') - ->when([ - 'status', - 'New' - ], 't2.expose_new'); - } - - /** - * Tests exprNow() method. - */ - public function testExprNow() - { - $this->assertEquals('update "employee" set "hired"=current_timestamp()', $this->q() - ->field('hired') - ->table('employee') - ->set('hired', $this->q() - ->exprNow()) - ->mode('update') - ->render()); - - $this->assertEquals('update "employee" set "hired"=current_timestamp(:a)', $this->q() - ->field('hired') - ->table('employee') - ->set('hired', $this->q() - ->exprNow(2)) - ->mode('update') - ->render()); - } - - /** - * Test table name with dots in it - Select. - */ - public function testTableNameDot1() - { - // render table - $this->assertEquals('"foo"."bar"', $this->callProtected($this->q() - ->table('foo.bar'), '_render_table')); - - $this->assertEquals('"foo"."bar" "a"', $this->callProtected($this->q() - ->table('foo.bar', 'a'), '_render_table')); - - // where clause - $this->assertEquals('select "name" from "db1"."employee" where "a" = :a', $this->q() - ->field('name') - ->table('db1.employee') - ->where('a', 1) - ->render()); - - $this->assertEquals('select "name" from "db1"."employee" where "db1"."employee"."a" = :a', $this->q() - ->field('name') - ->table('db1.employee') - ->where('db1.employee.a', 1) - ->render()); - } - - /** - * Test WITH. - */ - public function testWith() - { - $q1 = $this->q() - ->table('salaries') - ->field('salary'); - - $q2 = $this->q() - ->with($q1, 'q1') - ->table('q1'); - $this->assertEquals('with "q1" as (select "salary" from "salaries") select * from "q1"', $q2->render()); - - $q2 = $this->q() - ->with($q1, 'q1', null, true) - ->table('q1'); - $this->assertEquals('with recursive "q1" as (select "salary" from "salaries") select * from "q1"', $q2->render()); - - $q2 = $this->q() - ->with($q1, 'q11', [ - 'foo', - 'qwe"ry' - ]) - ->with($q1, 'q12', [ - 'bar', - 'baz' - ], true) - -> - // this one is recursive - table('q11') - ->table('q12'); - $this->assertEquals('with recursive "q11" ("foo","qwe""ry") as (select "salary" from "salaries"),"q12" ("bar","baz") as (select "salary" from "salaries") select * from "q11","q12"', $q2->render()); - - // now test some more useful reql life query - $quotes = $this->q() - ->table('quotes') - ->field('emp_id') - ->field($this->q() - ->expr('sum([])', [ - 'total_net' - ])) - ->group('emp_id'); - $invoices = $this->q() - ->table('invoices') - ->field('emp_id') - ->field($this->q() - ->expr('sum([])', [ - 'total_net' - ])) - ->group('emp_id'); - $q = $this->q() - ->with($quotes, 'q', [ - 'emp', - 'quoted' - ]) - ->with($invoices, 'i', [ - 'emp', - 'invoiced' - ]) - ->table('employees') - ->join('q.emp') - ->join('i.emp') - ->field([ - 'name', - 'salary', - 'q.quoted', - 'i.invoiced' - ]); - $this->assertEquals('with ' . '"q" ("emp","quoted") as (select "emp_id",sum(:a) from "quotes" group by "emp_id"),' . '"i" ("emp","invoiced") as (select "emp_id",sum(:b) from "invoices" group by "emp_id") ' . 'select "name","salary","q"."quoted","i"."invoiced" ' . 'from "employees" ' . 'left join "q" on "q"."emp" = "employees"."id" ' . 'left join "i" on "i"."emp" = "employees"."id"', $q->render()); - } -} diff --git a/tests/Db/RandomTest.php b/tests/Db/RandomTest.php deleted file mode 100644 index 9cf02d89..00000000 --- a/tests/Db/RandomTest.php +++ /dev/null @@ -1,113 +0,0 @@ -. - */ -namespace Pluf\Test\Db; - -use Pluf\Db\Query; -use Pluf\Test\PlufTestCase; - -/** - * - * @coversDefaultClass \atk4\dsql\Query - */ -class RandomTest extends PlufTestCase -{ - - public function q() - { - $args = func_get_args(); - switch (count($args)) { - case 1: - return new Query($args[0]); - case 2: - return new Query($args[0], $args[1]); - } - - return new Query(); - } - - public function testMiscInsert() - { - return $this->markTestIncomplete('This test has not been implemented yet.'); - $data = [ - 'id' => null, - 'system_id' => '3576', - 'system' => null, - 'created_dts' => 123, - 'contractor_from' => null, - 'contractor_to' => null, - 'vat_rate_id' => null, - 'currency_id' => null, - 'vat_period_id' => null, - 'journal_spec_id' => '147735', - 'job_id' => '9341', - 'nominal_id' => null, - 'root_nominal_code' => null, - 'doc_type' => null, - 'is_cn' => 'N', - 'doc_date' => null, - 'ref_no' => '940 testingqq11111', - 'po_ref' => null, - 'total_gross' => '100.00', - 'total_net' => null, - 'total_vat' => null, - 'exchange_rate' => null, - 'note' => null, - 'archive' => 'N', - 'fx_document_id' => null, - 'exchanged_total_net' => null, - 'exchanged_total_gross' => null, - 'exchanged_total_vat' => null, - 'exchanged_total_a' => null, - 'exchanged_total_b' => null - ]; - $q = $this->q(); - $q->mode('insert'); - foreach ($data as $key => $val) { - $q->set($data); - } - $this->assertEquals("insert into (`id`,`system_id`,`system`,`created_dts`,`contractor_from`,`contractor_to`,`vat_rate_id`,`currency_id`,`vat_period_id`,`journal_spec_id`,`job_id`,`nominal_id`,`root_nominal_code`,`doc_type`,`is_cn`,`doc_date`,`ref_no`,`po_ref`,`total_gross`,`total_net`,`total_vat`,`exchange_rate`,`note`,`archive`,`fx_document_id`,`exchanged_total_net`,`exchanged_total_gross`,`exchanged_total_vat`,`exchanged_total_a`,`exchanged_total_b`) values (NULL,'3576',NULL,123,NULL,NULL,NULL,NULL,NULL,'147735','9341',NULL,NULL,NULL,'N',NULL,'940 testingqq11111',NULL,'100.00',NULL,NULL,NULL,NULL,'N',NULL,NULL,NULL,NULL,NULL,NULL) [:ad, :ac, :ab, :aa, :z, :y, :x, :w, :v, :u, :t, :s, :r, :q, :p, :o, :n, :m, :l, :k, :j, :i, :h, :g, :f, :e, :d, :c, :b, :a]", $q->getDebugQuery()); - } - - /** - * confirms that group concat works for all the SQL vendors we support. - */ - public function _groupConcatTest($q, $query) - { - $q->table('people'); - $q->group('age'); - - $q->field('age'); - $q->field($q->groupConcat('name', ',')); - - $q->groupConcat('name', ','); - - $this->assertEquals($query, $q->render()); - } - - public function testGroupConcat() - { - $this->_groupConcatTest(new Query\MySQL(), 'select `age`,group_concat(`name` separator :a) from `people` group by `age`'); - - $this->_groupConcatTest(new Query\SQLite(), 'select "age",group_concat("name", :a) from "people" group by "age"'); - - $this->_groupConcatTest(new Query\PgSQL(), 'select "age",string_agg("name", :a) from "people" group by "age"'); - - $this->_groupConcatTest(new Query\Oracle(), 'select "age",listagg("name", :a) from "people" group by "age"'); - } -} diff --git a/tests/Db/db/ConnectionTest.php b/tests/Db/db/ConnectionTest.php deleted file mode 100644 index 5290e1cc..00000000 --- a/tests/Db/db/ConnectionTest.php +++ /dev/null @@ -1,43 +0,0 @@ -expr("SELECT date('now')")->getOne(); - } - - public function testGenerator() - { - $c = new HelloWorldConnection(); - $test = 0; - foreach ($c->expr('abrakadabra') as $row) { - $test ++; - } - $this->assertEquals(10, $test); - } -} - -// @codingStandardsIgnoreStart -class HelloWorldConnection extends Connection -{ - - public function execute(Expression $e) - { - for ($x = 0; $x < 10; $x ++) { - yield $x => [ - 'greeting' => 'Hello World' - ]; - } - } - - // @codingStandardsIgnoreEnd -} diff --git a/tests/Db/db/PdoSelectTest.php b/tests/Db/db/PdoSelectTest.php deleted file mode 100644 index 22c64265..00000000 --- a/tests/Db/db/PdoSelectTest.php +++ /dev/null @@ -1,20 +0,0 @@ -c = Connection::connect(new \PDO($GLOBALS['DB_DSN'], $GLOBALS['DB_USER'], $GLOBALS['DB_PASSWD'])); - $this->pdo = $this->c->connection(); - - $this->pdo->query('CREATE TEMPORARY TABLE employee (id int not null, name text, surname text, retired bool, PRIMARY KEY (id))'); - } -} diff --git a/tests/Db/db/SelectTest.php b/tests/Db/db/SelectTest.php deleted file mode 100644 index 76c2f1f5..00000000 --- a/tests/Db/db/SelectTest.php +++ /dev/null @@ -1,328 +0,0 @@ -c = Connection::connect($GLOBALS['DB_DSN'], $GLOBALS['DB_USER'], $GLOBALS['DB_PASSWD']); - $this->pdo = $this->c->connection(); - - $this->pdo->query('CREATE TEMPORARY TABLE employee (id int not null, name text, surname text, retired bool, PRIMARY KEY (id))'); - } - - protected function getConnection() - { - return $this->createDefaultDBConnection($this->pdo, $GLOBALS['DB_DBNAME']); - } - - protected function getDataSet() - { - return $this->createFlatXMLDataSet(dirname(__FILE__) . '/SelectTest.xml'); - } - - private function q($table = null, $alias = null) - { - $q = $this->c->dsql(); - - // add table to query if specified - if ($table !== null) { - $q->table($table, $alias); - } - - return $q; - } - - private function e($template = null, $args = null) - { - return $this->c->expr($template, $args); - } - - public function testBasicQueries() - { - $this->assertEquals(4, $this->getConnection() - ->getRowCount('employee')); - - $this->assertEquals([ - 'name' => 'Oliver', - 'surname' => 'Smith' - ], $this->q('employee') - ->field('name,surname') - ->getRow()); - - $this->assertEquals([ - 'surname' => 'Taylor' - ], $this->q('employee') - ->field('surname') - ->where('retired', '1') - ->getRow()); - - $this->assertEquals(4, $this->q() - ->field(new Expression('2+2')) - ->getOne()); - - $this->assertEquals(4, $this->q('employee') - ->field(new Expression('count(*)')) - ->getOne()); - - $names = []; - foreach ($this->q('employee')->where('retired', false) as $row) { - $names[] = $row['name']; - } - $this->assertEquals([ - 'Oliver', - 'Jack', - 'Charlie' - ], $names); - - $this->assertEquals([ - [ - 'now' => 4 - ] - ], $this->q() - ->field(new Expression('2+2'), 'now') - ->get()); - - /* - * Postgresql needs to have values cast, to make the query work. - * But CAST(.. AS int) does not work in mysql. So we use two different tests.. - * (CAST(.. AS int) will work on mariaDB, whereas mysql needs it to be CAST(.. AS signed)) - */ - if ('pgsql' === $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME)) { - $this->assertEquals([ - [ - 'now' => 6 - ] - ], $this->q() - ->field(new Expression('CAST([] AS int)+CAST([] AS int)', [ - 3, - 3 - ]), 'now') - ->get()); - } else { - $this->assertEquals([ - [ - 'now' => 6 - ] - ], $this->q() - ->field(new Expression('[]+[]', [ - 3, - 3 - ]), 'now') - ->get()); - } - - $this->assertEquals(5, $this->q() - ->field(new Expression('COALESCE([],5)', [ - null - ]), 'null_test') - ->getOne()); - } - - public function testExpression() - { - /* - * Postgresql, at least versions before 10, needs to have the string cast to the - * correct datatype. - * But using CAST(.. AS CHAR) will return one single character on postgresql, but the - * entire string on mysql. - */ - if ('pgsql' === $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME)) { - $this->assertEquals('foo', $this->e('select CAST([] AS TEXT)', [ - 'foo' - ]) - ->getOne()); - } else { - $this->assertEquals('foo', $this->e('select CAST([] AS CHAR)', [ - 'foo' - ]) - ->getOne()); - } - } - - /** - * covers atk4\dsql\Expression::__toString, but on PHP 5.5 this hint doesn't work. - */ - public function testCastingToString() - { - // simple value - $this->assertEquals('Williams', (string) $this->q('employee') - ->field('surname') - ->where('name', 'Jack')); - // table as sub-query - $this->assertEquals('Williams', (string) $this->q($this->q('employee'), 'e2') - ->field('surname') - ->where('name', 'Jack')); - // field as expression - $this->assertEquals('Williams', (string) $this->q('employee') - ->field($this->e('surname')) - ->where('name', 'Jack')); - // cast to string multiple times - $q = $this->q('employee') - ->field('surname') - ->where('name', 'Jack'); - $this->assertEquals([ - 'Williams', - 'Williams' - ], [ - (string) $q, - (string) $q - ]); - // cast custom Expression to string - $this->assertEquals('7', (string) $this->e('select 3+4')); - } - - public function testOtherQueries() - { - // truncate table - $this->q('employee')->truncate(); - $this->assertEquals(0, $this->q('employee') - ->field(new Expression('count(*)')) - ->getOne()); - - // insert - $this->q('employee') - ->set([ - 'id' => 1, - 'name' => 'John', - 'surname' => 'Doe', - 'retired' => 1 - ]) - ->insert(); - $this->q('employee') - ->set([ - 'id' => 2, - 'name' => 'Jane', - 'surname' => 'Doe', - 'retired' => 0 - ]) - ->insert(); - $this->assertEquals([ - [ - 'id' => 1, - 'name' => 'John' - ], - [ - 'id' => 2, - 'name' => 'Jane' - ] - ], $this->q('employee') - ->field('id,name') - ->order('id') - ->get()); - $this->assertEquals([ - [ - 'id' => 1, - 'name' => 'John' - ], - [ - 'id' => 2, - 'name' => 'Jane' - ] - ], $this->q('employee') - ->field('id,name') - ->order('id') - ->select() - ->fetchAll()); - - // update - $this->q('employee') - ->where('name', 'John') - ->set('name', 'Johnny') - ->update(); - $this->assertEquals([ - [ - 'id' => 1, - 'name' => 'Johnny' - ], - [ - 'id' => 2, - 'name' => 'Jane' - ] - ], $this->q('employee') - ->field('id,name') - ->order('id') - ->get()); - - // replace - if ('pgsql' !== $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME)) { - $this->q('employee') - ->set([ - 'id' => 1, - 'name' => 'Peter', - 'surname' => 'Doe', - 'retired' => 1 - ]) - ->replace(); - } else { - $this->q('employee') - ->set([ - 'name' => 'Peter', - 'surname' => 'Doe', - 'retired' => 1 - ]) - ->where('id', 1) - ->update(); - } - - // In SQLite replace is just like insert, it just checks if there is - // duplicate key and if it is it deletes the row, and inserts the new - // one, otherwise it just inserts. - // So order of records after REPLACE in SQLite will be [Jane, Peter] - // not [Peter, Jane] as in MySQL, which in theory does the same thing, - // but returns [Peter, Jane] - in original order. - // That's why we add usort here. - $data = $this->q('employee') - ->field('id,name') - ->get(); - usort($data, function ($a, $b) { - return $a['id'] - $b['id']; - }); - $this->assertEquals([ - [ - 'id' => 1, - 'name' => 'Peter' - ], - [ - 'id' => 2, - 'name' => 'Jane' - ] - ], $data); - - // delete - $this->q('employee') - ->where('retired', 1) - ->delete(); - $this->assertEquals([ - [ - 'id' => 2, - 'name' => 'Jane' - ] - ], $this->q('employee') - ->field('id,name') - ->get()); - } - - /** - * - * @expectedException Exception - */ - public function testEmptyGetOne() - { - // truncate table - $this->q('employee')->truncate(); - $this->q('employee') - ->field('name') - ->getOne(); - } -} diff --git a/tests/Db/db/SelectTest.xml b/tests/Db/db/SelectTest.xml deleted file mode 100644 index 4f3cbc71..00000000 --- a/tests/Db/db/SelectTest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/tests/Db/db/TransactionTest.php b/tests/Db/db/TransactionTest.php deleted file mode 100644 index e6b264f6..00000000 --- a/tests/Db/db/TransactionTest.php +++ /dev/null @@ -1,288 +0,0 @@ -c = Connection::connect($GLOBALS['DB_DSN'], $GLOBALS['DB_USER'], $GLOBALS['DB_PASSWD']); -// $this->pdo = $this->c->connection(); - -// $this->pdo->query('CREATE TEMPORARY TABLE employee (id int not null, name text, surname text, retired bool, PRIMARY KEY (id))'); -// } - -// /** -// * @return PHPUnit_Extensions_Database_DB_IDatabaseConnection -// */ -// protected function getConnection() -// { -// return $this->createDefaultDBConnection($this->pdo, $GLOBALS['DB_DBNAME']); -// } - -// /** -// * @return PHPUnit_Extensions_Database_DataSet_IDataSet -// */ -// protected function getDataSet() -// { -// return $this->createFlatXMLDataSet(dirname(__FILE__).'/SelectTest.xml'); -// } - -// private function q($table = null, $alias = null) -// { -// $q = $this->c->dsql(); - -// // add table to query if specified -// if ($table !== null) { -// $q->table($table, $alias); -// } - -// return $q; -// } - -// private function e($template = null, $args = null) -// { -// return $this->c->expr($template, $args); -// } - -// /** -// * @expectedException Exception -// */ -// public function testCommitException1() -// { -// // try to commit when not in transaction -// $this->c->commit(); -// } - -// /** -// * @expectedException Exception -// */ -// public function testCommitException2() -// { -// // try to commit when not in transaction anymore -// $this->c->beginTransaction(); -// $this->c->commit(); -// $this->c->commit(); -// } - -// /** -// * @expectedException Exception -// */ -// public function testRollbackException1() -// { -// // try to rollback when not in transaction -// $this->c->rollBack(); -// } - -// /** -// * @expectedException Exception -// */ -// public function testRollbackException2() -// { -// // try to rollback when not in transaction anymore -// $this->c->beginTransaction(); -// $this->c->rollBack(); -// $this->c->rollBack(); -// } - -// /** -// * Tests simple and nested transactions. -// */ -// public function testTransactions() -// { -// // truncate table, prepare -// $this->q('employee')->truncate(); -// $this->assertEquals( -// 0, -// $this->q('employee')->field(new Expression('count(*)'))->getOne() -// ); - -// // without transaction, ignoring exceptions -// try { -// $this->q('employee') -// ->set(['id' => 1, 'name' => 'John', 'surname' => 'Doe', 'retired' => 1]) -// ->insert(); -// $this->q('employee') -// ->set(['id' => 2, 'FOO' => 'bar', 'name' => 'Jane', 'surname' => 'Doe', 'retired' => 0]) -// ->insert(); -// } catch (\Exception $e) { -// // ignore -// } - -// $this->assertEquals( -// 1, -// $this->q('employee')->field(new Expression('count(*)'))->getOne() -// ); - -// // 1-level transaction: begin, insert, 2, rollback, 1 -// $this->c->beginTransaction(); -// $this->q('employee') -// ->set(['id' => 3, 'name' => 'John', 'surname' => 'Doe', 'retired' => 1]) -// ->insert(); -// $this->assertEquals( -// 2, -// $this->q('employee')->field(new Expression('count(*)'))->getOne() -// ); - -// $this->c->rollBack(); -// $this->assertEquals( -// 1, -// $this->q('employee')->field(new Expression('count(*)'))->getOne() -// ); - -// // atomic method, rolls back everything inside atomic() callback in case of exception -// try { -// $this->c->atomic(function () { -// $this->q('employee') -// ->set(['id' => 3, 'name' => 'John', 'surname' => 'Doe', 'retired' => 1]) -// ->insert(); -// $this->q('employee') -// ->set(['id' => 4, 'FOO' => 'bar', 'name' => 'Jane', 'surname' => 'Doe', 'retired' => 0]) -// ->insert(); -// }); -// } catch (\Exception $e) { -// // ignore -// } - -// $this->assertEquals( -// 1, -// $this->q('employee')->field(new Expression('count(*)'))->getOne() -// ); - -// // atomic method, nested atomic transaction, rolls back everything -// try { -// $this->c->atomic(function () { -// $this->q('employee') -// ->set(['id' => 3, 'name' => 'John', 'surname' => 'Doe', 'retired' => 1]) -// ->insert(); - -// // success, in, fail, out, fail -// $this->c->atomic(function () { -// $this->q('employee') -// ->set(['id' => 4, 'name' => 'John', 'surname' => 'Doe', 'retired' => 1]) -// ->insert(); -// $this->q('employee') -// ->set(['id' => 5, 'FOO' => 'bar', 'name' => 'Jane', 'surname' => 'Doe', 'retired' => 0]) -// ->insert(); -// }); - -// $this->q('employee') -// ->set(['id' => 6, 'FOO' => 'bar', 'name' => 'Jane', 'surname' => 'Doe', 'retired' => 0]) -// ->insert(); -// }); -// } catch (\Exception $e) { -// // ignore -// } - -// $this->assertEquals( -// 1, -// $this->q('employee')->field(new Expression('count(*)'))->getOne() -// ); - -// // atomic method, nested atomic transaction, rolls back everything -// try { -// $this->c->atomic(function () { -// $this->q('employee') -// ->set(['id' => 3, 'name' => 'John', 'surname' => 'Doe', 'retired' => 1]) -// ->insert(); - -// // success, in, success, out, fail -// $this->c->atomic(function () { -// $this->q('employee') -// ->set(['id' => 4, 'name' => 'John', 'surname' => 'Doe', 'retired' => 1]) -// ->insert(); -// }); - -// $this->q('employee') -// ->set(['id' => 5, 'FOO' => 'bar', 'name' => 'Jane', 'surname' => 'Doe', 'retired' => 0]) -// ->insert(); -// }); -// } catch (\Exception $e) { -// // ignore -// } - -// $this->assertEquals( -// 1, -// $this->q('employee')->field(new Expression('count(*)'))->getOne() -// ); - -// // atomic method, nested atomic transaction, rolls back everything -// try { -// $this->c->atomic(function () { -// $this->q('employee') -// ->set(['id' => 3, 'name' => 'John', 'surname' => 'Doe', 'retired' => 1]) -// ->insert(); - -// // success, in, fail, out, catch exception -// $this->c->atomic(function () { -// $this->q('employee') -// ->set(['id' => 4, 'FOO' => 'bar', 'name' => 'John', 'surname' => 'Doe', 'retired' => 1]) -// ->insert(); -// }); - -// $this->q('employee') -// ->set(['id' => 5, 'name' => 'Jane', 'surname' => 'Doe', 'retired' => 0]) -// ->insert(); -// }); -// } catch (\Exception $e) { -// // ignore -// } - -// $this->assertEquals( -// 1, -// $this->q('employee')->field(new Expression('count(*)'))->getOne() -// ); - -// // atomic method, success - commit -// try { -// $this->c->atomic(function () { -// $this->q('employee') -// ->set(['id' => 3, 'name' => 'John', 'surname' => 'Doe', 'retired' => 1]) -// ->insert(); -// }); -// } catch (\Exception $e) { -// // ignore -// } - -// $this->assertEquals( -// 2, -// $this->q('employee')->field(new Expression('count(*)'))->getOne() -// ); -// } - -// /** -// * Tests inTransaction(). -// */ -// public function testInTransaction() -// { -// // inTransaction tests -// $this->assertEquals( -// false, -// $this->c->inTransaction() -// ); - -// $this->c->beginTransaction(); -// $this->assertEquals( -// true, -// $this->c->inTransaction() -// ); - -// $this->c->rollBack(); -// $this->assertEquals( -// false, -// $this->c->inTransaction() -// ); - -// $this->c->beginTransaction(); -// $this->c->commit(); -// $this->assertEquals( -// false, -// $this->c->inTransaction() -// ); -// } -// } diff --git a/tests/Dispatcher/CounterProcessor.php b/tests/Dispatcher/CounterProcessor.php deleted file mode 100644 index b1abd93a..00000000 --- a/tests/Dispatcher/CounterProcessor.php +++ /dev/null @@ -1,48 +0,0 @@ -counter; - if (! isset($counter)) { - $counter = 0; - } - $counter ++; - $request->counter = $counter; - } - - /** - * Redirects all response - * - * {@inheritdoc} - * @see \Pluf\Processor::response() - */ - public function response(Request $request, Response $response): Response - { - if ($response->hasBody()) { - return $response; - } - $response->setBody($request->counter); - return $response; - } -} - diff --git a/tests/Dispatcher/DispatcherTest.php b/tests/Dispatcher/DispatcherTest.php deleted file mode 100755 index 581b16df..00000000 --- a/tests/Dispatcher/DispatcherTest.php +++ /dev/null @@ -1,195 +0,0 @@ -. - */ -namespace Pluf\Test\Dispatcher; - -use Pluf\Dispatcher; -use Pluf\HTTP\Request; -use Pluf\HTTP\Response; -use Pluf\HTTP\Response\Redirect; -use Pluf\Test\PlufTestCase; - -class DispatcherTest extends PlufTestCase -{ - - protected $views = array(); - - /** - * - * @before - */ - public function setUpTest() - { - $this->views = (isset($GLOBALS['_PX_views'])) ? $GLOBALS['_PX_views'] : array(); - } - - /** - * - * @after - */ - public function tearDownTest() - { - $GLOBALS['_PX_views'] = $this->views; - } - - public function hello() - { - return new Response('ok'); - } - - public function hello1() - { - return 1; - } - - public function hello2() - { - return 2; - } - - public function hello3() - { - return 3; - } - - public function hello4() - { - return 4; - } - - /** - * - * @test - */ - public function testSimple() - { - $views = [ - [ - 'regex' => '#^/hello/$#', - 'processors' => [ - SimpleTextProcessor::class - ] - ], - [ - 'regex' => '#^/hello-redirect/$#', - 'processors' => [ - RedirectProcessor::class - ] - ] - ]; - $req1 = new Request('/hello/'); - $req2 = new Request('/hello-redirect/'); - - $dispatcher = Dispatcher::getInstance()->setViews($views); - $this->assertEquals(200, $dispatcher->dispatch($req1) - ->getStatusCode()); - $this->assertEquals('ok', $dispatcher->dispatch($req1) - ->getBody()); - $this->assertInstanceOf(Redirect::class, $dispatcher->dispatch($req2)); - } - - /** - * - * @test - */ - public function testSimpleNotfound() - { - $views = [ - [ - 'regex' => '#^/hello/$#', - 'processors' => [ - SimpleTextProcessor::class - ] - ] - ]; - $this->assertEquals(404, Dispatcher::getInstance()->setViews($views) - ->dispatch(new Request('/hello/you/')) - ->getStatusCode()); - } - - /** - * - * @test - */ - public function testRecursif() - { - $views = [ - [ - 'regex' => '#^/hello$#', - 'processors' => [ - CounterProcessor::class - ] - ], - [ - 'regex' => '#^/hello#', - 'processors' => [ - CounterProcessor::class - ], - 'sub' => [ - [ - 'regex' => '#^/hello$#', - 'processors' => [ - CounterProcessor::class - ] - ], - [ - 'regex' => '#^/hello#', - 'processors' => [ - CounterProcessor::class - ], - 'sub' => [ - [ - 'regex' => '#^/hello$#', - 'processors' => [ - CounterProcessor::class - ] - ], - [ - 'regex' => '#^/hello#', - 'processors' => [ - CounterProcessor::class - ], - 'sub' => [ - [ - 'regex' => '#^/hello$#', - 'processors' => [ - CounterProcessor::class - ] - ] - ] - ] - ] - ] - ] - ] - ]; - - $dispatcher = Dispatcher::getInstance()->setViews($views); - - $this->assertEquals(200, $dispatcher->dispatch(new Request('/hello')) - ->getStatusCode()); - $this->assertEquals(1, $dispatcher->dispatch(new Request('/hello')) - ->getBody()); - $this->assertEquals(2, $dispatcher->dispatch(new Request('/hello/hello')) - ->getBody()); - $this->assertEquals(3, $dispatcher->dispatch(new Request('/hello/hello/hello')) - ->getBody()); - $this->assertEquals(4, $dispatcher->dispatch(new Request('/hello/hello/hello/hello')) - ->getBody()); - } -} \ No newline at end of file diff --git a/tests/Dispatcher/PlufDispatcherTest.php b/tests/Dispatcher/PlufDispatcherTest.php deleted file mode 100755 index 186f9f6b..00000000 --- a/tests/Dispatcher/PlufDispatcherTest.php +++ /dev/null @@ -1,49 +0,0 @@ -. - */ -namespace Pluf\PlufTest\Dispatcher; - -use Pluf\Dispatcher; -use Pluf\Module; -use Pluf\Test\PlufTestCase; - -class DispatcherTest extends PlufTestCase -{ - - /** - * Loads application to start the test - * - * @before - */ - public function setUpTest() - { - \Pluf::start(__DIR__ . '/../conf/config.php'); - } - - /** - * Creates new instance of dispatcher and load module views - * - * @test - */ - public function createNewInstance() - { - $dispatcher = Dispatcher::getInstance(); - - $this->assertNotNull($dispatcher->setViews(Module::loadControllers())); - } -} diff --git a/tests/Dispatcher/RedirectProcessor.php b/tests/Dispatcher/RedirectProcessor.php deleted file mode 100644 index cf0e6ce1..00000000 --- a/tests/Dispatcher/RedirectProcessor.php +++ /dev/null @@ -1,28 +0,0 @@ -. - */ -namespace Pluf\Test; - -use Pluf\Encoder; -use Exception; -use Pluf; - -/** - * - * @backupGlobals disabled - * @backupStaticAttributes disabled - */ -class PlufEncoderTest extends PlufTestCase -{ - - /** - * Undocumented function - * - * @return void - * @before - */ - protected function setUpTest() - { - Pluf::start(__DIR__ . '/../conf/config.php'); - } - - /** - * Undocumented function - * - * @after - * @return void - */ - protected function tearDownTest() - { - putenv('PHP_TZ=' . Pluf::getConfig('timezone')); - } - - public function testEncoder() - { - $p = array(); - $form = array(); - $enc = new Encoder(); - $this->assertEquals(true, $enc->checkEmpty('', $form, $p)); - $p['blank'] = false; - // ------------- url ------------------------------- - $good = array( - 'http://www.example.com/lkjasd', - 'https://wwwcom/lkjasd', - 'https://www-com/lkjasd', - 'http://123.345.234.12/lkjasd' - ); - $bad = array( - 'www.com' - ); - foreach ($good as $url) { - $this->assertEquals($url, $enc->url($url, $form, $p)); - } - foreach ($bad as $url) { - try { - $enc->url($url, $form, $p); - $this->assertEquals(false, $url); - } catch (Exception $e) { - $this->assertEquals(true, true); - } - } - // ------------- date ------------------------------- - $good = array( - '1995-12-04', - '1995-12-1', - '1000-2-2', - '9999-12-31' - ); - $bad = array( - '23-12-2', - '1996-2-31', - '2006.05.12' - ); - foreach ($good as $date) { - $this->assertEquals($date, $enc->date($date, $form, $p)); - } - foreach ($bad as $date) { - try { - $enc->date($date, $form, $p); - $this->assertEquals(false, $date); - } catch (Exception $e) { - $this->assertEquals(true, true); - } - } - } - - public function testTimeShift() - { - $enc = new Encoder(); - $p = array( - 'blank' => false - ); - $form = array(); - // When passing a datetime (not a date and not a time) - // from the browser, the datetime must be converted - // into GMT time. - $tests = array(); - $tests[] = array( - 'Europe/Berlin', - '2006-03-16 01:15:35', - '2006-03-16 00:15:35' - ); - $tests[] = array( - 'America/New_York', - '2006-03-16 01:15:35', - '2006-03-16 06:15:35' - ); - $tests[] = array( - 'America/Los_Angeles', - '2006-03-16 01:15:35', - '2006-03-16 09:15:35' - ); - foreach ($tests as $test) { - putenv('TZ=' . $test[0]); - date_default_timezone_set($test[0]); - $this->assertEquals($test[2], $enc->datetime($test[1], $form, $p)); - $this->assertEquals($test[1], date('Y-m-d H:i:s', strtotime($test[2] . ' GMT'))); - } - } -} \ No newline at end of file diff --git a/tests/ExceptionWrapper.php b/tests/ExceptionWrapper.php deleted file mode 100644 index 7eb76c92..00000000 --- a/tests/ExceptionWrapper.php +++ /dev/null @@ -1,25 +0,0 @@ -previous = $previous; - parent::__construct($message, $code, $previous); - } -} diff --git a/tests/Graphql/Compiler/ModelRelationTest.php b/tests/Graphql/Compiler/ModelRelationTest.php deleted file mode 100755 index 9c55e6ea..00000000 --- a/tests/Graphql/Compiler/ModelRelationTest.php +++ /dev/null @@ -1,160 +0,0 @@ -. - */ -namespace Pluf\Test\Graphql\Compiler; - -use PHPUnit\Framework\TestCase; -use Pluf\Graphql\Compiler; -use Pluf\Relation\ManyToManyOne; -use Pluf\Relation\ManyToManyTwo; -use Pluf\Relation\ModelRecurse; -use Pluf; - -class ModelRelationTest extends TestCase -{ - - /** - * - * @beforeClass - */ - public static function installApplication1() - { - Pluf::start(__DIR__ . '/../../conf/config.php'); - $m = new \Pluf\Migration(); - $m->install(); - } - - /** - * - * @afterClass - */ - public static function removeDatabses1() - { - Pluf::start(__DIR__ . '/../../conf/config.php'); - $m = new \Pluf\Migration(); - $m->uninstall(); - } - - /** - * - * @test - */ - public function testForeignkeyRenderAndRun() - { - // create data - $model = new ModelRecurse(); - $model->title = 'myvalue'; - $this->assertEquals('myvalue', $model->title); - $model->create(); - - $model2 = new ModelRecurse(); - $model2->title = 'child 1'; - $model2->parent_id = $model->getId(); - $this->assertEquals(true, $model2->create()); - - $model3 = new ModelRecurse(); - $model3->title = 'child 2'; - $model3->parent_id = $model->getId(); - $this->assertEquals(true, $model3->create()); - - $class_name = 'Pluf_GraphQl_Model_Test_' . rand(); - $filename = Pluf::getConfig('tmp_folder', '/tmp') . '/' . $class_name . '.phps'; - if (file_exists($filename)) { - unlink($filename); - } - $compiler = new Compiler(ModelRecurse::class); - $compiler->write($class_name, $filename); - $this->assertTrue(file_exists($filename)); - include $filename; - - $rootValue = new ModelRecurse($model2->id); - - // get all - $compiler = new $class_name(); - $result = $compiler->render($rootValue, '{id, parent{id}}'); - $this->assertFalse(array_key_exists('errors', $result)); - - $result = $result['data']; - $this->assertTrue(array_key_exists('parent', $result)); - - $parnet = $result['parent']; - $this->assertEquals($parnet['id'], $model->id); - - $rootValue = new ModelRecurse($model->id); - $result = $compiler->render($rootValue, '{id, title, children{id, title, parent_id, parent{id}}}'); - $this->assertFalse(array_key_exists('errors', $result)); - $result = $result['data']; - $this->assertTrue(array_key_exists('children', $result)); - $children = $result['children']; - $this->assertEquals(sizeof($children), 2); - foreach ($children as $child) { - $this->assertTrue(array_key_exists('parent_id', $child)); - $this->assertTrue(array_key_exists('parent', $child)); - } - } - - /** - * - * @test - */ - public function testManyToManyRenderAndRun() - { - // create data - $model = new ManyToManyOne(); - $model->one = 'One item '; - $this->assertTrue($model->create()); - - $model2 = new ManyToManyTwo(); - $model2->two = 'Two item'; - $this->assertEquals(true, $model2->create()); - - $model->setAssoc($model2); - - $class_name = 'Pluf_GraphQl_Model_ManyToMany_' . rand(); - $filename = Pluf::f('tmp_folder', '/tmp') . '/' . $class_name . '.phps'; - if (file_exists($filename)) { - unlink($filename); - } - $compiler = new Compiler(ManyToManyOne::class); - $compiler->write($class_name, $filename); - $this->assertTrue(file_exists($filename)); - include $filename; - - $rootValue = new ManyToManyOne($model->id); - - // get all - $compiler = new $class_name(); - $result = $compiler->render($rootValue, '{id, twos{id}}'); - $this->assertFalse(array_key_exists('errors', $result)); - - $result = $result['data']; - $this->assertTrue(array_key_exists('twos', $result)); - - $parnet = $result['twos'][0]; - $this->assertEquals($parnet['id'], $model->id); - - // - $result = $compiler->render($rootValue, '{id, twos{id, ones{id}}}'); - $this->assertFalse(array_key_exists('errors', $result)); - $this->assertEquals($result['data']['twos'][0]['ones'][0]['id'], $rootValue->id); - } -} - - - - diff --git a/tests/Graphql/Compiler/ModelTest.php b/tests/Graphql/Compiler/ModelTest.php deleted file mode 100755 index 808c025c..00000000 --- a/tests/Graphql/Compiler/ModelTest.php +++ /dev/null @@ -1,136 +0,0 @@ -. - */ -namespace Pluf\Test\Graphql\Compiler; - -use PHPUnit\Framework\TestCase; -use Pluf\Graphql\Compiler; -use Pluf\NoteBook\Book; -use Pluf; - -class ModelTest extends TestCase -{ - - /** - * - * @beforeClass - */ - public static function installApplication1() - { - Pluf::start(__DIR__ . '/../../conf/config.php'); - $m = new \Pluf\Migration(); - $m->install(); - } - - /** - * - * @afterClass - */ - public static function removeDatabses1() - { - Pluf::start(__DIR__ . '/../../conf/config.php'); - $m = new \Pluf\Migration(); - $m->uninstall(); - } - - /** - * - * @test - */ - public function testRenderAndLoad() - { - $types = [ - // model item - '\Pluf\NoteBook\Book' => null, - '\Pluf\NoteBook\Item' => null, - '\Pluf\NoteBook\Tag' => null - ]; - foreach ($types as $rootType => $itemType) { - $class_name = 'Pluf_GraphQl_TestRender_' . rand(); - $filename = Pluf::f('tmp_folder', '/tmp') . '/' . $class_name . '.phps'; - if (file_exists($filename)) { - unlink($filename); - } - $compiler = new Compiler($rootType, $itemType); - $compiler->write($class_name, $filename); - $this->assertTrue(file_exists($filename)); - - include $filename; - class_exists($class_name); - } - } - - /** - * - * @test - */ - public function testRenderAndRun() - { - $class_name = 'Pluf_GraphQl_Model_Test_' . rand(); - $filename = Pluf::f('tmp_folder', '/tmp') . '/' . $class_name . '.phps'; - if (file_exists($filename)) { - unlink($filename); - } - $compiler = new Compiler(Book::class); - $compiler->write($class_name, $filename); - $this->assertTrue(file_exists($filename)); - - include $filename; - class_exists($class_name); - - $rootValue = new Book(); - $rootValue->id = 1; - $rootValue->title = 'title'; - $rootValue->description = 'description'; - - // get all - $compiler = new $class_name(); - $result = $compiler->render($rootValue, '{id, title, description}'); - $this->assertFalse(array_key_exists('errors', $result)); - $this->assertTrue(array_key_exists('data', $result)); - - $result = $result['data']; - $this->assertTrue(array_key_exists('id', $result)); - $this->assertEquals($result['id'], $rootValue->id); - - $this->assertTrue(array_key_exists('title', $result)); - $this->assertEquals($result['title'], $rootValue->title); - - $this->assertTrue(array_key_exists('description', $result)); - $this->assertEquals($result['description'], $rootValue->description); - - // get id - $compiler = new $class_name(); - $result = $compiler->render($rootValue, '{id}'); - $this->assertFalse(array_key_exists('errors', $result)); - $this->assertTrue(array_key_exists('data', $result)); - - $result = $result['data']; - $this->assertTrue(array_key_exists('id', $result)); - $this->assertEquals($result['id'], $rootValue->id); - - // get invalid - $compiler = new $class_name(); - $result = $compiler->render($rootValue, '{idx}'); - $this->assertTrue(array_key_exists('errors', $result)); - } -} - - - - diff --git a/tests/Graphql/Compiler/PaginatorTest.php b/tests/Graphql/Compiler/PaginatorTest.php deleted file mode 100755 index ce1460d4..00000000 --- a/tests/Graphql/Compiler/PaginatorTest.php +++ /dev/null @@ -1,101 +0,0 @@ -. -// */ -// use PHPUnit\Framework\TestCase; - -// set_include_path(get_include_path() . PATH_SEPARATOR . __DIR__ . '/../apps'); - -// /** -// * -// * @backupGlobals disabled -// * @backupStaticAttributes disabled -// */ -// class Pluf_Graphql_Compiler_PaginatorTest extends TestCase -// { - -// /** -// * -// * @beforeClass -// */ -// public static function installApplication1() -// { -// $conf = include __DIR__ . '/../conf/config.php'; -// $conf['installed_apps'] = array( -// 'bootstrap', -// 'Test' -// ); -// Pluf::start($conf); -// $m = new \Pluf\Migration($conf['installed_apps']); -// $m->install(); -// } - -// /** -// * -// * @afterClass -// */ -// public static function removeDatabses1() -// { -// $m = new \Pluf\Migration(array( -// 'bootstrap', -// 'Test' -// )); -// $m->uninstall(); -// } - -// /** -// * -// * @test -// */ -// public function testRenderPaginatorAndLoad() -// { -// $types = [ -// // Paginated list -// 'Pluf_Paginator' => 'Test_Model', -// 'Pluf_Paginator' => 'Test_ModelRecurse', -// 'Pluf_Paginator' => 'Test_ModelCount', -// 'Pluf_Paginator' => 'Test_RelatedToTestModel', -// 'Pluf_Paginator' => 'Test_RelatedToTestModel2', -// 'Pluf_Paginator' => 'Test_ManyToManyOne', -// 'Pluf_Paginator' => 'Test_ManyToManyTwo' -// ]; -// foreach ($types as $rootType => $itemType) { -// $class_name = 'Pluf_GraphQl_TestRender_' . rand(); -// $filename = Pluf::f('tmp_folder', '/tmp') . '/' . $class_name . '.phps'; -// if (file_exists($filename)) { -// unlink($filename); -// } -// $compiler = new Pluf_Graphql_Compiler($rootType, $itemType); -// $compiler->write($class_name, $filename); -// $this->assertTrue(file_exists($filename)); - -// include $filename; -// class_exists($class_name); - -// $builder = new Pluf_Paginator_Builder(new Test_Model()); -// $rootValue = $builder->build(); - -// $compiler = new $class_name(); -// $result = $compiler->render($rootValue, '{items{id}}'); -// $this->assertFalse(array_key_exists('errors', $result)); -// $this->assertTrue(array_key_exists('data', $result)); -// } -// } -// } - - - diff --git a/tests/Graphql/RenderTest.php b/tests/Graphql/RenderTest.php deleted file mode 100755 index 7d376d48..00000000 --- a/tests/Graphql/RenderTest.php +++ /dev/null @@ -1,100 +0,0 @@ -. - */ -namespace Pluf\Test\Graphql; - -use PHPUnit\Framework\TestCase; -use Pluf\Graphql; -use Pluf\NoteBook\Book; -use Pluf; - -class RenderTest extends TestCase -{ - - /** - * - * @beforeClass - */ - public static function installApplication1() - { - // Load config - Pluf::start(__DIR__ . '/../conf/config.php'); - $migration = new \Pluf\Migration(); - $migration->install(); - } - - /** - * - * @afterClass - */ - public static function removeDatabses1() - { - Pluf::start(__DIR__ . '/../conf/config.php'); - $m = new \Pluf\Migration(); - $m->uninstall(); - } - - /** - * - * @test - */ - public function testRenderAndRun() - { - $rootValue = new Book(); - $rootValue->id = 1; - $rootValue->title = 'title'; - $rootValue->description = 'description'; - - $gl = new Graphql(); - $result = $gl->render($rootValue, '{id, title, description}'); - $this->assertTrue(array_key_exists('id', $result)); - $this->assertTrue(array_key_exists('title', $result)); - $this->assertTrue(array_key_exists('description', $result)); - } - - /** - * - * @test - */ - public function testRenderAndRunNonDebug() - { - $rootValue = new Book(); - $rootValue->id = 1; - $rootValue->title = 'title'; - $rootValue->description = 'description'; - - $conf = include __DIR__ . '/../conf/config.php'; - $conf['installed_apps'] = array( - 'bootstrap', - 'Test' - ); - $conf['debug'] = false; - Pluf::start($conf); - - for ($i = 0; $i < 2; $i ++) { - $gl = new Graphql(); - $result = $gl->render($rootValue, '{id, title, description}'); - $this->assertTrue(array_key_exists('id', $result)); - $this->assertTrue(array_key_exists('title', $result)); - $this->assertTrue(array_key_exists('description', $result)); - } - } -} - - - diff --git a/tests/HTTP/Response/ExceptionTest.php b/tests/HTTP/Response/ExceptionTest.php deleted file mode 100644 index 4c4dff80..00000000 --- a/tests/HTTP/Response/ExceptionTest.php +++ /dev/null @@ -1,42 +0,0 @@ -. - */ -namespace Pluf\Test\HTTP\Response; - -use PHPUnit\Framework\TestCase; -use Pluf\HTTP\Response\ServerError; - -/** - * - * @backupGlobals disabled - * @backupStaticAttributes disabled - */ -class ExceptionTest extends TestCase -{ - - /** - * - * @test - */ - public function createInternallErrorResponse() - { - $e = new \Exception(); - $resp = new ServerError($e); - $this->assertNotNull($resp); - } -} diff --git a/tests/HTTP/Response/FileHashCodeTest.php b/tests/HTTP/Response/FileHashCodeTest.php deleted file mode 100644 index 1d498e1e..00000000 --- a/tests/HTTP/Response/FileHashCodeTest.php +++ /dev/null @@ -1,72 +0,0 @@ -. - */ -namespace Pluf\Test\HTTP\Response; - -use PHPUnit\Framework\TestCase; -use Pluf\HTTP\Response\File; - -/** - * - * @backupGlobals disabled - * @backupStaticAttributes disabled - */ -class FileHashCodeTest extends TestCase -{ - - /** - * - * @before - */ - public function setUpTest() - { - // TODO: - $this->TestFileHashCode = md5_file(__DIR__ . '/TestFile'); - } - - /** - * - * @test - */ - public function testHashFunction() - { - $response = new File(__DIR__ . '/TestFile'); - $this->assertTrue(method_exists($response, 'hashCode')); - } - - /** - * - * @test - */ - public function testHashFunction1() - { - $response = new File(__DIR__ . '/TestFile'); - $this->assertTrue($this->TestFileHashCode === $response->hashCode()); - } - - /** - * - * @test - */ - public function testHashFunction2() - { - $response = new File(__DIR__ . '/TestFile'); - $this->assertTrue($this->TestFileHashCode === $response->hashCode()); - $this->assertTrue($this->TestFileHashCode === $response->hashCode()); - } -} diff --git a/tests/HTTP/Response/HashCodeTest.php b/tests/HTTP/Response/HashCodeTest.php deleted file mode 100755 index ccbd33e3..00000000 --- a/tests/HTTP/Response/HashCodeTest.php +++ /dev/null @@ -1,62 +0,0 @@ -. - */ -namespace Pluf\Test\HTTP\Response; - -use PHPUnit\Framework\TestCase; -use Pluf\HTTP\Response; - -/** - * - * @backupGlobals disabled - * @backupStaticAttributes disabled - */ -class HashCodeTest extends TestCase -{ - - /** - * - * @test - */ - public function testHashFunction() - { - $response = new Response('Hi'); - $this->assertTrue(method_exists($response, 'hashCode')); - } - - /** - * - * @test - */ - public function testHashFunction1() - { - $response = new Response('Hi'); - $this->assertTrue(md5('Hi') === $response->hashCode()); - } - - /** - * - * @test - */ - public function testHashFunction2() - { - $response = new Response('Hi'); - $this->assertTrue(md5('Hi') === $response->hashCode()); - $this->assertTrue(md5('Hi') === $response->hashCode()); - } -} diff --git a/tests/HTTP/Response/TestFile b/tests/HTTP/Response/TestFile deleted file mode 100644 index 06964f04..00000000 --- a/tests/HTTP/Response/TestFile +++ /dev/null @@ -1 +0,0 @@ -Thi is a test file to test Pluf_HTTP_Response_File. \ No newline at end of file diff --git a/tests/HTTP/URLTest.php b/tests/HTTP/URLTest.php deleted file mode 100755 index 6ab9ca09..00000000 --- a/tests/HTTP/URLTest.php +++ /dev/null @@ -1,137 +0,0 @@ -. - */ -use PHPUnit\Framework\TestCase; -use Pluf\HTTP\URL; - -/** - * - * @backupGlobals disabled - * @backupStaticAttributes disabled - */ -class URLTest extends TestCase -{ - - protected function setUp() - { - Pluf::start(__DIR__ . '/../conf/config.php'); - } - - public function testGenerateSimple() - { - $murl = new URL('simple'); - $url = $murl->generate('/toto/titi/', array( - 'param1' => 'value%*one', - 'param2' => 'value&two' - ), false); - $this->assertRegExp('/^\?.*\_px\_action\=.*$/', $url); - $this->assertRegExp('/^\?.*param1\=.*$/', $url); - $this->assertRegExp('/^\?.*param2\=.*$/', $url); - } - - public function testGenerateModRewrite() - { - $murl = new URL('mod_rewrite'); - $url = $murl->generate('/toto/titi/', array( - 'param1' => 'value%*one', - 'param2' => 'value&two' - ), false); - $this->assertEquals('/toto/titi/?param1=value%' . '25%2Aone¶m2=value%26two', $url); - } - - public function testGetActionModRewrite() - { - $_SERVER['PATH_INFO'] = '/toto/titi/'; - $murl = new URL('mod_rewrite'); - $this->assertEquals('/toto/titi/', $murl->getAction()); - } - - public function testGetActionModRewriteOrgin() - { - $_SERVER['ORIG_PATH_INFO'] = '/toto/titi/'; - $murl = new URL('mod_rewrite'); - $this->assertEquals('/toto/titi/', $murl->getAction()); - } - - public function testGetActionSimple() - { - $_GET['_px_action'] = '/toto/titi/'; - $murl = new URL('simple'); - $this->assertEquals('/toto/titi/', $murl->getAction()); - } - - public function testReverseSimpleUrl() - { - $url = URL::buildReverseUrl('#^/toto/$#'); - $this->assertEquals('/toto/', $url); - } - - public function testReverseSimpleArgUrl() - { - $url = URL::buildReverseUrl('#^/toto/(\d+)/$#', array( - 23 - )); - $this->assertEquals('/toto/23/', $url); - } - - public function testReverseMultipleArgUrl() - { - $url = URL::buildReverseUrl('#^/toto/(\d+)/asd/(.*)/$#', array( - 23, - 'titi' - )); - $this->assertEquals('/toto/23/asd/titi/', $url); - } - - public function testComplexReverseMultipleArgUrl() - { - $url = URL::buildReverseUrl('#^/toto/([A-Z]{2})/asd/(.*)/$#', array( - 'AB', - 'titi' - )); - $this->assertEquals('/toto/AB/asd/titi/', $url); - } - - public function testReverseWithBackSlashes() - { - $url = URL::buildReverseUrl('#^/toto/(.*)\.txt$#', array( - 'AB' - )); - $this->assertEquals('/toto/AB.txt', $url); - } - - // Reversce not supported anymore - // public function testReverseMultipleArgUrlFailure() - // { - // $url_regex = '#^/toto/(\s+)/asd/(.*)/$#'; - // $params = array( - // '23', - // 'titi' - // ); - // $url = URL::buildReverseUrl($url_regex, $params); - // $this->fail('An exception as not been raised, regex:' . $url_regex . ' should not match params: ' . var_export($params, true)); - // } - // public function testReverseUrlFromView() - // { - // $url = URL::reverse('Todo_Views::updateItem', array( - // '32' - // )); - // $this->assertEquals('/item/32/update/', $url); - // } -} - diff --git a/tests/Logger/LoggerAppenderFileTest.php b/tests/Logger/LoggerAppenderFileTest.php deleted file mode 100644 index 1d1d5bd7..00000000 --- a/tests/Logger/LoggerAppenderFileTest.php +++ /dev/null @@ -1,24 +0,0 @@ -write($message); - $this->assertNotNull($loggerHandler); - } -} - diff --git a/tests/Logger/LoggerTest.php b/tests/Logger/LoggerTest.php deleted file mode 100644 index 2492928d..00000000 --- a/tests/Logger/LoggerTest.php +++ /dev/null @@ -1,33 +0,0 @@ -assertTrue(true); - } -} - diff --git a/tests/Mail/PlufMailTest.php b/tests/Mail/PlufMailTest.php deleted file mode 100755 index c2ef3f30..00000000 --- a/tests/Mail/PlufMailTest.php +++ /dev/null @@ -1,46 +0,0 @@ -. - */ -namespace Pluf\Test\Mail; - -use PHPUnit\Framework\TestCase; -use Pluf; - -/** - * - * @backupGlobals disabled - * @backupStaticAttributes disabled - */ -class PlufMailTest extends TestCase -{ - - /** - * - * @before - * @return void - */ - protected function setUpTest() - { - Pluf::start(__DIR__ . '/../conf/config.php'); - } - - public function testGenerateEmail() - { - $this->markTestSkipped('It implies PEAR testing and PHPUnit does not work well with PEAR.'); - } -} diff --git a/tests/Mail/check_on.png b/tests/Mail/check_on.png deleted file mode 100755 index 079d930b326d284e35a292d1cb1cfdc1f6b4dd5f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 767 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$3?vg*uel1On2Vh}LpV4%Za?&Yz`($05#STz z`v3p`FF!wi{`uk4kN2N`zJKuk#=ZB~?!LQn=k1kSZ!X?^bN=kPGeEHU+M;78j~qR5 z&Lnb;e&ia1h}C+r%grNJ z8icLZ30$ETxI#5*iD|%cRsZG6p^NpymgxH~Q}9_T@3%xbbiRJbJblk4G9mNz-4{u@ zEs_YDtLwTz!ePFE!+b%9xk9${c-*He*v{hjo~&juo84=on$>h34*y zjCxsIo7D6=7o)O4B|w3`^T8yU3f7_@2`G;0_%s~I$_7&Iyw)XNyu ziW$@j8B_}xRB{+pvKc-q+&2IQX+}wqUobEX;eY|oqCVsF|LX}rJ2?wHB8wRqxP?HN z@zUM8KR`jT64!_l=c3falFa-(g^UaF zqc`l{uv<7Xq4BeP)A{|i`}h=E&fIg-OgzZ2WNZ87kd2PdzgXn#^VoLl?e=H3ENb-= z-t;Zs61Ba!NnMm%$6T5BL>l*kM_ige%V#Gpm=V|%!sjygp@oN;wibg@{iFNU8~D`u zCS1|mqtL=I^?G#q^M67|wf{-5wHyEbQ. - */ -namespace Pluf\Test\Migration; - -use PHPUnit\Framework\TestCase; -use Pluf\NoteBook\Book; -use Pluf; - -/** - * Single tenant test - * - * @backupGlobals disabled - * @backupStaticAttributes disabled - */ -class InitTest extends TestCase -{ - - /** - * - * @beforeClass - */ - public static function createDataBase() - { - $_SERVER['REQUEST_METHOD'] = 'GET'; - $_SERVER['REQUEST_URI'] = '/'; - $_SERVER['REMOTE_ADDR'] = '/'; - - $GLOBALS['_PX_uniqid'] = '1234'; - } - - /** - * - * @test - */ - public function shouldInstallEmptyApp() - { - $conf = include __DIR__ . '/../conf/config.php'; - $conf['installed_apps'] = array( - 'Smallest' - ); - $conf['db_table_prefix'] = 'pluf_unit_tests_' . rand() . '_'; - Pluf::start($conf); - $m = new \Pluf\Migration(array( - 'NoteBook' - )); - $this->assertTrue($m->install()); - $this->assertTrue($m->uninstall()); - } - - /** - * - * @test - */ - public function shouldInitEmptyFromConfig() - { - $conf = include __DIR__ . '/../conf/config.php'; - $conf['installed_apps'] = array( - 'Smallest' - ); - $conf['db_table_prefix'] = 'pluf_unit_tests_' . rand() . '_'; - Pluf::start($conf); - $m = new \Pluf\Migration(array( - 'Smallest' - )); - $this->assertTrue($m->install()); - - $this->assertTrue($m->init()); - $this->assertTrue($m->uninstall()); - } - - /** - * - * @test - */ - public function shouldInstallNoteApp() - { - $conf = include __DIR__ . '/../conf/config.php'; - $conf['installed_apps'] = array( - 'NoteBook' - ); - $conf['db_table_prefix'] = 'pluf_unit_tests_' . rand() . '_'; - Pluf::start($conf); - $m = new \Pluf\Migration(array( - 'NoteBook' - )); - $this->assertTrue($m->install()); - $this->assertTrue($m->uninstall()); - } - - /** - * - * @test - */ - public function shouldInitNoteFromConfig() - { - $conf = include __DIR__ . '/../conf/config.php'; - $conf['installed_apps'] = array( - 'NoteBook' - ); - Pluf::start($conf); - $m = new \Pluf\Migration(array( - 'NoteBook' - )); - $this->assertTrue($m->install()); - - $this->assertTrue($m->init()); - - $note = new Book(); - $this->assertTrue(sizeof($note->getList()) > 0, 'Notes are not created'); - - $this->assertTrue($m->unInstall()); - } -} - - - diff --git a/tests/Migration/MtInitTest.php b/tests/Migration/MtInitTest.php deleted file mode 100644 index 68c542bf..00000000 --- a/tests/Migration/MtInitTest.php +++ /dev/null @@ -1,139 +0,0 @@ -. - */ -namespace Pluf\Test\Migration; - -use PHPUnit\Framework\TestCase; -use Pluf\NoteBook\Book; -use Pluf\Pluf\Tenant; -use Pluf; - -/** - * Single tenant test - * - * @backupGlobals disabled - * @backupStaticAttributes disabled - */ -class Pluf_Migration_MtnitTest extends TestCase -{ - - /** - * - * @test - */ - public function shouldInstallEmptyApp() - { - $conf = include __DIR__ . '/../conf/config.php'; - $conf['multitenant'] = true; - Pluf::start($conf); - $m = new \Pluf\Migration(array( - 'Pluf', - 'Smallest' - )); - - $this->assertTrue($m->install()); - $this->assertTrue($m->uninstall()); - } - - /** - * - * @test - */ - public function shouldInitEmptyFromConfig() - { - $conf = include __DIR__ . '/../conf/config.php'; - $conf['installed_apps'] = array( - 'Smallest' - ); - Pluf::start($conf); - $m = new \Pluf\Migration(array( - 'Pluf', - 'Smallest' - )); - $this->assertTrue($m->install()); - - $tenant = new Tenant(); - $tenant->title = 'Default Tenant'; - $tenant->description = 'Auto generated tenant'; - $tenant->subdomain = Pluf::f('tenant_default', 'main'); - $tenant->domain = Pluf::f('general_domain', 'donate.com'); - $tenant->create(); - $this->assertTrue($m->init($tenant)); - - $this->assertTrue($m->uninstall()); - } - - /** - * - * @test - */ - public function shouldInstallNoteApp() - { - $conf = include __DIR__ . '/../conf/config.php'; - $conf['installed_apps'] = array( - 'NoteBook' - ); - $conf['db_table_prefix'] = 'pluf_unit_tests_' . rand() . '_'; - Pluf::start($conf); - $m = new \Pluf\Migration(array( - 'Pluf', - 'NoteBook' - )); - $this->assertTrue($m->install()); - $this->assertTrue($m->uninstall()); - } - - /** - * - * @test - */ - public function shouldInitNoteFromConfig() - { - $conf = include __DIR__ . '/../conf/config.php'; - $conf['installed_apps'] = array( - 'NoteBook' - ); - $conf['db_table_prefix'] = 'pluf_unit_tests_' . rand() . '_'; - Pluf::start($conf); - $m = new \Pluf\Migration(array( - 'Pluf', - 'NoteBook' - )); - $this->assertTrue($m->install()); - - $tenant = new Tenant(); - $tenant->title = 'Default Tenant'; - $tenant->description = 'Auto generated tenant'; - $tenant->subdomain = Pluf::f('tenant_default', 'main'); - $tenant->domain = Pluf::f('general_domain', 'donate.com'); - $tenant->create(); - $this->assertTrue($m->init($tenant)); - - // 1- Switch Tenant to the new one - Tenant::setCurrent($tenant); - - // 2- Create new instance of book - $note = new Book(); - $this->assertTrue(sizeof($note->getList()) > 0, 'Notes are not created'); - - $this->assertTrue($m->unInstall()); - } -} - - - diff --git a/tests/ObjectMappertTest.php b/tests/ObjectMappertTest.php deleted file mode 100644 index 621ed235..00000000 --- a/tests/ObjectMappertTest.php +++ /dev/null @@ -1,64 +0,0 @@ -headers->setHeader('Content-Type', 'application/x-www-form-urlencoded'); - $request->REQUEST['title'] = $title = 'title' . rand(); - $request->method = 'POST'; - - $this->assertTrue(ObjectMapper::getInstance($request)->hasMore()); - $book = ObjectMapper::getInstance($request)->next($book); - $this->assertEquals($title, $book->title); - } - - /** - * - * @test - */ - public function fillBooksFromArray() - { - $books = [ - [ - "title" => "title" . rand(), - "description" => "description" . rand() - ], - [ - "title" => "x" . rand(), - "description" => "z" . rand() - ] - ]; - $mapper = ObjectMapper::getInstance($books); - for ($i = 0; $i < count($books); $i ++) { - $this->assertTrue($mapper->hasMore()); - $item = $mapper->next(Book::class); - $this->assertEquals($books[$i]['title'], $item->title); - $this->assertEquals($books[$i]['description'], $item->description); - } - } -} - diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php deleted file mode 100644 index c980d220..00000000 --- a/tests/OptionsTest.php +++ /dev/null @@ -1,114 +0,0 @@ - 'a', - 'b' => 'b' - )); - $this->assertEquals($options->a, 'a'); - $this->assertEquals($options->b, 'b'); - } - - /** - * Checks if you can use an option as default vlaues - * - * @test - */ - public function getDefaultValueFromOptionTest() - { - $options = new Options(); - $options->a = 'a'; - $options->b = 'b'; - $this->assertEquals($options->a, 'a'); - $this->assertEquals($options->b, 'b'); - - $options2 = new Options($options); - $this->assertEquals($options2->a, 'a'); - $this->assertEquals($options2->b, 'b'); - } - - /** - * Check override of default value - * - * @test - */ - public function getOverridedDefaultValue() - { - $options = new Options(array( - 'a' => 'a', - 'b' => 'b' - )); - $options->a = 'c'; - $this->assertEquals($options->a, 'c'); - $this->assertEquals($options->b, 'b'); - } - - /** - * - * @test - */ - public function getOptionsStartWith() - { - $options = new Options(array( - 'a_a' => 'a', - 'a_b' => 'b' - )); - - $pre = $options->startsWith('a_', true); - $this->assertEquals($pre->a, 'a'); - $this->assertEquals($pre->b, 'b'); - - $pre = $options->startsWith('a_', false); - $this->assertEquals($pre->a_a, 'a'); - $this->assertEquals($pre->a_b, 'b'); - } - - /** - * - * @test - */ - public function getOptionsStartWithChain() - { - $options = new Options(array( - 'a_b_a' => 'a', - 'a_b_b' => 'b' - )); - - $pre = $options->startsWith('a_', true)->startsWith('b_', true); - $this->assertEquals($pre->a, 'a'); - $this->assertEquals($pre->b, 'b'); - - $pre = $options->startsWith('a_', true)->startsWith('b_', false); - $this->assertEquals($pre->b_a, 'a'); - $this->assertEquals($pre->b_b, 'b'); - - $pre = $options->startsWith('a_', false)->startsWith('a_b_', false); - $this->assertEquals($pre->a_b_a, 'a'); - $this->assertEquals($pre->a_b_b, 'b'); - } -} - diff --git a/tests/PlufCryptTest.php b/tests/PlufCryptTest.php deleted file mode 100755 index 5b9a35be..00000000 --- a/tests/PlufCryptTest.php +++ /dev/null @@ -1,38 +0,0 @@ -. - */ -namespace Pluf\Test; - -use Pluf\Crypt; -use PHPUnit\Framework\TestCase; - -class PlufCryptTest extends TestCase -{ - - /** - * - * @test - */ - public function testEncrypt() - { - $crypt = new Crypt('mykeyasdkjhsdkfjsdhfksjdh'); - $a = $crypt->encrypt('Thisisalongemail.name@longemailcompany.domain.com'); - $this->assertEquals($crypt->decrypt($a), 'Thisisalongemail.name@longemailcompany.domain.com'); - } -} - diff --git a/tests/PlufResultPrinter.php b/tests/PlufResultPrinter.php deleted file mode 100644 index 0b7c4b4f..00000000 --- a/tests/PlufResultPrinter.php +++ /dev/null @@ -1,29 +0,0 @@ -thrownException(); - if (! $e instanceof ExceptionWrapper) { - parent::printDefectTrace($defect); - - return; - } - $this->write((string) $e); - - $p = $e->getPrevious(); - - if ($p instanceof \Pluf\Exception) { - $this->write($p->getColorfulText()); - } - } -} \ No newline at end of file diff --git a/tests/PlufTest.php b/tests/PlufTest.php deleted file mode 100755 index 48822046..00000000 --- a/tests/PlufTest.php +++ /dev/null @@ -1,141 +0,0 @@ -. - */ -namespace Pluf\Test; - -use PHPUnit\Framework\TestCase; -use Pluf; - -class PlufTest extends TestCase -{ - - /** - * - * @before - */ - public function setUpTest() - { - $conf = include __DIR__ . '/conf/config.php'; - $conf['test-var'] = false; - Pluf::start($conf); - } - - /** - * - * @test - */ - public function testF() - { - $this->assertEquals(Pluf::f('test-var'), false); - } - - /** - * - * @test - */ - public function testFactory() - { - $pluf = Pluf::factory('Pluf'); - $this->assertEquals(get_class($pluf), 'Pluf'); - - $pluf = Pluf::factory(Pluf::class); - $this->assertEquals(get_class($pluf), Pluf::class); - } - - /** - * - * @test - */ - public function testFileExists() - { - $this->assertTrue(Pluf::fileExists('PlufTest.php') !== false); - } - - /** - * - * @test - */ - public function testLoadClass() - { - Pluf::loadClass('\Pluf\Data\Model'); - $this->assertEquals(true, class_exists('\Pluf\Data\Model')); - } - - /** - * - * @test - */ - public function phpConceptsTest() - { - /* - * We are about to accept the folloing RFC : - * https://wiki.php.net/rfc/class_name_literal_on_object - * - * How ever the oldest RFC is accepted and used in the code - */ - $this->assertEquals(Pluf::class, 'Pluf'); - // PHP8 - // $obj = new Pluf_Dispatcher(); - // $this->assertEquals($obj::class, 'Pluf_Dispatcher'); - } - - /** - * - * @test - */ - public function testLoadCache() - { - Pluf::start([ - 'cache_engine' => 'array' - ]); - $this->assertTrue(Pluf::getCache() instanceof \Pluf\Cache\ArrayCache); - - Pluf::start([ - 'cache_engine' => 'file', - 'cache_file_timeout' => 1234 - ]); - $this->assertTrue(Pluf::getCache() instanceof Pluf\Cache\File); - $this->assertEquals(1234, Pluf::getCache()->getTimeout()); - } - - /** - * - * @test - */ - public function testLoadOptions() - { - Pluf::start([ - 'xxx' => 'yyy', - 'a_xxx' => 'yyy' - ]); - $this->assertEquals('yyy', Pluf::getConfig('xxx')); - $this->assertTrue(Pluf::getConfigByPrefix('a_') instanceof Pluf\Options); - } - - /** - * - * @test - */ - public function testLoadDbConnection() - { - Pluf::start([ - 'db_dsn' => 'sqlite::memory:' - ]); - $this->assertTrue(Pluf::db() instanceof Pluf\Db\Connection); - } -} diff --git a/tests/PlufTestCase.php b/tests/PlufTestCase.php deleted file mode 100644 index 20691c07..00000000 --- a/tests/PlufTestCase.php +++ /dev/null @@ -1,75 +0,0 @@ -getMethod($name); - $method->setAccessible(true); - - return $method->invokeArgs($obj, $args); - } - - /** - * Returns protected property value. - * - * NOTE: this method must only be used for low-level functionality, not - * for general test-scripts. - * - * @param object $obj - * @param string $name - * - * @throws \ReflectionException - * - * @return mixed - */ - public function getProtected($obj, $name) - { - $class = new \ReflectionClass($obj); - $method = $class->getProperty($name); - $method->setAccessible(true); - - return $method->getValue($obj); - } - - /** - * Fake test. - * Otherwise Travis gives warning that there are no tests in here. - */ - public function testFake() - { - $this->assertTrue(true); - } -} diff --git a/tests/PlufUtilsTest.php b/tests/PlufUtilsTest.php deleted file mode 100755 index 634ac54d..00000000 --- a/tests/PlufUtilsTest.php +++ /dev/null @@ -1,178 +0,0 @@ -. - */ -namespace Pluf\Test; - -use PHPUnit\Framework\TestCase; -use Pluf\Utils; - -class PlufUtilsTest extends TestCase -{ - - // /** - // * - // * @before - // */ - // protected function setUpTest() - // { - // Pluf::start(__DIR__ . '/../conf/config.php'); - // } - - /** - * - * @test - */ - public function testCleanName() - { - $files = array( - array( - 'normal', - 'normal' - ), - array( - 'nor mal', - 'nor_mal' - ), - array( - 'nor mal.zip', - 'nor_mal.zip' - ), - array( - 'néor mal.zip', - 'néor_mal.zip' - ) - ); - // Double byte effect - foreach ($files as $file) { - $this->assertTrue($file[1] === Utils::cleanFileName($file[0])); - } - } - - public function testValidEmail() - { - $emails = array( - array( - 'test1@example.com', - true - ), - array( - 'test1@example.com-qwe.', - false - ), - array( - 'cal@iamcalx.com', - true - ), - array( - 'cal+henderson@iamcalx.com', - true - ), - array( - 'cal henderson@iamcalx.com', - false - ), - array( - '"cal henderson"@iamcalx.com', - false - ), - array( - 'cal@iamcalx', - true - ), - array( - 'cal@iamcalx com', - false - ), - array( - 'cal@hello world.com', - false - ), - array( - 'cal@[hello].com', - false - ), - array( - 'cal@[hello world].com', - false - ), - array( - 'cal@[hello\\ world].com', - false - ), - array( - 'cal@[hello.com]', - true - ), - array( - 'cal@[hello world.com]', - false - ), - array( - 'cal@[hello\\ world.com]', - false - ), - array( - 'abcdefghijklmnopqrstuvwxyz@abcdefghijklmnopqrstuvwxyz', - true - ), - array( - 'woo\\ yay@example.com', - false - ), - array( - 'woo\\@yay@example.com', - false - ), - array( - 'woo\\.yay@example.com', - false - ), - array( - '"woo yay"@example.com', - false - ), - array( - '"woo@yay"@example.com', - true - ), - array( - '"woo.yay"@example.com', - true - ), - array( - '"woo\\"yay"@test.com', - true - ), - array( - 'webstaff@redcross.org', - true - ), - array( - 'user@???', - true - ), - array( - 'user.@domain.com', - false - ) - ); - foreach ($emails as $email) { - $this->assertFalse($email[1] xor Utils::isValidEmail($email[0])); - } - } -} diff --git a/tests/Process/Http/AccessLogTest.php b/tests/Process/Http/AccessLogTest.php new file mode 100644 index 00000000..913f48f8 --- /dev/null +++ b/tests/Process/Http/AccessLogTest.php @@ -0,0 +1,86 @@ + + * Copyright (C) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +namespace Pluf\Tests\Process\Http; + +use PHPUnit\Framework\TestCase; +use Pluf\Scion\UnitTrackerInterface; +use Pluf\Scion\Process\Http\AccessLog; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Log\LoggerInterface; + +/** + * AccessLog test case. + */ +class AccessLogTest extends TestCase +{ + + private ?AccessLog $accessLog; + + /** + * + * @befor + */ + public function setUpText() + { + parent::setUp(); + $this->accessLog = new AccessLog(/* parameters */); + } + + /** + * + * @after + */ + public function tearDownTest() + { + $this->accessLog = null; + parent::tearDown(); + } + + /** + * + * @test + */ + public function testInvokeByRequst() + { + // Mocking request + $requestMock = $this->createMock(RequestInterface::class); + + // Mocking response + $responseMock = $this->createMock(ResponseInterface::class); + $responseMock->expects($this->once()) + ->method('getStatusCode') + ->willReturn(200); + + // Mocking unit tracker + $unitTrackerMock = $this->createMock(UnitTrackerInterface::class); + $unitTrackerMock->expects($this->once()) + ->method('next') + ->willReturn($responseMock); + + // Mocking logs + $loggerMock = $this->createMock(LoggerInterface::class); + $loggerMock->expects($this->once()) + ->method('info'); + + $accessLog = new AccessLog(); + $accessLog($requestMock, $unitTrackerMock, $loggerMock); + } +} + diff --git a/tests/Process/Http/FileToResponseTest.php b/tests/Process/Http/FileToResponseTest.php new file mode 100644 index 00000000..0635bdd6 --- /dev/null +++ b/tests/Process/Http/FileToResponseTest.php @@ -0,0 +1,47 @@ +requestFactory = new ServerRequestFactory(); + $this->responseFactory = new ResponseFactory(); + } + + /** + * + * @test + */ + public function existedOriginTest() + { + $origin = __DIR__ . '/assets/sample-1.jpeg'; + + // process tracker mock + $processTracker = $this->createMock(UnitTrackerInterface::class); + $processTracker->expects($this->once()) + ->method('next') + ->willReturn($origin); + + $request = $this->requestFactory->createServerRequest('GET', '/download/file'); + $response = $this->responseFactory->createResponse(500, 'no result'); + $process = new FileToResponse(); + $res = $process($request, $response, $processTracker); + $this->assertNotNull($res); + } +} + + diff --git a/tests/Process/Http/IfMethodIsTest.php b/tests/Process/Http/IfMethodIsTest.php new file mode 100644 index 00000000..a548d688 --- /dev/null +++ b/tests/Process/Http/IfMethodIsTest.php @@ -0,0 +1,214 @@ + + * Copyright (C) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +namespace Pluf\Tests\Process\Http; + +use PHPUnit\Framework\TestCase; +use Pluf\Scion\UnitTrackerInterface; +use Pluf\Scion\Process\Http\IfMethodIsDelete; +use Pluf\Scion\Process\Http\IfMethodIsGet; +use Pluf\Scion\Process\Http\IfMethodIsPost; +use Psr\Http\Message\RequestInterface; +use Pluf\Scion\Process\Http\IfMethodIsPut; +use Pluf\Scion\Process\Http\IfMethodIs; + +/** + * IfMethodIsDelete test case. + */ +class IfMethodIsTest extends TestCase +{ + + public function getMethodTestData(): array + { + return [ + // ---------------------------IfMethodIsPost---------------------------- + [ + "GET", + "jump", + rand(), + new IfMethodIsPost() + ], + [ + "POST", + "next", + rand(), + new IfMethodIsPost() + ], + [ + "PUT", + "jump", + rand(), + new IfMethodIsPost() + ], + [ + "HEAD", + "jump", + rand(), + new IfMethodIsPost() + ], + [ + "DELETE", + "jump", + rand(), + new IfMethodIsPost() + ], + // ---------------------------IfMethodIsGet---------------------------- + [ + "GET", + "next", + rand(), + new IfMethodIsGet() + ], + [ + "POST", + "jump", + rand(), + new IfMethodIsGet() + ], + [ + "PUT", + "jump", + rand(), + new IfMethodIsGet() + ], + [ + "HEAD", + "jump", + rand(), + new IfMethodIsGet() + ], + [ + "DELETE", + "jump", + rand(), + new IfMethodIsGet() + ], + // ---------------------------IfMethodIsPut---------------------------- + [ + "GET", + "jump", + rand(), + new IfMethodIsPut() + ], + [ + "POST", + "jump", + rand(), + new IfMethodIsPut() + ], + [ + "PUT", + "next", + rand(), + new IfMethodIsPut() + ], + [ + "HEAD", + "jump", + rand(), + new IfMethodIsPut() + ], + [ + "DELETE", + "jump", + rand(), + new IfMethodIsPut() + ], + // ---------------------------IfMethodIsDelet---------------------------- + [ + "GET", + "jump", + rand(), + new IfMethodIsDelete() + ], + [ + "POST", + "jump", + rand(), + new IfMethodIsDelete() + ], + [ + "PUT", + "jump", + rand(), + new IfMethodIsDelete() + ], + [ + "HEAD", + "jump", + rand(), + new IfMethodIsDelete() + ], + [ + "DELETE", + "next", + rand(), + new IfMethodIsDelete() + ], + // ---------------------------IfMethodIs---------------------------- + [ + "GET", + "next", + rand(), + new IfMethodIs("GET") + ], + [ + "POST", + "next", + rand(), + new IfMethodIs("POST") + ], + [ + "DELETE", + "next", + rand(), + new IfMethodIs("DELETE") + ], + [ + "PUT", + "next", + rand(), + new IfMethodIs("PUT") + ] + ]; + } + + /** + * + * @dataProvider getMethodTestData + * @test + */ + public function testIfMethodIs($requestMethod, $jumbOrNext, $result, $process) + { + // Mocking request + $requestMock = $this->createMock(RequestInterface::class); + $requestMock->expects($this->once()) + ->method("getMethod") + ->willReturn($requestMethod); + + // Mocking unit tracker + $unitTrackerMock = $this->createMock(UnitTrackerInterface::class); + $unitTrackerMock->expects($this->once()) + ->method($jumbOrNext) + ->willReturn($result); + + $actual = $process($requestMock, $unitTrackerMock); + $this->assertEquals($result, $actual, "Result value is not match with unit return value"); + } +} + diff --git a/tests/Process/Http/IfPathAndMethodIsTest.php b/tests/Process/Http/IfPathAndMethodIsTest.php new file mode 100644 index 00000000..db4b8d96 --- /dev/null +++ b/tests/Process/Http/IfPathAndMethodIsTest.php @@ -0,0 +1,122 @@ + + * Copyright (C) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +namespace Pluf\Tests\Process\Http; + +use PHPUnit\Framework\TestCase; +use Pluf\Http\UriFactory; +use Pluf\Scion\UnitTrackerInterface; +use Pluf\Scion\Process\Http\IfPathAndMethodIs; +use Psr\Http\Message\RequestInterface; + +/** + * IfMethodIsDelete test case. + */ +class IfPathAndMethodIsTest extends TestCase +{ + + public ?UriFactory $uriFactory = null; + + /** + * + * @before + */ + public function initServices() + { + $this->uriFactory = new UriFactory(); + } + + public function getPathAndMethodData(): array + { + return [ + [ + new IfPathAndMethodIs('#^/test$#', [ + "GET", + "POST", + "DELETE", + "PUT" + ], true), + '/test', + 'HEAD', + rand(), + 'jump' + ], + [ + new IfPathAndMethodIs('#^/test$#', [ + "GET", + "POST", + "DELETE", + "PUT" + ], true), + '/1test', + 'PUT', + rand(), + 'jump' + ], + [ + new IfPathAndMethodIs('#^/test$#', [ + "POST", + "GET" + ], true), + '/test', + 'GET', + rand(), + 'next' + ], + [ + new IfPathAndMethodIs('#^/test$#', [ + "POST", + "GET" + ], false), + '/test', + 'GET', + rand(), + 'next' + ] + ]; + } + + /** + * + * @dataProvider getPathAndMethodData + * @test + */ + public function testIfMethodIs($process, $requestPath, $requestMethod, $result, $jumbOrNext) + { + // Mocking request + $requestMock = $this->createMock(RequestInterface::class); + // get method + $requestMock->expects($this->once()) + ->method("getMethod") + ->willReturn($requestMethod); + // get url and path + $requestMock->expects($this->once()) + ->method("getUri") + ->willReturn($this->uriFactory->createUri('http://test.com' . $requestPath)); + + // Mocking unit tracker + $unitTrackerMock = $this->createMock(UnitTrackerInterface::class); + $unitTrackerMock->expects($this->once()) + ->method($jumbOrNext) + ->willReturn($result); + + $actual = $process($requestMock, $unitTrackerMock); + $this->assertEquals($result, $actual, "Result value is not match with unit return value"); + } +} + diff --git a/tests/Process/Http/IfPathIsTest.php b/tests/Process/Http/IfPathIsTest.php new file mode 100644 index 00000000..d3331717 --- /dev/null +++ b/tests/Process/Http/IfPathIsTest.php @@ -0,0 +1,98 @@ + + * Copyright (C) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +namespace Pluf\Tests\Process\Http; + +use PHPUnit\Framework\TestCase; +use Pluf\Http\UriFactory; +use Pluf\Scion\UnitTrackerInterface; +use Pluf\Scion\Process\Http\IfPathIs; +use Psr\Http\Message\RequestInterface; + +/** + * IfMethodIsDelete test case. + */ +class IfPathIsTest extends TestCase +{ + + public ?UriFactory $uriFactory = null; + + /** + * + * @before + */ + public function initServices() + { + $this->uriFactory = new UriFactory(); + } + + public function getPathAndMethodData(): array + { + return [ + [ + new IfPathIs('#^/test$#', true), + '/test', + rand(), + 'next' + ], + [ + new IfPathIs('#^/test$#', true), + '/1test', + rand(), + 'jump' + ], + [ + new IfPathIs('#^/test$#', true), + '/test', + rand(), + 'next' + ], + [ + new IfPathIs('#^/test$#', false), + '/test', + rand(), + 'next' + ] + ]; + } + + /** + * + * @dataProvider getPathAndMethodData + * @test + */ + public function testIfMethodIs($process, $requestPath, $result, $jumbOrNext) + { + // Mocking request + $requestMock = $this->createMock(RequestInterface::class); + // get url and path + $requestMock->expects($this->once()) + ->method("getUri") + ->willReturn($this->uriFactory->createUri('http://test.com' . $requestPath)); + + // Mocking unit tracker + $unitTrackerMock = $this->createMock(UnitTrackerInterface::class); + $unitTrackerMock->expects($this->once()) + ->method($jumbOrNext) + ->willReturn($result); + + $actual = $process($requestMock, $unitTrackerMock); + $this->assertEquals($result, $actual, "Result value is not match with unit return value"); + } +} + diff --git a/tests/Process/Http/Mock/ReturnRequestParsedBody.php b/tests/Process/Http/Mock/ReturnRequestParsedBody.php new file mode 100644 index 00000000..4e3eba67 --- /dev/null +++ b/tests/Process/Http/Mock/ReturnRequestParsedBody.php @@ -0,0 +1,14 @@ +getParsedBody(); + } +} + diff --git a/tests/Process/Http/RequestBodyParserTest.php b/tests/Process/Http/RequestBodyParserTest.php new file mode 100644 index 00000000..56515c6d --- /dev/null +++ b/tests/Process/Http/RequestBodyParserTest.php @@ -0,0 +1,109 @@ +requestFactory = new ServerRequestFactory(); + } + + public function generateRawData() + { + return [ + [ + "POST", // methos + (object) [ + "id" => 1, + "float" => 2.3, + "name" => "example string", + "array" => [ + 1, + 2.3, + "example string" + ], + "object" => (object) [ + "id" => 1, + "float" => 2.3, + "name" => "example string" + ] + ] + ], + [ + "POST", // methos + [ + 1, + 2.3, + "example string" + ] + ], + [ // internal array + "POST", // methos + [ + 1, + 2.3, + "example string", + [ + 1, + 2.3, + "example string" + ] + ] + ] + ]; + } + + public function generateValidStreamJSON() + { + $data = $this->generateRawData(); + $streamBuilder = new StreamFactory(); + for ($i = 0; $i < count($data); $i ++) { + $data[$i][] = "application/json"; + $data[$i][] = $streamBuilder->createStream(json_encode($data[$i][1])); + } + return $data; + } + + /** + * + * @dataProvider generateValidStreamJSON + * @test + */ + public function checkParseBodyNotFail($requestMethod, $sourceBody, $contentType, $stream) + { + $uriFactory = new UriFactory(); + $requestFactory = new ServerRequestFactory(); + $request = $requestFactory->createServerRequest($requestMethod, $uriFactory->createUri("http://test.com/api")); + $request = $request->withMethod($requestMethod) + ->withBody($stream) + ->withAddedHeader("Content-Type", $contentType); + + // Mocking unit tracker + $unitTracker = new UnitTracker([ + RequestBodyParser::class, + ReturnRequestParsedBody::class + ]); + + $result = $unitTracker->doProcess([ + "request" => $request + ]); + $this->assertNotNull($result); + $this->assertEquals($sourceBody, $result); + } +} + diff --git a/tests/Process/Http/assets/sample-1.jpeg b/tests/Process/Http/assets/sample-1.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..24a0aaa1783fd8fc76533137426f1322ee89036c GIT binary patch literal 110738 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 literal 0 HcmV?d00001 diff --git a/tests/Processors/TenantSingleTest.php b/tests/Processors/TenantSingleTest.php deleted file mode 100644 index 5c6089e0..00000000 --- a/tests/Processors/TenantSingleTest.php +++ /dev/null @@ -1,69 +0,0 @@ -install(); - } - - /** - * - * @afterClass - */ - public static function removeApplication() - { - // unistall - $migration = new \Pluf\Migration(); - $migration->uninstall(); - } - - /** - * - * @test - */ - public function shouldRedirectOnBadTenant() - { - $_SERVER['HTTP_HOST'] = 'xxx.' . rand(); - - $this->assertEquals(200, Dispatcher::getInstance()->setViews(Module::loadControllers()) - ->dispatch(new Request('/helloword/HelloWord')) - ->getStatusCode()); - } - - /** - * - * @test - */ - public function shouldRedirectOnBadDomainNameTenant() - { - $_SERVER['HTTP_HOST'] = 'x x x.' . rand(); - - $this->assertEquals(200, Dispatcher::getInstance()->setViews(Module::loadControllers()) - ->dispatch(new Request('/helloword/HelloWord')) - ->getStatusCode()); - } -} \ No newline at end of file diff --git a/tests/Processors/TenantTest.php b/tests/Processors/TenantTest.php deleted file mode 100644 index e1ed1f64..00000000 --- a/tests/Processors/TenantTest.php +++ /dev/null @@ -1,141 +0,0 @@ -install(); - } - - /** - * - * @afterClass - */ - public static function removeApplication() - { - // unistall - $migration = new \Pluf\Migration(); - $migration->uninstall(); - } - - /** - * - * @test - */ - public function shouldRedirectOnBadTenant() - { - $_SERVER['HTTP_HOST'] = 'xxx.' . rand(); - - $this->assertEquals(302, Dispatcher::getInstance()->setViews(Module::loadControllers()) - ->dispatch(new Request('/helloword/HelloWord')) - ->getStatusCode()); - } - - /** - * - * @test - */ - public function shouldRedirectOnBadDomainNameTenant() - { - $_SERVER['HTTP_HOST'] = 'x x x.' . rand(); - - $this->assertEquals(302, Dispatcher::getInstance()->setViews(Module::loadControllers()) - ->dispatch(new Request('/helloword/HelloWord')) - ->getStatusCode()); - } - - /** - * - * @test - */ - public function shouldFindTenantBasedDomain() - { - $_SERVER['HTTP_HOST'] = 'xxx.' . rand(); - - $tenant = new Tenant(); - $tenant->domain = $_SERVER['HTTP_HOST']; - $tenant->subdomain = 'yyy' . rand(); - $tenant->create(); - $this->assertFalse($tenant->isAnonymous()); - - $this->assertEquals(200, Dispatcher::getInstance()->setViews(Module::loadControllers()) - ->dispatch(new Request('/helloword/HelloWord')) - ->getStatusCode()); - } - - /** - * - * @test - */ - public function shouldFindTenantBasedSubdomain() - { - /* - * From Dispatcher - */ - $sub = rand() . 'xxx' . rand(); - $_SERVER['HTTP_HOST'] = $sub . '.' . rand(); - - $tenant = new Tenant(); - $tenant->domain = $_SERVER['HTTP_HOST']; - $tenant->subdomain = $sub; - $tenant->create(); - $this->assertFalse($tenant->isAnonymous()); - - $this->assertEquals(200, Dispatcher::getInstance()->setViews(Module::loadControllers()) - ->dispatch(new Request('/helloword/HelloWord')) - ->getStatusCode()); - } - - /** - * - * @test - */ - public function shouldFindTenantFromHeader() - { - /* - * From Dispatcher - */ - $_SERVER['HTTP_HOST'] = 'xxx.' . rand(); - - $tenant = new Tenant(); - $tenant->domain = 'xxxxsssaa' . rand(); - $tenant->subdomain = 'wweerr' . rand(); - $tenant->create(); - $this->assertFalse($tenant->isAnonymous()); - - $this->assertEquals(302, Dispatcher::getInstance()->setViews(Module::loadControllers()) - ->dispatch(new Request('/helloword/HelloWord')) - ->getStatusCode()); - - $request = new Request('/helloword/HelloWord'); - $request->setHeader('_PX_tenant', $tenant->id); - $this->assertEquals(200, Dispatcher::getInstance()->setViews(Module::loadControllers()) - ->dispatch($request) - ->getStatusCode()); - } -} - diff --git a/tests/Template/Compiler/CompilerBlockTransTest.php b/tests/Template/Compiler/CompilerBlockTransTest.php deleted file mode 100755 index a0c9ab88..00000000 --- a/tests/Template/Compiler/CompilerBlockTransTest.php +++ /dev/null @@ -1,69 +0,0 @@ -. -// */ -// namespace Pluf\Test\Template\Compiler; - -// use PHPUnit\Framework\TestCase; -// use Pluf; -// use Pluf_Template_Compiler; -// use Pluf_Translation_TemplateExtractor; - -// /** -// * -// * @backupGlobals disabled -// * @backupStaticAttributes disabled -// */ -// class CompilerBlockTransTest extends TestCase -// { - -// protected function setUp() -// { -// Pluf::start(__DIR__ . '/../conf/config.php'); -// } - -// public function testCompile() -// { -// return ''; -// $compiler = new Pluf_Template_Compiler('dummy', array(), false); -// $compiler->templateContent = '{blocktrans $count, $toto}We have one {$toto} element.{plural}We have {$counter} {$toto} elements.{/blocktrans}'; -// $this->assertEquals('_vars[\'count\']; ob_start(); ? >We have one %2$s element.We have %1$d %2$s elements._vars[\'toto\'], false))); ? >', $compiler->getCompiledTemplate()); -// } - -// public function testExtract() -// { -// $compiler = new Pluf_Translation_TemplateExtractor('dummy', array(), false); -// $compiler->templateContent = 'not in block {blocktrans $count, $toto}We have one {$toto} {$titi|nl2br} element.{plural}We have {$counter} {$toto} elements.{/blocktrans} not in block {trans \'toto\'} not in block {blocktrans}simple' . "\n" . ' block{/blocktrans} sad {trans "youpla"}'; -// // print $compiler->compile(); -// } - -// public function testCompileComplex() -// { -// $compiler = new Pluf_Template_Compiler('dummy', array(), false); -// $compiler->templateContent = ' -//

      {trans "Pluf internationalization"}

      -// {assign $n_methods = $methods.count()} -//

      {blocktrans $n_methods}To translate your code, use the following method:{plural}To translate your code, use one of the {$n_methods} methods:{/blocktrans}

      -//
        -// {foreach $methods as $method} -//
      • {blocktrans}Name: {$method.name}, Description: {$method.description}.{/blocktrans}
      • -// {/foreach} -//
      -// '; -// // echo $compiler->getCompiledTemplate(); -// } -// } \ No newline at end of file diff --git a/tests/Template/Compiler/MethodCompile2Test.php b/tests/Template/Compiler/MethodCompile2Test.php deleted file mode 100755 index 37d2dcab..00000000 --- a/tests/Template/Compiler/MethodCompile2Test.php +++ /dev/null @@ -1,59 +0,0 @@ -. - */ -namespace Pluf\Test\Template\Compiler; - -use Pluf\Template\Compiler; -use Pluf\Test\PlufTestCase; -use Pluf; - -/** - * - * @backupGlobals disabled - * @backupStaticAttributes disabled - */ -class MethodCompile2Test extends PlufTestCase -{ - - protected function setUp() - { - Pluf::start(__DIR__ . '/../../conf/config.php'); - } - - public function testCompile() - { - $compiler = new Compiler('tpl-extends.html', array( - dirname(__FILE__) - )); - $compiled = file_get_contents(dirname(__FILE__) . '/tpl-extends.compiled.html'); - $this->assertEquals($compiled, $compiler->getCompiledTemplate() . "\n"); - } - - public function testCompileMultiFolders() - { - $folders = array( - dirname(__FILE__) . '/tpl1', - dirname(__FILE__) . '/tpl2' - ); - $compiler = new Compiler('tpl-extends.html', $folders); - $compiled = file_get_contents(dirname(__FILE__) . '/tpl-extends.compiled.html'); - $this->assertEquals($compiled, $compiler->getCompiledTemplate() . "\n"); - } -} - -?> \ No newline at end of file diff --git a/tests/Template/Compiler/MethodCompileTest.php b/tests/Template/Compiler/MethodCompileTest.php deleted file mode 100755 index 4e65dcdd..00000000 --- a/tests/Template/Compiler/MethodCompileTest.php +++ /dev/null @@ -1,115 +0,0 @@ -. - */ -namespace Pluf\Test\Template\Compiler; - -use PHPUnit\Framework\TestCase; -use Pluf\Template\Compiler; -use Pluf; - -class MethodCompileTest extends TestCase -{ - - protected function setUp() - { - Pluf::start(__DIR__ . '/../../conf/config.php'); - } - - public function testCompile() - { - $compiler = new Compiler('tpl-test1.html', array( - dirname(__FILE__) - )); - $this->assertEquals('_vars->toto); ?>' . "\n\n", $compiler->getCompiledTemplate()); - } - - public function testCompile2() - { - $compiler = new Compiler('tpl-test2.html', array( - dirname(__FILE__) - )); - $this->assertEquals('_vars->toto); ?>' . "\n\n", $compiler->getCompiledTemplate()); - } - - public function testCompile3() - { - $compiler = new Compiler('tpl-test3.html', array( - dirname(__FILE__) - )); - $res = file_get_contents(dirname(__FILE__) . '/tpl-test3.compiled.html'); - $this->assertEquals($res, $compiler->getCompiledTemplate()); - } - - public function testCompile5() - { - $compiler = new Compiler('tpl-test5.html', array( - dirname(__FILE__) - )); - $this->assertEquals("_vars->string3 = \$t->_vars->string2.\$t->_vars->string1; ?>", $compiler->getCompiledTemplate()); - } - - public function testCompileString() - { - $compiler = new Compiler('tpl-teststring.html', array( - dirname(__FILE__) - )); - $this->assertEquals('' . "\n\n", $compiler->getCompiledTemplate()); - } - - public function testCompileStringModifier() - { - $compiler = new Compiler('tpl-teststring-modifier.html', array( - dirname(__FILE__) - )); - $this->assertEquals('' . "\n\n", $compiler->getCompiledTemplate()); - } - - public function testCompileRemoveComments() - { - $compiler = new Compiler('dummy', array(), false); - $compiler->templateContent = 'you {* this is a comment *} boum'; - $this->assertEquals('you boum', $compiler->getCompiledTemplate()); - } - - public function testCompileRemovePhpCode() - { - $compiler = new Compiler('dummy', array(), false); - $compiler->templateContent = 'you boum'; - $this->assertEquals('you boum', $compiler->getCompiledTemplate()); - } - - public function testCompileSimpleAssignInLoop() - { - $compiler = new Compiler('dummy', array(), false); - $compiler->templateContent = '{assign $counter = 1} -{foreach $lines as $line} -{$counter} -{assign $counter = $counter+1} -{/foreach}'; - $this->assertEquals('_vars->counter = 1; ?> - -_vars->lines as $t->_vars->line): ?> - -_vars->counter); ?> - -_vars->counter = $t->_vars->counter+1; ?> - -', $compiler->getCompiledTemplate()); - } -} - diff --git a/tests/Template/Compiler/tpl-base.html b/tests/Template/Compiler/tpl-base.html deleted file mode 100755 index 8404fe57..00000000 --- a/tests/Template/Compiler/tpl-base.html +++ /dev/null @@ -1,5 +0,0 @@ -This is the base template - -{block blockname}Hello blockname{/block} - -{block block2}Bye bye block2{/block} \ No newline at end of file diff --git a/tests/Template/Compiler/tpl-extends.compiled.html b/tests/Template/Compiler/tpl-extends.compiled.html deleted file mode 100755 index f31c07e5..00000000 --- a/tests/Template/Compiler/tpl-extends.compiled.html +++ /dev/null @@ -1,8 +0,0 @@ -This is the base template - -Hello blockname - -toto _vars->titi): ?> toto 2 - - -Template base "Bye bye block2" here:Bye bye block2 diff --git a/tests/Template/Compiler/tpl-extends.html b/tests/Template/Compiler/tpl-extends.html deleted file mode 100755 index 59228f3d..00000000 --- a/tests/Template/Compiler/tpl-extends.html +++ /dev/null @@ -1,6 +0,0 @@ -{extends 'tpl-base.html'} -{block blockname3}toto{/block} - -{block block2}toto {if $titi} toto 2 {/if} - -Template base "Bye bye block2" here:{superblock}{/block} \ No newline at end of file diff --git a/tests/Template/Compiler/tpl-test1.html b/tests/Template/Compiler/tpl-test1.html deleted file mode 100755 index 1bdb4c5d..00000000 --- a/tests/Template/Compiler/tpl-test1.html +++ /dev/null @@ -1 +0,0 @@ -{$toto} diff --git a/tests/Template/Compiler/tpl-test2.html b/tests/Template/Compiler/tpl-test2.html deleted file mode 100755 index 0bc5e7ea..00000000 --- a/tests/Template/Compiler/tpl-test2.html +++ /dev/null @@ -1 +0,0 @@ -{include 'tpl-test1.html'} \ No newline at end of file diff --git a/tests/Template/Compiler/tpl-test3.compiled.html b/tests/Template/Compiler/tpl-test3.compiled.html deleted file mode 100755 index 842c63fe..00000000 --- a/tests/Template/Compiler/tpl-test3.compiled.html +++ /dev/null @@ -1,5 +0,0 @@ -start('/action/'.$t->_vars->toto.'/'); ?> - -start('sdfsd', 'sdfsdf'); $t->set('mytag', 'hello world!');?> - -end('param 1'); $t->set('mytag', '');?> \ No newline at end of file diff --git a/tests/Template/Compiler/tpl-test3.html b/tests/Template/Compiler/tpl-test3.html deleted file mode 100755 index 1db046c8..00000000 --- a/tests/Template/Compiler/tpl-test3.html +++ /dev/null @@ -1,3 +0,0 @@ -{url '/action/'~$toto~'/'} -{mytag 'sdfsd', 'sdfsdf'} -{/mytag 'param 1'} \ No newline at end of file diff --git a/tests/Template/Compiler/tpl-test4.html b/tests/Template/Compiler/tpl-test4.html deleted file mode 100755 index eb7fbf5b..00000000 --- a/tests/Template/Compiler/tpl-test4.html +++ /dev/null @@ -1,3 +0,0 @@ -{foreach $items as $item} -{$item} -{/foreach} \ No newline at end of file diff --git a/tests/Template/Compiler/tpl-test5.html b/tests/Template/Compiler/tpl-test5.html deleted file mode 100755 index d677d9f7..00000000 --- a/tests/Template/Compiler/tpl-test5.html +++ /dev/null @@ -1 +0,0 @@ -{assign $string3 = $string2~$string1} \ No newline at end of file diff --git a/tests/Template/Compiler/tpl-teststring-modifier.html b/tests/Template/Compiler/tpl-teststring-modifier.html deleted file mode 100755 index 04d832c0..00000000 --- a/tests/Template/Compiler/tpl-teststring-modifier.html +++ /dev/null @@ -1 +0,0 @@ -{"this is a string"|unsafe} diff --git a/tests/Template/Compiler/tpl-teststring.html b/tests/Template/Compiler/tpl-teststring.html deleted file mode 100755 index b91d843b..00000000 --- a/tests/Template/Compiler/tpl-teststring.html +++ /dev/null @@ -1 +0,0 @@ -{"this is a string"} diff --git a/tests/Template/Compiler/tpl1/tpl-extends.html b/tests/Template/Compiler/tpl1/tpl-extends.html deleted file mode 100755 index d561e939..00000000 --- a/tests/Template/Compiler/tpl1/tpl-extends.html +++ /dev/null @@ -1,6 +0,0 @@ -{extends 'tpl-base.html'} -{block blockname3}toto{/block} - -{block block2}toto {if $titi} toto 2 {/if} - -Template base "Bye bye block2" here:{superblock}{/block} diff --git a/tests/Template/Compiler/tpl2/tpl-base.html b/tests/Template/Compiler/tpl2/tpl-base.html deleted file mode 100755 index 8404fe57..00000000 --- a/tests/Template/Compiler/tpl2/tpl-base.html +++ /dev/null @@ -1,5 +0,0 @@ -This is the base template - -{block blockname}Hello blockname{/block} - -{block block2}Bye bye block2{/block} \ No newline at end of file diff --git a/tests/Template/PlufTemplateTest.php b/tests/Template/PlufTemplateTest.php deleted file mode 100755 index f3657153..00000000 --- a/tests/Template/PlufTemplateTest.php +++ /dev/null @@ -1,55 +0,0 @@ -. - */ -namespace Pluf\Test\Template; - -use Pluf\Template; -use Pluf\Test\PlufTestCase; -use Pluf; - -/** - * - * @backupGlobals disabled - * @backupStaticAttributes disabled - */ -class PlufTemplateTest extends PlufTestCase -{ - - protected function setUp() - { - Pluf::start(__DIR__ . '/../conf/config.php'); - } - - public function testRender() - { - $folders = array( - dirname(__FILE__) . '/Compiler/tpl1', - dirname(__FILE__) . '/Compiler/tpl2' - ); - $tmpl = new Template('tpl-extends.html', $folders); - $this->assertEquals("This is the base template - -Hello blockname - -toto - -Template base \"Bye bye block2\" here:Bye bye block2", $tmpl->render()); - } -} - -?> \ No newline at end of file diff --git a/tests/Template/Tenant/PlufTemplateTest.php b/tests/Template/Tenant/PlufTemplateTest.php deleted file mode 100755 index acf284c1..00000000 --- a/tests/Template/Tenant/PlufTemplateTest.php +++ /dev/null @@ -1,99 +0,0 @@ -. - */ -namespace Pluf\Test\Template\Tenant; - -use PHPUnit\Framework\TestCase; -use Pluf\Template; -use Pluf\Pluf\Tenant; -use Pluf; - -/** - * - * @backupGlobals disabled - * @backupStaticAttributes disabled - */ -class PlufTenantTemplateTest extends TestCase -{ - - /** - * - * @before - * @return void - */ - protected function setUpTest() - { - Pluf::start(array( - 'test' => false, - 'timezone' => 'Europe/Berlin', - 'debug' => true, - 'installed_apps' => array( - 'Pluf' - ), - 'tmp_folder' => '/tmp', - 'templates_folder' => array( - dirname(__FILE__) . '/../templates' - ), - - 'template_tags' => array( - 'tenant' => '\\Pluf\\Template\\Tag\\TenantTag' - ), - 'pluf_use_rowpermission' => true, - 'mimetype' => 'text/html', - 'db_login' => 'testpluf', - 'db_password' => 'testpluf', - 'db_server' => 'localhost', - 'db_database' => '/tmp/tmp.sqlite.db', - 'app_base' => '/testapp', - 'url_format' => 'simple', - 'db_table_prefix' => 'pluf_unit_tests_' . rand() . '_', - 'db_version' => '5.0', - 'db_engine' => 'SQLite', - 'bank_debug' => true - )); - } - - public function testId() - { - $folders = array( - dirname(__FILE__) - ); - $tmpl = new Template('tpl-id.html', $folders); - $this->assertEquals("1", $tmpl->render()); - } - - public function testTitle() - { - $tenant = Tenant::current(); - $folders = array( - dirname(__FILE__) - ); - $tmpl = new Template('tpl-title.html', $folders); - $this->assertEquals($tenant->title, $tmpl->render()); - } - - public function testDomain() - { - $tenant = Tenant::current(); - $folders = array( - dirname(__FILE__) - ); - $tmpl = new Template('tpl-domain.html', $folders); - $this->assertEquals($tenant->domain, $tmpl->render()); - } -} diff --git a/tests/Template/Tenant/tpl-domain.html b/tests/Template/Tenant/tpl-domain.html deleted file mode 100644 index d08b1fd9..00000000 --- a/tests/Template/Tenant/tpl-domain.html +++ /dev/null @@ -1 +0,0 @@ -{tenant 'domain'} \ No newline at end of file diff --git a/tests/Template/Tenant/tpl-id.html b/tests/Template/Tenant/tpl-id.html deleted file mode 100755 index a21390f6..00000000 --- a/tests/Template/Tenant/tpl-id.html +++ /dev/null @@ -1 +0,0 @@ -{tenant 'id'} \ No newline at end of file diff --git a/tests/Template/Tenant/tpl-title.html b/tests/Template/Tenant/tpl-title.html deleted file mode 100644 index b29a0482..00000000 --- a/tests/Template/Tenant/tpl-title.html +++ /dev/null @@ -1 +0,0 @@ -{tenant 'title'} \ No newline at end of file diff --git a/tests/Template/tpl-test1.html b/tests/Template/tpl-test1.html deleted file mode 100755 index 1bdb4c5d..00000000 --- a/tests/Template/tpl-test1.html +++ /dev/null @@ -1 +0,0 @@ -{$toto} diff --git a/tests/Text/TextHTMLFilterTest.php b/tests/Text/TextHTMLFilterTest.php deleted file mode 100755 index fa71dc90..00000000 --- a/tests/Text/TextHTMLFilterTest.php +++ /dev/null @@ -1,311 +0,0 @@ -. - */ -namespace Pluf\Test\Text; - -use PHPUnit\Framework\TestCase; -use Pluf\Text\HTML\Filter; -use Pluf; - -/** - * Based on the tests provided with the Pluf_Text_HTML_Filter original - * library. - * - * @backupGlobals disabled - * @backupStaticAttributes disabled - */ -class TextHTMLFilterTest extends TestCase -{ - - public $filter; - - /** - * - * @befor - */ - public function setUpTest() - { - Pluf::start(__DIR__ . '/../conf/config.php'); - } - - /** - * - * @after - */ - public function tearDownTest() - {} - - /** - * - * @test - */ - public function testRunBatchOfTests() - { - $this->filter = new Filter(); - // basics - $this->filter_harness("", ""); - $this->filter_harness("hello", "hello"); - - // balancing tags - $this->filter_harness("hello", "hello"); - $this->filter_harness("hello", "hello"); - $this->filter_harness("helloworld", "helloworld"); - $this->filter_harness("hello", "hello"); - $this->filter_harness("hello", "hello"); - $this->filter_harness("helloworld", "helloworld"); - $this->filter_harness("hello", "hello"); - $this->filter_harness("", ""); - - // end slashes - $this->filter_harness('', ''); - $this->filter_harness('', ''); - $this->filter_harness('', ''); - - // balancing angle brakets - - $this->filter->always_make_tags = 1; - $this->filter_harness(''); - $this->filter_harness('b>', ''); - $this->filter_harness('b>hello', 'hello'); - $this->filter_harness(''); - $this->filter_harness('>', ''); - $this->filter_harness('hellofilter_harness('b>foo', 'foo'); - $this->filter_harness('>filter_harness('b><', ''); - $this->filter_harness('>', ''); - $this->filter_harness('foo bar>', ''); - $this->filter_harness('foo>bar>baz', 'baz'); - $this->filter_harness('foo>bar', 'bar'); - $this->filter_harness('foo>bar>', ''); - $this->filter_harness('>foo>bar', 'bar'); - $this->filter_harness('>foo>bar>', ''); - - $this->filter->always_make_tags = 0; - $this->filter_harness('filter_harness('b>', 'b>'); - $this->filter_harness('b>hello', 'b>hello'); - $this->filter_harness('filter_harness('>', '>'); - $this->filter_harness('hellofilter_harness('b>foo', 'b>foo'); - $this->filter_harness('>filter_harness('b><', 'b><'); - $this->filter_harness('>', '>'); - $this->filter_harness('foo bar>', 'foo bar>'); - $this->filter_harness('foo>bar>baz', 'foo>bar>baz'); - $this->filter_harness('foo>bar', 'foo>bar'); - $this->filter_harness('foo>bar>', 'foo>bar>'); - $this->filter_harness('>foo>bar', '>foo>bar'); - $this->filter_harness('>foo>bar>', '>foo>bar>'); - - // attributes - $this->filter_harness('', ''); - $this->filter_harness('', ''); - $this->filter_harness('', ''); - - // non-allowed tags - $this->filter_harness('', ''); - $this->filter_harness('