├── .gitignore ├── LICENSE ├── README.md ├── advanced ├── ajax-request.md ├── localization.md ├── mail.md ├── scheduling-tasks.md ├── testing.md └── validation.md ├── customize ├── layouts.md ├── markup-guide.md ├── media-manager.md ├── pages.md ├── partials.md └── themes.md ├── extend ├── components.md ├── controllers.md ├── extensions.md ├── forms.md ├── lists.md ├── permissions.md └── widgets.md ├── installation.md ├── resources ├── code-of-conduct.md ├── contribution-guide.md ├── js-coding-guidelines.md └── php-coding-guidelines.md └── upgrade-guide.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw? 2 | .idea 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 TastyIgniter 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 | # TastyIgniter Documentation 2 | 3 | You can find the online version of the TastyIgniter documentation at 4 | 5 | # Contribution Guidelines 6 | 7 | If you are submitting documentation for the current stable release, submit it to the `master` branch. 8 | -------------------------------------------------------------------------------- /advanced/ajax-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Handling AJAX Requests" 3 | section: advanced 4 | sortOrder: 300 5 | --- 6 | 7 | ## Introduction 8 | 9 | TastyIgniter provides a simple and easy way to make AJAX requests to the server using the `$.request` JavaScript object. This object is a wrapper around the jQuery AJAX function, providing a more convenient way to make AJAX requests. 10 | 11 | In this guide, you'll learn how to make AJAX requests using the `$.request` object, handle AJAX requests on the server, and customize AJAX requests using options. 12 | 13 | Alternatively, you can use [Livewire's Event Listeners](https://livewire.laravel.com/docs/actions#event-listeners) to make AJAX requests. 14 | 15 | ## How AJAX requests work 16 | 17 | The `$.request` object is a wrapper around the jQuery AJAX function, providing a more convenient way to make AJAX requests. It is used to send an HTTP request to the server and receive a response from the server without reloading the page. 18 | 19 | Here's an example of how to make an AJAX request using the `$.request` object: 20 | 21 | ```javascript 22 | $.request('onSave', { 23 | data: { 24 | var1: 'some string', 25 | var2: 'another string' 26 | }, 27 | success: function(data) { 28 | console.log(data); 29 | } 30 | }); 31 | ``` 32 | 33 | In this example, the `$.request` object is used to send an AJAX request to the server. The first argument is the name of the handler method on the server that will handle the request. The second argument is an object containing the request data and a callback function to handle the response. 34 | 35 | You can use data attributes to trigger AJAX requests by adding the `data-request` attribute to HTML elements. The value of the `data-request` attribute should be the name of the handler method on the server that will handle the request. 36 | 37 | ```blade 38 | 39 | ``` 40 | 41 | ## Using AJAX handlers 42 | 43 | To handle an AJAX request on the server, you need to create a handler method in your controller that will process the request and return a response. The handler method should be named `on{HandlerName}`. 44 | 45 | Here's an example of a handler method: 46 | 47 | ```php 48 | public function onSave() 49 | { 50 | $param1 = post('param1'); 51 | $param2 = post('param2'); 52 | 53 | // Process the request data 54 | 55 | return ['result' => 'success']; 56 | } 57 | ``` 58 | 59 | You can pass data to the server using the `data` option in the `$.request` object. The data should be an object containing key-value pairs of the request data. 60 | 61 | Here's an example of how to pass data to the server in an AJAX request: 62 | 63 | ```javascript 64 | $.request('onSave', { 65 | data: { 66 | param1: 'value1', 67 | param2: 'value2' 68 | }, 69 | success: function(data) { 70 | console.log(data); 71 | } 72 | }); 73 | ``` 74 | 75 | In this example, the `data` option is used to pass data to the server in the AJAX request. 76 | 77 | ## Handling the response 78 | 79 | You can handle the response from the server using the `success` option in the `$.request` object. The `success` option should be a callback function that will be called when the server returns a response. Additionally, you can use the `done` promise method to handle errors. 80 | 81 | Here's an example of how to handle the response from the server in an AJAX request: 82 | 83 | ```javascript 84 | $.request('onSave', { 85 | success: function(data) { 86 | console.log(data); 87 | } 88 | }); 89 | ``` 90 | 91 | And here's how you can handle the response using the `done` promise method: 92 | 93 | ```javascript 94 | $.request('onSave').done(function(data) { 95 | console.log(data); 96 | }); 97 | ``` 98 | 99 | ## Error handling 100 | 101 | You can handle errors that occur during the AJAX request using the `error` option in the `$.request` object. The `error` option should be a callback function that will be called when an error occurs during the request. Additionally, you can use the `fail` promise method to handle errors. 102 | 103 | Here's an example of how to handle errors in an AJAX request: 104 | 105 | ```javascript 106 | $.request('onSave', { 107 | error: function(xhr, status, error) { 108 | console.log('An error occurred: ' + error); 109 | } 110 | }); 111 | ``` 112 | 113 | And here's how you can handle errors using the `fail` promise method: 114 | 115 | ```javascript 116 | $.request('onSave').fail(function(xhr, status, error) { 117 | console.log('An error occurred: ' + error); 118 | }); 119 | ``` 120 | 121 | ## Request options 122 | 123 | The `$.request` object supports additional options for customizing AJAX requests. These options can be defined either programmatically using JavaScript or through the data attributes API by adding data attributes to HTML elements. 124 | 125 | Some of the common options include: 126 | 127 | {.grid-2} 128 | - [update](../advanced/ajax-request#update) 129 | - [confirm](../advanced/ajax-request#confirm) 130 | - [data](../advanced/ajax-request#data) 131 | - [redirect](../advanced/ajax-request#redirect) 132 | - [headers](../advanced/ajax-request#headers) 133 | - [attach-loading](../advanced/ajax-request#attach-loading) 134 | - [replace-loading](../advanced/ajax-request#replace-loading) 135 | - [beforeUpdate](../advanced/ajax-request#beforeUpdate) 136 | - [success](../advanced/ajax-request#success) 137 | - [error](../advanced/ajax-request#error) 138 | - [complete](../advanced/ajax-request#complete) 139 | - [submit](../advanced/ajax-request#submit) 140 | - [form](../advanced/ajax-request#form) 141 | 142 | #### `update` 143 | 144 | _(Object)_ Specifies a list of partials and page elements to be updated with the response data. The key is the partial name, and the value is the CSS selector of the target element to be updated. 145 | 146 | **JavaScript:** 147 | 148 | ```javascript 149 | $.request('onSave', { 150 | update: { 151 | 'partial1': '#element1', 152 | 'partial2': '#element2' 153 | } 154 | }); 155 | ``` 156 | 157 | **Data attribute** 158 | 159 | ```html 160 | 161 | ``` 162 | 163 | > You may prepend the CSS Selector with `@` to append contents to the element, `^` to prepend and `~` to replace with. 164 | 165 | #### `confirm` 166 | 167 | _(string)_ Specifies a confirmation message that will be displayed to the user before sending the request. If the user confirms the request, the request will be sent; otherwise, the request will be canceled. 168 | 169 | **JavaScript:** 170 | 171 | ```javascript 172 | $.request('onSave', { 173 | confirm: 'Are you sure?' 174 | }); 175 | ``` 176 | 177 | **Data attribute:** 178 | 179 | ```html 180 | 181 | ``` 182 | 183 | #### `data` 184 | 185 | _(Object)_ Specifies the data to be sent with the request. The data should be an object containing key-value pairs of the request data. 186 | 187 | **JavaScript:** 188 | 189 | ```javascript 190 | $.request('onSave', { 191 | data: { 192 | param1: 'value1', 193 | param2: 'value2' 194 | } 195 | }); 196 | ``` 197 | 198 | **Data attribute:** 199 | 200 | ```html 201 | 202 | ``` 203 | 204 | #### `redirect` 205 | 206 | _(string)_ Specifies the URL to redirect to after the request is completed. 207 | 208 | **JavaScript:** 209 | 210 | ```javascript 211 | $.request('onSave', { 212 | redirect: '/success' 213 | }); 214 | ``` 215 | 216 | **Data attribute:** 217 | 218 | ```html 219 | 220 | ``` 221 | 222 | #### `headers` 223 | 224 | _(Object)_ Specifies additional headers to be sent with the request. 225 | 226 | **JavaScript:** 227 | 228 | ```javascript 229 | $.request('onSave', { 230 | headers: { 231 | 'X-CSRF-TOKEN': 'token' 232 | } 233 | }); 234 | ``` 235 | 236 | #### `attach-loading` 237 | 238 | _(string)_ Specifies the CSS selector to add to the target element while the request is loading. The attribute value is optional, if not provided, the target element will be disabled. 239 | 240 | **Data attribute:** 241 | 242 | ```html 243 | 244 | ``` 245 | 246 | #### `replace-loading` 247 | 248 | _(string)_ Specifies the CSS selector to replace on the target element while the request is loading. 249 | 250 | **Data attribute:** 251 | 252 | ```html 253 | 254 | ``` 255 | 256 | #### `beforeUpdate` 257 | 258 | _(function)_ Specifies a callback function or Javascript code to be executed before updating the target element with the response. 259 | 260 | **JavaScript:** 261 | 262 | ```javascript 263 | $.request('onSave', { 264 | beforeUpdate: function(data) { 265 | console.log('Before update'); 266 | } 267 | }); 268 | ``` 269 | 270 | **Data attribute** 271 | 272 | ```html 273 | 274 | ``` 275 | 276 | #### `success` 277 | 278 | _(function)_ Specifies a callback function or Javascript code to be executed after the request is successful. 279 | 280 | **JavaScript:** 281 | 282 | ```javascript 283 | $.request('onSave', { 284 | success: function(data) { 285 | console.log('Success'); 286 | } 287 | }); 288 | ``` 289 | 290 | **Data attribute** 291 | 292 | ```html 293 | 294 | ``` 295 | 296 | #### `error` 297 | 298 | _(function)_ Specifies a callback function or Javascript code to be executed when an error occurs during the request. 299 | 300 | **JavaScript:** 301 | 302 | ```javascript 303 | $.request('onSave', { 304 | error: function(xhr, status, error) { 305 | console.log('An error occurred: ' + error); 306 | } 307 | }); 308 | ``` 309 | 310 | **Data attribute** 311 | 312 | ```html 313 | 314 | ``` 315 | 316 | #### `complete` 317 | 318 | _(function)_ Specifies a callback function or Javascript code to be executed after the request is completed. 319 | 320 | **JavaScript:** 321 | 322 | ```javascript 323 | $.request('onSave', { 324 | complete: function(xhr, status) { 325 | console.log('Request completed'); 326 | } 327 | }); 328 | ``` 329 | 330 | **Data attribute** 331 | 332 | ```html 333 | 334 | ``` 335 | 336 | #### `submit` 337 | 338 | When set to `true`, the form will be submitted using the default form submission method. 339 | 340 | **JavaScript:** 341 | 342 | ```javascript 343 | $.request('onSave', { 344 | submit: true 345 | }); 346 | ``` 347 | 348 | **Data attribute** 349 | 350 | ```html 351 | 352 | ``` 353 | 354 | #### `form` 355 | 356 | _(CSS Selector)_ Specifies the form element to be submitted. Useful when the request is triggered by a button outside the form. 357 | 358 | **JavaScript:** 359 | 360 | ```javascript 361 | $.request('onSave', { 362 | form: '#myForm' 363 | }); 364 | ``` 365 | 366 | **Data attribute** 367 | 368 | ```html 369 | 370 | ``` 371 | 372 | ## Global AJAX events 373 | 374 | The AJAX framework triggers several events on the updated elements, the triggering element, form, and the window object. These events are triggered regardless of which API was used - the data attributes API or the JavaScript API. 375 | 376 | Here are some of the global AJAX events that you can listen to: 377 | 378 | {.grid-2} 379 | 380 | - [ajaxBeforeSend](../advanced/ajax-request#ajaxbeforesend) 381 | - [ajaxBeforeUpdate](../advanced/ajax-request#ajaxbeforeupdate) 382 | - [ajaxUpdate](../advanced/ajax-request#ajaxupdate) 383 | - [ajaxUpdateComplete](../advanced/ajax-request#ajaxupdatecomplete) 384 | - [ajaxSuccess](../advanced/ajax-request#ajaxsuccess) 385 | - [ajaxError](../advanced/ajax-request#ajaxerror) 386 | - [ajaxErrorMessage](../advanced/ajax-request#ajaxerrormessage) 387 | - [ajaxConfirmMessage](../advanced/ajax-request#ajaxconfirmmessage) 388 | - [ajaxSetup](../advanced/ajax-request#ajaxsetup) 389 | - [ajaxPromise](../advanced/ajax-request#ajaxpromise) 390 | - [ajaxDone](../advanced/ajax-request#ajaxdone) 391 | - [ajaxFail](../advanced/ajax-request#ajaxfail) 392 | - [ajaxAlways](../advanced/ajax-request#ajaxalways) 393 | 394 | #### `ajaxBeforeSend` 395 | 396 | Triggered on the window object before the AJAX request is sent. The handler receives the context object as an argument. 397 | 398 | ```javascript 399 | $(window).on('ajaxBeforeSend', function(context) { 400 | console.log(context); 401 | }); 402 | ``` 403 | 404 | #### `ajaxBeforeUpdate` 405 | 406 | Triggered on the form element immediately after the request is completed but before updating the page. The event handler receives the `event` object, the `context` object, the `data` object received from the server, the `status` text string, and the `jqXHR` object as arguments. 407 | 408 | ```javascript 409 | $('form').on('ajaxBeforeUpdate', function(event, context, data, status, jqXHR) { 410 | console.log(event, context, data, status, jqXHR); 411 | }); 412 | ``` 413 | 414 | #### `ajaxUpdate` 415 | 416 | Triggered on the page element after updating the element with the response data. The event handler receives the `event` object, the `context` object, the `data` object received from the server, the `status` text string, and the `jqXHR` object as arguments. 417 | 418 | ```javascript 419 | $('#element').on('ajaxUpdate', function(event, context, data, status, jqXHR) { 420 | console.log(event, context, data, status, jqXHR); 421 | }); 422 | ``` 423 | 424 | #### `ajaxUpdateComplete` 425 | 426 | Triggered on the window object after all element are updated. The event handler receives the `event` object, the `context` object, the `data` object received from the server, the `status` text string, and the `jqXHR` object as arguments. 427 | 428 | ```javascript 429 | $(window).on('ajaxUpdateComplete', function(event, context, data, status, jqXHR) { 430 | console.log(event, context, data, status, jqXHR); 431 | }); 432 | ``` 433 | 434 | #### `ajaxSuccess` 435 | 436 | Triggered on the form element after the request is successful. The event handler receives the `event` object, the `context` object, the `data` object received from the server, the `status` text string, and the `jqXHR` object as arguments. 437 | 438 | ```javascript 439 | $('form').on('ajaxSuccess', function(event, context, data, status, jqXHR) { 440 | console.log(event, context, data, status, jqXHR); 441 | }); 442 | ``` 443 | 444 | #### `ajaxError` 445 | 446 | Triggered on the form element when an error occurs during the AJAX request. The event handler receives the `event` object, the `context` object, the `error` message, the `status` text string, and the `jqXHR` object as arguments. 447 | 448 | ```javascript 449 | $('form').on('ajaxError', function(event, context, error, status, jqXHR) { 450 | console.log(event, context, data, status, jqXHR); 451 | }); 452 | ``` 453 | 454 | #### `ajaxErrorMessage` 455 | 456 | Triggered on the window object when an error occurs during the AJAX request. The event handler receives the `event` object and `error` message as an argument. 457 | 458 | ```javascript 459 | $(window).on('ajaxErrorMessage', function(event, error) { 460 | console.log(event, error); 461 | }); 462 | ``` 463 | 464 | #### `ajaxConfirmMessage` 465 | 466 | Triggered on the window object when a `confirm` option is given. The event handler receives the event object and the confirmation message as arguments. 467 | 468 | ```javascript 469 | $(window).on('ajaxConfirmMessage', function(event, message) { 470 | console.log(event, message); 471 | }); 472 | ``` 473 | 474 | #### `ajaxSetup` 475 | 476 | Triggered on the triggering element before the AJAX request is formed. The event handler receives the `context` object as an argument. 477 | 478 | ```javascript 479 | $('#element').on('ajaxSetup', function(context) { 480 | console.log(context); 481 | }); 482 | ``` 483 | 484 | #### `ajaxPromise` 485 | 486 | Triggered on the triggering element before the AJAX request is sent. The event handler receives the `context` object as an argument. 487 | 488 | ```javascript 489 | $('#element').on('ajaxPromise', function(context) { 490 | console.log(context); 491 | }); 492 | ``` 493 | 494 | #### `ajaxDone` 495 | 496 | Triggered on the triggering element when the AJAX request is successful. The event handler receives the `context` object, the `data` object received from the server, the `status` text string, and the `jqXHR` object as arguments. 497 | 498 | ```javascript 499 | $('#element').on('ajaxDone', function(context, data, textStatus, jqXHR) { 500 | console.log(data); 501 | }); 502 | ``` 503 | 504 | #### `ajaxFail` 505 | 506 | Triggered on the triggering element when the AJAX request fails. The event handler receives the `context` object, the `status` text string, and the `jqXHR` object as arguments. 507 | 508 | ```javascript 509 | $('#element').on('ajaxFail', function(context, textStatus, jqXHR) { 510 | console.log(jqXHR, textStatus, errorThrown); 511 | }); 512 | ``` 513 | 514 | #### `ajaxAlways` 515 | 516 | Triggered on the triggering element when the AJAX request is completed. The event handler receives the `context` object, the `data` object received from the server, the `status` text string, and the `jqXHR` object as arguments. 517 | 518 | ```javascript 519 | $('#element').on('ajaxAlways', function(context, dataOrXhr, textStatus, xhrOrError) { 520 | console.log(jqXHR, textStatus); 521 | }); 522 | ``` 523 | -------------------------------------------------------------------------------- /advanced/localization.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Localization" 3 | section: advanced 4 | sortOrder: 310 5 | --- 6 | 7 | ## Introduction 8 | 9 | TastyIgniter features a robust translation system that leverages Laravel's localization features, providing a convenient method for retrieving strings in various languages, allowing you to easily support multiple languages in your TastyIgniter application. 10 | 11 | Language directories and files are stored in PHP files within your application's **lang** directory. 12 | 13 | ## Directory structure 14 | 15 | Here is an example of how the **lang** directory might be structured: 16 | 17 | ```yaml 18 | lang/ 19 | en/ <=== Language directory 20 | custom.php <=== Language file 21 | es/ <=== Language directory 22 | custom.php <=== Language file 23 | ``` 24 | 25 | In this example, the language directory contains language files. Each language has its own subdirectory named using the [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) code (e.g., `en` for English, `es` for Spanish), and each subdirectory contains a `custom.php` language file with the translation strings for that language. 26 | 27 | ## Defining language strings 28 | 29 | All language files return an array of keyed language strings. For example: 30 | 31 | ```php 32 | 'This is a sample language string.', 36 | 'alert' => [ // Namespacing for alerts language strings 37 | 'success' => 'This is a success alert' 38 | ] 39 | ]; 40 | ``` 41 | 42 | ## Accessing language strings 43 | 44 | You can use the `Lang` facade, `@lang` Blade directive, `__` helper function, or `lang` helper function to retrieve strings from language files. For instance, to retrieve the `sample_key` language string from the `lang/en/custom.php` language file, you can use any of these methods. 45 | 46 | ```php 47 | // Using the `Lang` facade 48 | echo Lang('custom.sample_key') 49 | 50 | // Using the `@lang` Blade directive 51 | @lang('custom.sample_key') 52 | 53 | // Using the `lang` helper function 54 | echo lang('custom.sample_key') 55 | 56 | // Using the `lang` helper function in a Blade template 57 | {{ lang('custom.sample_key') }} 58 | 59 | // Using the `__` helper function 60 | echo __('custom.sample_key'); 61 | 62 | // Using the `__` helper function in a Blade template 63 | {{ __('custom.sample_key') }} 64 | ``` 65 | 66 | You can also retrieve nested language strings using dot notation. For example, to retrieve the `success` language string from the `alert` namespace in the `lang/en/custom.php` language file: 67 | 68 | ```php 69 | // Using the `Lang` facade 70 | {{ __('custom.alert.success') }} 71 | ``` 72 | 73 | ### Replacing parameters in translation strings 74 | 75 | If you wish, you can define placeholders in your language strings. All placeholders are prefixed with a `:`. For example, you can define a welcome message with a placeholder name like so: 76 | 77 | ```php 78 | 'Welcome, :name', 82 | ]; 83 | ``` 84 | 85 | To replace the placeholders when retrieving a language string, you may pass an array of replacements as the second argument to the `__` function: 86 | 87 | ```php 88 | {{ __('custom.welcome', ['name' => 'dayle']) }} 89 | ``` 90 | 91 | If your placeholder contains all capital letters, or only has its first letter capitalized, the translated value will be capitalized accordingly: 92 | 93 | ```php 94 | 'Welcome, :NAME', // Welcome, DAYLE 98 | 'goodbye' => 'Goodbye, :Name', // Goodbye, Dayle 99 | ]; 100 | ``` 101 | 102 | ### Pluralization with language strings 103 | 104 | Pluralization can be a complex issue due to the various rules across different languages. However, Laravel provides a solution to translate strings differently based on your defined pluralization rules. You can distinguish between singular and plural forms of a string using the `|` character, like this: 105 | 106 | ```php 107 | 'apples' => 'There is one apple|There are many apples', 108 | ``` 109 | 110 | You can create even more complex pluralization rules, specifying translation strings for multiple value ranges: 111 | 112 | ```php 113 | 'apples' => '{0} There are none|[1,19] There are some|[20,*] There are many', 114 | ``` 115 | 116 | Once you've defined a translation string with pluralization options, you can use the `trans_choice` function to retrieve the line for a given "count". For example: 117 | 118 | ```php 119 | {{ trans_choice('custom.apples', 10) }} 120 | ``` 121 | 122 | You can also define placeholder attributes in pluralization strings. These placeholders can be replaced by passing an array as the third argument to the `trans_choice` function: 123 | 124 | ```php 125 | 'minutes_ago' => '{1} :value minute ago|[2,*] :value minutes ago', 126 | 127 | {{ trans_choice('custom.minutes_ago', 5, ['value' => 5]) }} // 5 minutes ago 128 | ``` 129 | 130 | If you want to display the integer value passed to the `trans_choice` function, you can use the built-in `:count` placeholder: 131 | 132 | ```php 133 | 'apples' => '{0} There are none|{1} There is one|[2,*] There are :count', 134 | 135 | {{ trans_choice('custom.minutes_ago', 5, ['value' => 5]) }} // There are 5 136 | ``` 137 | 138 | ## Overriding language strings 139 | 140 | You can customize all application language strings from the _Manage > Settings > Languages > Edit > Translations_ admin page. 141 | 142 | Some TastyIgniter extensions, themes and Laravel packages come with their own language files. If you need to customize these strings without modifying the package's core files, you can override them by placing files in the `lang/vendor/{author}-{package}/{locale}` directory. 143 | 144 | For instance, if you want to modify the language string `override_key` in `custom.php` for an extension named `acme.helloworld`, you should create a language file `lang/vendor/acme-helloworld/en/custom.php` within the root of your TastyIgniter installation. 145 | 146 | ```yaml 147 | lang/ 148 | vendor/ 149 | acme-helloworld/ 150 | en/ <=== Language directory 151 | custom.php <=== Language override file 152 | ``` 153 | 154 | In this file, define only the language strings you want to change. Any strings you don't override will still be loaded from the original language file. 155 | 156 | ```php 157 | 'This is an overridden language string.', 161 | ]; 162 | ``` 163 | 164 | ## Making your site multilingual 165 | 166 | In this section, we'll guide you through the steps required to make your TastyIgniter site multilingual. There are two main ways to install additional languages: directly from the _Manage > Settings > Languages_ page in the admin interface, or manually downloading a language pack from the TastyIgniter translations Crowdin project page. 167 | 168 | ### Importing translated language strings 169 | 170 | TastyIgniter supports the use of language packs, which are collections of translated language strings for various languages. These language packs can be imported to provide translations for the TastyIgniter admin interface and frontend. 171 | 172 | Language packs are typically provided by the TastyIgniter community and can be installed directly from the admin interface or manually downloaded from the TastyIgniter translations Crowdin project page. 173 | 174 | You can import translated language strings from the _Manage > Settings > Languages_ page of the admin interface. 175 | 176 | - Navigate to the _Manage > Settings > Languages_ page in the admin interface. 177 | - Click on the **New** button to create a new language. In the **Locale Code** field, enter the language code (e.g., `es` for Spanish) and the **Name** field, enter the language name (e.g., `Spanish`). 178 | - Click the **Save** button to create the new language. 179 | - Once the language is created, you will see the **Import Translations** button on the right side of the page. 180 | - Click on the **Import Translations** button to open the import dialog. 181 | - In the import dialog, you will see a list of available language packs. 182 | - Click on the **Download translated strings** button to download the language packs. 183 | - Once the download is complete, click on the **Close** button to reload the page and see the newly installed translated language strings 184 | - Go to the _Manage > Settings > Languages_ page and click on the **Set as Default** button next to the language you just created to activate it. 185 | - That's it! You have successfully installed a new language pack. 186 | 187 | #### Installing a language pack from the command line 188 | 189 | You can also install a language pack from the command line using the `igniter:language-install` Artisan command. This command allows you to install a language pack directly from the TastyIgniter Crowdin project page. 190 | 191 | Run the following command from the application directory to install a **Spanish (ES)** language pack: 192 | 193 | ```bash 194 | php artisan igniter:language-install es 195 | ``` 196 | 197 | ### Manually download a language pack 198 | 199 | Follow these steps to manually download a community translated language pack. 200 | 201 | - Join our translations Crowdin project page. 202 | - Choose the language you wish to install. For example, let's choose **Spanish (ES)**. 203 | - Download and unzip the language pack. To download, you need to click on the button at the top right of the Crowdin language page. 204 | - The extracted language pack should have a specific folder and file structure. Copy the files and folders within the `Namespaced` directories into your TastyIgniter `lang` directory, _see below_. If you don't have a `lang` directory in your application root, create a new one. 205 | 206 | ```yaml 207 | lang/ 208 | vendor/ 209 | igniter/ 210 | es_ES/ <=== Language directory 211 | admin.php 212 | main.php 213 | system.php 214 | ``` 215 | 216 | > Notice the language directory name uses underscore `_` instead of a hyphen `-` (e.g. "es_ES"). 217 | 218 | ### Configuring the default language 219 | 220 | You may want to set the installed language pack as the default language for new users and visitors. You can do this on the _Manage > Settings > Languages_ page of the admin interface, by clicking the **Set as Default** button next to the language you want to use as the default. 221 | 222 | ### Enabling language detection 223 | 224 | TastyIgniter comes with built-in support for language negotiation, offering various methods to automatically detect the user's language without requiring manual selection. The language detection process follows this sequence: 225 | 226 | - **On the Admin Interface** 227 | - Determine the Admin Interface language from currently logged in admin user language settings. 228 | - Determine the language from the user browser's language settings. 229 | - Determine the language from the **default** language setting. 230 | - **On the FrontEnd** 231 | - Determine the frontend language from the **request/session** parameter. 232 | - Using the locale picker provided within the [FrontEnd extension](..extensions/frontend#locale-picker). 233 | - Determine the language from the **default** language setting. 234 | 235 | ## Translating third-party extensions 236 | 237 | Language packs downloaded from the _Manage > Settings > Languages_ page in the admin interface typically include translations for all TastyIgniter recommended extensions. However, it's important to note that they may be missing translations for other extensions you've installed. 238 | 239 | Developers of TastyIgniter extensions are responsible for providing and maintaining translations for their extensions. Before installing a third-party extension, ensure that it includes translations for each language pack you have installed. If you find that an extension doesn't support a language you need, please contact the developer directly to arrange for the necessary translations to be added. 240 | -------------------------------------------------------------------------------- /advanced/mail.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Mail" 3 | section: advanced 4 | sortOrder: 320 5 | --- 6 | 7 | ## Introduction 8 | 9 | TastyIgniter uses the Laravel Mail component to send emails. The Mail component provides a clean, simple API which allows you to send emails through a variety of drivers, including SMTP, Mailgun, Postmark and Amazon SES. 10 | 11 | ### Driver prerequisites 12 | 13 | To use the Mailgun, Postmark and Amazon SES drivers, install the required dependencies via Composer. 14 | 15 | #### Mailgun 16 | 17 | ```bash 18 | composer require symfony/mailgun-mailer symfony/http-client 19 | ``` 20 | 21 | #### Postmark 22 | 23 | ```bash 24 | composer require symfony/postmark-mailer symfony/http-client 25 | ``` 26 | 27 | #### Amazon SES 28 | 29 | ```bash 30 | composer require aws/aws-sdk-php 31 | ``` 32 | 33 | ## Configuration 34 | 35 | Next, you can configure your mail settings from the _Manage > Settings > Mail_ admin settings page or through the mail configuration file located at `config/mail.php`. In this file, you may configure the default mail driver, mail sending options, and mail "from" address. Next, verify that your `config/services.php` configuration file contains the required credentials for your mail service. 36 | 37 | ## Writing mail 38 | 39 | In TastyIgniter, you can send mail messages using either mail templates or mail views. Mail templates can be managed through the _Design > Mail templates_ admin page. On the other hand, mail views supplied by the application or extension are stored in the `resources/views` directory within the extension's directory. 40 | 41 | Optionally, you can [register mail views in the Extension class](../advanced/mail#registering-mail-templates-layouts--partials) with the `registerMailTemplates` method. This enables automatic generation of mail templates for easy customization via the admin interface. 42 | 43 | > All mail messages support using Blade and Markdown syntax for markup. 44 | 45 | ### Creating mail views 46 | 47 | Mail views are stored in the file system, and the _mail code_ is used to represent the path to the view file. For example, sending mail with the code `vendor.extension::mail.message` would use the content in the corresponding file at `vendor/extension/views/mail/message.blade.php`. 48 | 49 | The mail view file content can include up to 3 sections: **configuration**, **plain text**, and **HTML markup**. These sections are separated using the `==` sequence. For example: 50 | 51 | ```blade 52 | subject = "Your order has been placed" 53 | == 54 | Hello {{ $customer->first_name }}, 55 | 56 | Your order has been placed successfully. 57 | 58 | Thank you for your order. 59 | == 60 |

