├── .editorconfig ├── .gitignore ├── .htaccess ├── LICENSE ├── README.md ├── app ├── HomeController.php ├── IntroController.php ├── PhotoController.php └── index.php ├── backup.sh ├── backups └── .gitignore ├── composer.json ├── composer.lock ├── config ├── .env.example └── .gitignore ├── deploy.sh ├── i18n.sh ├── index.php ├── locale └── .gitignore ├── maintenance.php ├── public ├── css │ └── index.html ├── favicon.ico ├── img │ └── index.html ├── js │ └── index.html ├── media │ └── index.html └── robots.txt ├── storage ├── app │ └── .gitignore └── framework │ └── views │ └── cache │ └── .gitignore └── views ├── 404.html.twig ├── greeting.html.twig ├── includes ├── flash │ └── bootstrap-v3.html.twig ├── footer.html.twig └── header.html.twig ├── mail ├── de-DE │ ├── confirm-email.txt.twig │ ├── email-changed.txt.twig │ ├── forgot-password.txt.twig │ ├── includes │ │ ├── footer.txt.twig │ │ └── header.txt.twig │ ├── password-changed.txt.twig │ └── sign-up.txt.twig └── en-US │ ├── confirm-email.txt.twig │ ├── email-changed.txt.twig │ ├── forgot-password.txt.twig │ ├── includes │ ├── footer.txt.twig │ └── header.txt.twig │ ├── password-changed.txt.twig │ └── sign-up.txt.twig └── welcome.html.twig /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = tab 7 | trim_trailing_whitespace = true 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | indent_style = space 13 | indent_size = 4 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ 2 | .idea/ 3 | 4 | # Composer 5 | vendor/ 6 | composer.phar 7 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | ### PHP-Foundation (https://github.com/delight-im/PHP-Foundation) 2 | ### Copyright (c) delight.im (https://www.delight.im/) 3 | ### Licensed under the MIT License (https://opensource.org/licenses/MIT) 4 | 5 | ########## BEGIN MAINTENANCE MODE ########## 6 | 7 | 8 | 9 | RewriteEngine On 10 | 11 | # Enable maintenance mode (Uncomment 1 line below) 12 | # RewriteRule . maintenance.php [END] 13 | 14 | 15 | 16 | ########## END MAINTENANCE MODE ########## 17 | 18 | ########## BEGIN PERFORMANCE AND SECURITY (https://github.com/delight-im/htaccess) ########## 19 | 20 | 21 | 22 | # Prevent clickjacking (forbids framing by third-party sites) 23 | Header set X-Frame-Options sameorigin 24 | 25 | # Prevent content sniffing (MIME sniffing) 26 | Header set X-Content-Type-Options nosniff 27 | 28 | # Attempt to enable XSS filters in browsers, if available, and block reflected XSS 29 | Header set X-XSS-Protection "1; mode=block" 30 | 31 | # Cache media files for a month 32 | 33 | Header set Cache-Control max-age=2629800 34 | 35 | 36 | # Remove response headers that provide no value but leak information 37 | Header unset X-Powered-By 38 | 39 | # Disable "ETag" headers so that browsers rely on the "Cache-Control" and "Expires" headers 40 | Header unset ETag 41 | 42 | 43 | 44 | 45 | 46 | # Turn off directory listings for folders without default documents 47 | Options -Indexes 48 | 49 | 50 | 51 | 52 | 53 | # Disable 'MultiViews' implicit filename pattern matches 54 | Options -MultiViews 55 | 56 | 57 | 58 | # Serve "text/plain" and "text/html" documents as UTF-8 by default 59 | AddDefaultCharset utf-8 60 | 61 | # Disable "ETag" headers so that browsers rely on the "Cache-Control" and "Expires" headers 62 | FileETag None 63 | 64 | ########## END PERFORMANCE AND SECURITY ########## 65 | 66 | ########## BEGIN CUSTOM (YOUR RULES GO HERE) ########## 67 | 68 | 69 | 70 | # Enable HTTP Strict Transport Security (HSTS) with a duration of six months (Uncomment 1 line below) 71 | # Header set Strict-Transport-Security max-age=15778800 72 | 73 | 74 | 75 | 76 | 77 | RewriteEngine On 78 | 79 | # Force 'www' (i.e. prefix the "bare" domain and all subdomains with 'www' through permanent redirects) (Uncomment 4 lines below) 80 | # RewriteCond %{HTTP_HOST} !^$ 81 | # RewriteCond %{HTTP_HOST} !^www\. [NC] 82 | # RewriteCond %{HTTPS}s ^on(s)| 83 | # RewriteRule ^ http%1://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L] 84 | 85 | # Force HTTPS (Uncomment 2 lines below) 86 | # RewriteCond %{HTTPS} off 87 | # RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L] 88 | 89 | 90 | 91 | # Prevent access to non-minified CSS and JS (Uncomment 3 lines below) 92 | # 93 | # Require all denied 94 | # 95 | 96 | # Announce contact information for security issues (Uncomment 2 lines below) 97 | # Header set X-Vulnerability-Disclosure "https://www.example.com/security" 98 | # Header set X-Security-Contact "security@example.com" 99 | 100 | ########## END CUSTOM ########## 101 | 102 | ########## BEGIN ROUTING (https://github.com/delight-im/PHP-Router) ########## 103 | 104 | 105 | 106 | RewriteEngine On 107 | 108 | # Don't rewrite requests for files in the 'public' directory 109 | RewriteRule ^(public)($|/) - [L] 110 | 111 | # For all other files first check if they exist in the 'public' directory 112 | RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -f 113 | RewriteRule ^ public%{REQUEST_URI} [L] 114 | 115 | # And let 'index.php' handle everything else 116 | RewriteRule . index.php [L] 117 | 118 | 119 | 120 | ########## END ROUTING ########## 121 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) delight.im (https://www.delight.im/) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP-Foundation 2 | 3 | **Writing modern PHP applications efficiently** 4 | 5 | Build *better* applications *faster*, while still using “plain old PHP” only. 6 | 7 | There are no DSLs or pseudo-languages that you need to learn (except [Twig](views/welcome.html)!), nor any “magical” command-line utilities. 8 | 9 | ## Requirements 10 | 11 | * Apache HTTP Server 2.2.0+ 12 | * `mod_rewrite` 13 | * `mod_headers` 14 | * PHP 5.6.0+ 15 | * Multibyte String extension (`mbstring`) 16 | * PDO (PHP Data Objects) extension (`pdo`) 17 | * OpenSSL extension (`openssl`) 18 | * GMP (GNU Multiple Precision) extension (`gmp`) 19 | * MySQL 5.5.3+ **or** MariaDB 5.5.23+ **or** PostgreSQL 9.5.10+ **or** SQLite 3.14.1+ 20 | 21 | ## Installation 22 | 23 | 1. Copy all files from this repository to your development path or web server. 24 | 25 | **Note:** This framework does currently work in the web root (at `/` under your domain) only. 26 | 27 | 1. Copy the configuration template `config/.env.example` to `config/.env`. This new file is where your private configuration will be stored. Fill in the correct settings for your environment. The most important setting is `APP_PUBLIC_URL`, which is required for your application to work correctly in the first place. 28 | 1. If you want to use the built-in authentication component, create the database tables for [MariaDB](https://github.com/delight-im/PHP-Auth/blob/master/Database/MySQL.sql), [MySQL](https://github.com/delight-im/PHP-Auth/blob/master/Database/MySQL.sql), [PostgreSQL](https://github.com/delight-im/PHP-Auth/blob/master/Database/PostgreSQL.sql) or [SQLite](https://github.com/delight-im/PHP-Auth/blob/master/Database/SQLite.sql) in the database that you specified in `config/.env`. 29 | 1. Get Composer [[?]](https://github.com/delight-im/Knowledge/blob/master/Composer%20(PHP).md) and run 30 | 31 | ``` 32 | # composer check-platform-reqs 33 | $ composer install 34 | ``` 35 | 36 | in this directory (containing the `README.md`) to set up the framework and its dependencies. 37 | 1. Make sure that the content of the `storage/` directory is writable by the web server, e.g. using 38 | 39 | ``` 40 | $ sudo chown -R www-data:www-data /var/www/html/storage/* 41 | ``` 42 | 43 | on Linux if this directory (containing the `README.md`) is in `/var/www/html/` on the server. 44 | 1. Ensure that the configuration in the `config/` directory – which usually contains sensitive information such as database credentials, SMTP passwords or API tokens – can be read only by the web server. For example, execute 45 | 46 | ``` 47 | $ sudo chown -R www-data:www-data /var/www/html/config 48 | $ sudo chmod 0555 /var/www/html/config 49 | $ sudo chmod 0400 /var/www/html/config/.env 50 | ``` 51 | 52 | on Linux if this directory (containing the `README.md`) is in `/var/www/html/` on the server. 53 | 1. If you want to enable automatic backups in compressed and encrypted form, please refer to the [“Backups” section](#backups) further below. 54 | 55 | ## Usage 56 | 57 | * [Application structure](#application-structure) 58 | * [Routing](#routing) 59 | * [Charsets and encodings](#charsets-and-encodings) 60 | * [Storage](#storage) 61 | * [Database access](#database-access) 62 | * [String handling](#string-handling) 63 | * [Input validation](#input-validation) 64 | * [HTML escaping](#html-escaping) 65 | * [Templates](#templates) 66 | * [Authentication](#authentication) 67 | * [Sessions and cookies](#sessions-and-cookies) 68 | * [Flash messages](#flash-messages) 69 | * [Mail](#mail) 70 | * [Obfuscation of IDs](#obfuscation-of-ids) 71 | * [Uploading files](#uploading-files) 72 | * [Serving files](#serving-files) 73 | * [File downloads](#file-downloads) 74 | * [Internationalization (I18N) and localization (L10N)](#internationalization-i18n-and-localization-l10n) 75 | * [Helpers and utilities](#helpers-and-utilities) 76 | 77 | ### Application structure 78 | 79 | * `app/`: This is the most important directory: It’s where you’ll write all your PHP code. It’s entirely up to you how you structure your application in this directory. Create as many files and subdirectories here as you wish. The `index.php` file is the main entry point to your application. The complete folder is exclusively for you. 80 | * `backups/`: If you make use of the convenient `backup.sh` script, this is where the compressed and encrypted backups will be stored. See the [“Backups” section](#backups) further down. 81 | * `config/`: Store your local configuration, which should include all confidential information, keys and secrets, here. Put everything in `config/.env` while keeping an up-to-date copy of that file in `config/.env.example` which should have exactly the same keys but all the confidential values removed. The first file will be your private configuration, the second file can be checked in to version control for others to see what configuration keys they need. Always remember to securely back up the private configuration file (`config/.env`) somewhere outside of your VCS. Do *not* store it in version control. 82 | * `public/`: This is where you can store your static assets, such as CSS files, JavaScript files, images, your `robots.txt` and so on. The files will be available at the root URL of your application, i.e. `public/favicon.ico` can simply be accessed at `favicon.ico`. The complete folder is exclusively for you. 83 | * `storage/app/`: Storage that you can use in your app to store files temporarily or permanently. This space is private to your application. Feel free to add any number of subfolders and files here. 84 | * `storage/framework/`: Storage used by the framework, e.g. for caching. Do *not* add, modify or delete anything here. 85 | * `vendor/`: This directory is where Composer will install all dependencies. Do *not* add, modify or delete anything here. 86 | * `views/`: If you use templates for HTML, XML, CSV or LaTeX (or anything else) that should be rendered by the built-in template engine, this is where you should store them. 87 | * `.htaccess`: Rules and optimizations for Apache HTTP Server. You may add your own rules here, but only in the `CUSTOM` section at the bottom. 88 | * `backup.sh`: Script that can create compressed and encrypted backups in the `backups/` directory for you. See the [“Backups” section](#backups) further below. 89 | * `composer.json`: The dependencies of your application. This framework must *always* be included in the `require` section as `delight-im/framework`, so please don’t remove that entry. But otherwise, feel free to add or modify any of your own entries. 90 | * `index.php`: This is the main controller of this framework. It sets everything up correctly and passes on control to your application code. Do *not* change or delete this. 91 | 92 | ### Routing 93 | 94 | Your application will be available at the URL where this root folder is accessible on your web server. 95 | 96 | **Note:** You should have added this URL to the configuration in `config/.env` as `APP_PUBLIC_URL` already. This is required for your application to work correctly. 97 | 98 | Sometimes a single application is expected to respond to *multiple* hostnames with *different* content. Examples include the use of language-specific subdomains (such as `fr.example.com` and `id.example.com`), country-specific TLDs (such as `example.com` and `example.org`), separate storefronts (such as `seller-a.example.com` and `seller-b.example.com`), or user-specific content (such as `jane.example.org` and `john.example.org`). For those cases, you can define multiple supported URLs in `APP_PUBLIC_URL` by separating the individual URLs using the vertical bar or pipe character (`U+007C VERTICAL LINE`). In your application code, you should then decide which content to show based on the value of `$app->getCanonicalHost()`. 99 | 100 | However, if you just want to serve the *same* content at *multiple* hostnames or schemes, you should consider using redirects instead, which can forward from different hostnames or schemes to one canonical form. In most of these cases, you’ll want to send HTTP status `301` for *permanent* redirects. 101 | 102 | The single pages or endpoints of your application will be served at the routes that you define in the `app/` directory. A few sample routes have already been added. 103 | 104 | For more detailed information on routes, please refer to the [documentation of the router](https://github.com/delight-im/PHP-Router). 105 | 106 | ### Charsets and encodings 107 | 108 | Everything is UTF-8 all the way through! Just remember to save your own source files as UTF-8, too. 109 | 110 | ### Storage 111 | 112 | You may store files private to your application in `storage/app/`. Feel free to store the files either in the root directory or in any number of subfolders that you create. 113 | 114 | The storage path can be retrieved in your application code via `$app->getStoragePath('/path/to/subfolder/file.txt')`, for example. 115 | 116 | Files inside `storage/app/` are private to your application. You may use PHP’s file handling utilities to work with the files or offer them to the user as a download (see "File downloads" below). 117 | 118 | ### Database access 119 | 120 | Did you set your database credentials in `config/.env`? If the configuration is valid, getting a database connection is as simple as this: 121 | 122 | ```php 123 | $app->db(); 124 | ``` 125 | 126 | For information on how to read and write data using this instance, please refer to the [documentation of the database library](https://github.com/delight-im/PHP-DB). 127 | 128 | **Note:** You don’t have to construct the database instance or establish the connection yourself. This will be done for you automatically. Just call the methods to read and write data on the instance provided. 129 | 130 | ### String handling 131 | 132 | Convenient string handling in an object-oriented way with full Unicode support is available on any string after wrapping it in the `s(...)` helper function. 133 | 134 | For information on all the methods available with strings wrapped in `s(...)`, please refer to the [documentation of the string library](https://github.com/delight-im/PHP-Str). 135 | 136 | **Note:** There is no need for you to set up the `s(...)` shorthand anymore. This has been done already. 137 | 138 | ### Input validation 139 | 140 | This framework comes with easy and safe input validation for `GET`, `POST` and cookie data as well as for any existing variable. 141 | 142 | Both validation and filtering are performed automatically and *correctly typecast* values are returned for you. 143 | 144 | If a value does not exist or if it’s invalid, you’re guaranteed to receive `null` as the result. This way, you don’t have to check for empty strings, `null`, `false` or invalid formats separately. 145 | 146 | ```php 147 | $app->input()->get('username'); // equivalent to TYPE_STRING 148 | $app->input()->get('profileId', TYPE_INT); 149 | $app->input()->get('disabled', TYPE_BOOL); 150 | $app->input()->get('weight', TYPE_FLOAT); 151 | $app->input()->get('message', TYPE_TEXT); 152 | $app->input()->get('recipientEmail', TYPE_EMAIL); 153 | $app->input()->get('linkTo', TYPE_URL); 154 | $app->input()->get('whoisIp', TYPE_IP); 155 | $app->input()->get('transmission', TYPE_RAW); 156 | 157 | $app->input()->post('country'); // equivalent to TYPE_STRING 158 | $app->input()->post('zipCode', TYPE_INT); 159 | $app->input()->post('subscribe', TYPE_BOOL); 160 | $app->input()->post('price', TYPE_FLOAT); 161 | $app->input()->post('csv', TYPE_TEXT); 162 | $app->input()->post('newsletterEmail', TYPE_EMAIL); 163 | $app->input()->post('referringSite', TYPE_URL); 164 | $app->input()->post('signUpIp', TYPE_IP); 165 | $app->input()->post('print', TYPE_RAW); 166 | 167 | $app->input()->cookie('realName'); // equivalent to TYPE_STRING 168 | $app->input()->cookie('lastLoginTimestamp', TYPE_INT); 169 | $app->input()->cookie('hideAds', TYPE_BOOL); 170 | $app->input()->cookie('cartTotalAmount', TYPE_FLOAT); 171 | $app->input()->cookie('draft', TYPE_TEXT); 172 | $app->input()->cookie('friend', TYPE_EMAIL); 173 | $app->input()->cookie('social', TYPE_URL); 174 | $app->input()->cookie('antiFraud', TYPE_IP); 175 | $app->input()->cookie('display', TYPE_RAW); 176 | 177 | $app->input()->value($username); // equivalent to TYPE_STRING 178 | $app->input()->value($profileId, TYPE_INT); 179 | $app->input()->value($disabled, TYPE_BOOL); 180 | $app->input()->value($weight, TYPE_FLOAT); 181 | $app->input()->value($message, TYPE_TEXT); 182 | $app->input()->value($recipientEmail, TYPE_EMAIL); 183 | $app->input()->value($linkTo, TYPE_URL); 184 | $app->input()->value($whoisIp, TYPE_IP); 185 | $app->input()->value($transmission, TYPE_RAW); 186 | ``` 187 | 188 | ### HTML escaping 189 | 190 | In order to escape any string for safe use in HTML, just wrap the string in the `e(...)` helper function: 191 | 192 | ```php 193 | echo e('Bob says "Hello world"'); 194 | // => Bob <b>says</b> "Hello world" 195 | 196 | // or 197 | 198 | echo 'Bob'; 199 | ``` 200 | 201 | ```html 202 | 203 | 204 | Bob 205 | ``` 206 | 207 | If you use templates stored in the `views/` directory (see below), you don’t need this, as templates come with automatic escaping by default. 208 | 209 | ### Templates 210 | 211 | Place your HTML, XML, CSV or LaTeX (or anything else) templates in the `views/` folder. Make them powerful and re-usable with the [Twig language](http://twig.sensiolabs.org/doc/templates.html). A few examples for such templates have already been added. 212 | 213 | In order to render a template, just load it in your PHP code: 214 | 215 | ```php 216 | echo $app->view('welcome.html.twig'); 217 | ``` 218 | 219 | Usually, you’ll want to pass data to the templates as well: 220 | 221 | ```php 222 | echo $app->view('welcome.html.twig', [ 223 | 'userId' => $id 224 | 'name' => $name 225 | 'messages' => $messages 226 | ]); 227 | ``` 228 | 229 | The data that you passed in the second parameter of the `$app->view(...)` method will be available in your template: 230 | 231 | ```html 232 |

