├── updates
└── version.yaml
├── components
├── create
│ └── default.htm
├── update
│ └── default.htm
├── listitems
│ └── default.htm
├── ListItems.php
├── Update.php
└── Create.php
├── LICENSE
├── README.md
└── Plugin.php
/updates/version.yaml:
--------------------------------------------------------------------------------
1 | 1.0.1: First version of CRUD
2 |
--------------------------------------------------------------------------------
/components/create/default.htm:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/components/update/default.htm:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/components/listitems/default.htm:
--------------------------------------------------------------------------------
1 |
3 | {% for item in __SELF__.items %}
4 |
5 | |
6 | {{ item[__SELF__.field] }}
7 | |
8 |
9 |
11 | edit
12 |
13 |
19 | |
20 |
21 | {% endfor %}
22 |
23 |
24 | {% if __SELF__.createPageUrl is not empty %}
25 | New item
26 | {% endif %}
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 inetis.ch
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # About
2 |
3 | Sample OctoberCMS plugin to demonstrate creating CRUD features for existing backend models/controllers within the frontend.
4 |
5 | >**NOTE:** This plugin is NOT production ready. Additional security checks and verification steps MUST be added before implementing on a production site.
6 |
7 | ## Demo
8 | 
9 |
10 | ## Usage
11 | This plugin demonstrates the following three components:
12 |
13 | ### crudList
14 | Displays a list of items and adds ability to delete items when clicking on the delete button.
15 |
16 | Property | Description | Example
17 | --- | :--- | ---
18 | Model | The full class name of the model you want to use (with namespace) | `\Dev\Plugin\Models\User`
19 | Field to display | The name of the property you want to display in the list | `title`
20 | Page Edit Item | The page that contains the crudUpdate component (used by the Edit buttons) | `update.htm`
21 | Page New Item | The page that contain the crudCreate component (used by the New item button) | `create.htm`
22 |
23 | ### crudUpdate
24 | Displays an edit form.
25 |
26 | Property | Description | Example
27 | --- | :--- | ---
28 | Model | The full class name of the model you want to use (with namespace) | `Dev\Plugin\Models\User`
29 | Controller | The full class name of the controller you want to use (with namespace) | `Dev\Plugin\Controllers\Users`
30 | Form context | The context you want to use (see [Backend forms > Field options > Context](https://octobercms.com/docs/backend/forms#form-field-options)) | `frontend`
31 | ID | The primary key of the item you want to edit (can be a URL parameter) | `{{ :id }}`
32 | Form config (optional) | Override the default form configuration file (`fields.yaml`) defined in the controller (`config_form.yaml`) (see [Backend forms > Defining form fields](https://octobercms.com/docs/backend/forms#form-fields) | `$/dev/plugin/models/user/fields.yaml`
33 | Success page | The Page to redirect the user after model has correctly saved | `update.htm`
34 |
35 | ### crudCreate
36 | Displays a create form
37 |
38 | Property | Description | Example
39 | --- | :--- | ---
40 | Model | The full class name of the model you want to use (with namespace) | `Dev\Plugin\Models\User`
41 | Controller | The full class name of the controller you want to use (with namespace) | `Dev\Plugin\Controllers\Users`
42 | Form context | The context you want to use (see [Backend forms > Field options > Context](https://octobercms.com/docs/backend/forms#form-field-options)) | `frontend`
43 | ID | Primary key of the item you want edit
44 | Form config (optional) | Override the default form configuration file (`fields.yaml`) defined in the controller (`config_form.yaml`) (see [Backend forms > Defining form fields](https://octobercms.com/docs/backend/forms#form-fields) | `$/dev/plugin/models/user/fields.yaml`
45 | Success page | The Page to redirect the user after model has correctly saved | `update.htm`
46 |
47 | ## Current Limitations
48 | - Backend widget are not supported (the DOM is generated but without any JS/CSS) - Any PRs are welcome
49 | - No access validation, anybody with access to the routes that these components live on can update your models without authorization or validation.
50 |
51 | ## Author
52 | inetis is a webdesign agency in Vufflens-la-Ville, Switzerland. We love coding and creating powerful apps and sites [see our website](https://inetis.ch).
53 |
--------------------------------------------------------------------------------
/Plugin.php:
--------------------------------------------------------------------------------
1 | 'Frontend CRUD example plugin',
19 | 'description' => 'CRUD auto-generated from existing backend model controller',
20 | 'author' => 'inetis',
21 | 'icon' => 'icon-list-alt',
22 | 'homepage' => 'https://github.com/inetis-ch/oc-CRUD-plugin'
23 | ];
24 | }
25 |
26 | /**
27 | * Registers any front-end components implemented in this plugin.
28 | *
29 | * @return array
30 | */
31 | public function registerComponents()
32 | {
33 | return [
34 | 'Inetis\Crud\Components\Create' => 'crudCreate',
35 | 'Inetis\Crud\Components\ListItems' => 'crudList',
36 | 'Inetis\Crud\Components\Update' => 'crudUpdate',
37 | ];
38 | }
39 |
40 | /**
41 | * Register backend widgets
42 | *
43 | * @return array
44 | */
45 | public function registerFormWidgets()
46 | {
47 | return [
48 | 'Backend\FormWidgets\CodeEditor' => [
49 | 'label' => 'Code editor',
50 | 'code' => 'codeeditor'
51 | ],
52 |
53 | 'Backend\FormWidgets\RichEditor' => [
54 | 'label' => 'Rich editor',
55 | 'code' => 'richeditor'
56 | ],
57 |
58 | 'Backend\FormWidgets\MarkdownEditor' => [
59 | 'label' => 'Markdown editor',
60 | 'code' => 'markdown'
61 | ],
62 |
63 | 'Backend\FormWidgets\FileUpload' => [
64 | 'label' => 'File uploader',
65 | 'code' => 'fileupload'
66 | ],
67 |
68 | 'Backend\FormWidgets\Relation' => [
69 | 'label' => 'Relationship',
70 | 'code' => 'relation'
71 | ],
72 |
73 | 'Backend\FormWidgets\DatePicker' => [
74 | 'label' => 'Date picker',
75 | 'code' => 'datepicker'
76 | ],
77 |
78 | 'Backend\FormWidgets\TimePicker' => [
79 | 'label' => 'Time picker',
80 | 'code' => 'timepicker'
81 | ],
82 |
83 | 'Backend\FormWidgets\ColorPicker' => [
84 | 'label' => 'Color picker',
85 | 'code' => 'colorpicker'
86 | ],
87 |
88 | 'Backend\FormWidgets\DataTable' => [
89 | 'label' => 'Data Table',
90 | 'code' => 'datatable'
91 | ],
92 |
93 | 'Backend\FormWidgets\RecordFinder' => [
94 | 'label' => 'Record Finder',
95 | 'code' => 'recordfinder'
96 | ],
97 |
98 | 'Backend\FormWidgets\Repeater' => [
99 | 'label' => 'Repeater',
100 | 'code' => 'repeater'
101 | ],
102 |
103 | 'Backend\FormWidgets\TagList' => [
104 | 'label' => 'Tag List',
105 | 'code' => 'taglist'
106 | ]
107 | ];
108 | }
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/components/ListItems.php:
--------------------------------------------------------------------------------
1 | 'List items',
21 | 'description' => 'List all items of a model'
22 | ];
23 | }
24 |
25 | public function defineProperties()
26 | {
27 | return [
28 | 'modelClass' => [
29 | 'title' => 'Model',
30 | 'description' => 'Full model name (with namespace)',
31 | 'type' => 'string',
32 | 'required' => true
33 | ],
34 | 'field' => [
35 | 'title' => 'Field to display',
36 | 'type' => 'string',
37 | 'required' => true
38 | ],
39 | 'updatePage' => [
40 | 'title' => "Page Edit item",
41 | 'description' => "Form for edit an item",
42 | 'type' => 'dropdown',
43 | 'required' => true
44 | ],
45 | 'createPage' => [
46 | 'title' => "Page New item",
47 | 'description' => "Form for create a new item",
48 | 'type' => 'dropdown',
49 | 'required' => true
50 | ],
51 | ];
52 | }
53 |
54 | /**
55 | * Dropdown for choose edit page
56 | *
57 | * @return mixed
58 | */
59 | public function getUpdatePageOptions()
60 | {
61 | return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
62 | }
63 |
64 | /**
65 | * Dropdown for choose create page
66 | *
67 | * @return mixed
68 | */
69 | public function getCreatePageOptions()
70 | {
71 | return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
72 | }
73 |
74 | /**
75 | * Display the list
76 | */
77 | public function onRun()
78 | {
79 | $this->fillParameters();
80 |
81 | $this->createPageUrl = $this->controller->pageUrl($this->createPage);
82 | $this->listItems();
83 | }
84 |
85 | /**
86 | * AJAX - delete an item
87 | *
88 | * @return array
89 | */
90 | public function onDelete()
91 | {
92 | $this->fillParameters();
93 | $modelClass = $this->modelClass;
94 |
95 | $model = $modelClass::findOrFail(post('id'));
96 | $model->delete();
97 |
98 | if (post('list_id')) {
99 | return [
100 | '#' . post('list_id') => $this->listItems()
101 | ];
102 | }
103 | }
104 |
105 |
106 | /**
107 | * Trigger exception if a required property is not set
108 | *
109 | * @return array Properties indexed by name
110 | * @throws SystemException
111 | */
112 | private function fillParameters()
113 | {
114 | $properties = $this->defineProperties();
115 | foreach ($data = $this->getProperties() as $key => $value) {
116 | if (!key_exists($key, $properties)) {
117 | continue;
118 | }
119 |
120 | if (empty($value) && $properties[$key]['required']) {
121 | throw new SystemException("Property $key ({$properties[$key]['title']}) can't be empty");
122 | }
123 |
124 | if (property_exists($this, $key)) {
125 | $this->{$key} = $value;
126 | }
127 | }
128 | }
129 |
130 | /**
131 | * Generate list view
132 | *
133 | * @return string
134 | */
135 | protected function listItems()
136 | {
137 | $modelClass = $this->modelClass;
138 |
139 | $this->items = $modelClass::all();
140 | $this->items->each(function ($item) {
141 | $item->crudUpdatePage = $this->controller->pageUrl($this->updatePage, ['id' => $item->id]);
142 | });
143 |
144 | return $this->renderPartial('@default');
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/components/Update.php:
--------------------------------------------------------------------------------
1 | 'Update existing item',
26 | 'description' => 'Display form for update an existing item'
27 | ];
28 | }
29 |
30 | public function defineProperties()
31 | {
32 | return [
33 | 'modelClass' => [
34 | 'title' => 'Model',
35 | 'description' => 'Full model name (with namespace)',
36 | 'type' => 'string',
37 | 'required' => true,
38 | ],
39 | 'controllerClass' => [
40 | 'title' => 'Controller',
41 | 'description' => 'Full controller name (with namespace)',
42 | 'type' => 'string',
43 | 'required' => true,
44 | ],
45 | 'formControllerContext' => [
46 | 'title' => 'Form context',
47 | 'description' => '',
48 | 'type' => 'string',
49 | 'default' => 'frontend',
50 | 'required' => true,
51 | ],
52 | 'itemId' => [
53 | 'title' => 'ID',
54 | 'type' => 'string',
55 | 'default' => '{{ :id }}',
56 | 'required' => true,
57 | ],
58 | 'formConfigFile' => [
59 | 'title' => 'Form config',
60 | 'description' => 'Path to model fields config (by default use the one defined in config_form.yaml) - ex: $/dev/plugin/models/user/fields.yaml',
61 | 'type' => 'string',
62 | 'required' => false,
63 | ],
64 | 'successPage' => [
65 | 'title' => 'Success page',
66 | 'description' => 'Page where redirect user when item was successfully created',
67 | 'type' => 'dropdown',
68 | 'required' => false
69 | ],
70 | ];
71 | }
72 |
73 | /**
74 | * Dropdown for choose edit page
75 | *
76 | * @return mixed
77 | */
78 | public function getSuccessPageOptions()
79 | {
80 | return ['' => 'Nothing'] + Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
81 | }
82 |
83 | public function onRun()
84 | {
85 | $this->fillParameters();
86 | $this->validateModel();
87 |
88 | $formController = new FormController(new $this->controllerClass);
89 |
90 | if (!empty($this->formConfigFile)) {
91 | $formController->setConfig(array_merge(
92 | (array)$formController->getConfig(),
93 | ['form' => $this->formConfigFile]
94 | ));
95 | }
96 |
97 | $formController->update($this->itemId, $this->formControllerContext);
98 |
99 | $this->form = $formController;
100 | }
101 |
102 | /**
103 | * AJAX - Model edit
104 | *
105 | * @return \Illuminate\Http\RedirectResponse
106 | */
107 | public function onUpdate()
108 | {
109 | $this->fillParameters();
110 | $modelClass = $this->modelClass;
111 | $arrayName = class_basename($modelClass);
112 |
113 | $model = $modelClass::findOrFail($this->itemId);
114 |
115 | foreach (post($arrayName) as $property => $value) {
116 | $model->{$property} = $value;
117 | }
118 | $model->save();
119 |
120 | Flash::success('Item correctly edited');
121 |
122 | if (!$this->successPage) {
123 | return;
124 | }
125 |
126 | return Redirect::to($this->controller->pageUrl($this->successPage));
127 | }
128 |
129 | /**
130 | * Trigger exception if a required property is not set
131 | *
132 | * @return array Properties indexed by name
133 | * @throws SystemException
134 | */
135 | private function fillParameters()
136 | {
137 | foreach ($data = $this->getProperties() as $key => $value) {
138 | if (empty($value) && $this->defineProperties()[$key]['required']) {
139 | throw new SystemException('Property ' . $key . ' is can\'t be empty');
140 | }
141 |
142 | if (property_exists($this, $key)) {
143 | $this->{$key} = $value;
144 | }
145 | }
146 | }
147 |
148 | /**
149 | * Trigger exception if model class not exist or if item not exist
150 | *
151 | * @param string $modelName
152 | * @param string $id
153 | *
154 | * @throws SystemException|ModelNotFoundException
155 | */
156 | private function validateModel($modelName = null, $id = null)
157 | {
158 | $modelName = $modelName ?: $this->modelClass;
159 | $id = $id ?: $this->itemId;
160 |
161 | if (!class_exists($modelName)) {
162 | throw new SystemException('Model class ' . $modelName . ' not exist');
163 | }
164 |
165 | $modelName::findOrFail($id);
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/components/Create.php:
--------------------------------------------------------------------------------
1 | 'Create new item',
25 | 'description' => 'Display form for create a new item'
26 | ];
27 | }
28 |
29 | public function defineProperties()
30 | {
31 | return [
32 | 'modelClass' => [
33 | 'title' => 'Model',
34 | 'description' => 'Full model name (with namespace)',
35 | 'type' => 'string',
36 | 'required' => true,
37 | ],
38 | 'controllerClass' => [
39 | 'title' => 'Controller',
40 | 'description' => 'Full controller name (with namespace)',
41 | 'type' => 'string',
42 | 'required' => true,
43 | ],
44 | 'formControllerContext' => [
45 | 'title' => 'Form context',
46 | 'description' => '',
47 | 'type' => 'string',
48 | 'default' => 'frontend',
49 | 'required' => true,
50 | ],
51 | 'formConfigFile' => [
52 | 'title' => 'Form config',
53 | 'description' => 'Path to model fields config (by default use the one defined in config_form.yaml) - ex: $/dev/plugin/models/user/fields.yaml',
54 | 'type' => 'string',
55 | 'required' => false,
56 | ],
57 | 'successPage' => [
58 | 'title' => 'Success page',
59 | 'description' => 'Page where redirect user when item was successfully created',
60 | 'type' => 'dropdown',
61 | 'required' => false
62 | ],
63 | ];
64 | }
65 |
66 | /**
67 | * Dropdown for choose edit page
68 | *
69 | * @return mixed
70 | */
71 | public function getSuccessPageOptions()
72 | {
73 | return ['' => 'Nothing'] + Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
74 | }
75 |
76 | /**
77 | * Display form
78 | */
79 | public function onRun()
80 | {
81 | $this->fillParameters();
82 | $this->validateModel();
83 |
84 | $formController = new FormController(new $this->controllerClass);
85 |
86 | if (!empty($this->formConfigFile)) {
87 | $formController->setConfig(array_merge(
88 | (array)$formController->getConfig(),
89 | ['form' => $this->formConfigFile]
90 | ));
91 | }
92 |
93 | $formController->create($this->formControllerContext);
94 |
95 | $this->page['form'] = $formController;
96 | }
97 |
98 | /**
99 | * AJAX - Model create
100 | *
101 | * @return \Illuminate\Http\RedirectResponse
102 | */
103 | public function onSave()
104 | {
105 | $this->fillParameters();
106 | $modelClass = $this->modelClass;
107 | $arrayName = class_basename($modelClass);
108 |
109 | $model = new $modelClass;
110 | foreach (post($arrayName) as $property => $value) {
111 | $model->{$property} = $value;
112 | }
113 |
114 | $model->save();
115 |
116 | Flash::success('Item correctly created');
117 |
118 | if ($this->successPage) {
119 | return Redirect::to($this->controller->pageUrl($this->successPage));
120 | }
121 | }
122 |
123 |
124 | /**
125 | * Trigger exception if a required property is not set
126 | *
127 | * @return array Properties indexed by name
128 | * @throws SystemException
129 | */
130 | private function fillParameters()
131 | {
132 | foreach ($data = $this->getAllProperties() as $key => $value) {
133 | if (empty($value) && $this->defineProperties()[$key]['required']) {
134 | throw new SystemException('Property ' . $key . ' can\'t be empty');
135 | }
136 |
137 | if (property_exists($this, $key)) {
138 | $this->{$key} = $value;
139 | }
140 | }
141 | }
142 |
143 | /**
144 | * Get all properties (include not defined properties)
145 | *
146 | * @return array
147 | */
148 | private function getAllProperties()
149 | {
150 | $properties = $this->getProperties();
151 |
152 | foreach ($this->defineProperties() as $key => $property) {
153 | if (isset($properties[$key])) {
154 | continue;
155 | }
156 |
157 | if (!isset($property['default']) || starts_with($property['default'], '{{')) {
158 | $properties[$key] = null;
159 | continue;
160 | }
161 |
162 | $properties[$key] = $property['default'];
163 | }
164 |
165 | return $properties;
166 | }
167 |
168 | /**
169 | * Trigger exception if model class not exist or if item not exist
170 | *
171 | * @param string $modelName
172 | *
173 | * @throws SystemException|ModelNotFoundException
174 | */
175 | private function validateModel($modelName = null)
176 | {
177 | $modelName = $modelName ?: $this->modelClass;
178 |
179 | if (!class_exists($modelName)) {
180 | throw new SystemException('Model class "' . $modelName . '" not exist');
181 | }
182 | }
183 |
184 | }
185 |
--------------------------------------------------------------------------------