├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── changelog.md ├── composer.json ├── docs ├── beginner_guide.md ├── customize_views.md ├── fields.md ├── routes_and_controllers.md ├── the_entry.md └── the_table.md ├── phpunit.xml ├── readme.md ├── resources ├── assets │ └── fields │ │ ├── datepicker │ │ ├── bootstrap-datepicker.fr.min.js │ │ ├── bootstrap-datepicker.min.js │ │ └── bootstrap-datepicker3.min.css │ │ └── tinymce │ │ ├── langs │ │ └── readme.md │ │ ├── license.txt │ │ ├── plugins │ │ ├── advlist │ │ │ └── plugin.min.js │ │ ├── anchor │ │ │ └── plugin.min.js │ │ ├── autolink │ │ │ └── plugin.min.js │ │ ├── autoresize │ │ │ └── plugin.min.js │ │ ├── autosave │ │ │ └── plugin.min.js │ │ ├── bbcode │ │ │ └── plugin.min.js │ │ ├── charmap │ │ │ └── plugin.min.js │ │ ├── code │ │ │ └── plugin.min.js │ │ ├── codesample │ │ │ ├── css │ │ │ │ └── prism.css │ │ │ └── plugin.min.js │ │ ├── colorpicker │ │ │ └── plugin.min.js │ │ ├── contextmenu │ │ │ └── plugin.min.js │ │ ├── directionality │ │ │ └── plugin.min.js │ │ ├── emoticons │ │ │ ├── img │ │ │ │ ├── smiley-cool.gif │ │ │ │ ├── smiley-cry.gif │ │ │ │ ├── smiley-embarassed.gif │ │ │ │ ├── smiley-foot-in-mouth.gif │ │ │ │ ├── smiley-frown.gif │ │ │ │ ├── smiley-innocent.gif │ │ │ │ ├── smiley-kiss.gif │ │ │ │ ├── smiley-laughing.gif │ │ │ │ ├── smiley-money-mouth.gif │ │ │ │ ├── smiley-sealed.gif │ │ │ │ ├── smiley-smile.gif │ │ │ │ ├── smiley-surprised.gif │ │ │ │ ├── smiley-tongue-out.gif │ │ │ │ ├── smiley-undecided.gif │ │ │ │ ├── smiley-wink.gif │ │ │ │ └── smiley-yell.gif │ │ │ └── plugin.min.js │ │ ├── example │ │ │ ├── dialog.html │ │ │ └── plugin.min.js │ │ ├── example_dependency │ │ │ └── plugin.min.js │ │ ├── fullpage │ │ │ └── plugin.min.js │ │ ├── fullscreen │ │ │ └── plugin.min.js │ │ ├── hr │ │ │ └── plugin.min.js │ │ ├── image │ │ │ └── plugin.min.js │ │ ├── imagetools │ │ │ └── plugin.min.js │ │ ├── importcss │ │ │ └── plugin.min.js │ │ ├── insertdatetime │ │ │ └── plugin.min.js │ │ ├── layer │ │ │ └── plugin.min.js │ │ ├── legacyoutput │ │ │ └── plugin.min.js │ │ ├── link │ │ │ └── plugin.min.js │ │ ├── lists │ │ │ └── plugin.min.js │ │ ├── media │ │ │ ├── moxieplayer.swf │ │ │ └── plugin.min.js │ │ ├── nonbreaking │ │ │ └── plugin.min.js │ │ ├── noneditable │ │ │ └── plugin.min.js │ │ ├── pagebreak │ │ │ └── plugin.min.js │ │ ├── paste │ │ │ └── plugin.min.js │ │ ├── preview │ │ │ └── plugin.min.js │ │ ├── print │ │ │ └── plugin.min.js │ │ ├── save │ │ │ └── plugin.min.js │ │ ├── searchreplace │ │ │ └── plugin.min.js │ │ ├── spellchecker │ │ │ └── plugin.min.js │ │ ├── tabfocus │ │ │ └── plugin.min.js │ │ ├── table │ │ │ └── plugin.min.js │ │ ├── template │ │ │ └── plugin.min.js │ │ ├── textcolor │ │ │ └── plugin.min.js │ │ ├── textpattern │ │ │ └── plugin.min.js │ │ ├── visualblocks │ │ │ ├── css │ │ │ │ └── visualblocks.css │ │ │ └── plugin.min.js │ │ ├── visualchars │ │ │ └── plugin.min.js │ │ └── wordcount │ │ │ └── plugin.min.js │ │ ├── skins │ │ └── lightgray │ │ │ ├── content.inline.min.css │ │ │ ├── content.min.css │ │ │ ├── fonts │ │ │ ├── tinymce-small.eot │ │ │ ├── tinymce-small.svg │ │ │ ├── tinymce-small.ttf │ │ │ ├── tinymce-small.woff │ │ │ ├── tinymce.eot │ │ │ ├── tinymce.svg │ │ │ ├── tinymce.ttf │ │ │ └── tinymce.woff │ │ │ ├── img │ │ │ ├── anchor.gif │ │ │ ├── loader.gif │ │ │ ├── object.gif │ │ │ └── trans.gif │ │ │ ├── skin.ie7.min.css │ │ │ └── skin.min.css │ │ ├── themes │ │ ├── inlite │ │ │ └── theme.min.js │ │ └── modern │ │ │ └── theme.min.js │ │ └── tinymce.min.js ├── lang │ ├── en │ │ ├── buttons.php │ │ ├── form.php │ │ └── table.php │ └── fr │ │ ├── buttons.php │ │ ├── form.php │ │ └── table.php ├── screenshot-create.png ├── screenshot-table.png └── views │ ├── fields │ ├── assets.blade.php │ ├── date-picker.blade.php │ ├── email.blade.php │ ├── fileupload.blade.php │ ├── partials │ │ ├── errors.blade.php │ │ └── help.blade.php │ ├── radio.blade.php │ ├── select-relation.blade.php │ ├── text.blade.php │ ├── textarea.blade.php │ └── tinymce.blade.php │ ├── form-create.blade.php │ ├── form-update.blade.php │ ├── row.blade.php │ ├── scaffold │ ├── bootstrap3-form.blade.php │ ├── bootstrap3-layout.blade.php │ └── bootstrap3-table.blade.php │ └── table.blade.php ├── src ├── Console │ ├── CrudControllerMakeCommand.php │ └── stubs │ │ └── controller.stub ├── Crud.php ├── CrudFacade.php ├── CrudServiceProvider.php ├── Exceptions │ ├── CrudException.php │ ├── DuplicateFieldIdentifierException.php │ ├── FailedValidationException.php │ ├── InvalidArgumentException.php │ ├── InvalidModelException.php │ ├── InvalidRelationException.php │ ├── InvalidRouteIdentifierException.php │ └── NoFieldsException.php ├── Fields │ ├── DatePickerField.php │ ├── EmailField.php │ ├── Field.php │ ├── FileUploadField.php │ ├── RadioField.php │ ├── SelectRelationField.php │ ├── TextField.php │ ├── TextareaField.php │ └── TinymceField.php ├── Services │ ├── CrudEntry.php │ ├── CrudFields.php │ ├── CrudManager.php │ ├── CrudTable.php │ └── CrudValidator.php ├── Traits │ ├── Crudable.php │ ├── FieldHandleUpload.php │ ├── FieldHasUiModifiers.php │ ├── FieldWithOptions.php │ └── FieldWithRelation.php └── helpers.php └── tests ├── AbstractTestCase.php ├── CrudFieldsTest.php ├── CrudTest.php ├── CrudValidatorTest.php ├── CrudableModelTest.php ├── Fields ├── DatePickerFieldTest.php ├── FieldHandleUploadTest.php ├── FieldTest.php ├── FieldWithOptionsTest.php └── FieldWithRelationTest.php └── Fixtures ├── Models ├── Category.php └── Post.php └── database └── migrations ├── 2016_10_30_000000_create_posts_table.php └── 2016_11_18_000000_create_categories_table.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.lock 3 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | checks: 2 | php: 3 | code_rating: true 4 | filter: 5 | paths: ["src/*.php"] 6 | excluded_paths: 7 | - "tests/" 8 | - "resources/" 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 7.1 4 | - 7.0 5 | - 5.6 6 | before_script: composer install 7 | script: vendor/bin/phpunit -c phpunit.xml 8 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.0.3 4 | 5 | ### Added 6 | 7 | - `DatePickerField` with its assets and its tests 8 | - `getValidator` method on `CrudValidator` 9 | 10 | ## 1.0.2 11 | 12 | ### Added 13 | 14 | - `SelectRelationField` 15 | - `FieldWithRelation` 16 | 17 | ## 1.0.1 18 | 19 | ### Added 20 | 21 | - Default option on `FieldWithOptions` 22 | - `beforeSave` method on `Field` -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marceauka/laravel-crudable", 3 | "description": "Laravel CRUD builder powered by custom fields", 4 | "keywords": ["laravel", "crud", "fields", "admin"], 5 | "license": "MIT", 6 | "type": "plugin", 7 | "require": { 8 | "php": ">=5.6.4", 9 | "laravel/framework": ">=5.3" 10 | }, 11 | "require-dev": { 12 | "phpunit/phpunit": "~5.0", 13 | "phpspec/phpspec": "~2.1", 14 | "laracasts/testdummy": "~2.0", 15 | "laravel/laravel": "~5.4", 16 | "mockery/mockery": "~0.9" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "Akibatech\\Crud\\": "src/" 21 | }, 22 | "files": [ 23 | "src/helpers.php" 24 | ] 25 | }, 26 | "autoload-dev": { 27 | "classmap": [ 28 | "tests/AbstractTestCase.php" 29 | ], 30 | "psr-4": { 31 | "TestModel\\": "tests/Fixtures/Models/" 32 | } 33 | }, 34 | "config": { 35 | "preferred-install": "dist" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /docs/beginner_guide.md: -------------------------------------------------------------------------------- 1 | # Beginner Guide - Step by Step 2 | 3 | This guide is for beginners, we'll see the installation process from Laravel 5.3 Installation to our first entry creation. 4 | It's also available as a [3 minutes video guide](https://youtu.be/Cb8ext3G8E0). 5 | 6 | ## Summary 7 | 8 | 1. [Install Laravel 5.3 and Laravel Crudable](#install-laravel-53-and-laravel-crudable) 9 | 2. [Our first model and its migration](#our-first-model-and-its-migration) 10 | 3. [Configure the fields](#configure-the-fields) 11 | 4. [Configure the CRUD](#configure-the-crud) 12 | 5. [Voilà!](#voilà) 13 | 14 | ## Install Laravel 5.3 and Laravel Crudable 15 | 16 | Open a terminal and install Laravel 5.3. 17 | 18 | ```bash 19 | composer create-project laravel/laravel crud-demo ^5.3 20 | // Once done 21 | cd crud-demo 22 | ``` 23 | 24 | Now, we configure database (.env), open `.env` file and replace `DB_CONNECTION=mysql` by `DB_CONNECTION=sqlite`. 25 | You can remove all others lines beginning with `DB_*`. 26 | 27 | Then type create the SQLite database. 28 | 29 | ```bash 30 | touch database/database.sqlite 31 | ``` 32 | 33 | Install laravel-crudable. 34 | ```bash 35 | composer require marceauka/laravel-crudable 36 | ``` 37 | 38 | And add it to your `config/app.php` in the `Application Service Providers` section. 39 | ```php 40 | // ... 41 | \Akibatech\Crud\CrudServiceProvider::class, 42 | ``` 43 | 44 | Finally, you need to publish all CRUD vendors by typing: 45 | ``` 46 | php artisan vendor:publish --tag=crud 47 | ``` 48 | 49 | Done! 50 | 51 | ## Our first model and its migration 52 | 53 | For the demo, we'll create a crudable model `Post`. Post is like a blog entry. 54 | It's defined by an ID, a title, an introduction, an HTML content, an heading illustration and a status (draft, live). 55 | 56 | ```bash 57 | php artisan make:model Post -m 58 | ``` 59 | 60 | Now edit the migration file called `database/migrations/*_create_posts_table.php` and update it like this. 61 | 62 | ```php 63 | public function up() 64 | { 65 | Schema::create('posts', function (Blueprint $table) { 66 | $table->increments('id'); 67 | $table->string('title'); 68 | $table->string('introduction'); 69 | $table->string('content'); 70 | $table->string('illustration')->nullable(); 71 | $table->tinyInteger('status')->unsigned()->default(0); 72 | $table->timestamps(); 73 | }); 74 | } 75 | ``` 76 | 77 | Finally, type: 78 | ``` 79 | php artisan migrate 80 | ``` 81 | 82 | Done! 83 | 84 | ## Configure the fields 85 | 86 | Open your `Post` model located at `app/Post.php` and update it like this. 87 | 88 | ```php 89 | namespace App; 90 | 91 | use Akibatech\Crud\Traits\Crudable; 92 | use Akibatech\Crud\Services\CrudFields; 93 | 94 | use Akibatech\Crud\Fields\TextField; 95 | use Akibatech\Crud\Fields\TextareaField; 96 | use Akibatech\Crud\Fields\TinymceField; 97 | use Akibatech\Crud\Fields\RadioField; 98 | use Akibatech\Crud\Fields\FileUploadField; 99 | 100 | use App\Http\Controllers\PostsController; 101 | use Illuminate\Database\Eloquent\Model; 102 | 103 | class Post extends Model 104 | { 105 | use Crudable; 106 | 107 | public function getCrudFields() 108 | { 109 | return CrudFields::make([ 110 | TextField::handle('title', 'required|min:3')->withPlaceholder('Title of the post'), 111 | TextareaField::handle('introduction', 'required|min:3')->withPlaceholder('Post introduction'), 112 | TinymceField::handle('content', 'required|min:3'), 113 | FileUploadField::handle('illustration')->withMaxSize(1024 * 1024)->withTypes('jpeg'), 114 | RadioField::handle('status', 'required')->withOptions([0 => 'Draft', 1 => 'Live']) 115 | ]); 116 | } 117 | } 118 | ``` 119 | 120 | What we done on our model? 121 | 122 | 1. Added the trait `Crudable`. 123 | 2. Added the method `getCrudFields` that returns the required CrudFields instance. 124 | 3. Configured 5 fields: 125 | - TextField for our title 126 | - TextareaField for our introduction 127 | - TinymceField for our content 128 | - FileUploadField for our illustration 129 | - RadioField for our status 130 | 131 | ## Configure the CRUD 132 | 133 | Before continue we need a Controller to handle our CRUD requests. 134 | 135 | This package comes with a new `make:crud:controller` artisan command. 136 | 137 | ```bash 138 | php artisan make:crud:controller PostsController App/Post 139 | ``` 140 | 141 | The first argument is the name of the controller and the second is our model name. 142 | 143 | Please note that the controller creation step is optionnal because you can use your pre-existing controller but it's a good start. 144 | 145 | Finally, open your route file located at `routes/web.php` and add: 146 | ```php 147 | // ... 148 | App\Post::crudRoutes(); 149 | ``` 150 | 151 | ## Voilà! 152 | 153 | You can now serve your application: 154 | ```bash 155 | php artisan serve 156 | ``` 157 | 158 | And go to `http://localhost:8000/posts/index`. 159 | -------------------------------------------------------------------------------- /docs/customize_views.md: -------------------------------------------------------------------------------- 1 | # Customize views 2 | 3 | All views are customizable and are stored in `resources/views/vendor/crud`. 4 | 5 | Views are published to your app with the command: 6 | ```bash 7 | php artisan vendor:publish --tag=crud 8 | ``` 9 | 10 | Here's the details of each views. 11 | 12 | ### Table view `table.blade.php` 13 | 14 | - `$create_url` URL to the create form. 15 | - `$title` Title of the table. 16 | - `$count` Entries count. It's based on total rows and not on entries in the current page. 17 | - `$is_empty` Empty or not? It's based on total rows and not on entries in the current page. 18 | - `$entries` Entries collection. Each entries calls the row view. 19 | - `$pagination` Pagination of the entries. Same as classic pagination. 20 | - `$columns` An array containing names of columns. 21 | 22 | ### Row view `row.blade.php` 23 | 24 | - `$entry` CrudEntry instance. 25 | - `$manager` CrudManager instance. 26 | - `$actions` The entry buttons. Not customizable yet. 27 | 28 | ### Form view `form-create.blade.php` and `form-update.blade.php` 29 | 30 | - `$back_url` URL to the index view. 31 | - `$entry` CrudEntry instance. 32 | - `$errors` Validation errors. 33 | - `$old` Old inputs values. 34 | - `$title` Form title. 35 | - `$form_url` Form action URL. 36 | - `$form_method` Form method. 37 | - and more... 38 | 39 | ### Field view `fields/*.blade.php` 40 | 41 | Each view is named by its field name. 42 | Ex: `fields/text.blade.php` for the `TextField`. 43 | 44 | - `$field` Field instance itself 45 | - `$error` Validation error for the field 46 | - `$old` Old value for the field 47 | - and more... 48 | -------------------------------------------------------------------------------- /docs/fields.md: -------------------------------------------------------------------------------- 1 | # Fields 2 | 3 | Fields are the way to bring power to your model attributes. 4 | Many are available and all are customizable to your needs. 5 | 6 | ## Usage 7 | 8 | Each field can be attached to your model via the `getCrudFields` method on your `Crudable` model. 9 | And each of them share the same base API and some have their own additionnal API. 10 | 11 | ```php 12 | public function getCrudFields() 13 | { 14 | return CrudFields::make([ 15 | // Your fields 16 | ]); 17 | } 18 | ``` 19 | 20 | ## Attach fields 21 | 22 | The minimum working code is: 23 | ```php 24 | TextField::handle('your-attribute') 25 | // or 26 | new TextField('your-attribute') 27 | ``` 28 | 29 | You can pass your rules as second parameter: 30 | ```php 31 | TextField::handle('your-attribute', 'required|min:3') 32 | ``` 33 | 34 | ## Fields reference 35 | 36 | ### TextField 37 | 38 | It's a basic ``. 39 | 40 | View: `fields/text.blade.php` 41 | Extends: `Field` 42 | Trait: - 43 | 44 | ### TextareaField 45 | 46 | It's a basic HTML textarea. 47 | 48 | View: `fields/textarea.blade.php` 49 | Extends: `Field` 50 | Trait: - 51 | 52 | ### RadioField 53 | 54 | Simple HTML radio. 55 | 56 | View: `fields/radio.blade.php` 57 | Extends: `Field` 58 | Trait: `FieldWithOptions` 59 | 60 | Additional API: 61 | ```php 62 | $field = RadioField::handle('status'); 63 | 64 | // Add radio options as an array. Takes care to add validation rules. 65 | // The second parameter is the default value but it can be null. 66 | $field->withOptions([0 => 'Dra`ft', 1 => 'Live'], 1); 67 | ``` 68 | 69 | ### EmailField 70 | 71 | Same as [TextField](#textfield) but takes care of the email contained. 72 | 73 | View: `fields/email.blade.php` 74 | Extends: `Field` 75 | Trait: - 76 | 77 | ### TinymceField 78 | 79 | Same as [TextareaField](#textareafield) but produces a WYSIWYG via [TinyMCE 4](https://www.tinymce.com/). 80 | To use it you should read [Fields with assets](#publishing-assets). 81 | 82 | View: `fields/tinymce.blade.php` 83 | Extends: `Field` 84 | Trait: - 85 | 86 | ### FileUploadField 87 | 88 | File upload field. You can specify extensions authorized, max filesize, upload path and storage disk. 89 | Once added this field will add `enctype="multipart/form-data"` to the entry form. 90 | By default; this field value is not displayed in the entries table. 91 | 92 | View: `fields/fileupload.blade.php` 93 | Extends: `Field` 94 | Trait: `FieldHandleUpload` 95 | 96 | Additional API: 97 | ```php 98 | $field = FileUploadField::handle('illustration'); 99 | 100 | // Only accept this mimes. 101 | // Default: empty 102 | $field->withTypes('jpeg,png'); 103 | 104 | // Set the maximum file size (in kB) 105 | // Default: empty 106 | $field->withMaxSize(1024 * 1024); 107 | 108 | // Set the upload destination folder on the disk. 109 | // Default: 'uploads' 110 | $field->uploadToPath('uploaded-illustrations'); 111 | 112 | // Set the disk that'll store uploads. Configure them in "config/filesystems.php". 113 | // Default: 'public'. 114 | $field->uploadToDisk('disk-name'); 115 | ``` 116 | 117 | ### SelectRelationField 118 | 119 | This field adds a select input based on an existing model relation. 120 | For example, a Post model belongs to a Category model: 121 | ```php 122 | SelectRelationField::handle('category_id')->withRelation('category') 123 | ``` 124 | 125 | View: `fields/select-relation.blade.php` 126 | Extends: `Field` 127 | Trait: `FieldWithRelation` 128 | 129 | Additional API: 130 | ```php 131 | $field = SelectRelationField::handle('category_id'); 132 | 133 | // The relation to use. The parameter is the name of the relation used in the model. 134 | // Default: empty 135 | $field->withRelation('category'); 136 | ``` 137 | 138 | ### DatePickerField 139 | 140 | This field provides a date picker (based on [Bootstrap Datepicker](https://github.com/uxsolutions/bootstrap-datepicker). 141 | 142 | View: `fields/date-picker.blade.php` 143 | Extends: `Field` 144 | Trait: - 145 | 146 | Additional API: 147 | ```php 148 | $field = DatePicker::handle('published_at'); 149 | 150 | // Customize the date format both used when saving and selecting a date in the picker. 151 | // Default: 'Y-m-d' 152 | $field->withDateFormat('Y-m-d'); 153 | 154 | // Set a minimum selectable date. You can pass a date as a string or as a Carbon instance. 155 | // The field will add needed rules to the validation. 156 | // Default: empty 157 | $field->withMinDate('2016-12-01'); 158 | $field->withMinDate(Carbon::now()); 159 | 160 | // Set a maximum selectable date. Same as min date. 161 | // Default: empty 162 | $field->withMaxDate('2016-12-01'); 163 | $field->withMaxDate(Carbon::now()->addDays(3)); 164 | ``` 165 | 166 | ## Customize fields 167 | 168 | Once declared, you can use the Field API to declare your modifiers. 169 | 170 | ```php 171 | $field = TextField::handle('title'); 172 | 173 | // Attach some additional validation rules 174 | // Default: empty 175 | $field->withRules('required|min:3'); 176 | 177 | // Change the displayed label for this tield 178 | // Default: Capitalized attribute name 179 | $field->withLabel('The title'); 180 | 181 | // Set an input pladeholder 182 | // Default: empty 183 | $field->withPlaceholder('Ex: Hello world'); 184 | 185 | // Set a custom help message 186 | // Default: empty 187 | $field->withHelp('This is the title of your post'); 188 | 189 | // Show this field on the entries table ? 190 | // Default: true 191 | $field->displayInColumns(true); 192 | ``` 193 | 194 | ## Publishing assets 195 | 196 | Some fields such as [TinymceField](#tinymcefield) comes with custom assets. 197 | 198 | Default form views will insert the two custom blade sections `crud_js` and `crud_css`. 199 | To work properly you must include `@yield('crud_js')` and `@yield('crud_css')` somewhere in your blade layout. 200 | 201 | Fields assets are published in `public/vendor/crud/` when you call the command `php artisan vendor:publish --tag=crud`. -------------------------------------------------------------------------------- /docs/routes_and_controllers.md: -------------------------------------------------------------------------------- 1 | # Routes and controllers 2 | 3 | Each `Crudable` model is managed by a `CrudManager` instance. 4 | 5 | ## Usage 6 | 7 | By default, the `CrudManager` is automatically and basicly configured. 8 | This configuration comes from the `getCrudManager` on the `Crudable` trait, but you can override it. 9 | 10 | In your model: 11 | ```php 12 | use Crudable; 13 | 14 | public function getCrudManager() 15 | { 16 | $manager = CrudManager::make($this); 17 | 18 | // Configuration of your manager. 19 | 20 | return $manager; 21 | } 22 | ``` 23 | 24 | ## Configuration 25 | 26 | Many methods are available to configure the way your `Crudable` model is managed. 27 | 28 | ### Controller 29 | 30 | ```php 31 | $manager->setController('ModelController'); 32 | ``` 33 | 34 | All actions will use the `ModelController` located in the namespace `App\Http\Controllers`. 35 | 36 | ### Routes 37 | 38 | By default all you model crud routes are: 39 | 40 | - [Route URI] => [Route name] 41 | - model/index => model.index 42 | - model/create => model.create 43 | - model/store => model.store 44 | - model/edit/{id} => model.edit 45 | - model/update/{id} => model.update 46 | - model/delete/{id}/{csrf} => model.delete 47 | 48 | Where the 'model' value is the model name, ex: `Post` => 'posts, `Category` => 'categories', ... 49 | 50 | You can change all crud routes URI prefix like this: 51 | ```php 52 | $manager->setUriPrefix('admin/posts'); 53 | ``` 54 | 55 | You also can change the custom prefix for the named routes: 56 | ```php 57 | $manager->setNamePrefix('admin.posts'); 58 | ``` 59 | 60 | ### Name 61 | 62 | By default the name of the crud is the name of the model, but you can configure it like this: 63 | ```php 64 | $manager->setName('Post'); 65 | ``` 66 | 67 | The name is used to display a title on the table and the form. 68 | 69 | ### Entries per page 70 | 71 | You can customize entries displayed per page like this: 72 | ```php 73 | $manager->setPerPage(50); 74 | ``` 75 | 76 | By default, 25 entries are displayed per page. 77 | 78 | ## Register routes 79 | 80 | All `Crudable` model comes with a static `registerRoutes` method. 81 | 82 | Once routes and controller are configured, you can bind you crud model routes in your routes file like this: 83 | ```php 84 | // routes/web.php 85 | Model::registerRoutes(); 86 | ``` 87 | 88 | ## API 89 | 90 | The `CrudManager` instance comes with many public method. 91 | 92 | ```php 93 | $manager = $model->getCrudManager(); 94 | 95 | // Returns the number of entries per page 96 | // => 25 97 | $manager->getPerPage(); 98 | 99 | // Returns the name of the CRUD 100 | // => 'Model' 101 | $manager->getName(); 102 | 103 | // Returns the controller 104 | // => 'ModelController' 105 | $manager->getController(); 106 | 107 | // Returns URL for a route 108 | // => 'model/store' 109 | $manager->getActionRoute('store'); 110 | 111 | // Returns needed method for a route 112 | // => 'POST' 113 | $manager->getActionMethod('store'); 114 | 115 | // Returns the URI prefix 116 | // => 'model/' 117 | $manager->getUriPrefix(); 118 | 119 | // Returns the routes name prefix 120 | // => 'model.' 121 | $manager->getNamePrefix(); 122 | ``` 123 | -------------------------------------------------------------------------------- /docs/the_entry.md: -------------------------------------------------------------------------------- 1 | # The entry 2 | 3 | The `CrudEntry` object is used to handle an instance of `Crudable` entry. 4 | 5 | ## Usage 6 | 7 | You can scaffold the CRUD entry in many ways: 8 | ```php 9 | // Via helper 10 | $crud = crud_entry(Model::class); // Or crud_table($model_instance) 11 | // Via Facade 12 | $crud = Crud::entry(Model::class); 13 | // Via Factory 14 | $crud = Akibatech\Crud\Crud::entry(Model::class) 15 | // Via model instance 16 | $crud = $model->crudEntry(); 17 | ``` 18 | 19 | Once the entry instance in hands, the entry form displayed like this: 20 | ```blade 21 | // In a view (powered by __toString) 22 | {!! $crud->form() !!} 23 | // Or like this from a model instance 24 | {!! $model->crudEntry()->form() !!} 25 | ``` 26 | 27 | The entry instance takes care if the model should be created or updated. 28 | 29 | ## API 30 | 31 | The `CrudEntry` object comes with useful methods described here: 32 | 33 | ```php 34 | $entry = App\Model::findOrFail(1)->crudEntry(); 35 | 36 | // Returns a validator ready (CrudValidator) to handle the entry. 37 | $validator = $entry->getValidator(); 38 | 39 | // Validate an array of data. Returns a CrudValidator instance. 40 | $validator = $entry->validate(['title' => 'Foo', '...']); 41 | 42 | // Save the entry to database (only if there's dirty attributes). 43 | $entry->save(); 44 | 45 | // Returns all visible fields in a Generator. 46 | $entry->fields(); 47 | 48 | // Returns all fields in a Generator. 49 | $entry->formFields(); 50 | 51 | // Returns the model ID (primary key). 52 | $entry->getId(); 53 | 54 | // Returns the entry row. It's the template used to display the entry in the table. 55 | $entry->row(); 56 | 57 | // Returns the CrudManager instance 58 | $manager = $entry->getManage(); 59 | 60 | // Returns the CrudFields instance 61 | $entry->getFields(); 62 | 63 | // Returns the original model. 64 | $entry->getModel(); 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/the_table.md: -------------------------------------------------------------------------------- 1 | # The table 2 | 3 | The `CrudTable` object is used to handle a collection of `Crudable` entries. 4 | 5 | ## Usage 6 | 7 | Once a Crudable model in hands, you can, scaffold the CRUD table in many ways: 8 | ```php 9 | // Via helper 10 | $crud = crud_table(Model::class); // Or crud_table($model_instance) 11 | // Via Facade 12 | $crud = Crud::table(Model::class); 13 | // Via Factory 14 | $crud = Akibatech\Crud\Crud::table(Model::class) 15 | // Via model instance 16 | $crud = $model->crudTable(); 17 | ``` 18 | 19 | Once the table instance in hands, it can be displayed like this: 20 | ```blade 21 | // In a view (powered by __toString) 22 | {!! $crud->table() !!} 23 | // Or like this from a model instance 24 | {!! $model->crudTable()->table() !!} 25 | ``` 26 | 27 | ## API 28 | 29 | The `CrudTable` object has for the moment no public API. 30 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Laravel Crudable 2 | 3 | [![Build Status](https://travis-ci.org/MarceauKa/laravel-crudable.svg?branch=master)](https://travis-ci.org/MarceauKa/laravel-crudable) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/MarceauKa/laravel-crudable/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/MarceauKa/laravel-crudable/?branch=master) 4 | 5 | Laravel Crudable is a library built to bring **Custom Fields** powered **CRUD functionnalities** to **your Eloquent models**. 6 | 7 | ## Summary 8 | 9 | A step by step tutorial for beginners is available here: [Beginner Guide](docs/beginner_guide.md) (also available as a 3 min [video](https://youtu.be/Cb8ext3G8E0)). 10 | 11 | - [Goals](#goals) 12 | - [Installation](#installation) 13 | - [Usage](#usage) 14 | - [Fields](#fields) 15 | - [Controller and routes](#controller-and-routes) 16 | - [Customizing](#customizing) 17 | - [Tests](#tests) 18 | - [Contribute](#contribute) 19 | 20 | ## Goals 21 | 22 | - Easy to integrate on a **new project** 23 | - Easy to integrate to an **existing project** 24 | - Non-intrusive API (just add a trait and one method to your model) 25 | - Focus on fields 26 | - Customizable 27 | - Laravel's way 28 | 29 | ### Non-goals 30 | 31 | - Roles or permissions 32 | - Admin panel 33 | 34 | ## Installation 35 | 36 | This version is compatible with Laravel **5.4** and **5.3**. For Laravel 5.2 compatibility see the branch **1.0**. 37 | 38 | Install via composer: 39 | ```bash 40 | composer require marceauka/laravel-crudable 41 | ``` 42 | 43 | Then register the service provider in your `config/app.php`. 44 | ```php 45 | // After other service providers 46 | Akibatech\Crud\CrudServiceProvider::class 47 | ``` 48 | 49 | Finally, publish resources: 50 | ```bash 51 | php artisan vendor:publish --tag=crud 52 | ``` 53 | 54 | This command will publish language files and views for easy customization. 55 | 56 | ## Usage 57 | 58 | Add the trait `Crudable` to your Eloquent Model, then implement the required method `getCrudFields` 59 | 60 | Example model: 61 | ```php 62 | class Post extends Model 63 | { 64 | use Crudable; 65 | 66 | /** 67 | * @return \Akibatech\Crud\Services\CrudFields 68 | */ 69 | public function getCrudFields() 70 | { 71 | return CrudFields::make([ 72 | // Bind the title attribute to a required Text Field. 73 | TextField::handle('title', 'required|min:3')->withPlaceholder('Title of the post'), 74 | 75 | // Bind the introduction attribute to a required Textarea Field. 76 | TextareaField::handle('introduction', 'required|min:3')->withPlaceholder('Short introduction'), 77 | 78 | // Bind the content attribute to a Tinymce Field 79 | TinymceField::handle('content', 'required'), 80 | 81 | // Bind the illustration attribute to a file upload allowing 10Mb JPG or PNG picture 82 | FileUploadField::handle('illustration')->withMaxSize(1024 * 1024)->withTypes('jpeg,png'), 83 | 84 | // Bind the status attribute to a Radio Field allowing Draft or Live options. 85 | RadioField::handle('status', 'required')->withOptions([0 => 'Draft', 1 => 'Live']) 86 | ]); 87 | } 88 | } 89 | ``` 90 | 91 | ### Display the table of entries 92 | 93 | In your controller: 94 | ```php 95 | public function index() 96 | { 97 | $model = App\Post::class; 98 | 99 | return view('your-view', compact($model)); 100 | } 101 | ``` 102 | 103 | In your view: 104 | ```blade 105 | @crudtable($model) 106 | ``` 107 | 108 | Learn more: [The Table](docs/the_table.md) 109 | 110 | ![Entry table](https://github.com/AkibaTech/laravel-crudable/blob/master/resources/screenshot-table.png) 111 | 112 | ### Display the entry create form 113 | 114 | In your controller: 115 | ```php 116 | public function create() 117 | { 118 | $model = App\Post::class; 119 | 120 | return view('your-view', compact($model)); 121 | } 122 | 123 | public function store(Request $request) 124 | { 125 | $entry = (new App\Post)->crudEntry(); 126 | $validation = $entry->validate($request->all()); 127 | 128 | if ($validation->passes()) 129 | { 130 | $validation->save(); 131 | } 132 | 133 | // Redirect to the form with the errors if validation fails, or to the index page 134 | return $validation->redirect(); 135 | } 136 | ``` 137 | 138 | In your view: 139 | ```blade 140 | @crudentry($model) 141 | ``` 142 | 143 | Learn more: [The Entry](docs/the_entry.md) 144 | 145 | ![Entry form](https://github.com/AkibaTech/laravel-crudable/blob/master/resources/screenshot-create.png) 146 | 147 | ## Fields 148 | 149 | Fields are the way to bind your **model attributes** to **powerful behaviors** and **reusable view components**. 150 | 151 | At this stage, you can use `TextField`, `TextareaField`, `RadioField`, `EmailField`, `TinymceField`, `FileUploadField`, `SelectRelationField` and `DatePickerField`, but many others are planned. 152 | 153 | Lean more: [Fields](docs/fields.md) 154 | 155 | ## Controller and routes 156 | 157 | By default each crudded model needs a Controller. 158 | 159 | You can scaffold it with the command `make:crud:controller `. 160 | Ex: `artisan make:crud:controller PostsController Post`. 161 | 162 | This command will generate a CRUD ready controller for your model with some scaffolded views but it's up to you to customize them. 163 | 164 | Once generated, your need to register routes like this: 165 | ```php 166 | // routes/web.php 167 | App\Post::crudRoutes(); 168 | ``` 169 | 170 | Learn more: [Routes and controlllers](docs/routes_and_controllers.md) 171 | 172 | ## Customizing 173 | 174 | All views are customizable and are stored in `resources/views/vendor/crud`. 175 | 176 | Complete documentation: [Customize Views](docs/customize_views.md) 177 | 178 | ## Tests 179 | 180 | You can launch tests with 181 | ```bash 182 | vendor/bin/phpunit 183 | ``` 184 | 185 | ## Contribute 186 | 187 | Feel free to contribute using issues and pull requests on this repo. 188 | 189 | ### Authors 190 | 191 | - [Marceau Casals](https://marceau.casals.fr) 192 | 193 | ### Licence 194 | 195 | [The MIT License (MIT)](https://opensource.org/licenses/MIT) 196 | Copyright (c) 2016 Marceau Casals 197 | 198 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 199 | 200 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 201 | 202 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 203 | -------------------------------------------------------------------------------- /resources/assets/fields/datepicker/bootstrap-datepicker.fr.min.js: -------------------------------------------------------------------------------- 1 | !function(a){a.fn.datepicker.dates.fr={days:["dimanche","lundi","mardi","mercredi","jeudi","vendredi","samedi"],daysShort:["dim.","lun.","mar.","mer.","jeu.","ven.","sam."],daysMin:["d","l","ma","me","j","v","s"],months:["janvier","février","mars","avril","mai","juin","juillet","août","septembre","octobre","novembre","décembre"],monthsShort:["janv.","févr.","mars","avril","mai","juin","juil.","août","sept.","oct.","nov.","déc."],today:"Aujourd'hui",monthsTitle:"Mois",clear:"Effacer",weekStart:1,format:"dd/mm/yyyy"}}(jQuery); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/langs/readme.md: -------------------------------------------------------------------------------- 1 | This is where language files should be placed. 2 | 3 | Please DO NOT translate these directly use this service: https://www.transifex.com/projects/p/tinymce/ 4 | -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/advlist/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("advlist",function(a){function b(a,b){var c=[];return tinymce.each(b.split(/[ ,]/),function(a){c.push({text:a.replace(/\-/g," ").replace(/\b\w/g,function(a){return a.toUpperCase()}),data:"default"==a?"":a})}),c}function c(b,c){a.undoManager.transact(function(){var d,e=a.dom,f=a.selection;if(d=e.getParent(f.getNode(),"ol,ul"),!d||d.nodeName!=b||c===!1){var h={"list-style-type":c?c:""};a.execCommand("UL"==b?"InsertUnorderedList":"InsertOrderedList",!1,h)}c=c===!1?g[b]:c,g[b]=c,d=e.getParent(f.getNode(),"ol,ul"),d&&(e.setStyle(d,"listStyleType",c?c:null),d.removeAttribute("data-mce-style")),a.focus()})}function d(b){var c=a.dom.getStyle(a.dom.getParent(a.selection.getNode(),"ol,ul"),"listStyleType")||"";b.control.items().each(function(a){a.active(a.settings.data===c)})}var e,f,g={};e=b("OL",a.getParam("advlist_number_styles","default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman")),f=b("UL",a.getParam("advlist_bullet_styles","default,circle,disc,square")),a.addButton("numlist",{type:"splitbutton",tooltip:"Numbered list",menu:e,onshow:d,onselect:function(a){c("OL",a.control.settings.data)},onclick:function(){c("OL",!1)}}),a.addButton("bullist",{type:"splitbutton",tooltip:"Bullet list",menu:f,onshow:d,onselect:function(a){c("UL",a.control.settings.data)},onclick:function(){c("UL",!1)}})}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/anchor/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("anchor",function(a){function b(){var b=a.selection.getNode(),c="",d="A"==b.tagName&&""===a.dom.getAttrib(b,"href");d&&(c=b.name||b.id||""),a.windowManager.open({title:"Anchor",body:{type:"textbox",name:"name",size:40,label:"Name",value:c},onsubmit:function(c){var e=c.data.name;d?b.id=e:(a.selection.collapse(!0),a.execCommand("mceInsertContent",!1,a.dom.createHTML("a",{id:e})))}})}a.addCommand("mceAnchor",b),a.addButton("anchor",{icon:"anchor",tooltip:"Anchor",onclick:b,stateSelector:"a:not([href])"}),a.addMenuItem("anchor",{icon:"anchor",text:"Anchor",context:"insert",onclick:b})}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/autolink/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("autolink",function(a){function b(a){e(a,-1,"(",!0)}function c(a){e(a,0,"",!0)}function d(a){e(a,-1,"",!1)}function e(a,b,c){function d(a,b){if(0>b&&(b=0),3==a.nodeType){var c=a.data.length;b>c&&(b=c)}return b}function e(a,b){1!=a.nodeType||a.hasChildNodes()?h.setStart(a,d(a,b)):h.setStartBefore(a)}function f(a,b){1!=a.nodeType||a.hasChildNodes()?h.setEnd(a,d(a,b)):h.setEndAfter(a)}var h,i,j,k,l,m,n,o,p,q;if("A"!=a.selection.getNode().tagName){if(h=a.selection.getRng(!0).cloneRange(),h.startOffset<5){if(o=h.endContainer.previousSibling,!o){if(!h.endContainer.firstChild||!h.endContainer.firstChild.nextSibling)return;o=h.endContainer.firstChild.nextSibling}if(p=o.length,e(o,p),f(o,p),h.endOffset<5)return;i=h.endOffset,k=o}else{if(k=h.endContainer,3!=k.nodeType&&k.firstChild){for(;3!=k.nodeType&&k.firstChild;)k=k.firstChild;3==k.nodeType&&(e(k,0),f(k,k.nodeValue.length))}i=1==h.endOffset?2:h.endOffset-1-b}j=i;do e(k,i>=2?i-2:0),f(k,i>=1?i-1:0),i-=1,q=h.toString();while(" "!=q&&""!==q&&160!=q.charCodeAt(0)&&i-2>=0&&q!=c);h.toString()==c||160==h.toString().charCodeAt(0)?(e(k,i),f(k,j),i+=1):0===h.startOffset?(e(k,0),f(k,j)):(e(k,i),f(k,j)),m=h.toString(),"."==m.charAt(m.length-1)&&f(k,j-1),m=h.toString(),n=m.match(g),n&&("www."==n[1]?n[1]="http://www.":/@$/.test(n[1])&&!/^mailto:/.test(n[1])&&(n[1]="mailto:"+n[1]),l=a.selection.getBookmark(),a.selection.setRng(h),a.execCommand("createlink",!1,n[1]+n[2]),a.selection.moveToBookmark(l),a.nodeChanged())}}var f,g=/^(https?:\/\/|ssh:\/\/|ftp:\/\/|file:\/|www\.|(?:mailto:)?[A-Z0-9._%+\-]+@)(.+)$/i;return a.settings.autolink_pattern&&(g=a.settings.autolink_pattern),a.on("keydown",function(b){return 13==b.keyCode?d(a):void 0}),tinymce.Env.ie?void a.on("focus",function(){if(!f){f=!0;try{a.execCommand("AutoUrlDetect",!1,!0)}catch(b){}}}):(a.on("keypress",function(c){return 41==c.keyCode?b(a):void 0}),void a.on("keyup",function(b){return 32==b.keyCode?c(a):void 0}))}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/autoresize/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("autoresize",function(a){function b(){return a.plugins.fullscreen&&a.plugins.fullscreen.isFullscreen()}function c(d){var g,h,i,j,k,l,m,n,o,p,q,r,s=tinymce.DOM;if(h=a.getDoc()){if(i=h.body,j=h.documentElement,k=e.autoresize_min_height,!i||d&&"setcontent"===d.type&&d.initial||b())return void(i&&j&&(i.style.overflowY="auto",j.style.overflowY="auto"));m=a.dom.getStyle(i,"margin-top",!0),n=a.dom.getStyle(i,"margin-bottom",!0),o=a.dom.getStyle(i,"padding-top",!0),p=a.dom.getStyle(i,"padding-bottom",!0),q=a.dom.getStyle(i,"border-top-width",!0),r=a.dom.getStyle(i,"border-bottom-width",!0),l=i.offsetHeight+parseInt(m,10)+parseInt(n,10)+parseInt(o,10)+parseInt(p,10)+parseInt(q,10)+parseInt(r,10),(isNaN(l)||0>=l)&&(l=tinymce.Env.ie?i.scrollHeight:tinymce.Env.webkit&&0===i.clientHeight?0:i.offsetHeight),l>e.autoresize_min_height&&(k=l),e.autoresize_max_height&&l>e.autoresize_max_height?(k=e.autoresize_max_height,i.style.overflowY="auto",j.style.overflowY="auto"):(i.style.overflowY="hidden",j.style.overflowY="hidden",i.scrollTop=0),k!==f&&(g=k-f,s.setStyle(a.iframeElement,"height",k+"px"),f=k,tinymce.isWebKit&&0>g&&c(d))}}function d(b,e,f){tinymce.util.Delay.setEditorTimeout(a,function(){c({}),b--?d(b,e,f):f&&f()},e)}var e=a.settings,f=0;a.settings.inline||(e.autoresize_min_height=parseInt(a.getParam("autoresize_min_height",a.getElement().offsetHeight),10),e.autoresize_max_height=parseInt(a.getParam("autoresize_max_height",0),10),a.on("init",function(){var b,c;b=a.getParam("autoresize_overflow_padding",1),c=a.getParam("autoresize_bottom_margin",50),b!==!1&&a.dom.setStyles(a.getBody(),{paddingLeft:b,paddingRight:b}),c!==!1&&a.dom.setStyles(a.getBody(),{paddingBottom:c})}),a.on("nodechange setcontent keyup FullscreenStateChanged",c),a.getParam("autoresize_on_init",!0)&&a.on("init",function(){d(20,100,function(){d(5,1e3)})}),a.addCommand("mceAutoResize",c))}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/autosave/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce._beforeUnloadHandler=function(){var a;return tinymce.each(tinymce.editors,function(b){b.plugins.autosave&&b.plugins.autosave.storeDraft(),!a&&b.isDirty()&&b.getParam("autosave_ask_before_unload",!0)&&(a=b.translate("You have unsaved changes are you sure you want to navigate away?"))}),a},tinymce.PluginManager.add("autosave",function(a){function b(a,b){var c={s:1e3,m:6e4};return a=/^(\d+)([ms]?)$/.exec(""+(a||b)),(a[2]?c[a[2]]:1)*parseInt(a,10)}function c(){var a=parseInt(n.getItem(k+"time"),10)||0;return(new Date).getTime()-a>m.autosave_retention?(d(!1),!1):!0}function d(b){n.removeItem(k+"draft"),n.removeItem(k+"time"),b!==!1&&a.fire("RemoveDraft")}function e(){!j()&&a.isDirty()&&(n.setItem(k+"draft",a.getContent({format:"raw",no_events:!0})),n.setItem(k+"time",(new Date).getTime()),a.fire("StoreDraft"))}function f(){c()&&(a.setContent(n.getItem(k+"draft"),{format:"raw"}),a.fire("RestoreDraft"))}function g(){l||(setInterval(function(){a.removed||e()},m.autosave_interval),l=!0)}function h(){var b=this;b.disabled(!c()),a.on("StoreDraft RestoreDraft RemoveDraft",function(){b.disabled(!c())}),g()}function i(){a.undoManager.beforeChange(),f(),d(),a.undoManager.add()}function j(b){var c=a.settings.forced_root_block;return b=tinymce.trim("undefined"==typeof b?a.getBody().innerHTML:b),""===b||new RegExp("^<"+c+"[^>]*>((\xa0| |[ ]|]*>)+?|)|
$","i").test(b)}var k,l,m=a.settings,n=tinymce.util.LocalStorage;k=m.autosave_prefix||"tinymce-autosave-{path}{query}-{id}-",k=k.replace(/\{path\}/g,document.location.pathname),k=k.replace(/\{query\}/g,document.location.search),k=k.replace(/\{id\}/g,a.id),m.autosave_interval=b(m.autosave_interval,"30s"),m.autosave_retention=b(m.autosave_retention,"20m"),a.addButton("restoredraft",{title:"Restore last draft",onclick:i,onPostRender:h}),a.addMenuItem("restoredraft",{text:"Restore last draft",onclick:i,onPostRender:h,context:"file"}),a.settings.autosave_restore_when_empty!==!1&&(a.on("init",function(){c()&&j()&&f()}),a.on("saveContent",function(){d()})),window.onbeforeunload=tinymce._beforeUnloadHandler,this.hasDraft=c,this.storeDraft=e,this.restoreDraft=f,this.removeDraft=d,this.isEmpty=j}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/bbcode/plugin.min.js: -------------------------------------------------------------------------------- 1 | !function(){tinymce.create("tinymce.plugins.BBCodePlugin",{init:function(a){var b=this,c=a.getParam("bbcode_dialect","punbb").toLowerCase();a.on("beforeSetContent",function(a){a.content=b["_"+c+"_bbcode2html"](a.content)}),a.on("postProcess",function(a){a.set&&(a.content=b["_"+c+"_bbcode2html"](a.content)),a.get&&(a.content=b["_"+c+"_html2bbcode"](a.content))})},getInfo:function(){return{longname:"BBCode Plugin",author:"Ephox Corp",authorurl:"http://www.tinymce.com",infourl:"http://www.tinymce.com/wiki.php/Plugin:bbcode"}},_punbb_html2bbcode:function(a){function b(b,c){a=a.replace(b,c)}return a=tinymce.trim(a),b(/(.*?)<\/a>/gi,"[url=$1]$2[/url]"),b(/(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]"),b(/(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]"),b(/(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]"),b(/(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]"),b(/(.*?)<\/span>/gi,"[color=$1]$2[/color]"),b(/(.*?)<\/font>/gi,"[color=$1]$2[/color]"),b(/(.*?)<\/span>/gi,"[size=$1]$2[/size]"),b(/(.*?)<\/font>/gi,"$1"),b(//gi,"[img]$1[/img]"),b(/(.*?)<\/span>/gi,"[code]$1[/code]"),b(/(.*?)<\/span>/gi,"[quote]$1[/quote]"),b(/(.*?)<\/strong>/gi,"[code][b]$1[/b][/code]"),b(/(.*?)<\/strong>/gi,"[quote][b]$1[/b][/quote]"),b(/(.*?)<\/em>/gi,"[code][i]$1[/i][/code]"),b(/(.*?)<\/em>/gi,"[quote][i]$1[/i][/quote]"),b(/(.*?)<\/u>/gi,"[code][u]$1[/u][/code]"),b(/(.*?)<\/u>/gi,"[quote][u]$1[/u][/quote]"),b(/<\/(strong|b)>/gi,"[/b]"),b(/<(strong|b)>/gi,"[b]"),b(/<\/(em|i)>/gi,"[/i]"),b(/<(em|i)>/gi,"[i]"),b(/<\/u>/gi,"[/u]"),b(/(.*?)<\/span>/gi,"[u]$1[/u]"),b(//gi,"[u]"),b(/]*>/gi,"[quote]"),b(/<\/blockquote>/gi,"[/quote]"),b(/
/gi,"\n"),b(//gi,"\n"),b(/
/gi,"\n"),b(/

/gi,""),b(/<\/p>/gi,"\n"),b(/ |\u00a0/gi," "),b(/"/gi,'"'),b(/</gi,"<"),b(/>/gi,">"),b(/&/gi,"&"),a},_punbb_bbcode2html:function(a){function b(b,c){a=a.replace(b,c)}return a=tinymce.trim(a),b(/\n/gi,"
"),b(/\[b\]/gi,""),b(/\[\/b\]/gi,""),b(/\[i\]/gi,""),b(/\[\/i\]/gi,""),b(/\[u\]/gi,""),b(/\[\/u\]/gi,""),b(/\[url=([^\]]+)\](.*?)\[\/url\]/gi,'$2'),b(/\[url\](.*?)\[\/url\]/gi,'$1'),b(/\[img\](.*?)\[\/img\]/gi,''),b(/\[color=(.*?)\](.*?)\[\/color\]/gi,'$2'),b(/\[code\](.*?)\[\/code\]/gi,'$1 '),b(/\[quote.*?\](.*?)\[\/quote\]/gi,'$1 '),a}}),tinymce.PluginManager.add("bbcode",tinymce.plugins.BBCodePlugin)}(); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/code/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("code",function(a){function b(){var b=a.windowManager.open({title:"Source code",body:{type:"textbox",name:"code",multiline:!0,minWidth:a.getParam("code_dialog_width",600),minHeight:a.getParam("code_dialog_height",Math.min(tinymce.DOM.getViewPort().h-200,500)),spellcheck:!1,style:"direction: ltr; text-align: left"},onSubmit:function(b){a.focus(),a.undoManager.transact(function(){a.setContent(b.data.code)}),a.selection.setCursorLocation(),a.nodeChanged()}});b.find("#code").value(a.getContent({source_view:!0}))}a.addCommand("mceCodeEditor",b),a.addButton("code",{icon:"code",tooltip:"Source code",onclick:b}),a.addMenuItem("code",{icon:"code",text:"Source code",context:"tools",onclick:b})}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/codesample/css/prism.css: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript */ 2 | /** 3 | * prism.js default theme for JavaScript, CSS and HTML 4 | * Based on dabblet (http://dabblet.com) 5 | * @author Lea Verou 6 | */ 7 | 8 | code[class*="language-"], 9 | pre[class*="language-"] { 10 | color: black; 11 | text-shadow: 0 1px white; 12 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 13 | direction: ltr; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | word-wrap: normal; 19 | line-height: 1.5; 20 | 21 | -moz-tab-size: 4; 22 | -o-tab-size: 4; 23 | tab-size: 4; 24 | 25 | -webkit-hyphens: none; 26 | -moz-hyphens: none; 27 | -ms-hyphens: none; 28 | hyphens: none; 29 | } 30 | 31 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 32 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 33 | text-shadow: none; 34 | background: #b3d4fc; 35 | } 36 | 37 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 38 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 39 | text-shadow: none; 40 | background: #b3d4fc; 41 | } 42 | 43 | @media print { 44 | code[class*="language-"], 45 | pre[class*="language-"] { 46 | text-shadow: none; 47 | } 48 | } 49 | 50 | /* Code blocks */ 51 | pre[class*="language-"] { 52 | padding: 1em; 53 | margin: .5em 0; 54 | overflow: auto; 55 | } 56 | 57 | :not(pre) > code[class*="language-"], 58 | pre[class*="language-"] { 59 | background: #f5f2f0; 60 | } 61 | 62 | /* Inline code */ 63 | :not(pre) > code[class*="language-"] { 64 | padding: .1em; 65 | border-radius: .3em; 66 | } 67 | 68 | .token.comment, 69 | .token.prolog, 70 | .token.doctype, 71 | .token.cdata { 72 | color: slategray; 73 | } 74 | 75 | .token.punctuation { 76 | color: #999; 77 | } 78 | 79 | .namespace { 80 | opacity: .7; 81 | } 82 | 83 | .token.property, 84 | .token.tag, 85 | .token.boolean, 86 | .token.number, 87 | .token.constant, 88 | .token.symbol, 89 | .token.deleted { 90 | color: #905; 91 | } 92 | 93 | .token.selector, 94 | .token.attr-name, 95 | .token.string, 96 | .token.char, 97 | .token.builtin, 98 | .token.inserted { 99 | color: #690; 100 | } 101 | 102 | .token.operator, 103 | .token.entity, 104 | .token.url, 105 | .language-css .token.string, 106 | .style .token.string { 107 | color: #a67f59; 108 | background: hsla(0, 0%, 100%, .5); 109 | } 110 | 111 | .token.atrule, 112 | .token.attr-value, 113 | .token.keyword { 114 | color: #07a; 115 | } 116 | 117 | .token.function { 118 | color: #DD4A68; 119 | } 120 | 121 | .token.regex, 122 | .token.important, 123 | .token.variable { 124 | color: #e90; 125 | } 126 | 127 | .token.important, 128 | .token.bold { 129 | font-weight: bold; 130 | } 131 | .token.italic { 132 | font-style: italic; 133 | } 134 | 135 | .token.entity { 136 | cursor: help; 137 | } 138 | 139 | -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/colorpicker/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("colorpicker",function(a){function b(b,c){function d(a){var b=new tinymce.util.Color(a),c=b.toRgb();f.fromJSON({r:c.r,g:c.g,b:c.b,hex:b.toHex().substr(1)}),e(b.toHex())}function e(a){f.find("#preview")[0].getEl().style.background=a}var f=a.windowManager.open({title:"Color",items:{type:"container",layout:"flex",direction:"row",align:"stretch",padding:5,spacing:10,items:[{type:"colorpicker",value:c,onchange:function(){var a=this.rgb();f&&(f.find("#r").value(a.r),f.find("#g").value(a.g),f.find("#b").value(a.b),f.find("#hex").value(this.value().substr(1)),e(this.value()))}},{type:"form",padding:0,labelGap:5,defaults:{type:"textbox",size:7,value:"0",flex:1,spellcheck:!1,onchange:function(){var a,b,c=f.find("colorpicker")[0];return a=this.name(),b=this.value(),"hex"==a?(b="#"+b,d(b),void c.value(b)):(b={r:f.find("#r").value(),g:f.find("#g").value(),b:f.find("#b").value()},c.value(b),void d(b))}},items:[{name:"r",label:"R",autofocus:1},{name:"g",label:"G"},{name:"b",label:"B"},{name:"hex",label:"#",value:"000000"},{name:"preview",type:"container",border:1}]}]},onSubmit:function(){b("#"+this.toJSON().hex)}});d(c)}a.settings.color_picker_callback||(a.settings.color_picker_callback=b)}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/contextmenu/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("contextmenu",function(a){var b,c=a.settings.contextmenu_never_use_native,d=function(a){return a.ctrlKey&&!c},e=function(){return tinymce.Env.mac&&tinymce.Env.webkit};a.on("mousedown",function(b){e()&&2===b.button&&!d(b)&&a.selection.isCollapsed()&&a.once("contextmenu",function(b){a.selection.placeCaretAt(b.clientX,b.clientY)})}),a.on("contextmenu",function(c){var e;if(!d(c)){if(c.preventDefault(),e=a.settings.contextmenu||"link image inserttable | cell row column deletetable",b)b.show();else{var f=[];tinymce.each(e.split(/[ ,]/),function(b){var c=a.menuItems[b];"|"==b&&(c={text:b}),c&&(c.shortcut="",f.push(c))});for(var g=0;g'}),a+=""}),a+=""}var d=[["cool","cry","embarassed","foot-in-mouth"],["frown","innocent","kiss","laughing"],["money-mouth","sealed","smile","surprised"],["tongue-out","undecided","wink","yell"]];a.addButton("emoticons",{type:"panelbutton",panel:{role:"application",autohide:!0,html:c,onclick:function(b){var c=a.dom.getParent(b.target,"a");c&&(a.insertContent(''+c.getAttribute('),this.hide())}},tooltip:"Emoticons"})}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/example/dialog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Custom dialog

5 | Input some text: 6 | 7 | 8 | -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/example/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("example",function(a,b){a.addButton("example",{text:"My button",icon:!1,onclick:function(){a.windowManager.open({title:"Example plugin",body:[{type:"textbox",name:"title",label:"Title"}],onsubmit:function(b){a.insertContent("Title: "+b.data.title)}})}}),a.addMenuItem("example",{text:"Example plugin",context:"tools",onclick:function(){a.windowManager.open({title:"TinyMCE site",url:b+"/dialog.html",width:600,height:400,buttons:[{text:"Insert",onclick:function(){var b=a.windowManager.getWindows()[0];a.insertContent(b.getContentWindow().document.getElementById("content").value),b.close()}},{text:"Close",onclick:"close"}]})}})}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/example_dependency/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("example_dependency",function(){},["example"]); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/fullpage/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("fullpage",function(a){function b(){var b=c();a.windowManager.open({title:"Document properties",data:b,defaults:{type:"textbox",size:40},body:[{name:"title",label:"Title"},{name:"keywords",label:"Keywords"},{name:"description",label:"Description"},{name:"robots",label:"Robots"},{name:"author",label:"Author"},{name:"docencoding",label:"Encoding"}],onSubmit:function(a){d(tinymce.extend(b,a.data))}})}function c(){function b(a,b){var c=a.attr(b);return c||""}var c,d,f=e(),g={};return g.fontface=a.getParam("fullpage_default_fontface",""),g.fontsize=a.getParam("fullpage_default_fontsize",""),c=f.firstChild,7==c.type&&(g.xml_pi=!0,d=/encoding="([^"]+)"/.exec(c.value),d&&(g.docencoding=d[1])),c=f.getAll("#doctype")[0],c&&(g.doctype=""),c=f.getAll("title")[0],c&&c.firstChild&&(g.title=c.firstChild.value),k(f.getAll("meta"),function(a){var b,c=a.attr("name"),d=a.attr("http-equiv");c?g[c.toLowerCase()]=a.attr("content"):"Content-Type"==d&&(b=/charset\s*=\s*(.*)\s*/gi.exec(a.attr("content")),b&&(g.docencoding=b[1]))}),c=f.getAll("html")[0],c&&(g.langcode=b(c,"lang")||b(c,"xml:lang")),g.stylesheets=[],tinymce.each(f.getAll("link"),function(a){"stylesheet"==a.attr("rel")&&g.stylesheets.push(a.attr("href"))}),c=f.getAll("body")[0],c&&(g.langdir=b(c,"dir"),g.style=b(c,"style"),g.visited_color=b(c,"vlink"),g.link_color=b(c,"link"),g.active_color=b(c,"alink")),g}function d(b){function c(a,b,c){a.attr(b,c?c:void 0)}function d(a){g.firstChild?g.insert(a,g.firstChild):g.append(a)}var f,g,h,j,m,n=a.dom;f=e(),g=f.getAll("head")[0],g||(j=f.getAll("html")[0],g=new l("head",1),j.firstChild?j.insert(g,j.firstChild,!0):j.append(g)),j=f.firstChild,b.xml_pi?(m='version="1.0"',b.docencoding&&(m+=' encoding="'+b.docencoding+'"'),7!=j.type&&(j=new l("xml",7),f.insert(j,f.firstChild,!0)),j.value=m):j&&7==j.type&&j.remove(),j=f.getAll("#doctype")[0],b.doctype?(j||(j=new l("#doctype",10),b.xml_pi?f.insert(j,f.firstChild):d(j)),j.value=b.doctype.substring(9,b.doctype.length-1)):j&&j.remove(),j=null,k(f.getAll("meta"),function(a){"Content-Type"==a.attr("http-equiv")&&(j=a)}),b.docencoding?(j||(j=new l("meta",1),j.attr("http-equiv","Content-Type"),j.shortEnded=!0,d(j)),j.attr("content","text/html; charset="+b.docencoding)):j&&j.remove(),j=f.getAll("title")[0],b.title?(j?j.empty():(j=new l("title",1),d(j)),j.append(new l("#text",3)).value=b.title):j&&j.remove(),k("keywords,description,author,copyright,robots".split(","),function(a){var c,e,g=f.getAll("meta"),h=b[a];for(c=0;c"))}function e(){return new tinymce.html.DomParser({validate:!1,root_name:"#document"}).parse(i)}function f(b){function c(a){return a.replace(/<\/?[A-Z]+/g,function(a){return a.toLowerCase()})}var d,f,h,l,m=b.content,n="",o=a.dom;if(!b.selection&&!("raw"==b.format&&i||b.source_view&&a.getParam("fullpage_hide_in_source_view"))){0!==m.length||b.source_view||(m=tinymce.trim(i)+"\n"+tinymce.trim(m)+"\n"+tinymce.trim(j)),m=m.replace(/<(\/?)BODY/gi,"<$1body"),d=m.indexOf("",d),i=c(m.substring(0,d+1)),f=m.indexOf("\n"),h=e(),k(h.getAll("style"),function(a){a.firstChild&&(n+=a.firstChild.value)}),l=h.getAll("body")[0],l&&o.setAttribs(a.getBody(),{style:l.attr("style")||"",dir:l.attr("dir")||"",vLink:l.attr("vlink")||"",link:l.attr("link")||"",aLink:l.attr("alink")||""}),o.remove("fullpage_styles");var p=a.getDoc().getElementsByTagName("head")[0];n&&(o.add(p,"style",{id:"fullpage_styles"},n),l=o.get("fullpage_styles"),l.styleSheet&&(l.styleSheet.cssText=n));var q={};tinymce.each(p.getElementsByTagName("link"),function(a){"stylesheet"==a.rel&&a.getAttribute("data-mce-fullpage")&&(q[a.href]=a)}),tinymce.each(h.getAll("link"),function(a){var b=a.attr("href");q[b]||"stylesheet"!=a.attr("rel")||o.add(p,"link",{rel:"stylesheet",text:"text/css",href:b,"data-mce-fullpage":"1"}),delete q[b]}),tinymce.each(q,function(a){a.parentNode.removeChild(a)})}}function g(){var b,c="",d="";return a.getParam("fullpage_default_xml_pi")&&(c+='\n'),c+=a.getParam("fullpage_default_doctype",""),c+="\n\n\n",(b=a.getParam("fullpage_default_title"))&&(c+=""+b+"\n"),(b=a.getParam("fullpage_default_encoding"))&&(c+='\n'),(b=a.getParam("fullpage_default_font_family"))&&(d+="font-family: "+b+";"),(b=a.getParam("fullpage_default_font_size"))&&(d+="font-size: "+b+";"),(b=a.getParam("fullpage_default_text_color"))&&(d+="color: "+b+";"),c+="\n\n"}function h(b){b.selection||b.source_view&&a.getParam("fullpage_hide_in_source_view")||(b.content=tinymce.trim(i)+"\n"+tinymce.trim(b.content)+"\n"+tinymce.trim(j))}var i,j,k=tinymce.each,l=tinymce.html.Node;a.addCommand("mceFullPageProperties",b),a.addButton("fullpage",{title:"Document properties",cmd:"mceFullPageProperties"}),a.addMenuItem("fullpage",{text:"Document properties",cmd:"mceFullPageProperties",context:"file"}),a.on("BeforeSetContent",f),a.on("GetContent",h)}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/fullscreen/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("fullscreen",function(a){function b(){var a,b,c=window,d=document,e=d.body;return e.offsetWidth&&(a=e.offsetWidth,b=e.offsetHeight),c.innerWidth&&c.innerHeight&&(a=c.innerWidth,b=c.innerHeight),{w:a,h:b}}function c(){var a=tinymce.DOM.getViewPort();return{x:a.x,y:a.y}}function d(a){scrollTo(a.x,a.y)}function e(){function e(){m.setStyle(p,"height",b().h-(o.clientHeight-p.clientHeight))}var n,o,p,q,r=document.body,s=document.documentElement;l=!l,o=a.getContainer(),n=o.style,p=a.getContentAreaContainer().firstChild,q=p.style,l?(k=c(),f=q.width,g=q.height,q.width=q.height="100%",i=n.width,j=n.height,n.width=n.height="",m.addClass(r,"mce-fullscreen"),m.addClass(s,"mce-fullscreen"),m.addClass(o,"mce-fullscreen"),m.bind(window,"resize",e),e(),h=e):(q.width=f,q.height=g,i&&(n.width=i),j&&(n.height=j),m.removeClass(r,"mce-fullscreen"),m.removeClass(s,"mce-fullscreen"),m.removeClass(o,"mce-fullscreen"),m.unbind(window,"resize",h),d(k)),a.fire("FullscreenStateChanged",{state:l})}var f,g,h,i,j,k,l=!1,m=tinymce.DOM;return a.settings.inline?void 0:(a.on("init",function(){a.addShortcut("Ctrl+Shift+F","",e)}),a.on("remove",function(){h&&m.unbind(window,"resize",h)}),a.addCommand("mceFullScreen",e),a.addMenuItem("fullscreen",{text:"Fullscreen",shortcut:"Ctrl+Shift+F",selectable:!0,onClick:function(){e(),a.focus()},onPostRender:function(){var b=this;a.on("FullscreenStateChanged",function(a){b.active(a.state)})},context:"view"}),a.addButton("fullscreen",{tooltip:"Fullscreen",shortcut:"Ctrl+Shift+F",onClick:e,onPostRender:function(){var b=this;a.on("FullscreenStateChanged",function(a){b.active(a.state)})}}),{isFullscreen:function(){return l}})}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/hr/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("hr",function(a){a.addCommand("InsertHorizontalRule",function(){a.execCommand("mceInsertContent",!1,"
")}),a.addButton("hr",{icon:"hr",tooltip:"Horizontal line",cmd:"InsertHorizontalRule"}),a.addMenuItem("hr",{icon:"hr",text:"Horizontal line",cmd:"InsertHorizontalRule",context:"insert"})}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/importcss/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("importcss",function(a){function b(a){var b=tinymce.Env.cacheSuffix;return"string"==typeof a&&(a=a.replace("?"+b,"").replace("&"+b,"")),a}function c(b){var c=a.settings,d=c.skin!==!1?c.skin||"lightgray":!1;if(d){var e=c.skin_url;return e=e?a.documentBaseURI.toAbsolute(e):tinymce.baseURL+"/skins/"+d,b===e+"/content"+(a.inline?".inline":"")+".min.css"}return!1}function d(a){return"string"==typeof a?function(b){return-1!==b.indexOf(a)}:a instanceof RegExp?function(b){return a.test(b)}:a}function e(d,e){function f(a,d){var h,i=a.href;if(i=b(i),i&&e(i,d)&&!c(i)){n(a.imports,function(a){f(a,!0)});try{h=a.cssRules||a.rules}catch(j){}n(h,function(a){a.styleSheet?f(a.styleSheet,!0):a.selectorText&&n(a.selectorText.split(","),function(a){g.push(tinymce.trim(a))})})}}var g=[],h={};n(a.contentCSS,function(a){h[a]=!0}),e||(e=function(a,b){return b||h[a]});try{n(d.styleSheets,function(a){f(a)})}catch(i){}return g}function f(b){var c,d=/^(?:([a-z0-9\-_]+))?(\.[a-z0-9_\-\.]+)$/i.exec(b);if(d){var e=d[1],f=d[2].substr(1).split(".").join(" "),g=tinymce.makeMap("a,img");return d[1]?(c={title:b},a.schema.getTextBlockElements()[e]?c.block=e:a.schema.getBlockElements()[e]||g[e.toLowerCase()]?c.selector=e:c.inline=e):d[2]&&(c={inline:"span",title:b.substr(1),classes:f}),a.settings.importcss_merge_classes!==!1?c.classes=f:c.attributes={"class":f},c}}function g(a,b){return tinymce.util.Tools.grep(a,function(a){return!a.filter||a.filter(b)})}function h(a){return tinymce.util.Tools.map(a,function(a){return tinymce.util.Tools.extend({},a,{original:a,selectors:{},filter:d(a.filter),item:{text:a.title,menu:[]}})})}function i(a,b){return null===b||a.settings.importcss_exclusive!==!1}function j(b,c,d){return!(i(a,c)?b in d:b in c.selectors)}function k(b,c,d){i(a,c)?d[b]=!0:c.selectors[b]=!0}function l(b,c,d){var e,g=a.settings;return e=d&&d.selector_converter?d.selector_converter:g.importcss_selector_converter?g.importcss_selector_converter:f,e.call(b,c,d)}var m=this,n=tinymce.each;a.on("renderFormatsMenu",function(b){var c=a.settings,f={},i=d(c.importcss_selector_filter),o=b.control,p=h(c.importcss_groups),q=function(b,c){if(j(b,c,f)){k(b,c,f);var d=l(m,b,c);if(d){var e=d.name||tinymce.DOM.uniqueId();return a.formatter.register(e,d),tinymce.extend({},o.settings.itemDefaults,{text:d.title,format:e})}}return null};a.settings.importcss_append||o.items().remove(),n(e(b.doc||a.getDoc(),d(c.importcss_file_filter)),function(a){if(-1===a.indexOf(".mce-")&&(!i||i(a))){var b=g(p,a);if(b.length>0)tinymce.util.Tools.each(b,function(b){var c=q(a,b);c&&b.item.menu.push(c)});else{var c=q(a,null);c&&o.add(c)}}}),n(p,function(a){a.item.menu.length>0&&o.add(a.item)}),b.control.renderNew()}),m.convertSelectorToFormat=f}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/insertdatetime/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("insertdatetime",function(a){function b(b,c){function d(a,b){if(a=""+a,a.length'+d+"";var f=a.dom.getParent(a.selection.getStart(),"time");if(f)return void a.dom.setOuterHTML(f,d)}a.insertContent(d)}var d,e,f="Sun Mon Tue Wed Thu Fri Sat Sun".split(" "),g="Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sunday".split(" "),h="Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),i="January February March April May June July August September October November December".split(" "),j=[];a.addCommand("mceInsertDate",function(){c(a.getParam("insertdatetime_dateformat",a.translate("%Y-%m-%d")))}),a.addCommand("mceInsertTime",function(){c(a.getParam("insertdatetime_timeformat",a.translate("%H:%M:%S")))}),a.addButton("insertdatetime",{type:"splitbutton",title:"Insert date/time",onclick:function(){c(d||e)},menu:j}),tinymce.each(a.settings.insertdatetime_formats||["%H:%M:%S","%Y-%m-%d","%I:%M:%S %p","%D"],function(a){e||(e=a),j.push({text:b(a),onclick:function(){d=a,c(a)}})}),a.addMenuItem("insertdatetime",{icon:"date",text:"Insert date/time",menu:j,context:"insert"})}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/layer/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("layer",function(a){function b(a){do if(a.className&&-1!=a.className.indexOf("mceItemLayer"))return a;while(a=a.parentNode)}function c(b){var c=a.dom;tinymce.each(c.select("div,p",b),function(a){/^(absolute|relative|fixed)$/i.test(a.style.position)&&(a.hasVisual?c.addClass(a,"mceItemVisualAid"):c.removeClass(a,"mceItemVisualAid"),c.addClass(a,"mceItemLayer"))})}function d(c){var d,e,f=[],g=b(a.selection.getNode()),h=-1,i=-1;for(e=[],tinymce.walk(a.getBody(),function(a){1==a.nodeType&&/^(absolute|relative|static)$/i.test(a.style.position)&&e.push(a)},"childNodes"),d=0;dh&&e[d]==g&&(h=d);if(0>c){for(d=0;d-1?(e[h].style.zIndex=f[i],e[i].style.zIndex=f[h]):f[h]>0&&(e[h].style.zIndex=f[h]-1)}else{for(d=0;df[h]){i=d;break}i>-1?(e[h].style.zIndex=f[i],e[i].style.zIndex=f[h]):e[h].style.zIndex=f[h]+1}a.execCommand("mceRepaint")}function e(){var b=a.dom,c=b.getPos(b.getParent(a.selection.getNode(),"*")),d=a.getBody();a.dom.add(d,"div",{style:{position:"absolute",left:c.x,top:c.y>20?c.y:20,width:100,height:100},"class":"mceItemVisualAid mceItemLayer"},a.selection.getContent()||a.getLang("layer.content")),tinymce.Env.ie&&b.setHTML(d,d.innerHTML)}function f(){var c=b(a.selection.getNode());c||(c=a.dom.getParent(a.selection.getNode(),"DIV,P,IMG")),c&&("absolute"==c.style.position.toLowerCase()?(a.dom.setStyles(c,{position:"",left:"",top:"",width:"",height:""}),a.dom.removeClass(c,"mceItemVisualAid"),a.dom.removeClass(c,"mceItemLayer")):(c.style.left||(c.style.left="20px"),c.style.top||(c.style.top="20px"),c.style.width||(c.style.width=c.width?c.width+"px":"100px"),c.style.height||(c.style.height=c.height?c.height+"px":"100px"),c.style.position="absolute",a.dom.setAttrib(c,"data-mce-style",""),a.addVisual(a.getBody())),a.execCommand("mceRepaint"),a.nodeChanged())}a.addCommand("mceInsertLayer",e),a.addCommand("mceMoveForward",function(){d(1)}),a.addCommand("mceMoveBackward",function(){d(-1)}),a.addCommand("mceMakeAbsolute",function(){f()}),a.addButton("moveforward",{title:"layer.forward_desc",cmd:"mceMoveForward"}),a.addButton("movebackward",{title:"layer.backward_desc",cmd:"mceMoveBackward"}),a.addButton("absolute",{title:"layer.absolute_desc",cmd:"mceMakeAbsolute"}),a.addButton("insertlayer",{title:"layer.insertlayer_desc",cmd:"mceInsertLayer"}),a.on("init",function(){tinymce.Env.ie&&a.getDoc().execCommand("2D-Position",!1,!0)}),a.on("mouseup",function(c){var d=b(c.target);d&&a.dom.setAttrib(d,"data-mce-style","")}),a.on("mousedown",function(c){var d,e=c.target,f=a.getDoc();tinymce.Env.gecko&&(b(e)?"on"!==f.designMode&&(f.designMode="on",e=f.body,d=e.parentNode,d.removeChild(e),d.appendChild(e)):"on"==f.designMode&&(f.designMode="off"))}),a.on("NodeChange",c)}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/legacyoutput/plugin.min.js: -------------------------------------------------------------------------------- 1 | !function(a){a.PluginManager.add("legacyoutput",function(b,c,d){b.settings.inline_styles=!1,b.on("init",function(){var c="p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img",d=a.explode(b.settings.font_size_style_values),e=b.schema;b.formatter.register({alignleft:{selector:c,attributes:{align:"left"}},aligncenter:{selector:c,attributes:{align:"center"}},alignright:{selector:c,attributes:{align:"right"}},alignjustify:{selector:c,attributes:{align:"justify"}},bold:[{inline:"b",remove:"all"},{inline:"strong",remove:"all"},{inline:"span",styles:{fontWeight:"bold"}}],italic:[{inline:"i",remove:"all"},{inline:"em",remove:"all"},{inline:"span",styles:{fontStyle:"italic"}}],underline:[{inline:"u",remove:"all"},{inline:"span",styles:{textDecoration:"underline"},exact:!0}],strikethrough:[{inline:"strike",remove:"all"},{inline:"span",styles:{textDecoration:"line-through"},exact:!0}],fontname:{inline:"font",attributes:{face:"%value"}},fontsize:{inline:"font",attributes:{size:function(b){return a.inArray(d,b.value)+1}}},forecolor:{inline:"font",attributes:{color:"%value"}},hilitecolor:{inline:"font",styles:{backgroundColor:"%value"}}}),a.each("b,i,u,strike".split(","),function(a){e.addValidElements(a+"[*]")}),e.getElementRule("font")||e.addValidElements("font[face|size|color|style]"),a.each(c.split(","),function(a){var b=e.getElementRule(a);b&&(b.attributes.align||(b.attributes.align={},b.attributesOrder.push("align")))})}),b.addButton("fontsizeselect",function(){var a=[],c="8pt=1 10pt=2 12pt=3 14pt=4 18pt=5 24pt=6 36pt=7",d=b.settings.fontsize_formats||c;return b.$.each(d.split(" "),function(b,c){var d=c,e=c,f=c.split("=");f.length>1&&(d=f[0],e=f[1]),a.push({text:d,value:e})}),{type:"listbox",text:"Font Sizes",tooltip:"Font Sizes",values:a,fixedWidth:!0,onPostRender:function(){var a=this;b.on("NodeChange",function(){var c;c=b.dom.getParent(b.selection.getNode(),"font"),c?a.value(c.size):a.value("")})},onclick:function(a){a.control.settings.value&&b.execCommand("FontSize",!1,a.control.settings.value)}}}),b.addButton("fontselect",function(){function a(a){a=a.replace(/;$/,"").split(";");for(var b=a.length;b--;)a[b]=a[b].split("=");return a}var c="Andale Mono=andale mono,monospace;Arial=arial,helvetica,sans-serif;Arial Black=arial black,sans-serif;Book Antiqua=book antiqua,palatino,serif;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier,monospace;Georgia=georgia,palatino,serif;Helvetica=helvetica,arial,sans-serif;Impact=impact,sans-serif;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco,monospace;Times New Roman=times new roman,times,serif;Trebuchet MS=trebuchet ms,geneva,sans-serif;Verdana=verdana,geneva,sans-serif;Webdings=webdings;Wingdings=wingdings,zapf dingbats",e=[],f=a(b.settings.font_formats||c);return d.each(f,function(a,b){e.push({text:{raw:b[0]},value:b[1],textStyle:-1==b[1].indexOf("dings")?"font-family:"+b[1]:""})}),{type:"listbox",text:"Font Family",tooltip:"Font Family",values:e,fixedWidth:!0,onPostRender:function(){var a=this;b.on("NodeChange",function(){var c;c=b.dom.getParent(b.selection.getNode(),"font"),c?a.value(c.face):a.value("")})},onselect:function(a){a.control.settings.value&&b.execCommand("FontName",!1,a.control.settings.value)}}})})}(tinymce); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/link/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("link",function(a){function b(b){return function(){var c=a.settings.link_list;"string"==typeof c?tinymce.util.XHR.send({url:c,success:function(a){b(tinymce.util.JSON.parse(a))}}):"function"==typeof c?c(b):b(c)}}function c(a,b,c){function d(a,c){return c=c||[],tinymce.each(a,function(a){var e={text:a.text||a.title};a.menu?e.menu=d(a.menu):(e.value=a.value,b&&b(e)),c.push(e)}),c}return d(a,c||[])}function d(b){function d(a){var b=l.find("#text");(!b.value()||a.lastControl&&b.value()==a.lastControl.text())&&b.value(a.control.text()),l.find("#href").value(a.control.value())}function e(b){var c=[];return tinymce.each(a.dom.select("a:not([href])"),function(a){var d=a.name||a.id;d&&c.push({text:d,value:"#"+d,selected:-1!=b.indexOf("#"+d)})}),c.length?(c.unshift({text:"None",value:""}),{name:"anchor",type:"listbox",label:"Anchors",values:c,onselect:d}):void 0}function f(){!k&&0===u.text.length&&m&&this.parent().parent().find("#text")[0].value(this.value())}function g(b){var c=b.meta||{};o&&o.value(a.convertURL(this.value(),"href")),tinymce.each(b.meta,function(a,b){l.find("#"+b).value(a)}),c.text||f.call(this)}function h(a){var b=v.getContent();if(/]+>[^<]+<\/a>$/.test(b)||-1==b.indexOf("href=")))return!1;if(a){var c,d=a.childNodes;if(0===d.length)return!1;for(c=d.length-1;c>=0;c--)if(3!=d[c].nodeType)return!1}return!0}var i,j,k,l,m,n,o,p,q,r,s,t,u={},v=a.selection,w=a.dom;i=v.getNode(),j=w.getParent(i,"a[href]"),m=h(),u.text=k=j?j.innerText||j.textContent:v.getContent({format:"text"}),u.href=j?w.getAttrib(j,"href"):"",j?u.target=w.getAttrib(j,"target"):a.settings.default_link_target&&(u.target=a.settings.default_link_target),(t=w.getAttrib(j,"rel"))&&(u.rel=t),(t=w.getAttrib(j,"class"))&&(u["class"]=t),(t=w.getAttrib(j,"title"))&&(u.title=t),m&&(n={name:"text",type:"textbox",size:40,label:"Text to display",onchange:function(){u.text=this.value()}}),b&&(o={type:"listbox",label:"Link list",values:c(b,function(b){b.value=a.convertURL(b.value||b.url,"href")},[{text:"None",value:""}]),onselect:d,value:a.convertURL(u.href,"href"),onPostRender:function(){o=this}}),a.settings.target_list!==!1&&(a.settings.target_list||(a.settings.target_list=[{text:"None",value:""},{text:"New window",value:"_blank"}]),q={name:"target",type:"listbox",label:"Target",values:c(a.settings.target_list)}),a.settings.rel_list&&(p={name:"rel",type:"listbox",label:"Rel",values:c(a.settings.rel_list)}),a.settings.link_class_list&&(r={name:"class",type:"listbox",label:"Class",values:c(a.settings.link_class_list,function(b){b.value&&(b.textStyle=function(){return a.formatter.getCssText({inline:"a",classes:[b.value]})})})}),a.settings.link_title!==!1&&(s={name:"title",type:"textbox",label:"Title",value:u.title}),l=a.windowManager.open({title:"Insert link",data:u,body:[{name:"href",type:"filepicker",filetype:"file",size:40,autofocus:!0,label:"Url",onchange:g,onkeyup:f},n,s,e(u.href),o,p,q,r],onSubmit:function(b){function c(b,c){var d=a.selection.getRng();tinymce.util.Delay.setEditorTimeout(a,function(){a.windowManager.confirm(b,function(b){a.selection.setRng(d),c(b)})})}function d(){var b={href:e,target:u.target?u.target:null,rel:u.rel?u.rel:null,"class":u["class"]?u["class"]:null,title:u.title?u.title:null};j?(a.focus(),m&&u.text!=k&&("innerText"in j?j.innerText=u.text:j.textContent=u.text),w.setAttribs(j,b),v.select(j),a.undoManager.add()):m?a.insertContent(w.createHTML("a",b,w.encode(u.text))):a.execCommand("mceInsertLink",!1,b)}var e;return u=tinymce.extend(u,b.data),(e=u.href)?e.indexOf("@")>0&&-1==e.indexOf("//")&&-1==e.indexOf("mailto:")?void c("The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?",function(a){a&&(e="mailto:"+e),d()}):a.settings.link_assume_external_targets&&!/^\w+:/i.test(e)||!a.settings.link_assume_external_targets&&/^\s*www[\.|\d\.]/i.test(e)?void c("The URL you entered seems to be an external link. Do you want to add the required http:// prefix?",function(a){a&&(e="http://"+e),d()}):void d():void a.execCommand("unlink")}})}a.addButton("link",{icon:"link",tooltip:"Insert/edit link",shortcut:"Meta+K",onclick:b(d),stateSelector:"a[href]"}),a.addButton("unlink",{icon:"unlink",tooltip:"Remove link",cmd:"unlink",stateSelector:"a[href]"}),a.addShortcut("Meta+K","",b(d)),a.addCommand("mceLink",b(d)),this.showDialog=d,a.addMenuItem("link",{icon:"link",text:"Insert/edit link",shortcut:"Meta+K",onclick:b(d),stateSelector:"a[href]",context:"insert",prependToContext:!0})}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/media/moxieplayer.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarceauKa/laravel-crudable/986f4617f4318bda933fae13fafd764b575861dd/resources/assets/fields/tinymce/plugins/media/moxieplayer.swf -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/nonbreaking/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("nonbreaking",function(a){var b=a.getParam("nonbreaking_force_tab");if(a.addCommand("mceNonBreaking",function(){a.insertContent(a.plugins.visualchars&&a.plugins.visualchars.state?' ':" "),a.dom.setAttrib(a.dom.select("span.mce-nbsp"),"data-mce-bogus","1")}),a.addButton("nonbreaking",{title:"Nonbreaking space",cmd:"mceNonBreaking"}),a.addMenuItem("nonbreaking",{text:"Nonbreaking space",cmd:"mceNonBreaking",context:"insert"}),b){var c=+b>1?+b:3;a.on("keydown",function(b){if(9==b.keyCode){if(b.shiftKey)return;b.preventDefault();for(var d=0;c>d;d++)a.execCommand("mceNonBreaking")}})}}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/noneditable/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("noneditable",function(a){function b(a){return function(b){return-1!==(" "+b.attr("class")+" ").indexOf(a)}}function c(b){function c(b){var c=arguments,d=c[c.length-2];return d>0&&'"'==g.charAt(d-1)?b:''+a.dom.encode("string"==typeof c[1]?c[1]:c[0])+""}var d=f.length,g=b.content,h=tinymce.trim(e);if("raw"!=b.format){for(;d--;)g=g.replace(f[d],c);b.content=g}}var d,e,f,g="contenteditable";d=" "+tinymce.trim(a.getParam("noneditable_editable_class","mceEditable"))+" ",e=" "+tinymce.trim(a.getParam("noneditable_noneditable_class","mceNonEditable"))+" ";var h=b(d),i=b(e);f=a.getParam("noneditable_regexp"),f&&!f.length&&(f=[f]),a.on("PreInit",function(){f&&a.on("BeforeSetContent",c),a.parser.addAttributeFilter("class",function(a){for(var b,c=a.length;c--;)b=a[c],h(b)?b.attr(g,"true"):i(b)&&b.attr(g,"false")}),a.serializer.addAttributeFilter(g,function(a){for(var b,c=a.length;c--;)b=a[c],(h(b)||i(b))&&(f&&b.attr("data-mce-content")?(b.name="#text",b.type=3,b.raw=!0,b.value=b.attr("data-mce-content")):b.attr(g,null))})})}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/pagebreak/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("pagebreak",function(a){var b="mce-pagebreak",c=a.getParam("pagebreak_separator",""),d=new RegExp(c.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g,function(a){return"\\"+a}),"gi"),e='';a.addCommand("mcePageBreak",function(){a.settings.pagebreak_split_block?a.insertContent("

"+e+"

"):a.insertContent(e)}),a.addButton("pagebreak",{title:"Page break",cmd:"mcePageBreak"}),a.addMenuItem("pagebreak",{text:"Page break",icon:"pagebreak",cmd:"mcePageBreak",context:"insert"}),a.on("ResolveName",function(c){"IMG"==c.target.nodeName&&a.dom.hasClass(c.target,b)&&(c.name="pagebreak")}),a.on("click",function(c){c=c.target,"IMG"===c.nodeName&&a.dom.hasClass(c,b)&&a.selection.select(c)}),a.on("BeforeSetContent",function(a){a.content=a.content.replace(d,e)}),a.on("PreInit",function(){a.serializer.addNodeFilter("img",function(b){for(var d,e,f=b.length;f--;)if(d=b[f],e=d.attr("class"),e&&-1!==e.indexOf("mce-pagebreak")){var g=d.parent;if(a.schema.getBlockElements()[g.name]&&a.settings.pagebreak_split_block){g.type=3,g.value=c,g.raw=!0,d.remove();continue}d.type=3,d.value=c,d.raw=!0}})})}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/preview/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("preview",function(a){var b=a.settings,c=!tinymce.Env.ie;a.addCommand("mcePreview",function(){a.windowManager.open({title:"Preview",width:parseInt(a.getParam("plugin_preview_width","650"),10),height:parseInt(a.getParam("plugin_preview_height","500"),10),html:'",buttons:{text:"Close",onclick:function(){this.parent().parent().close()}},onPostRender:function(){var d,e="";e+='',tinymce.each(a.contentCSS,function(b){e+=''});var f=b.body_id||"tinymce";-1!=f.indexOf("=")&&(f=a.getParam("body_id","","hash"),f=f[a.id]||f);var g=b.body_class||"";-1!=g.indexOf("=")&&(g=a.getParam("body_class","","hash"),g=g[a.id]||"");var h=' ',i=a.settings.directionality?' dir="'+a.settings.directionality+'"':"";if(d=""+e+'"+a.getContent()+h+"",c)this.getEl("body").firstChild.src="data:text/html;charset=utf-8,"+encodeURIComponent(d);else{var j=this.getEl("body").firstChild.contentWindow.document;j.open(),j.write(d),j.close()}}})}),a.addButton("preview",{title:"Preview",cmd:"mcePreview"}),a.addMenuItem("preview",{text:"Preview",cmd:"mcePreview",context:"view"})}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/print/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("print",function(a){a.addCommand("mcePrint",function(){a.getWin().print()}),a.addButton("print",{title:"Print",cmd:"mcePrint"}),a.addShortcut("Meta+P","","mcePrint"),a.addMenuItem("print",{text:"Print",cmd:"mcePrint",icon:"print",shortcut:"Meta+P",context:"file"})}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/save/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("save",function(a){function b(){var b;return b=tinymce.DOM.getParent(a.id,"form"),!a.getParam("save_enablewhendirty",!0)||a.isDirty()?(tinymce.triggerSave(),a.getParam("save_onsavecallback")?(a.execCallback("save_onsavecallback",a),void a.nodeChanged()):void(b?(a.setDirty(!1),b.onsubmit&&!b.onsubmit()||("function"==typeof b.submit?b.submit():c(a.translate("Error: Form submit field collision."))),a.nodeChanged()):c(a.translate("Error: No form element found.")))):void 0}function c(b){a.notificationManager.open({text:b,type:"error"})}function d(){var b=tinymce.trim(a.startContent);return a.getParam("save_oncancelcallback")?void a.execCallback("save_oncancelcallback",a):(a.setContent(b),a.undoManager.clear(),void a.nodeChanged())}function e(){var b=this;a.on("nodeChange dirty",function(){b.disabled(a.getParam("save_enablewhendirty",!0)&&!a.isDirty())})}a.addCommand("mceSave",b),a.addCommand("mceCancel",d),a.addButton("save",{icon:"save",text:"Save",cmd:"mceSave",disabled:!0,onPostRender:e}),a.addButton("cancel",{text:"Cancel",icon:!1,cmd:"mceCancel",disabled:!0,onPostRender:e}),a.addShortcut("Meta+S","","mceSave")}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/searchreplace/plugin.min.js: -------------------------------------------------------------------------------- 1 | !function(){function a(a){return a&&1==a.nodeType&&"false"===a.contentEditable}function b(b,c,d,e,f){function g(a,b){if(b=b||0,!a[0])throw"findAndReplaceDOMText cannot handle zero-length matches";var c=a.index;if(b>0){var d=a[b];if(!d)throw"Invalid capture group";c+=a[0].indexOf(d),a[0]=d}return[c,c+a[0].length,[a[0]]]}function h(b){var c;if(3===b.nodeType)return b.data;if(o[b.nodeName]&&!n[b.nodeName])return"";if(c="",a(b))return"\n";if((n[b.nodeName]||p[b.nodeName])&&(c+="\n"),b=b.firstChild)do c+=h(b);while(b=b.nextSibling);return c}function i(b,c,d){var e,f,g,h,i=[],j=0,k=b,l=c.shift(),m=0;a:for(;;){if((n[k.nodeName]||p[k.nodeName]||a(k))&&j++,3===k.nodeType&&(!f&&k.length+j>=l[1]?(f=k,h=l[1]-j):e&&i.push(k),!e&&k.length+j>l[0]&&(e=k,g=l[0]-j),j+=k.length),e&&f){if(k=d({startNode:e,startNodeIndex:g,endNode:f,endNodeIndex:h,innerNodes:i,match:l[2],matchIndex:m}),j-=f.length-h,e=null,f=null,i=[],l=c.shift(),m++,!l)break}else if(o[k.nodeName]&&!n[k.nodeName]||!k.firstChild){if(k.nextSibling){k=k.nextSibling;continue}}else if(!a(k)){k=k.firstChild;continue}for(;;){if(k.nextSibling){k=k.nextSibling;break}if(k.parentNode===b)break a;k=k.parentNode}}}function j(a){var b;if("function"!=typeof a){var c=a.nodeType?a:m.createElement(a);b=function(a,b){var d=c.cloneNode(!1);return d.setAttribute("data-mce-index",b),a&&d.appendChild(m.createTextNode(a)),d}}else b=a;return function(a){var c,d,e,f=a.startNode,g=a.endNode,h=a.matchIndex;if(f===g){var i=f;e=i.parentNode,a.startNodeIndex>0&&(c=m.createTextNode(i.data.substring(0,a.startNodeIndex)),e.insertBefore(c,i));var j=b(a.match[0],h);return e.insertBefore(j,i),a.endNodeIndexn;++n){var p=a.innerNodes[n],q=b(p.data,h);p.parentNode.replaceChild(q,p),l.push(q)}var r=b(g.data.substring(0,a.endNodeIndex),h);return e=f.parentNode,e.insertBefore(c,f),e.insertBefore(k,f),e.removeChild(f),e=g.parentNode,e.insertBefore(r,g),e.insertBefore(d,g),e.removeChild(g),r}}var k,l,m,n,o,p,q=[],r=0;if(m=c.ownerDocument,n=f.getBlockElements(),o=f.getWhiteSpaceElements(),p=f.getShortEndedElements(),l=h(c)){if(b.global)for(;k=b.exec(l);)q.push(g(k,e));else k=l.match(b),q.push(g(k,e));return q.length&&(r=q.length,i(c,q,j(d))),r}}function c(a){function c(){function b(){f.statusbar.find("#next").disabled(!g(l+1).length),f.statusbar.find("#prev").disabled(!g(l-1).length)}function c(){a.windowManager.alert("Could not find the specified string.",function(){f.find("#find")[0].focus()})}var d,e={};d=tinymce.trim(a.selection.getContent({format:"text"}));var f=a.windowManager.open({layout:"flex",pack:"center",align:"center",onClose:function(){a.focus(),k.done()},onSubmit:function(a){var d,h,i,j;return a.preventDefault(),h=f.find("#case").checked(),j=f.find("#words").checked(),i=f.find("#find").value(),i.length?e.text==i&&e.caseState==h&&e.wholeWord==j?0===g(l+1).length?void c():(k.next(),void b()):(d=k.find(i,h,j),d||c(),f.statusbar.items().slice(1).disabled(0===d),b(),void(e={text:i,caseState:h,wholeWord:j})):(k.done(!1),void f.statusbar.items().slice(1).disabled(!0))},buttons:[{text:"Find",subtype:"primary",onclick:function(){f.submit()}},{text:"Replace",disabled:!0,onclick:function(){k.replace(f.find("#replace").value())||(f.statusbar.items().slice(1).disabled(!0),l=-1,e={})}},{text:"Replace all",disabled:!0,onclick:function(){k.replace(f.find("#replace").value(),!0,!0),f.statusbar.items().slice(1).disabled(!0),e={}}},{type:"spacer",flex:1},{text:"Prev",name:"prev",disabled:!0,onclick:function(){k.prev(),b()}},{text:"Next",name:"next",disabled:!0,onclick:function(){k.next(),b()}}],title:"Find and replace",items:{type:"form",padding:20,labelGap:30,spacing:10,items:[{type:"textbox",name:"find",size:40,label:"Find",value:d},{type:"textbox",name:"replace",size:40,label:"Replace with"},{type:"checkbox",name:"case",text:"Match case",label:" "},{type:"checkbox",name:"words",text:"Whole words",label:" "}]}})}function d(a){var b=a.getAttribute("data-mce-index");return"number"==typeof b?""+b:b}function e(c){var d,e;return e=a.dom.create("span",{"data-mce-bogus":1}),e.className="mce-match-marker",d=a.getBody(),k.done(!1),b(c,d,e,!1,a.schema)}function f(a){var b=a.parentNode;a.firstChild&&b.insertBefore(a.firstChild,a),a.parentNode.removeChild(a)}function g(b){var c,e=[];if(c=tinymce.toArray(a.getBody().getElementsByTagName("span")),c.length)for(var f=0;f0}var k=this,l=-1;k.init=function(a){a.addMenuItem("searchreplace",{text:"Find and replace",shortcut:"Meta+F",onclick:c,separator:"before",context:"edit"}),a.addButton("searchreplace",{tooltip:"Find and replace",shortcut:"Meta+F",onclick:c}),a.addCommand("SearchReplace",c),a.shortcuts.add("Meta+F","",c)},k.find=function(a,b,c){a=a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&"),a=c?"\\b"+a+"\\b":a;var d=e(new RegExp(a,b?"g":"gi"));return d&&(l=-1,l=h(!0)),d},k.next=function(){var a=h(!0);-1!==a&&(l=a)},k.prev=function(){var a=h(!1);-1!==a&&(l=a)},k.replace=function(b,c,e){var h,m,n,o,p,q,r=l;for(c=c!==!1,n=a.getBody(),m=tinymce.grep(tinymce.toArray(n.getElementsByTagName("span")),j),h=0;hl&&m[h].setAttribute("data-mce-index",p-1)}return a.undoManager.add(),l=r,c?(q=g(r+1).length>0,k.next()):(q=g(r-1).length>0,k.prev()),!e&&q},k.done=function(b){var c,e,g,h;for(e=tinymce.toArray(a.getBody().getElementsByTagName("span")),c=0;c0){for(j=g+1;j=0;j--)if(i(h[j]))return h[j];return null}var g,h,i,j;if(!(9!==b.keyCode||b.ctrlKey||b.altKey||b.metaKey||b.isDefaultPrevented())&&(i=f(a.getParam("tab_focus",a.getParam("tabfocus_elements",":prev,:next"))),1==i.length&&(i[1]=i[0],i[0]=":prev"),h=b.shiftKey?":prev"==i[0]?c(-1):d.get(i[0]):":next"==i[1]?c(1):d.get(i[1]))){var k=tinymce.get(h.id||h.name);h.id&&k?k.focus():tinymce.util.Delay.setTimeout(function(){tinymce.Env.webkit||window.focus(),h.focus()},10),b.preventDefault()}}var d=tinymce.DOM,e=tinymce.each,f=tinymce.explode;a.on("init",function(){a.inline&&tinymce.DOM.setAttrib(a.getBody(),"tabIndex",null),a.on("keyup",b),tinymce.Env.gecko?a.on("keypress keydown",c):a.on("keydown",c)})}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/template/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("template",function(a){function b(b){return function(){var c=a.settings.templates;return"function"==typeof c?void c(b):void("string"==typeof c?tinymce.util.XHR.send({url:c,success:function(a){b(tinymce.util.JSON.parse(a))}}):b(c))}}function c(b){function c(b){function c(b){if(-1==b.indexOf("")){var c="";tinymce.each(a.contentCSS,function(b){c+=''});var e=a.settings.body_class||"";-1!=e.indexOf("=")&&(e=a.getParam("body_class","","hash"),e=e[a.id]||""),b=""+c+''+b+""}b=f(b,"template_preview_replace_values");var g=d.find("iframe")[0].getEl().contentWindow.document;g.open(),g.write(b),g.close()}var g=b.control.value();g.url?tinymce.util.XHR.send({url:g.url,success:function(a){e=a,c(e)}}):(e=g.content,c(e)),d.find("#description")[0].text(b.control.value().description)}var d,e,h=[];if(!b||0===b.length){var i=a.translate("No templates defined.");return void a.notificationManager.open({text:i,type:"info"})}tinymce.each(b,function(a){h.push({selected:!h.length,text:a.title,value:{url:a.url,content:a.content,description:a.description}})}),d=a.windowManager.open({title:"Insert template",layout:"flex",direction:"column",align:"stretch",padding:15,spacing:10,items:[{type:"form",flex:0,padding:0,items:[{type:"container",label:"Templates",items:{type:"listbox",label:"Templates",name:"template",values:h,onselect:c}}]},{type:"label",name:"description",label:"Description",text:"\xa0"},{type:"iframe",flex:1,border:1}],onsubmit:function(){g(!1,e)},minWidth:Math.min(tinymce.DOM.getViewPort().w,a.getParam("template_popup_width",600)),minHeight:Math.min(tinymce.DOM.getViewPort().h,a.getParam("template_popup_height",500))}),d.find("listbox")[0].fire("select")}function d(b,c){function d(a,b){if(a=""+a,a.length0&&(i=k.create("div",null),i.appendChild(j[0].cloneNode(!0))),h(k.select("*",i),function(b){g(b,a.getParam("template_cdate_classes","cdate").replace(/\s+/g,"|"))&&(b.innerHTML=d(a.getParam("template_cdate_format",a.getLang("template.cdate_format")))),g(b,a.getParam("template_mdate_classes","mdate").replace(/\s+/g,"|"))&&(b.innerHTML=d(a.getParam("template_mdate_format",a.getLang("template.mdate_format")))),g(b,a.getParam("template_selected_content_classes","selcontent").replace(/\s+/g,"|"))&&(b.innerHTML=l)}),e(i),a.execCommand("mceInsertContent",!1,i.innerHTML),a.addVisual()}var h=tinymce.each;a.addCommand("mceInsertTemplate",g),a.addButton("template",{title:"Insert template",onclick:b(c)}),a.addMenuItem("template",{text:"Insert template",onclick:b(c),context:"insert"}),a.on("PreProcess",function(b){var c=a.dom;h(c.select("div",b.node),function(b){c.hasClass(b,"mceTmpl")&&(h(c.select("*",b),function(b){c.hasClass(b,a.getParam("template_mdate_classes","mdate").replace(/\s+/g,"|"))&&(b.innerHTML=d(a.getParam("template_mdate_format",a.getLang("template.mdate_format"))))}),e(b))})})}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/textcolor/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("textcolor",function(a){function b(b){var c;return a.dom.getParents(a.selection.getStart(),function(a){var d;(d=a.style["forecolor"==b?"color":"background-color"])&&(c=d)}),c}function c(b){var c,d,e=[];for(d=["000000","Black","993300","Burnt orange","333300","Dark olive","003300","Dark green","003366","Dark azure","000080","Navy Blue","333399","Indigo","333333","Very dark gray","800000","Maroon","FF6600","Orange","808000","Olive","008000","Green","008080","Teal","0000FF","Blue","666699","Grayish blue","808080","Gray","FF0000","Red","FF9900","Amber","99CC00","Yellow green","339966","Sea green","33CCCC","Turquoise","3366FF","Royal blue","800080","Purple","999999","Medium gray","FF00FF","Magenta","FFCC00","Gold","FFFF00","Yellow","00FF00","Lime","00FFFF","Aqua","00CCFF","Sky blue","993366","Red violet","FFFFFF","White","FF99CC","Pink","FFCC99","Peach","FFFF99","Light yellow","CCFFCC","Pale green","CCFFFF","Pale cyan","99CCFF","Light sky blue","CC99FF","Plum"],d=a.settings.textcolor_map||d,d=a.settings[b+"_map"]||d,c=0;c
'+(c?"×":"")+"
"}var d,e,f,g,h,k,l,m,n=this,o=n._id,p=0;for(m=n.settings.origin,d=c(m),d.push({text:tinymce.translate("No color"),color:"transparent"}),f='',g=d.length-1,k=0;k",h=0;hg?f+="":(e=d[l],f+=b(e.color,e.text));f+=""}if(a.settings.color_picker_callback){for(f+='",f+="",h=0;hb.start.length?-1:a.start.length'+a+""}function f(){var a,b="";for(a in n)b+=a;return new RegExp("["+b+"]","g")}function g(){var a,b="";for(a in n)b&&(b+=","),b+="span.mce-"+n[a];return b}var h,i,j,k,l,m,n,o,p=a.getBody(),q=a.selection;if(n={"\xa0":"nbsp","\xad":"shy"},d=!d,e.state=d,a.fire("VisualChars",{state:d}),o=f(),b&&(m=q.getBookmark()),d)for(i=[],tinymce.walk(p,function(a){3==a.nodeType&&a.nodeValue&&o.test(a.nodeValue)&&i.push(a)},"childNodes"),j=0;j=0;j--)a.dom.remove(i[j],1);q.moveToBookmark(m)}function c(){var b=this;a.on("VisualChars",function(a){b.active(a.state)})}var d,e=this;a.addCommand("mceVisualChars",b),a.addButton("visualchars",{title:"Show invisible characters",cmd:"mceVisualChars",onPostRender:c}),a.addMenuItem("visualchars",{text:"Show invisible characters",cmd:"mceVisualChars",onPostRender:c,selectable:!0,context:"view",prependToContext:!0}),a.on("beforegetcontent",function(a){d&&"raw"!=a.format&&!a.draft&&(d=!0,b(!1))})}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/plugins/wordcount/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add("wordcount",function(a){function b(){a.theme.panel.find("#wordcount").text(["Words: {0}",e.getCount()])}var c,d,e=this;c=a.getParam("wordcount_countregex",/[\w\u2019\x27\-\u00C0-\u1FFF]+/g),d=a.getParam("wordcount_cleanregex",/[0-9.(),;:!?%#$?\x27\x22_+=\\\/\-]*/g),a.on("init",function(){var c=a.theme.panel&&a.theme.panel.find("#statusbar")[0];c&&tinymce.util.Delay.setEditorTimeout(a,function(){c.insert({type:"label",name:"wordcount",text:["Words: {0}",e.getCount()],classes:"wordcount",disabled:a.settings.readonly},0),a.on("setcontent beforeaddundo",b),a.on("keyup",function(a){32==a.keyCode&&b()})},0)}),e.getCount=function(){var b=a.getContent({format:"raw"}),e=0;if(b){b=b.replace(/\.\.\./g," "),b=b.replace(/<.[^<>]*?>/g," ").replace(/ | /gi," "),b=b.replace(/(\w+)(&#?[a-z0-9]+;)+(\w+)/i,"$1$3").replace(/&.+?;/g," "),b=b.replace(d,"");var f=b.match(c);f&&(e=f.length)}return e}}); -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/skins/lightgray/content.inline.min.css: -------------------------------------------------------------------------------- 1 | .mce-content-body .mce-reset{margin:0;padding:0;border:0;outline:0;vertical-align:top;background:transparent;text-decoration:none;color:black;font-family:Arial;font-size:11px;text-shadow:none;float:none;position:static;width:auto;height:auto;white-space:nowrap;cursor:inherit;line-height:normal;font-weight:normal;text-align:left;-webkit-tap-highlight-color:transparent;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;direction:ltr;max-width:none}.mce-object{border:1px dotted #3a3a3a;background:#d5d5d5 url(img/object.gif) no-repeat center}.mce-preview-object{display:inline-block;position:relative;margin:0 2px 0 2px;line-height:0;border:1px solid gray}.mce-preview-object .mce-shim{position:absolute;top:0;left:0;width:100%;height:100%;background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)}figure.align-left{float:left}figure.align-right{float:right}figure.image.align-center{display:table;margin-left:auto;margin-right:auto}figure.image{display:inline-block;border:1px solid gray;margin:0 2px 0 1px;background:#f5f2f0}figure.image img{margin:8px 8px 0 8px}figure.image figcaption{margin:6px 8px 6px 8px;text-align:center}.mce-preview-object[data-mce-selected="2"] .mce-shim{display:none}.mce-pagebreak{cursor:default;display:block;border:0;width:100%;height:5px;border:1px dashed #666;margin-top:15px;page-break-before:always}@media print{.mce-pagebreak{border:0}}.mce-item-anchor{cursor:default;display:inline-block;-webkit-user-select:all;-webkit-user-modify:read-only;-moz-user-select:all;-moz-user-modify:read-only;user-select:all;user-modify:read-only;width:9px !important;height:9px !important;border:1px dotted #3a3a3a;background:#d5d5d5 url(img/anchor.gif) no-repeat center}.mce-nbsp,.mce-shy{background:#aaa}.mce-shy::after{content:'-'}hr{cursor:default}.mce-match-marker{background:#aaa;color:#fff}.mce-match-marker-selected{background:#39f;color:#fff}.mce-spellchecker-word{border-bottom:2px solid #f00;cursor:default}.mce-spellchecker-grammar{border-bottom:2px solid #008000;cursor:default}.mce-item-table,.mce-item-table td,.mce-item-table th,.mce-item-table caption{border:1px dashed #bbb}td[data-mce-selected],th[data-mce-selected]{background-color:#39f !important}.mce-edit-focus{outline:1px dotted #333}.mce-content-body *[contentEditable=false] *[contentEditable=true]:focus{outline:2px solid #2d8ac7}.mce-content-body *[contentEditable=false] *[contentEditable=true]:hover{outline:2px solid #7acaff}.mce-content-body *[contentEditable=false][data-mce-selected]{outline:2px solid #2d8ac7}.mce-resize-bar-dragging{background-color:blue;opacity:.25;filter:alpha(opacity=25);zoom:1} -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/skins/lightgray/content.min.css: -------------------------------------------------------------------------------- 1 | body{background-color:#fff;color:#000;font-family:Verdana,Arial,Helvetica,sans-serif;font-size:11px;scrollbar-3dlight-color:#f0f0ee;scrollbar-arrow-color:#676662;scrollbar-base-color:#f0f0ee;scrollbar-darkshadow-color:#ddd;scrollbar-face-color:#e0e0dd;scrollbar-highlight-color:#f0f0ee;scrollbar-shadow-color:#f0f0ee;scrollbar-track-color:#f5f5f5}td,th{font-family:Verdana,Arial,Helvetica,sans-serif;font-size:11px}.mce-content-body .mce-reset{margin:0;padding:0;border:0;outline:0;vertical-align:top;background:transparent;text-decoration:none;color:black;font-family:Arial;font-size:11px;text-shadow:none;float:none;position:static;width:auto;height:auto;white-space:nowrap;cursor:inherit;line-height:normal;font-weight:normal;text-align:left;-webkit-tap-highlight-color:transparent;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;direction:ltr;max-width:none}.mce-object{border:1px dotted #3a3a3a;background:#d5d5d5 url(img/object.gif) no-repeat center}.mce-preview-object{display:inline-block;position:relative;margin:0 2px 0 2px;line-height:0;border:1px solid gray}.mce-preview-object .mce-shim{position:absolute;top:0;left:0;width:100%;height:100%;background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)}figure.align-left{float:left}figure.align-right{float:right}figure.image.align-center{display:table;margin-left:auto;margin-right:auto}figure.image{display:inline-block;border:1px solid gray;margin:0 2px 0 1px;background:#f5f2f0}figure.image img{margin:8px 8px 0 8px}figure.image figcaption{margin:6px 8px 6px 8px;text-align:center}.mce-preview-object[data-mce-selected="2"] .mce-shim{display:none}.mce-pagebreak{cursor:default;display:block;border:0;width:100%;height:5px;border:1px dashed #666;margin-top:15px;page-break-before:always}@media print{.mce-pagebreak{border:0}}.mce-item-anchor{cursor:default;display:inline-block;-webkit-user-select:all;-webkit-user-modify:read-only;-moz-user-select:all;-moz-user-modify:read-only;user-select:all;user-modify:read-only;width:9px !important;height:9px !important;border:1px dotted #3a3a3a;background:#d5d5d5 url(img/anchor.gif) no-repeat center}.mce-nbsp,.mce-shy{background:#aaa}.mce-shy::after{content:'-'}hr{cursor:default}.mce-match-marker{background:#aaa;color:#fff}.mce-match-marker-selected{background:#39f;color:#fff}.mce-spellchecker-word{border-bottom:2px solid #f00;cursor:default}.mce-spellchecker-grammar{border-bottom:2px solid #008000;cursor:default}.mce-item-table,.mce-item-table td,.mce-item-table th,.mce-item-table caption{border:1px dashed #bbb}td[data-mce-selected],th[data-mce-selected]{background-color:#39f !important}.mce-edit-focus{outline:1px dotted #333}.mce-content-body *[contentEditable=false] *[contentEditable=true]:focus{outline:2px solid #2d8ac7}.mce-content-body *[contentEditable=false] *[contentEditable=true]:hover{outline:2px solid #7acaff}.mce-content-body *[contentEditable=false][data-mce-selected]{outline:2px solid #2d8ac7}.mce-resize-bar-dragging{background-color:blue;opacity:.25;filter:alpha(opacity=25);zoom:1} -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/skins/lightgray/fonts/tinymce-small.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarceauKa/laravel-crudable/986f4617f4318bda933fae13fafd764b575861dd/resources/assets/fields/tinymce/skins/lightgray/fonts/tinymce-small.eot -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/skins/lightgray/fonts/tinymce-small.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarceauKa/laravel-crudable/986f4617f4318bda933fae13fafd764b575861dd/resources/assets/fields/tinymce/skins/lightgray/fonts/tinymce-small.ttf -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/skins/lightgray/fonts/tinymce-small.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarceauKa/laravel-crudable/986f4617f4318bda933fae13fafd764b575861dd/resources/assets/fields/tinymce/skins/lightgray/fonts/tinymce-small.woff -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/skins/lightgray/fonts/tinymce.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarceauKa/laravel-crudable/986f4617f4318bda933fae13fafd764b575861dd/resources/assets/fields/tinymce/skins/lightgray/fonts/tinymce.eot -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/skins/lightgray/fonts/tinymce.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarceauKa/laravel-crudable/986f4617f4318bda933fae13fafd764b575861dd/resources/assets/fields/tinymce/skins/lightgray/fonts/tinymce.ttf -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/skins/lightgray/fonts/tinymce.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarceauKa/laravel-crudable/986f4617f4318bda933fae13fafd764b575861dd/resources/assets/fields/tinymce/skins/lightgray/fonts/tinymce.woff -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/skins/lightgray/img/anchor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarceauKa/laravel-crudable/986f4617f4318bda933fae13fafd764b575861dd/resources/assets/fields/tinymce/skins/lightgray/img/anchor.gif -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/skins/lightgray/img/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarceauKa/laravel-crudable/986f4617f4318bda933fae13fafd764b575861dd/resources/assets/fields/tinymce/skins/lightgray/img/loader.gif -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/skins/lightgray/img/object.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarceauKa/laravel-crudable/986f4617f4318bda933fae13fafd764b575861dd/resources/assets/fields/tinymce/skins/lightgray/img/object.gif -------------------------------------------------------------------------------- /resources/assets/fields/tinymce/skins/lightgray/img/trans.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarceauKa/laravel-crudable/986f4617f4318bda933fae13fafd764b575861dd/resources/assets/fields/tinymce/skins/lightgray/img/trans.gif -------------------------------------------------------------------------------- /resources/lang/en/buttons.php: -------------------------------------------------------------------------------- 1 | 'New', 6 | 'edit' => 'Edit', 7 | 'destroy' => 'Delete', 8 | 'back' => 'Back', 9 | 'save' => 'Save' 10 | 11 | ]; 12 | -------------------------------------------------------------------------------- /resources/lang/en/form.php: -------------------------------------------------------------------------------- 1 | ':Name update', 6 | 'create_title' => ':Name create', 7 | 8 | ]; 9 | -------------------------------------------------------------------------------- /resources/lang/en/table.php: -------------------------------------------------------------------------------- 1 | '{0} No entries|{1} 1 entry|[2,Inf] :count entries', 6 | 'title' => ':Name list' 7 | 8 | ]; 9 | -------------------------------------------------------------------------------- /resources/lang/fr/buttons.php: -------------------------------------------------------------------------------- 1 | 'Ajouter', 6 | 'edit' => 'Modifier', 7 | 'destroy' => 'Supprimer', 8 | 'back' => 'Retour', 9 | 'save' => 'Enregistrer' 10 | 11 | ]; 12 | -------------------------------------------------------------------------------- /resources/lang/fr/form.php: -------------------------------------------------------------------------------- 1 | 'Modifier :Name', 6 | 'create_title' => 'Créer :Name', 7 | 8 | ]; 9 | -------------------------------------------------------------------------------- /resources/lang/fr/table.php: -------------------------------------------------------------------------------- 1 | '{0} Aucune entrée|{1} 1 entrée|[2,Inf] :count entrées', 6 | 'title' => 'Liste des :name' 7 | 8 | ]; 9 | -------------------------------------------------------------------------------- /resources/screenshot-create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarceauKa/laravel-crudable/986f4617f4318bda933fae13fafd764b575861dd/resources/screenshot-create.png -------------------------------------------------------------------------------- /resources/screenshot-table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarceauKa/laravel-crudable/986f4617f4318bda933fae13fafd764b575861dd/resources/screenshot-table.png -------------------------------------------------------------------------------- /resources/views/fields/assets.blade.php: -------------------------------------------------------------------------------- 1 | @section('crud-styles') 2 | @parent 3 | @foreach($crud_css as $file) 4 | 5 | @endforeach 6 | @endsection 7 | 8 | @section('crud-scripts') 9 | @parent 10 | @foreach($crud_js as $file) 11 | 12 | @endforeach 13 | @endsection 14 | -------------------------------------------------------------------------------- /resources/views/fields/date-picker.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | @include('crud::fields.partials.help') 5 | @include('crud::fields.partials.errors') 6 |
7 | 24 | -------------------------------------------------------------------------------- /resources/views/fields/email.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | @include('crud::fields.partials.help') 5 | @include('crud::fields.partials.errors') 6 |
7 | -------------------------------------------------------------------------------- /resources/views/fields/fileupload.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
{!! $field->getTableValue() !!}
4 | 5 | @include('crud::fields.partials.help') 6 | @include('crud::fields.partials.errors') 7 |
8 | -------------------------------------------------------------------------------- /resources/views/fields/partials/errors.blade.php: -------------------------------------------------------------------------------- 1 | @if($has_error) 2 |

{{ $error }}

3 | @endif -------------------------------------------------------------------------------- /resources/views/fields/partials/help.blade.php: -------------------------------------------------------------------------------- 1 | @if($help) 2 |

{{ $help }}

3 | @endif -------------------------------------------------------------------------------- /resources/views/fields/radio.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | @foreach($options as $option_key => $option_name) 4 |
5 | 10 |
11 | @endforeach 12 | @include('crud::fields.partials.help') 13 | @include('crud::fields.partials.errors') 14 |
15 | -------------------------------------------------------------------------------- /resources/views/fields/select-relation.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 8 | @include('crud::fields.partials.help') 9 | @include('crud::fields.partials.errors') 10 |
11 | -------------------------------------------------------------------------------- /resources/views/fields/text.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | @include('crud::fields.partials.help') 5 | @include('crud::fields.partials.errors') 6 |
7 | -------------------------------------------------------------------------------- /resources/views/fields/textarea.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | @include('crud::fields.partials.help') 5 | @include('crud::fields.partials.errors') 6 |
7 | -------------------------------------------------------------------------------- /resources/views/fields/tinymce.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | @include('crud::fields.partials.help') 5 | @include('crud::fields.partials.errors') 6 |
7 | 22 | -------------------------------------------------------------------------------- /resources/views/form-create.blade.php: -------------------------------------------------------------------------------- 1 |

{{ $title }}

2 | 3 |
4 | {!! $method_field !!} 5 | {!! $csrf_field !!} 6 | @foreach($entry->formFields() as $field) 7 | {!! $field->form() !!} 8 | @endforeach 9 |
10 | @lang('crud::buttons.back') 11 | 12 | 13 | @include('crud::fields.assets', compact($crud_js, $crud_css)) -------------------------------------------------------------------------------- /resources/views/form-update.blade.php: -------------------------------------------------------------------------------- 1 |

{{ $title }}

2 | 3 |
4 | {!! $method_field !!} 5 | {!! $csrf_field !!} 6 | @foreach($entry->formFields() as $field) 7 | {!! $field->form() !!} 8 | @endforeach 9 |
10 | @lang('crud::buttons.back') 11 | 12 | 13 | @include('crud::fields.assets', compact($crud_js, $crud_css)) -------------------------------------------------------------------------------- /resources/views/row.blade.php: -------------------------------------------------------------------------------- 1 |
2 | @foreach($entry->fields() as $field) 3 | 4 | @endforeach 5 | 10 | 11 | -------------------------------------------------------------------------------- /resources/views/scaffold/bootstrap3-form.blade.php: -------------------------------------------------------------------------------- 1 | @extends('crud::scaffold.bootstrap3-layout') 2 | 3 | @section('content') 4 | @crudentry($crud) 5 | @endsection -------------------------------------------------------------------------------- /resources/views/scaffold/bootstrap3-layout.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ config('app.name') }} 9 | 10 | 11 | 49 | 50 | 51 |
52 | @if (Route::has('login')) 53 | 57 | @endif 58 | 59 |
60 |
61 |
62 |
63 | @yield('content') 64 |
65 |
66 |
67 |
68 |
69 | 70 | 71 | @yield('crud-styles') 72 | @yield('crud-scripts') 73 | 74 | 75 | -------------------------------------------------------------------------------- /resources/views/scaffold/bootstrap3-table.blade.php: -------------------------------------------------------------------------------- 1 | @extends('crud::scaffold.bootstrap3-layout') 2 | 3 | @section('content') 4 | @crudtable($crud) 5 | @endsection -------------------------------------------------------------------------------- /resources/views/table.blade.php: -------------------------------------------------------------------------------- 1 |

{{ $title }} {{ trans('crud::buttons.create') }}

2 | 3 | @if($is_empty === false) 4 |

5 | {{ trans_choice('crud::table.count_results', $count) }}
6 |

7 | 8 |
9 |
{!! $field->getTableValue() !!} 6 | @foreach($actions as $action) 7 | {{ $action['value'] }} 8 | @endforeach 9 |
10 | 11 | 12 | @foreach($columns as $column) 13 | 14 | @endforeach 15 | 16 | 17 | 18 | 19 | @foreach($entries as $entry) 20 | {!! $entry->crudEntry()->row() !!} 21 | @endforeach 22 | 23 |
{{ $column }}Actions
24 | 25 | 26 | {!! $pagination->links() !!} 27 | @else 28 |
29 |

30 | {{ trans_choice('crud::table.count_results', 0) }}
31 | {{ trans('crud::buttons.create') }} 32 |

33 |
34 | @endif 35 | -------------------------------------------------------------------------------- /src/Console/CrudControllerMakeCommand.php: -------------------------------------------------------------------------------- 1 | argument('model')); 52 | $rootNamespace = $this->laravel->getNamespace(); 53 | 54 | if (Str::contains($name, '/')) 55 | { 56 | $name = str_replace('/', '\\', $name); 57 | } 58 | 59 | if (Str::startsWith($name, $rootNamespace)) 60 | { 61 | return '\\' . $name; 62 | } 63 | 64 | return '\\' . $rootNamespace . $name; 65 | } 66 | 67 | /** 68 | * @return array 69 | */ 70 | protected function getArguments() 71 | { 72 | return array_merge(parent::getArguments(), [ 73 | ['model', InputArgument::REQUIRED, 'The name of the model, for example, "Post" or "App/Post"'], 74 | ]); 75 | } 76 | 77 | /** 78 | * @param string $name 79 | * @return string 80 | */ 81 | protected function buildClass($name) 82 | { 83 | $namespace = $this->getNamespace($name); 84 | 85 | $modelName = $this->getModelName(); 86 | $modelClass = $modelName . '::class'; 87 | 88 | $class = parent::buildClass($name); 89 | $class = str_replace("use {$namespace}\Controller;\n", '', $class); 90 | $class = str_replace('DummyModelClass', $modelClass, $class); 91 | $class = str_replace('DummyModel', $modelName, $class); 92 | 93 | return $class; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Console/stubs/controller.stub: -------------------------------------------------------------------------------- 1 | $crud]); 21 | } 22 | 23 | /** 24 | * Display the CRUD create form. 25 | * 26 | * @param void 27 | * @return \Illuminate\Http\Response 28 | */ 29 | public function create() 30 | { 31 | $crud = crud_entry(new DummyModel); 32 | 33 | return view('crud::scaffold.bootstrap3-form', ['crud' => $crud]); 34 | } 35 | 36 | /** 37 | * Store the CRUD entry. 38 | * 39 | * @param \Illuminate\Http\Request $request 40 | * @return \Illuminate\Http\Response 41 | */ 42 | public function store(Request $request) 43 | { 44 | $crud = new DummyModel; 45 | $validator = $crud->crudEntry()->getValidator(); 46 | 47 | $validator->validateRequest($request); 48 | 49 | if ($validator->passes()) 50 | { 51 | $validator->save(); 52 | } 53 | 54 | return $validator->redirect(); 55 | } 56 | 57 | /** 58 | * Show the form for editing the current CRUD entry. 59 | * 60 | * @param int $id 61 | * @return \Illuminate\Http\Response 62 | */ 63 | public function edit($id) 64 | { 65 | $crud = crud_entry(DummyModel::findOrFail($id)); 66 | 67 | return view('crud::scaffold.bootstrap3-form', ['crud' => $crud]); 68 | } 69 | 70 | /** 71 | * Update the CRUD entry. 72 | * 73 | * @param \Illuminate\Http\Request $request 74 | * @param int $id 75 | * @return \Illuminate\Http\Response 76 | */ 77 | public function update(Request $request, $id) 78 | { 79 | $model = DummyModel::findOrFail($id); 80 | $validator = $model->crudEntry()->getValidator(); 81 | 82 | $validator->validateRequest($request); 83 | 84 | if ($validator->passes()) 85 | { 86 | $validator->save(); 87 | } 88 | 89 | return $validator->redirect(); 90 | } 91 | 92 | /** 93 | * Destroy the current CRUD entry. 94 | * 95 | * @param int $id 96 | * @param string $csrf 97 | * @return \Illuminate\Http\Response 98 | */ 99 | public function destroy($id, $csrf) 100 | { 101 | if (csrf_token() != $csrf) 102 | { 103 | abort(403); 104 | } 105 | 106 | $model = DummyModel::findOrFail($id); 107 | 108 | $model->delete(); 109 | 110 | return redirect()->back(); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Crud.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) 23 | { 24 | $this->commands([ 25 | CrudControllerMakeCommand::class 26 | ]); 27 | 28 | $this->publishes([ 29 | __DIR__ . '/../resources/lang/' => resource_path('lang/vendor/crud'), 30 | ], 'crud'); 31 | 32 | $this->publishes([ 33 | __DIR__ . '/../resources/views/' => resource_path('views/vendor/crud'), 34 | ], 'crud'); 35 | 36 | $this->publishes([ 37 | __DIR__ . '/../resources/assets/' => public_path('vendor/crud'), 38 | ], 'crud'); 39 | } 40 | 41 | if ($this->app->runningUnitTests()) 42 | { 43 | $this->loadViewsFrom(__DIR__ . '/../resources/views/', 'crud'); 44 | $this->loadTranslationsFrom(__DIR__ . '/../resources/lang/', 'crud'); 45 | } 46 | else 47 | { 48 | $this->loadViewsFrom(resource_path('views/vendor/crud'), 'crud'); 49 | $this->loadTranslationsFrom(resource_path('lang/vendor/crud'), 'crud'); 50 | } 51 | 52 | Blade::directive('crudtable', function($expression) { 53 | return ""; 54 | }); 55 | 56 | Blade::directive('crudentry', function($expression) { 57 | return ""; 58 | }); 59 | } 60 | 61 | /** 62 | * @param void 63 | * @return void 64 | */ 65 | public function register() 66 | { 67 | $this->app->bind('Akibatech\Crud\Crud', Crud::class); 68 | $this->app->alias('Akibatech\Crud\Crud', 'crud'); 69 | 70 | $loader = \Illuminate\Foundation\AliasLoader::getInstance(); 71 | $loader->alias('Crud', 'Akibatech\Crud\CrudFacade'); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Exceptions/CrudException.php: -------------------------------------------------------------------------------- 1 | date_format = $format; 51 | 52 | return $this; 53 | } 54 | 55 | /** 56 | * @param string|Carbon $date 57 | * @return self 58 | * @throws InvalidArgumentException 59 | */ 60 | public function withMinDate($date) 61 | { 62 | if ($date instanceof Carbon) 63 | { 64 | $this->date_min = $date->format($this->date_format); 65 | } 66 | else if (is_string($date)) 67 | { 68 | $this->date_min = $date; 69 | } 70 | else 71 | { 72 | throw new InvalidArgumentException("Min date must be a string or a Carbon instance."); 73 | } 74 | 75 | return $this; 76 | } 77 | 78 | /** 79 | * @param string|Carbon $date 80 | * @return self 81 | * @throws InvalidArgumentException 82 | */ 83 | public function withMaxDate($date) 84 | { 85 | if ($date instanceof Carbon) 86 | { 87 | $this->date_max = $date->format($this->date_format); 88 | } 89 | else if (is_string($date)) 90 | { 91 | $this->date_max = $date; 92 | } 93 | else 94 | { 95 | throw new InvalidArgumentException("Max date must be a string or a Carbon instance."); 96 | } 97 | 98 | return $this; 99 | } 100 | 101 | /** 102 | * @param void 103 | * @return array 104 | */ 105 | public function getViewVariables() 106 | { 107 | $locale = config('app.locale'); 108 | 109 | return [ 110 | 'date_format' => self::dateFormatJs($this->date_format), 111 | 'date_min' => $this->date_min, 112 | 'date_max' => $this->date_max, 113 | 'date_locale' => $locale != 'en' ? $locale : false 114 | ]; 115 | } 116 | 117 | /** 118 | * @param Validator $validator 119 | * @return Validator 120 | */ 121 | public function beforeValidation(Validator $validator) 122 | { 123 | $rules = []; 124 | 125 | $rules[] = 'date_format:' . $this->date_format; 126 | 127 | if (!is_null($this->date_min)) 128 | { 129 | $date = Carbon::createFromFormat($this->date_format, $this->date_min)->subDay()->format($this->date_format); 130 | $rules[] = 'after:' . $date; 131 | } 132 | 133 | if (!is_null($this->date_max)) 134 | { 135 | $date = Carbon::createFromFormat($this->date_format, $this->date_max)->addDay()->format($this->date_format); 136 | $rules[] = 'before:' . $date; 137 | } 138 | 139 | $this->mergeRules($validator, $rules); 140 | 141 | return $validator; 142 | } 143 | 144 | /** 145 | * {@inheritdoc} 146 | */ 147 | public function getCss() 148 | { 149 | return [ 150 | 'vendor/crud/fields/datepicker/bootstrap-datepicker3.min.css', 151 | ]; 152 | } 153 | 154 | /** 155 | * {@inheritdoc} 156 | */ 157 | public function getScripts() 158 | { 159 | $locale = config('app.locale'); 160 | $base = ['vendor/crud/fields/datepicker/bootstrap-datepicker.min.js']; 161 | $extend = []; 162 | 163 | if ($locale != 'fr') 164 | { 165 | $extend[] = "vendor/crud/fields/datepicker/bootstrap-datepicker.$locale.min.js"; 166 | } 167 | 168 | 169 | return array_merge($base, $extend); 170 | } 171 | 172 | /** 173 | * Convert a PHP date format to a JS date format. 174 | * 175 | * @param string $format 176 | * @return string 177 | */ 178 | public static function dateFormatJs($format) 179 | { 180 | $replacements = [ 181 | 'd' => 'dd', 182 | 'j' => 'd', 183 | 'D' => 'D', 184 | 'l' => 'DD', 185 | 'm' => 'mm', 186 | 'n' => 'm', 187 | 'M' => 'M', 188 | 'F' => 'MM', 189 | 'y' => 'yy', 190 | 'Y' => 'yyyy', 191 | ]; 192 | 193 | foreach ($replacements as $old => $new) 194 | { 195 | $format = preg_replace("#($old){1}#", $new, $format); 196 | } 197 | 198 | return $format; 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/Fields/EmailField.php: -------------------------------------------------------------------------------- 1 | addRule('email'); 27 | } 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function getViewName() 33 | { 34 | return 'crud::fields.email'; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Fields/FileUploadField.php: -------------------------------------------------------------------------------- 1 | store($this->path, $this->disk); 49 | 50 | $this->fields->getEntry()->getModel()->setAttribute($this->getIdentifier(), $value); 51 | } 52 | 53 | return $this; 54 | } 55 | 56 | /** 57 | * @param void 58 | * @return string 59 | */ 60 | public function getTableValue() 61 | { 62 | $value = $this->getValue(); 63 | 64 | return empty($value) ? '' : sprintf('%s', url($value), basename($value)); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Fields/RadioField.php: -------------------------------------------------------------------------------- 1 | $this->getOptions(), 36 | 'default_option' => $this->getDefaultOption() 37 | ]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Fields/SelectRelationField.php: -------------------------------------------------------------------------------- 1 | getValue(), 40); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Fields/TinymceField.php: -------------------------------------------------------------------------------- 1 | getValue()), 40); 31 | } 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function getScripts() 37 | { 38 | return [ 39 | 'vendor/crud/fields/tinymce/tinymce.min.js', 40 | ]; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Services/CrudEntry.php: -------------------------------------------------------------------------------- 1 | model = $model; 49 | $this->manager = $model->getCrudManager()->setEntry($this); 50 | $this->fields = $model->getCrudFields()->setEntry($this); 51 | 52 | if ($this->fields->count() === 0) 53 | { 54 | throw new NoFieldsException(get_class($model) . ' has no fields.'); 55 | } 56 | 57 | if (!app()->runningUnitTests() && !app()->runningInConsole()) 58 | { 59 | $this->fields->hydrateErrorsFromSession(); 60 | $this->fields->hydrateFieldsFromSession(); 61 | } 62 | } 63 | else 64 | { 65 | throw new InvalidModelException("The model must use Crudable trait."); 66 | } 67 | } 68 | 69 | /** 70 | * @param void 71 | * @return \Generator 72 | */ 73 | public function fields() 74 | { 75 | foreach ($this->fields->loop() as $field) 76 | { 77 | if ($field->isDisplayedInColumns()) 78 | { 79 | yield $field; 80 | } 81 | } 82 | } 83 | 84 | /** 85 | * @param void 86 | * @return \Generator 87 | */ 88 | public function formFields() 89 | { 90 | foreach ($this->fields->loop() as $field) 91 | { 92 | yield $field; 93 | } 94 | } 95 | 96 | /** 97 | * Get the entry ID. 98 | * 99 | * @param void 100 | * @return int|mixed 101 | */ 102 | public function getId() 103 | { 104 | return $this->model->getAttribute($this->model->getKeyName()); 105 | } 106 | 107 | /** 108 | * @param void 109 | * @return string 110 | */ 111 | public function row() 112 | { 113 | $actions = [ 114 | [ 115 | 'value' => trans('crud::buttons.edit'), 116 | 'class' => 'btn btn-primary btn-xs', 117 | 'uri' => $this->manager->getActionRoute('edit') 118 | ], 119 | [ 120 | 'value' => trans('crud::buttons.destroy'), 121 | 'class' => 'btn btn-danger btn-xs', 122 | 'uri' => $this->manager->getActionRoute('destroy') 123 | ], 124 | ]; 125 | 126 | $view = view()->make('crud::row')->with([ 127 | 'entry' => $this, 128 | 'manager' => $this->manager, 129 | 'actions' => $actions, 130 | ]); 131 | 132 | return $view->render(); 133 | } 134 | 135 | /** 136 | * @param void 137 | * @return CrudFields 138 | */ 139 | public function getFields() 140 | { 141 | return $this->fields; 142 | } 143 | 144 | /** 145 | * Resets the validator. 146 | * 147 | * @param void 148 | * @return self 149 | */ 150 | public function resetValidator() 151 | { 152 | $this->validator = null; 153 | 154 | return $this; 155 | } 156 | 157 | /** 158 | * Shortcut to self validate. 159 | * 160 | * @param array $data 161 | * @return CrudValidator 162 | */ 163 | public function validate(array $data) 164 | { 165 | return $this->getValidator()->validate($data); 166 | } 167 | 168 | /** 169 | * Gets the validator. 170 | * 171 | * @param void 172 | * @return CrudValidator 173 | */ 174 | public function getValidator() 175 | { 176 | if (is_null($this->validator)) 177 | { 178 | $this->validator = new CrudValidator($this); 179 | } 180 | 181 | return $this->validator; 182 | } 183 | 184 | /** 185 | * Saves the model. 186 | * 187 | * @param void 188 | * @return bool 189 | */ 190 | public function save() 191 | { 192 | foreach ($this->fields->loop() as $field) 193 | { 194 | $field->beforeSave(); 195 | } 196 | 197 | return $this->model->save(); 198 | } 199 | 200 | /** 201 | * @param void 202 | * @return string 203 | */ 204 | public function __toString() 205 | { 206 | return $this->form(); 207 | } 208 | 209 | /** 210 | * @param void 211 | * @return string 212 | */ 213 | public function form() 214 | { 215 | $is_new = !$this->getModel()->exists; 216 | $name = $this->getManager()->getName(); 217 | 218 | $with = [ 219 | 'back_url' => $this->manager->getActionRoute('index'), 220 | 'entry' => $this, 221 | 'manager' => $this->manager, 222 | 'errors' => $this->fields->getErrors(), 223 | 'old' => $this->fields->getOldInput(), 224 | 'crud_js' => $this->fields->getFieldsScripts(), 225 | 'crud_css' => $this->fields->getFieldsCss(), 226 | 'multipart' => $this->fields->getMultipart() ? ' enctype="multipart/form-data"' : '' 227 | ]; 228 | 229 | if ($is_new) 230 | { 231 | $view_name = 'crud::form-create'; 232 | $method = $this->manager->getActionMethod('store'); 233 | 234 | $with += [ 235 | 'title' => trans('crud::form.create_title', ['name' => $name]), 236 | 'form_url' => $this->manager->getActionRoute('store'), 237 | 'form_method' => $method, 238 | 'csrf_field' => csrf_field(), 239 | 'method_field' => method_field($method), 240 | ]; 241 | } 242 | else 243 | { 244 | $view_name = 'crud::form-update'; 245 | $method = $this->manager->getActionMethod('update'); 246 | 247 | $with += [ 248 | 'title' => trans('crud::form.update_title', ['name' => $name]), 249 | 'form_url' => $this->manager->getActionRoute('update'), 250 | 'form_method' => $method, 251 | 'csrf_field' => csrf_field(), 252 | 'method_field' => method_field($method), 253 | ]; 254 | } 255 | 256 | $view = View::make($view_name)->with($with); 257 | 258 | return $view->render(); 259 | } 260 | 261 | /** 262 | * Get the entry's model. 263 | * 264 | * @param void 265 | * @return Crudable|Model 266 | */ 267 | public function getModel() 268 | { 269 | return $this->model; 270 | } 271 | 272 | /** 273 | * @param void 274 | * @return CrudManager 275 | */ 276 | public function getManager() 277 | { 278 | return $this->manager; 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /src/Services/CrudTable.php: -------------------------------------------------------------------------------- 1 | class = $crudable; 52 | $crudable = new $crudable; 53 | } 54 | else 55 | { 56 | if (array_key_exists(Crudable::class, class_uses($crudable, true))) 57 | { 58 | $this->class = get_class($crudable); 59 | } 60 | } 61 | 62 | $this->fields = $crudable->getCrudFields(); 63 | $this->manager = $crudable->getCrudManager(); 64 | 65 | $this->hydrateEntries(); 66 | } 67 | 68 | /** 69 | * @param void 70 | * @return self 71 | */ 72 | protected function hydrateEntries() 73 | { 74 | $class = $this->class; 75 | 76 | $this->paginator = $class::latest()->paginate($this->manager->getPerPage()); 77 | $this->entries = $this->paginator->getCollection(); 78 | 79 | return $this; 80 | } 81 | 82 | /** 83 | * @param void 84 | * @return string 85 | */ 86 | public function getClass() 87 | { 88 | return $this->class; 89 | } 90 | 91 | /** 92 | * @param void 93 | * @return string 94 | */ 95 | public function table() 96 | { 97 | $view = View::make('crud::table')->with([ 98 | 'create_url' => $this->manager->getActionRoute('create'), 99 | 'title' => trans('crud::table.title', ['name' => $this->manager->getPluralizedName()]), 100 | 'count' => $this->paginator->total(), 101 | 'is_empty' => $this->paginator->total() === 0, 102 | 'pagination' => $this->paginator, 103 | 'entries' => $this->entries->all(), 104 | 'columns' => $this->fields->columns() 105 | ]); 106 | 107 | return $view->render(); 108 | } 109 | 110 | /** 111 | * @param void 112 | * @return string 113 | */ 114 | public function __toString() 115 | { 116 | return $this->table(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/Services/CrudValidator.php: -------------------------------------------------------------------------------- 1 | setEntry($entry); 40 | } 41 | 42 | /** 43 | * Defines the CrudEntry to validate. 44 | * 45 | * @param CrudEntry $entry 46 | * @return self 47 | */ 48 | public function setEntry(CrudEntry $entry) 49 | { 50 | $this->entry = $entry; 51 | 52 | return $this; 53 | } 54 | 55 | /** 56 | * Gets the entry. 57 | * 58 | * @param void 59 | * @return CrudEntry 60 | */ 61 | public function getEntry() 62 | { 63 | return $this->entry; 64 | } 65 | 66 | /** 67 | * @param array $data 68 | * @return self 69 | */ 70 | public function setFormData(array $data) 71 | { 72 | $this->form_data = $data; 73 | 74 | return $this; 75 | } 76 | 77 | /** 78 | * @param Request $request 79 | * @return self 80 | */ 81 | public function validateRequest(Request $request) 82 | { 83 | return $this->validate($request->only($this->getEntry()->getFields()->keys())); 84 | } 85 | 86 | /** 87 | * Validate given array of data against fields rules. 88 | * 89 | * @param array $fields_data 90 | * @return self 91 | */ 92 | public function validate(array $fields_data) 93 | { 94 | $rules = $this->getEntry()->getFields()->validationRules(); 95 | $this->setFormData($fields_data); 96 | 97 | $this->validator = Validator::make($fields_data, $rules); 98 | $this->validator = $this->getEntry()->getFields()->contextualValidationRules($this->validator); 99 | $this->validator->fails(); 100 | 101 | return $this; 102 | } 103 | 104 | /** 105 | * @param void 106 | * @return bool 107 | */ 108 | public function fails() 109 | { 110 | return count($this->validator->failed()) === 0 ? false : true; 111 | } 112 | 113 | /** 114 | * @param void 115 | * @return bool 116 | */ 117 | public function passes() 118 | { 119 | return $this->validator->fails() === false; 120 | } 121 | 122 | /** 123 | * @param void 124 | * @return RedirectResponse 125 | */ 126 | public function redirect() 127 | { 128 | if ($this->fails()) 129 | { 130 | return redirect()->back()->withInput()->withErrors($this->validator); 131 | } 132 | 133 | return redirect($this->entry->getManager()->getActionRoute('index')); 134 | } 135 | 136 | /** 137 | * @param void 138 | * @return bool 139 | * @throws FailedValidationException 140 | */ 141 | public function save() 142 | { 143 | if ($this->fails()) 144 | { 145 | throw new FailedValidationException("Entry has failed its validation and can't be saved."); 146 | } 147 | 148 | $this->entry->getFields()->hydrateFormData($this->form_data); 149 | 150 | return $this->entry->save(); 151 | } 152 | 153 | /** 154 | * @param void 155 | * @return self 156 | */ 157 | protected function resetValidator() 158 | { 159 | $this->validator = null; 160 | $this->form_data = null; 161 | $this->getEntry()->resetValidator(); 162 | 163 | return $this; 164 | } 165 | 166 | /** 167 | * @param void 168 | * @return Validator 169 | */ 170 | public function getValidator() 171 | { 172 | return $this->validator; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/Traits/Crudable.php: -------------------------------------------------------------------------------- 1 | crud_entry)) 36 | { 37 | $this->crud_entry = Crud::entry($this); 38 | } 39 | 40 | return $this->crud_entry; 41 | } 42 | 43 | /** 44 | * @param void 45 | * @return CrudTable 46 | * @throws \Akibatech\Crud\Exceptions\InvalidArgumentException 47 | */ 48 | public function crudTable() 49 | { 50 | if (is_null($this->crud_table)) 51 | { 52 | $this->crud_table = Crud::table($this); 53 | } 54 | 55 | return $this->crud_table; 56 | } 57 | 58 | /** 59 | * Returns model fields configuration. 60 | * 61 | * @param void 62 | * @return CrudFields 63 | */ 64 | abstract public function getCrudFields(); 65 | 66 | /** 67 | * Returns the CRUD configuration. 68 | * 69 | * @param void 70 | * @return CrudManager 71 | */ 72 | public function getCrudManager() 73 | { 74 | return CrudManager::make($this); 75 | } 76 | 77 | /** 78 | * @param void 79 | * @return CrudManager 80 | */ 81 | public static function crudRoutes() 82 | { 83 | return (new static)->getCrudManager()->registerRoutes(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Traits/FieldHandleUpload.php: -------------------------------------------------------------------------------- 1 | disk = $disk; 42 | 43 | return $this; 44 | } 45 | 46 | /** 47 | * @param string $path 48 | * @return self 49 | */ 50 | public function uploadToPath($path = 'uploads') 51 | { 52 | $this->path = $path; 53 | 54 | return $this; 55 | } 56 | 57 | /** 58 | * @param string $types 59 | * @return self 60 | */ 61 | public function withTypes($types) 62 | { 63 | $this->mimes = $types; 64 | 65 | return $this; 66 | } 67 | 68 | /** 69 | * @param int $size 70 | * @return self 71 | */ 72 | public function withMaxSize($size = 2048) 73 | { 74 | $this->max_size = $size; 75 | 76 | return $this; 77 | } 78 | 79 | /** 80 | * @param Validator $validator 81 | * @return Validator 82 | */ 83 | public function beforeValidation(Validator $validator) 84 | { 85 | $rules = []; 86 | $identifier = $this->getIdentifier(); 87 | 88 | if ($this->mimes) 89 | { 90 | $rules[] = 'mimes:' . $this->mimes; 91 | } 92 | 93 | if ($this->max_size) 94 | { 95 | $rules[] = 'max:' . $this->max_size; 96 | } 97 | 98 | if (count($rules) > 0) 99 | { 100 | $validator->sometimes($this->getIdentifier(), $rules, function (Fluent $input) use ($identifier) 101 | { 102 | return is_null($input->{$identifier}) ? false : true; 103 | }); 104 | } 105 | 106 | return $validator; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Traits/FieldHasUiModifiers.php: -------------------------------------------------------------------------------- 1 | label = $name; 43 | 44 | return $this; 45 | } 46 | 47 | /** 48 | * Defines a placeholder for the field. 49 | * 50 | * @param string $placeholder 51 | * @return self 52 | */ 53 | public function withPlaceholder($placeholder) 54 | { 55 | $this->placeholder = $placeholder; 56 | 57 | return $this; 58 | } 59 | 60 | /** 61 | * Appends an help message to the input. 62 | * 63 | * @param string $help 64 | * @return self 65 | */ 66 | public function withHelp($help) 67 | { 68 | $this->help = $help; 69 | 70 | return $this; 71 | } 72 | 73 | /** 74 | * Returns the field's label. 75 | * 76 | * @param void 77 | * @return string 78 | */ 79 | public function getLabel() 80 | { 81 | if (empty($this->label)) 82 | { 83 | return title_case($this->identifier); 84 | } 85 | 86 | return $this->label; 87 | } 88 | 89 | /** 90 | * Returns the field's placeholder. 91 | * 92 | * @param void 93 | * @return string 94 | */ 95 | public function getPlaceholder() 96 | { 97 | if (empty($this->placeholder)) 98 | { 99 | return null; 100 | } 101 | 102 | return $this->placeholder; 103 | } 104 | 105 | /** 106 | * Returns the field's help. 107 | * 108 | * @param void 109 | * @return string 110 | */ 111 | public function getHelp() 112 | { 113 | if (empty($this->help)) 114 | { 115 | return null; 116 | } 117 | 118 | return $this->help; 119 | } 120 | 121 | /** 122 | * @param bool $state 123 | * @return self 124 | */ 125 | public function displayInColumns($state = true) 126 | { 127 | $this->columnize = $state; 128 | 129 | return $this; 130 | } 131 | 132 | /** 133 | * @param void 134 | * @return bool 135 | */ 136 | public function isDisplayedInColumns() 137 | { 138 | return $this->columnize === true; 139 | } 140 | 141 | /** 142 | * Return fields specific scripts files from public folder. 143 | * Example: ['js/field.js'] 144 | * 145 | * @param void 146 | * @return array 147 | */ 148 | public function getScripts() 149 | { 150 | return []; 151 | } 152 | 153 | /** 154 | * Return fields specific stylesheets files from public folder. 155 | * Example: ['css/field.css'] 156 | * 157 | * @param void 158 | * @return array 159 | */ 160 | public function getCss() 161 | { 162 | return []; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/Traits/FieldWithOptions.php: -------------------------------------------------------------------------------- 1 | options = $options; 31 | 32 | $this->addRule('in:' . implode(',', $this->getOptionsKeys())); 33 | 34 | if (!is_null($default)) 35 | { 36 | if (!array_key_exists($default, $options)) 37 | { 38 | throw new InvalidModelException("$default option does not exist in given options."); 39 | } 40 | 41 | $this->default_option = $default; 42 | } 43 | 44 | return $this; 45 | } 46 | 47 | /** 48 | * @param mixed $identifier 49 | * @return bool 50 | */ 51 | public function hasOption($identifier) 52 | { 53 | return array_key_exists($identifier, $this->getOptions()); 54 | } 55 | 56 | /** 57 | * @param void 58 | * @return array 59 | */ 60 | public function getOptions() 61 | { 62 | return $this->options; 63 | } 64 | 65 | /** 66 | * @param void 67 | * @return array 68 | */ 69 | public function getOptionsKeys() 70 | { 71 | return array_keys($this->options); 72 | } 73 | 74 | /** 75 | * @param void 76 | * @return string|null 77 | */ 78 | public function getOption($identifier) 79 | { 80 | return $this->hasOption($identifier) ? $this->options[$identifier] : null; 81 | } 82 | 83 | /** 84 | * @param void 85 | * @return mixed 86 | */ 87 | public function getDefaultOption() 88 | { 89 | return $this->default_option; 90 | } 91 | 92 | /** 93 | * @param void 94 | * @return string|null 95 | */ 96 | public function getTableValue() 97 | { 98 | return $this->getOption($this->getValue()); 99 | } 100 | 101 | /** 102 | * @param void 103 | * @return self 104 | */ 105 | public function beforeSave() 106 | { 107 | if (is_null($this->getValue()) && !is_null($this->default_option)) 108 | { 109 | $this->newValue($this->default_option); 110 | } 111 | 112 | return $this; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Traits/FieldWithRelation.php: -------------------------------------------------------------------------------- 1 | relation = $relation; 33 | 34 | return $this; 35 | } 36 | 37 | /** 38 | * @param void 39 | * @return Relation 40 | */ 41 | public function getRelation() 42 | { 43 | if (empty($this->relation)) 44 | { 45 | throw new InvalidRelationException("{$this->getIdentifier()} does not contain any relation."); 46 | } 47 | 48 | $relation = $this->relation; 49 | 50 | return $this->getFields()->getEntry()->getModel()->$relation(); 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function getOptions() 57 | { 58 | if (is_null($this->options)) 59 | { 60 | $related = get_class($this->getRelation()->getRelated()); 61 | $options = $related::get()->pluck('name', 'id')->toArray(); 62 | 63 | $this->options = $options; 64 | } 65 | 66 | return $this->options; 67 | } 68 | 69 | /** 70 | * @param Validator $validator 71 | * @return Validator 72 | */ 73 | public function beforeValidation(Validator $validator) 74 | { 75 | $related_class = $this->getRelation()->getRelated(); 76 | $related_instance = new $related_class; 77 | $related_table = $related_class->getTable(); 78 | $related_pk = $related_instance->getKeyName(); 79 | 80 | $this->mergeRules($validator, "exists:$related_table,$related_pk"); 81 | 82 | return $validator; 83 | } 84 | 85 | /** 86 | * {@inheritdoc} 87 | */ 88 | public function getTableValue() 89 | { 90 | $relation = $this->relation; 91 | return $this->getFields()->getEntry()->getModel()->$relation->name; 92 | } 93 | 94 | /** 95 | * {@inheritdoc} 96 | */ 97 | public function getViewVariables() 98 | { 99 | return [ 100 | 'options' => $this->getOptions(), 101 | ]; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 | register(\Akibatech\Crud\CrudServiceProvider::class); 22 | 23 | $app->make('Illuminate\Contracts\Console\Kernel')->bootstrap(); 24 | 25 | $app['config']->set('database.default','sqlite'); 26 | $app['config']->set('database.connections.sqlite.database', ':memory:'); 27 | $dir = __DIR__ . '/Fixtures/database/'; 28 | $app->useDatabasePath($dir); 29 | 30 | return $app; 31 | } 32 | 33 | /** 34 | * @return void 35 | */ 36 | public function refreshApplication() 37 | { 38 | $this->app = $this->createApplication(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/CrudFieldsTest.php: -------------------------------------------------------------------------------- 1 | model = new \TestModel\Post(); 35 | $this->fields = $this->model->getCrudFields(); 36 | } 37 | 38 | /** 39 | * @test 40 | */ 41 | public function returns_the_number_of_configured_fields() 42 | { 43 | $this->assertEquals($this->expected_fields_count, $this->fields->count()); 44 | } 45 | 46 | /** 47 | * @test 48 | */ 49 | public function accepts_a_new_field() 50 | { 51 | $this->fields->set(new \Akibatech\Crud\Fields\TextareaField('foo')); 52 | $this->assertEquals(true, $this->fields->has('foo')); 53 | } 54 | 55 | /** 56 | * @test 57 | */ 58 | public function accepts_many_new_fields_at_once() 59 | { 60 | $this->fields->add([ 61 | new \Akibatech\Crud\Fields\TextareaField('foo'), 62 | new \Akibatech\Crud\Fields\TextareaField('bar') 63 | ]); 64 | 65 | $this->assertTrue($this->fields->has('foo')); 66 | $this->assertTrue($this->fields->has('bar')); 67 | $this->assertFalse($this->fields->has('baz')); 68 | } 69 | 70 | /** 71 | * @test 72 | * @expectedException \Akibatech\Crud\Exceptions\DuplicateFieldIdentifierException 73 | */ 74 | public function throws_an_exception_when_adding_many_fields_with_the_same_identifier() 75 | { 76 | $this->fields->add([ 77 | new \Akibatech\Crud\Fields\TextField('title'), 78 | new \Akibatech\Crud\Fields\TextField('title') 79 | ]); 80 | } 81 | 82 | /** 83 | * @test 84 | */ 85 | public function returns_a_field_when_calling_it_with_its_identifier() 86 | { 87 | $field = $this->fields->get('title'); 88 | 89 | $this->assertInstanceOf(Akibatech\Crud\Fields\TextField::class, $field); 90 | } 91 | 92 | /** 93 | * @test 94 | */ 95 | public function returns_all_field_when_trying_to_get_without_identifier() 96 | { 97 | $fields = $this->fields->get(); 98 | 99 | $this->assertTrue(is_array($fields)); 100 | $this->assertEquals($this->expected_fields_count, count($fields)); 101 | } 102 | 103 | /** 104 | * @test 105 | */ 106 | public function returns_null_when_getting_an_unknow_identifier() 107 | { 108 | $field = $this->fields->get('qux'); 109 | 110 | $this->assertNull($field); 111 | } 112 | 113 | /** 114 | * @test 115 | */ 116 | public function fields_loop_returns_all_fields() 117 | { 118 | $fields = []; 119 | 120 | foreach ($this->fields->loop() as $field) 121 | { 122 | $fields[] = $field; 123 | } 124 | 125 | $this->assertEquals($this->expected_fields_count, count($fields)); 126 | } 127 | 128 | /** 129 | * @test 130 | */ 131 | public function fields_columns_returns_exact_labels() 132 | { 133 | $labels = []; 134 | 135 | foreach ($this->fields->columns() as $label) 136 | { 137 | $labels[] = $label; 138 | } 139 | 140 | $this->assertEquals('Title', $labels[0]); 141 | $this->assertEquals('Introduction', $labels[1]); 142 | $this->assertEquals('Content', $labels[2]); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /tests/CrudTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(\Akibatech\Crud\Services\CrudEntry::class, \Akibatech\Crud\Crud::entry('\TestModel\Post')); 35 | } 36 | 37 | /** 38 | * @test 39 | */ 40 | public function it_returns_table_from_string() 41 | { 42 | $this->assertInstanceOf(\Akibatech\Crud\Services\CrudTable::class, \Akibatech\Crud\Crud::table('\TestModel\Post')); 43 | } 44 | 45 | /** 46 | * @test 47 | * @depends it_returns_entry_from_string 48 | */ 49 | public function it_returns_entry_from_instance() 50 | { 51 | $this->assertInstanceOf(\Akibatech\Crud\Services\CrudEntry::class, \Akibatech\Crud\Crud::entry(new \TestModel\Post())); 52 | } 53 | 54 | /** 55 | * @test 56 | * @depends it_returns_table_from_string 57 | */ 58 | public function it_returns_table_from_instance() 59 | { 60 | $this->assertInstanceOf(\Akibatech\Crud\Services\CrudTable::class, \Akibatech\Crud\Crud::table(new \TestModel\Post())); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/CrudValidatorTest.php: -------------------------------------------------------------------------------- 1 | model = new \TestModel\Post(); 30 | } 31 | 32 | /** 33 | * @test 34 | */ 35 | public function instance_creation() 36 | { 37 | $validator = new \Akibatech\Crud\Services\CrudValidator($this->model->crudEntry()); 38 | 39 | $this->assertInstanceOf(Akibatech\Crud\Services\CrudValidator::class, $validator); 40 | $this->assertEquals($validator->getEntry(), $this->model->crudEntry()); 41 | } 42 | 43 | /** 44 | * @test 45 | * @depends instance_creation 46 | */ 47 | public function entry_returns_self_validator() 48 | { 49 | $validator = $this->model->crudEntry()->getValidator(); 50 | 51 | $this->assertInstanceOf(Akibatech\Crud\Services\CrudValidator::class, $validator); 52 | $this->assertEquals($validator->getEntry(), $this->model->crudEntry()); 53 | } 54 | 55 | /** 56 | * @test 57 | * @depends entry_returns_self_validator 58 | */ 59 | public function passes_valid_data() 60 | { 61 | $validator = $this->model->crudEntry()->validate([ 62 | 'title' => 'My title', 63 | 'introduction' => 'My introduction', 64 | 'content' => 'My content', 65 | 'status' => 'live', 66 | 'category_id' => 1, 67 | 'illustration' => $this->getMockedUploadedFile() 68 | ]); 69 | 70 | $this->assertTrue($validator->passes()); 71 | $this->assertFalse($validator->fails()); 72 | } 73 | 74 | /** 75 | * @test 76 | * @depends passes_valid_data 77 | */ 78 | public function fails_invalid_data() 79 | { 80 | $validator = $this->model->crudEntry()->validate([ 81 | 'title' => 'My title', 82 | 'introduction' => 'My intro', 83 | 'content' => 'My content', 84 | 'status' => 'draft', 85 | 'category_id' => 1, 86 | // Error here 87 | 'illustration' => $this->getMockedUploadedFile(['guessExtension' => 'pdf']) 88 | ]); 89 | 90 | $this->assertFalse($validator->passes()); 91 | $this->assertTrue($validator->fails()); 92 | } 93 | 94 | /** 95 | * @test 96 | * @depends fails_invalid_data 97 | */ 98 | public function hydrates_fields_on_correct_validation() 99 | { 100 | $this->model->crudEntry()->validate([ 101 | 'title' => 'My title', 102 | 'introduction' => 'My introduction', 103 | 'content' => 'My content', 104 | 'status' => 'draft', 105 | 'category_id' => 1, 106 | 'illustration' => $this->getMockedUploadedFile() 107 | ])->save(); 108 | 109 | $this->assertEquals('My title', $this->model->crudEntry()->getFields()->get('title')->getValue()); 110 | $this->assertEquals('123.jpeg', $this->model->crudEntry()->getFields()->get('illustration')->getValue()); 111 | } 112 | 113 | /** 114 | * @test 115 | * @depends hydrates_fields_on_correct_validation 116 | * @expectedException \Akibatech\Crud\Exceptions\FailedValidationException 117 | */ 118 | public function throws_exception_when_trying_to_save_failed_validation() 119 | { 120 | $this->model->crudEntry()->validate([ 121 | 'title' => 'My title', 122 | ])->save(); 123 | } 124 | 125 | /** 126 | * @param array $options 127 | * @return \Mockery\MockInterface 128 | */ 129 | private function getMockedUploadedFile($options = []) 130 | { 131 | return Mockery::mock('\Illuminate\Http\UploadedFile', 132 | array_merge([ 133 | 'store' => '123.jpeg', 134 | 'isValid' => true, 135 | 'getPath' => '/', 136 | 'guessExtension' => 'jpeg', 137 | 'getSize' => 2048 138 | ], $options)); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /tests/CrudableModelTest.php: -------------------------------------------------------------------------------- 1 | model = new \TestModel\Post(); 25 | } 26 | 27 | /** 28 | * @test 29 | */ 30 | public function it_returns_crud_fields() 31 | { 32 | $this->assertInstanceOf('Akibatech\Crud\Services\CrudFields', $this->model->getCrudFields()); 33 | } 34 | 35 | /** 36 | * @test 37 | */ 38 | public function it_returns_crud_manager() 39 | { 40 | $this->assertInstanceOf('Akibatech\Crud\Services\CrudManager', $this->model->getCrudManager()); 41 | } 42 | 43 | /** 44 | * @test 45 | */ 46 | public function it_returns_crud_table() 47 | { 48 | $this->assertInstanceOf(\Akibatech\Crud\Services\CrudTable::class, $this->model->crudTable()); 49 | } 50 | 51 | /** 52 | * @test 53 | */ 54 | public function it_returns_crud_entry() 55 | { 56 | $this->assertInstanceOf(\Akibatech\Crud\Services\CrudEntry::class, $this->model->crudEntry()); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/Fields/DatePickerFieldTest.php: -------------------------------------------------------------------------------- 1 | model = new \TestModel\Post(); 35 | $this->entry = $this->model->crudEntry(); 36 | $this->field = $this->entry->getFields()->get('published_at'); 37 | } 38 | 39 | /** 40 | * @test 41 | */ 42 | public function it_returns_valid_js_format() 43 | { 44 | $this->assertEquals('yyyy-mm-dd', \Akibatech\Crud\Fields\DatePickerField::dateFormatJs('Y-m-d')); 45 | $this->assertEquals('dd/mm/yy', \Akibatech\Crud\Fields\DatePickerField::dateFormatJs('d/m/y')); 46 | $this->assertEquals('DD d MM yyyy', \Akibatech\Crud\Fields\DatePickerField::dateFormatJs('l j F Y')); 47 | } 48 | 49 | /** 50 | * @test 51 | */ 52 | public function it_returns_its_options_variables() 53 | { 54 | $format = 'Y-m-d'; 55 | $options = $this->field->getViewVariables(); 56 | 57 | $this->assertEquals([ 58 | 'date_format' => \Akibatech\Crud\Fields\DatePickerField::dateFormatJs($format), 59 | 'date_min' => \Carbon\Carbon::now()->format($format), 60 | 'date_max' => null, 61 | 'date_locale' => false 62 | ], $options); 63 | } 64 | 65 | /** 66 | * @test 67 | */ 68 | public function it_validates_valid_data() 69 | { 70 | $validation = $this->entry->validate(['published_at' => \Carbon\Carbon::now()->format('Y-m-d')]); 71 | 72 | $this->assertArrayNotHasKey('published_at', $validation->getValidator()->failed()); 73 | 74 | } 75 | 76 | /** 77 | * @test 78 | */ 79 | public function it_not_validates_invalid_data() 80 | { 81 | $validation = $this->entry->validate(['published_at' => \Carbon\Carbon::now()->subDays(2)->format('Y-m-d')]); 82 | 83 | $this->assertArrayHasKey('published_at', $validation->getValidator()->failed()); 84 | $this->assertArrayHasKey('After', $validation->getValidator()->failed()['published_at']); 85 | 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/Fields/FieldHandleUploadTest.php: -------------------------------------------------------------------------------- 1 | model = new \TestModel\Post(); 35 | $this->entry = $this->model->crudEntry(); 36 | $this->field = $this->entry->getFields()->get('illustration'); 37 | } 38 | 39 | /** 40 | * @test 41 | */ 42 | public function it_handles_new_value_from_uploaded_file() 43 | { 44 | $file = Mockery::mock( 45 | '\Illuminate\Http\UploadedFile', 46 | [ 47 | 'store' => 'uploads/123.jpeg' 48 | ] 49 | ); 50 | 51 | $this->field->newValue($file); 52 | 53 | $this->assertEquals('uploads/123.jpeg', $this->field->getValue()); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/Fields/FieldTest.php: -------------------------------------------------------------------------------- 1 | model = new \TestModel\Post(); 35 | $this->entry = $this->model->crudEntry(); 36 | $this->field = $this->entry->getFields()->get('title'); 37 | } 38 | 39 | /** 40 | * @test 41 | */ 42 | public function field_returns_its_identifier() 43 | { 44 | $this->assertEquals('title', $this->field->getIdentifier()); 45 | } 46 | 47 | /** 48 | * @test 49 | */ 50 | public function field_returns_its_label() 51 | { 52 | $this->assertEquals('Title', $this->field->getLabel()); 53 | $this->field->withLabel('My label'); 54 | $this->assertEquals('My label', $this->field->getLabel()); 55 | } 56 | 57 | /** 58 | * @test 59 | */ 60 | public function field_returns_its_placeholder() 61 | { 62 | $this->assertEquals('Title of the post', $this->field->getPlaceholder()); 63 | $this->field->withPlaceholder('Foo'); 64 | $this->assertEquals('Foo', $this->field->getPlaceholder()); 65 | } 66 | 67 | /** 68 | * @test 69 | */ 70 | public function field_returns_its_help() 71 | { 72 | $this->assertNull($this->field->getHelp()); 73 | $this->field->withHelp('This is the help'); 74 | $this->assertEquals('This is the help', $this->field->getHelp()); 75 | } 76 | 77 | /** 78 | * @test 79 | */ 80 | public function field_returns_its_forms() 81 | { 82 | $this->field->withLabel('Foo label')->withHelp('Foo help')->withPlaceholder('Strange placeholder'); 83 | $form = $this->field->form(); 84 | 85 | $this->assertContains('Foo label', $form); 86 | $this->assertContains('Foo help', $form); 87 | $this->assertContains('Strange placeholder', $form); 88 | } 89 | 90 | /** 91 | * @test 92 | */ 93 | public function field_returns_its_assets() 94 | { 95 | $this->field = $this->entry->getFields()->get('content'); 96 | 97 | $this->assertContains('tinymce.init', $this->field->form()); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tests/Fields/FieldWithOptionsTest.php: -------------------------------------------------------------------------------- 1 | model = new \TestModel\Post(); 35 | $this->entry = $this->model->crudEntry(); 36 | $this->field = $this->entry->getFields()->get('status'); 37 | } 38 | 39 | /** 40 | * @test 41 | */ 42 | public function it_returns_its_options() 43 | { 44 | $this->assertEquals([ 45 | 'draft' => 'Draft', 46 | 'live' => 'Live' 47 | ], $this->field->getOptions()); 48 | } 49 | 50 | /** 51 | * @test 52 | */ 53 | public function it_verify_its_options() 54 | { 55 | $this->assertTrue($this->field->hasOption('live')); 56 | $this->assertFalse($this->field->hasOption('foo')); 57 | } 58 | 59 | /** 60 | * @test 61 | */ 62 | public function it_returns_its_options_keys() 63 | { 64 | $this->assertEquals(['draft', 'live'], $this->field->getOptionsKeys()); 65 | } 66 | 67 | /** 68 | * @test 69 | */ 70 | public function it_returns_an_option_value() 71 | { 72 | $this->assertEquals('Live', $this->field->getOption('live')); 73 | } 74 | 75 | /** 76 | * @test 77 | */ 78 | public function it_registers_its_own_rule() 79 | { 80 | $this->assertContains('in:draft,live', $this->field->getRules()); 81 | } 82 | 83 | /** 84 | * @test 85 | */ 86 | public function it_returns_the_correct_table_value() 87 | { 88 | $this->assertNull($this->field->getTableValue()); 89 | $this->field->newValue('live'); 90 | $this->assertEquals('Live', $this->field->getTableValue()); 91 | } 92 | 93 | /** 94 | * @test 95 | */ 96 | public function it_saves_the_default_value_when_empty() 97 | { 98 | try { 99 | $this->entry->save(); 100 | } catch (\Illuminate\Database\QueryException $e) { 101 | unset($e); 102 | } 103 | 104 | $this->assertEquals('live', $this->field->getValue()); 105 | } 106 | 107 | /** 108 | * @test 109 | */ 110 | public function it_not_saves_the_default_value_when_not_empty() 111 | { 112 | $this->field->newValue('draft'); 113 | 114 | try { 115 | $this->entry->save(); 116 | } catch (\Illuminate\Database\QueryException $e) { 117 | unset($e); 118 | } 119 | 120 | $this->assertNotEquals('live', $this->field->getValue()); 121 | $this->assertEquals('draft', $this->field->getValue()); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /tests/Fields/FieldWithRelationTest.php: -------------------------------------------------------------------------------- 1 | model = new \TestModel\Post(); 30 | $this->entry = $this->model->crudEntry(); 31 | $this->field = $this->entry->getFields()->get('category_id'); 32 | } 33 | 34 | /** 35 | * @test 36 | */ 37 | public function it_returns_its_options() 38 | { 39 | $this->assertEquals([ 40 | '1' => 'PHP', 41 | '2' => 'Javascript', 42 | '3' => 'Linux' 43 | ], $this->field->getOptions()); 44 | } 45 | 46 | /** 47 | * @test 48 | */ 49 | public function is_displays_correct_relation_value() 50 | { 51 | $this->entry->getFields()->get('category_id')->newValue(1); 52 | $this->assertEquals('PHP', $this->field->getTableValue()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/Fixtures/Models/Category.php: -------------------------------------------------------------------------------- 1 | hasMany(Post::class); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Fixtures/Models/Post.php: -------------------------------------------------------------------------------- 1 | withPlaceholder('Title of the post'), 26 | TextareaField::handle('introduction', 'required|min:3')->withPlaceholder('Short introduction to the post'), 27 | TinymceField::handle('content', 'required|min:3')->withPlaceholder('Your content !'), 28 | FileUploadField::handle('illustration')->withMaxSize(1024 * 1024)->withTypes('jpeg,png'), 29 | SelectRelationField::handle('category_id')->withRelation('category')->withLabel('Category'), 30 | RadioField::handle('status', 'required')->withOptions(['draft' => 'Draft', 'live' => 'Live'], 'live'), 31 | DatePickerField::handle('published_at')->withDateFormat('Y-m-d')->withMinDate(Carbon::now()), 32 | ]); 33 | } 34 | 35 | public function getCrudManager() 36 | { 37 | return CrudManager::make() 38 | ->setNamePrefix('posts') 39 | ->setUriPrefix('crud/posts') 40 | ->setName('Post'); 41 | } 42 | 43 | public function category() 44 | { 45 | return $this->belongsTo(Category::class); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/Fixtures/database/migrations/2016_10_30_000000_create_posts_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('title'); 19 | $table->string('introduction'); 20 | $table->string('content'); 21 | $table->string('illustration')->nullable(); 22 | $table->integer('category_id')->unsigned(); 23 | $table->integer('published_at')->nullable(); 24 | $table->tinyInteger('status')->unsigned()->default(0); 25 | $table->timestamps(); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::dropIfExists('posts'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Fixtures/database/migrations/2016_11_18_000000_create_categories_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('name'); 19 | $table->timestamps(); 20 | }); 21 | 22 | DB::table('categories')->insert([ 23 | ['name' => 'PHP'], 24 | ['name' => 'Javascript'], 25 | ['name' => 'Linux'], 26 | ]); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::dropIfExists('categories'); 37 | } 38 | } 39 | --------------------------------------------------------------------------------