Hello, {{ name }}!

233 | ``` 234 | 235 | All output is escaped (and thus safe) by default! If you need to disable escaping for a variable, you can do so using the `raw` filter: 236 | 237 | ```html 238 | Goodbye, {{ name | raw }}! 239 | ``` 240 | 241 | Conditional expressions and loops are available as control structures: 242 | 243 | ```html 244 | {% if messages|length > 0 %} 245 |

You have {{ messages|length }} new messages!

246 | {% endif %} 247 | ``` 248 | 249 | ```html 250 |
251 | {% for picture in pictures %} 252 | {{ picture.description }} 253 | {% endfor %} 254 |
255 | ``` 256 | 257 | For less code and more markup re-use, templates can be embedded inside each other: 258 | 259 | ```html 260 | {% include 'includes/header.html.twig' %} 261 | ``` 262 | 263 | Embedding can even be combined with loops: 264 | 265 | ```html 266 | 275 | ``` 276 | 277 | You can add custom filters in your PHP code which can then be used to modify data in the templates: 278 | 279 | ```php 280 | $app->getTemplateManager()->addFilter('repeatAndReverse', function ($str) { 281 | return strrev($str.' '.$str); 282 | }); 283 | ``` 284 | 285 | ```html 286 |

We will repeat {{ myVariable | repeatAndReverse }} and reverse this!

287 | ``` 288 | 289 | Moreover, you can add variables as globals in your PHP code which can then be accessed in the templates: 290 | 291 | ```php 292 | $app->getTemplateManager()->addGlobal('googleAnalytics', $ga); 293 | ``` 294 | 295 | ```html 296 | {{ googleAnalytics.trackingCode | raw }} 297 | 298 | ``` 299 | 300 | The main application object is available as a global variable named `app` by default. This means that you can access all methods from the application object in your templates: 301 | 302 | ```html 303 | 304 | ``` 305 | 306 | Finally, you can add functions from your PHP code which then become available for use in the templates: 307 | 308 | ```php 309 | $app->getTemplateManager()->addFunction('translate', function ($value) { 310 | return gettext($value); 311 | }); 312 | ``` 313 | 314 | ```html 315 |

{{ translate('Thanks for joining our community!') }}