Hello {{ $customer->first_name }},

61 | 62 |

Your order has been placed successfully.

63 | 64 |

Thank you for your order.

65 | ``` 66 | 67 | The **configuration** section sets the mail view parameters. The following configuration parameters are supported: 68 | 69 | | Parameter | Description | 70 | |-------------|---------------------------------------------------------------| 71 | | `subject` | the mail message subject, **required**. | 72 | | `layout` | the mail layout code, **optional**. Default value is default. | 73 | 74 | The **plain text** section is optional, while the **configuration** and **HTML markup** sections are required. 75 | 76 | ```blade 77 | subject = "Your order has been placed" 78 | == 79 |

Hello {{ $customer->first_name }},

80 | 81 |

Your order has been placed successfully.

82 | 83 |

Thank you for your order.

84 | ``` 85 | 86 | ### Creating mail templates 87 | 88 | Mail templates are used to define the structure of the mail message. You can create mail templates in the admin interface by navigating to _Design > Mail templates_. The `code` specified in the template is a unique identifier and cannot be changed once created. 89 | 90 | Here is an example of a simple mail template: 91 | 92 | ```blade 93 | subject: Your order has been placed 94 | == 95 |

Hello {{ $customer->first_name }},

96 | 97 |

Your order has been placed successfully.

98 | 99 |

Thank you for your order.

100 | ``` 101 | 102 | ### Creating mail layouts 103 | 104 | Mail layouts can be created by navigating to _Design > Mail templates > Layouts_ in the admin interface. Mail layouts are used to define the structure of the mail message, including the header, footer, and other common elements. The `code` specified in the layout is a unique identifier and cannot be changed once created. 105 | 106 | By default, TastyIgniter comes with a `default` mail layout that can be used as a starting point for creating custom mail layouts. 107 | 108 | Here is an example of a simple mail layout: 109 | 110 | ```blade 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 122 |
123 |

{{ $subject }}

124 |
125 | 126 |
127 | {!! $body !!} 128 |
129 | 130 | 133 | 134 | 135 | ``` 136 | 137 | In this example, the layout includes a header, main content, and footer sections. The `{{ $custom_css }}` variable is used to include custom CSS styles in the mail layout, and the `{{ $layout_css }}` variable is used to include the mail layout CSS styles defined in the admin interface. The `{{ $subject }}` and `{!! $body !!}` variables are used to include the mail message subject and content, respectively. 138 | 139 | ### Creating mail partials 140 | 141 | Mail partials are reusable components that can be included in mail templates and layouts. You can create mail partials by navigating to _Design > Mail templates > Partials_ in the admin interface. The `code` specified in the partial is a unique identifier and cannot be changed once created. 142 | 143 | Here is an example of a simple mail partial: 144 | 145 | ```blade 146 | name = "Footer" 147 | == 148 | ------------------- 149 | {{ $slot }} 150 | == 151 |

{{ $slot }}

152 | ``` 153 | 154 | You can include the mail partial in a mail template or layout using the `@partial` directive: 155 | 156 | ```blade 157 | @partial('footer') 158 |

© {{ date('Y') }} TastyIgniter

159 | @endpartial 160 | ``` 161 | 162 | ### Mail variables 163 | 164 | You may access all the data passed to the mail view or template by using the `{{ $variable }}` syntax. For example, if you pass a `$customer_name` variable to the mail view, you can access it in the view like this: 165 | 166 | ```blade 167 |

Hello {{ $customer_name }},

