├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.xml ├── build ├── phpcs-ruleset-win.xml ├── phpcs-ruleset.xml ├── phpdox.xml └── phpmd.xml ├── codeception.yml ├── composer.json ├── composer.lock ├── docs ├── application.md ├── assets.md ├── custard │ └── index.md ├── date-time.md ├── dependency-injection.md ├── deployment.md ├── encryption.md ├── events.md ├── exceptions.md ├── files-and-directories.md ├── filters-and-layout.md ├── http-clients.md ├── logging.md ├── login.md ├── modules.md ├── processing-overview.md ├── recordstreams.md ├── request.md ├── response-generating.md ├── response.md ├── sessions.md ├── settings.md ├── simple-xml-transcoder.md ├── static-resources.md ├── string-tools.md ├── toc.txt └── url-handlers.md ├── phpunit.xml ├── platform ├── boot-application.php ├── boot-rhubarb.php ├── execute-cli.php ├── execute-http.php ├── execute-test.php └── standard-development-apache-rewrites.conf ├── resources ├── resource-manager.js └── validation.js ├── src ├── Application.php ├── Assets │ ├── Asset.php │ ├── AssetCatalogueProvider.php │ ├── AssetCatalogueSettings.php │ ├── AssetUrlHandler.php │ ├── LocalStorageAssetCatalogueProvider.php │ ├── LocalStorageAssetCatalogueProviderSettings.php │ ├── LoginValidatedAssetUrlHandler.php │ └── TempLocalStorageAssetCatalogueProvider.php ├── DataStreams │ ├── CsvStream.php │ ├── RecordStream.php │ └── XmlStream.php ├── DateTime │ ├── RhubarbDate.php │ ├── RhubarbDateInterval.php │ ├── RhubarbDateTime.php │ └── RhubarbTime.php ├── DependencyInjection │ ├── Container.php │ ├── ProviderInterface.php │ ├── ProviderTrait.php │ ├── SingletonInterface.php │ ├── SingletonProviderTrait.php │ └── SingletonTrait.php ├── Deployment │ ├── Deployable.php │ ├── DeploymentPackage.php │ ├── RelocationResourceDeploymentProvider.php │ ├── ResourceDeploymentPackage.php │ └── ResourceDeploymentProvider.php ├── Encryption │ ├── Aes256ComputedKeyEncryptionProvider.php │ ├── Aes256EncryptionProvider.php │ ├── EncryptionProvider.php │ ├── HashProvider.php │ ├── PlainTextHashProvider.php │ └── Sha512HashProvider.php ├── Events │ ├── Event.php │ └── EventEmitter.php ├── Exceptions │ ├── AssetException.php │ ├── AssetExposureException.php │ ├── AssetNotFoundException.php │ ├── AttemptToModifyReadOnlyPropertyException.php │ ├── ClassMappingException.php │ ├── CollectionUrlException.php │ ├── DeploymentException.php │ ├── EmailException.php │ ├── EndOfStreamException.php │ ├── FileNotFoundException.php │ ├── ForceResponseException.php │ ├── Handlers │ │ ├── DefaultExceptionHandler.php │ │ ├── ExceptionHandler.php │ │ └── ExceptionSettings.php │ ├── HttpResponseException.php │ ├── ImplementationException.php │ ├── NonRhubarbException.php │ ├── ResourceNotFound.php │ ├── RhubarbException.php │ ├── SettingMissingException.php │ ├── StaticResource404Exception.php │ ├── StaticResourceNotFoundException.php │ └── StopGeneratingResponseException.php ├── Html │ └── ResourceLoader.php ├── Http │ ├── CurlHttpClient.php │ ├── HttpClient.php │ ├── HttpRequest.php │ └── HttpResponse.php ├── Layout │ ├── Exceptions │ │ └── LayoutNotFoundException.php │ ├── Layout.php │ ├── LayoutModule.php │ └── ResponseFilters │ │ └── LayoutFilter.php ├── Logging │ ├── IndentedMessageLog.php │ ├── Log.php │ ├── MonologLog.php │ └── PhpLog.php ├── LoginProviders │ ├── CredentialsLoginProviderInterface.php │ ├── Exceptions │ │ ├── CredentialsFailedException.php │ │ ├── LoginFailedException.php │ │ └── NotLoggedInException.php │ ├── LoginProvider.php │ └── UrlHandlers │ │ └── ValidateLoginUrlHandler.php ├── Mime │ ├── MimeDocument.php │ ├── MimePart.php │ ├── MimePartBinaryFile.php │ ├── MimePartCollection.php │ ├── MimePartImage.php │ └── MimePartText.php ├── Modelling │ ├── AllPublicModelState.php │ └── ModelState.php ├── Module.php ├── PhpContext.php ├── Request │ ├── BinaryRequest.php │ ├── CliRequest.php │ ├── JsonRequest.php │ ├── MultiPartFormDataRequest.php │ ├── Request.php │ ├── WebRequest.php │ └── XmlRequest.php ├── Response │ ├── BasicAuthorisationRequiredResponse.php │ ├── BinaryResponse.php │ ├── ExpiredResponse.php │ ├── FileResponse.php │ ├── GeneratesResponseInterface.php │ ├── HtmlResponse.php │ ├── JsonResponse.php │ ├── NotAuthorisedResponse.php │ ├── NotFoundResponse.php │ ├── RedirectResponse.php │ ├── Response.php │ ├── TooManyLoginAttemptsResponse.php │ └── XmlResponse.php ├── ResponseFilters │ └── ResponseFilter.php ├── Scripts │ └── ProjectTemplates.php ├── Sendables │ ├── Email │ │ ├── Email.php │ │ ├── EmailProvider.php │ │ ├── EmailRecipient.php │ │ ├── EmailSettings.php │ │ ├── PhpMailEmailProvider.php │ │ ├── SimpleEmail.php │ │ └── TemplateEmail.php │ ├── Sendable.php │ ├── SendableProvider.php │ └── SendableRecipient.php ├── Sessions │ ├── EncryptedSession.php │ ├── Exceptions │ │ └── SessionProviderNotFoundException.php │ ├── Session.php │ └── SessionProviders │ │ ├── PhpSessionProvider.php │ │ └── SessionProvider.php ├── Settings.php ├── Settings │ ├── HtmlPageSettings.php │ └── WebsiteSettings.php ├── String │ ├── StringTools.php │ └── Template.php ├── UrlHandlers │ ├── CallableUrlHandler.php │ ├── ClassMappedUrlHandler.php │ ├── CollectionUrlHandling.php │ ├── GreedyUrlHandler.php │ ├── NamespaceMappedUrlHandler.php │ ├── NumericGreedyUrlHandler.php │ ├── StaticResourceUrlHandler.php │ ├── UrlCapturedDataUrlHandler.php │ └── UrlHandler.php └── Xml │ ├── Node.php │ ├── NodeStrategy.php │ ├── NodeStrategyCollation.php │ ├── NodeStrategyCollationDictionary.php │ ├── NodeStrategyRead.php │ ├── NodeStrategyTraversal.php │ ├── SimpleXmlTranscoder.php │ └── XmlParser.php └── tests ├── .gitignore ├── Fixtures ├── Codeception │ ├── RhubarbConnector.php │ └── RhubarbFramework.php ├── DependencyInjection │ ├── DependencyWithArguments.php │ ├── ExtendedSimpleClass.php │ ├── OneDependency.php │ └── SimpleClass.php ├── Emails │ ├── FancyUnitTestingTemplateEmail.php │ └── UnitTestingTemplateEmail.php ├── Layout │ ├── TestLayout.php │ └── TestLayout2.php ├── LoginProviders │ └── UnitTestingLoginProvider.php ├── Modules │ ├── UnitTestingModule.php │ └── UnitTestingModuleB.php ├── SimpleContent.php ├── TestCases │ ├── AppTestCase.php │ ├── RequestTestCase.php │ └── RhubarbTestCase.php ├── UnitTestingEmailProvider.php ├── UnitTestingRhubarbRequestHttpClient.php ├── UnitTestingSettings.php └── UrlHandlers │ ├── DifferentNamespaceTest │ ├── FindMe.php │ └── readme.txt │ ├── NamespaceMappedHandlerTests │ ├── ObjectA.php │ └── SubFolder │ │ ├── Index.php │ │ └── ObjectB.php │ ├── TestParentHandler.php │ ├── UnitTestComputedUrlHandler.php │ ├── base.css │ ├── subfolder │ └── test3.txt │ ├── test.txt │ └── test2.txt ├── _bootstrap.php ├── _data ├── _multiPartFormDataRequestUnitTestFile.txt └── dump.sql ├── _output └── .gitignore ├── _support ├── AcceptanceTester.php ├── FunctionalTester.php ├── Helper │ ├── Acceptance.php │ ├── Functional.php │ └── Unit.php ├── UnitTester.php └── _generated │ ├── AcceptanceTesterActions.php │ ├── FunctionalTesterActions.php │ └── UnitTesterActions.php ├── acceptance.suite.yml ├── acceptance └── _bootstrap.php ├── functional.suite.yml ├── functional └── _bootstrap.php ├── unit.suite.yml └── unit ├── ApplicationTest.php ├── Assets ├── .gitignore ├── AssetCatalogueProviderTest.php ├── AssetCatalogueProviderTests.php ├── AssetUrlHandlerTest.php ├── LocalStorageAssetCatalogueProviderTest.php └── LoginValidatedAssetUrlHandlerTest.php ├── ContainerTest.php ├── DataStreams ├── CsvStreamTest.php ├── RecordStreamTest.php └── XmlStreamTest.php ├── DateTime ├── RhubarbDateIntervalTest.php ├── RhubarbDateTest.php ├── RhubarbDateTimeTest.php └── RhubarbTimeTest.php ├── Deployment ├── RelocationResourceDeploymentHandlerTest.php └── ResourceDeploymentPackageTest.php ├── Encryption ├── Aes256EncryptionProviderTest.php ├── PlainTextHashProviderTest.php ├── Sha512HashProviderTest.php └── UnitTestingAes256EncryptionProvider.php ├── Events ├── EventEmitterTest.php └── EventTest.php ├── Exceptions └── Handlers │ └── DefaultExceptionHandlerTest.php ├── Html └── ResourceLoaderTest.php ├── Layout └── LayoutModuleTest.php ├── Logging ├── IndentedMessageLogTest.php ├── LogTest.php └── UnitTestLog.php ├── LoginProviders ├── LoginProviderTest.php └── ValidateLoginUrlHandlerTest.php ├── Mime └── MimeDocumentTest.php ├── Modelling └── ModelStateTest.php ├── PhpContextTest.php ├── Request ├── JsonRequestTest.php ├── MultiPartFormDataRequestTest.php ├── RequestTest.php ├── WebRequestSSLOffWindowsTest.php ├── WebRequestSSLOnTest.php ├── WebRequestTest.php └── XmlRequestTest.php ├── Response ├── HtmlResponseTest.php ├── JsonResponseTest.php ├── RedirectResponseTest.php ├── ResponseTest.php └── XmlResponseTest.php ├── Sendables └── Email │ ├── EmailAddressTest.php │ ├── EmailTest.php │ └── TemplateEmailTest.php ├── Sessions ├── EncryptedSessionTest.php ├── SessionProviders │ └── PhpSessionProviderTest.php ├── SessionTest.php ├── UnitTestingSession.php └── UnitTestingSessionProvider.php ├── SettingsTest.php ├── String ├── StringToolsTest.php └── TemplateTest.php ├── UrlHandlers ├── CallableUrlHandlerTest.php ├── ClassMappedHandlerTest.php ├── GreedyUrlHandlerTest.php ├── NamespaceMappedHandlerTest.php ├── NumericGreedyUrlHandlerTest.php ├── StaticResourceTest.php └── UrlHandlerTestUnitTest.php ├── Xml ├── SimpleXmlTranscoderTest.php └── XmlParserTest.php └── _bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.phar 2 | vendor/ 3 | 4 | # Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file 5 | # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file 6 | # composer.lock 7 | /.idea/ 8 | /deployed/ 9 | /logs/ 10 | /bin/ 11 | tests/_output/* 12 | /cache/ 13 | /build/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rhubarb PHP 2 | 3 | Rhubarb is an application development framework for PHP. Its focus is on allowing developers to build enterprise ready applications that are fast, scale well, allow for architectural rethinks late in the project and that maximise the potential for code reuse. 4 | 5 | ## Projects and Modules 6 | 7 | Rhubarb is a modular system. Your application only brings in the modules it needs. So if you're building an API you will use the RestAPI module but not the MVP module. This keeps the burden on autoloaders down and makes your application easier to deploy and maintain. 8 | 9 | The main framework resides in the `rhubarb` project. This includes the platform bootstraps and a core set of classes called 'Crown'. 10 | 11 | Rhubarb uses Composer to import additional packages into the solution including it's own modules. To keep our github organisation tidy other Rhubarb modules reside in projects called `module.[modulename]`. For example `module.modelling` or `module.sendgrid` 12 | 13 | ## Contributing 14 | 15 | Rhubarb is an open source project and as such anyone may make a contribution. Contributions can be made by forking any of the rhubarb projects and making a pull request back to the base fork. 16 | 17 | Rhubarb has a list of senior contributors who guard and protect the values of Rhubarb and make the final decision on the merits of each pull request. 18 | -------------------------------------------------------------------------------- /build/phpcs-ruleset-win.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 27 | A custom coding standard 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | */tests/* 38 | 39 | 40 | 41 | */tests/* 42 | 43 | 44 | 45 | */tests/* 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /build/phpcs-ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 27 | A custom coding standard 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | */tests/* 37 | 38 | 39 | 40 | */tests/* 41 | 42 | 43 | 44 | */tests/* 45 | 46 | 47 | -------------------------------------------------------------------------------- /build/phpmd.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 25 | 26 | My custom rule set that checks my code... 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /codeception.yml: -------------------------------------------------------------------------------- 1 | actor: Tester 2 | paths: 3 | tests: tests 4 | log: tests/_output 5 | data: tests/_data 6 | support: tests/_support 7 | envs: tests/_envs 8 | settings: 9 | bootstrap: _bootstrap.php 10 | colors: true 11 | memory_limit: 1024M 12 | extensions: 13 | enabled: 14 | - Codeception\Extension\RunFailed 15 | modules: 16 | config: 17 | Db: 18 | dsn: '' 19 | user: '' 20 | password: '' 21 | dump: tests/_data/dump.sql 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rhubarbphp/rhubarb", 3 | "description": "A modern enterprise ready PHP framework", 4 | "keywords": [ "php", "framework" ], 5 | "homepage": "http://www.rhubarbphp.com/", 6 | "license": "Apache-2.0", 7 | "autoload": { 8 | "psr-4": { 9 | "Rhubarb\\Crown\\": "src/", 10 | "Rhubarb\\Crown\\Tests\\": "tests/" 11 | } 12 | }, 13 | "require": { 14 | "php": ">=8.0.0", 15 | "firebase/php-jwt": "^4.0 || ^5.0", 16 | "psr/container": "^2.0.0" 17 | }, 18 | "require-dev": { 19 | "rhubarbphp/custard": "^1.0.9", 20 | "rhubarbphp/module-build-status-updater": "^1.0.5", 21 | "codeception/codeception": "^2.0.0" 22 | }, 23 | "config": { 24 | "bin-dir": "bin/" 25 | }, 26 | "scripts": { 27 | "post-create-project-cmd": [ 28 | "Rhubarb\\Crown\\Scripts\\ProjectTemplates::createMinimumProject" 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /docs/custard/index.md: -------------------------------------------------------------------------------- 1 | Custard 2 | ======= 3 | 4 | Custard is an extension of the [Symfony Command](http://symfony.com/doc/current/console.html) object that 5 | provides a familiar interface for running command line scripts on the terminal. 6 | 7 | The Symfony Command object is used by a number of frameworks and composer so 8 | is instantly recognisable in it's usage. 9 | 10 | ## Running Custard 11 | 12 | Custard is a development requirement of the main Rhubarb framework however if you 13 | project needs it for running scripts in a deployed environment you should add it 14 | to your composer.json: 15 | 16 | ``` javascript 17 | "require": { 18 | "rhubarbphp/custard": "^1.0.9" 19 | } 20 | ``` 21 | 22 | As per composer's default behaviour the command itself will be found in the vendor 23 | folder on the following path: 24 | 25 | ``` bash 26 | vendor/rhubarbphp/custard/bin/custard 27 | ``` 28 | 29 | As it's such a popular tool it's common for projects to tell composer to put binaries 30 | in a folder closer to your top level folder. This is achieved by adding a `bin-dir` 31 | section to your composer.json: 32 | 33 | ``` javascript 34 | "config": { 35 | "bin-dir": "bin/" 36 | } 37 | ``` 38 | 39 | Now you can run custard with: 40 | 41 | ``` bash 42 | bin/custard 43 | ``` 44 | 45 | This is the form used in examples in this documentation. 46 | 47 | ## Listing available commands. 48 | 49 | Running custard with no arguments returns the list of available commands. Commands 50 | are prefixed with a short 'namespace' which usually hints at the module providing 51 | the command. 52 | 53 | ``` bash 54 | bin/custard 55 | ``` 56 | 57 | ## Running a command 58 | 59 | Simply add the name of the command on the end: 60 | 61 | ``` bash 62 | bin/custard stem:document-models 63 | ``` 64 | 65 | Many commands require interaction on the terminal to answer questions and some provide 66 | switches and arguments you can supply to skip the interactions. Consult the documentation 67 | for each individual command for those details. 68 | -------------------------------------------------------------------------------- /docs/http-clients.md: -------------------------------------------------------------------------------- 1 | HTTP Clients 2 | ============ 3 | 4 | To make an HTTP client you should try to avoid using PHP libraries such as Curl directly. There are many 5 | open source HTTP client libraries that make the job of integrating with HTTP services much easier. Rhubarb 6 | includes a similar library simply called `HttpClient`. 7 | 8 | HttpClient is an abstract base class that implements the provider pattern. It has one method 9 | `getResponse(HttpRequest $request)` that is supplied by concrete types. 10 | 11 | This approach allows the HTTP client to be swapped with one using a different library or for unit testing 12 | a mock client that can extend the reach of testing with Rhubarb REST APIs. 13 | 14 | The default HTTP client is the CurlHttpClient which as the name suggests uses the Curl library to make its 15 | requests. 16 | 17 | As a provider you can call the `setProviderClassName()` function to set the required provider for your application: 18 | 19 | ``` php 20 | HttpClient::setProviderClassName(GuzzleHttpClient::class); 21 | ``` 22 | 23 | To make a request create a `HttpRequest` object, get the client and call `getResponse`: 24 | 25 | ``` php 26 | $myObject = [1, 2, 3]; 27 | $request = new HttpRequest('http://api.myservice.com/', 'put', json_encode($myObject)); 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/response-generating.md: -------------------------------------------------------------------------------- 1 | Generating a Response 2 | ===================== 3 | 4 | To generate a response a class should implement the `GeneratesResponseInterface` interface and define the 5 | 'generateResponse()' method. 6 | 7 | `UrlHandler` objects implement this interface however they usually instantiate a specialist class that generates 8 | the real response. 9 | 10 | The class should return a `Response` object, normally a `HtmlResponse` for web requests or a `JsonResponse` for a 11 | REST API. 12 | 13 | A simple response generator might look like this: 14 | 15 | ``` php 16 | class GreetingResponder implements GeneratesResponseInterface 17 | { 18 | public function generateResponse(Request $request) 19 | { 20 | $response = new HtmlResponse(); 21 | $response->setContent("