316 | ``` 317 | 318 | Please refer to the [documentation of the template engine](http://twig.sensiolabs.org/doc/templates.html) for more information on how to write templates. 319 | 320 | ### Authentication 321 | 322 | Convenient and secure authentication is always available via the `$app->auth()` helper method. 323 | 324 | For information on how to register or log in users with a few lines of code, please refer to the [documentation of the authentication library](https://github.com/delight-im/PHP-Auth). 325 | 326 | ### Sessions and cookies 327 | 328 | Convenient management of session data and cookies is available via the two classes `\Delight\Cookie\Cookie` and `\Delight\Cookie\Session`. 329 | 330 | For information on how to use these two classes, please refer to [the documentation of the session and cookie library](https://github.com/delight-im/PHP-Cookie). 331 | 332 | **Note:** You don’t have to include these two classes yourself anymore. They are included and loaded automatically. 333 | 334 | ### Flash messages 335 | 336 | If you want to store short messages to be displayed right upon the next request, you can use the built-in flash message helpers: 337 | 338 | ```php 339 | $app->flash()->success('Congratulations!'); 340 | // or 341 | $app->flash()->info('Please note!'); 342 | // or 343 | $app->flash()->warning('Watch out!'); 344 | // or 345 | $app->flash()->danger('Oh snap!'); 346 | ``` 347 | 348 | In order to *display* the messages, which you’ll probably want to do in your templates, use the methods available on the `$app->flash()` instance for retrieving the data. Alternatively, use one of the built-in partial templates for popular front-end solutions: 349 | 350 | * [Bootstrap 3](http://getbootstrap.com/) 351 | 352 | ``` 353 | {% include 'includes/flash/bootstrap-v3.html.twig' %} 354 | ``` 355 | 356 | ### Mail 357 | 358 | The pre-configured mailing component is always available via the `$app->mail()` helper: 359 | 360 | ```php 361 | $message = $app->mail()->createMessage(); 362 | 363 | $message->setSubject('This is the subject'); 364 | $message->setFrom([ 'john.doe@example.com' => 'John Doe' ]); 365 | $message->setTo([ 'jane@example.org' => 'Jane Doe' ]); 366 | $message->setBody('Here is the message'); 367 | 368 | if ($app->mail()->send($message)) { 369 | // Success 370 | } 371 | else { 372 | // Failure 373 | } 374 | ``` 375 | 376 | Please refer to the [documentation of the mailing component](http://swiftmailer.org/docs/messages.html) for more information on how to build message instances. 377 | 378 | ### Obfuscation of IDs 379 | 380 | Do you want to prevent your internal IDs from leaking to competitors or attackers when using them in URLs or forms? 381 | 382 | Just use the following two methods to encode or decode your IDs, respectively: 383 | 384 | ```php 385 | $app->ids()->encode(6); // => e.g. "43Vht7" 386 | // and 387 | $app->ids()->decode('43Vht7'); // => e.g. 6 388 | ``` 389 | 390 | If you want to configure the underlying transformation, you simply have to change the following configuration values in your `config/.env` file: 391 | 392 | * `SECURITY_IDS_ALPHABET` 393 | * `SECURITY_IDS_PRIME` 394 | * `SECURITY_IDS_INVERSE` 395 | * `SECURITY_IDS_RANDOM` 396 | 397 | Please refer to the [documentation of the ID obfuscation library](https://github.com/delight-im/PHP-IDs) for information on what constitutes proper values and how to generate them. 398 | 399 | ### Uploading files 400 | 401 | Whenever you want to let users upload files to your application, there’s a built-in component that does this in a convenient and safe way: 402 | 403 | ```php 404 | $upload = $app->upload('/uploads/users/' . $userId . '/avatars'); 405 | $upload->from('my-input-name'); 406 | $upload->save(); 407 | ``` 408 | 409 | For more information on how to use the upload handler, including more advanced controls, please refer to the [documentation of the file upload library](https://github.com/delight-im/PHP-FileUpload#usage). 410 | 411 | If you want to check for the presence of (optional) file uploads before actually processing them, you can use the following two helpers: 412 | 413 | ```php 414 | $app->hasUploads(); 415 | // and/or 416 | $app->hasUpload('my-input-name'); 417 | ``` 418 | 419 | ### Serving files 420 | 421 | You can serve (static) files to the client from your PHP code, e.g. after performing access control: 422 | 423 | ```php 424 | $app->serveFile($app->getStoragePath('/photos/314.png'), 'image/png'); 425 | ``` 426 | 427 | Most commonly, this will be used to serve images. If certain files aren’t public or cannot be accessed directly for other reasons, thus requiring more dynamic behavior, the helper above will be useful. 428 | 429 | If you want to offer files for download, however, e.g. documents or videos, there is another helper that is more suitable (see "File downloads"). 430 | 431 | ### File downloads 432 | 433 | In order to send strings or files to the client and prompt a download, use one of the following methods: 434 | 435 | ```php 436 | $app->downloadContent('...', 'Bookmarks-Export.html', 'text/html'); 437 | // or 438 | $app->downloadFile($app->getStoragePath('/photos/42.jpg'), 'My Photo.jpg', 'image/jpeg'); 439 | ``` 440 | 441 | ### Internationalization (I18N) and localization (L10N) 442 | 443 | If you want to localize your application and translate strings in your application code and in your templates, the built-in internationalization component offers everything you need. 444 | 445 | First, in order to enable the component, go to your configuration in `config/.env` and set `I18N_SUPPORTED_LOCALES` to a comma-separated list of languages or locales that you want to support. You can use the codes [listed here](https://github.com/delight-im/PHP-I18N/blob/master/src/Codes.php): 446 | 447 | ``` 448 | I18N_SUPPORTED_LOCALES=en-US,da-DK,es,es-AR,ko 449 | ``` 450 | 451 | Some of the locales you want to use may not be installed on your local machine or on the server that you deploy your application to. In that case, you will have to [install](https://github.com/delight-im/PHP-I18N#decide-on-your-initial-set-of-supported-locales) the locales first. 452 | 453 | The best language for the client is usually detected and applied automatically (from the HTTP header `Accept-Language` that is sent by the client), but you may select a language manually by supplying it in a subdomain (e.g. `da-DK.example.com`), in a path prefix (e.g. `http://www.example.com/pt-BR/welcome.html`), or as a parameter in the query string (as `locale`, `language`, `lang` or `lc`). 454 | 455 | Optionally, if you want to store the currently selected language in the session or in a cookie, you can define the key or name to use in your configuration in `config/.env`: 456 | 457 | ```bash 458 | I18N_SESSION_FIELD=language 459 | # and / or 460 | I18N_COOKIE_NAME=lang 461 | ``` 462 | 463 | You can always check the currently active locale for a client using `$app->i18n()->getLocale()` in your application code or using `app.i18n().getLocale()` in your templates. Apart from that, there are many other [helpers and utilities](https://github.com/delight-im/PHP-I18N#information-about-locales) available that let you access names and other information for your set of locales. 464 | 465 | Next, you need to identify and mark all strings in your code that can be translated. For an existing application, this might require some more effort, but you can start with only a few strings, of course. 466 | 467 | This allows for the marked strings to be extracted automatically, so that they can be translated outside of the actual code, before being inserted again automatically during runtime. 468 | 469 | * [Basic strings](https://github.com/delight-im/PHP-I18N#basic-strings) 470 | 471 | * In your application code 472 | 473 | ``` 474 | _('Welcome to our online store!'); 475 | ``` 476 | 477 | * In your templates 478 | 479 | ``` 480 | {{ _('Welcome to our online store!') }} 481 | ``` 482 | 483 | * [Strings with formatting](https://github.com/delight-im/PHP-I18N#strings-with-formatting) 484 | 485 | * In your application code 486 | 487 | ``` 488 | _f('Hello %s!', 'Jane'); 489 | ``` 490 | 491 | * In your templates 492 | 493 | ``` 494 | {{ _f('Hello %s!', 'Jane') }} 495 | ``` 496 | 497 | * [Strings with extended formatting](https://github.com/delight-im/PHP-I18N#strings-with-extended-formatting) 498 | 499 | * In your application code 500 | 501 | ``` 502 | _fe('{0} was born on {1, date}', 'John', -14182916); 503 | ``` 504 | 505 | * In your templates 506 | 507 | ``` 508 | {{ _fe('{0} was born on {1, date}', 'John', -14182916) }} 509 | ``` 510 | 511 | * [Singular and plural forms](https://github.com/delight-im/PHP-I18N#singular-and-plural-forms) 512 | 513 | * In your application code 514 | 515 | ``` 516 | _p('The file has been saved.', 'The files have been saved.', 3); 517 | ``` 518 | 519 | * In your templates 520 | 521 | ``` 522 | {{ _p('The file has been saved.', 'The files have been saved.', 3) }} 523 | ``` 524 | 525 | * [Singular and plural forms with formatting](https://github.com/delight-im/PHP-I18N#singular-and-plural-forms-with-formatting) 526 | 527 | * In your application code 528 | 529 | ``` 530 | _pf('You have %d new message', 'You have %d new messages', 32); 531 | ``` 532 | 533 | * In your templates 534 | 535 | ``` 536 | {{ _pf('You have %d new message', 'You have %d new messages', 32) }} 537 | ``` 538 | 539 | * [Singular and plural forms with extended formatting](https://github.com/delight-im/PHP-I18N#singular-and-plural-forms-with-extended-formatting) 540 | 541 | * In your application code 542 | 543 | ``` 544 | _pfe('There is {0, number} monkey in {1}.', 'There are {0, number} monkeys in {1}.', 5, 'Anytown'); 545 | ``` 546 | 547 | * In your templates 548 | 549 | ``` 550 | {{ _pfe('There is {0, number} monkey in {1}.', 'There are {0, number} monkeys in {1}.', 5, 'Anytown') }} 551 | ``` 552 | 553 | * [Strings with context](https://github.com/delight-im/PHP-I18N#strings-with-context) 554 | 555 | * In your application code 556 | 557 | ``` 558 | _c('Select order:', 'sorting'); 559 | ``` 560 | 561 | * In your templates 562 | 563 | ``` 564 | {{ _c('Select order:', 'sorting') }} 565 | ``` 566 | 567 | * [Strings marked for later translation](https://github.com/delight-im/PHP-I18N#strings-marked-for-later-translation) 568 | 569 | * In your application code 570 | 571 | ``` 572 | $title = _m('Profile picture'); 573 | ``` 574 | 575 | Finally, after having identified and marked some (or all) strings for translation in your application code and in your templates, you can use the built-in tools to extract the strings automatically. 576 | 577 | To allow for the extraction of the strings from your templates, refresh and replace the cached versions of your template files by running the following two commands in the root directory of your project: 578 | 579 | ```bash 580 | $ sudo -u www-data php ./index.php clear-template-cache 581 | $ sudo -u www-data php ./index.php precompile-templates 582 | ``` 583 | 584 | Then, if you want to create or update a PO (Portable Object) file for a specific language, along with its MO (Machine Object) version, which is usually what you want, you need to run the following command in the root directory of the project (using the locale `mr-IN` as an example): 585 | 586 | ```bash 587 | $ bash ./i18n.sh mr-IN 588 | ``` 589 | 590 | **Note:** On Windows 10+, this command can be used by enabling the [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10). Then, inside WSL, run `sudo apt-get install gettext`, `sudo apt-get install dos2unix` and `dos2unix i18n.sh` before being able to use the command. 591 | 592 | If you want a generic POT (Portable Object Template) file instead, just drop the locale code from the end: 593 | 594 | ```bash 595 | $ bash ./i18n.sh 596 | ``` 597 | 598 | Now you can [translate](https://github.com/delight-im/PHP-I18N#translating-the-extracted-strings) the exported PO or POT files, either manually or using any tool or service you want. By running the commands listed above again, you can [compile](https://github.com/delight-im/PHP-I18N#exporting-translations-to-binary-format) the translated files to an updated binary version, after which you only need to restart your web server for the translations to appear in your application. 599 | 600 | ### Helpers and utilities 601 | 602 | The following helpers and utilities are available throughout your application code and in all templates: 603 | 604 | * `$app->url($toPath)` (`app.url(toPath)` in templates) 605 | * `$app->urlWithLang($toPath)` (`app.urlWithLang(toPath)` in templates) 606 | * `$app->urlWithParams($toPath, $params)` (`app.urlWithParams(toPath, params)` in templates) 607 | * `$app->redirect($toPath)` 608 | * `$app->redirectToUrl($toUrl)` 609 | * `$app->setStatus($code)` 610 | * `$app->setContentType($type)` 611 | * `$app->flash()` (`app.flash()` in templates) 612 | * `$app->isDebug()` (`app.isDebug()` in templates) 613 | * `$app->currentRoute()` (`app.currentRoute()` in templates) 614 | * `$app->currentUrl()` (`app.currentUrl()` in templates) 615 | * `$app->currentUrlWithLang()` (`app.currentUrlWithLang()` in templates) 616 | * `$app->currentUrlWithParams($params)` (`app.currentUrlWithParams(params)` in templates) 617 | * `$app->currentUrlWithQuery()` (`app.currentUrlWithQuery()` in templates) 618 | * `$app->currentUrlWithQueryAndLang()` (`app.currentUrlWithQueryAndLang()` in templates) 619 | * `$app->currentUrlWithQueryAndParams($params)` (`app.currentUrlWithQueryAndParams(params)` in templates) 620 | * `$app->getCanonicalHost()` (`app.getCanonicalHost()` in templates) 621 | * `$app->getHost()` (`app.getHost()` in templates) 622 | * `$app->getReferrer()` (`app.getReferrer()` in templates) 623 | * `$app->getClientIp()` (`app.getClientIp()` in templates) 624 | * `$app->getUa()` (`app.getUa()` in templates) 625 | * `$app->isBotAgent()` (`app.isBotAgent()` in templates) 626 | * `$app->isClientLoopback()` (`app.isClientLoopback()` in templates) 627 | * `$app->isClientCli()` 628 | * `$app->hasCliArgument()` 629 | * `$app->hasCliArgument($position)` 630 | * `$app->getCliArgument()` 631 | * `$app->getCliArgument($position)` 632 | * `$app->isSameSiteRequest()` (`app.isSameSiteRequest()` in templates) 633 | * `$app->isHttp()` 634 | * `$app->isHttps()` 635 | * `$app->getProtocol()` 636 | * `$app->getPort()` 637 | * `$app->getQueryString()` 638 | * `$app->getRequestMethod()` 639 | * `$app->getUtmSource()` (`app.getUtmSource()` in templates) 640 | * `$app->getUtmMedium()` (`app.getUtmMedium()` in templates) 641 | * `$app->getUtmCampaign()` (`app.getUtmCampaign()` in templates) 642 | * `$app->getUtmTerm()` (`app.getUtmTerm()` in templates) 643 | * `$app->getUtmContent()` (`app.getUtmContent()` in templates) 644 | 645 | ## Backups 646 | 647 | This package includes support for automatic backups of your database, app storage and log files in compressed and encrypted form. These backups are not enabled by default. For information on how to enable them and make good use of them, please see below. 648 | 649 | ### Enabling backups 650 | 651 | 1. Execute the following commands on Linux if this directory (containing the `README.md`) is in `/var/www/html/` on the server: 652 | 653 | ``` 654 | # Make the backup script executable while preventing modifications to its content 655 | $ sudo chmod 0500 /var/www/html/backup.sh 656 | # Assign ownership of the backup script to the superuser 657 | $ sudo chown root:root /var/www/html/backup.sh 658 | # Generate a public/private keypair and store the private key in the `backups` directory 659 | $ openssl genpkey -algorithm RSA -out /var/www/html/backups/asymmetric-key.private.pem -pkeyopt rsa_keygen_bits:4096 660 | # Extract the public key from the new private key in the `backups` directory 661 | $ openssl rsa -in /var/www/html/backups/asymmetric-key.private.pem -outform PEM -out /var/www/html/backups/asymmetric-key.public.pem -pubout 662 | # Allow only read access by its owner for the private key 663 | $ sudo chmod 0400 /var/www/html/backups/asymmetric-key.private.pem 664 | # Grant everybody read access to the public key but no other rights 665 | $ sudo chmod 0444 /var/www/html/backups/asymmetric-key.public.pem 666 | # Make the `backups` directory writable only by its owner 667 | $ sudo chmod 0755 /var/www/html/backups 668 | # Assign ownership of the `backups` directory and its contents to the superuser 669 | $ sudo chown -R root:root /var/www/html/backups 670 | ``` 671 | 672 | 1. Copy – or perhaps *move* – the private key that has been generated above (`backups/asymmetric-key.private.pem`) to a *secure* location on *another machine*. It is important that you choose a location where *you won’t lose* the key and *nobody else will be able to view* the key. Without the private key, you won’t be able to restore your backups later. 673 | 674 | 1. In order to set up the periodic execution of the backup script, run 675 | 676 | ``` 677 | $ sudo crontab -e 678 | ``` 679 | 680 | and add a new line with the following content: 681 | 682 | ``` 683 | 30 4 * * * /bin/bash /var/www/html/backup.sh > /var/www/html/backups/last.log 2>&1 684 | ``` 685 | 686 | This will create a new backup every night at 04:30. While daily backups should usually be the minimum, you *may* create backups more frequently, if required, e.g. by separating multiple hours with a comma in the crontab entry, as in `4,10,16,22`. 687 | 688 | If you don’t want your backups to be encrypted, pass `unencrypted` as the only argument to `backup.sh`. Otherwise, you may pass `encrypted`, which is also the default. 689 | 690 | ### Moving backups offsite 691 | 692 | For a truly effective backup strategy, of course, you should move the archives created in the `backups/` directory *offsite*, i.e. to a remote location such as another server that you maintain specifically for backups. 693 | 694 | Thus, you should either set up a periodic task on a remote machine to *pull* the contents of the `backups/` directory regularly, or set up a periodic task on the local machine to *push* the backups to a remote location with *append-only* privileges (so that a compromised server could not destroy all your backups). 695 | 696 | ## Security 697 | 698 | * **SQL injections** are prevented if you write to the database using prepared statements only. That is, just follow the examples from the "Database access" section above. 699 | * **Cross-site scripting (XSS)** protection is available via the convenient escaping and templating features (see sections "HTML escaping" and "Templates" above). 700 | * **Clickjacking** prevention is built-in and applied automatically. 701 | * **Content sniffing (MIME sniffing)** protection is built-in and applied automatically. 702 | * **Cross-site request forgery (CSRF)** mitigation is built-in as well using [same-site cookies](https://github.com/delight-im/PHP-Cookie). For this automatic protection to be sufficient, however, you must prevent XSS attacks and you must not use the HTTP method `GET` for "dangerous" operations. 703 | * Safe and convenient input validation is built-in as a solid basis for handling untrusted input. 704 | 705 | ## Contributing 706 | 707 | All contributions are welcome! If you wish to contribute, please create an issue first so that your feature, problem or question can be discussed. 708 | 709 | ## License 710 | 711 | This project is licensed under the terms of the [MIT License](https://opensource.org/licenses/MIT). 712 | -------------------------------------------------------------------------------- /app/HomeController.php: -------------------------------------------------------------------------------- 1 | view('welcome.html.twig', [ 15 | 'users' => [ 'Alice', 'Bob' ] 16 | ]); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/IntroController.php: -------------------------------------------------------------------------------- 1 | view('greeting.html.twig', [ 15 | 'name' => $name, 16 | 'greetingId' => $app->input()->get('greetingId', \TYPE_INT) 17 | ]); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /app/PhotoController.php: -------------------------------------------------------------------------------- 1 | get('/', [ '\App\HomeController', 'getIndex' ]); 4 | $app->get('/greet/:name', [ '\App\IntroController', 'getGreet' ]); 5 | $app->post('/photos/:id/delete', [ '\App\PhotoController', 'postDelete' ]); 6 | 7 | // // OR USE CLOSURES (ANONYMOUS FUNCTIONS) DIRECTLY IN THIS FILE 8 | // 9 | // use Delight\Foundation\App; 10 | // 11 | // $app->get('/', function (App $app) { 12 | // // do something 13 | // // ... 14 | // 15 | // // and return a view 16 | // echo $app->view('welcome.html.twig', [ 17 | // 'users' => [ 'Alice', 'Bob' ] 18 | // ]); 19 | // }); 20 | // 21 | // $app->get('/greet/:name', function (App $app, $name) { 22 | // // do something 23 | // // ... 24 | // 25 | // // and return a view 26 | // echo $app->view('greeting.html.twig', [ 27 | // 'name' => $name, 28 | // 'greetingId' => $app->input()->get('greetingId', TYPE_INT) 29 | // ]); 30 | // }); 31 | // 32 | // $app->post('/photos/:id/delete', function (App $app, $id) { 33 | // // do something 34 | // // ... 35 | // }); 36 | 37 | // return an error page for undefined pages 38 | $app->setStatus(404); 39 | echo $app->view('404.html.twig'); 40 | -------------------------------------------------------------------------------- /backup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### PHP-Foundation (https://github.com/delight-im/PHP-Foundation) 4 | ### Copyright (c) delight.im (https://www.delight.im/) 5 | ### Licensed under the MIT License (https://opensource.org/licenses/MIT) 6 | 7 | # Switch to the directory where the current script is located 8 | cd "${BASH_SOURCE%/*}" || exit 1 9 | 10 | # BEGIN CONSTANTS 11 | 12 | # Parent directory of all backups 13 | CONTAINER_DIRECTORY="./backups" 14 | # Unique name for the current backup 15 | BACKUP_NAME=$(date -u +'%Y%m%dT%H%M%SZ') 16 | # Directory where the current backup will be stored 17 | BACKUP_DIRECTORY="${CONTAINER_DIRECTORY}/${BACKUP_NAME}" 18 | # Filename for the archive of all data to be backed up 19 | DATA_ARCHIVE_FILENAME="data.tar.gz" 20 | # Filename for the archive of all data in symmetrically encrypted form 21 | DATA_ENCRYPTED_FILENAME="data.tar.gz.crypt" 22 | # Filename for the archive of data from the file system 23 | FILE_SYSTEM_ARCHIVE_FILENAME="file-system.tar" 24 | # Filename for the archive of log files 25 | LOGS_ARCHIVE_FILENAME="logs.tar" 26 | # Filename for the temporary (and private) MySQL configuration 27 | MYSQL_CONFIGURATION_FILENAME=".mysql.cnf" 28 | # Filename for the database schema 29 | DATABASE_SCHEMA_FILENAME="database-schema.sql" 30 | # Filename for the database seeds 31 | DATABASE_SEEDS_FILENAME="database-seeds.sql" 32 | # Filename for the full database 33 | DATABASE_FULL_FILENAME="database-full.sql" 34 | # Filename for the key for symmetric encryption 35 | SYMMETRIC_KEY_FILENAME="symmetric-key.bin" 36 | # Filename for the symmetric key in asymmetrically encrypted form 37 | SYMMETRIC_KEY_ENCRYPTED_FILENAME="symmetric-key.bin.crypt" 38 | # Filename for the ultimate result of the current backup 39 | BACKUP_FILENAME="${BACKUP_NAME}.tar" 40 | # Path to the public key for asymmetric encryption 41 | ASYMMETRIC_PUBLIC_KEY_FILENAME="asymmetric-key.public.pem" 42 | # Filename for the exemplary script showing how decryption works 43 | DECRYPTION_SAMPLE_SCRIPT_FILENAME="decrypt.sh" 44 | 45 | # END CONSTANTS 46 | 47 | # Announce that the backup process begins 48 | echo "Creating backup" 49 | 50 | # Verify that the source directory is a valid project root by looking for some important files and directories 51 | if [ -d "app" ] && [ -f "index.php" ] && [ -d "public" ] && [ -d "vendor" ] && [ -d "views" ]; then 52 | echo " * Source directory successfully verified ..." 53 | else 54 | echo " * Error: Source directory could not be verified" 55 | exit 2 56 | fi 57 | 58 | # Verify that the current script is executed with superuser privileges 59 | if [[ $EUID > 0 ]]; then 60 | echo " * Error: Please run script with superuser privileges instead" 61 | exit 3 62 | fi 63 | 64 | # Verify that the configuration of the application exists and can be read 65 | if [ -d "config" ] && [ -f "config/.env" ] && [ -r "config/.env" ]; then 66 | echo " * Configuration verified to be readable ..." 67 | else 68 | echo " * Error: Configuration could not be found or accessed" 69 | exit 4 70 | fi 71 | 72 | # Verify that the parent directory for any backups exists and can be written to 73 | if [ -d $CONTAINER_DIRECTORY ] && [ -w $CONTAINER_DIRECTORY ]; then 74 | echo " * Directory 'backups' exists and can be written to ..." 75 | else 76 | echo " * Error: Directory 'backups' does not exist or cannot be written to" 77 | exit 5 78 | fi 79 | 80 | # Verify that the public key for asymmetric encryption exists and can be read 81 | if [ "$1" != "unencrypted" ]; then 82 | if [ -f "${CONTAINER_DIRECTORY}/${ASYMMETRIC_PUBLIC_KEY_FILENAME}" ] && [ -r "${CONTAINER_DIRECTORY}/${ASYMMETRIC_PUBLIC_KEY_FILENAME}" ]; then 83 | echo " * Public key for asymmetric encryption found ..." 84 | else 85 | echo " * Error: Public key for asymmetric encryption could not be found" 86 | exit 6 87 | fi 88 | fi 89 | 90 | # Read the database configuration 91 | echo " * Reading configuration of application ..." 92 | APP_DEBUG=$(grep --perl-regexp --text --only-matching '(?<=APP_DEBUG=)(.+)' config/.env) 93 | DB_DRIVER=$(grep --perl-regexp --text --only-matching '(?<=DB_DRIVER=)(.+)' config/.env) 94 | DB_HOST=$(grep --perl-regexp --text --only-matching '(?<=DB_HOST=)(.+)' config/.env) 95 | DB_PORT=$(grep --perl-regexp --text --only-matching '(?<=DB_PORT=)(.+)' config/.env) 96 | DB_NAME=$(grep --perl-regexp --text --only-matching '(?<=DB_NAME=)(.+)' config/.env) 97 | DB_USERNAME=$(grep --perl-regexp --text --only-matching '(?<=DB_USERNAME=)(.+)' config/.env) 98 | DB_PASSWORD=$(grep --perl-regexp --text --only-matching '(?<=DB_PASSWORD=)(.+)' config/.env) 99 | DB_CHARSET=$(grep --perl-regexp --text --only-matching '(?<=DB_CHARSET=)(.+)' config/.env) 100 | 101 | # Verify that the configuration is complete 102 | if [ "$DB_DRIVER" != "" ] && [ "$DB_HOST" != "" ] && [ "$DB_PORT" != "" ] && [ "$DB_NAME" != "" ] && [ "$DB_USERNAME" != "" ] && [ "$DB_CHARSET" != "" ]; then 103 | echo " * Configuration verified to be complete ..." 104 | else 105 | echo " * Error: Configuration is incomplete" 106 | exit 7 107 | fi 108 | 109 | # Check whether the database driver specified in the configuration is supported 110 | if [ "$DB_DRIVER" == "mysql" ]; then 111 | echo " * Database driver specified in the configuration is supported ..." 112 | else 113 | echo " * Error: Database driver specified in the configuration is not supported" 114 | exit 8 115 | fi 116 | 117 | # Create a new directory for the current backup 118 | echo " * Creating target directory for backup ..." 119 | mkdir $BACKUP_DIRECTORY 120 | 121 | # Create a temporary (and private) configuration file for MySQL to avoid passing the password on the command line 122 | touch "${BACKUP_DIRECTORY}/${MYSQL_CONFIGURATION_FILENAME}" 123 | chmod 0600 "${BACKUP_DIRECTORY}/${MYSQL_CONFIGURATION_FILENAME}" 124 | echo -e "[mysqldump]\npassword=${DB_PASSWORD}" > "${BACKUP_DIRECTORY}/${MYSQL_CONFIGURATION_FILENAME}" 125 | 126 | # Announce that the database export begins 127 | echo " * Exporting the database ..." 128 | 129 | # If the application is in debug mode 130 | if [ "$APP_DEBUG" == "1" ]; then 131 | # Export the database schema 132 | echo " * Exporting database schema ..." 133 | touch "${BACKUP_DIRECTORY}/${DATABASE_SCHEMA_FILENAME}" 134 | chmod 0600 "${BACKUP_DIRECTORY}/${DATABASE_SCHEMA_FILENAME}" 135 | mysqldump \ 136 | --defaults-extra-file="${BACKUP_DIRECTORY}/${MYSQL_CONFIGURATION_FILENAME}" \ 137 | --add-locks \ 138 | --complete-insert \ 139 | --create-options \ 140 | --default-character-set="$DB_CHARSET" \ 141 | --disable-keys \ 142 | --skip-extended-insert \ 143 | --lock-tables \ 144 | --order-by-primary \ 145 | --protocol=tcp \ 146 | --quick \ 147 | --quote-names \ 148 | --set-charset \ 149 | --skip-add-drop-table \ 150 | --skip-comments \ 151 | --skip-triggers \ 152 | --tz-utc \ 153 | --host="$DB_HOST" \ 154 | --port="$DB_PORT" \ 155 | --user="$DB_USERNAME" \ 156 | --result-file="${BACKUP_DIRECTORY}/${DATABASE_SCHEMA_FILENAME}" \ 157 | --no-data \ 158 | "$DB_NAME" 159 | 160 | # Export the database seeds 161 | echo " * Exporting database seeds ..." 162 | touch "${BACKUP_DIRECTORY}/${DATABASE_SEEDS_FILENAME}" 163 | chmod 0600 "${BACKUP_DIRECTORY}/${DATABASE_SEEDS_FILENAME}" 164 | mysqldump \ 165 | --defaults-extra-file="${BACKUP_DIRECTORY}/${MYSQL_CONFIGURATION_FILENAME}" \ 166 | --add-locks \ 167 | --complete-insert \ 168 | --create-options \ 169 | --default-character-set="$DB_CHARSET" \ 170 | --disable-keys \ 171 | --skip-extended-insert \ 172 | --lock-tables \ 173 | --order-by-primary \ 174 | --protocol=tcp \ 175 | --quick \ 176 | --quote-names \ 177 | --set-charset \ 178 | --skip-add-drop-table \ 179 | --skip-comments \ 180 | --skip-triggers \ 181 | --tz-utc \ 182 | --host="$DB_HOST" \ 183 | --port="$DB_PORT" \ 184 | --user="$DB_USERNAME" \ 185 | --result-file="${BACKUP_DIRECTORY}/${DATABASE_SEEDS_FILENAME}" \ 186 | --no-create-info \ 187 | "$DB_NAME" 188 | # If the application is in production mode 189 | else 190 | # Export the entire database 191 | touch "${BACKUP_DIRECTORY}/${DATABASE_FULL_FILENAME}" 192 | chmod 0600 "${BACKUP_DIRECTORY}/${DATABASE_FULL_FILENAME}" 193 | mysqldump \ 194 | --defaults-extra-file="${BACKUP_DIRECTORY}/${MYSQL_CONFIGURATION_FILENAME}" \ 195 | --add-locks \ 196 | --complete-insert \ 197 | --create-options \ 198 | --default-character-set="$DB_CHARSET" \ 199 | --disable-keys \ 200 | --extended-insert \ 201 | --lock-tables \ 202 | --order-by-primary \ 203 | --protocol=tcp \ 204 | --quick \ 205 | --quote-names \ 206 | --set-charset \ 207 | --skip-add-drop-table \ 208 | --skip-comments \ 209 | --skip-triggers \ 210 | --tz-utc \ 211 | --host="$DB_HOST" \ 212 | --port="$DB_PORT" \ 213 | --user="$DB_USERNAME" \ 214 | --result-file="${BACKUP_DIRECTORY}/${DATABASE_FULL_FILENAME}" \ 215 | "$DB_NAME" 216 | fi 217 | 218 | # Delete the temporary (and private) configuration file for MySQL again 219 | rm "${BACKUP_DIRECTORY}/${MYSQL_CONFIGURATION_FILENAME}" 220 | 221 | # Export data from the file system 222 | echo " * Exporting app storage and '.htaccess' ..." 223 | touch "${BACKUP_DIRECTORY}/${FILE_SYSTEM_ARCHIVE_FILENAME}" 224 | chmod 0600 "${BACKUP_DIRECTORY}/${FILE_SYSTEM_ARCHIVE_FILENAME}" 225 | tar \ 226 | --create \ 227 | --file="${BACKUP_DIRECTORY}/${FILE_SYSTEM_ARCHIVE_FILENAME}" \ 228 | ".htaccess" \ 229 | "storage/app" 230 | 231 | # Export log files 232 | echo " * Exporting log files ..." 233 | touch "${BACKUP_DIRECTORY}/${LOGS_ARCHIVE_FILENAME}" 234 | chmod 0600 "${BACKUP_DIRECTORY}/${LOGS_ARCHIVE_FILENAME}" 235 | tar \ 236 | --create \ 237 | --file="${BACKUP_DIRECTORY}/${LOGS_ARCHIVE_FILENAME}" \ 238 | $( \ 239 | ls \ 240 | -d \ 241 | /var/log/apache2/access.log \ 242 | /var/log/apache2/error.log \ 243 | /var/log/apt/history.log \ 244 | /var/log/apt/term.log \ 245 | /var/log/auth.log \ 246 | /var/log/cloud-init.log \ 247 | /var/log/dpkg.log \ 248 | /var/log/mysql/error.log \ 249 | /var/log/ufw.log \ 250 | /var/log/unattended-upgrades/unattended-upgrades.log \ 251 | /var/log/unattended-upgrades/unattended-upgrades-dpkg.log \ 252 | 2>/dev/null \ 253 | ) \ 254 | 2>/dev/null 255 | 256 | # Pack all exported data into a single archive 257 | echo " * Packing exported data ..." 258 | touch "${BACKUP_DIRECTORY}/${DATA_ARCHIVE_FILENAME}" 259 | chmod 0600 "${BACKUP_DIRECTORY}/${DATA_ARCHIVE_FILENAME}" 260 | tar \ 261 | --create \ 262 | --gzip \ 263 | --file="${BACKUP_DIRECTORY}/${DATA_ARCHIVE_FILENAME}" \ 264 | --directory="$BACKUP_DIRECTORY" \ 265 | --exclude="$DATA_ARCHIVE_FILENAME" \ 266 | . 267 | 268 | # Delete the temporary files from the individual exports again 269 | echo " * Deleting non-packed exports ..." 270 | rm -f "${BACKUP_DIRECTORY}/${DATABASE_SCHEMA_FILENAME}" 271 | rm -f "${BACKUP_DIRECTORY}/${DATABASE_SEEDS_FILENAME}" 272 | rm -f "${BACKUP_DIRECTORY}/${DATABASE_FULL_FILENAME}" 273 | rm -f "${BACKUP_DIRECTORY}/${FILE_SYSTEM_ARCHIVE_FILENAME}" 274 | rm -f "${BACKUP_DIRECTORY}/${LOGS_ARCHIVE_FILENAME}" 275 | 276 | if [ "$1" == "unencrypted" ]; then 277 | echo " * Skipping encryption as desired" 278 | else 279 | # Generate random bytes to be used as the key for symmetric encryption 280 | echo " * Generating random key for symmetric encryption ..." 281 | touch "${BACKUP_DIRECTORY}/${SYMMETRIC_KEY_FILENAME}" 282 | chmod 0600 "${BACKUP_DIRECTORY}/${SYMMETRIC_KEY_FILENAME}" 283 | openssl rand -out "${BACKUP_DIRECTORY}/${SYMMETRIC_KEY_FILENAME}" 32 284 | 285 | # Symmetrically encrypt the exported data with the generated key 286 | echo " * Symmetrically encrypting exported data ..." 287 | touch "${BACKUP_DIRECTORY}/${DATA_ENCRYPTED_FILENAME}" 288 | chmod 0644 "${BACKUP_DIRECTORY}/${DATA_ENCRYPTED_FILENAME}" 289 | openssl enc \ 290 | -aes-256-cbc \ 291 | -e \ 292 | -in "${BACKUP_DIRECTORY}/${DATA_ARCHIVE_FILENAME}" \ 293 | -out "${BACKUP_DIRECTORY}/${DATA_ENCRYPTED_FILENAME}" \ 294 | -pass "file:${BACKUP_DIRECTORY}/${SYMMETRIC_KEY_FILENAME}" \ 295 | -salt 296 | 297 | # Delete the unencrypted version of the data again 298 | echo " * Deleting unencrypted data ..." 299 | rm -f "${BACKUP_DIRECTORY}/${DATA_ARCHIVE_FILENAME}" 300 | 301 | # Asymmetrically encrypt the symmetric encryption key 302 | echo " * Asymmetrically encrypting symmetric key ..." 303 | touch "${BACKUP_DIRECTORY}/${SYMMETRIC_KEY_ENCRYPTED_FILENAME}" 304 | chmod 0644 "${BACKUP_DIRECTORY}/${SYMMETRIC_KEY_ENCRYPTED_FILENAME}" 305 | openssl pkeyutl \ 306 | -encrypt \ 307 | -in "${BACKUP_DIRECTORY}/${SYMMETRIC_KEY_FILENAME}" \ 308 | -out "${BACKUP_DIRECTORY}/${SYMMETRIC_KEY_ENCRYPTED_FILENAME}" \ 309 | -inkey "${CONTAINER_DIRECTORY}/${ASYMMETRIC_PUBLIC_KEY_FILENAME}" \ 310 | -keyform PEM \ 311 | -pubin 312 | 313 | # Delete the unencrypted version of the symmetric encryption key again 314 | echo " * Deleting unencrypted symmetric key ..." 315 | rm -f "${BACKUP_DIRECTORY}/${SYMMETRIC_KEY_FILENAME}" 316 | 317 | # Add an exemplary script that shows how to decrypt the symmetric key and the data 318 | echo " * Adding manual for decryption" 319 | decryption="openssl pkeyutl -decrypt -in \"symmetric-key.bin.crypt\"" 320 | decryption+=" -out \"symmetric-key.bin\" -inkey \"asymmetric-key.private.pem\"" 321 | decryption+=" -keyform PEM" 322 | decryption+="\n" 323 | decryption+="openssl enc -aes-256-cbc -d -in \"data.tar.gz.crypt\"" 324 | decryption+=" -out \"data.tar.gz\" -pass \"file:symmetric-key.bin\"" 325 | echo -e $decryption > "${BACKUP_DIRECTORY}/${DECRYPTION_SAMPLE_SCRIPT_FILENAME}" 326 | fi 327 | 328 | # Pack the results into a single file 329 | echo " * Packing results ..." 330 | touch "${CONTAINER_DIRECTORY}/${BACKUP_FILENAME}" 331 | chmod 0644 "${CONTAINER_DIRECTORY}/${BACKUP_FILENAME}" 332 | tar \ 333 | --create \ 334 | --file="${CONTAINER_DIRECTORY}/${BACKUP_FILENAME}" \ 335 | --directory="${BACKUP_DIRECTORY}" \ 336 | . 337 | 338 | # Remove the folder for the current backup that has now been packed 339 | echo " * Deleting temporary directory ..." 340 | rm -rf "$BACKUP_DIRECTORY" 341 | 342 | # Announce that the backup process has finished 343 | echo "Done" 344 | -------------------------------------------------------------------------------- /backups/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything except this file itself 2 | * 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "php": ">=5.6.0", 4 | "delight-im/foundation-core": "^4.15" 5 | }, 6 | "autoload": { 7 | "psr-4": { 8 | "App\\": "app/" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "9cb5638e108cff706b9105be13711414", 8 | "packages": [ 9 | { 10 | "name": "delight-im/auth", 11 | "version": "v8.2.1", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/delight-im/PHP-Auth.git", 15 | "reference": "157a7095b0a4e12246923449e6083212438fc071" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/delight-im/PHP-Auth/zipball/157a7095b0a4e12246923449e6083212438fc071", 20 | "reference": "157a7095b0a4e12246923449e6083212438fc071", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "delight-im/base64": "^1.0", 25 | "delight-im/cookie": "^3.1", 26 | "delight-im/db": "^1.3", 27 | "ext-openssl": "*", 28 | "php": ">=5.6.0" 29 | }, 30 | "type": "library", 31 | "autoload": { 32 | "psr-4": { 33 | "Delight\\Auth\\": "src/" 34 | } 35 | }, 36 | "notification-url": "https://packagist.org/downloads/", 37 | "license": [ 38 | "MIT" 39 | ], 40 | "description": "Authentication for PHP. Simple, lightweight and secure.", 41 | "homepage": "https://github.com/delight-im/PHP-Auth", 42 | "keywords": [ 43 | "Authentication", 44 | "auth", 45 | "login", 46 | "security" 47 | ], 48 | "time": "2020-05-06T20:36:45+00:00" 49 | }, 50 | { 51 | "name": "delight-im/base64", 52 | "version": "v1.0.0", 53 | "source": { 54 | "type": "git", 55 | "url": "https://github.com/delight-im/PHP-Base64.git", 56 | "reference": "687b2a49f663e162030a8d27b32838bbe7f91c78" 57 | }, 58 | "dist": { 59 | "type": "zip", 60 | "url": "https://api.github.com/repos/delight-im/PHP-Base64/zipball/687b2a49f663e162030a8d27b32838bbe7f91c78", 61 | "reference": "687b2a49f663e162030a8d27b32838bbe7f91c78", 62 | "shasum": "" 63 | }, 64 | "require": { 65 | "php": ">=5.3.0" 66 | }, 67 | "type": "library", 68 | "autoload": { 69 | "psr-4": { 70 | "Delight\\Base64\\": "src/" 71 | } 72 | }, 73 | "notification-url": "https://packagist.org/downloads/", 74 | "license": [ 75 | "MIT" 76 | ], 77 | "description": "Simple and convenient Base64 encoding and decoding for PHP", 78 | "homepage": "https://github.com/delight-im/PHP-Base64", 79 | "keywords": [ 80 | "URL-safe", 81 | "base-64", 82 | "base64", 83 | "decode", 84 | "decoding", 85 | "encode", 86 | "encoding", 87 | "url" 88 | ], 89 | "time": "2017-07-24T18:59:51+00:00" 90 | }, 91 | { 92 | "name": "delight-im/cookie", 93 | "version": "v3.4.0", 94 | "source": { 95 | "type": "git", 96 | "url": "https://github.com/delight-im/PHP-Cookie.git", 97 | "reference": "67065d34272377d63bab0bd58f984f9b228c803f" 98 | }, 99 | "dist": { 100 | "type": "zip", 101 | "url": "https://api.github.com/repos/delight-im/PHP-Cookie/zipball/67065d34272377d63bab0bd58f984f9b228c803f", 102 | "reference": "67065d34272377d63bab0bd58f984f9b228c803f", 103 | "shasum": "" 104 | }, 105 | "require": { 106 | "delight-im/http": "^2.0", 107 | "php": ">=5.4.0" 108 | }, 109 | "type": "library", 110 | "autoload": { 111 | "psr-4": { 112 | "Delight\\Cookie\\": "src/" 113 | } 114 | }, 115 | "notification-url": "https://packagist.org/downloads/", 116 | "license": [ 117 | "MIT" 118 | ], 119 | "description": "Modern cookie management for PHP", 120 | "homepage": "https://github.com/delight-im/PHP-Cookie", 121 | "keywords": [ 122 | "cookie", 123 | "cookies", 124 | "csrf", 125 | "http", 126 | "same-site", 127 | "samesite", 128 | "xss" 129 | ], 130 | "time": "2020-04-16T11:01:26+00:00" 131 | }, 132 | { 133 | "name": "delight-im/db", 134 | "version": "v1.3.1", 135 | "source": { 136 | "type": "git", 137 | "url": "https://github.com/delight-im/PHP-DB.git", 138 | "reference": "7d6f4c7a7e8ba6a297bfc30d65702479fd0b5f7c" 139 | }, 140 | "dist": { 141 | "type": "zip", 142 | "url": "https://api.github.com/repos/delight-im/PHP-DB/zipball/7d6f4c7a7e8ba6a297bfc30d65702479fd0b5f7c", 143 | "reference": "7d6f4c7a7e8ba6a297bfc30d65702479fd0b5f7c", 144 | "shasum": "" 145 | }, 146 | "require": { 147 | "ext-pdo": "*", 148 | "php": ">=5.6.0" 149 | }, 150 | "type": "library", 151 | "autoload": { 152 | "psr-4": { 153 | "Delight\\Db\\": "src/" 154 | } 155 | }, 156 | "notification-url": "https://packagist.org/downloads/", 157 | "license": [ 158 | "MIT" 159 | ], 160 | "description": "Safe and convenient SQL database access in a driver-agnostic way", 161 | "homepage": "https://github.com/delight-im/PHP-DB", 162 | "keywords": [ 163 | "database", 164 | "mysql", 165 | "pdo", 166 | "pgsql", 167 | "postgresql", 168 | "sql", 169 | "sqlite" 170 | ], 171 | "time": "2020-02-21T10:46:03+00:00" 172 | }, 173 | { 174 | "name": "delight-im/file-upload", 175 | "version": "v1.2.0", 176 | "source": { 177 | "type": "git", 178 | "url": "https://github.com/delight-im/PHP-FileUpload.git", 179 | "reference": "a793f4b9d6d2fc4ce57487c324a1547cce942cb6" 180 | }, 181 | "dist": { 182 | "type": "zip", 183 | "url": "https://api.github.com/repos/delight-im/PHP-FileUpload/zipball/a793f4b9d6d2fc4ce57487c324a1547cce942cb6", 184 | "reference": "a793f4b9d6d2fc4ce57487c324a1547cce942cb6", 185 | "shasum": "" 186 | }, 187 | "require": { 188 | "php": ">=5.6.0" 189 | }, 190 | "type": "library", 191 | "autoload": { 192 | "psr-4": { 193 | "Delight\\FileUpload\\": "src/" 194 | } 195 | }, 196 | "notification-url": "https://packagist.org/downloads/", 197 | "license": [ 198 | "MIT" 199 | ], 200 | "description": "Simple and convenient file uploads — secure by default", 201 | "homepage": "https://github.com/delight-im/PHP-FileUpload", 202 | "keywords": [ 203 | "file", 204 | "files", 205 | "form", 206 | "input", 207 | "upload", 208 | "validation" 209 | ], 210 | "time": "2018-03-13T18:36:56+00:00" 211 | }, 212 | { 213 | "name": "delight-im/foundation-core", 214 | "version": "v4.15.0", 215 | "source": { 216 | "type": "git", 217 | "url": "https://github.com/delight-im/PHP-Foundation-Core.git", 218 | "reference": "f39ff133b94ed433bbb10c27a8e420c8ba815cf9" 219 | }, 220 | "dist": { 221 | "type": "zip", 222 | "url": "https://api.github.com/repos/delight-im/PHP-Foundation-Core/zipball/f39ff133b94ed433bbb10c27a8e420c8ba815cf9", 223 | "reference": "f39ff133b94ed433bbb10c27a8e420c8ba815cf9", 224 | "shasum": "" 225 | }, 226 | "require": { 227 | "delight-im/auth": "^7.6 || ^8.0", 228 | "delight-im/cookie": "^3.1", 229 | "delight-im/db": "^1.2", 230 | "delight-im/file-upload": "^1.2", 231 | "delight-im/i18n": "^1.0", 232 | "delight-im/ids": "^1.0", 233 | "delight-im/router": "^3.1.1", 234 | "delight-im/str": "^2.4", 235 | "ext-mbstring": "*", 236 | "filp/whoops": "^2.1", 237 | "php": ">=5.6.0", 238 | "swiftmailer/swiftmailer": "^5.4", 239 | "twig/twig": "^1.35", 240 | "vlucas/phpdotenv": "^2.4" 241 | }, 242 | "type": "library", 243 | "autoload": { 244 | "psr-4": { 245 | "Delight\\Foundation\\": "src/" 246 | } 247 | }, 248 | "notification-url": "https://packagist.org/downloads/", 249 | "license": [ 250 | "MIT" 251 | ], 252 | "description": "Core of 'PHP-Foundation'", 253 | "homepage": "https://github.com/delight-im/PHP-Foundation-Core", 254 | "keywords": [ 255 | "bootstrap", 256 | "framework" 257 | ], 258 | "time": "2020-07-05T13:44:47+00:00" 259 | }, 260 | { 261 | "name": "delight-im/http", 262 | "version": "v2.0.0", 263 | "source": { 264 | "type": "git", 265 | "url": "https://github.com/delight-im/PHP-HTTP.git", 266 | "reference": "0a19a72a7eac8b1301aa972fb20cff494ac43e09" 267 | }, 268 | "dist": { 269 | "type": "zip", 270 | "url": "https://api.github.com/repos/delight-im/PHP-HTTP/zipball/0a19a72a7eac8b1301aa972fb20cff494ac43e09", 271 | "reference": "0a19a72a7eac8b1301aa972fb20cff494ac43e09", 272 | "shasum": "" 273 | }, 274 | "require": { 275 | "php": ">=5.3.0" 276 | }, 277 | "type": "library", 278 | "autoload": { 279 | "psr-4": { 280 | "Delight\\Http\\": "src/" 281 | } 282 | }, 283 | "notification-url": "https://packagist.org/downloads/", 284 | "license": [ 285 | "MIT" 286 | ], 287 | "description": "Hypertext Transfer Protocol (HTTP) utilities for PHP", 288 | "homepage": "https://github.com/delight-im/PHP-HTTP", 289 | "keywords": [ 290 | "headers", 291 | "http", 292 | "https" 293 | ], 294 | "time": "2016-07-21T15:05:01+00:00" 295 | }, 296 | { 297 | "name": "delight-im/i18n", 298 | "version": "v1.0.2", 299 | "source": { 300 | "type": "git", 301 | "url": "https://github.com/delight-im/PHP-I18N.git", 302 | "reference": "cc50010bc6df3dbcd39a72d41683b4bc67cd35a4" 303 | }, 304 | "dist": { 305 | "type": "zip", 306 | "url": "https://api.github.com/repos/delight-im/PHP-I18N/zipball/cc50010bc6df3dbcd39a72d41683b4bc67cd35a4", 307 | "reference": "cc50010bc6df3dbcd39a72d41683b4bc67cd35a4", 308 | "shasum": "" 309 | }, 310 | "require": { 311 | "ext-gettext": "*", 312 | "ext-intl": "*", 313 | "php": ">=5.6.0" 314 | }, 315 | "type": "library", 316 | "autoload": { 317 | "psr-4": { 318 | "Delight\\I18n\\": "src/" 319 | } 320 | }, 321 | "notification-url": "https://packagist.org/downloads/", 322 | "license": [ 323 | "MIT" 324 | ], 325 | "description": "Internationalization and localization for PHP", 326 | "homepage": "https://github.com/delight-im/PHP-I18N", 327 | "keywords": [ 328 | "gettext", 329 | "i18n", 330 | "internationalization", 331 | "intl", 332 | "l10n", 333 | "language", 334 | "locale", 335 | "localization", 336 | "translation" 337 | ], 338 | "time": "2019-06-14T18:54:26+00:00" 339 | }, 340 | { 341 | "name": "delight-im/ids", 342 | "version": "v1.0.1", 343 | "source": { 344 | "type": "git", 345 | "url": "https://github.com/delight-im/PHP-IDs.git", 346 | "reference": "5f95867c5f555fc829a6b7fe61f74b821f51fb4e" 347 | }, 348 | "dist": { 349 | "type": "zip", 350 | "url": "https://api.github.com/repos/delight-im/PHP-IDs/zipball/5f95867c5f555fc829a6b7fe61f74b821f51fb4e", 351 | "reference": "5f95867c5f555fc829a6b7fe61f74b821f51fb4e", 352 | "shasum": "" 353 | }, 354 | "require": { 355 | "ext-gmp": "*", 356 | "jenssegers/optimus": "^0.2.1", 357 | "php": ">=5.6.0" 358 | }, 359 | "require-dev": { 360 | "phpseclib/phpseclib": "^2.0" 361 | }, 362 | "type": "library", 363 | "autoload": { 364 | "psr-4": { 365 | "Delight\\Ids\\": "src/" 366 | } 367 | }, 368 | "notification-url": "https://packagist.org/downloads/", 369 | "license": [ 370 | "MIT" 371 | ], 372 | "description": "Short, obfuscated and efficient IDs for PHP", 373 | "homepage": "https://github.com/delight-im/PHP-IDs", 374 | "keywords": [ 375 | "decode", 376 | "encode", 377 | "hash", 378 | "hashid", 379 | "hashids", 380 | "id", 381 | "ids", 382 | "obfuscate", 383 | "obfuscation", 384 | "shorten" 385 | ], 386 | "time": "2016-09-10T15:18:16+00:00" 387 | }, 388 | { 389 | "name": "delight-im/router", 390 | "version": "v3.1.1", 391 | "source": { 392 | "type": "git", 393 | "url": "https://github.com/delight-im/PHP-Router.git", 394 | "reference": "2dcb65715cfc69b4695f1a460317fc99d1d771d2" 395 | }, 396 | "dist": { 397 | "type": "zip", 398 | "url": "https://api.github.com/repos/delight-im/PHP-Router/zipball/2dcb65715cfc69b4695f1a460317fc99d1d771d2", 399 | "reference": "2dcb65715cfc69b4695f1a460317fc99d1d771d2", 400 | "shasum": "" 401 | }, 402 | "require": { 403 | "php": ">=5.6.0" 404 | }, 405 | "type": "library", 406 | "autoload": { 407 | "psr-4": { 408 | "Delight\\Router\\": "src/" 409 | } 410 | }, 411 | "notification-url": "https://packagist.org/downloads/", 412 | "license": [ 413 | "MIT" 414 | ], 415 | "description": "Router for PHP. Simple, lightweight and convenient.", 416 | "homepage": "https://github.com/delight-im/PHP-Router", 417 | "keywords": [ 418 | "http", 419 | "route", 420 | "router", 421 | "routing" 422 | ], 423 | "time": "2019-06-14T12:33:23+00:00" 424 | }, 425 | { 426 | "name": "delight-im/str", 427 | "version": "v2.4.0", 428 | "source": { 429 | "type": "git", 430 | "url": "https://github.com/delight-im/PHP-Str.git", 431 | "reference": "ea3e40132e9d4ce27da337dae6286f2478b15f56" 432 | }, 433 | "dist": { 434 | "type": "zip", 435 | "url": "https://api.github.com/repos/delight-im/PHP-Str/zipball/ea3e40132e9d4ce27da337dae6286f2478b15f56", 436 | "reference": "ea3e40132e9d4ce27da337dae6286f2478b15f56", 437 | "shasum": "" 438 | }, 439 | "require": { 440 | "ext-mbstring": "*", 441 | "php": ">=5.3.0" 442 | }, 443 | "type": "library", 444 | "autoload": { 445 | "psr-4": { 446 | "Delight\\Str\\": "src/" 447 | } 448 | }, 449 | "notification-url": "https://packagist.org/downloads/", 450 | "license": [ 451 | "MIT" 452 | ], 453 | "description": "Convenient object-oriented operations on strings", 454 | "homepage": "https://github.com/delight-im/PHP-Str", 455 | "keywords": [ 456 | "string", 457 | "strings" 458 | ], 459 | "time": "2016-07-27T22:19:41+00:00" 460 | }, 461 | { 462 | "name": "filp/whoops", 463 | "version": "2.7.1", 464 | "source": { 465 | "type": "git", 466 | "url": "https://github.com/filp/whoops.git", 467 | "reference": "fff6f1e4f36be0e0d0b84d66b413d9dcb0c49130" 468 | }, 469 | "dist": { 470 | "type": "zip", 471 | "url": "https://api.github.com/repos/filp/whoops/zipball/fff6f1e4f36be0e0d0b84d66b413d9dcb0c49130", 472 | "reference": "fff6f1e4f36be0e0d0b84d66b413d9dcb0c49130", 473 | "shasum": "" 474 | }, 475 | "require": { 476 | "php": "^5.5.9 || ^7.0", 477 | "psr/log": "^1.0.1" 478 | }, 479 | "require-dev": { 480 | "mockery/mockery": "^0.9 || ^1.0", 481 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0", 482 | "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" 483 | }, 484 | "suggest": { 485 | "symfony/var-dumper": "Pretty print complex values better with var-dumper available", 486 | "whoops/soap": "Formats errors as SOAP responses" 487 | }, 488 | "type": "library", 489 | "extra": { 490 | "branch-alias": { 491 | "dev-master": "2.6-dev" 492 | } 493 | }, 494 | "autoload": { 495 | "psr-4": { 496 | "Whoops\\": "src/Whoops/" 497 | } 498 | }, 499 | "notification-url": "https://packagist.org/downloads/", 500 | "license": [ 501 | "MIT" 502 | ], 503 | "authors": [ 504 | { 505 | "name": "Filipe Dobreira", 506 | "homepage": "https://github.com/filp", 507 | "role": "Developer" 508 | } 509 | ], 510 | "description": "php error handling for cool kids", 511 | "homepage": "https://filp.github.io/whoops/", 512 | "keywords": [ 513 | "error", 514 | "exception", 515 | "handling", 516 | "library", 517 | "throwable", 518 | "whoops" 519 | ], 520 | "time": "2020-01-15T10:00:00+00:00" 521 | }, 522 | { 523 | "name": "jenssegers/optimus", 524 | "version": "v0.2.3", 525 | "source": { 526 | "type": "git", 527 | "url": "https://github.com/jenssegers/optimus.git", 528 | "reference": "9bb0fcbaa3b7a6a0eb3e6c07e672ce4de42600af" 529 | }, 530 | "dist": { 531 | "type": "zip", 532 | "url": "https://api.github.com/repos/jenssegers/optimus/zipball/9bb0fcbaa3b7a6a0eb3e6c07e672ce4de42600af", 533 | "reference": "9bb0fcbaa3b7a6a0eb3e6c07e672ce4de42600af", 534 | "shasum": "" 535 | }, 536 | "require": { 537 | "php": ">=5.4.0" 538 | }, 539 | "require-dev": { 540 | "kherge/box": "^2.7", 541 | "phpseclib/phpseclib": "^2.0", 542 | "phpunit/phpunit": "^4.0|^5.0", 543 | "satooshi/php-coveralls": "^1.0", 544 | "symfony/console": "^2.6|^3.0" 545 | }, 546 | "suggest": { 547 | "ext-gmp": "Required for 32bit systems" 548 | }, 549 | "bin": [ 550 | "optimus" 551 | ], 552 | "type": "library", 553 | "autoload": { 554 | "psr-4": { 555 | "Jenssegers\\Optimus\\": "src" 556 | } 557 | }, 558 | "notification-url": "https://packagist.org/downloads/", 559 | "license": [ 560 | "MIT" 561 | ], 562 | "authors": [ 563 | { 564 | "name": "Jens Segers", 565 | "homepage": "https://jenssegers.com" 566 | } 567 | ], 568 | "description": "Id obfuscation based on Knuth's integer hash method", 569 | "homepage": "https://github.com/jenssegers/optimus", 570 | "keywords": [ 571 | "hashids", 572 | "id obfuscation", 573 | "ids", 574 | "obfuscation", 575 | "optimus" 576 | ], 577 | "time": "2017-04-21T10:14:58+00:00" 578 | }, 579 | { 580 | "name": "psr/log", 581 | "version": "1.1.3", 582 | "source": { 583 | "type": "git", 584 | "url": "https://github.com/php-fig/log.git", 585 | "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" 586 | }, 587 | "dist": { 588 | "type": "zip", 589 | "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", 590 | "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", 591 | "shasum": "" 592 | }, 593 | "require": { 594 | "php": ">=5.3.0" 595 | }, 596 | "type": "library", 597 | "extra": { 598 | "branch-alias": { 599 | "dev-master": "1.1.x-dev" 600 | } 601 | }, 602 | "autoload": { 603 | "psr-4": { 604 | "Psr\\Log\\": "Psr/Log/" 605 | } 606 | }, 607 | "notification-url": "https://packagist.org/downloads/", 608 | "license": [ 609 | "MIT" 610 | ], 611 | "authors": [ 612 | { 613 | "name": "PHP-FIG", 614 | "homepage": "http://www.php-fig.org/" 615 | } 616 | ], 617 | "description": "Common interface for logging libraries", 618 | "homepage": "https://github.com/php-fig/log", 619 | "keywords": [ 620 | "log", 621 | "psr", 622 | "psr-3" 623 | ], 624 | "time": "2020-03-23T09:12:05+00:00" 625 | }, 626 | { 627 | "name": "swiftmailer/swiftmailer", 628 | "version": "v5.4.12", 629 | "source": { 630 | "type": "git", 631 | "url": "https://github.com/swiftmailer/swiftmailer.git", 632 | "reference": "181b89f18a90f8925ef805f950d47a7190e9b950" 633 | }, 634 | "dist": { 635 | "type": "zip", 636 | "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/181b89f18a90f8925ef805f950d47a7190e9b950", 637 | "reference": "181b89f18a90f8925ef805f950d47a7190e9b950", 638 | "shasum": "" 639 | }, 640 | "require": { 641 | "php": ">=5.3.3" 642 | }, 643 | "require-dev": { 644 | "mockery/mockery": "~0.9.1", 645 | "symfony/phpunit-bridge": "~3.2" 646 | }, 647 | "type": "library", 648 | "extra": { 649 | "branch-alias": { 650 | "dev-master": "5.4-dev" 651 | } 652 | }, 653 | "autoload": { 654 | "files": [ 655 | "lib/swift_required.php" 656 | ] 657 | }, 658 | "notification-url": "https://packagist.org/downloads/", 659 | "license": [ 660 | "MIT" 661 | ], 662 | "authors": [ 663 | { 664 | "name": "Chris Corbyn" 665 | }, 666 | { 667 | "name": "Fabien Potencier", 668 | "email": "fabien@symfony.com" 669 | } 670 | ], 671 | "description": "Swiftmailer, free feature-rich PHP mailer", 672 | "homepage": "https://swiftmailer.symfony.com", 673 | "keywords": [ 674 | "email", 675 | "mail", 676 | "mailer" 677 | ], 678 | "time": "2018-07-31T09:26:32+00:00" 679 | }, 680 | { 681 | "name": "symfony/polyfill-ctype", 682 | "version": "v1.15.0", 683 | "source": { 684 | "type": "git", 685 | "url": "https://github.com/symfony/polyfill-ctype.git", 686 | "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14" 687 | }, 688 | "dist": { 689 | "type": "zip", 690 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/4719fa9c18b0464d399f1a63bf624b42b6fa8d14", 691 | "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14", 692 | "shasum": "" 693 | }, 694 | "require": { 695 | "php": ">=5.3.3" 696 | }, 697 | "suggest": { 698 | "ext-ctype": "For best performance" 699 | }, 700 | "type": "library", 701 | "extra": { 702 | "branch-alias": { 703 | "dev-master": "1.15-dev" 704 | } 705 | }, 706 | "autoload": { 707 | "psr-4": { 708 | "Symfony\\Polyfill\\Ctype\\": "" 709 | }, 710 | "files": [ 711 | "bootstrap.php" 712 | ] 713 | }, 714 | "notification-url": "https://packagist.org/downloads/", 715 | "license": [ 716 | "MIT" 717 | ], 718 | "authors": [ 719 | { 720 | "name": "Gert de Pagter", 721 | "email": "BackEndTea@gmail.com" 722 | }, 723 | { 724 | "name": "Symfony Community", 725 | "homepage": "https://symfony.com/contributors" 726 | } 727 | ], 728 | "description": "Symfony polyfill for ctype functions", 729 | "homepage": "https://symfony.com", 730 | "keywords": [ 731 | "compatibility", 732 | "ctype", 733 | "polyfill", 734 | "portable" 735 | ], 736 | "time": "2020-02-27T09:26:54+00:00" 737 | }, 738 | { 739 | "name": "twig/twig", 740 | "version": "v1.42.5", 741 | "source": { 742 | "type": "git", 743 | "url": "https://github.com/twigphp/Twig.git", 744 | "reference": "87b2ea9d8f6fd014d0621ca089bb1b3769ea3f8e" 745 | }, 746 | "dist": { 747 | "type": "zip", 748 | "url": "https://api.github.com/repos/twigphp/Twig/zipball/87b2ea9d8f6fd014d0621ca089bb1b3769ea3f8e", 749 | "reference": "87b2ea9d8f6fd014d0621ca089bb1b3769ea3f8e", 750 | "shasum": "" 751 | }, 752 | "require": { 753 | "php": ">=5.5.0", 754 | "symfony/polyfill-ctype": "^1.8" 755 | }, 756 | "require-dev": { 757 | "psr/container": "^1.0", 758 | "symfony/phpunit-bridge": "^4.4|^5.0" 759 | }, 760 | "type": "library", 761 | "extra": { 762 | "branch-alias": { 763 | "dev-master": "1.42-dev" 764 | } 765 | }, 766 | "autoload": { 767 | "psr-0": { 768 | "Twig_": "lib/" 769 | }, 770 | "psr-4": { 771 | "Twig\\": "src/" 772 | } 773 | }, 774 | "notification-url": "https://packagist.org/downloads/", 775 | "license": [ 776 | "BSD-3-Clause" 777 | ], 778 | "authors": [ 779 | { 780 | "name": "Fabien Potencier", 781 | "email": "fabien@symfony.com", 782 | "homepage": "http://fabien.potencier.org", 783 | "role": "Lead Developer" 784 | }, 785 | { 786 | "name": "Twig Team", 787 | "role": "Contributors" 788 | }, 789 | { 790 | "name": "Armin Ronacher", 791 | "email": "armin.ronacher@active-4.com", 792 | "role": "Project Founder" 793 | } 794 | ], 795 | "description": "Twig, the flexible, fast, and secure template language for PHP", 796 | "homepage": "https://twig.symfony.com", 797 | "keywords": [ 798 | "templating" 799 | ], 800 | "time": "2020-02-11T05:59:23+00:00" 801 | }, 802 | { 803 | "name": "vlucas/phpdotenv", 804 | "version": "v2.6.3", 805 | "source": { 806 | "type": "git", 807 | "url": "https://github.com/vlucas/phpdotenv.git", 808 | "reference": "df4c4d08a639be4ef5d6d1322868f9e477553679" 809 | }, 810 | "dist": { 811 | "type": "zip", 812 | "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/df4c4d08a639be4ef5d6d1322868f9e477553679", 813 | "reference": "df4c4d08a639be4ef5d6d1322868f9e477553679", 814 | "shasum": "" 815 | }, 816 | "require": { 817 | "php": ">=5.3.9", 818 | "symfony/polyfill-ctype": "^1.9" 819 | }, 820 | "require-dev": { 821 | "ext-filter": "*", 822 | "ext-pcre": "*", 823 | "phpunit/phpunit": "^4.8.35 || ^5.0" 824 | }, 825 | "suggest": { 826 | "ext-filter": "Required to use the boolean validator.", 827 | "ext-pcre": "Required to use most of the library." 828 | }, 829 | "type": "library", 830 | "extra": { 831 | "branch-alias": { 832 | "dev-master": "2.6-dev" 833 | } 834 | }, 835 | "autoload": { 836 | "psr-4": { 837 | "Dotenv\\": "src/" 838 | } 839 | }, 840 | "notification-url": "https://packagist.org/downloads/", 841 | "license": [ 842 | "BSD-3-Clause" 843 | ], 844 | "authors": [ 845 | { 846 | "name": "Vance Lucas", 847 | "email": "vance@vancelucas.com", 848 | "homepage": "http://www.vancelucas.com" 849 | } 850 | ], 851 | "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", 852 | "keywords": [ 853 | "dotenv", 854 | "env", 855 | "environment" 856 | ], 857 | "time": "2020-04-12T15:11:38+00:00" 858 | } 859 | ], 860 | "packages-dev": [], 861 | "aliases": [], 862 | "minimum-stability": "stable", 863 | "stability-flags": [], 864 | "prefer-stable": false, 865 | "prefer-lowest": false, 866 | "platform": { 867 | "php": ">=5.6.0" 868 | }, 869 | "platform-dev": [] 870 | } 871 | -------------------------------------------------------------------------------- /config/.env.example: -------------------------------------------------------------------------------- 1 | ########## BEGIN FRAMEWORK CONFIGURATION (DON'T CHANGE THE STRUCTURE HERE -- PLEASE SET ALL REQUIRED VALUES) ########## 2 | 3 | # Whether the application should be in debug mode (`1`) or in production mode (`0`) 4 | APP_DEBUG=1 5 | # The public HTTP(S) URL pointing at the application's root directory 6 | APP_PUBLIC_URL=http://localhost/ 7 | # The character set or encoding to use throughout the application (usually 'UTF-8') 8 | APP_CHARSET=UTF-8 9 | # The default timezone for the application (see 'http://php.net/manual/de/timezones.php' for possible values) 10 | APP_DEFAULT_TIMEZONE=UTC 11 | 12 | # The name of the database driver (e.g. `mysql`, `pgsql` or `sqlite`) 13 | DB_DRIVER=mysql 14 | DB_HOST=localhost 15 | DB_PORT=3306 16 | DB_FILE_PATH= 17 | DB_IN_MEMORY=0 18 | DB_NAME=blog 19 | DB_USERNAME=root 20 | DB_PASSWORD=monkey 21 | DB_CHARSET=utf8mb4 22 | DB_PREFIX= 23 | 24 | # The transport mechanism that is used to send mails (either `smtp`, `sendmail` or `php`) 25 | MAIL_TRANSPORT=php 26 | MAIL_HOST=smtp.example.com 27 | MAIL_PORT=587 28 | MAIL_USERNAME=user@example.com 29 | MAIL_PASSWORD=monkey 30 | MAIL_TLS=0 31 | 32 | # The (comma-separated) list of supported locales (see 'https://github.com/delight-im/PHP-I18N/blob/master/src/Codes.php' for possible values) 33 | I18N_SUPPORTED_LOCALES= 34 | I18N_SESSION_FIELD= 35 | I18N_COOKIE_NAME= 36 | I18N_COOKIE_LIFETIME=31556952 37 | 38 | # The settings of the encoder/decoder that can be used to obfuscate IDs 39 | SECURITY_IDS_ALPHABET=GZwBHpfWybgQ5d_2mM-jh84K69tqYknx7LN3zvDrcSJVRPXsCFT 40 | SECURITY_IDS_PRIME=1125812041 41 | SECURITY_IDS_INVERSE=348986105 42 | SECURITY_IDS_RANDOM=998048641 43 | 44 | ########## END FRAMEWORK CONFIGURATION ########## 45 | 46 | ########## BEGIN APP CONFIGURATION (PLEASE PUT YOUR OWN SETTINGS HERE (IF ANY)) ########## 47 | 48 | # MY_KEY=my-value 49 | 50 | ########## END APP CONFIGURATION ########## 51 | -------------------------------------------------------------------------------- /config/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore the private version of the configuration 2 | # Keep a public and up-to-date template in '.env.example' 3 | .env 4 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### PHP-Foundation (https://github.com/delight-im/PHP-Foundation) 4 | ### Copyright (c) delight.im (https://www.delight.im/) 5 | ### Licensed under the MIT License (https://opensource.org/licenses/MIT) 6 | 7 | # Switch to the directory where the current script is located 8 | cd "${BASH_SOURCE%/*}" || exit 1 9 | 10 | # If no command-line arguments (or only incomplete ones) have been provided 11 | if [ "$1" == "" ] || [ "$2" == "" ] || [ "$3" == "" ] || [ "$4" == "" ]; then 12 | # Explain command 13 | echo "Usage:" 14 | echo " deploy.sh " 15 | echo " : hostname of the target server, e.g. 'example.com'" 16 | echo " : SSH port at the target server, e.g. '22'" 17 | echo " : user for authentication at the target server, e.g. 'john-doe'" 18 | echo " : application directory on the target server (without trailing slash), e.g. '/var/www/example.com'" 19 | 20 | # If no command-line arguments have been specified at all 21 | if [ "$1" == "" ] && [ "$2" == "" ] && [ "$3" == "" ] && [ "$4" == "" ]; then 22 | # Return with success 23 | exit 0 24 | # If the command-line arguments have just been incomplete 25 | else 26 | # Return with failure 27 | exit 2 28 | fi 29 | fi 30 | 31 | # BEGIN CONSTANTS 32 | 33 | # Hostname of the target server as received with the command-line arguments 34 | TARGET_SSH_HOST=$1 35 | # SSH port at the target server as received with the command-line arguments 36 | TARGET_SSH_PORT=$2 37 | # User for authentication at the target server as received with the command-line arguments 38 | TARGET_SSH_USER=$3 39 | # Application directory on the target server (without trailing slash) as received with the command-line arguments 40 | TARGET_APPLICATION_PATH=$4 41 | # Unique name for the new deployment 42 | DEPLOYMENT_NAME="deployment-$(date -u +'%Y%m%dT%H%M%SZ')" 43 | # Filename used for archives of the new deployment 44 | DEPLOYMENT_ARCHIVE_FILENAME="$DEPLOYMENT_NAME.tar.gz" 45 | 46 | # END CONSTANTS 47 | 48 | # Introduce the deployment to the user and confirm the target host and directory 49 | echo "Deploying to '$TARGET_APPLICATION_PATH' on '$TARGET_SSH_HOST'" 50 | 51 | # Verify that the source directory is a valid project root by looking for some important files and directories 52 | if [ -d "app" ] && [ -f "index.php" ] && [ -d "public" ] && [ -d "vendor" ] && [ -d "views" ]; then 53 | echo " * Verified source directory ..." 54 | else 55 | echo " * Source directory could not be verified ..." 56 | exit 3 57 | fi 58 | 59 | # Create an archive of all files in the source directory that are to be transferred to the target host 60 | echo " * Packing files in source directory ..." 61 | echo " * Ignoring '.htaccess' file (environment-specific) ..." # [1] 62 | echo " * Ignoring 'backups' directory (environment-specific) ..." # [1] 63 | echo " * Ignoring 'backup.sh' file (protected) ..." # [1] 64 | echo " * Ignoring 'config' directory (environment-specific) ..." # [1] 65 | echo " * Ignoring 'deploy.sh' file (needless) ..." # [1] 66 | echo " * Ignoring 'storage/app' directory (environment-specific) ..." # [1] 67 | echo " * Ignoring 'storage/framework' directory (environment-specific) ..." # [1] 68 | touch "$DEPLOYMENT_ARCHIVE_FILENAME" 69 | tar \ 70 | --create \ 71 | --gzip \ 72 | --exclude "./$DEPLOYMENT_ARCHIVE_FILENAME" \ 73 | --exclude "./.git" \ 74 | --exclude "./.idea" \ 75 | --exclude "./.htaccess" \ 76 | --exclude "./backups" \ 77 | --exclude "./backup.sh" \ 78 | --exclude "./config" \ 79 | --exclude "./deploy.sh" \ 80 | --exclude "./storage/app" \ 81 | --exclude "./storage/framework" \ 82 | --file="$DEPLOYMENT_ARCHIVE_FILENAME" \ 83 | . # [1] 84 | 85 | # Transfer the generated archive to the target host and delete it from the source directory afterwards 86 | echo " * Moving packed files from source to target host ..." 87 | scp -q -P "$TARGET_SSH_PORT" "$DEPLOYMENT_ARCHIVE_FILENAME" "${TARGET_SSH_USER}@${TARGET_SSH_HOST}:$TARGET_APPLICATION_PATH" 88 | rm "$DEPLOYMENT_ARCHIVE_FILENAME" 89 | 90 | # Establish an SSH connection to the target host 91 | ssh -p "$TARGET_SSH_PORT" "${TARGET_SSH_USER}@${TARGET_SSH_HOST}" /bin/bash <<- EOF 92 | # Verify that the target directory exists, and, if found, switch to that directory 93 | if [ -d "$TARGET_APPLICATION_PATH" ]; then 94 | cd "$TARGET_APPLICATION_PATH" 95 | fi 96 | 97 | # Verify that the target directory is now the active working directory 98 | if [ "\$PWD" = "$TARGET_APPLICATION_PATH" ]; then 99 | echo " * Found target directory ..." 100 | else 101 | echo " * Target directory could not be found ..." 102 | exit 4 103 | fi 104 | 105 | # Verify that the target directory is a valid project root by looking for some important files and directories 106 | if [ -f ".htaccess" ] && [ -d "storage" ]; then 107 | echo " * Verified target directory ..." 108 | else 109 | echo " * Target directory could not be verified ..." 110 | exit 5 111 | fi 112 | 113 | # Enable maintenance mode on the site 114 | echo " * Enabling maintenance mode ..." 115 | sed -i 's/^\t# RewriteRule . maintenance.php \[END]/\tRewriteRule . maintenance.php [END]/m' .htaccess 116 | 117 | # Delete all directories and most files that new versions will subsequently be deployed for 118 | echo " * Cleaning up old files ..." 119 | find . \ 120 | -depth \ 121 | \! -path "./$DEPLOYMENT_ARCHIVE_FILENAME" \ 122 | \! -path './index.php' \ 123 | \! -path './maintenance.php' \ 124 | \! -path './.htaccess' \ 125 | \! -path './backups' \ 126 | \! -path './backups/*' \ 127 | \! -path './backup.sh' \ 128 | \! -path './config' \ 129 | \! -path './config/*' \ 130 | \! -path './deploy.sh' \ 131 | \! -path './storage' \ 132 | \! -path './storage/app' \ 133 | \! -path './storage/app/*' \ 134 | \! -path './storage/framework' \ 135 | \! -path './storage/framework/*' \ 136 | -delete # [1] 137 | 138 | # Extract the transferred archive in the target directory and delete the archive afterwards 139 | echo " * Unpacking files in target directory ..." 140 | tar --extract --gzip --overwrite --file="$DEPLOYMENT_ARCHIVE_FILENAME" 141 | rm "$DEPLOYMENT_ARCHIVE_FILENAME" 142 | 143 | # Disable maintenance mode on the site again 144 | echo " * Disabling maintenance mode ..." 145 | sed -i 's/^\tRewriteRule . maintenance.php \[END]/\t# RewriteRule . maintenance.php [END]/m' .htaccess 146 | 147 | # Announce that deployment has finished 148 | echo 'Done' 149 | EOF 150 | 151 | # [1] The entries in the set of ignored files should be kept consistent in all places 152 | -------------------------------------------------------------------------------- /i18n.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### PHP-I18N (https://github.com/delight-im/PHP-I18N) 4 | ### Copyright (c) delight.im (https://www.delight.im/) 5 | ### Licensed under the MIT License (https://opensource.org/licenses/MIT) 6 | 7 | set -eu 8 | 9 | # Switch to the directory where the current script is located 10 | cd "${BASH_SOURCE%/*}" || exit 1 11 | 12 | echo "Extracting and updating translations" 13 | 14 | LOCALE_CODE="${1:-}" 15 | LOCALE_PARENT_DIR="${2:-locale}" 16 | LOCALE_DOMAIN="${3:-messages}" 17 | GENERATE_FUZZY="${4:-fuzzy}" 18 | 19 | if [ ! -d "${LOCALE_PARENT_DIR}" ]; then 20 | echo " * Error: Target directory “${LOCALE_PARENT_DIR}” not found" 21 | exit 2 22 | fi 23 | 24 | if [ ! -w "${LOCALE_PARENT_DIR}" ]; then 25 | echo " * Error: Target directory “${LOCALE_PARENT_DIR}” not writable" 26 | exit 3 27 | fi 28 | 29 | if [ -z "${LOCALE_CODE}" ]; then 30 | echo " * Creating generic POT (Portable Object Template) file" 31 | fi 32 | 33 | find . -iname "*.php" -not -path "./vendor/*" | xargs xgettext --output="${LOCALE_DOMAIN}.pot" --output-dir="${LOCALE_PARENT_DIR}" --language=PHP --from-code=UTF-8 --force-po --no-location --no-wrap --sort-output --copyright-holder="" --keyword --keyword="_:1,1t" --keyword="_f:1" --keyword="_fe:1" --keyword="_p:1,2,3t" --keyword="_pf:1,2" --keyword="_pfe:1,2" --keyword="_c:1,2c,2t" --keyword="_m:1,1t" --flag="_f:1:php-format" --flag="_fe:1:no-php-format" --flag="_pf:1:php-format" --flag="_pfe:1:no-php-format" 34 | sed -i '/# SOME DESCRIPTIVE TITLE./d' "${LOCALE_PARENT_DIR}/${LOCALE_DOMAIN}.pot" 35 | sed -i '/# This file is put in the public domain./d' "${LOCALE_PARENT_DIR}/${LOCALE_DOMAIN}.pot" 36 | sed -i '/# FIRST AUTHOR , YEAR./d' "${LOCALE_PARENT_DIR}/${LOCALE_DOMAIN}.pot" 37 | sed -i '0,/#, fuzzy/{s/#, fuzzy//}' "${LOCALE_PARENT_DIR}/${LOCALE_DOMAIN}.pot" 38 | sed -i '/\"Project-Id-Version: PACKAGE VERSION\\n\"/d' "${LOCALE_PARENT_DIR}/${LOCALE_DOMAIN}.pot" 39 | sed -i '/\"Report-Msgid-Bugs-To: \\n\"/d' "${LOCALE_PARENT_DIR}/${LOCALE_DOMAIN}.pot" 40 | sed -i '/\"POT-Creation-Date: /d' "${LOCALE_PARENT_DIR}/${LOCALE_DOMAIN}.pot" 41 | sed -i '/\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"/d' "${LOCALE_PARENT_DIR}/${LOCALE_DOMAIN}.pot" 42 | sed -i '/\"Last-Translator: FULL NAME \\n\"/d' "${LOCALE_PARENT_DIR}/${LOCALE_DOMAIN}.pot" 43 | sed -i '/\"Language-Team: LANGUAGE \\n\"/d' "${LOCALE_PARENT_DIR}/${LOCALE_DOMAIN}.pot" 44 | sed -i '0,/\"Language: \\n\"/{s/\"Language: \\n\"/\"Language: xx\\n\"/}' "${LOCALE_PARENT_DIR}/${LOCALE_DOMAIN}.pot" 45 | sed -i '0,/\"Content-Type: text\/plain; charset=CHARSET\\n\"/{s/\"Content-Type: text\/plain; charset=CHARSET\\n\"/\"Content-Type: text\/plain; charset=UTF-8\\n\"/}' "${LOCALE_PARENT_DIR}/${LOCALE_DOMAIN}.pot" 46 | 47 | if [ ! -z "${LOCALE_CODE}" ]; then 48 | LOCALE_CONTENTS_DIR="${LOCALE_PARENT_DIR}/${LOCALE_CODE}/LC_MESSAGES" 49 | 50 | mkdir --parents "${LOCALE_CONTENTS_DIR}" 51 | 52 | echo " * Creating PO (Portable Object) file for “${LOCALE_CODE}”" 53 | 54 | if [ -f "${LOCALE_CONTENTS_DIR}/${LOCALE_DOMAIN}.po" ]; then 55 | msgmerge --update --backup=none --suffix=".bak" --previous --force-po --no-location $( [[ "$GENERATE_FUZZY" == "nofuzzy" ]] && printf %s '--no-fuzzy-matching' ) --no-wrap --sort-output "${LOCALE_CONTENTS_DIR}/${LOCALE_DOMAIN}.po" "${LOCALE_PARENT_DIR}/${LOCALE_DOMAIN}.pot" 56 | else 57 | msginit --input="${LOCALE_PARENT_DIR}/${LOCALE_DOMAIN}.pot" --output-file="${LOCALE_CONTENTS_DIR}/${LOCALE_DOMAIN}.po" --locale="${LOCALE_CODE}" --no-translator --no-wrap 58 | sed -i '/\"Project-Id-Version: /d' "${LOCALE_CONTENTS_DIR}/${LOCALE_DOMAIN}.po" 59 | sed -i '/\"Last-Translator: Automatically generated\\n\"/d' "${LOCALE_CONTENTS_DIR}/${LOCALE_DOMAIN}.po" 60 | sed -i '/\"Language-Team: none\\n\"/d' "${LOCALE_CONTENTS_DIR}/${LOCALE_DOMAIN}.po" 61 | fi 62 | 63 | echo " * Creating MO (Machine Object) file for “${LOCALE_CODE}”" 64 | 65 | msgfmt --output-file="${LOCALE_CONTENTS_DIR}/${LOCALE_DOMAIN}.mo" --check-format --check-domain "${LOCALE_CONTENTS_DIR}/${LOCALE_DOMAIN}.po" 66 | 67 | rm "${LOCALE_PARENT_DIR}/${LOCALE_DOMAIN}.pot" 68 | fi 69 | 70 | echo "Done" 71 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | overload(); 28 | } 29 | 30 | // in debug mode 31 | if (!isset($_ENV['APP_DEBUG']) || $_ENV['APP_DEBUG'] === '1') { 32 | // enable assertions 33 | \ini_set('assert.active', 1); 34 | @\ini_set('zend.assertions', 1); 35 | \ini_set('assert.exception', 1); 36 | 37 | // show errors 38 | \ini_set('display_errors', 1); 39 | \ini_set('display_startup_errors', 1); 40 | 41 | // terminate on errors and warnings 42 | (new \Whoops\Run())->pushHandler(new \Whoops\Handler\PlainTextHandler())->register(); 43 | } 44 | // in production mode 45 | else { 46 | // disable assertions 47 | \ini_set('assert.active', 0); 48 | @\ini_set('zend.assertions', -1); 49 | \ini_set('assert.exception', 0); 50 | 51 | // hide errors 52 | \ini_set('display_errors', 0); 53 | \ini_set('display_startup_errors', 0); 54 | } 55 | 56 | if (!isset($_ENV['APP_DEBUG']) && !isset($_ENV['APP_PUBLIC_URL'])) { 57 | throw new \RuntimeException('Environment variables not set'); 58 | } 59 | 60 | // if the internal character encoding has been configured 61 | if (isset($_ENV['APP_CHARSET'])) { 62 | // set the internal charset for PHP 63 | \mb_internal_encoding($_ENV['APP_CHARSET']); 64 | } 65 | 66 | // if the default timezone has been configured 67 | if (isset($_ENV['APP_DEFAULT_TIMEZONE'])) { 68 | // set the timezone for PHP 69 | \date_default_timezone_set($_ENV['APP_DEFAULT_TIMEZONE']); 70 | } 71 | 72 | // define a shorthand for access to the string handling methods 73 | function s($str) { 74 | return new \Delight\Str\Str($str); 75 | } 76 | 77 | // define a shorthand for access to HTML escaping 78 | function e($str) { 79 | return s($str)->escapeForHtml(); 80 | } 81 | 82 | // create the main application instance 83 | $app = new \Delight\Foundation\App(__DIR__ . '/storage/app', __DIR__ . '/views', __DIR__ . '/storage/framework'); 84 | 85 | // set the default content type and the correct charset for HTTP responses 86 | $app->setContentType('html'); 87 | 88 | // reluctantly put some constants into the global namespace for maximum convenience 89 | \define('TYPE_STRING', \Delight\Foundation\Input::DATA_TYPE_STRING); 90 | \define('TYPE_INT', \Delight\Foundation\Input::DATA_TYPE_INT); 91 | \define('TYPE_BOOL', \Delight\Foundation\Input::DATA_TYPE_BOOL); 92 | \define('TYPE_FLOAT', \Delight\Foundation\Input::DATA_TYPE_FLOAT); 93 | \define('TYPE_EMAIL', \Delight\Foundation\Input::DATA_TYPE_EMAIL); 94 | \define('TYPE_URL', \Delight\Foundation\Input::DATA_TYPE_URL); 95 | \define('TYPE_IP', \Delight\Foundation\Input::DATA_TYPE_IP); 96 | \define('TYPE_TEXT', \Delight\Foundation\Input::DATA_TYPE_TEXT); 97 | \define('TYPE_RAW', \Delight\Foundation\Input::DATA_TYPE_RAW); 98 | 99 | // reluctantly put some functions into the global namespace for maximum convenience 100 | function _f($text, ...$replacements) { global $app; return $app->i18n()->translateFormatted($text, ...$replacements); } 101 | function _fe($text, ...$replacements) { global $app; return $app->i18n()->translateFormattedExtended($text, ...$replacements); } 102 | function _p($text, $alternative, $count) { global $app; return $app->i18n()->translatePlural($text, $alternative, $count); } 103 | function _pf($text, $alternative, $count, ...$replacements) { global $app; return $app->i18n()->translatePluralFormatted($text, $alternative, $count, ...$replacements); } 104 | function _pfe($text, $alternative, $count, ...$replacements) { global $app; return $app->i18n()->translatePluralFormattedExtended($text, $alternative, $count, ...$replacements); } 105 | function _c($text, $context) { global $app; return $app->i18n()->translateWithContext($text, $context); } 106 | function _m($text) { global $app; return $app->i18n()->markForTranslation($text); } 107 | 108 | if ($app->isClientCli() || $app->isClientLoopback()) { 109 | if ($app->hasCliArgument()) { 110 | if ($app->getCliArgument() === 'clear-template-cache') { 111 | $app->clearTemplateCache(); 112 | exit; 113 | } 114 | elseif ($app->getCliArgument() === 'precompile-templates') { 115 | $app->precompileTemplates(); 116 | exit; 117 | } 118 | } 119 | } 120 | 121 | // include the actual application code 122 | require __DIR__ . '/app/index.php'; 123 | -------------------------------------------------------------------------------- /locale/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore MO (Machine Object) files 2 | *.mo 3 | # Ignore POT (Portable Object Template) files 4 | *.pot 5 | -------------------------------------------------------------------------------- /maintenance.php: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | Maintenance 19 | 48 | 49 | 50 |
51 |
52 |