168 | ``` 169 | 170 | ### Attachments 171 | 172 | You can attach files to your emails using the `attach` method on the `Mail` facade. The `attach` method accepts the full path to the file as its first argument: 173 | 174 | ```php 175 | use Illuminate\Support\Facades\Mail; 176 | 177 | Mail::send('vendor.extension::mail.message', $data, function($message) { 178 | // ... 179 | $message->attach('/path/to/file'); 180 | }); 181 | ``` 182 | 183 | #### Inline Attachments 184 | 185 | To embed media in your emails, you can use the `embed` method on the `$message` variable: 186 | 187 | ```html 188 | 189 | 190 | 191 | ``` 192 | 193 | #### Embedding Raw Data 194 | 195 | To embed raw data in your emails, you can use the `embedData` method on the `$message` variable. This method accepts the raw data and the name of the file as its first and second arguments, respectively: 196 | 197 | ```html 198 | 199 | 200 | 201 | ``` 202 | 203 | ## Sending mail templates 204 | 205 | To send a mail template in TastyIgniter, you can use the `sendTemplate` method on the `Mail` facade. This method accepts the code of the mail template, an array of data to pass to the view, and a closure that receives a message instance which allows you to customize the recipients, subject, and other aspects of the mail message: 206 | 207 | ```php 208 | use Igniter\System\Helpers\MailHelper; 209 | 210 | $data = []; 211 | 212 | MailHelper::sendTemplate('vendor.extension::mail.message', $data, function($message) use ($customer) { 213 | $message->to($customer->email, $customer->name); 214 | }); 215 | ``` 216 | 217 | ### Queueing mail templates 218 | 219 | To queue a mail message, you can use the `queueTemplate` method on the `Mail` facade. This method will automatically push the email onto the queue, so it will be sent in the background by a queue worker. This can help to improve the response time of your application by offloading the sending of the email to a background process: 220 | 221 | ```php 222 | use Igniter\System\Helpers\MailHelper; 223 | 224 | $data = []; 225 | 226 | MailHelper::queueTemplate('vendor.extension::mail.message', $data, function($message) use ($customer) { 227 | $message->to($customer->email, $customer->name); 228 | }); 229 | ``` 230 | 231 | ### The `SendsMailTemplate` model trait 232 | 233 | The `SendsMailTemplate` trait provides a convenient way to send mail templates from a model. To use this trait, add it to your model class and define the `mailGetData` method that returns the mail template variables: 234 | 235 | ```php 236 | use Igniter\Flame\Mail\SendMailTemplate; 237 | 238 | class Order extends Model 239 | { 240 | use SendMailTemplate; 241 | 242 | public function mailGetData() 243 | { 244 | return [ 245 | 'customer' => $this->customer, 246 | ]; 247 | } 248 | } 249 | ``` 250 | 251 | You can use the `mailGetRecipients` method to define the recipients of the mail message. The `mailGetRecipients` method accepts a single `$recipientType` parameter and returns an array of recipients, where each recipient is an array containing the email address and name of the recipient. 252 | 253 | ```php 254 | public function mailGetRecipients($recipientType) 255 | { 256 | return [ 257 | [$this->customer->email, $this->customer->name], 258 | ]; 259 | } 260 | ``` 261 | 262 | You may also customise the reply-to address by defining the `mailGetReplyTo` method: 263 | 264 | ```php 265 | public function mailGetReplyTo() 266 | { 267 | return [$this->customer->email, $this->customer->name]; 268 | } 269 | ``` 270 | 271 | To send the mail message, use the `mailSend` method on the model instance, where the first parameter is the mail template code, and the second parameter is the recipient type: 272 | 273 | ```php 274 | $order = Order::find(1); 275 | 276 | $order->mailSend('vendor.extension::mail.message', 'customer'); 277 | ``` 278 | 279 | Possible values for the recipient type are `customer`, `location` or `admin`. 280 | 281 | ## Registering mail templates, layouts & partials 282 | 283 | To register mail templates, layouts, and partials in the Extension class, you can use the [`registerMailTemplates`](../extend/extensions#extension-class-methods), [`registerMailLayouts`](../extend/extensions#extension-class-methods), and [`registerMailPartials`](../extend/extensions#extension-class-methods) methods, respectively. These methods allow you to define the mail templates, layouts, and partials that your extension provides, making them available for customization via the admin interface: 284 | 285 | ```php 286 | public function registerMailTemplates(): array 287 | { 288 | return [ 289 | 'vendor.extension::mail.message' => 'Registered mail template message', 290 | ]; 291 | } 292 | 293 | public function registerMailLayouts(): array 294 | { 295 | return [ 296 | 'vendor.extension::mail.layouts.default' => 'Default Layout', 297 | ]; 298 | } 299 | 300 | public function registerMailPartials(): array 301 | { 302 | return [ 303 | 'vendor.extension::mail.partials.footer' => 'Footer partial', 304 | ]; 305 | } 306 | ``` 307 | 308 | ## Mail local development 309 | 310 | When developing locally, you may want to catch all outgoing mail messages and display them in the browser instead of sending them. You can use the `log` mail driver to log all outgoing mail messages to the log file. To enable this driver, set the `MAIL_DRIVER` environment variable to `log` in your `.env` file: 311 | 312 | ```bash 313 | MAIL_DRIVER=log 314 | ``` 315 | -------------------------------------------------------------------------------- /advanced/scheduling-tasks.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Scheduling Automated Tasks" 3 | section: advanced 4 | sortOrder: 330 5 | --- 6 | 7 | ## Introduction 8 | 9 | TastyIgniter's task scheduler is a powerful feature that allows you to define your command schedule within the application itself. It is built on top of the Laravel's task scheduling system, providing a fluent and expressive interface for defining your schedule. 10 | 11 | When using the scheduler, only a single Cron entry is needed on your server. This eliminates the need for multiple Cron entries and allows you to keep your schedule of tasks in source control. For more detailed information, you can refer to the [Laravel Task Scheduling documentation](https://laravel.com/docs/scheduling). 12 | 13 | > **Note**: See the [installation guide](../installation#setting-up-the-task-scheduler) for instructions on how to set up the task scheduler. 14 | 15 | Task Scheduling in TastyIgniter is the mechanism used to manage time-based tasks. Many essential features, such as update checks and auto-assignment of orders to staff, rely on the scheduler to function. 16 | 17 | ## Defining Schedules 18 | 19 | To define scheduled tasks in TastyIgniter, you can override the `registerSchedule` method within the `Extension` class. This method takes a single argument `$schedule`, which is used to define commands along with their frequency. 20 | 21 | Here's an example of how to schedule a task: 22 | 23 | ```php 24 | // Inside your extension class 25 | 26 | public function registerSchedule($schedule) 27 | { 28 | $schedule->call(function () { 29 | // Your task logic here 30 | })->daily(); 31 | } 32 | ``` 33 | 34 | In this example, a closure is scheduled to run at midnight every day. Inside the closure, you can define the logic for your task, such as executing a database query to clear a table. 35 | 36 | In addition to scheduling closure calls, you may also schedule [console commands](https://laravel.com/docs/artisan), [queued jobs](https://laravel.com/docs/queues) and operating system commands. For example, to schedule a console command, you can use the `command` method: 37 | 38 | ```php 39 | $schedule->command('cache:clear')->daily(); 40 | ``` 41 | 42 | To schedule a queued job, you can use the `job` method: 43 | 44 | ```php 45 | $schedule->job(new MyJob)->daily(); 46 | ``` 47 | 48 | The `exec` method may be used to issue a command to the operating system: 49 | 50 | ```php 51 | $schedule->exec('node /home/acme/script.js')->daily(); 52 | ``` 53 | 54 | More information on task scheduling can be found on the [Laravel Task Scheduling docs](https://laravel.com/docs/scheduling). 55 | 56 | ## Writing commands 57 | 58 | To create a new command, you may use the `make:command` Artisan command. 59 | 60 | ```bash 61 | php artisan create:command Vendor.Extension CommandName 62 | ``` 63 | 64 | This command will create a new command class in the `app/Console/Commands` directory. The generated command will include a `signature` and `description`, as well as a `handle` method where you may place your command's logic. 65 | 66 | ```php 67 | namespace Vendor\Extension\Console; 68 | 69 | class CommandName extends \Illuminate\Console\Command 70 | { 71 | protected string $signature = 'extension:command {argument : Description} {--option : Description}'; 72 | 73 | protected string $description = 'Command description'; 74 | 75 | public function handle() 76 | { 77 | // Your command logic here 78 | } 79 | } 80 | ``` 81 | -------------------------------------------------------------------------------- /advanced/testing.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Testing" 3 | section: advanced 4 | sortOrder: 390 5 | callout: This section is incomplete. Please help to improve it. 6 | --- 7 | -------------------------------------------------------------------------------- /advanced/validation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Validation" 3 | section: extend 4 | sortOrder: 370 5 | --- 6 | 7 | ## Introduction 8 | 9 | Validation is an essential part of TastyIgniter. It ensures that the data entered by users is accurate and meets the required criteria. 10 | 11 | ## Defining validation rules 12 | 13 | Validation rules are defined as an array of key-value pairs, where the key is the field name, and the value is an array containing one or more validation rules. 14 | 15 | ```php 16 | $rules = [ 17 | 'name' => ['required', 'min:5'] 18 | 'email' => ['required', 'email', 'unique:users'] 19 | ]; 20 | ``` 21 | 22 | ## Available validation rules 23 | 24 | TastyIgniter leverages Laravel's robust validation system, which provides a variety of validation rules that you can use to validate user input. For a comprehensive list of available validation rules and their usage, please refer to the [Laravel Validation Documentation](https://laravel.com/docs/validation#available-validation-rules). 25 | 26 | ## Conditional validation 27 | 28 | You can conditionally apply [validation rules](../extend/validation#available-validation-rules) based on the value of another field. To do this, you can use the `required_if`, `required_unless`, `required_with`, `required_with_all`, `required_without`, and `required_without_all` rules. For example, to require a field only if another field is present: 29 | 30 | ```php 31 | $rules = [ 32 | 'password' => 'required_with:password_confirmation' 33 | ]; 34 | ``` 35 | 36 | ## Validating arrays 37 | 38 | To validate an array of form fields, you can use the `.*` wildcard character to validate each element in the array. For example, to validate an array of email addresses: 39 | 40 | ```php 41 | $rules = [ 42 | 'emails.*' => 'required|email' 43 | ]; 44 | ``` 45 | 46 | Or, you can validate each element in the array: 47 | 48 | ```php 49 | $rules = [ 50 | 'users.*.email' => 'required|email' 51 | ]; 52 | ``` 53 | 54 | Or, to validate an array of form fields with a specific index: 55 | 56 | ```php 57 | $rules = [ 58 | 'users.0.email' => 'required|email' 59 | ]; 60 | ``` 61 | 62 | Or, if the incoming HTTP request contains a `records[name]` field, you may define rules like so: 63 | 64 | ```php 65 | $rules = [ 66 | 'records.name' => ['required', 'min:5'] 67 | ]; 68 | ``` 69 | 70 | ## Using the validator 71 | 72 | In typical scenarios within TastyIgniter, you should initially capture user input using the `post()` helper function. This input is then passed as the first argument to the `make` method, along with the validation rules as the second argument. Here’s how you can handle this in practice: 73 | 74 | ```php 75 | $validator = Validator::make($data, [ 76 | 'name' => ['required', 'min:5'] // Array 77 | ]); 78 | ``` 79 | 80 | To validate multiple fields, simply add each field and its corresponding rules to the validation array. 81 | 82 | ```php 83 | $validator = Validator::make($data, [ 84 | 'name' => 'required', 85 | 'password' => ['required', 'min:8'], 86 | 'email' => ['required', 'email', 'unique:users'] 87 | ]); 88 | ``` 89 | 90 | ### Checking the validation results 91 | 92 | Once you've created a Validator instance, you can use the `fails` (or `passes`) method to execute the validation checks. 93 | 94 | ```php 95 | if ($validator->fails()) { 96 | // The given data did not pass validation 97 | } 98 | ``` 99 | 100 | If validation has failed, you may retrieve the error messages from the validator. 101 | 102 | ```php 103 | $messages = $validator->messages(); 104 | ``` 105 | 106 | You can also access an array of the failed validation rules, without their accompanying messages, by using the `failed` method. 107 | 108 | ```php 109 | $failed = $validator->failed(); 110 | ``` 111 | 112 | ### Throwing validation exceptions 113 | 114 | You can also handle validation errors by throwing Laravel's `\Illuminate\Validation\ValidationException`. This exception automatically returns the appropriate HTTP response based on the type of request. 115 | 116 | For a traditional HTTP request, it triggers a redirect response to the previous URL, along with the validation errors. If the request is an AJAX request, it instead returns a JSON response that includes the validation errors. 117 | 118 | ```php 119 | $validator = Validator::make($data, $rules); 120 | 121 | if ($validator->fails()) { 122 | throw new \Illuminate\Validation\ValidationException($validator); 123 | } 124 | ``` 125 | 126 | As a shorter way to validate the form similar to the example above, you can use the `validate` method directly. 127 | 128 | ```php 129 | $data = Validator::validate($data, $rules); 130 | ``` 131 | 132 | ### Customizing the error messages 133 | 134 | You can customize the error messages for each field by passing an array of custom messages as the third argument to the `make` method. 135 | 136 | ```php 137 | $validator = Validator::make($data, $rules, [ 138 | 'name.required' => lang('author.extension::default.error_name') 139 | ]); 140 | ``` 141 | 142 | ### Customizing the validation attributes 143 | 144 | You can customize the field names used in the validation error messages by passing an array of custom attributes as the fourth argument to the `make` method. 145 | 146 | ```php 147 | $validator = Validator::make($data, $rules, $messages, [ 148 | 'name' => lang('author.extension::default.label_name') 149 | ]); 150 | ``` 151 | 152 | ## Using the `Request` facade 153 | 154 | Another approach is to use the `Request` facade to validate all user inputs directly. This method simplifies the process by eliminating the need to manually supply the data; you only need to provide the validation rules as the first argument. The `validate` method then returns the filtered user data, including only the attributes and values that were successfully validated. 155 | 156 | ```php 157 | $data = Request::validate([ 158 | 'name' => ['required', 'min:5'], 159 | 'email' => ['required', 'email', 'unique:users'] 160 | ]); 161 | ``` 162 | 163 | In the example above, the `validate` method will return an array containing the validated data. If the validation fails, an exception will be thrown, and the user will be redirected back to the previous page with the validation errors. 164 | 165 | ## Using form requests 166 | 167 | Form requests provide a convenient way to validate incoming HTTP requests in TastyIgniter. By defining validation rules and error messages in a single location, you can maintain and reuse the validation logic across multiple controller actions. This approach helps to keep your controller actions clean and readable by moving the validation logic out of the controller and into a separate class. 168 | 169 | Form Requests are typically stored in the `src/Http/Requests` directory of an extension. 170 | 171 | ### Creating a form request 172 | 173 | Form requests are custom request classes that extends the `Igniter\System\Classes\FormRequest` class, containing both validation and authorization logic. Here is an example of a simple form request class: 174 | 175 | ```php 176 | namespace Author\Extension\Http\Requests; 177 | 178 | class RecordRequest extends \Igniter\System\Classes\FormRequest 179 | { 180 | public function rules(): array 181 | { 182 | return [ 183 | 'name' => ['required', 'min:5'], 184 | 'email' => ['required', 'email', 'unique:users'] 185 | ]; 186 | } 187 | } 188 | ``` 189 | 190 | ### Applying the form request 191 | 192 | To use the form request in your admin controller to validate form fields. You can add the form request class to the controller's `$formConfig` property along with the `FormController` action class. 193 | 194 | ```php 195 | namespace Author\Extension\Http\Controllers; 196 | 197 | class MyController extends \Igniter\Admin\Controllers\Controller 198 | { 199 | public array $implement = [\Igniter\Admin\Http\Actions\FormController::class]; 200 | 201 | public $formConfig = [ 202 | 'request' => \Author\Extension\Http\Requests\RecordRequest::class, 203 | 'create' => [ 204 | // ... 205 | ], 206 | ]; 207 | } 208 | ``` 209 | 210 | ### Performing additional validation 211 | 212 | After the initial validation, you may need to perform further validation. You can do this by calling the form request's `after` function. 213 | 214 | The `after` function should return an array of callables or closures that will be executed after validation is complete. The specified callables will receive an `Illuminate\Validation\Validator` object, allowing you to issue extra error messages as needed: 215 | 216 | ```php 217 | namespace Author\Extension\Http\Requests; 218 | 219 | use Illuminate\Validation\Validator; 220 | 221 | class RecordRequest extends \Igniter\System\Classes\FormRequest 222 | { 223 | public function after(Validator $validator): array 224 | { 225 | return [ 226 | new \Author\Extension\Validation\ValidateUserStatus, 227 | function ($validator) { 228 | if ($this->somethingElseIsInvalid()) { 229 | $validator->errors()->add('field', 'Something is wrong with this field!'); 230 | } 231 | }, 232 | ]; 233 | } 234 | } 235 | ``` 236 | 237 | ### Stopping on the first validation failure 238 | 239 | By default, the form request will continue to validate all fields, even if one field fails validation. If you want to stop validation on the first failure, you can set the `stopOnFirstFailure` property to `true` in the form request class. 240 | 241 | ```php 242 | protected $stopOnFirstFailure = true; 243 | ``` 244 | 245 | ### Customizing the redirect location 246 | 247 | By default, if validation fails, the user is redirected back to the previous page. However, you can customize this behavior in your form request class. To specify a different redirect location, you can override the `$redirect` property. Alternatively, if you prefer to redirect users to a specific named route, you can set the `$redirectRoute` property like so: 248 | 249 | ```php 250 | protected $redirect = '/custom-url'; // Redirects to a custom URL 251 | 252 | // Or 253 | 254 | protected $redirectRoute = 'route-name'; // Redirects to a named route 255 | ``` 256 | 257 | ### Customizing the error messages 258 | 259 | You can customize the error messages for each field by overriding the `messages` method in the form request class. 260 | 261 | ```php 262 | public function messages(): array 263 | { 264 | return [ 265 | 'name.required' => lang('author.extension::default.error_name') 266 | ]; 267 | } 268 | ``` 269 | 270 | ### Customizing the validation attributes 271 | 272 | You can customize the field names used in the validation error messages by overriding the `attributes` method in the form request class. 273 | 274 | ```php 275 | public function attributes(): array 276 | { 277 | return [ 278 | 'name' => lang('author.extension::default.label_name') 279 | ]; 280 | } 281 | ``` 282 | -------------------------------------------------------------------------------- /customize/layouts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Layouts" 3 | section: customize 4 | sortOrder: 120 5 | --- 6 | 7 | ## Introduction 8 | 9 | In TastyIgniter, a theme layout is a template that wraps around your content. It allows you to have your template source code in one place so that you don't have to repeat things like your header and footer on every page. 10 | 11 | Layout files live in the **resources/views/_layouts** subdirectory of a theme directory. For example: 12 | 13 | ```yaml 14 | acme/ <=== Theme vendor directory 15 | purple/ <=== Theme directory 16 | resources/ 17 | views/ 18 | _layouts/ <=== Layouts subdirectory 19 | default.blade.php <=== Layout template file 20 | ``` 21 | 22 | The convention is to have a basic layout called `default.blade.php` and be used by other pages as required. Within the layout file, you should use the `@themePage` tag to display the content of the page. 23 | 24 | ```blade 25 | 26 | 27 | 28 | 29 | 30 | 31 | @themePage 32 | 33 | 34 | ``` 35 | 36 | ## Front matter section 37 | 38 | The front matter section is optional for layouts. The optional front matter parameter `description` is used in the Admin Interface. 39 | 40 | | Variable | Description | 41 | |----------------|---------------------------------------------------------------| 42 | | `description` | Use this variable as the layout title or description or both. | 43 | 44 | Here is an example of a layout file: 45 | 46 | ```blade 47 | --- 48 | description: My first layout example 49 | --- 50 | 51 | 52 | 53 | 54 | 55 | 56 | @themePage 57 | 58 | 59 | ``` 60 | 61 | You can set the front matter in layouts, the only difference is you use the `$this->layout` object instead of the `$this->page`. For example: 62 | 63 | ```blade 64 | --- 65 | description: My first layout example 66 | --- 67 |

{{ $this->layout->description }}

68 | 69 | @themePage 70 | ``` 71 | 72 | You can pass your own variables to the layout by defining them in the front matter section. For example: 73 | 74 | ```blade 75 | --- 76 | description: My first layout example 77 | food: "Pizza" 78 | --- 79 |

{{ $this->layout->food }}

80 | ``` 81 | 82 | ## PHP code section 83 | 84 | The PHP code section and front matter are both optional, but the HTML markup section is required. For example: 85 | 86 | ```blade 87 | --- 88 | description: My first layout example 89 | --- 90 | 96 | --- 97 |

{{ $this->layout->hello }}

98 | ``` 99 | 100 | ## Using layouts 101 | 102 | To use a layout, you need to specify the layout file in the front matter of your page. For example, if you have a layout file called `default.blade.php`, you would specify `default` as the layout in the front matter of your page. For example: 103 | 104 | ```blade 105 | --- 106 | permalink: "/page" 107 | layout: default 108 | --- 109 |

This is the content of my page

110 | ``` 111 | 112 | ## Including partials 113 | 114 | [Partials](../customize/partials) are reusable Blade markup blocks that can be used in layouts, pages and other partials. Here, we'll cover the basics of rendering partials within a layout. 115 | 116 | You can render a partial in a layout using Blade's `@include` directive. For example: 117 | 118 | ```blade 119 | 120 | 121 | 122 | 123 | 124 | 125 | @include('acme.purple::includes.header') 126 | 127 | @themePage 128 | 129 | 130 | ``` 131 | 132 | > `acme.purple` is the theme code specified in the [theme manifest file](../customize/themes#theme-manifest) and `includes.header` is the partial located in `resources/views/includes/header.blade.php`. 133 | 134 | You can pass variables to partials by defining them in the `@include` directive after the partial name. For example: 135 | 136 | ```blade 137 | @include('acme.purple::includes.header', ['pages' => $pages]) 138 | ``` 139 | 140 | You can access variables within the partial like any other template variable: 141 | 142 | ```blade 143 | 148 | ``` 149 | 150 | ## Rendering components 151 | 152 | [Components](../extend/components) are reusable Blade markup blocks combining state and behavior that serve as building blocks in layouts or pages. Here, we'll cover the basics of rendering [Livewire components](../extend/components#livewire-component) within a layout. 153 | 154 | Livewire component consists of two files. The first is the component class `src/Livewire/HelloBlock.php` and the second is the component template file `resources/views/livewire/hello-block.blade.php`. 155 | 156 | You can render a Livewire component in a layout using the `` syntax. For example: 157 | 158 | ```blade 159 | 160 | 161 | 162 | 163 | 164 | 165 | @themePage 166 | 167 | 168 | 169 | 170 | ``` 171 | 172 | For more on components, see the [Components](../extend/components) documentation. 173 | 174 | ## Execution life cycle 175 | 176 | Specific functions can be defined in the layouts PHP code section for handling the page execution life cycle: `onInit`, `onStart` and `onEnd`. 177 | 178 | The `onInit` function is executed when all [components](../extend/components) are initialized and before AJAX requests are handled. The `onStart` function is executed at the start of the execution of the page. The `onEnd` function executes after the page renders. 179 | 180 | ```blade 181 | --- 182 | name: My First Layout 183 | description: My first layout example 184 | --- 185 | 191 | --- 192 |

{{ $this->layout->hello }}

193 | ``` 194 | 195 | In this example, the `onStart` function is executed at the start of the execution of the page and sets a variable `hello` that is then used in the layout and can be used in the page content. 196 | 197 | ## Inject page assets 198 | 199 | The controller's `addCss` and `addJs` methods allow you to inject assets files (CSS and JavaScript) to layouts. This can be done in the `onStart` function defined in a layout template PHP section. 200 | 201 | ```php 202 | --- 203 | addCss('assets/css/my-style.css'); 207 | $this->addJs('assets/js/my-script.js'); 208 | } 209 | ?> 210 | --- 211 | ``` 212 | 213 | In order to output the injected assets on pages and layouts use the `@themeStyles` and `@themeScripts` tags. Example: 214 | 215 | ```blade 216 | 217 | ... 218 | @themeStyles 219 | 220 | 221 | ... 222 | @themeScripts 223 | 224 | ``` 225 | 226 | > The page output in the above example will also include all assets files registered within the `resources/meta/assets.json` manifest file. 227 | -------------------------------------------------------------------------------- /customize/markup-guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Markup Guide" 3 | section: customize 4 | sortOrder: 150 5 | --- 6 | 7 | ## Introduction 8 | 9 | TastyIgniter uses the [Blade template engine](https://laravel.com/docs/blade) to render views. Blade is a simple, yet powerful templating engine provided with Laravel. Unlike other popular PHP templating engines, Blade does not restrict you from using plain PHP code in your views. All Blade views are compiled into plain PHP code and cached until they are modified, meaning Blade adds essentially zero overhead to your application. 10 | 11 | Blade views are typically stored in the `resources/views` directory of your theme or extension. The Blade file extension is `.blade.php`. 12 | 13 | ## Variables 14 | 15 | The following variables are available in all layout and page templates: 16 | 17 | - `$this->page` - The current page object. 18 | - `$this->layout` - The current layout object. 19 | - `$this->theme` - The current theme object. 20 | - `$this->param` - The current request parameters. 21 | 22 | Use these variables to access the properties of the current page, layout, theme, or request parameters. For example, to access the title of the current page, use `$this->page->title`. 23 | 24 | You may display variables in your templates using the `{{ $variable }}` syntax. For example, to display the title of the current page, use `{{ $this->page->title }}`. 25 | 26 | ## Directives 27 | 28 | Directives are a unique feature to Laravel Blade and are prefixed with `@` character. TastyIgniter extends the [Blade template engine](https://laravel.com/docs/blade) by adding a number of directives and variables. 29 | 30 | > **Note:** The Blade directives provided by TastyIgniter are not the same as the Laravel Blade directives. The Blade directives provided by TastyIgniter are specific to TastyIgniter and are not compatible with Laravel. 31 | 32 | ### `@themePage` 33 | 34 | The `@themePage` tag renders the contents of a page into a layout template. The directive has no parameters. 35 | 36 | ```blade 37 | @themePage 38 | ``` 39 | 40 | ### `@themePartial` 41 | 42 | The `@themePartial` directive renders a partial template file located under the `_partials` subdirectory of a theme. If you are rendering blade views from the other subdirectory of a theme, use Blade's `@include` directive instead. 43 | 44 | The directive has a single required parameter - the name of the partial file without the `.blade.php` extension. You can specify the name of the subdirectory if you refer a partial from a subdirectory `@partial('directory.partial-name')`. 45 | 46 | ```blade 47 | @themePartial('partial-name') 48 | ``` 49 | 50 | The directive also accepts a second parameter - an optional array of data to pass to the partial. For example: 51 | 52 | ```blade 53 | @themePartial('partial-name', ['status' => 'complete']) 54 | ``` 55 | 56 | If you need to render a partial only if it exists, you can use the `@themePartialIf` directives. 57 | 58 | ```blade 59 | @themePartialIf('partial-name', ['status' => 'complete']) 60 | ``` 61 | 62 | If you need to render a partial only if a boolean condition evaluates to `true` or `false`, you can use the `@themePartialWhen` and `@themePartialUnless` directives. 63 | 64 | ```blade 65 | @themePartialWhen($expression, 'partial-name', ['status' => 'complete']) 66 | 67 | @themePartialUnless($expression, 'partial-name', ['status' => 'complete']) 68 | ``` 69 | 70 | If you need to render the first partial that exists from a given array of partials, you can use the `@themePartialFirst` directive. 71 | 72 | ```blade 73 | @themePartialFirst(['custom.partial-name', 'partial-name'], ['status' => 'complete']) 74 | ``` 75 | 76 | ### `@themeComponent` 77 | 78 | The `@themeComponent` directive renders a Theme component. The directive has a single required parameter - the name of the component. To render a Livewire component, use the `@livewire` directive instead. 79 | 80 | ```blade 81 | @themeComponent('componentName') 82 | ``` 83 | 84 | The directive also accepts a second parameter - an optional array of data to pass to the component. For example: 85 | 86 | ```blade 87 | @themeComponent('componentName', ['status' => 'complete']) 88 | ``` 89 | 90 | If you need to render a component only if it exists, you can use the `@themeComponentIf` directives. 91 | 92 | ```blade 93 | @themeComponentIf('componentName', ['status' => 'complete']) 94 | ``` 95 | 96 | If you need to render a component only if a boolean condition evaluates to `true` or `false`, you can use the `@themeComponentWhen` and `@themeComponentUnless` directives. 97 | 98 | ```blade 99 | @themeComponentWhen($expression, 'componentName', ['status' => 'complete']) 100 | 101 | @themeComponentUnless($expression, 'componentName', ['status' => 'complete']) 102 | ``` 103 | 104 | If you need to render the first component that exists from a given array of components, you can use the `@themeComponentFirst` directive. 105 | 106 | ```blade 107 | @themeComponentFirst(['componentNameExtended', 'componentName'], ['status' => 'complete']) 108 | ``` 109 | 110 | ### `@themeContent` 111 | 112 | The `@themeContent` directive renders the contents of a static template file located under the `_content` subdirectory of a theme. If you are rendering blade views from the other subdirectory of a theme, use Blade's `@include` directive instead. 113 | 114 | The directive has a single required parameter - the name of the content template file without the `.htm` extension. You can specify the name of the subdirectory if you refer a content from a subdirectory `@themeContent('directory.content-name')`. 115 | 116 | ```blade 117 | @themeContent('content-name') 118 | ``` 119 | 120 | ### `@themeStyles` 121 | 122 | The `@themeStyles` directive renders all stylesheets that are defined during the rendering of the page. This includes all `.css` files registered within the `resources/meta/assets.json` manifest file, as well as stylesheets injected using the controller's `addCss` method. The directive has no parameters. 123 | 124 | ```blade 125 | 126 | ... 127 | @themeStyles 128 | 129 | 130 | ... 131 | 132 | ``` 133 | 134 | ### `@themeScripts` 135 | 136 | The `@themeScripts` directive renders all scripts that are defined during the rendering of the page. This includes all `.js` files registered within the `resources/meta/assets.json` manifest file, as well as scripts injected using the controller's `addJs` method. The directive has no parameters. 137 | 138 | ```blade 139 | 140 | ... 141 | 142 | 143 | ... 144 | @themeScripts 145 | 146 | ``` 147 | 148 | ## Blade directives 149 | 150 | Blade also provides a variety of directives for working with control structures, such as loops and conditional statements. 151 | 152 | ### If statements 153 | 154 | You can construct if statements using the Blade directives `@if`, `@elseif`, `@else`, and `@endif`. 155 | 156 | ```blade 157 | @if (count($categories) === 1) 158 | I have one category! 159 | @elseif (count($categories) > 1) 160 | I have multiple categories! 161 | @else 162 | I don't have any categories! 163 | @endif 164 | 165 | @unless (Cart::content()) 166 | Cart is empty. 167 | @endunless 168 | 169 | @isset($categories) 170 | // $categories is defined and is not null... 171 | @endisset 172 | 173 | @empty($categories) 174 | // $categories is "empty"... 175 | @endempty 176 | ``` 177 | 178 | ### Switch statements 179 | 180 | You can construct switch statements using the Blade directives `@switch`, `@case`, `@break`, `@default`, and `@endswitch`. 181 | 182 | ```blade 183 | @switch($i) 184 | @case(1) 185 | First case... 186 | @break 187 | 188 | @case(2) 189 | Second case... 190 | @break 191 | 192 | @default 193 | Default case... 194 | @endswitch 195 | ``` 196 | 197 | ### Loops 198 | 199 | You can construct loops using the Blade directives `@for`, `@foreach`, `@forelse`, `@while`, and `@endfor`, `@endforeach`, `@endforelse`, `@endwhile`. 200 | 201 | ```blade 202 | @for ($i = 0; $i < 10; $i++) 203 | The current value is {{ '{{ $i }}' }} 204 | @endfor 205 | 206 | @foreach ($categories as $category) 207 |

This is category {{ '{{ $category->name }}' }}

208 | @endforeach 209 | 210 | @forelse ($categories as $category) 211 |
  • {{ '{{ $category->name }}' }}
  • 212 | @empty 213 |

    No categories

    214 | @endforelse 215 | 216 | @while (true) 217 |

    I'm looping forever.

    218 | @endwhile 219 | ``` 220 | -------------------------------------------------------------------------------- /customize/media-manager.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Media manager" 3 | section: customize 4 | sortOrder: 170 5 | --- 6 | 7 | ## Introduction 8 | 9 | TastyIgniter provides a flexible and configurable media management system. The Media Manager allows you to upload, organize, and manage media files. By default, Media Manager works with the `storage/app/public/media` directory. It is possible to use external storages such as Amazon S3. 10 | 11 | ## Configuring external storage 12 | 13 | To use the S3 driver, install the required dependencies via Composer. 14 | 15 | ```bash 16 | composer require league/flysystem-aws-s3-v3 "^3.0" --with-all-dependencies 17 | ``` 18 | 19 | Configure the S3 driver using the `.env` file. 20 | 21 | ```dotenv 22 | FILESYSTEM_DISK=s3 23 | AWS_ACCESS_KEY_ID= 24 | AWS_SECRET_ACCESS_KEY= 25 | AWS_DEFAULT_REGION=us-east-1 26 | AWS_BUCKET= 27 | AWS_USE_PATH_STYLE_ENDPOINT=false 28 | ``` 29 | 30 | Using S3 compatible storage services like DigitalOcean Spaces, configure the driver using the `.env` file. 31 | 32 | ```dotenv 33 | AWS_ENDPOINT=https://nyc3.digitaloceanspaces.com 34 | ``` 35 | 36 | ## Media configuration options 37 | 38 | The Media Manager in TastyIgniter can be configured according to your preference. You can do this by editing the `config/igniter-system.php` file. 39 | 40 | The `config/igniter-system.php` configuration file contains several options that allow you to customize the behavior of the Media Manager: 41 | 42 | - `media`: This section contains the configuration options for the media uploaded files. 43 | - `disk`: This option specifies the storage disk that the Media Manager should use for uploads. By default, it uses the `public` disk. 44 | - `folder`: This option specifies the folder within the storage disk where the media files should be stored. By default, it is set to `media/uploads/`. 45 | - `path`: This option specifies the path to prepend to the file name when generating the file URL. By default, it is set to `media/uploads/`. 46 | - `max_upload_size`: This option specifies the maximum size (in kilobytes) of the files that can be uploaded through the Media Manager. By default, it is set to 1500 (1.5 MB). 47 | - `enable_uploads`: This option allows you to enable or disable file uploads. By default, it is set to `true`. 48 | - `enable_new_folder`: This option allows you to enable or disable the creation of new folders through the Media Manager. By default, it is set to `true`. 49 | - `enable_rename`: This option allows you to enable or disable the renaming of files and folders through the Media Manager. By default, it is set to `true`. 50 | - `enable_move`: This option allows you to enable or disable the moving of files and folders through the Media Manager. By default, it is set to `true`. 51 | - `enable_copy`: This option allows you to enable or disable the copying of files and folders through the Media Manager. By default, it is set to `true`. 52 | - `enable_delete`: This option allows you to enable or disable the deletion of files and folders through the Media Manager. By default, it is set to `true`. 53 | 54 | - `attachment`: This section contains the configuration options for the attachment files. 55 | - `disk`: This option specifies the storage disk that the Media Manager should use for attachments. By default, it uses the `public` disk. 56 | - `folder`: This option specifies the folder within the storage disk where the attachment files should be stored. By default, it is set to `media/attachments/`. 57 | - `path`: This option specifies the path to prepend to the file name when generating the file URL. By default, it is set to `media/attachments/`. 58 | 59 | These options provide a high level of control over how media files are handled in your application. You can customize these options according to your application's requirements. 60 | 61 | To publish the config file to `config/igniter-system.php` run: 62 | 63 | ```bash 64 | php artisan vendor:publish --tag="igniter-config" 65 | ``` 66 | 67 | For more information on configuring external storage, check out Laravel's filesystem docs. 68 | 69 | ## Defining media fields 70 | 71 | Media fields allow you to choose or attach images, videos, and other media files from the library. You can define media fields in the configuration file of your model and use them in forms. 72 | 73 | To use a media field in a form, you can use the `mediafinder` form widget. For a single image field, you can use the `mediafinder` form widget with the `isMulti` set to `false`: 74 | 75 | ```php 76 | return [ 77 | 'fields' => [ 78 | 'featured_image' => [ 79 | 'label' => 'Featured Image', 80 | 'type' => 'mediafinder', 81 | 'isMulti' => false, 82 | ], 83 | ], 84 | ]; 85 | ``` 86 | 87 | For a multiple images field, you can use the `mediafinder` form widget with the `isMulti` set to `true`: 88 | 89 | ```php 90 | return [ 91 | 'fields' => [ 92 | 'gallery_images' => [ 93 | 'label' => 'Gallery Images', 94 | 'type' => 'mediafinder', 95 | 'isMulti' => true, 96 | ], 97 | ], 98 | ]; 99 | ``` 100 | 101 | ## Attaching media fields to a model 102 | 103 | Media files can be attached to a model using the `Igniter\Flame\Database\Attach\HasMedia` trait and defining the `$mediable` property. For example, to attach single media files to the `gallery_images` field of `MenuItem` model, you can use the following code 104 | 105 | ```php 106 | class MenuItem extends Model 107 | { 108 | use \Igniter\Flame\Database\Attach\HasMedia; 109 | 110 | public array $mediable = ['featured_image']; 111 | } 112 | ``` 113 | 114 | To attach multiple media files to the `gallery_images` field of `MenuItem` model, you can use the following code: 115 | 116 | ```php 117 | class MenuItem extends Model 118 | { 119 | use \Igniter\Flame\Database\Attach\HasMedia; 120 | 121 | public array $mediable = ['gallery_images' => ['multiple' => true]]; 122 | } 123 | ``` 124 | 125 | ## Displaying media on your site 126 | 127 | To display a media file in a template, you can use the `media` method of the media model. 128 | 129 | For a single media field, you can display the image using the following code: 130 | 131 | ```blade 132 | Featured Image 133 | ``` 134 | 135 | For a multiple media field, you can display the images using a loop: 136 | 137 | ```blade 138 | @foreach($model->gallery_images as $media) 139 | Gallery Image 140 | @endforeach 141 | ``` 142 | 143 | For a single attached media field, you can display the image using the following code: 144 | 145 | ```blade 146 | Featured Image 147 | ``` 148 | 149 | For a multiple attached media field, you can display the images using a loop: 150 | 151 | ```blade 152 | @foreach($model->gallery_images as $media) 153 | Gallery Image 154 | @endforeach 155 | ``` 156 | 157 | The `getThumb` method accepts an optional parameter to specify the size of the thumbnail. For example, to get a thumbnail of size 200x200, you can use: 158 | 159 | ```blade 160 | Gallery Image 164 | ``` 165 | -------------------------------------------------------------------------------- /customize/pages.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Pages" 3 | section: customize 4 | sortOrder: 110 5 | --- 6 | 7 | ## Introduction 8 | 9 | All websites are made up of pages. In TastyIgniter, pages are the most basic building block for content. Page file names or directory structure do not affect the routing, but naming your pages according to the page function is a good idea. 10 | 11 | Pages files live in the **resources/views/_pages** subdirectory of a theme directory. For example, a page file named `home.blade.php` located in the **_pages** directory of a theme. 12 | 13 | ```yaml 14 | acme/ <=== Theme vendor directory 15 | purple/ <=== Theme directory 16 | resources/ 17 | views/ 18 | _pages/ <=== Pages subdirectory 19 | home.blade.php <=== Page template file 20 | ``` 21 | 22 | A page file consists of three sections: front matter, PHP code (optional), and HTML markup. Here's an example of a page file: 23 | 24 | ```blade 25 | --- 26 | title: My Home Page 27 | description: "My home page example" 28 | permalink: "/" 29 | layout: default 30 | --- 31 | 37 | --- 38 |

    {{ $hello }}, this is the content of my page

    39 | ``` 40 | 41 | ## Front matter section 42 | 43 | The front matter section is **required** for pages. A number of predefined parameters can be set in the front-matter of a page. 44 | 45 | | Variable | Description | 46 | |---------------|----------------------------------------------------------------------------------------------------------------| 47 | | `title` | Use this variable as the page title. | 48 | | `description` | Use this variable as the page description. | 49 | | `permalink` | Set this variable and use it as the page URL, **required**. | 50 | | `layout` | If set, this specifies the layout file to be used. Use the name of the layout file without the file extension. | 51 | 52 | You can also define your own front-matter variables that can be accessed in PHP. For example, if you set a `food` variable, you can use it on your page: 53 | 54 | ```blade 55 | --- 56 | food: "Pizza" 57 | --- 58 |

    {{ $this->page->food }}

    59 | ``` 60 | 61 | ### Permalink variable 62 | 63 | The `permalink` variable is used to set the URL of the page. It can contain parameters and can be set using front matter. Permalinks should start with the forward slash character `/`. 64 | 65 | For example, you might have a page on your site located at `resources/views/_pages/common/about.blade.php` and you want the url to be `/about`. In front matter of the page you would set: 66 | 67 | ```blade 68 | permalink: "/about" 69 | ``` 70 | 71 | ### Permalink parameters 72 | 73 | For any address such as `/pages/my-first-page`, a page with the **permalink pattern** defined in the following example would be displayed. 74 | 75 | ```blade 76 | permalink: "/pages/:title" - this will match /pages/page-title 77 | ``` 78 | 79 | To make a parameter optional add the **question mark** after its name: 80 | 81 | ```blade 82 | permalink: "/pages/:title?" - this will match /pages/page-title OR /pages 83 | ``` 84 | 85 | Parameters can not be optional in the **middle** of the permalink: 86 | 87 | ```blade 88 | permalink: "/pages/:title?/:slug" - this will match /pages/page-title/page-slug 89 | ``` 90 | 91 | A default value can be specified after the **question mark** and used as fallback values in case the real parameter value is not presented. **Default values can not contain any asterisks, pipe symbols or question marks.** 92 | 93 | ```blade 94 | permalink: "/pages/:title?default" - this will match /pages/page-title OR /pages/default 95 | ``` 96 | 97 | You could also use regular expressions to validate parameters: 98 | 99 | ```blade 100 | permalink: "/pages/:title|^[a-z0-9\-]+$" - this will match /pages/page-title 101 | ``` 102 | 103 | A special wildcard parameter can be used by placing the asterisk after the parameter: 104 | 105 | ```blade 106 | permalink: "/pages/:title*/:slug" - this will match /pages/page-title/child/page/page-slug 107 | ``` 108 | 109 | ## PHP code section 110 | 111 | The PHP code section is optional, but the front matter and the HTML markup sections are both required. For example: 112 | 113 | ```blade 114 | --- 115 | permalink: "/" 116 | --- 117 | 123 | --- 124 |

    {{ $this->page->hello }}

    125 | ``` 126 | 127 | ## Using layouts 128 | 129 | To use a layout, you need to specify the layout file in the front matter of your page. For example, if you have a layout file called `default.blade.php`, you would specify `default` as the layout in the front matter of your page. For example: 130 | 131 | ```blade 132 | --- 133 | permalink: "/" 134 | layout: default 135 | --- 136 |

    This is the content of my page

    137 | ``` 138 | 139 | ## Including partials 140 | 141 | [Partials](../customize/partials) are reusable Blade markup blocks that can be used in pages, layouts and other partials. Here, we'll cover the basics of rendering partials within a page. 142 | 143 | You can render a partial in a page using Blade's `@include` directive. For example: 144 | 145 | ```blade 146 | --- 147 | permalink: "/" 148 | food: "Pizza" 149 | --- 150 | @include('acme.purple::includes.sidebar') 151 | 152 |

    {{ $this->page->food }}

    153 | ``` 154 | 155 | > `acme.purple` is the theme code specified in the [theme manifest file](../customize/themes#theme-manifest) and `includes.sidebar` is the partial located in `resources/views/includes/sidebar.blade.php`. 156 | 157 | You can pass variables to partials by defining them in the `@include` directive after the partial name. For example: 158 | 159 | ```blade 160 | @include('acme.purple::includes.header', ['pages' => $pages]) 161 | ``` 162 | 163 | You can access variables within the partial like any other template variable: 164 | 165 | ```blade 166 |
      167 | @foreach($pages as $page) 168 |
    • {{ $page->name }}
    • 169 | @endforeach 170 |
    171 | ``` 172 | 173 | ## Rendering components 174 | 175 | [Components](../extend/components) are reusable Blade markup blocks combining state and behavior that serve as building blocks in layouts or pages. Here, we'll cover the basics of rendering [Livewire components](../extend/components#livewire-component) within a page. 176 | 177 | Livewire component consists of two files. The first is the component class `src/Livewire/HelloBlock.php` and the second is the component template file `resources/views/livewire/hello-block.blade.php`. 178 | 179 | You can render a Livewire component in a page using the `` syntax. For example: 180 | 181 | ```blade 182 | --- 183 | permalink: "/" 184 | food: "Pizza" 185 | --- 186 | 187 | 188 |

    {{ $this->page->food }}

    189 | ``` 190 | 191 | For more on components, see the [Components](../extend/components) documentation. 192 | 193 | ## Execution life cycle 194 | 195 | In the PHP code section of pages and layouts there are specific functions: `onInit`, `onStart` and `onEnd`. 196 | 197 | The function `onInit` is executed when all [components](../extend/components) are initialized and before AJAX requests are handled. The `onStart` function is executed at the start of the execution of the page. The `onEnd` function is executed before the page renders and the page components executed. 198 | 199 | ```blade 200 | --- 201 | 207 | --- 208 |

    {{ $this->page->hello }}

    209 | ``` 210 | 211 | ## Custom response 212 | 213 | Return a response from any of the methods defined in the execution life cycle. The life cycle methods have the ability to halt the process when a response is returned. Return `Hello world!` string to the browser without loading any page contents with the following example: 214 | 215 | ```php 216 | --- 217 | 223 | --- 224 | ``` 225 | 226 | Below is an example of triggering a redirect using the `Redirect` facade: 227 | 228 | ```php 229 | --- 230 | 236 | --- 237 | ``` 238 | 239 | ## Inject page assets 240 | 241 | The controller's `addCss` and `addJs` methods allow you to inject assets files (CSS and JavaScript) to pages. This can be done in the `onStart` function defined in a page template PHP section. 242 | 243 | ```php 244 | --- 245 | addCss('assets/css/my-style.css'); 249 | $this->addJs('assets/js/my-script.js'); 250 | } 251 | ?> 252 | --- 253 | ``` 254 | 255 | In order to output the injected assets on pages or layouts use the `@themeStyles` and `@themeScripts` tags. Example: 256 | 257 | ```blade 258 | 259 | ... 260 | @themeStyles 261 | 262 | 263 | ... 264 | @themeScripts 265 | 266 | ``` 267 | 268 | > The page output in the above example will also include all assets files registered within the `resources/meta/assets.json` manifest file. 269 | -------------------------------------------------------------------------------- /customize/partials.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Partials" 3 | section: customize 4 | sortOrder: 130 5 | --- 6 | 7 | ## Introduction 8 | 9 | Partials include reusable Blade markup blocks that can be used anywhere on the website. Partials are extremely useful for page sections that appear on multiple pages or layouts. 10 | 11 | While you can use Blade's `@include` directive, [components](../extend/components) provide similar functionality and have several advantages over the `@include` directive, including data and attribute binding. 12 | 13 | Partial files live in the **resources/views/includes** subdirectory of a theme directory. For example: 14 | 15 | ## Creating a partial 16 | 17 | To create a partial, you need to create a new `.blade.php` file in the `resources/views/includes` directory of your theme. The name of the file will be the name of the partial. For example, to create a partial named `header`, you would create a file named `header.blade.php`. 18 | 19 | ```yaml 20 | acme/ <=== Theme vendor directory 21 | purple/ <=== Theme directory 22 | resources/ 23 | views/ 24 | includes/ <=== Partials subdirectory 25 | header.blade.php <=== Partial template file 26 | ``` 27 | 28 | Inside this file, you can write any Blade markup that you want to reuse across your theme. 29 | 30 | ## Rendering partials 31 | 32 | The Blade directive `@include('partial-name')` renders a partial. The directive has a single parameter that is required - the name of the partial file without the `.blade.php` extension. You can specify the name of the subdirectory if you refer a partial from a subdirectory `@include('directory.partial-name')`. You may use the `@include` directive within a page, layout or other partials. An example of a page rendering a partial: 33 | 34 | ```blade 35 | 38 | ``` 39 | 40 | ### Passing data to partials 41 | 42 | You may pass variables to partials by defining them in the `@include` directive after the partial name: 43 | 44 | ```blade 45 | 48 | ``` 49 | 50 | You can access variables within the partial like any other markup variable: 51 | 52 | ```blade 53 |
      54 | @foreach($pages as $page) 55 |
    • {{ $page->name }}
    • 56 | @endforeach 57 |
    58 | ``` 59 | 60 | For more information, the [Including Subviews section of the Laravel Blade documentation](https://laravel.com/docs/blade#including-subviews) provides a good starting point. 61 | 62 | ## Overriding a component partial 63 | 64 | To override a component partial in a theme, you need to create a new `.blade.php` file with the same name as the partial you want to override, in the `resources/views/_components` directory of your theme. The content of this file will be used instead of the original partial. 65 | 66 | For example, to override the `default` partial for component `acme.hello-world::hello-block`, you would create a file named `hello-block.blade.php` in the `resources/views/_components/hello-block.blade.php` directory of your theme. 67 | 68 | ## Using partials within components 69 | 70 | Partials can be used with components to create reusable pieces of functionality. To use a partial within a component, you can use the `@include` directive, followed by the name of the partial. 71 | 72 | For example, to include the `header` partial within a component, you would use the following code: 73 | 74 | ```blade 75 | @include('header') 76 | ``` 77 | 78 | This will render the header partial wherever the component is used. 79 | -------------------------------------------------------------------------------- /customize/themes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Themes" 3 | section: customize 4 | sortOrder: 100 5 | --- 6 | 7 | ## Introduction 8 | 9 | TastyIgniter themes are files that work together to create a TastyIgniter website. Each theme can be different, offering site owners many choices to change their website look instantly. Themes, just like extensions are built on the foundation of Laravel packages. 10 | 11 | Themes typically contains all the [pages](../customize/pages), [partials](../customize/partials), [layouts](../customize/layouts), assets files and an optional theme PHP file (`theme.php`). Additionally, a theme can have a manifest file (`theme.json`) and a meta directory (`_meta`) that contains the assets manifest file (`assets.json`) and a fields file (`fields.php`) for the Theme settings feature through the Admin Interface. 12 | 13 | Activating a theme can be done through the _Design > Themes_ Admin page with the Theme Selector or by running this command: 14 | 15 | ```bash 16 | php artisan igniter:util set theme --theme=your-theme 17 | ``` 18 | 19 | This article is about developing themes for TastyIgniter. 20 | 21 | ## Directory structure 22 | 23 | Below is an example of a theme directory structure. Every TastyIgniter theme is represented by way of a separate directory and typically a theme is activated to display the website. 24 | 25 | ```yaml 26 | acme/ <=== Theme vendor directory 27 | purple/ <=== Theme directory 28 | public/ 29 | css/ <=== Directory contains compiled CSS files 30 | js/ <=== Directory contains compiled JavaScript files 31 | images/ <=== Directory contains image files 32 | resources/ <=== Theme resources directory 33 | scss/ <=== Directory contains source SCSS files 34 | js/ <=== Directory contains source JavaScript files 35 | meta/ <=== Meta directory 36 | assets.json <=== Registers global css, js files and HTML meta tags 37 | fields.php <=== Registers form fields for the theme customization 38 | views/ <=== Theme views directory 39 | _layouts/ <=== Layouts directory 40 | default.blade.php 41 | _pages/ <=== Pages directory contains the website pages 42 | home.blade.php 43 | includes/ <=== Partials directory contains reusable HTML chucks 44 | sidebar.blade.php 45 | screenshot.png 46 | composer.json <=== Manifest file 47 | theme.php <=== Theme PHP file - Loaded on every theme page request just before running the page code. 48 | ``` 49 | 50 | TastyIgniter supports a single level subdirectory for layouts, pages and partials files (any structure can be used in the `assets` directory), making it easier to organise large websites. 51 | 52 | A theme can contain any number of other subdirectories as well. 53 | 54 | **Screenshot** 55 | 56 | Create a screenshot for your theme. The screenshot should be named `screenshot.png` and should be placed in the top level directory. The recommended image size is 1200px wide by 900px tall. The screenshot is usually smaller but the over-size image allows high-resolution viewing on HiDPI displays. 57 | 58 | ## Theme manifest 59 | 60 | A `composer.json` file for a theme looks like this: 61 | 62 | ```json 63 | { 64 | "name": "acme/ti-theme-purple", 65 | "type": "tastyigniter-package", 66 | "description": "Purple theme for TastyIgniter", 67 | "authors": [ 68 | { 69 | "name": "Acme Labs" 70 | } 71 | ], 72 | "require": { 73 | "tastyigniter/ti-theme-orange": "*" 74 | }, 75 | "extra": { 76 | "tastyigniter-theme": { 77 | "code": "acme-purple", 78 | "name": "Purple Theme", 79 | "locked": true, 80 | "source-path": "/resources/views", 81 | "meta-path": "/resources/meta", 82 | "publish-paths": [ 83 | "/public" 84 | ] 85 | } 86 | } 87 | } 88 | ``` 89 | 90 | | Field | Description | 91 | |-------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 92 | | **name** | The Composer package's name in vendor/package format, **required**. You should use a vendor name that is unique to you, such as your GitHub username. You should prefix the package part with ti-theme- to indicate that your package is intended for use with TastyIgniter. | 93 | | **type** | MUST be set to tastyigniter-package, ensures that your theme will be installed as such when someone "requires" it, **required**. | 94 | | **description** | A one-sentence summary of what the extension does, **required**. (max. char: 130) | 95 | | **authors** | An object to specify the name of the extension author, **required**. | 96 | | **authors.0.name** | An object to specify the name of the extension author, **required**. | 97 | | **authors.0.email** | An object to specify the email of the extension author, **optional**. | 98 | | **require** | Defines other TastyIgniter packages your theme depends on, **optional**. In the example above, **acme-purple** theme depends on the **tastyigniter/ti-theme-orange** theme. | 99 | | **extra.tastyigniter-theme** | Holds TastyIgniter-specific extension metadata, such as your theme's display name and paths, **required**. | 100 | | **extra.tastyigniter-theme.code** | the theme code, **required**. The value is used on the TastyIgniter marketplace for setting the theme code value. | 101 | | **extra.tastyigniter-theme.name** | specifies the theme name, **required**. | 102 | | **extra.tastyigniter-theme.locked** | specifies whether a child theme must be created to customize the theme, **optional**. | 103 | 104 | ## Assets manifest 105 | 106 | Before adding your files to the theme assets manifest file, you must place them within your theme in the correct directory structure, as shown in the [theme directory structure](#directory-structure). 107 | 108 | A `resources/meta/assets.json` file looks like this: 109 | 110 | ```json 111 | { 112 | "doctype": "html5", 113 | "favicon": "favicon.ico", 114 | "meta": [ 115 | { 116 | "name": "Content-type", 117 | "content": "text/html; charset=utf-8", 118 | "type": "equiv" 119 | }, 120 | { 121 | "name": "viewport", 122 | "content": "width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no", 123 | "type": "name" 124 | } 125 | ], 126 | "css": [ 127 | { 128 | "path": "$/acme-purple/css/app.css", 129 | "name": "app-css" 130 | } 131 | ], 132 | "js": [ 133 | { 134 | "path": "$/acme-purple/js/app.js", 135 | "name": "app-js" 136 | } 137 | ], 138 | "bundles": [ 139 | { 140 | "type": "scss", 141 | "files": "acme-purple::/scss/app.scss", 142 | "destination": "$/acme-purple/css/app.css" 143 | } 144 | ] 145 | } 146 | ``` 147 | 148 | > The `$` symbol is a placeholder for the `public` path. Theme assets are published to the `public` directory of the TastyIgniter installation when you install or update the theme or run the `php artisan igniter:theme-vendor-publish --theme=your-theme` command. 149 | 150 | JavaScript code should be placed in external files whenever possible. Use [`@scripts`](../customize/markup-guide#themescripts) to load your scripts and [`@styles`](../customize/markup-guide#themestyles) to load your styles. 151 | 152 | ## Template structure 153 | 154 | Template is a file that defines how a page or layout is organized and how its content is displayed. It can contain up to three sections: front-matter, PHP code, and HTML markup. These sections are separated by the `---` sequence. 155 | 156 | A **page template** file looks like this: 157 | 158 | ```blade 159 | --- 160 | title: Homepage 161 | permalink: / 162 | layout: default 163 | 164 | '[helloBlock]': 165 | maxItems: 1 166 | --- 167 | 173 | --- 174 |
    175 |

    Rendering a Theme component

    176 | @themeComponent('helloBlock') 177 | 178 |

    Rendering a Livewire component

    179 | 180 | 181 |

    Rendering sub views

    182 | @include('acme-purple::includes.sidebar') 183 |
    184 | ``` 185 | 186 | A **layout template** file looks like this: 187 | 188 | ```blade 189 | --- 190 | description: Default Layout 191 | --- 192 | 193 | 194 | 195 | 196 | {{ $this->page->title }} 197 | @themeStyles 198 | 199 | 200 | @themePage 201 | @themeScripts 202 | 203 | 204 | ``` 205 | 206 | ### Front-matter section 207 | 208 | This is the first section in the file which contains valid YAML set between triple-dashed lines. You can set predefined variables or create custom ones which will be available to you to access from component blade views. Here is a basic example: 209 | 210 | ```blade 211 | --- 212 | title: Homepage 213 | permalink: / 214 | layout: default 215 | 216 | '[helloBlock]': 217 | maxItems: 1 218 | --- 219 | ``` 220 | 221 | ### PHP code section 222 | 223 | This section is executed each time before the template is rendered. It is optional and the content depends on the type of template it is defined within. The PHP open `` tags should always be set 224 | within the section separator `---` on a different line. For example: 225 | 226 | ```blade 227 | --- 228 | 235 | --- 236 |

    Categories

    237 |
      238 | @foreach ($categories as $category) { 239 |
    • {{ $category }}
    • 240 | @endforeach 241 |
    242 | ``` 243 | 244 | The PHP section is converted to a PHP class when the template is parsed, you can only define functions and `use` namespaces, no other PHP code is allowed. You can access `$this` object to read and set variables. 245 | 246 | ### HTML markup 247 | 248 | The HTML section defines the markup to be rendered by the template. The HTML section content can contain both HTML and PHP tags, functions, the content depends on whether the template is a page or layout. 249 | 250 | ## Including theme partials 251 | 252 | To render the `includes/sidebar.blade.php` blade view from the [directory structure](../customize/themes#directory-structure) above within a page or layout or another partial, you can use `@include('includes.sidebar')` to make it easy for a theme to reuse code sections. The template paths are always absolute. If you render a partial from the subdirectory `includes/blog/`, the subdirectory name still needs to be specified, `@include('includes.blog.sidebar')`. 253 | 254 | ## Global variables 255 | 256 | | Variable | Description | 257 | |----------------|---------------------------------------------------------------------------------------------------------------------------------| 258 | | **theme** | Theme object `$this->theme` for reading customized settings. | 259 | | **page** | Page object `$this->page`. Custom variables set via front matter in [pages](../customize/pages) will be available here. | 260 | | **layout** | Layout object `$this->layout`. Custom variables set via front matter in [layouts](../customize/layouts) will be available here. | 261 | | **controller** | Access the underlying `MainController` object `$this->controller`. | 262 | 263 | ## Theme settings 264 | 265 | The theme settings feature in TastyIgniter allows you to customize the appearance and behavior of your theme directly from the admin interface. This feature is available by default for enabled TastyIgniter themes from the _Design > Themes_ Admin page. 266 | 267 | To enable theme settings, you need to register form fields using the `resources/meta/fields.php` file in your theme directory. This file should return an array of form fields that will be displayed in the theme settings interface. 268 | 269 | Here's an example of a `resources/meta/fields.php` file: 270 | 271 | ```php 272 | return [ 273 | // Set form fields for the admin theme settings. 274 | 'form' => [ 275 | 'fields' => [ 276 | 'font_family' => [ 277 | 'label' => 'Font Family', 278 | 'type' => 'text', 279 | 'default' => '"Inter",Arial,sans-serif', 280 | 'comment' => 'The font family to use for the main body text.', 281 | 'rules' => 'required|string', 282 | ], 283 | ] 284 | ] 285 | ]; 286 | ``` 287 | 288 | In this example, a text field is defined for customizing the font family. The field has a label, a default value, a comment, and a validation rule. 289 | 290 | Once the fields are defined, you can access the values inside any of your theme templates using $this->theme->field_name. For example: 291 | 292 | ```blade 293 |

    Welcome to our website!

    294 | ``` 295 | 296 | In this example, the font family defined in the theme settings interface is applied to a heading. 297 | 298 | ## Best practices 299 | 300 | - All template files (layouts, pages and partials) should use `.blade.php`. 301 | - Use relative paths in your CSS files, for example: `url(../img/bg.png);` 302 | - Use lowercase filenames 303 | - Use hyphens `-` **NOT** underscores `_` to separate words in filenames 304 | -------------------------------------------------------------------------------- /extend/controllers.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Controllers" 3 | section: extend 4 | sortOrder: 210 5 | --- 6 | 7 | ## Introduction 8 | 9 | TastyIgniter Admin implements the MVC pattern. Controllers manage admin pages and implement various features like forms and lists. This article describes how to develop admin controllers and how to configure controller methods. 10 | 11 | Controllers are typically stored in the `src/Http/Controllers` directory of an extension. Controller views are `.blade.php` blade views that reside in the `resources/views` directory of an extension. The controller view directory name matches the controller class name written in lowercase. An example of a controller directory structure: 12 | 13 | ```yaml 14 | author/ 15 | extension/ 16 | src/ 17 | Http/ 18 | Controllers/ 19 | MyController.php <=== Controller class 20 | resources/ 21 | views/ 22 | mycontroller/ <=== Controller view directory 23 | edit.blade.php <=== Controller view file 24 | ``` 25 | 26 | ## Writing controller 27 | 28 | ### Controller class 29 | 30 | Each controller is simply a class that extends the `Igniter\Admin\Classes\AdminController` class. Here is an example of a controller class: 31 | 32 | ```php 33 | namespace Author\Extension\Http\Controllers; 34 | 35 | class MyController extends \Igniter\Admin\Classes\AdminController 36 | { 37 | public function index() // Controller action 38 | { 39 | return $this->makeView('index'); 40 | } 41 | } 42 | ``` 43 | 44 | ### Controller properties 45 | 46 | Controllers defines a number of properties to configure the controller's behavior. The following properties are available: 47 | 48 | | Property | Description | 49 | |------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 50 | | `$pageTitle` | The page title to display in the admin interface. | 51 | | `$bodyClass` | The CSS class to apply to the admin page body. | 52 | | `$currentUser` | The currently logged-in administrator. | 53 | | `$defaultView` | The default view file to render when no view is returned from the action. | 54 | | `$suppressView` | A boolean value that determines whether to suppress the view rendering. | 55 | | `$layout` | The layout file to use for rendering the controller views. | 56 | | `$requiredPermissions` | An array of permission keys that determine the access level needed to access the controller. If an administrator has any of the permissions listed, they are granted access to the controller pages. | 57 | | `$skipRouteRegister` | A boolean value that determines whether to skip automatically registering the controller routes. | 58 | | `$guarded` | An array of controller methods that can not be called as actions. Can be extended in the controller constructor. | 59 | 60 | > **Note** These properties can be set in the class definition, in the controller constructor or in the action method. 61 | 62 | ## Actions, views and routing 63 | 64 | Actions are methods within the controller that handle specific tasks. They interact with the model, manipulate data, and load views. Routing is used to map URLs to these controller actions. 65 | 66 | For example, the URL for the `index` action of the `MyController` controller in the `Author.Extension` extension would be: 67 | 68 | ```blade 69 | http://site.com/admin/author/extension/mycontroller/index 70 | ``` 71 | 72 | ## Passing data to views 73 | 74 | Data can be passed from the controller to the view using the `$this->vars` property. This property is an array that holds the data to be passed to the view. Here is an example of passing data to the view: 75 | 76 | ```php 77 | public function index() 78 | { 79 | $this->vars['records'] = Record::all(); 80 | } 81 | ``` 82 | 83 | The variables passed with the `$this->vars` property can now be accessed directly in your view: 84 | 85 | ```php 86 | @foreach ($records as $record) 87 | {{ $record->name }} 88 | @endforeach 89 | ``` 90 | 91 | ## Rendering views 92 | 93 | Views are rendered using the `makeView` method. This method takes the view file name as an argument and returns the rendered view. Here is an example of rendering a view: 94 | 95 | ```php 96 | public function index() 97 | { 98 | return $this->makeView('index'); 99 | } 100 | ``` 101 | 102 | ## Setting the navigation context 103 | 104 | Extensions can register admin navigation menus and submenus in the [Extension class](../extend/extensions#extension-class-methods). The navigation context can be set in the controller to highlight the active menu item in the admin interface. The `AdminMenu::setContext` method is used to set the navigation context. Here is an example of setting the navigation context: 105 | 106 | ```php 107 | public function __construct() 108 | { 109 | parent::__construct(); 110 | 111 | AdminMenu::setContext('nav-item', 'parent-item'); 112 | } 113 | ``` 114 | 115 | ## Controller middleware 116 | 117 | For example, TastyIgniter includes a middleware that verifies the user of your application is authenticated. If the user is not authenticated, the middleware will redirect the user to the login screen. However, if the user is authenticated, the middleware will allow the request to proceed further into the application. 118 | 119 | Controller middleware is executed after the request is processed by TastyIgniter, but before the response is sent to the browser, allowing you to perform actions or modify the request/response during this lifecycle stage. 120 | 121 | To define middleware for your controller, you may specify it in the `__construct()` method of your Admin controller by calling the `middleware()` method. Here is an example of defining middleware for a controller: 122 | 123 | ```php 124 | public function __construct() 125 | { 126 | parent::__construct(); 127 | 128 | $this->middleware(function ($request, $response) { 129 | // Middleware functionality 130 | }); 131 | } 132 | ``` 133 | 134 | You can also control which actions will use your middleware by using the `only()` and `except()` modifiers. For example, to restrict the middleware to only run on the `index` action or on every action except the `index` action, you could do the following: 135 | 136 | ```php 137 | public function __construct() 138 | { 139 | parent::__construct(); 140 | 141 | $this->middleware(function ($request, $response) { 142 | // Middleware functionality 143 | })->only('index'); 144 | 145 | // Or 146 | 147 | $this->middleware(function ($request, $response) { 148 | // Middleware functionality 149 | })->except('index'); 150 | } 151 | ``` 152 | 153 | ## Controller action classes 154 | 155 | Action classes are behaviours that can be attached to controllers to extend their functionality. Action classes are useful for sharing common controller logic across multiple controllers. To attach an action class to a controller, you can set the `$implement` property on the controller class. 156 | 157 | Here is an example of attaching an action class to a controller: 158 | 159 | ```php 160 | namespace Author\Extension\Http\Controllers; 161 | 162 | class MyController extends \Igniter\Admin\Classes\AdminController 163 | { 164 | public array $implement = [\Author\Extension\Http\Actions\MyAction::class]; 165 | 166 | // 167 | } 168 | ``` 169 | 170 | Each controller action is simply a class that extends the `Igniter\System\Classes\ControllerAction` class. Here is an example of a controller action class: 171 | 172 | ```php 173 | namespace Author\Extension\Http\Actions; 174 | 175 | class MyAction extends \Igniter\System\Classes\ControllerAction 176 | { 177 | public function __construct($controller) 178 | { 179 | parent::__construct($controller); 180 | 181 | // Configure the action 182 | } 183 | 184 | public function index() // Action method 185 | { 186 | return $this->makeView('index'); 187 | } 188 | } 189 | ``` 190 | 191 | ## Using AJAX handlers 192 | 193 | Controllers can define AJAX handlers to handle AJAX requests. AJAX handlers are methods with the name starting with "on" string that return an array of data, throw an exception or redirect to another page (see [AJAX event handlers](../advanced/ajax-request#using-ajax-handlers)). Here is an example of defining an AJAX handler: 194 | 195 | ```php 196 | public function onAction() 197 | { 198 | if (Request::input('someVar') != 'someValue') { 199 | throw new ApplicationException('Invalid value'); 200 | } 201 | 202 | return ['data' => 'value']; 203 | } 204 | ``` 205 | 206 | The AJAX handler can be triggered with the data-request attribute in the view: 207 | 208 | ```blade 209 | 214 | ``` 215 | 216 | ## Restricting access 217 | 218 | Access to controller actions can be restricted using the `$requiredPermissions` property. This property holds an array of permission keys that determine the access level needed. If an administrator has any of the permissions listed, they are granted access to the controller pages. 219 | 220 | Here is an example of how to restrict access to a controller using permissions: 221 | 222 | ```php 223 | namespace Author\Extension\Http\Controllers; 224 | 225 | class MyController extends \Igniter\Admin\Classes\AdminController 226 | { 227 | public null|string|array $requiredPermissions = ['Author.Extension.ManageRecord']; 228 | 229 | // 230 | } 231 | ``` 232 | 233 | See more about [restricting access to admin pages](../customize/permissions#restricting-access-to-admin-pages) and [restricting access to admin controller actions](../customize/permissions#restricting-access-to-admin-controller-actions). 234 | -------------------------------------------------------------------------------- /extend/extensions.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Extensions" 3 | section: extend 4 | sortOrder: 200 5 | --- 6 | 7 | ## Introduction 8 | 9 | Extensions are the foundation for adding new features to TastyIgniter by extending it. The core of TastyIgniter is designed to be lean and lightweight, to maximize flexibility and minimize code bloat. With extensions, you can add specific set of features or services, such as: 10 | 11 | - Define components, mail templates and staff permissions 12 | - Add, remove or replace navigation items 13 | - Create or alter database table structures and seed database records 14 | - Alter core or other extension functionality 15 | - Provide services, admin controllers and assets. 16 | 17 | TastyIgniter extensions are built on the foundation of Laravel packages. They leverage standard practices such as implementing service providers to register routes, views, and translations. If you're new to Laravel package development, [the Package Development part of the Laravel documentation](https://laravel.com/docs/packages) is a useful resource. 18 | 19 | ### Directory structure 20 | 21 | Below is an example of an extension directory structure. 22 | 23 | ```yaml 24 | acme/ <=== Author name (namespace) 25 | helloworld/ <=== Extension name 26 | src/ 27 | Extension.php <=== Extension class 28 | composer.json <=== Contains extension metadata 29 | README.md <=== Extension readme file 30 | ``` 31 | 32 | ## Creating an extension 33 | 34 | This section of the article covers the steps you need to take - and some things you need to consider - when creating a well-structured TastyIgniter extension. 35 | 36 | ### Naming your extension 37 | 38 | The first step in creating an extension is to select a **Namespace** and **Short Name** for it. This extension name **Namespace.ShortName** is used to refer to your extension by core TastyIgniter. The namespace will be used as your author 39 | code when publishing your extensions on the [TastyIgniter marketplace](https://tastyigniter.com/marketplace). 40 | 41 | Both namespace and extension name must follow these important rules: 42 | 43 | - Only letters must be provided. 44 | - Folder names must be lowercase, as shown in the directory structure example. 45 | - Should not contain spaces. 46 | - Must be unique. The name of your extension should not be the same with any other extension or theme. 47 | 48 | Here's an example: **Acme.HelloWorld** 49 | 50 | Given the above example **Acme.HelloWorld**, creating an extension is pretty simple and straightforward, simply call the 51 | following command from the application directory to generate a basic extension directory and files within your `extensions` directory: 52 | 53 | ```bash 54 | php artisan make:igniter-extension Acme.HelloWorld 55 | ``` 56 | 57 | > We strongly recommend that you follow the [TastyIgniter coding standards](../resources/php-coding-guidelines) when creating your own extensions. It is a requirement for any changes to the TastyIgniter core code. 58 | 59 | ### Extension manifest (composer.json) 60 | 61 | A `composer.json` file is essential for storing metadata about the extension. 62 | 63 | ```json 64 | { 65 | "name": "acme/ti-ext-helloworld", 66 | "type": "tastyigniter-package", 67 | "description": "Say hello to the rest of the world..", 68 | "authors": [ 69 | { 70 | "name": "Acme Labs" 71 | } 72 | ], 73 | "require": { 74 | "tastyigniter/ti-ext-local": "*" 75 | }, 76 | "extra": { 77 | "tastyigniter-extension": { 78 | "code": "acme.helloworld", 79 | "name": "Hello World", 80 | "icon": { 81 | "class": "fa fa-puzzle-piece", 82 | "color": "#FFF", 83 | "backgroundColor": "#ED561A" 84 | } 85 | } 86 | } 87 | } 88 | ``` 89 | 90 | | Field | Description | 91 | |-------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 92 | | **name** | The Composer package's name in vendor/package format, **required**. You should use a vendor name that is unique to you, such as your GitHub username. You should prefix the package part with ti-ext- to indicate that your package is intended for use with TastyIgniter. | 93 | | **type** | MUST be set to tastyigniter-package, ensures that your extension will be installed as such when someone "requires" it, **required**. | 94 | | **description** | A one-sentence summary of what the extension does, **required**. (max. char: 130) | 95 | | **authors** | An object to specify the name of the extension author, **required**. | 96 | | **require** | Defines other TastyIgniter extensions your extension depends on, **optional**. In the example above, **acme.helloworld** extension depends on the **tastyigniter/ti-ext-local** extension. | 97 | | **extra.tastyigniter-extension** | Holds TastyIgniter-specific extension metadata, such as your extension's display name and icon style, **required**. | 98 | | **extra.tastyigniter-extension.code** | The extension unique identifier code, **required**. | 99 | | **extra.tastyigniter-extension.name** | Specifies the extension name, **required**. The value is used as the extension display name. | 100 | | **extra.tastyigniter-extension.icon** | An object that defines the icon for your extension. The **name** property is the name of a [Font Awesome icon class](https://fontawesome.com/icons). All other properties are used as the style attribute for your extension's icon. | 101 | | **extra.tastyigniter-extension.homepage** | Specifies the extension website URL, **optional**. | 102 | 103 | See [the composer.json schema](https://getcomposer.org/doc/04-schema.md) documentation for information about other 104 | properties you can add to composer.json. 105 | 106 | ### Readme file 107 | 108 | If you want to publish your extension on the [TastyIgniter marketplace](https://tastyigniter.com/marketplace), you must 109 | create a **README.md** file in a standardized format in your extension directory. 110 | 111 | ## Extension class 112 | 113 | An **Extension.php** file (aka *Extension class*) is an essential part of the TastyIgniter extension for providing methods for extending the TastyIgniter core. 114 | 115 | The extension class should extend the `\Igniter\System\Classes\BaseExtension` class, which in turn extends Laravel's `Illuminate\Foundation\Support\Providers\EventServiceProvider` class. This allows the Extension class to take advantage of the functionality provided by Laravel's service providers, such as registering services, event subscribers, model observers and listening for events. 116 | 117 | Here is an example of an Extension class: 118 | 119 | ```php 120 | namespace Acme\HelloWord; 121 | 122 | class Extension extends \Igniter\System\Classes\BaseExtension 123 | { 124 | public function register() 125 | { 126 | 127 | } 128 | 129 | public function boot() 130 | { 131 | 132 | } 133 | } 134 | ``` 135 | 136 | In this example, the `register` method is used to bind services into the container, and the `boot` method is used to perform any operations after all other services have been registered. 137 | 138 | ### Extension class methods 139 | 140 | The following methods are supported in the extension class: 141 | 142 | | Method | Description | 143 | |--------------------------------|----------------------------------------------------------------------------------------------------------------------------| 144 | | **register()** | register method, called when the plugin is first registered. | 145 | | **boot()** | boot method, called right before the request route. | 146 | | **registerNavigation()** | registers admin navigation menu items for this extension, see below for example. | 147 | | **registerPermissions()** | registers any [staff permissions](../customize/permissions#registering-permissions) supplied by this extension. | 148 | | **registerSettings()** | registers any [admin settings page](../extend/extensions#registering-settings-navigation-link) supplied by this extension. | 149 | | **registerDashboardWidgets()** | registers any [admin dashboard widgets](../extend/widgets#using-dashboard-widget), supplied by this extension. | 150 | | **registerFormWidgets()** | registers any [admin form widgets](../extend/widgets#using-form-widget) supplied by this extension. | 151 | | **registerMailLayouts()** | registers any mail view layouts supplied by this extension. | 152 | | **registerMailTemplates()** | registers any mail view templates supplied by this extension, see below for example. | 153 | | **registerMailPartials()** | registers any mail view partials supplied by this extension. | 154 | | **registerSchedule()** | registers any [scheduled tasks](../advanced/scheduling-tasks) supplied by this extension. | 155 | 156 | An example registering **mail templates view** file `extensions/acme/helloworld/views/mail/contact`: 157 | 158 | ```php 159 | public function registerMailTemplates(): array 160 | { 161 | return [ 162 | 'acme.helloworld::mail.contact' => 'Contact form email to admin', 163 | ]; 164 | } 165 | 166 | ``` 167 | 168 | An example registering a top-level and child **menu navigation menu item**: 169 | 170 | ```php 171 | public function registerNavigation(): array 172 | { 173 | return [ 174 | 'messages' => [ 175 | 'priority' => 300, 176 | 'title' => 'Messages', 177 | 'href' => admin_url('acme/helloworld/messages'), 178 | 'permission' => ['Acme.HelloWorld'], 179 | 'child' => [ 180 | 'banners' => [ 181 | 'priority' => 500, 182 | 'title' => 'Banners', 183 | 'href' => admin_url('acme/helloworld/banners'), 184 | 'permission' => ['Acme.HelloWorld.Banners'], 185 | ], 186 | ], 187 | ], 188 | ]; 189 | } 190 | ``` 191 | 192 | To register a custom **middleware**, you can call any of the following in your **boot** method. 193 | 194 | ```php 195 | public function boot() 196 | { 197 | // Add a new middleware to a controller class. 198 | \Igniter\Main\Classes\MainController::extend(function($controller) { 199 | $controller->middleware(\Path\To\CustomMiddleware::class); 200 | }); 201 | 202 | // Alternatively, add a new middleware to beginning of the igniter group stack. 203 | $this->app['router']->prependMiddlewareToGroup('igniter', \Path\To\CustomMiddleware::class); 204 | 205 | // Alternatively, add a new middleware to end of the igniter group stack. 206 | $this->app['router']->pushMiddlewareToGroup('igniter', \Path\To\CustomMiddleware::class); 207 | } 208 | ``` 209 | 210 | An example registering **console commands**: 211 | 212 | ```php 213 | public function register() 214 | { 215 | $this->registerConsoleCommand( 216 | 'create.apiresource', \Igniter\Api\Console\CreateApiResource::class 217 | ); 218 | } 219 | ``` 220 | 221 | ## Resources 222 | 223 | ### Settings & Configuration 224 | 225 | There are two ways to configure extensions; with settings models (database settings) and configuration files. 226 | 227 | #### Using the settings model configuration 228 | 229 | Settings models, like any other models, resides in the **src/Models** subdirectory of the extension directory. 230 | 231 | ```yaml 232 | acme/ 233 | helloworld/ 234 | resources/ 235 | models/ 236 | settings.php <=== Setting model form fields 237 | src/ 238 | Models/ 239 | Settings.php <=== Setting model class file 240 | ``` 241 | 242 | By implementing the `\Igniter\System\Actions\SettingsModel` action class and extending the base `\Igniter\Flame\Database\Model` class in a model class, you can create models for storing settings in the `extension_settings` database table. 243 | 244 | ```php 245 | [ 267 | 'toolbar' => [ 268 | 'buttons' => [ 269 | 'save' => ['label' => 'lang:admin::lang.button_save', 'class' => 'btn btn-primary', 'data-request' => 'onSave'], 270 | 'saveClose' => [ 271 | 'label' => 'lang:admin::lang.button_save_close', 272 | 'class' => 'btn btn-default', 273 | 'data-request' => 'onSave', 274 | 'data-request-data' => 'close:1', 275 | ], 276 | ], 277 | ], 278 | 'fields' => [ 279 | 'field_name' => [ 280 | 'label' => 'Field Label', 281 | 'type' => 'text', 282 | 'span' => 'left', 283 | ], 284 | ], 285 | ], 286 | ]; 287 | ``` 288 | 289 | An example of **writing** to a settings model: 290 | 291 | ```php 292 | use Acme\HelloWorld\Models\Settings; 293 | 294 | // Set a single value 295 | Settings::set('cart_key', 'ABCD'); 296 | 297 | // Set an array of values 298 | Settings::set(['cart_key' => 'ABCD']); 299 | ``` 300 | 301 | An example of **reading** from a settings model: 302 | 303 | ```php 304 | use Acme\HelloWorld\Models\Settings; 305 | 306 | // Get a single value 307 | $cartKey = Settings::get('cart_key'); 308 | 309 | // Get a value and return a default value if it doesn't exist 310 | $abandonedCart = Settings::get('abandoned_cart', true); 311 | ``` 312 | 313 | #### Using the file-based configuration 314 | 315 | Just like Laravel packages, extensions can have configuration files in the extension `config` subdirectory. Configuration files are PHP scripts that define and return an array, e.g. `acme/helloworld/config/settings.php` configuration file 316 | 317 | ```php 318 | true, 322 | 'cartSessionTtl' => 120 323 | ]; 324 | ``` 325 | 326 | To make the configuration file available to the application, you need to register the configuration file in the extension class `register` method. 327 | 328 | ```php 329 | public function register() 330 | { 331 | $this->mergeConfigFrom(__DIR__.'/config/settings.php', 'helloworld-settings'); 332 | } 333 | ``` 334 | 335 | Use the `Config` facade to access the configuration values defined in the configuration file. 336 | 337 | ```php 338 | use Config; 339 | 340 | // Get a value and return a default value if it doesn't exist 341 | $cartSessionTtl = Config::get('helloworld-settings.cartSessionTtl', 120); 342 | ``` 343 | 344 | #### Registering settings navigation link 345 | 346 | An example showing how to register a system settings item which links to a settings model. Registered settings will appear on the *Manage > Settings* admin page. 347 | 348 | ```php 349 | public function registerSettings(): array 350 | { 351 | return [ 352 | 'settings' => [ 353 | 'label' => 'Extension Settings', 354 | 'description' => 'Manage extension settings.', 355 | 'icon' => 'fa fa-plugin', 356 | 'model' => Acme\HelloWorld\Models\Settings::class, 357 | 'permissions' => ['Acme.HelloWorld.Settings'], 358 | ], 359 | ]; 360 | } 361 | ``` 362 | 363 | ### Migrations 364 | 365 | If your extension contains database migrations stored in the extension's `database/migrations` directory, TastyIgniter will automatically load them when the extension is loaded. 366 | 367 | Here is an example of a `2020_01_01_000000_create_messages_table.php` migration file located in `acme/helloworld/database/migrations`: 368 | 369 | ```php 370 | 371 | use Illuminate\Database\Migrations\Migration; 372 | use Illuminate\Database\Schema\Blueprint; 373 | use Illuminate\Support\Facades\Schema; 374 | 375 | return new class extends Migration 376 | { 377 | public function up() 378 | { 379 | Schema::create('acme_helloworld_messages', function (Blueprint $table) { 380 | $table->increments('id'); 381 | $table->string('subject'); 382 | $table->text('message'); 383 | $table->timestamps(); 384 | }); 385 | } 386 | 387 | public function down() 388 | { 389 | Schema::dropIfExists('acme_helloworld_messages'); 390 | } 391 | }; 392 | ``` 393 | 394 | For more information on database migrations, refer to the [Laravel documentation on database migrations](https://laravel.com/docs/migrations). 395 | 396 | ### Seeders 397 | 398 | Seeders are used to populate database tables with sample data. Just like Laravel Packages, seeders are stored in the extension's `database/seeds` directory. 399 | 400 | Here is an example of a `MessagesTableSeeder.php` seeder file located in `acme/helloworld/database/seeds`: 401 | 402 | ```php 403 | namespace Acme\HelloWorld\Database\Seeds; 404 | 405 | use Illuminate\Database\Seeder; 406 | 407 | class MessagesTableSeeder extends Seeder 408 | { 409 | public function run() 410 | { 411 | DB::table('acme_helloworld_messages')->insert([ 412 | 'subject' => 'Hello World', 413 | 'message' => 'Welcome to the world of TastyIgniter!', 414 | ]); 415 | } 416 | } 417 | ``` 418 | 419 | To run the seeder, you can use the `php artisan db:seed --class=Acme\\HelloWorld\\Database\\Seeds\\MessagesTableSeeder` command. 420 | 421 | ### Routes 422 | 423 | If your extension includes a `routes/web.php` route file, TastyIgniter will automatically load it, adding any defined routes to the application's routing table. 424 | 425 | Extensions in TastyIgniter can define routes using the `routes/web.php` file or handle admin routes through admin controllers. 426 | 427 | To handle admin routes, use [admin controllers](../extend/controllers). TastyIgniter automatically maps admin URLs to any controller class that extends the `Igniter\Admin\Classes\AdminController` and resides in the extension's `src/Http/Controllers` directory. For example, admin URL `acme/helloworld/messages` will be mapped to the `Acme\HelloWorld\Http\Controllers\Messages` admin controller. 428 | 429 | ### Language files 430 | 431 | If your extension contains [language files](../advanced/localization) stored in the extension's `resources/lang` directory, TastyIgniter will automatically load them when the extension is loaded. When developing your extension, you should consider using language strings even if you do not intend to use it in more than one language. You never know: if you decide to make your extension available to users around the world, it can be handy later. 432 | 433 | It's highly recommended to include a complete set of English resources with your extension, especially if you plan to publish your extension in the TastyIgniter marketplace. 434 | 435 | Here's an example of a language file named `default.php` located in `acme/helloworld/resources/lang`: 436 | 437 | ```php 438 | return [ 439 | 'label' => 'Hello World', 440 | 'description' => 'Say hello to the rest of the world.', 441 | ]; 442 | ``` 443 | 444 | To reference these language lines, use the `author.extension::file.key` syntax. For instance, to access the `label` language string from the `acme.helloworld` extension, use the `lang` helper function: 445 | 446 | ```php 447 | echo lang('acme.helloworld::default.label'); 448 | ``` 449 | 450 | In the example above, `acme.helloworld` prefix is the extension code, `default` is the language file name without the `.php` extension, and `label` is the key of the language string. 451 | 452 | ### Views 453 | 454 | If your extension contains views stored in the extension's `resources/views` directory, TastyIgniter will automatically load them when the extension is loaded. 455 | 456 | Extension views are referenced using the `author.extension::view` syntax convention. So, once your extension is loaded, you can load the `hello.blade.php` view file from the `acme.helloworld` extension like so: 457 | 458 | ```php 459 | return view('acme.helloworld::hello'); 460 | ``` 461 | 462 | #### Overriding extension views 463 | 464 | When TastyIgniter automatically loads your view files, it registers two locations for your views: the application's `resources/views/vendor` directory and the extension's own `resources/views` directory. This setup enables developers to customize or override the views of your extension by placing a modified version in the `resources/views/vendor/author/extension` directory within the application. 465 | 466 | For more information on views, refer to the [Laravel documentation on views](https://laravel.com/docs/views). 467 | 468 | ## Using Third-Party composer packages 469 | 470 | There are a number of scenarios that require the developer to add a third-party composer packages to their extension. 471 | 472 | Here is a full example of how the **Igniter.Api** extension adds third-party package `laravel/sanctum`: 473 | 474 | ```json 475 | { 476 | "name": "tastyigniter/ti-ext-api", 477 | "type": "tastyigniter-package", 478 | "require": { 479 | "laravel/sanctum": "^3.0" 480 | }, 481 | "extra": { 482 | "tastyigniter-extension": { 483 | "code": "igniter.api", 484 | "name": "APIs", 485 | "icon": { 486 | "class": "fa fa-cloud", 487 | "color": "#fff", 488 | "backgroundColor": "#02586F" 489 | }, 490 | "homepage": "https://tastyigniter.com/marketplace/item/igniter-api" 491 | } 492 | } 493 | } 494 | ``` 495 | 496 | > Do not commit the `/vendor` directory, the `composer.json` file should be committed to the repository. 497 | 498 | ## Extending an extension 499 | 500 | Why extend one extension with another? To ensure that the changes remain compatible if the extension is updated. Extensions are mostly used to inject or modify the functionality of core classes and other extensions via the 'Event' dispatcher service. 501 | 502 | The most common place to subscribe to an event is the extension class `boot` method. 503 | -------------------------------------------------------------------------------- /extend/lists.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Lists" 3 | section: extend 4 | sortOrder: 240 5 | --- 6 | 7 | ## Introduction 8 | 9 | The `Igniter\Admin\Http\Actions\ListController` provide a way to display a list of records from a model and handle user interaction such as searching, sorting, filtering, and pagination. 10 | 11 | ## Configuring the list controller 12 | 13 | To use the `ListController` in your extension, you need to add it to the controller's `$implement` property. Also, you should define a `$listConfig` property in your controller and its value should be an array of configuration options. Here is an example of a list controller configuration: 14 | 15 | ```php 16 | namespace Author\Extension\Http\Controllers; 17 | 18 | class MyController extends \Igniter\Admin\Classes\AdminController 19 | { 20 | public array $listConfig = [ 21 | 'model' => 'Author\Extension\Models\Record', 22 | 'title' => 'Records', 23 | 'emptyMessage' => 'No records found', 24 | 'defaultSort' => ['id', 'DESC'], 25 | 'configFile' => 'record', 26 | ]; 27 | } 28 | ``` 29 | 30 | The following fields are required in the list configuration: 31 | 32 | - `model` - The model class name to use for the list. 33 | - `configFile` - Reference to the [list definition file](../extend/lists#list-definition-file). 34 | 35 | The configuration options listed below are optional. 36 | 37 | - `title` - The title of the list. 38 | - `emptyMessage` - The message to display when no records are found. 39 | - `showSorting` - Whether to show sorting controls. Default is `true`. 40 | - `defaultSort` - The default sort order for the list. The value should be an array with the column name and sort direction. 41 | - `pageLimit` - The number of records to display per page. Default is `20`. 42 | - `showPageNumbers` - Whether to show page numbers. Default is `true`. 43 | - `showCheckboxes` - Whether to show checkboxes for each record. 44 | - `showSetup` - Whether to show the setup button. Default is `true`. 45 | 46 | ### List definition file 47 | 48 | The list definition file is typically stored in the `resources/models` directory of an extension. The list definition file should return a `list` array of [toolbar buttons](../extend/lists#toolbar-button-options), [filter scopes](../extend/lists#scope-options) and [lists columns](../extend/lists#column-options) definitions. For example: 49 | 50 | ```php 51 | return [ 52 | 'list' => [ 53 | 'toolbar' => [ 54 | 'buttons' => [ 55 | // ... 56 | ], 57 | ], 58 | 'filter' => [ 59 | 'search' => [ 60 | 'prompt' => 'Search records', 61 | 'mode' => 'all', 62 | ], 63 | 'scopes' => [ 64 | // ... 65 | ], 66 | ], 67 | 'columns' => [ 68 | // ... 69 | ], 70 | ], 71 | // ... 72 | ]; 73 | ``` 74 | 75 | ## Adding a Toolbar 76 | 77 | You can add a toolbar to the list controller by defining a `toolbar` key in the list definition file. The toolbar configuration should be an array of toolbar buttons. Here is an example of a list controller with a toolbar defined in the list definition file `resources/models/record.php`: 78 | 79 | ```php 80 | return [ 81 | 'list' => [ 82 | 'toolbar' => [ 83 | 'buttons' => [ 84 | 'new' => [ 85 | 'label' => 'New Record', 86 | 'class' => 'btn btn-primary', 87 | 'href' => 'author/extension/mycontroller/create', 88 | ], 89 | ], 90 | ], 91 | ], 92 | ]; 93 | ``` 94 | 95 | ### Toolbar button options 96 | 97 | For each toolbar button, you can specify these options: 98 | 99 | - `type` - The type of button to display. Available types are: `link`, `button`, `dropdown`. Default is `link`. 100 | - `label` - The label to display for the button. 101 | - `context` - The context to apply to the button. Default is `primary`. 102 | - `disabled` - Whether the button is disabled. Default is `false`. 103 | - `partial` - The partial view to render when the button is clicked. 104 | - `permissions` - The [permissions](../customize/permissions) required to access the button. 105 | - `class` - The CSS class to apply to the button. 106 | - `href` - The URL to link to when the button is clicked. 107 | 108 | ## Filtering the list 109 | 110 | You can filter the list by defining a `filter` key in the list definition file. The filter configuration should be an array of the search configuration and filter scopes. Here is an example of a list controller with a filter defined in the list definition file `resources/models/record.php`: 111 | 112 | ```php 113 | return [ 114 | 'list' => [ 115 | 'filter' => [ 116 | 'search' => [ 117 | 'prompt' => 'Search records', 118 | 'mode' => 'all', 119 | ], 120 | 'scopes' => [ 121 | 'status' => [ 122 | 'label' => 'Status', 123 | 'type' => 'switch', 124 | 'conditions' => 'status = :filtered', 125 | ], 126 | ], 127 | ], 128 | ], 129 | ]; 130 | ``` 131 | 132 | ### Search options 133 | 134 | For the search filter, you can specify these options: 135 | 136 | - `prompt` - The placeholder text to display in the search input. 137 | - `mode` - The search mode to use. Available modes are: `all`, `any`, `exact`. Default is `all`. 138 | - `scope` - The query scope method defined in the list model to apply to the search query. 139 | 140 | ### Scope options 141 | 142 | For each scope you can specify these options (where applicable): 143 | 144 | - `label` - The label to display for the filter scope. 145 | - `type` - The type of filter scope to display. 146 | - `conditions` - The raw where query statement to apply to the list query. `:filtered` parameter represents the filtered value(s). 147 | - `default` - The default value for the filter scope. 148 | - `options` - An array of options to display in the filter scope dropdown. 149 | - `scope` - The query scope method defined in the list model to apply to the list query. 150 | - `nameFrom` - The model attribute to use as the label of the filter scope. 151 | - `permissions` - The [permissions](../customize/permissions) required to access the filter scope. 152 | 153 | ### Available filter scope types 154 | 155 | These types can be used to determine how the filter scope should be displayed. 156 | 157 | - [Switch](../extend/lists#switch) 158 | - [Select](../extend/lists#select) 159 | - [Select list](../extend/lists#select-list) 160 | - [Date](../extend/lists#date) 161 | - [Date range](../extend/lists#date-range) 162 | - [Checkbox](../extend/lists#checkbox) 163 | 164 | #### Switch 165 | 166 | `switch` used as a switch to toggle between two predefined conditions. 167 | 168 | ```php 169 | 'status' => [ 170 | 'label' => 'Status', 171 | 'type' => 'switch', 172 | 'default' => 1, 173 | 'conditions' => ['status <> true', 'status = :filtered'], 174 | ], 175 | ``` 176 | 177 | #### Select 178 | 179 | `select` used as a dropdown to apply a predefined condition to the list. 180 | 181 | ```php 182 | 'status' => [ 183 | 'label' => 'Status', 184 | 'type' => 'select', 185 | 'conditions' => 'status = :filtered', 186 | 'options' => [ 187 | 'active' => 'Active', 188 | 'inactive' => 'Inactive', 189 | ], 190 | ], 191 | ``` 192 | 193 | #### Select list 194 | 195 | `selectlist` used as a dropdown to apply one or more predefined conditions to the list. 196 | 197 | ```php 198 | 'status' => [ 199 | 'label' => 'Status', 200 | 'type' => 'selectlist', 201 | 'conditions' => 'status = :filtered', 202 | 'options' => [ 203 | 'active' => 'Active', 204 | 'inactive' => 'Inactive', 205 | ], 206 | ], 207 | ``` 208 | 209 | #### Date 210 | 211 | `date` displays a date picker for a single date to be selected and applied to the list. 212 | 213 | ```php 214 | 'date' => [ 215 | 'label' => 'Date', 216 | 'type' => 'date', 217 | 'conditions' => 'date = :filtered', 218 | ], 219 | ``` 220 | 221 | #### Date range 222 | 223 | `daterange` - Date range picker 224 | 225 | #### Checkbox 226 | 227 | `checkbox` - Checkbox input 228 | 229 | ## Defining list columns 230 | 231 | You can define the columns to display in the list by defining a `columns` key in the list definition file. The columns configuration should be an array of column definitions. Here is an example of a list controller with columns defined in the list definition file `resources/models/record.php`: 232 | 233 | ```php 234 | return [ 235 | 'list' => [ 236 | 'columns' => [ 237 | 'name' => [ 238 | 'label' => 'Name', 239 | 'searchable' => true, 240 | ], 241 | 'email' => [ 242 | 'label' => 'Email', 243 | 'searchable' => true, 244 | ], 245 | ], 246 | ], 247 | ]; 248 | ``` 249 | 250 | ### Column options 251 | 252 | For each column can specify these options (where applicable): 253 | 254 | - `label` - The label to display for the column. 255 | - `type` - The type of column to display (see [Column types](../extend/lists#available-column-types) below). Default is `text`. 256 | - `default` - The default value for the column. 257 | - `searchable` - Whether the column is searchable. Default is `false`. 258 | - `sortable` - Whether the column is sortable. Default is `true`. 259 | - `invisible` - Whether the column is visible. Default is `false`. 260 | - `select` - Defines a custom SQL select statement to use for the value. 261 | - `relation` - The model relationship name to use to fetch the column value. 262 | - `valueFrom` - The model attribute to use as the column value. 263 | - `cssClass` - The CSS class to apply to the column container. 264 | - `width` - The width of the column. Default is `auto`. 265 | - `permissions` - The [permissions](../customize/permissions) required to access the column. 266 | 267 | ### Available column types 268 | 269 | There are various column types that can be used to control how the list column is displayed. 270 | 271 | {.grid-2} 272 | - [Text](../extend/lists#text) 273 | - [Switch](../extend/lists#switch-1) 274 | - [Money](../extend/lists#money) 275 | - [Currency](../extend/lists#currency) 276 | - [Date](../extend/lists#date-1) 277 | - [Time](../extend/lists#time) 278 | - [Datetime](../extend/lists#datetime) 279 | - [Time since](../extend/lists#time-since) 280 | - [Time tense](../extend/lists#time-tense) 281 | - [Date since](../extend/lists#date-since) 282 | - [Partial](../extend/lists#partial) 283 | 284 | #### Text 285 | 286 | `text` displays a text column. The `type` key is optional and defaults to `text`. 287 | 288 | ```php 289 | 'name' => [ 290 | 'label' => 'Name', 291 | 'searchable' => true, 292 | ], 293 | ``` 294 | 295 | #### Switch 296 | 297 | `switch` displays on or off state for boolean columns. 298 | 299 | ```php 300 | 'is_active' => [ 301 | 'label' => 'Active', 302 | 'type' => 'switch', 303 | ], 304 | ``` 305 | 306 | #### Money 307 | 308 | `money` displays a column with a numeric value formatted to two decimal places. 309 | 310 | ```php 311 | 'price' => [ 312 | 'label' => 'Price', 313 | 'type' => 'money', 314 | ], 315 | ``` 316 | 317 | #### Currency 318 | 319 | `currency` displays a column with a numeric value formatted with a currency symbol. 320 | 321 | ```php 322 | 'price' => [ 323 | 'label' => 'Price', 324 | 'type' => 'currency', 325 | ], 326 | ``` 327 | 328 | #### Date 329 | 330 | `date` displays the column value as date format `MMM DD, YYYY` - **Dec 01, 2024**. You can customise this format setting the value of `format` key in the column configuration. 331 | 332 | ```php 333 | 'date' => [ 334 | 'label' => 'Date', 335 | 'type' => 'date', 336 | 'format' => 'MMM DD, YYYY', // Optional 337 | ], 338 | ``` 339 | 340 | #### Time 341 | 342 | `time` displays the column value as time format `hh:mm a` - **09:05 pm**. You can customise this format setting the value of `format` key in the column configuration. 343 | 344 | ```php 345 | 'time' => [ 346 | 'label' => 'Time', 347 | 'type' => 'time', 348 | 'format' => 'hh:mm a', // Optional 349 | ], 350 | ``` 351 | 352 | #### Datetime 353 | 354 | `datetime` displays the column value as datetime format `DD MMMM YYYY HH:mm` - **01 December 2024 16:05**. You can customise this format setting the value of `format` key in the column configuration. 355 | 356 | ```php 357 | 'datetime' => [ 358 | 'label' => 'Datetime', 359 | 'type' => 'datetime', 360 | 'format' => 'DD MMMM YYYY HH:mm', // Optional 361 | ], 362 | ``` 363 | 364 | #### Time since 365 | 366 | `timesince` displays the human-readable time difference from the column value to the current time. Eg: **10 days ago**, **20 minutes ago**, **Just now** 367 | 368 | ```php 369 | 'timesince' => [ 370 | 'label' => 'Time Since', 371 | 'type' => 'timesince', 372 | ], 373 | ``` 374 | 375 | #### Time tense 376 | 377 | `timetense` displays 24-hour time and the day using the grammatical tense of the current date. Eg: **Today at 14:59**, **Yesterday at 4:00** or **18 Sep 2023 at 16:23**. 378 | 379 | ```php 380 | 'timetense' => [ 381 | 'label' => 'Time Tense', 382 | 'type' => 'timetense', 383 | ], 384 | ``` 385 | 386 | #### Date since 387 | 388 | `datesince` displays the column value using the grammatical tense of the current date. Eg: **Today**, **Yesterday**, **18 Sep 2023** 389 | 390 | ```php 391 | 'datesince' => [ 392 | 'label' => 'Date Since', 393 | 'type' => 'datesince', 394 | ], 395 | ``` 396 | 397 | #### Partial 398 | 399 | `partial` displays a partial view in the column. The `path` key is required and should be the path to the partial view file. 400 | 401 | ```php 402 | 'partial' => [ 403 | 'label' => 'Partial', 404 | 'type' => 'partial', 405 | 'path' => '/path/to/partial', 406 | ], 407 | ``` 408 | 409 | ## Displaying the list 410 | 411 | Typically, lists are rendered in the `index` blade view file. Because lists include the toolbar and filters, the view file will look like this: 412 | 413 | ```blade 414 | {!! $this->renderListToolbar() !!} 415 | 416 | {!! $this->renderListFilter() !!} 417 | 418 | {!! $this->renderList(null, true) !!} 419 | ``` 420 | 421 | ## Multiple list definitions 422 | 423 | You can define multiple list configurations on the `$listConfig` property by using the `lists` key. Each list configuration should be an array of configuration options. Here is an example of `$listConfig` property with multiple list configurations: 424 | 425 | ```php 426 | public array $listConfig = [ 427 | 'list1' => [ 428 | 'model' => 'Author\Extension\Models\Record', 429 | 'title' => 'Records', 430 | 'emptyMessage' => 'No records found', 431 | 'defaultSort' => ['id', 'DESC'], 432 | 'configFile' => 'record', 433 | ], 434 | 'list2' => [ 435 | 'model' => 'Author\Extension\Models\Record', 436 | 'title' => 'Records', 437 | 'emptyMessage' => 'No records found', 438 | 'defaultSort' => ['id', 'DESC'], 439 | 'configFile' => 'record', 440 | ], 441 | ]; 442 | ``` 443 | 444 | Each defined list can be rendered by passing the list name as an argument to the `renderList` method. For example: 445 | 446 | ```php 447 | {!! $this->renderList('list1', true) !!} 448 | ``` 449 | 450 | ## Extending list controller 451 | 452 | You may need to extend the list controller to add custom functionality. There are several ways to extend the list controller: 453 | 454 | ### Extending the list configuration 455 | 456 | You can extend the list configuration by overriding the `listExtendConfig` method in the controller. Here is an example of extending the list configuration: 457 | 458 | ```php 459 | public function listExtendConfig($config, $listName) 460 | { 461 | if ($listName === 'list1') { 462 | $config['toolbar']['buttons']['new'] = [ 463 | 'label' => 'New Record', 464 | 'class' => 'btn btn-primary', 465 | 'href' => 'author/extension/mycontroller/create', 466 | ]; 467 | } 468 | 469 | return $config; 470 | } 471 | ``` 472 | 473 | ### Overriding controller action 474 | 475 | You can override the controller action by defining a method with the same name as the action method in the controller. Here is an example of overriding the `index` action: 476 | 477 | ```php 478 | public function index() 479 | { 480 | // Call the ListController action index() method 481 | $this->asExtension('ListController')->index(); 482 | } 483 | ``` 484 | 485 | ### Overriding views 486 | 487 | You can override the list views by creating a new view file in the `resources/views` directory of an extension. The view file should have the same name as the list view file you want to override. For example, to override the `index.blade.php` view file rendered from `MyController`, create a new view file in the `resources/views/mycontroller` directory with the same name. Here is an example adding a sidebar to the list: 488 | 489 | ```blade 490 |
    491 |
    492 | 495 |
    496 |
    497 | {!! $this->renderListToolbar() !!} 498 | 499 | {!! $this->renderListFilter() !!} 500 | 501 | {!! $this->renderList(null, true) !!} 502 |
    503 |
    504 | ``` 505 | 506 | ### Extending column definitions 507 | 508 | You can extend the list columns by overriding the `listExtendColumns` method in the controller: 509 | 510 | ```php 511 | public function listExtendColumns(Lists $widget, $model) 512 | { 513 | if (!$model instanceof MyModel) { 514 | return; 515 | } 516 | 517 | $list->addColumns([ 518 | 'my_column' => [ 519 | 'label' => 'My Column' 520 | ] 521 | ]); 522 | } 523 | ``` 524 | 525 | Or, you can use the `admin.list.extendColumns` event to extend the list columns from another extension: 526 | 527 | ```php 528 | Event::listen('admin.list.extendColumns', function (Lists $widget, $model) { 529 | if (!$model instanceof MyModel) { 530 | return; 531 | } 532 | 533 | $list->addColumns([ 534 | 'my_column' => [ 535 | 'label' => 'My Column' 536 | ] 537 | ]); 538 | }); 539 | ``` 540 | 541 | ### Extending filter scopes 542 | 543 | You can extend the list filter scopes by overriding the `listFilterExtendScopes` method in the controller: 544 | 545 | ```php 546 | public function listFilterExtendScopes(Filter $widget, $scopes) 547 | { 548 | if (!$widget->getController() instanceof MyController) { 549 | return; 550 | } 551 | 552 | // Add a new filter scope 553 | $widget->addScopes([ 554 | 'my_scope' => [ 555 | 'label' => 'My Scope', 556 | 'conditions' => 'my_column = :filtered', 557 | ] 558 | ]); 559 | } 560 | ``` 561 | 562 | Or, you can use the `admin.filter.extendScopes` event to extend the filter scopes from another extension: 563 | 564 | ```php 565 | Event::listen('admin.filter.extendScopes', function (Filter $widget, $scopes) { 566 | if (!$widget->getController() instanceof MyController) { 567 | return; 568 | } 569 | 570 | // Add a new filter scope 571 | $widget->addScopes([ 572 | 'my_scope' => [ 573 | 'label' => 'My Scope', 574 | 'conditions' => 'my_column = :filtered', 575 | ] 576 | ]); 577 | }); 578 | ``` 579 | 580 | Additionally, you can override the `listFilterExtendScopesBefore` method in your controller or use the `admin.filter.extendScopesBefore` event to modify the filter scope definitions before any of the filter scopes object is created: 581 | 582 | ```php 583 | public function listFilterExtendScopesBefore(Filter $widget) 584 | { 585 | if (!$widget->getController() instanceof MyController) { 586 | return; 587 | } 588 | 589 | unset($widget->scopes['status']); 590 | } 591 | 592 | // Or use the event 593 | 594 | Event::listen('admin.filter.extendScopesBefore', function (Filter $widget) { 595 | if (!$widget->getController() instanceof MyController) { 596 | return; 597 | } 598 | 599 | unset($widget->scopes['status']); 600 | }); 601 | ``` 602 | 603 | ### Extending the list model query 604 | 605 | You can extend the list query by overriding the `listExtendQuery` method in the controller: 606 | 607 | ```php 608 | public function listExtendQuery($query, $alias) 609 | { 610 | return $query->where('status', 'active'); 611 | } 612 | ``` 613 | 614 | Or, you can use the `admin.list.extendQuery` event to extend the list query from another extension: 615 | 616 | ```php 617 | Event::listen('admin.list.extendQuery', function ($widget, $query) { 618 | if (!$widget->getController() instanceof MyController) { 619 | return; 620 | } 621 | 622 | return $query->where('status', 'active'); 623 | }); 624 | ``` 625 | 626 | Additionally, you can override the `listExtendQueryBefore` method in your controller or use the `admin.list.extendQueryBefore` event to modify the query just after the query builder is created, and before any conditions are applied: 627 | 628 | ```php 629 | public function listExtendQueryBefore($query, $alias) 630 | { 631 | return $query->where('status', 'active'); 632 | } 633 | 634 | // Or use the event 635 | 636 | Event::listen('admin.list.extendQueryBefore', function ($widget, $query) { 637 | if (!$widget->getController() instanceof MyController) { 638 | return; 639 | } 640 | 641 | return $query->where('status', 'active'); 642 | }); 643 | ``` 644 | 645 | ### Extending the filter model query 646 | 647 | You can extend the filter query by overriding the `listFilterExtendQuery` method in the controller: 648 | 649 | ```php 650 | public function listFilterExtendQuery($query, $scope) 651 | { 652 | if ($scope->scopeName == 'status') { 653 | $query->where('status', 'active'); 654 | } 655 | } 656 | ``` 657 | 658 | Or, you can use the `admin.filter.extendQuery` event to extend the filter query from another extension: 659 | 660 | ```php 661 | Event::listen('admin.filter.extendQuery', function ($widget, $query, $scope) { 662 | if (!$widget->getController() instanceof MyController) { 663 | return; 664 | } 665 | 666 | if ($scope->scopeName == 'status') { 667 | $query->where('status', 'active'); 668 | } 669 | }); 670 | ``` 671 | 672 | ### Extending the records collection 673 | 674 | You can extend the records collection by overriding the `listExtendRecords` method in the controller: 675 | 676 | ```php 677 | public function listExtendRecords($records) 678 | { 679 | return $records->where('status', 'active'); 680 | } 681 | ``` 682 | 683 | Or, you can use the `admin.list.extendRecords` event to extend the records collection from another extension: 684 | 685 | ```php 686 | Event::listen('admin.list.extendRecords', function ($widget, $records) { 687 | if (!$widget->getController() instanceof MyController) { 688 | return; 689 | } 690 | 691 | return $records->where('status', 'active'); 692 | }); 693 | ``` 694 | -------------------------------------------------------------------------------- /extend/permissions.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Permissions" 3 | section: customize 4 | sortOrder: 290 5 | --- 6 | 7 | ## Introduction 8 | 9 | The Permissions system in TastyIgniter is designed to manage access and restrictions to the admin interface and its features. Permissions are assigned to staff roles, and staff roles are assigned to staff members. Staff members inherit the permissions of the roles they are assigned. 10 | 11 | Super Admins are identified by the `super_user` flag set to `true`. Below them are Administrators who have varying levels of permissions. Super Admins have unrestricted access to the entire system and can only be managed by themselves or other Super Admins. Regular administrators, regardless of whether they possess the `Admin.Staff` permission, cannot access or modify Super Admin accounts. 12 | 13 | ## How Permissions Work 14 | 15 | Permissions in TastyIgniter are represented by string keys formatted as `Author.Extension.Permission`. These permissions are assigned to an administrator via the staff's role, which inherits specific permissions. 16 | 17 | For instance, if administrator _Sam_ is part of the _Chef_ role and the _Chef_ role includes the `Admin.AssignOrder` permission, Sam will inherit the ability to manage orders. Staff Roles function as named groups of permissions, defining the role of an administrator within the system. A staff member can be assigned only one role at a time, but multiple staff members can share the same role. TastyIgniter comes with four predefined system roles: `owner`, `manager`, `waiter`, and `delivery`. Additionally, you can create and assign any number of custom roles with unique permission combinations to administrators. 18 | 19 | It's important to note that Staff Groups are unrelated to permissions. They serve purely as an administrative tool for organizing administrators. For example, groups can be used to assign tasks to all administrators in the Kitchen, facilitating management and coordination. 20 | 21 | ## Registering permissions 22 | 23 | Administrator permissions in TastyIgniter can be registered by extensions by overriding the `registerPermissions` method in the [Extension class](../extend/extensions#extension-class). Permissions are defined as an array where the keys are the permission name, and the values provide details about the permission group and descriptions. Each permission key follows a format that includes the name of the author, the name of the extension, and the name of the function. 24 | 25 | Here is an example of how permissions might be registered: 26 | 27 | ```php 28 | public function registerPermissions(): array 29 | { 30 | return [ 31 | 'Author.Extension.ManageSettings' => [ 32 | 'group' => 'Configuration', 33 | 'label' => 'Manage extension settings' 34 | ], 35 | ]; 36 | } 37 | ``` 38 | 39 | In this example, each key `Author.Extension.ManageSettings` represents a specific action or capability within the extension that can be enabled or disabled per administrator. The `group` and `label` provide context and description for these permissions, making it easier to understand and manage within the admin interface. 40 | 41 | ## Wildcard permissions 42 | 43 | Additionally, you can use the asterisk symbol (`*`) to represent a wildcard, indicating the "all permissions" condition within a specific scope. This allows any administrator with permissions starting with a specific prefix to access the controller pages. For example: 44 | 45 | ```php 46 | public $requiredPermissions = ['Author.Extension.*']; 47 | ``` 48 | 49 | This setup means that any administrator with permissions that begin with `Author.Extension.` (such as `Author.Extension.Create`, `Author.Extension.Edit`, etc.) will have access to the pages controlled by the `StaticPages` controller. This wildcard approach is useful for granting access to multiple permissions at once. 50 | 51 | ## Restricting access to admin pages 52 | 53 | In the [admin controller](../extend/controllers) class of TastyIgniter, you can specify the required permissions for accessing the controller's actions using the `$requiredPermissions` property. This property holds an array of permission keys that determine the access level needed. If an administrator has any of the permissions listed, they are granted access to the controller pages. 54 | 55 | Here is an example of how to restrict access to a controller using permissions: 56 | 57 | ```php 58 | 'Author.Extension.DeleteRecord' 82 | ]; 83 | } 84 | ``` 85 | 86 | ## Restricting access to features 87 | 88 | In TastyIgniter, the `\Igniter\User\Models\User` user model includes a method named `hasPermission` for determining whether the administrator has specific permissions. This functionality is particularly useful for implementing access controls within the admin user interface, ensuring that only authorized users can access certain features or data. 89 | 90 | The `hasPermission` method can be called with either a single permission key string or an array of key strings. Additionally, there is an optional parameter that allows you to specify whether all listed permissions are required (`true`) or if having any one of the permissions is sufficient (`false`). By default, this parameter is set to `false`, meaning the function will return `true` if any of the specified permissions match. 91 | 92 | This method will always return `true` for administrators with the `super_user` flag set to `true`, indicating they are superadmins and have unrestricted access. For other users, it checks the permissions assigned through their role or group. 93 | 94 | Here an example using the `hasPermission` method in the controller code: 95 | 96 | ```php 97 | // Check if the user has any permission within the 'Author.Extension' scope 98 | if ($user->hasPermission('Author.Extension.*')) { 99 | // 100 | } 101 | 102 | // Check if the user has both 'ManagePages' and 'ManageMenus' permissions 103 | if ($user->hasPermission([ 104 | 'Author.Extension.ManagePages', 105 | 'Author.Extension.ManageMenus' 106 | ], true)) { 107 | // Execute code only if the user has both permissions 108 | } 109 | ``` 110 | -------------------------------------------------------------------------------- /extend/widgets.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Widgets" 3 | section: extend 4 | sortOrder: 220 5 | --- 6 | 7 | ## Introduction 8 | 9 | In TastyIgniter, widgets are self-contained blocks of functionality used to perform specific tasks within the Admin Interface. Widgets always have a user interface and admin controller, referred to as the widget class. The widget class is responsible for preparing data for the widget and handling AJAX requests that are initiated from the widget's user interface. 10 | 11 | ## Generic widget 12 | 13 | Widgets in TastyIgniter are the admin equivalent of frontend [components](../extend/components). The major difference is that admin widgets use PHP arrays for configuration and are linked specifically to admin pages. 14 | 15 | Widget classes reside in the `src/Widgets` directory of an extension. Widgets may include assets and partials to enhance functionality. Here's an example of a typical widget directory structure: 16 | 17 | ```yaml 18 | author/ 19 | extension/ 20 | src/ 21 | Widgets/ 22 | MyWidget.php 23 | resources/ 24 | css/ 25 | mywidget.css 26 | js/ 27 | mywidget.js 28 | views/ 29 | _partials/ 30 | widgets/ 31 | mywidget.blade.php 32 | ``` 33 | 34 | ### Widget class 35 | 36 | The widget class is a PHP class that extends the `\Igniter\Admin\Classes\BaseWidget` class. The class contains properties and methods that define the widget's behavior and appearance. Here is an example of a simple widget class: 37 | 38 | ```php 39 | namespace Author\Extension\Widgets; 40 | 41 | class MyWidget extends \Igniter\Admin\Classes\BaseWidget 42 | { 43 | public string $defaultAlias = 'mywidget'; 44 | 45 | public function render() 46 | { 47 | $this->vars['var'] = 'value'; 48 | 49 | return $this->makePartial('mywidget'); 50 | } 51 | } 52 | ``` 53 | 54 | The widget class must implement a `render()` method that returns the widget's markup. In this example, the `makePartial()` method will scan the `resources/views/widgets` or `resources/views` directory of an extension to render the `mywidget.blade.php` view file. The `$vars` property is used to pass data to the view file. Alternatively you may pass the variables to the second parameter of the `makePartial()` method: 55 | 56 | ```php 57 | return $this->makePartial('mywidget', ['var' => 'value']); 58 | ``` 59 | 60 | ### AJAX handlers 61 | 62 | Widgets in TastyIgniter implement the same AJAX handling approach as [admin controllers](../extend/controllers). Widgets can handle AJAX requests by defining methods that start with `on` followed by the AJAX handler name. For example, to handle an AJAX request with the handler name `onAction`, you would define a method named `onAction` in the widget class. Here's an example of an AJAX handler method: 63 | 64 | ```php 65 | public function onAction() 66 | { 67 | // Your AJAX handler logic here 68 | } 69 | ``` 70 | 71 | The only different between widget AJAX handlers and those of admin controllers is how they are referenced. When referring to a widget AJAX handler within widget partials, you should use the widget's `getEventHandler()` method to accurately return the handler name specific to the widget. For example: 72 | 73 | ```blade 74 | 78 | ``` 79 | 80 | The example above will generate the following HTML attribute value by prefixing the handler name with the widget's alias: 81 | 82 | ```html 83 | 87 | ``` 88 | 89 | ### Binding widgets to controllers 90 | 91 | Before you can use a widget on admin pages in TastyIgniter, it must be bound to an admin controller. This is done using the widget's `bindToController` method. The optimal place to initialize a widget is within the constructor of the controller. For example: 92 | 93 | ```php 94 | public function __construct() 95 | { 96 | parent::__construct(); 97 | 98 | $myWidget = new MyWidget($this); 99 | $myWidget->bindToController(); 100 | } 101 | ``` 102 | 103 | Once a widget is bound to a backend controller in TastyIgniter, you can access it within the controller's view or partial using its assigned alias. 104 | 105 | ```blade 106 | {{ $this->widgets['mywidget']->render() }} 107 | ``` 108 | 109 | ## Using form widget 110 | 111 | Form widgets in TastyIgniter allow you to introduce new control types to admin forms. To use form widgets, they must first be registered in the [Extension class](../extend/extensions#extension-class). 112 | 113 | Form widget classes are located in the `src/FormWidgets` directory of an extension. Form widgets can also include assets and partials. Here's an example: 114 | 115 | ```yaml 116 | author/ 117 | extension/ 118 | src/ 119 | FormWidgets/ 120 | MyFormWidget.php 121 | resources/ 122 | css/ 123 | myformwidget.css 124 | js/ 125 | myformwidget.js 126 | views/ 127 | _partials/ 128 | formwidgets/ 129 | myformwidget.blade.php 130 | ``` 131 | 132 | ### Form widget class 133 | 134 | The form widget class is a PHP class that extends the `\Igniter\Admin\Classes\BaseFormWidget` class. A registered widget can be used in the admin [form definition file](../extend/forms#form-definition-file). Here is an example of a simple form widget class: 135 | 136 | ```php 137 | namespace Author\Extension\FormWidgets; 138 | 139 | class MyFormWidget extends \Igniter\Admin\Classes\BaseFormWidget 140 | { 141 | public string $defaultAlias = 'myformwidget'; 142 | 143 | public function render() 144 | { 145 | $this->vars['var'] = 'value'; 146 | 147 | return $this->makePartial('myformwidget'); 148 | } 149 | } 150 | ``` 151 | 152 | ### Form widget properties 153 | 154 | Form widgets can have properties that define their behavior and appearance that can be set in the form definition file. Define the configurable properties directly on the class. Then, within the `initialize` method, use the `fillFromConfig` method to populate these properties with values from your form definition file. Here is an example of a form widget class with properties: 155 | 156 | ```php 157 | namespace Author\Extension\FormWidgets; 158 | 159 | class MyFormWidget extends \Igniter\Admin\Classes\BaseFormWidget 160 | { 161 | // 162 | // Configurable properties 163 | // 164 | 165 | public string $mode = 'grid'; 166 | 167 | public string $property = 'value'; 168 | 169 | // 170 | // Object properties 171 | // 172 | 173 | public string $defaultAlias = 'myformwidget'; 174 | 175 | public function initialize() 176 | { 177 | $this->fillFromConfig([ 178 | 'mode' 179 | 'property' 180 | ]); 181 | } 182 | } 183 | ``` 184 | 185 | You can then set these properties in the form definition file: 186 | 187 | ```php 188 | 'field' => [ 189 | 'label' => 'Field', 190 | 'type' => 'myformwidget', 191 | 'mode' => 'inline', 192 | 'property' => 'new value', 193 | ], 194 | ``` 195 | 196 | ### Form widget registration 197 | 198 | To register a form widget, you must add it to the `registerFormWidgets` method in the extension class. The method returns an array containing the widget class in the keys and widget short code as the value. Here is an example of how to register a form widget: 199 | 200 | ```php 201 | public function registerFormWidgets() 202 | { 203 | return [ 204 | 'Author\Extension\FormWidgets\MyFormWidget' => 'myformwidget', 205 | ]; 206 | } 207 | ``` 208 | 209 | > **Note:** The widget short code should be a unique value. 210 | 211 | ### Loading form data 212 | 213 | The primary function of a form widget is to interact with your model, by loading and saving data to the database. When a form widget is rendered, it retrieves its stored value using the `getLoadValue` method. Additionally, the `getId` and `getFieldName` methods provide a unique identifier and the name for an HTML element used in the form. These values are typically passed to the widget partial at the time of rendering. 214 | 215 | Here's how you might implement this in the `render` method: 216 | 217 | ```php 218 | public function render() 219 | { 220 | $this->vars['id'] = $this->getId(); 221 | $this->vars['name'] = $this->getFieldName(); 222 | $this->vars['value'] = $this->getLoadValue(); 223 | 224 | return $this->makePartial('myformwidget'); 225 | } 226 | ``` 227 | 228 | In the partial `myformwidget`, the HTML element can be rendered using the prepared variables: 229 | 230 | ```blade 231 | 236 | ``` 237 | 238 | ### Saving form data 239 | 240 | When it comes to storing user input in the database, the form widget uses the `getSaveValue` method internally to capture the value. If you need to modify this behavior, you can override this method in your form widget class: 241 | 242 | ```php 243 | public function getSaveValue($value) 244 | { 245 | return $value; 246 | } 247 | ``` 248 | 249 | In scenarios where you might not want any value to be saved (e.g., a form widget that displays information without saving it), you can return a special constant `FormField::NO_SAVE_DATA` from the `Igniter\Admin\Classes\FormField` class to ensure the value is ignored: 250 | 251 | ```php 252 | public function getSaveValue($value) 253 | { 254 | return \Igniter\Admin\Classes\FormField::NO_SAVE_DATA; 255 | } 256 | ``` 257 | 258 | ## Using dashboard widget 259 | 260 | Dashboard widgets in TastyIgniter are used to display information on the admin dashboard. They are similar to form widgets but are designed to be used on the dashboard page. Dashboard widgets can be registered in the [Extension class](../extend/extensions#extension-class). 261 | 262 | Dashboard widget classes are located in the `src/DashboardWidgets` directory of an extension. Similarly to all form widgets, dashboard widgets can also include assets and partials. Here's an example: 263 | 264 | ```yaml 265 | author/ 266 | extension/ 267 | src/ 268 | DashboardWidgets/ 269 | MyDashboardWidget.php 270 | resources/ 271 | css/ 272 | mydashboardwidget.css 273 | js/ 274 | mydashboardwidget.js 275 | views/ 276 | _partials/ 277 | dashboardwidgets/ 278 | mydashboardwidget.blade.php 279 | ``` 280 | 281 | ### Dashboard widget class 282 | 283 | The dashboard widget class is a PHP class that extends the `\Igniter\Admin\Classes\BaseDashboardWidget` class. Here is an example of a simple dashboard widget class: 284 | 285 | ```php 286 | namespace Author\Extension\DashboardWidgets; 287 | 288 | class MyDashboard extends \Igniter\Admin\Classes\BaseDashboardWidget 289 | { 290 | public string $defaultAlias = 'mydashboardwidget'; 291 | 292 | public function render() 293 | { 294 | $this->vars['var'] = 'value'; 295 | 296 | return $this->makePartial('mydashboardwidget'); 297 | } 298 | } 299 | ``` 300 | 301 | When designing a widget partial, you can include various types of content such as charts, indicators, lists, or any other HTML markup. To ensure consistent styling and integration within the admin, wrap your markup within a **DIV** element with the class `dashboard-widget`. It's also recommended to use an **H3** element for the widget's header. 302 | 303 | ```blade 304 |
    305 |

    My Dashboard Widget

    306 | 307 |
    308 |
      309 |
    • First Data Point: 100
    • 310 |
    • Second Data Point: 200
    • 311 |
    • Third Data Point: 300
    • 312 |
    313 |
    314 |
    315 | ``` 316 | 317 | ### Dashboard widget properties 318 | 319 | Dashboard widgets can have properties that define their behavior and appearance. These properties can be defined using the `defineProperties` method in the dashboard widget class. Here is an example of a dashboard widget class with properties: 320 | 321 | ```php 322 | namespace Author\Extension\DashboardWidgets; 323 | 324 | class MyDashboard extends \Igniter\Admin\Classes\BaseDashboardWidget 325 | 326 | public function defineProperties(): array 327 | { 328 | return [ 329 | 'property' => [ 330 | 'label' => 'Property', 331 | 'type' => 'text', 332 | 'default' => 'value', 333 | ], 334 | ]; 335 | } 336 | } 337 | ``` 338 | 339 | ### Dashboard widget registration 340 | 341 | To register a dashboard widget, you must add it to the `registerDashboardWidgets` method in the extension class. The method returns an array containing the widget class in the keys and widget configuration (label, context, and required permissions) as the value. Here is an example of how to register a dashboard widget: 342 | 343 | ```php 344 | public function registerDashboardWidgets() 345 | { 346 | return [ 347 | \Author\Extension\DashboardWidgets\MyDashboard::class => [ 348 | 'label' => 'My Dashboard Widget', 349 | 'context' => 'dashboard', 350 | 'permissions' => ['Author.Extension.*'], 351 | ], 352 | ]; 353 | } 354 | ``` 355 | -------------------------------------------------------------------------------- /installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Installation" 3 | section: "getting-started" 4 | sortOrder: 10 5 | --- 6 | 7 | As of Version 4, TastyIgniter can be installed as a [stand-alone](#stand-alone-installation) application, or as a [package](#package-installation) inside an existing Laravel application. 8 | 9 | ## Stand-alone Installation 10 | 11 | ### Requirements 12 | 13 | These are the requirements to run TastyIgniter as a stand-alone application: 14 | 15 | - **Apache** (with mod_rewrite enabled) or **Nginx** 16 | - **PHP 8.3+** with the following extensions: bcmath, pdo_mysql, ctype, curl, openssl, dom, gd, exif, mbstring, json, 17 | tokenizer, zip, xml 18 | - **MySQL 8+** or **PostgreSQL 10.0** 19 | - **Composer 2.0** or **higher** (for installing dependencies) 20 | 21 | ### Installing TastyIgniter 22 | 23 | TastyIgniter manages its dependencies and extensions using composer. 24 | To install the platform, use the `create-project` command in the terminal to create a project. The command 25 | below creates a new project in the directory `tastyigniter`. 26 | 27 | ```bash 28 | composer create-project tastyigniter/tastyigniter tastyigniter 29 | ``` 30 | 31 | From here, you can move on to the [Setting up TastyIgniter](#setting-up-tastyigniter) step. 32 | 33 | ## Package Installation 34 | 35 | ### Requirements 36 | 37 | These are the requirements to run TastyIgniter as a package in a Laravel application: 38 | 39 | - **Laravel 11+** 40 | - **MySQL 8+** or **PostgreSQL 10.0** 41 | - **Composer 2.0** or **higher** (for installing dependencies) 42 | 43 | ### Installing TastyIgniter 44 | 45 | To install TastyIgniter as a package from your command line, run the following command in your Laravel project 46 | directory: 47 | 48 | ```bash 49 | composer require tastyigniter/core 50 | ``` 51 | 52 | From here, you can move on to the [Setting up TastyIgniter](#setting-up-tastyigniter) step. 53 | 54 | ## Setting up TastyIgniter 55 | 56 | TastyIgniter includes a command-line setup tool that will get you up and running in a few minutes. It will attempt to 57 | set up TastyIgniter and create an admin user account. 58 | 59 | In the TastyIgniter installation's root directory, run the following command: 60 | 61 | ```bash 62 | php artisan igniter:install 63 | ``` 64 | 65 | The setup command will guide you through the process of setting up TastyIgniter for the first time. It will ask 66 | for the database configuration, application URL and administrator details. 67 | 68 | > You can safely run the command multiple times if needed. 69 | 70 | **Command-line unattended setup** 71 | 72 | Some setup require an unattended mode so that the application can easily be built into automated infrastructure 73 | pipelines and build tools, e.g. Docker. 74 | 75 | To run this, it is similar to the above command, just instead we provide all the option values up-front within the 76 | projects 77 | `.env` and pass the `--no-interaction` flag to the installation script: 78 | 79 | ```bash 80 | php artisan igniter:install --no-interaction 81 | ``` 82 | 83 | ## Post-installation steps 84 | 85 | There are some things you may need to set up after the installation is complete. 86 | 87 | ### Directory permissions 88 | 89 | Once TastyIgniter is installed, you must grant the non-root user the necessary permissions so that TastyIgniter and Laravel can write to the required system directories. 90 | 91 | ```bash 92 | sudo chmod -R 755 /path/to/tastyigniter 93 | sudo chown -R www-data:www-data /path/to/tastyigniter 94 | ``` 95 | 96 | > You should never set any folder or file to permission level **777**, as this permission level allows anyone to access the content of the folder and file regardless of user or group. 97 | 98 | ### Setting up the task scheduler 99 | 100 | You should add the following Cron entry to your server for scheduled tasks to function properly. Crontab editing is 101 | usually done with the command `crontab -e`. 102 | 103 | ```bash 104 | * * * * * php /path/to/artisan schedule:run >> /dev/null 2>&1 105 | ``` 106 | 107 | Be sure to replace `/path/to/artisan` with the absolute path to the artisan file in your TastyIgniter root directory . 108 | This Cron will call the command scheduler every minute. When executing the `schedule:run` command, TastyIgniter will 109 | assess your scheduled tasks and run the tasks that are due. 110 | 111 | > Task Scheduling is how scheduling time-based tasks are managed in TastyIgniter. Several core features of TastyIgniter, 112 | > such as assigning orders and checking for updates, use the scheduler. 113 | 114 | ### Setting up the queue daemon 115 | 116 | By default, the queue in TastyIgniter is synchronous and will attempt to run tasks such as sending emails in real time. 117 | This behaviour can be set to an asynchronous method by updating the `QUEUE_CONNECTION` variable in your application's 118 | `.env` file. 119 | 120 | It is a good idea to run the queue process as a daemon service. Use the following 121 | command: 122 | 123 | ```bash 124 | php /path/to/artisan queue:work 125 | ``` 126 | 127 | You can use Supervisor process monitor to automatically restart the `queue:work` 128 | command if it fails. 129 | 130 | For more information on configuring Supervisor and using Queues, consult 131 | the Laravel Queue docs. 132 | 133 | ### Application configuration 134 | 135 | **Debug mode** 136 | 137 | The debug setting is found in the `config/app.php` configuration file with the `debug` parameter, and is disabled by default. 138 | 139 | When enabled, this setting will display detailed error messages when they occur along with other debugging functions. 140 | Debug mode should always be disabled in a live production site. This prevents the display of potentially sensitive 141 | information to the end user. 142 | 143 | > **Important:** Always set the `APP_DEBUG` setting to false in production environments. 144 | 145 | **CSRF protection** 146 | 147 | TastyIgniter offers a simple method to protect your application from cross-site request forgeries. 148 | 149 | For every active user session managed by the application, TastyIgniter automatically generates a CSRF "token." This 150 | token is used to check that the authenticated user is the one who actually makes the client requests. 151 | 152 | Although CSRF security is enabled by default, you can disable it by updating the `ENABLE_CSRF` variable in your application's `.env` file. 153 | 154 | ### Apache/Nginx configuration 155 | 156 | TastyIgniter has basic configuration that should be applied to your webserver. Common webservers and their configuration 157 | can be found below. 158 | 159 | #### Apache configuration 160 | 161 | TastyIgniter includes a [`.htaccess`](https://github.com/tastyigniter/TastyIgniter/blob/master/public/.htaccess) file - make sure it's been uploaded correctly. The `.htaccess` file is located in the `public` directory of your TastyIgniter installation. There are some extra system requirements if your webserver is running Apache, `mod_rewrite` should be installed and enabled and the `AllowOverride` option should be switched on. 162 | 163 | ```apache 164 | 165 | AllowOverride All 166 | 167 | ``` 168 | 169 | You will need to uncomment this line in the `.htaccess` file in some cases: 170 | 171 | ```html 172 | ## !IMPORTANT! You may need to uncomment the following line for some hosting environments, 173 | ## If your installation resides in a subdirectory, enter the name here also 174 | ## 175 | # RewriteBase / 176 | ``` 177 | 178 | If you've created a subdirectory, you can specify the subdirectory name as well: 179 | 180 | ```html 181 | RewriteBase /subdirectory/ 182 | ``` 183 | 184 | #### Nginx configuration 185 | 186 | Make sure that the [`.nginx.conf`](https://github.com/tastyigniter/TastyIgniter/blob/master/.nginx.conf) file included with TastyIgniter has been uploaded correctly. The `.nginx.conf` file is located in the root directory of your TastyIgniter installation. Then, assuming you have Nginx setup, add the following to your server's configuration block: 187 | 188 | ```html 189 | include /path/to/tastyigniter/.nginx.conf; 190 | ``` 191 | 192 | As an example, your site conf file should look something like: 193 | 194 | ```html 195 | server { 196 | listen 80; 197 | 198 | root /path/to/tastyigniter; 199 | index index.php; 200 | 201 | server_name mytastysite.com; 202 | 203 | gzip on; 204 | gzip_proxied expired no-cache no-store private auth; 205 | gzip_types text/plain text/css application/x-javascript application/json application/javascript image/x-icon image/png image/gif image/jpeg image/svg+xml; 206 | 207 | charset utf-8; 208 | 209 | access_log off; 210 | 211 | include /path/to/tastyigniter/.nginx.conf; 212 | } 213 | ``` 214 | 215 | ## Getting Started 216 | 217 | You can access the administrator panel from `/admin` with your username and password asked during the setup process. 218 | After you've logged in you'll be able to access the administration panel to configure your site. 219 | 220 | > Follow the getting started steps on the administration panel dashboard. 221 | 222 | ## Getting help 223 | 224 | - If you believe you have found a bug, please report it using 225 | the GitHub issue tracker, or better 226 | yet, fork the repo and submit a pull request. 227 | - If you have feedback to share, ideas you would like implemented, by all means, share them on 228 | the TastyIgniter Community Forums. 229 | - Join us on Discord to chat with us. 230 | 231 | ## Troubleshooting 232 | 233 | 1. **A 404 error page is displayed:** This could be a result of the mod_rewrite module not being activated/installed or 234 | configured properly. Activate mod_rewrite for the Apache web-server. If it is already activated, check the root 235 | htaccess file in `/.htaccess`, to make sure the `RewriteBase` value is configured properly. 236 | 2. **A blank screen is displayed when opening the application:** Check the file permissions are set correctly on 237 | the `/storage` files and folders and writable for the web server. Also check that your files are owned by the correct 238 | group and user. 239 | 3. **Setup successful but storefront links are not working:** Check that the theme's required extensions are all 240 | installed. 241 | 242 | > **Note:** A detailed log can be found in the `storage/logs/laravel.log` file. 243 | -------------------------------------------------------------------------------- /resources/code-of-conduct.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Code Of Conduct" 3 | section: "resources" 4 | sortOrder: 400 5 | --- 6 | 7 | One of our main goals, as an expanding community is to include as many contributors from various backgrounds and areas. We want every single member of the TastyIgniter community to get as much out of it as possible. Therefore, we don't want to scare people off and want to make it a place where all people can co-exist regardless of religion, socio-economic status, ethnicity, ability, sexual orientation or gender. 8 | 9 | Whether you are using the TastyIgniter forum, GitHub, Discord chat or an alternative means of communication outside our community, this code of conduct is applicable. 10 | 11 | ### Always exercise patience and have a friendly attitude 12 | 13 | We aren't always going to agree with one another. That doesn't mean we should fight or forget the manners our moms and dads taught us. Don't throw tantrums, just because you are frustrated at someone. It wouldn't be a healthy community if there were members who felt unwelcome or threatened. 14 | 15 | ### Always Be Considerate 16 | 17 | Remember that as you use other's work, they will use yours. Therefore, any decisions you make will impact colleagues and members, so always take the ramifications of those decisions into consideration when making them. TastyIgniter is a global community, so you may not always be communicating with individuals who speak the same language as you. 18 | 19 | ### Make All Responses Count 20 | 21 | Obviously, if you are participating in discussions, you are making effort and time to do so, in the hope other members will look at your suggestions and consider them. So, make the most of it. 22 | 23 | - Don't just reply to a title. Read the post properly and consider the conversation before responding. 24 | - Don't just add something so you feel included. Think about whether your comment will add to the discussion or not. If it won't, hold fire on replying. 25 | - Don't just reply with posts containing just one word to show you agree with another poster's comment or suggestion. You can use the "Like" button, amigo. 26 | - Don't post numerous posts when you could put all your points and thoughts into one. Remember, it's not a chatroom. 27 | - Will your comment swerve the discussion in a different direction? If so, maybe start a brand-new discussion of your very own? 28 | - As TastyIgniter is an inclusive community, you need to make sure that responses your posts offer support and constructive feedback. Don't shoot people down, no-one likes a show-off. 29 | 30 | We want everything to be light and breezy, where possible, so you will find that smart comments and jokes are welcomed. However, the discussions need to be effective and productive too, so avoid disrupting discussions completely with unrelated wise-ass statements. 31 | 32 | ### Choose your words very carefully 33 | 34 | This is a professional community and we act in a professional manner. Always be kind and avoid putting down or insulting other users. We will not tolerate exclusionary behavior or harassment between users. 35 | 36 | To be clear, we consider this type of behavior to include: 37 | 38 | - Insults directed at other members, especially sexist and racist terms and phrases 39 | - Posting material that is violent or sexually explicit 40 | - The use of discriminatory language and jokes 41 | - Directing violent language and threats at other people 42 | - Try to understand why disagreements happen. 43 | 44 | ### When we disagree, try to understand why. 45 | 46 | Technical and social disagreements are commonplace in the world and within the TastyIgniter community we are not immune to these. Therefore, it is imperative that we constructively and productively resolve differing viewpoints and disagreements. We are all different and one of the biggest strengths of our community is the fact that we come from different backgrounds. 47 | 48 | You can't force people to have the same opinions as you, even if they are wrong. Remember that humans make errors. We can't be a progressive community if we just end up pointing the finger at one another and passing blame. Always aim, rather, to help with the resolution of these issues and learn how to do things differently and better. 49 | 50 | The above are sections taken from the Drupal Code of Conduct and the Django Code of Conduct. -------------------------------------------------------------------------------- /resources/contribution-guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Contribution Guide" 3 | section: "resources" 4 | sortOrder: 410 5 | --- 6 | 7 | Interested in contributing to the development of TastyIgniter? All contributions are appreciated and welcome: from 8 | opening a bug report to creating a pull request. 9 | 10 | Before contributing, please read the [code of conduct](../resources/code-of-conduct). 11 | 12 | To order to learn a little more about how TastyIgniter operates, we suggest that you read the documentation, if you're 13 | just beginning. 14 | 15 | ## New features 16 | 17 | If you have a feature idea, the [TastyIgniter forum](https://forum.tastyigniter.com/) is the best place to suggest it. Please do not use GitHub issues to 18 | suggest a new feature. 19 | 20 | Use GitHub only if you plan to contribute and develop a new feature. If you'd like to discuss your idea first, you can 21 | always join us on Discord before posting it "officially" 22 | anywhere. 23 | 24 | ## Reporting bugs 25 | 26 | > Please don't use the main GitHub for reporting issues with extensions or themes. If you have found a bug in an extension or theme, the best place to report it is with the [author](https://tastyigniter.com/marketplace). 27 | 28 | Issues are a quick way to point out a bug. If you find a bug in TastyIgniter then please check a few things first: 29 | 30 | 1. Search the TastyIgniter forum, ask the community if they have seen the bug or know how to fix it. 31 | 2. There is not already an open Issue 32 | 3. The issue has already been fixed (look for closed Issues) 33 | 4. Is it something really obvious that you can fix yourself? 34 | 35 | When you are creating a bug report, please include as many details as possible. Fill out the required template, the information it asks for helps us resolve issues faster 36 | 37 | Please be very clear on your commit messages and pull request, duplicate or empty commit or pull request messages may be 38 | rejected without reason. 39 | 40 | ## Which Branch? 41 | 42 | > **Note:** This section applies specifically to those sending pull requests to any repositories under the TastyIgniter organization. 43 | 44 | **All** bug fixes should be sent to the latest stable branch that supports bug fixes (currently `4.x` and `master` for packages). Bug fixes 45 | should **never** be sent to the `develop` branch unless they fix features that exist only in the upcoming release. 46 | 47 | **Minor** features that are **fully backward compatible** with the current release may be sent to the latest stable 48 | branch. 49 | 50 | **Major** new features or features with breaking changes should always be sent to the `develop` branch, which contains 51 | the upcoming release. 52 | 53 | If you are unsure if your feature qualifies as a major or minor, please ask Sam Poyigi in the `#core` channel of 54 | the [TastyIgniter Discord server.](https://tastyigniter.com/discord) 55 | 56 | ## Compiled Assets 57 | 58 | If you are submitting a change that will affect a compiled file, do not commit the compiled files. Due to their large 59 | size, they cannot realistically be reviewed by a maintainer. This could be exploited as a way to inject malicious code 60 | into TastyIgniter. In order to defensively prevent this, all compiled files will be generated and committed by 61 | TastyIgniter maintainers. 62 | 63 | ## Development environment setup 64 | 65 | tastyigniter/TastyIgniter is the stand-alone 66 | application for installing 67 | tastyigniter/core using Composer. 68 | 69 | - Make sure Composer accepts unstable releases from your local copies by adjusting the value of `minimum-stability` 70 | in `composer.json` to `dev`. 71 | - Run `composer install` to install composer dependencies. 72 | - Finally, run `composer update "tastyigniter/*" --prefer-source` to clone TastyIgniter packages into the `vendor` directory for development. 73 | 74 | ## How to contribute 75 | 76 | Follow these steps: 77 | 78 | - First you need to complete the development environment setup 79 | - Identity the repository you want to contribute to 80 | - Fork the repository 81 | - Clone your forked repository into the development environment 82 | - Checkout the current release branch (3.x or 4.x) 83 | - Make your **changes** 84 | - Run `composer test` to run the test-suite on your changes 85 | - **Commit and push** your changes with a descriptive message. 86 | - Submit in the **pull request** on GitHub 87 | 88 | The CI/CD workflows must run successfully on your pull requests to be merged into the release branch. 89 | 90 | ## Development tools 91 | 92 | Most TastyIgniter contributors develop with 93 | PHPStorm. However, feel free to use your preferred IDE. 94 | 95 | To serve a local TastyIgniter website, Laravel 96 | Valet (Mac), XAMPP (Windows), 97 | and TastyIgniter Docker (Linux) are popular 98 | choices. 99 | 100 | ## Reporting security issues 101 | 102 | If you wish to contact us about any security vulnerability in TastyIgniter you may find, please send an e-mail to 103 | 104 | 105 | ## Writing documentation 106 | 107 | You are very welcome to contribute to the TastyIgniter documentation. Please follow these rules if you want to 108 | contribute. Here's how styling perfect TastyIgniter documentation pages is done: 109 | 110 | - Try not to use H1 headers. 111 | - A TOC list would be generated automatically for each page with at least one H2 header. The TOC would have links to all 112 | of all the page's H2 headers. 113 | - The introductory text would be displayed below the TOC. 114 | - Try to use only H2 and H3 headers. 115 | - Each H2 and H3 header could have a link defined as `` or have it generated automatically. 116 | - Avoid short, 1 sentence paragraphs. Combine short paragraphs and try to be a bit more verbose. 117 | - Avoid short paragraphs hanging below code sections. Combine these paragraphs with the text above the code blocks. 118 | - Use the inline code tags for all code-related - variable names, function names, syntax examples, etc. 119 | - Don't hesitate to make cross-links to other documentation articles. There is no need to add links to the same article 120 | in the same paragraph. 121 | - For your reference, see the [pages](https://github.com/tastyigniter/docs/blob/master/customize/pages.md) 122 | or [themes](https://github.com/tastyigniter/docs/blob/master/customize/themes.md) files. 123 | 124 | > For more information on contributing, read the guide here 125 | -------------------------------------------------------------------------------- /resources/js-coding-guidelines.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Javascript Coding Guidelines" 3 | section: "resources" 4 | sortOrder: 470 5 | --- 6 | 7 | [Prettier](https://prettier.io/) determines our code style. While Prettier's output isn't always the prettiest, it's consistent and removes all (meaningless) discussion about code style. 8 | 9 | We try to stick to Prettier's defaults, but have a few overrides to keep our JavaScript code style consistent with PHP. 10 | 11 | The first two rules are actually configured with `.editorconfig`. We use 4 spaces as indentation. 12 | 13 | ```editorconfig 14 | indent_size = 4 15 | ``` 16 | 17 | Since we use 4 spaces instead of the default 2, the default 80 18 | character line length can be a bit short (especially when writing 19 | templates in JSX). 20 | 21 | ```json 22 | { 23 | "printWidth": 120 24 | } 25 | ``` 26 | 27 | Finally, we prefer single quotes over double quotes for consistency with PHP. 28 | 29 | ```json 30 | { 31 | "singleQuote": true 32 | } 33 | ``` 34 | 35 | ## Variable assignment 36 | 37 | Prefer `const` over `let`. Only use `let` to indicate that a variable will be reassigned. Never use `var`. 38 | 39 | **Good:** 40 | 41 | ```jsx 42 | const person = { name: 'Sebastian' }; 43 | person.name = 'Seb'; 44 | ``` 45 | 46 | **Bad:** 47 | 48 | ```jsx 49 | // the variable was never reassigned 50 | let person = { name: 'Sebastian' }; 51 | person.name = 'Seb'; 52 | ``` 53 | 54 | ## Variable names 55 | 56 | Variable names generally shouldn't be abbreviated. 57 | 58 | **Good:** 59 | 60 | ```jsx 61 | function saveUser(user) { 62 | localStorage.set('user', user); 63 | } 64 | ``` 65 | 66 | **Bad:** 67 | 68 | ```jsx 69 | // it's hard to reason about abbreviations in blocks as they grow. 70 | function saveUser(u) { 71 | localStorage.set('user', u); 72 | } 73 | ``` 74 | 75 | In single-line arrow functions, abbreviations are allowed to reduce noise if the context is clear enough. For example, if you're calling `map` of `forEach` on a collection of items, it's clear that the parameter is an item of a certain type, which can be derived from the collection's substantive variable name. 76 | 77 | **Good:** 78 | 79 | ```jsx 80 | function saveUserSessions(userSessions) { 81 | userSessions.forEach(s => saveUserSession(s)); 82 | } 83 | 84 | // Ok, but pretty noisy. 85 | function saveUserSessions(userSessions) { 86 | userSessions.forEach(userSession => saveUserSession(userSession)); 87 | } 88 | ``` 89 | 90 | ## Comparisons 91 | 92 | Always use a triple equal to do variable comparisons. If you're unsure of the type, cast it first. 93 | 94 | **Good:** 95 | 96 | ```jsx 97 | const one = 1; 98 | const another = "1"; 99 | 100 | if (one === parseInt(another)) { 101 | // ... 102 | } 103 | ``` 104 | 105 | **Bad:** 106 | 107 | ```jsx 108 | // Bad 109 | const one = 1; 110 | const another = "1"; 111 | 112 | if (one == another) { 113 | // ... 114 | } 115 | ``` 116 | 117 | ## Function keyword vs. arrow functions 118 | 119 | Function declarations should use the function keyword. 120 | 121 | **Good:** 122 | 123 | ```jsx 124 | function scrollTo(offset) { 125 | // ... 126 | } 127 | ``` 128 | 129 | **Bad:** 130 | 131 | ```jsx 132 | // Using an arrow function doesn't provide any benefits here, while the 133 | // `function` keyword immediately makes it clear that this is a function. 134 | const scrollTo = (offset) => { 135 | // ... 136 | }; 137 | ``` 138 | 139 | Terse, single line functions may also use the arrow syntax. There's no hard rule here. 140 | 141 | **Good:** 142 | 143 | ```jsx 144 | function sum(a, b) { 145 | return a + b; 146 | } 147 | 148 | // It's a short and simple method, so squashing it to a one-liner is ok. 149 | const sum = (a, b) => a + b; 150 | ``` 151 | 152 | ```jsx 153 | export function query(selector) { 154 | return document.querySelector(selector); 155 | } 156 | 157 | // This one's a bit longer, having everything on one line feels a bit heavy. 158 | // It's not easily scannable unlike the previous example. 159 | export const query = (selector) => document.querySelector(selector); 160 | ``` 161 | 162 | Higher-order functions may use arrow functions if it improves readability. 163 | 164 | ```jsx 165 | function sum(a, b) { 166 | return a + b; 167 | } 168 | 169 | const adder = (a) => (b) => sum(a, b); 170 | 171 | // Ok, but unnecessarily noisy. 172 | function adder(a) { 173 | return function (b) { 174 | return sum(a, b); 175 | }; 176 | } 177 | ``` 178 | 179 | Anonymous functions should use arrow functions. 180 | 181 | ```jsx 182 | ['a', 'b'].map((a) => a.toUpperCase()); 183 | ``` 184 | 185 | Unless they need access to `this`. 186 | 187 | ```jsx 188 | $('a').on('click', function () { 189 | window.location = $(this).attr('href'); 190 | }); 191 | ``` 192 | 193 | Try to keep your functions pure and limit the usage of the `this` keyword. 194 | 195 | Object methods must use the shorthand method syntax. 196 | 197 | **Good:** 198 | 199 | ```jsx 200 | export default { 201 | methods: { 202 | handleClick(event) { 203 | event.preventDefault(); 204 | }, 205 | }, 206 | }; 207 | ``` 208 | 209 | **Bad:** 210 | 211 | ```jsx 212 | // The `function` keyword serves no purpose. 213 | export default { 214 | methods: { 215 | handleClick: function (event) { 216 | event.preventDefault(); 217 | }, 218 | }, 219 | }; 220 | ``` 221 | 222 | ## Object and array destructuring 223 | 224 | Destructuring is preferred over assigning variables to the corresponding keys. 225 | 226 | **Good:** 227 | 228 | ```jsx 229 | const [hours, minutes] = '12:00'.split(':'); 230 | ``` 231 | 232 | **Bad:** 233 | 234 | ```jsx 235 | // unnecessarily verbose, and requires an extra assignment in this case. 236 | const time = '12:00'.split(':'); 237 | const hours = time[0]; 238 | const minutes = time[1]; 239 | ``` 240 | 241 | Destructuring is very valuable for passing around configuration-like objects. 242 | 243 | ```jsx 244 | function uploader({ 245 | element, 246 | url, 247 | multiple = false, 248 | beforeUpload = noop, 249 | afterUpload = noop, 250 | }) { 251 | // ... 252 | } 253 | ``` 254 | 255 | ___ 256 | The above are sections taken from the [Spatie Javascript Code Guidelines](https://spatie.be/guidelines/javascript). 257 | -------------------------------------------------------------------------------- /upgrade-guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Upgrade from v3.x" 3 | section: "getting-started" 4 | sortOrder: 30 5 | --- 6 | 7 | The following is an instructional guide on how to upgrade your v3.x TastyIgniter installation to the latest version v4.x 8 | 9 | TastyIgniter has been rewritten as an installable Laravel package. This is a huge change that affects almost 10 | every part of the codebase. 11 | 12 | ## Backing Up The Database 13 | 14 | Use this command back up your MySQL database. For example, if the username is `root` and the database is called 15 | `database_name`. You will then be prompted to enter the password. 16 | 17 | ```bash 18 | mysqldump -u root -p database_name > tastyigniter_backup.sql 19 | ``` 20 | 21 | To restore the backup, you can use this command. 22 | 23 | ```bash 24 | mysql -u root -p database_name < tastyigniter_backup.sql 25 | ``` 26 | 27 | ## New requirements 28 | 29 | - **PHP 8.2+** with the following extensions: bcmath, pdo_mysql, ctype, curl, openssl, dom, gd, exif, mbstring, json, 30 | tokenizer, zip, xml 31 | - **MySQL 5.7+** or **MariaDB 10.3+** or **PostgreSQL 10.0** 32 | 33 | ## Upgrading from v3.x 34 | 35 | > Before you get started, it's a good idea to back up your website. This means if there are any issues you can restore 36 | > your website. 37 | 38 | Install TastyIgniter 4.0 in a new directory 39 | 40 | ```bash 41 | composer create-project tastyigniter/tastyigniter mytasty-new 42 | ``` 43 | 44 | To make the configuration process run smoothly, you can copy your old configuration to the new website. This step is 45 | optional 46 | 47 | ```bash 48 | cp mytasty/.env mytasty-new/ 49 | ``` 50 | 51 | To avoid broken media, you can either copy or move the `assets/media` directory to the storage directory on the new 52 | installation. 53 | 54 | ```bash 55 | cp assets/media storage/ 56 | ``` 57 | 58 | If you have custom extensions and themes you have developed yourself, you can copy them to the new installation after 59 | upgrade is complete. 60 | 61 | Proceed through the installation and database migration. Change directory to the new installation and running the 62 | installation command. 63 | 64 | ```bash 65 | cd mytasty-new 66 | php artisan igniter:install 67 | ``` 68 | 69 | Finally, complete the upgrade, replace the old installation directory with the new installation. 70 | 71 | ```bash 72 | cd .. 73 | mv mytasty mytasty-old 74 | mv mytasty-new mytasty 75 | ``` 76 | 77 | ### High impact changes 78 | 79 | #### TastyIgniter As A Package 80 | 81 | TastyIgniter can now be included in an existing Laravel application as a package. 82 | See [Package Installation](/installation#package-installation) for more details. 83 | 84 | #### Code Structure 85 | 86 | The codebase has been refactored and restructured to follow Laravel conventions which enhance maintainability and 87 | extensibility. This includes updated namespaces, relocated controllers, improved models, and form requests. 88 | 89 | - Classes under `Admin\\`, `Main\\`, `System\\` have moved to `Igniter\\Admin\\`, `Igniter\\Main\\`, `Igniter\\System\\` 90 | respectively. 91 | 92 | #### Admin Login with Email 93 | 94 | The admin login process now uses email addresses instead of usernames, providing a more convenient and familiar login 95 | experience for administrators. 96 | 97 | #### Mail Template Namespaces 98 | 99 | Mail template namespaces have been renamed for consistency. `admin::` is 100 | now `igniter.admin::`, `main::` is now `igniter.main::`, and `system::` is now `igniter.system::`. 101 | 102 | #### Translation String Keys 103 | 104 | Translation string keys have been updated to follow a consistent naming 105 | convention. `admin::lang.` is now `igniter::admin.`, `main::lang.` is now `igniter::main.`, and `system::lang.` is 106 | now `igniter::system.`. 107 | 108 | #### The Singleton Trait 109 | 110 | Singletons have been dropped, you can resolve all 'Manager' classes through the service container. This allows for better code 111 | organization and easier unit testing. `ExtensionManager::instance()` is now `resolve(ExtensionManager::class)`. 112 | 113 | #### Blade Directives 114 | 115 | We've introduced new Blade directives to streamline theme development. We've also renamed existing directives to prevent conflicts. The `@themeContent` directive now replaces `@content`, enabling the rendering of content template files. Similarly, `@themePage` has taken over from `@page` for rendering page contents. Additionally, `@themeStyles` and `@themeScripts` directives replace `@styles` and `@scripts` respectively. Furthermore, `@themeComponent` and `@themePartial` directives now replace the previous `@component` and `@partial` directives. 116 | 117 | See [Blade Directives on Markup guide](customize/markup-guide#directives) for more details. 118 | 119 | ### Medium impact changes 120 | 121 | #### Extension Configuration 122 | 123 | Extension configuration files are no longer merged automatically. Developers must use Laravel's `mergeConfigFrom()` method to merge configuration files from extension class `register` method. 124 | 125 | #### Database Changes 126 | 127 | Several database changes have been made, including merging `staffs` and `users` records into the `admin_users` table, 128 | renaming `staff_groups` to `user_groups`, prefixing user-related tables with `admin_`, dropping conflicting tables, and 129 | increasing the length of varchar to 255 on existing columns. 130 | 131 | ### Low impact changes 132 | 133 | #### Admin Controller Actions 134 | 135 | New base view files have been introduced for common admin controller actions such as index, edit, create, and preview. 136 | This eliminates the need to create these view files for your custom controller action. 137 | 138 | #### Mailable Integration 139 | 140 | Sending registered mail templates now uses Laravel's Mailable classes instead of custom logic. This provides a more 141 | standardized and maintainable approach to sending emails. 142 | 143 | #### Notification system 144 | 145 | The notification system has been refactored to use Laravel's notification system. This provides a more standardized and maintainable approach to sending notifications. 146 | --------------------------------------------------------------------------------