Welcome friend!

"); 22 | 23 | return $response; 24 | } 25 | } 26 | ``` 27 | 28 | To connect this with a URL you can use the `ClassMappedUrlHandler`. For example here we configure it to 29 | serve the homepage of our site: 30 | 31 | ``` php 32 | // In registerUrlHandlers of your module class: 33 | $this->addUrlHandlers( 34 | [ 35 | "/" => new ClassMappedUrlHandler(GreetingResponser::class) 36 | ]); 37 | ``` 38 | 39 | Creating HTML directly in a response generating object is something usually reserved for very simple, non interactive 40 | use cases. For normal interactive screen design it's better to use a design pattern like MVP which you can find 41 | in the [leaf](/manual/module.leaf/) module. 42 | -------------------------------------------------------------------------------- /docs/sessions.md: -------------------------------------------------------------------------------- 1 | Sessions 2 | === 3 | 4 | In the Rhubarb user sessions are managed through Session objects. Session objects handle the content of a session 5 | while session providers handle the storage and retrieval of sessions. 6 | 7 | Much like settings sessions are scoped into individually named classes. A single application might have multiple 8 | session objects in use at one time handling different session data. It's even possible for these different 9 | session objects to be using different session providers and thereby storing the session data in different ways. 10 | 11 | ~~~ php 12 | class LoginSession extends Session 13 | { 14 | public bool $loggedIn; 15 | 16 | public string $username; 17 | } 18 | 19 | $loginSession = LoginSession::singleton(); 20 | $loginSession->loggedIn = true; 21 | ~~~ 22 | 23 | The `Session` class extends the `Settings` class and as you've seen is used in a very similar way. 24 | 25 | To store the session for recovery on the next page make a call to `storeSession()` 26 | 27 | ~~~ php 28 | $loginSession->storeSession(); 29 | ~~~ 30 | 31 | ## Changing the default session provider 32 | 33 | The default session provider is `PhpSessionProvider` which will store and recover session data using the standard 34 | PHP session functions. To change the default provider call the static method `SessionProvider::setProviderClassName()` 35 | from your application configuration passing the name of the class to be used. Like all provider setup calls we pass 36 | the name of the class and not an instance of it so that scripts which don't require sessions won't waste 37 | resources with objects they don't need. 38 | 39 | ## Changing the session provider for an individual session class 40 | 41 | You can have different sessions using different session providers. Simply override the `getNewSessionProvider()` 42 | method of your individual session provider. 43 | 44 | ~~~ php 45 | class LoginSession extends Session 46 | { 47 | protected function getNewSessionProvider() 48 | { 49 | return new ModelSessionProvider(); 50 | } 51 | } 52 | ~~~ 53 | -------------------------------------------------------------------------------- /docs/simple-xml-transcoder.md: -------------------------------------------------------------------------------- 1 | Simple XML Transcoder 2 | ============ 3 | 4 | The simple xml transcoder is designed to be an XML equivalent of php's built in `json_encode` / `json_decode` functions. 5 | 6 | ## Usage 7 | 8 | This helper class provides two methods. Each method should behave exactly like it's json equivalent, 9 | but with XML input/output strings. 10 | 11 | ### Encode 12 | 13 | `\Rhubarb\Crown\Xml\SimpleXmlTranscoder::encode($mixed)` 14 | 15 | ### Decode 16 | 17 | `\Rhubarb\Crown\Xml\SimpleXmlTranscoder::decode($mixed, $objectsToAssociativeArrays = false)` 18 | 19 | ## XML Vocabulary 20 | 21 | ### XMLNS 22 | 23 | Any node names or attributes which the decoder relies on will be use the `rbrb` namespace. 24 | 25 | ### Type Attribute 26 | 27 | Denotes how the contents of a node should be decoded. 28 | Types can be set either using the `type` attribute on a named node, 29 | or as the actual node name if the node has no other meaningful name (eg the root node). 30 | 31 | Supported types: 32 | * `obj` denotes an object. Expect child nodes to be named as the object's property names. 33 | Node Values are the property values. 34 | * `arr` denotes an array. Each child node is an entry in the array. Child node names are inconsequential, 35 | but will try to take the single form of a plural array node name when using the `encode` method. 36 | * `num` the data type of this node's value should be treated as a number 37 | * `bool` the data type of this node's value should be treated as a boolean. Possible value: `true`/`false` 38 | -------------------------------------------------------------------------------- /docs/static-resources.md: -------------------------------------------------------------------------------- 1 | Static Resources 2 | ================ 3 | 4 | Static resources like images, Javascript, stylesheets and documents can be served by Rhubarb using the 5 | `StaticResourceUrlHandler`. This handler can be created with the path to either a single file or a directory 6 | of files: 7 | 8 | ``` php 9 | $this->addUrlHandlers( 10 | [ 11 | // A single file 12 | "/static/robots.txt" => new StaticResourceUrlHandler(__DIR__."/../static/robots.txt"), 13 | 14 | // A directory of files 15 | "/static/images" => new StaticResourceUrlHandler(__DIR__."/../static/images") 16 | ]); 17 | ``` 18 | 19 | As static resources require no processing by PHP much higher performance can be achieved by putting 20 | URL rewriting rules to bypass Rhubarb directly into your web server configuration. For example in Apache 21 | the following rules would achieve the same result as above but will be many times faster: 22 | 23 | ``` 24 | RewriteRule ^/static/robots.txt %{DOCUMENT_ROOT}/static/robots.txt [QSA,L,NC] 25 | RewriteRule ^/static/images/$1 %{DOCUMENT_ROOT}/static/images/$1 [QSA,L,NC] 26 | ``` 27 | 28 | To avoid proliferating the configuration file with rewrite rules it's common to find two main rewrite rules 29 | for serving static files: 30 | 31 | ``` 32 | RewriteRule ^/static/(.+) %{DOCUMENT_ROOT}/static/$1 [QSA,L,NC] 33 | RewriteRule ^/deployed/(.+) %{DOCUMENT_ROOT}/deployed/$1 [QSA,L,NC] 34 | ``` 35 | 36 | A common set of rewrite rules for Apache can be found in the 37 | `vendor/rhubarbphp/rhubarb/platform/standard-development-rewrites.conf` directory. 38 | 39 | Most project level static resources are then served under the `static` directory. The `deployed` directory is 40 | a requirement of the [RelocationResourceDeploymentProvider](deployment#content) class. 41 | 42 | > Remember! A production deployed project should not be serving static resources through a UrlHandler. -------------------------------------------------------------------------------- /docs/toc.txt: -------------------------------------------------------------------------------- 1 | Basic Concepts: 2 | . Files and Directory Layout:files-and-directories 3 | . Request Processing Overview:processing-overview 4 | . The Application Object:application 5 | . Modules:modules 6 | . Generating a Response:response-generating 7 | . URL Handlers:url-handlers 8 | . Requests:request 9 | . Responses:response 10 | . Filters and Layout:filters-and-layout 11 | . Dependency Injection:dependency-injection 12 | . Static Resources:static-resources 13 | Common Patterns: 14 | . Settings:settings 15 | . Sessions:sessions 16 | . Handling Logins:login 17 | . Sendables:sendables 18 | . Encryption:encryption 19 | . Deploying Resources:deployment 20 | . Asset Storage:assets 21 | . Logging:logging 22 | . Handling Exceptions:exceptions 23 | . Dates and Times:date-time 24 | . Events:events 25 | . Record Streams:recordstreams 26 | . Modules and Scaffolds:modules-and-scaffolds 27 | . Http Clients:http-clients 28 | Toolkit Classes: 29 | . StringTools:string-tools 30 | . Mime:mime 31 | . Xml:xml 32 | Custard: 33 | . Introduction & Setup:custard/ 34 | . Creating a custard command:custard/make-your-own -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 24 | 25 | tests 26 | 27 | 28 | -------------------------------------------------------------------------------- /platform/boot-application.php: -------------------------------------------------------------------------------- 1 | initialiseModules(); 30 | } else { 31 | // We need an application object for dependency injection and the developer hasn't given us one 32 | // Create an empty application as a back stop. 33 | $application = new \Rhubarb\Crown\Application(); 34 | } 35 | -------------------------------------------------------------------------------- /platform/boot-rhubarb.php: -------------------------------------------------------------------------------- 1 | loginProviderClassName = $loginProviderClassName; 44 | } 45 | 46 | protected function isPermitted() 47 | { 48 | $providerClass = $this->loginProviderClassName; 49 | 50 | if ($providerClass != ""){ 51 | $provider = $providerClass::singleton(); 52 | } else { 53 | $provider = LoginProvider::getProvider(); 54 | } 55 | 56 | return $provider->isLoggedIn(); 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /src/Assets/TempLocalStorageAssetCatalogueProvider.php: -------------------------------------------------------------------------------- 1 | readNextItem()) { 38 | $this->appendItem($item); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/DateTime/RhubarbTime.php: -------------------------------------------------------------------------------- 1 | setDate(self::$yearMustAlwaysBe, self::$monthMustAlwaysBe, self::$dayMustAlwaysBe); 38 | } 39 | 40 | /** 41 | * We never want to do comparisons on date - therefore we always force the date time to have the same date 42 | * @param int $year 43 | * @param int $month 44 | * @param int $day 45 | * 46 | * @return \DateTime 47 | */ 48 | public function setDate($year, $month, $day) 49 | { 50 | return parent::setDate(self::$yearMustAlwaysBe, self::$monthMustAlwaysBe, self::$dayMustAlwaysBe); 51 | } 52 | 53 | function __toString() 54 | { 55 | return $this->format("H:i:s"); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/DependencyInjection/ProviderInterface.php: -------------------------------------------------------------------------------- 1 | registerClass(static::class, $providerClassName); 25 | } 26 | 27 | /** 28 | * @return static 29 | */ 30 | public static function getProvider() 31 | { 32 | return Container::instance(static::class); 33 | } 34 | } -------------------------------------------------------------------------------- /src/DependencyInjection/SingletonInterface.php: -------------------------------------------------------------------------------- 1 | registerClass(static::class, $providerClassName, true); 27 | } 28 | } -------------------------------------------------------------------------------- /src/DependencyInjection/SingletonTrait.php: -------------------------------------------------------------------------------- 1 | getSingleton(static::class, function() use ($arguments) { 29 | return new static(...$arguments); 30 | }); 31 | } 32 | } -------------------------------------------------------------------------------- /src/Deployment/Deployable.php: -------------------------------------------------------------------------------- 1 | onDeploy(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Deployment/ResourceDeploymentPackage.php: -------------------------------------------------------------------------------- 1 | resourcesToDeploy as $path) { 40 | $urls[] = $deploymentHandler->getDeployedResourceUrl($path); 41 | } 42 | 43 | return $urls; 44 | } 45 | 46 | protected function onDeploy() 47 | { 48 | $deploymentHandler = ResourceDeploymentProvider::getProvider(); 49 | 50 | $urls = []; 51 | 52 | foreach ($this->resourcesToDeploy as $path) { 53 | $urls[] = $deploymentHandler->deployResource($path); 54 | } 55 | 56 | return $urls; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Deployment/ResourceDeploymentProvider.php: -------------------------------------------------------------------------------- 1 | getEncryptionKey($keySalt); 36 | 37 | return openssl_encrypt($data, "aes-256-cbc", $key, false, "3132333435363738"); 38 | } 39 | 40 | public function decrypt($data, $keySalt = "") 41 | { 42 | $key = $this->getEncryptionKey($keySalt); 43 | 44 | return openssl_decrypt($data, "aes-256-cbc", $key, false, "3132333435363738"); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Encryption/EncryptionProvider.php: -------------------------------------------------------------------------------- 1 | token = $token; 40 | } 41 | 42 | /** 43 | * Gets the token associated with this exception. 44 | * 45 | * @return string 46 | */ 47 | public function getToken() 48 | { 49 | return $this->token; 50 | } 51 | } -------------------------------------------------------------------------------- /src/Exceptions/AssetExposureException.php: -------------------------------------------------------------------------------- 1 | response = $response; 37 | } 38 | 39 | /** 40 | * Get's the response object. 41 | * 42 | * @return \Rhubarb\Crown\Response\Response 43 | */ 44 | public function getResponse() 45 | { 46 | return $this->response; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Exceptions/Handlers/ExceptionSettings.php: -------------------------------------------------------------------------------- 1 | response = $response; 32 | $this->request = $request; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Exceptions/ImplementationException.php: -------------------------------------------------------------------------------- 1 | getMessage()); 32 | 33 | $this->line = $nonRhubarbException->getLine(); 34 | $this->file = $nonRhubarbException->getFile(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Exceptions/ResourceNotFound.php: -------------------------------------------------------------------------------- 1 | publicMessage; 39 | } 40 | 41 | /** 42 | * Returns the private message attached to this exception. 43 | * 44 | * @return string 45 | */ 46 | public function getPrivateMessage() 47 | { 48 | return $this->getMessage(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Exceptions/SettingMissingException.php: -------------------------------------------------------------------------------- 1 | printLayout($response->getContent()); 41 | 42 | $fullHtml = ob_get_clean(); 43 | 44 | $response->setContent($fullHtml); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Layout/ResponseFilters/LayoutFilter.php: -------------------------------------------------------------------------------- 1 | processResponse($response); 53 | 54 | return $response; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Logging/IndentedMessageLog.php: -------------------------------------------------------------------------------- 1 | writeFormattedEntry($level, $message, $category, $additionalData); 36 | } 37 | 38 | /** 39 | * The logger should implement this method to perform the actual log committal. 40 | * 41 | * @param int $level The log level 42 | * @param string $message The text message to log 43 | * @param string $category The category of log message 44 | * @param array $additionalData Any number of additional key value pairs which can be understood by specific 45 | * logs (e.g. an API log might understand what AuthenticationToken means) 46 | * @return mixed 47 | */ 48 | abstract protected function writeFormattedEntry($level, $message, $category, $additionalData); 49 | } 50 | -------------------------------------------------------------------------------- /src/LoginProviders/CredentialsLoginProviderInterface.php: -------------------------------------------------------------------------------- 1 | publicMessage = "Sorry, we failed to authenticate your credentials at this time."; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/LoginProviders/Exceptions/NotLoggedInException.php: -------------------------------------------------------------------------------- 1 | publicMessage = "Sorry, you must be logged in to complete this action."; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Mime/MimePartBinaryFile.php: -------------------------------------------------------------------------------- 1 | headers["Content-Type"] = $mimeType; 28 | $this->headers["Content-Transfer-Encoding"] = "base64"; 29 | } 30 | 31 | /** 32 | * Creates an instance to represent a file stored locally. 33 | * 34 | * @param $path 35 | * @param string $name 36 | * @return MimePartBinaryFile 37 | */ 38 | public static function fromLocalPath($path, $name = "") 39 | { 40 | if ($name == "") { 41 | $name = basename($path); 42 | } 43 | 44 | $part = new MimePartBinaryFile(); 45 | $part->setTransformedBody(file_get_contents($path)); 46 | $part->addHeader("Content-Disposition", "attachment; filename=\"" . $name . "\""); 47 | 48 | return $part; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Mime/MimePartCollection.php: -------------------------------------------------------------------------------- 1 | headers["Content-Type"] = $mimeType; 28 | $this->headers["Content-Transfer-Encoding"] = "base64"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Mime/MimePartText.php: -------------------------------------------------------------------------------- 1 | headers["Content-Type"] = $mimeType; 28 | $this->headers["Content-Transfer-Encoding"] = "quoted-printable"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Modelling/AllPublicModelState.php: -------------------------------------------------------------------------------- 1 | modelData); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Request/BinaryRequest.php: -------------------------------------------------------------------------------- 1 | getRequestBody(); 28 | 29 | return $requestBody; 30 | } 31 | } -------------------------------------------------------------------------------- /src/Request/CliRequest.php: -------------------------------------------------------------------------------- 1 | getOriginatingPhpContext(); 38 | $requestBody = trim($context->getRequestBody()); 39 | 40 | return json_decode($requestBody, $this->objectsToAssocArrays); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Request/XmlRequest.php: -------------------------------------------------------------------------------- 1 | getOriginatingPhpContext(); 12 | $requestBody = trim($context->getRequestBody()); 13 | 14 | return SimpleXmlTranscoder::decode($requestBody, $this->objectsToAssocArrays); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Response/BasicAuthorisationRequiredResponse.php: -------------------------------------------------------------------------------- 1 | setHeader("WWW-authenticate", "Basic realm=\"" . $realm . "\""); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Response/BinaryResponse.php: -------------------------------------------------------------------------------- 1 | binaryData = $binaryData; 29 | $this->setHeader('Content-Type', $contentType); 30 | 31 | if ($fileName != "") { 32 | $this->setHeader('Content-Disposition', 'attachment; filename="' . $fileName . '"'); 33 | } 34 | 35 | $this->setHeader('Content-Transfer-Encoding', 'binary'); 36 | $this->setHeader('Expires', '0'); 37 | $this->setHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0'); 38 | $this->setHeader('Pragma', 'public'); 39 | $this->setHeader('Content-Length', strlen($binaryData)); 40 | } 41 | 42 | public function getBinaryData() 43 | { 44 | return $this->binaryData; 45 | } 46 | 47 | protected function printContent() 48 | { 49 | @ob_clean(); 50 | 51 | print $this->binaryData; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Response/ExpiredResponse.php: -------------------------------------------------------------------------------- 1 | setHeader("WWW-authenticate", "Basic realm=\"" . $realm . "\""); 14 | 15 | $this->setResponseCode(Response::HTTP_STATUS_CLIENT_ERROR_FORBIDDEN); 16 | $this->setResponseMessage("403 Forbidden"); 17 | 18 | $this->setContent("Sorry, your account has now expired."); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Response/GeneratesResponseInterface.php: -------------------------------------------------------------------------------- 1 | setHeader('Content-Type', 'text/html'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Response/JsonResponse.php: -------------------------------------------------------------------------------- 1 | setHeader('Content-Type', 'application/json'); 36 | } 37 | 38 | public function formatContent() 39 | { 40 | return json_encode($this->getContent()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Response/NotAuthorisedResponse.php: -------------------------------------------------------------------------------- 1 | setResponseCode(Response::HTTP_STATUS_CLIENT_ERROR_UNAUTHORIZED); 28 | $this->setResponseMessage("401 Unauthorized"); 29 | 30 | $this->setContent("Sorry, you are not authorised to access this resource."); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Response/NotFoundResponse.php: -------------------------------------------------------------------------------- 1 | setResponseCode(Response::HTTP_STATUS_CLIENT_ERROR_NOT_FOUND); 28 | $this->setResponseMessage("Not Found"); 29 | } 30 | } -------------------------------------------------------------------------------- /src/Response/TooManyLoginAttemptsResponse.php: -------------------------------------------------------------------------------- 1 | setHeader("WWW-authenticate", "Basic realm=\"" . $realm . "\""); 14 | 15 | $this->setResponseCode(Response::HTTP_STATUS_CLIENT_ERROR_UNAUTHORIZED); 16 | $this->setResponseMessage("401 Unauthorized"); 17 | 18 | $this->setContent("Sorry, your account has been disabled due too many failed login attempts."); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Response/XmlResponse.php: -------------------------------------------------------------------------------- 1 | setHeader('Content-Type', 'text/xml'); 33 | } 34 | 35 | public function formatContent() 36 | { 37 | if($this->content != '' && !is_string($this->content)) { 38 | return SimpleXmlTranscoder::encode(parent::formatContent()); 39 | } 40 | return parent::formatContent(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/ResponseFilters/ResponseFilter.php: -------------------------------------------------------------------------------- 1 | request(); 44 | $host = $request->server("SERVER_NAME"); 45 | 46 | $this->defaultSender = new EmailRecipient("donotreply@" . $host . ".com"); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Sendables/Email/PhpMailEmailProvider.php: -------------------------------------------------------------------------------- 1 | getRecipientList(); 31 | 32 | Log::debug("Sending message to ".$recipientList,"EMAIL"); 33 | 34 | mail( 35 | $recipientList, 36 | $email->getSubject(), 37 | $email->getBodyRaw(), 38 | $email->getMailHeadersAsString(), 39 | "-f".$email->getSender()->email 40 | ); 41 | 42 | Log::indent(); 43 | Log::debug("Sent message to ".$recipientList,"EMAIL"); 44 | Log::outdent(); 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Sendables/SendableProvider.php: -------------------------------------------------------------------------------- 1 | send($sendable); 45 | } 46 | 47 | /** 48 | * @param Sendable $sendable 49 | * @return SendableProvider 50 | */ 51 | private static function createProviderForSendable(Sendable $sendable) 52 | { 53 | $providerClass = $sendable->getProviderClassName(); 54 | $provider = $providerClass::getProvider(); 55 | 56 | return $provider; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Sendables/SendableRecipient.php: -------------------------------------------------------------------------------- 1 | needsInitialised) { 38 | $this->needsInitialised = false; 39 | $this->initialiseDefaultValues(); 40 | } 41 | } 42 | 43 | /** 44 | * Override this class to set default values for settings. 45 | */ 46 | protected function initialiseDefaultValues() 47 | { 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Settings/HtmlPageSettings.php: -------------------------------------------------------------------------------- 1 | $match) { 44 | if (isset($data[$matches[1][$key]])) { 45 | $template = str_replace($match, $data[$matches[1][$key]], $template); 46 | } else if (!$keepPlaceholders) { 47 | $template = str_replace($match, '', $template); 48 | } 49 | } 50 | } 51 | 52 | return $template; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/UrlHandlers/ClassMappedUrlHandler.php: -------------------------------------------------------------------------------- 1 | className = $className; 34 | } 35 | 36 | public function setClassName($className) 37 | { 38 | $this->className = $className; 39 | } 40 | 41 | protected function createHandlingClass() 42 | { 43 | $class = $this->className; 44 | $object = new $class(); 45 | 46 | return $object; 47 | } 48 | 49 | public function generateResponseForRequest($request = null) 50 | { 51 | $object = $this->createHandlingClass(); 52 | 53 | return $object->generateResponse($request); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/UrlHandlers/NumericGreedyUrlHandler.php: -------------------------------------------------------------------------------- 1 | nodeHandlers[ $nodeName ] = $strategy; 30 | 31 | return $this; 32 | } 33 | 34 | public function parse(\XMLReader $xmlReader, $startingDepth = 0, $parseOne = false) 35 | { 36 | parent::parse($xmlReader, $startingDepth, $parseOne); 37 | 38 | // Keep scanning elements while we have elements to scan and we are still within our scope 39 | // namely that the depth is greater than our own depth. 40 | while ($xmlReader->read() && ( $xmlReader->depth > $startingDepth )) { 41 | if ($xmlReader->nodeType == \XMLReader::ELEMENT) { 42 | foreach ($this->nodeHandlers as $name => $strategy) { 43 | if ($name == "*" || $name == $xmlReader->name) { 44 | $strategy->parse($xmlReader, $xmlReader->depth); 45 | 46 | if ($parseOne) { 47 | return true; 48 | } 49 | } 50 | } 51 | } 52 | } 53 | 54 | return false; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | /cache/ 2 | -------------------------------------------------------------------------------- /tests/Fixtures/Codeception/RhubarbFramework.php: -------------------------------------------------------------------------------- 1 | client = new RhubarbConnector(); 29 | } 30 | 31 | public function seeHeader($header, $headerValue = null) 32 | { 33 | $response = $this->client->getInternalResponse(); 34 | PHPUnit_Framework_Assert::assertContains($header, $response->getHeaders()); 35 | 36 | if ($headerValue != null) { 37 | PHPUnit_Framework_Assert::assertEquals($headerValue, $response->getHeader($header)); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/Fixtures/DependencyInjection/DependencyWithArguments.php: -------------------------------------------------------------------------------- 1 | argument1 = $argument1; 30 | $this->argument2 = $argument2; 31 | } 32 | } -------------------------------------------------------------------------------- /tests/Fixtures/DependencyInjection/ExtendedSimpleClass.php: -------------------------------------------------------------------------------- 1 | injected = $simpleClass; 27 | } 28 | } -------------------------------------------------------------------------------- /tests/Fixtures/DependencyInjection/SimpleClass.php: -------------------------------------------------------------------------------- 1 | {Content}"; 33 | } 34 | 35 | protected function GetTextLayout() 36 | { 37 | return "abc{Content}def"; 38 | } 39 | } -------------------------------------------------------------------------------- /tests/Fixtures/Emails/UnitTestingTemplateEmail.php: -------------------------------------------------------------------------------- 1 | TopTailloggedIn = true; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Fixtures/Modules/UnitTestingModuleB.php: -------------------------------------------------------------------------------- 1 | UnitTesting = true; 35 | 36 | HttpClient::setDefaultHttpClientClassName(UnitTestingRhubarbRequestHttpClient::class); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Fixtures/UnitTestingEmailProvider.php: -------------------------------------------------------------------------------- 1 | SettingWithDefault = "default"; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Fixtures/UrlHandlers/DifferentNamespaceTest/FindMe.php: -------------------------------------------------------------------------------- 1 | setContent("parent"); 39 | 40 | return $response; 41 | } 42 | 43 | /** 44 | * Should be implemented to return a true or false as to whether this handler supports the given request. 45 | * 46 | * Normally this involves testing the request URI. 47 | * 48 | * @param \Rhubarb\Crown\Request\Request $request 49 | * @param string $currentUrlFragment 50 | * @return bool 51 | */ 52 | protected function getMatchingUrlFragment(Request $request, $currentUrlFragment = "") 53 | { 54 | return (stripos($currentUrlFragment, $this->stub) === 0); 55 | } 56 | } -------------------------------------------------------------------------------- /tests/Fixtures/UrlHandlers/UnitTestComputedUrlHandler.php: -------------------------------------------------------------------------------- 1 | Content of a.html. 16 | 17 | ----=_NextPart_01CF23F5.37621C10-- 18 | -------------------------------------------------------------------------------- /tests/_data/dump.sql: -------------------------------------------------------------------------------- 1 | /* Replace this file with actual dump of your database */ -------------------------------------------------------------------------------- /tests/_output/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /tests/_support/AcceptanceTester.php: -------------------------------------------------------------------------------- 1 | 30 | 31 | 32 | Breakfast 33 | 100 34 | 35 | 36 | Dinner 37 | 200 38 | 39 | 40 | Lunch 41 | 300 42 | 43 | 44 | '); 45 | 46 | @unlink("cache/unit-test-csv-output-from-xml.csv"); 47 | 48 | $stream = new XmlStream("meal", "cache/unit-test-xml-stream.xml"); 49 | $csvStream = new CsvStream("cache/unit-test-csv-output-from-xml.csv"); 50 | 51 | $csvStream->appendStream($stream); 52 | 53 | $this->assertFileExists("cache/unit-test-csv-output-from-xml.csv"); 54 | 55 | $content = file_get_contents("cache/unit-test-csv-output-from-xml.csv"); 56 | $content = str_replace("\r\n", "\n", $content); 57 | 58 | $this->assertEquals("name,calories\nBreakfast,100\nDinner,200\nLunch,300", $content); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/unit/DataStreams/XmlStreamTest.php: -------------------------------------------------------------------------------- 1 | 30 | 31 | 32 | Breakfast 33 | 34 | 35 | Dinner 36 | 37 | 38 | Lunch 39 | 40 | 41 | '); 42 | 43 | $stream = new XmlStream("meal", "cache/unit-test-xml-stream.xml"); 44 | $meal = $stream->readNextItem(); 45 | 46 | $this->assertEquals("Breakfast", $meal["name"]); 47 | 48 | $stream->readNextItem(); 49 | $stream->readNextItem(); 50 | $meal = $stream->readNextItem(); 51 | 52 | $this->assertFalse($meal); 53 | 54 | // This will crash if the file handle isn't released 55 | //unlink( "cache/unit-test-xml-stream.xml" ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/unit/Encryption/Aes256EncryptionProviderTest.php: -------------------------------------------------------------------------------- 1 | encrypt("somedata"); 33 | 34 | $this->assertEquals("PrHKcixewGccUfgyQsMWjg==", $result); 35 | } 36 | 37 | public function testDecrypts() 38 | { 39 | $encrypter = new UnitTestingAes256EncryptionProvider(); 40 | $result = $encrypter->encrypt("somedata"); 41 | $original = $encrypter->decrypt($result); 42 | 43 | $this->assertEquals("somedata", $original); 44 | } 45 | } -------------------------------------------------------------------------------- /tests/unit/Encryption/PlainTextHashProviderTest.php: -------------------------------------------------------------------------------- 1 | createHash("abc123", ""); 29 | 30 | $this->assertEquals("abc123", $result); 31 | 32 | $this->assertTrue($plainTextProvider->compareHash("abc123", "abc123")); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/unit/Encryption/Sha512HashProviderTest.php: -------------------------------------------------------------------------------- 1 | createHash("abc123", "saltyfish"); 29 | 30 | $this->assertEquals( 31 | '$6$rounds=10000$saltyfish$xsdN77OODY/XmxLdlkFW9CNxuE4H6NjEGG7K7tGJbzHUyDrVDHROL/FqG.ANet3dcd6WqGOOvaDjLv/WeAtcK0', 32 | $result 33 | ); 34 | } 35 | 36 | public function testHashesAreCompared() 37 | { 38 | $hasher = new Sha512HashProvider(); 39 | 40 | $hash = $hasher->createHash("abc123", "saltyfish"); 41 | 42 | $result = $hasher->compareHash("abc123", $hash); 43 | $this->assertTrue($result); 44 | 45 | $result = $hasher->compareHash("dep456", $hash); 46 | $this->assertFalse($result); 47 | 48 | // Repeat the tests with an automated salt. 49 | $hash = $hasher->createHash("abc123"); 50 | 51 | $result = $hasher->compareHash("abc123", $hash); 52 | $this->assertTrue($result); 53 | 54 | $result = $hasher->compareHash("dep456", $hash); 55 | $this->assertFalse($result); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/unit/Encryption/UnitTestingAes256EncryptionProvider.php: -------------------------------------------------------------------------------- 1 | forceLogin(); 29 | 30 | $this->assertTrue($loginProvider->isLoggedIn()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/unit/Request/JsonRequestTest.php: -------------------------------------------------------------------------------- 1 | context = $this->application->context(); 35 | $this->context->simulateNonCli = true; 36 | 37 | $_SERVER["CONTENT_TYPE"] = "application/json"; 38 | } 39 | 40 | public function testPayload() 41 | { 42 | $testPayload = 43 | [ 44 | "a" => 1, 45 | "b" => 2 46 | ]; 47 | 48 | $this->context->simulatedRequestBody = json_encode($testPayload); 49 | 50 | $request = $this->application->request(); 51 | 52 | $this->assertEquals($testPayload, $request->getPayload()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/unit/Request/MultiPartFormDataRequestTest.php: -------------------------------------------------------------------------------- 1 | serverData["REQUEST_METHOD"] = 'PUT'; 20 | 21 | $request->setUnitTestRequestFile(__DIR__ . '/../../_data/_multiPartFormDataRequestUnitTestFile.txt'); 22 | self::assertTrue(is_array($request->getPayload())); 23 | self::assertCount(3, $request->getPayload()); 24 | 25 | $_SERVER["CONTENT_TYPE"] = $originalContentType; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/unit/Request/RequestTest.php: -------------------------------------------------------------------------------- 1 | testEnvKey] = 42; 36 | 37 | $this->request = new WebRequest(); 38 | } 39 | 40 | protected function tearDown() 41 | { 42 | unset($_ENV[$this->testEnvKey]); 43 | 44 | parent::tearDown(); 45 | } 46 | 47 | public function testGettingOfSuperGlobals() 48 | { 49 | $this->assertEquals($this->testEnvValue, $this->request->env($this->testEnvKey)); 50 | $this->assertEquals("defaultValue", $this->request->env("default", "defaultValue")); 51 | } 52 | } -------------------------------------------------------------------------------- /tests/unit/Request/WebRequestSSLOffWindowsTest.php: -------------------------------------------------------------------------------- 1 | request = new WebRequest(); 49 | } 50 | 51 | protected function tearDown() 52 | { 53 | $this->request = null; 54 | 55 | parent::tearDown(); 56 | } 57 | 58 | public function testNoSSL() 59 | { 60 | $this->assertFalse($this->request->isSSL()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/unit/Request/WebRequestSSLOnTest.php: -------------------------------------------------------------------------------- 1 | request = new WebRequest(); 46 | } 47 | 48 | protected function tearDown() 49 | { 50 | $this->request = null; 51 | 52 | parent::tearDown(); 53 | } 54 | 55 | public function testSSL() 56 | { 57 | $this->assertTrue($this->request->isSSL()); 58 | } 59 | } -------------------------------------------------------------------------------- /tests/unit/Request/XmlRequestTest.php: -------------------------------------------------------------------------------- 1 | context = $this->application->context(); 21 | $this->context->simulateNonCli = true; 22 | 23 | $_SERVER['CONTENT_TYPE'] = 'text/xml'; 24 | } 25 | 26 | public function testRequestType() 27 | { 28 | self::assertInstanceOf(XmlRequest::class, $this->application->request()); 29 | $this->setUp(); 30 | $_SERVER['CONTENT_TYPE'] = 'application/xml'; 31 | self::assertInstanceOf(XmlRequest::class, $this->application->request()); 32 | } 33 | 34 | public function testPayload() 35 | { 36 | $testPayload = 37 | [ 38 | 'a' => 1, 39 | 'b' => 2 40 | ]; 41 | 42 | $context = $this->application->context(); 43 | $context->simulatedRequestBody = SimpleXmlTranscoder::encode($testPayload); 44 | 45 | $request = $this->application->request(); 46 | 47 | $this->assertEquals($testPayload, $request->getPayload()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/unit/Response/HtmlResponseTest.php: -------------------------------------------------------------------------------- 1 | response = new HtmlResponse(); 30 | } 31 | 32 | protected function tearDown() 33 | { 34 | $this->response = null; 35 | } 36 | 37 | public function testDefaultContentTypeHeader() 38 | { 39 | $this->assertEquals( 40 | $this->response->getHeaders()['Content-Type'], 41 | 'text/html', 42 | "Content-Type header is not set to text/html" 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/unit/Response/JsonResponseTest.php: -------------------------------------------------------------------------------- 1 | Forename = "abc"; 29 | $test->Surname = "123"; 30 | $response->setContent($test); 31 | 32 | ob_start(); 33 | $response->send(); 34 | $buffer = ob_get_clean(); 35 | 36 | $this->assertEquals('{"Forename":"' . $test->Forename . '","Surname":"123"}', $buffer); 37 | } 38 | 39 | public function testResponseCanCodeNonModels() 40 | { 41 | $response = new JsonResponse(); 42 | $test = ["abc", "123"]; 43 | $response->setContent($test); 44 | 45 | ob_start(); 46 | $response->send(); 47 | $buffer = ob_get_clean(); 48 | 49 | $this->assertEquals('["abc","123"]', $buffer); 50 | 51 | $response = new JsonResponse(); 52 | $test = new \stdClass(); 53 | $test->abc = "123"; 54 | 55 | $response->setContent($test); 56 | 57 | ob_start(); 58 | $response->send(); 59 | $buffer = ob_get_clean(); 60 | 61 | $this->assertEquals('{"abc":"123"}', $buffer); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/unit/Response/RedirectResponseTest.php: -------------------------------------------------------------------------------- 1 | getHeaders(); 30 | 31 | $this->assertEquals("/go/to/here", $headers["Location"]); 32 | } 33 | } -------------------------------------------------------------------------------- /tests/unit/Response/XmlResponseTest.php: -------------------------------------------------------------------------------- 1 | setContent('some string content'); 15 | ob_start(); 16 | $response->send(); 17 | $buffer = ob_get_clean(); 18 | self::assertEquals('some string content', $buffer); 19 | 20 | $response->setContent([1,2,3]); 21 | ob_start(); 22 | $response->send(); 23 | $buffer = ob_get_clean(); 24 | self::assertEquals(SimpleXmlTranscoder::encode([1,2,3]), $buffer); 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/unit/Sendables/Email/TemplateEmailTest.php: -------------------------------------------------------------------------------- 1 | "Fairbanks", "Age" => "21++", "HairColour" => "brown"]); 30 | 31 | $this->assertEquals("Your name is Fairbanks", $email->getText()); 32 | $this->assertEquals("Your age is 21++", $email->getHtml()); 33 | $this->assertEquals("Your hair is brown", $email->getSubject()); 34 | 35 | $email = new FancyUnitTestingTemplateEmail(["Name" => "Fairbanks", "Age" => "21++", "HairColour" => "brown"]); 36 | 37 | $this->assertEquals("
Your age is 21++
", $email->getHtml(), "Templated emails using layouts aren't using the html layout"); 38 | $this->assertEquals("abcYour name is Fairbanksdef", $email->getText(), "Templated emails using layouts aren't using the text layout"); 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /tests/unit/Sessions/SessionProviders/PhpSessionProviderTest.php: -------------------------------------------------------------------------------- 1 | TestValue = "abc123"; 31 | $session->storeSession(); 32 | 33 | $this->assertEquals("abc123", $_SESSION['Rhubarb\Crown\Tests\unit\Sessions\UnitTestingSession']["TestValue"]); 34 | } 35 | 36 | public function testSessionRestore() 37 | { 38 | $session = UnitTestingSession::singleton(); 39 | $session->TestValue = "abc123"; 40 | $session->storeSession(); 41 | 42 | Container::current()->clearSingleton(UnitTestingSession::class); 43 | 44 | $session = UnitTestingSession::singleton(); 45 | 46 | $this->assertEquals("abc123", $session->TestValue); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/unit/Sessions/SessionTest.php: -------------------------------------------------------------------------------- 1 | registerClass(SessionProvider::class, UnitTestingSessionProvider::class); 35 | 36 | $session = UnitTestingSession::singleton(); 37 | 38 | $this->assertInstanceOf(UnitTestingSessionProvider::class, $session->testGetSessionProvider()); 39 | 40 | Container::current()->registerClass(SessionProvider::class, PhpSessionProvider::class); 41 | 42 | // Although we have changed the default provider, we already instantiated the session so the provider will not 43 | // have changed 44 | $this->assertInstanceOf(UnitTestingSessionProvider::class, $session->testGetSessionProvider()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/unit/Sessions/UnitTestingSession.php: -------------------------------------------------------------------------------- 1 | getSessionProvider(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/unit/Sessions/UnitTestingSessionProvider.php: -------------------------------------------------------------------------------- 1 | assertEquals("default", $settings->SettingWithDefault); 37 | 38 | $settings->SettingWithDefault = "abc"; 39 | 40 | $settings = UnitTestingSettings::singleton(); 41 | 42 | $this->assertEquals("abc", $settings->SettingWithDefault); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/unit/String/TemplateTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($plainTextTemplate, Template::parseTemplate($plainTextTemplate, [])); 30 | 31 | $template = "Ah something to process! {Forename}"; 32 | 33 | $this->assertEquals( 34 | "Ah something to process! Andrew", 35 | Template::parseTemplate($template, ["Forename" => "Andrew"]) 36 | ); 37 | 38 | $template = "Something to parse, but that you can't parse, because there is no {data}"; 39 | 40 | $this->assertEquals( 41 | $template, Template::parseTemplate($template, [], true) 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/unit/UrlHandlers/ClassMappedHandlerTest.php: -------------------------------------------------------------------------------- 1 | urlPath = "/wrong/path/"; 32 | 33 | $handler = new ClassMappedUrlHandler(TestTarget::class); 34 | $handler->setUrl("/right/path/"); 35 | 36 | $response = $handler->generateResponse($request); 37 | 38 | $this->assertFalse($response); 39 | 40 | $request = new WebRequest(); 41 | $request->urlPath = "/right/path/"; 42 | 43 | $response = $handler->generateResponse($request); 44 | 45 | $this->assertEquals("bing bang bong", $response->getContent()); 46 | } 47 | } 48 | 49 | class TestTarget implements GeneratesResponseInterface 50 | { 51 | public function generateResponse($request = null) 52 | { 53 | $response = new Response(); 54 | $response->setContent("bing bang bong"); 55 | 56 | return $response; 57 | } 58 | } -------------------------------------------------------------------------------- /tests/unit/_bootstrap.php: -------------------------------------------------------------------------------- 1 |