We’ll be back soon!

53 |

54 | We’re performing some maintenance at the moment and will be back shortly. 55 | Sorry for the inconvenience! 56 |

57 |
58 |

Wir sind gleich zurück!

59 |

60 | Wir führen gerade Wartungsarbeiten durch und sind in Kürze wieder da. 61 | Bitte entschuldigen Sie die Unannehmlichkeiten! 62 |

63 |
64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /public/css/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delight-im/PHP-Foundation/48fb2ae551fbf6556bc1fa03cfd0a37b7c5afd7c/public/css/index.html -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delight-im/PHP-Foundation/48fb2ae551fbf6556bc1fa03cfd0a37b7c5afd7c/public/favicon.ico -------------------------------------------------------------------------------- /public/img/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delight-im/PHP-Foundation/48fb2ae551fbf6556bc1fa03cfd0a37b7c5afd7c/public/img/index.html -------------------------------------------------------------------------------- /public/js/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delight-im/PHP-Foundation/48fb2ae551fbf6556bc1fa03cfd0a37b7c5afd7c/public/js/index.html -------------------------------------------------------------------------------- /public/media/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delight-im/PHP-Foundation/48fb2ae551fbf6556bc1fa03cfd0a37b7c5afd7c/public/media/index.html -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delight-im/PHP-Foundation/48fb2ae551fbf6556bc1fa03cfd0a37b7c5afd7c/public/robots.txt -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything except this file itself 2 | * 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/framework/views/cache/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything except this file itself 2 | * 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /views/404.html.twig: -------------------------------------------------------------------------------- 1 | {% include 'includes/header.html.twig' %} 2 |

404 Not Found

3 |

The requested page could not be found

4 |

Go back

5 | {% include 'includes/footer.html.twig' %} 6 | -------------------------------------------------------------------------------- /views/greeting.html.twig: -------------------------------------------------------------------------------- 1 | {% include 'includes/header.html.twig' %} 2 |

¡Buenos días, {{ name }}!

3 |

This is another page via {{ app.getProtocol() }} on port {{ app.getPort() }}!

4 |

That was greeting number {{ greetingId }}.

5 |

Let's go back

6 |

Feel free to change or remove this template.

7 | {% include 'includes/footer.html.twig' %} 8 | -------------------------------------------------------------------------------- /views/includes/flash/bootstrap-v3.html.twig: -------------------------------------------------------------------------------- 1 | {# 2 | * PHP-Foundation (https://github.com/delight-im/PHP-Foundation) 3 | * Copyright (c) delight.im (https://www.delight.im/) 4 | * Licensed under the MIT License (https://opensource.org/licenses/MIT) 5 | #} 6 | {% if app.flash().hasAny() %} 7 | {% for type,message in app.flash().getAll() %} 8 | 14 | {% endfor %} 15 | {% endif %} 16 | -------------------------------------------------------------------------------- /views/includes/footer.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /views/includes/header.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | My application 11 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /views/mail/de-DE/confirm-email.txt.twig: -------------------------------------------------------------------------------- 1 | {# 2 | * PHP-Foundation (https://github.com/delight-im/PHP-Foundation) 3 | * Copyright (c) delight.im (https://www.delight.im/) 4 | * Licensed under the MIT License (https://opensource.org/licenses/MIT) 5 | #} 6 | {% include 'mail/de-DE/includes/header.txt.twig' %} 7 | 8 | um deine E-Mail-Adresse für {{ projectName|raw }} zu bestätigen, öffne bitte den folgenden Link: 9 | 10 | {{ confirmationUrl|raw }} 11 | 12 | Nachdem du die Bestätigung abgeschlossen hast, kannst du dich mit dieser E-Mail-Adresse und deinem Passwort anmelden. 13 | 14 | Wenn du Fragen hast, kannst du uns gerne um Hilfe bitten. 15 | 16 | {% include 'mail/de-DE/includes/footer.txt.twig' %} 17 | -------------------------------------------------------------------------------- /views/mail/de-DE/email-changed.txt.twig: -------------------------------------------------------------------------------- 1 | {# 2 | * PHP-Foundation (https://github.com/delight-im/PHP-Foundation) 3 | * Copyright (c) delight.im (https://www.delight.im/) 4 | * Licensed under the MIT License (https://opensource.org/licenses/MIT) 5 | #} 6 | {% include 'mail/de-DE/includes/header.txt.twig' %} 7 | 8 | wir möchten dich darüber informieren, dass deine E-Mail-Adresse für {{ projectName|raw }} kürzlich{% if oldEmailAddress %} von <{{ oldEmailAddress|raw }}>{% endif %}{% if newEmailAddress %} zu <{{ newEmailAddress|raw }}>{% endif %} geändert wurde. 9 | 10 | Wenn du diese Änderung nicht vorgenommen hast, kontaktiere uns bitte über unsere E-Mail-Adresse, die du unten findest. 11 | 12 | Solltest du noch andere Fragen haben, kannst du uns ebenfalls gerne um Hilfe bitten. 13 | 14 | {% include 'mail/de-DE/includes/footer.txt.twig' %} 15 | -------------------------------------------------------------------------------- /views/mail/de-DE/forgot-password.txt.twig: -------------------------------------------------------------------------------- 1 | {# 2 | * PHP-Foundation (https://github.com/delight-im/PHP-Foundation) 3 | * Copyright (c) delight.im (https://www.delight.im/) 4 | * Licensed under the MIT License (https://opensource.org/licenses/MIT) 5 | #} 6 | {% include 'mail/de-DE/includes/header.txt.twig' %} 7 | 8 | um dein Passwort für {{ projectName|raw }} zurückzusetzen, öffne bitte den folgenden Link: 9 | 10 | {{ resetUrl|raw }} 11 | 12 | Du kannst anschließend ein neues Passwort für dein Benutzerkonto festlegen. 13 | 14 | Wenn du Fragen hast, kannst du uns gerne um Hilfe bitten. 15 | 16 | {% include 'mail/de-DE/includes/footer.txt.twig' %} 17 | -------------------------------------------------------------------------------- /views/mail/de-DE/includes/footer.txt.twig: -------------------------------------------------------------------------------- 1 | {# 2 | * PHP-Foundation (https://github.com/delight-im/PHP-Foundation) 3 | * Copyright (c) delight.im (https://www.delight.im/) 4 | * Licensed under the MIT License (https://opensource.org/licenses/MIT) 5 | #} 6 | Mit freundlichen Grüßen 7 | {{ projectName|raw }} 8 | {{ projectUrl|raw }} 9 | 10 | -- 11 | 12 | {% if reasonForEmailDelivery %}{{ reasonForEmailDelivery|raw }} 13 | 14 | {% endif %}Diese Nachricht wurde an <{{ recipientEmailAddress|raw }}> gesendet{% if requestedByIpAddress %} und wurde durch jemanden mit der IP-Adresse „{{ requestedByIpAddress|raw }}“ verursacht{% endif %}. 15 | 16 | Bitte antworte nicht direkt auf diese Nachricht, da wir von dieser E-Mail-Adresse aus nicht antworten können. Kontaktiere uns bitte stattdessen unter <{{ projectEmail|raw }}>. 17 | 18 | © {{ projectName|raw }}, {{ projectPostalAddress|raw }} 19 | -------------------------------------------------------------------------------- /views/mail/de-DE/includes/header.txt.twig: -------------------------------------------------------------------------------- 1 | {# 2 | * PHP-Foundation (https://github.com/delight-im/PHP-Foundation) 3 | * Copyright (c) delight.im (https://www.delight.im/) 4 | * Licensed under the MIT License (https://opensource.org/licenses/MIT) 5 | #} 6 | {% if recipientName %}Hallo {{ recipientName|raw }}{% else %}Hallo{% endif %}, 7 | -------------------------------------------------------------------------------- /views/mail/de-DE/password-changed.txt.twig: -------------------------------------------------------------------------------- 1 | {# 2 | * PHP-Foundation (https://github.com/delight-im/PHP-Foundation) 3 | * Copyright (c) delight.im (https://www.delight.im/) 4 | * Licensed under the MIT License (https://opensource.org/licenses/MIT) 5 | #} 6 | {% include 'mail/de-DE/includes/header.txt.twig' %} 7 | 8 | wir möchten dich darüber informieren, dass dein Passwort für {{ projectName|raw }} kürzlich geändert wurde. 9 | 10 | Bitte verwende von nun an das neue Passwort, um dich in deinem Benutzerkonto anzumelden. 11 | 12 | Wenn du diese Änderung nicht vorgenommen hast, kontaktiere uns bitte über unsere E-Mail-Adresse, die du unten findest. 13 | 14 | Solltest du noch andere Fragen haben, kannst du uns ebenfalls gerne um Hilfe bitten. 15 | 16 | {% include 'mail/de-DE/includes/footer.txt.twig' %} 17 | -------------------------------------------------------------------------------- /views/mail/de-DE/sign-up.txt.twig: -------------------------------------------------------------------------------- 1 | {# 2 | * PHP-Foundation (https://github.com/delight-im/PHP-Foundation) 3 | * Copyright (c) delight.im (https://www.delight.im/) 4 | * Licensed under the MIT License (https://opensource.org/licenses/MIT) 5 | #} 6 | {% include 'mail/de-DE/includes/header.txt.twig' %} 7 | 8 | willkommen bei {{ projectName|raw }}! 9 | 10 | {%if confirmationUrl %}Bitte bestätige deine E-Mail-Adresse, indem du den folgenden Link öffnest: 11 | 12 | {{ confirmationUrl|raw }} 13 | 14 | Nachdem du die Bestätigung abgeschlossen hast, kannst du dich{% else %}Du kannst dich jetzt{% endif %} mit dieser E-Mail-Adresse und dem Passwort, das du während der Registrierung gewählt hast, anmelden. 15 | 16 | Wenn du Fragen hast, kannst du uns gerne um Hilfe bitten. 17 | 18 | {% include 'mail/de-DE/includes/footer.txt.twig' %} 19 | -------------------------------------------------------------------------------- /views/mail/en-US/confirm-email.txt.twig: -------------------------------------------------------------------------------- 1 | {# 2 | * PHP-Foundation (https://github.com/delight-im/PHP-Foundation) 3 | * Copyright (c) delight.im (https://www.delight.im/) 4 | * Licensed under the MIT License (https://opensource.org/licenses/MIT) 5 | #} 6 | {% include 'mail/en-US/includes/header.txt.twig' %} 7 | 8 | In order to confirm your email address for {{ projectName|raw }}, please open the link below: 9 | 10 | {{ confirmationUrl|raw }} 11 | 12 | After completing the confirmation, you can sign in with this email address and your password. 13 | 14 | If you have any questions, please feel free to ask us for help. 15 | 16 | {% include 'mail/en-US/includes/footer.txt.twig' %} 17 | -------------------------------------------------------------------------------- /views/mail/en-US/email-changed.txt.twig: -------------------------------------------------------------------------------- 1 | {# 2 | * PHP-Foundation (https://github.com/delight-im/PHP-Foundation) 3 | * Copyright (c) delight.im (https://www.delight.im/) 4 | * Licensed under the MIT License (https://opensource.org/licenses/MIT) 5 | #} 6 | {% include 'mail/en-US/includes/header.txt.twig' %} 7 | 8 | We wanted to let you know that your email address for {{ projectName|raw }} has recently been changed{% if oldEmailAddress %} from <{{ oldEmailAddress|raw }}>{% endif %}{% if newEmailAddress %} to <{{ newEmailAddress|raw }}>{% endif %}. 9 | 10 | If you didn’t make this change, please contact us at our email address that you find below. 11 | 12 | Feel free to ask us for help if you have any other questions as well. 13 | 14 | {% include 'mail/en-US/includes/footer.txt.twig' %} 15 | -------------------------------------------------------------------------------- /views/mail/en-US/forgot-password.txt.twig: -------------------------------------------------------------------------------- 1 | {# 2 | * PHP-Foundation (https://github.com/delight-im/PHP-Foundation) 3 | * Copyright (c) delight.im (https://www.delight.im/) 4 | * Licensed under the MIT License (https://opensource.org/licenses/MIT) 5 | #} 6 | {% include 'mail/en-US/includes/header.txt.twig' %} 7 | 8 | In order to reset your password for {{ projectName|raw }}, please open the link below: 9 | 10 | {{ resetUrl|raw }} 11 | 12 | You will then be able to choose a new password for your account. 13 | 14 | If you have any questions, please feel free to ask us for help. 15 | 16 | {% include 'mail/en-US/includes/footer.txt.twig' %} 17 | -------------------------------------------------------------------------------- /views/mail/en-US/includes/footer.txt.twig: -------------------------------------------------------------------------------- 1 | {# 2 | * PHP-Foundation (https://github.com/delight-im/PHP-Foundation) 3 | * Copyright (c) delight.im (https://www.delight.im/) 4 | * Licensed under the MIT License (https://opensource.org/licenses/MIT) 5 | #} 6 | Sincerely, 7 | {{ projectName|raw }} 8 | {{ projectUrl|raw }} 9 | 10 | -- 11 | 12 | {% if reasonForEmailDelivery %}{{ reasonForEmailDelivery|raw }} 13 | 14 | {% endif %}This message was sent to <{{ recipientEmailAddress|raw }}>{% if requestedByIpAddress %} and was initiated by someone on IP address “{{ requestedByIpAddress|raw }}”{% endif %}. 15 | 16 | Please do not reply to this message directly, as we are unable to respond from this email address. Instead, please contact us at <{{ projectEmail|raw }}>. 17 | 18 | © {{ projectName|raw }}, {{ projectPostalAddress|raw }} 19 | -------------------------------------------------------------------------------- /views/mail/en-US/includes/header.txt.twig: -------------------------------------------------------------------------------- 1 | {# 2 | * PHP-Foundation (https://github.com/delight-im/PHP-Foundation) 3 | * Copyright (c) delight.im (https://www.delight.im/) 4 | * Licensed under the MIT License (https://opensource.org/licenses/MIT) 5 | #} 6 | {% if recipientName %}Dear {{ recipientName|raw }}{% else %}Hello{% endif %}, 7 | -------------------------------------------------------------------------------- /views/mail/en-US/password-changed.txt.twig: -------------------------------------------------------------------------------- 1 | {# 2 | * PHP-Foundation (https://github.com/delight-im/PHP-Foundation) 3 | * Copyright (c) delight.im (https://www.delight.im/) 4 | * Licensed under the MIT License (https://opensource.org/licenses/MIT) 5 | #} 6 | {% include 'mail/en-US/includes/header.txt.twig' %} 7 | 8 | We wanted to let you know that your password for {{ projectName|raw }} has recently been changed. 9 | 10 | From now on, please use the new password to sign in to your user account. 11 | 12 | If you didn’t make this change, please contact us at our email address that you find below. 13 | 14 | Feel free to ask us for help if you have any other questions as well. 15 | 16 | {% include 'mail/en-US/includes/footer.txt.twig' %} 17 | -------------------------------------------------------------------------------- /views/mail/en-US/sign-up.txt.twig: -------------------------------------------------------------------------------- 1 | {# 2 | * PHP-Foundation (https://github.com/delight-im/PHP-Foundation) 3 | * Copyright (c) delight.im (https://www.delight.im/) 4 | * Licensed under the MIT License (https://opensource.org/licenses/MIT) 5 | #} 6 | {% include 'mail/en-US/includes/header.txt.twig' %} 7 | 8 | Welcome to {{ projectName|raw }}! 9 | 10 | {%if confirmationUrl %}Please confirm your email address by opening the link below: 11 | 12 | {{ confirmationUrl|raw }} 13 | 14 | After completing the confirmation, you can{% else %}You can now{% endif %} sign in with this email address and the password that you chose during sign up. 15 | 16 | If you have any questions, please feel free to ask us for help. 17 | 18 | {% include 'mail/en-US/includes/footer.txt.twig' %} 19 | -------------------------------------------------------------------------------- /views/welcome.html.twig: -------------------------------------------------------------------------------- 1 | {% include 'includes/header.html.twig' %} 2 |

Hello world

3 |

Welcome to your first page on {{ now | date }}!

4 |
    5 | {% for user in users %} 6 |
  • 7 | Greet {{ user }} 8 |
  • 9 | {% endfor %} 10 |
11 |

Please read README.md for more information on how to write powerful templates.

12 |

Feel free to change or remove this template.

13 | {% include 'includes/footer.html.twig' %} 14 | --------------------------------------------------------------------------------