Setup is disabled
33 |If you think this is a mistake, contact your System Administrator.
34 |If you are the System Administrator see the wiki on how to enable setup.
35 |├── .gitattributes
├── CHANGELOG.md
├── LICENSE
├── README.md
├── composer.json
├── package
├── assets
│ ├── css
│ │ ├── app.css
│ │ ├── lib.css
│ │ ├── setup.css
│ │ └── vue.css
│ ├── fonts
│ │ ├── font-awesome
│ │ │ ├── font-awesome.min.css
│ │ │ ├── fontawesome-webfont.ttf
│ │ │ └── fontawesome-webfont.woff
│ │ └── lato
│ │ │ ├── lato-bold-webfont.woff
│ │ │ ├── lato-italic-webfont.woff
│ │ │ ├── lato-regular-webfont.woff
│ │ │ └── lato.css
│ ├── images
│ │ ├── ajax-loader-bars.gif
│ │ ├── ajax-loader-grey.gif
│ │ ├── ajax-loader.gif
│ │ └── favicon.ico
│ └── js
│ │ ├── app.js
│ │ ├── iframe.js
│ │ ├── lib.js
│ │ └── setup.js
├── config
│ ├── admin.json
│ └── settings.json
├── setup
│ ├── assets
│ │ ├── favicon.ico
│ │ ├── scripts.js
│ │ └── styles.css
│ ├── controllers
│ │ └── ExampleController.php
│ └── views
│ │ ├── example
│ │ └── index.blade.php
│ │ ├── head.blade.php
│ │ ├── help.blade.php
│ │ └── home.blade.php
├── templates
│ ├── controller.txt
│ └── method.txt
└── views
│ ├── help
│ ├── helpers
│ │ ├── code.blade.php
│ │ ├── icon.blade.php
│ │ ├── index.md
│ │ ├── list.blade.php
│ │ └── table.blade.php
│ ├── methods
│ │ ├── comments.blade.php
│ │ ├── contents.blade.php
│ │ ├── index.md
│ │ ├── parameters.blade.php
│ │ ├── run.blade.php
│ │ ├── test.blade.php
│ │ ├── typecasting.blade.php
│ │ ├── typehinting.blade.php
│ │ └── variables.blade.php
│ ├── output
│ │ ├── blade.blade.php
│ │ ├── form.blade.php
│ │ ├── formatting.md
│ │ ├── index.md
│ │ ├── links.blade.php
│ │ ├── markdown.blade.php
│ │ ├── pagination.blade.php
│ │ ├── vue-text.blade.php
│ │ └── vue.vue
│ ├── setup
│ │ ├── assets.md
│ │ ├── controllers.blade.php
│ │ ├── head.md
│ │ ├── index.md
│ │ ├── livereload.md
│ │ ├── middleware.md
│ │ ├── pages.md
│ │ ├── permissions.md
│ │ ├── settings.md
│ │ └── views.md
│ ├── tags
│ │ ├── css.blade.php
│ │ ├── field.blade.php
│ │ ├── iframe.blade.php
│ │ ├── index.md
│ │ ├── order.blade.php
│ │ └── warn.blade.php
│ └── tools
│ │ ├── cat.blade.php
│ │ ├── folder.blade.php
│ │ ├── index.md
│ │ ├── phpinfo.blade.php
│ │ └── routes.vue
│ ├── html
│ ├── list.blade.php
│ └── table.blade.php
│ ├── index.blade.php
│ ├── no-help.blade.php
│ ├── no-home.blade.php
│ ├── no-setup.blade.php
│ ├── setup.blade.php
│ └── sketchpad-head.blade.php
└── src
├── SketchpadServiceProvider.php
├── commands
└── InstallCommand.php
├── config
├── InstallerSettings.php
├── Paths.php
├── SketchpadConfig.php
└── SketchpadSettings.php
├── controllers
├── ApiController.php
├── SetupController.php
└── SketchpadController.php
├── help
├── demo
│ └── ToolsController.php
└── docs
│ ├── HelpersController.php
│ ├── MethodsController.php
│ ├── OutputController.php
│ ├── SetupController.php
│ └── TagsController.php
├── objects
├── file
│ ├── File.php
│ └── Folder.php
├── install
│ ├── ClassTemplate.php
│ ├── Composer.php
│ ├── Copier.php
│ ├── FilesystemObject.php
│ ├── Folder.php
│ ├── JSON.php
│ ├── NamespaceResolver.php
│ └── Template.php
├── reflection
│ ├── Comment.php
│ ├── Controller.php
│ ├── ControllerError.php
│ ├── Field.php
│ ├── Method.php
│ ├── Parameter.php
│ └── Tag.php
├── route
│ ├── CallReference.php
│ ├── ControllerErrorReference.php
│ ├── ControllerReference.php
│ ├── FolderReference.php
│ └── RouteReference.php
└── scanners
│ ├── AbstractScanner.php
│ ├── Finder.php
│ └── Scanner.php
├── routes.php
├── services
├── Installer.php
├── Router.php
├── Setup.php
└── Sketchpad.php
├── traits
├── GetterTrait.php
├── ReflectionTraits.php
└── SaveFileTrait.php
└── utils
├── Code.php
├── Html.php
├── Options.php
└── helpers.php
/.gitattributes:
--------------------------------------------------------------------------------
1 | /build export-ignore
2 | /resources export-ignore
3 | /.gitignore export-ignore
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](http://keepachangelog.com/)
5 |
6 | Note that like Laravel, Laravel Sketchpad *doesn't* adhere to semver; both use:
7 |
8 | - major versions for generations
9 | - minor versions for breaking changes
10 | - patch versions for features, nightly builds, and patches
11 |
12 | ## [1.2.*] - 2017-05-30
13 |
14 | ### Added
15 |
16 | - Added functionality to load custom views from a subfolder
17 | - Added static `Code` helper class
18 | - Added `icon()` helper function
19 | - Added icon functionality to `tb()` helper
20 | - Added text/data `type` functionality to `tb()` helper
21 | - Added column ordering to `tb()` helper
22 | - Added support to render `Paginator` instances to `tb()` helper
23 | - Added support for middleware
24 |
25 | ### Changed
26 |
27 | - Made Paths utility functions static
28 | - Moved demo tools controller before docs controller
29 | - `code()` is now a variadic function and calls `Code` class
30 | - Improved handling of no data in `tb()` helper
31 | - `text()` helper now converts HTML entities
32 | - `Html` class methods now `return` output, with helpers `echo`ing it
33 |
34 | ### Fixed
35 |
36 | - Modified test mode so it passes `$run` even if false
37 | - Modified custom views to load directly
38 | - Fixed security hole in setting per-user permissions
39 | - Fixed bug with windows paths in Setup
40 |
41 |
42 | ## [1.2.0] - 2017-05-09
43 |
44 | ### Added
45 |
46 | - Added top-level Search and Favourites
47 | - Implemented support for `@field` tags
48 | - New `text()` and `code()` helper functions
49 | - Data injection for `md()` function
50 | - Static `Sketchpad::$form` and `Sketchpad::$data` properties
51 | - Automatic injection of `$route` variable into views
52 | - Implemented controller reordering
53 | - Implemented support for implicit pagination
54 | - Included app controllers folder in paths
55 | - Custom page title functionality
56 | - Added custom Help page
57 | - Added custom head view
58 | - Implemented live-reloading for custom pages
59 | - Added index pages for all help controllers
60 | - Added scroll to section functionality for Settings
61 | - Bootstrap formatting for Markdown tables
62 | - Added favicon.ico
63 | - Added changelog
64 |
65 | ### Changed
66 |
67 | - Huge docs and examples update!
68 | - Created new Sketchpad docs sections
69 | - Updated a lot of help to be better-formatted and more consistent
70 | - Improved example controller and view content
71 | - Moved various settings to single "Site" section
72 | - Moved custom asset declaration to custom head view
73 | - Disabled Settings page now shows a message
74 | - Index pages can be re-run
75 | - Added formatting classes to `pr()` and `vd()`
76 | - Updated FontAwesome to latest version
77 | - Improved submission of form data
78 | - Improved formatting of tables and forms
79 | - Removed global appendOutput setting
80 | - New Param sub-components for different data types
81 | - Simplified copying of user views in setup
82 |
83 | ### Fixed
84 |
85 | - Defended against showing parent paths in Browse filesystem
86 | - Defended against saving settings if not allowed in admin.json
87 | - Fixed edge-case with buggy internal routes
88 | - Fixed bug with method titles showing parameters
89 | - Fixed Markdown not showing code or blockquotes correctly
90 | - Fixed bug where coloured @icon icons not showing
91 | - Fixed scroll to top animation
92 | - Fixed bug with intercepted link currentTarget
93 | - Fixed URL encoding bug with route parameters
94 | - Fixed bug in NumberParam where unset variables don't show 0
95 | - Fixed bug where Output only styles first markdown table
96 | - Fixed bug in internal href links not working for links with hashes
97 | - Fixed bug with validate-path directive causing settings to be reloaded on focus
98 | - Fixed routing bug in Help
99 | - Removed absolute path references from RouteReference classes
100 | - Updated demo link in readme
101 |
102 | ### Upgrade notes
103 |
104 | The new settings configuration has breaking changes:
105 |
106 | - Back up your existing settings file `storage/sketchpad/settings.json`
107 | - Reinstall Sketchpad via `http:// This is the index method for your installed Like all the other controllers on the left, its methods are shown as a list; you click them, they run :) To add or remove controller folders, see the Paths section of the Settings page. Your source files can be found at: These files are yours to modify, add to, or remove as you like. This is your custom help page, located at Edit it to suit your needs! This is your custom home page, located at Edit it to suit your needs!ExampleController
.Source
7 |
9 | {{ $config->controllers['sketchpad'] }}
10 | {{ $config->views }}
11 | {{ $config->assets }}
12 |
13 | Help
7 | Hello
10 | {{ $help }}
.Home
7 | Welcome
10 | {{ $home }}
.Code
helper class:
code( ... );3 |
Depending on the values passed in, the function can output:
4 |The selected option above calls:
12 |code{{ $signature }};13 | 14 |
Outputing - {{ $desc }} :
15 | 18 | 19 |You can call the Code
class directly if you prefer:
21 | Code::{{ $func }}{{ $signature }}; 22 |23 | 24 |
Check its source code at:
25 |vendor/davestewart/sketchpad/src/utils/Code.php-------------------------------------------------------------------------------- /package/views/help/helpers/icon.blade.php: -------------------------------------------------------------------------------- 1 |
The format of the function is:
2 |icon($name, $color = '')3 | 4 |
You can output various combinations of icon and colour, including bootstrap color constants and booleans:
5 | 6 | 7 |Icon | 11 |Code | 12 |
---|---|
{!! $datum->icon !!} | 18 |{{ $datum->code }} |
19 |
This helper differs from all others in that it returns an HTML string, rather than immediately echoing it, which allows it to be used in other functions:
25 | 26 |p(icon('bolt') . ' Email');27 | 28 |
The table helper has built-in support for converting columns to icons.
-------------------------------------------------------------------------------- /package/views/help/helpers/index.md: -------------------------------------------------------------------------------- 1 | ### Overview 2 | 3 | Sketchpad ships with a variety of helper functions to make it as easy as possible to put content on-screen. 4 | 5 | Text helpers make it easy to output well-formatted HTML without building views: 6 | 7 | | Helper | Result 8 | | --------- | ------- 9 | | `p()` | Output paragraphs 10 | | `text()` | Output preformatted text 11 | | `code()` | Output syntax-highlighted code 12 | | `alert()` | Output Bootstrap alert elements (useful for reporting) 13 | 14 | Data helpers make it easy to inspect arrays, objects and classes without writing any code: 15 | 16 | | Helper | Result 17 | | --------- | ------- 18 | | `pr()` | `print_r` data 19 | | `vd()` | `var_dump` data 20 | | `dump()` | `dd()` but don't die 21 | | `json()` | Interactively inspect objects in JSON format 22 | | `ls()` | Output objects or classes as list of name / value pairs 23 | | `tb()` | Output an array of objects in table format 24 | 25 | 26 | Note that helper functions `echo` immediately. If you need the raw HTML, call the source `Html` method: 27 | 28 | ```php 29 | $html = Html::tb($data); 30 | ``` 31 | 32 | ### Source 33 | 34 | You can view the source for this section at: 35 | 36 | vendor/davestewart/sketchpad/package/views/help/helpers 37 | vendor/davestewart/sketchpad/src/help/docs/HelpersController.php 38 | 39 | You can view the source for the helper functions at: 40 | 41 | ```text 42 | vendor/davestewart/sketchpad/package/views/html/* 43 | ``` 44 | 45 | 46 | -------------------------------------------------------------------------------- /package/views/help/helpers/list.blade.php: -------------------------------------------------------------------------------- 1 |The format of the function is:
2 |ls($values, $options = '');3 | 4 |
Valid options for lists are:
5 | {!! tb($opts, 'cols:100,400,300|html:example') !!} 6 | 7 |This is the validation config array, formatted as a list:
8 | {!! ls($data, $options) !!} 9 | -------------------------------------------------------------------------------- /package/views/help/helpers/table.blade.php: -------------------------------------------------------------------------------- 1 |The format of the function is:
2 |tb($values, $options = '');3 |
The table helper can render:
4 |Arrayable
class (such as Collections)Paginator
instanceThe options parameter passes formatting arguments, with a syntax similar to Laravel validation:
10 |tb($data, '');11 | 12 |
Within the options string, you can:
13 |Valid options for tables are:
20 | 21 | {!! tb($data, 'cols:100,400,300|html:example') !!} 22 | 23 | 42 | 43 |Experiment with updating the options parameter above, or click the values in the table above to update the example table below:
44 | 45 | {!! tb($preview, $options) !!} 46 | -------------------------------------------------------------------------------- /package/views/help/methods/comments.blade.php: -------------------------------------------------------------------------------- 1 |This makes it easy to see what a method does before calling it:
2 |3 | /** 4 | * The first line of DocBlock comments are shown in the method list and the page heading 5 | * 6 | * This line will not be shown 7 | */ 8 | public function comments() 9 | { 10 | 11 | } 12 |13 | 14 |
For further customisation using DocBlocks, check out the section on tags.
-------------------------------------------------------------------------------- /package/views/help/methods/contents.blade.php: -------------------------------------------------------------------------------- 1 |To show a contents page when you click on your controllers add an index()
method and echo or return some content:
3 | class SomeController extends Controller 4 | { 5 | public function index() 6 | { 7 | md('path.to.index'); // example uses markdown, but you could just as easily use Blade 8 | } 9 | }10 | 11 |
If you want to cheat, just save a markdown file in the same folder as the controller:
12 | 13 |14 | md(__DIR__ . '/some.md'); 15 |16 | 17 |
See the markdown example for more info about the md()
method.
The result of this call is:
2 |Hello, {{ $name }}!3 |
Optional parameters are exposed as editable front-end inputs:
4 |5 | public function parameters($name = 'World') 6 | { 7 | echo "Hello, $name!"; 8 | } 9 |10 |
Update the parameter to automatically call the method again
11 | -------------------------------------------------------------------------------- /package/views/help/methods/run.blade.php: -------------------------------------------------------------------------------- 1 |This is a method; it was called at:
2 |{{ $date }}3 |
Run it again by clicking the Run button
4 | -------------------------------------------------------------------------------- /package/views/help/methods/test.blade.php: -------------------------------------------------------------------------------- 1 |There are often occasions where you want to test code before running it.
2 | 3 |Sketchpad allows you to set an additional boolean parameter $run
which creates a special Test / Run
toggle on the front end:
public function processFiles($run = false) { ... }5 | 6 |
This allows you to preview output and only run additional code when happy with the results:
7 |8 | public function processFiles($run = false) 9 | { 10 | // show files 11 | $files = $this->getFiles(); 12 | pr($files); 13 | 14 | // do something with files 15 | if ($run) 16 | { 17 | foreach ($files as $file) { ... } 18 | } 19 | } 20 |21 | 22 |
If you prefer, you can use parameter names $save
or $update
(which also change the button text):
public function addColumn($name = 1, $save = false) { ... } 24 | public function editUser($id = 1, $udpate = false) { ... }25 | 26 |
Note that each time parameters are updated or the the method is called, the mode is reset to "Test".
27 | -------------------------------------------------------------------------------- /package/views/help/methods/typecasting.blade.php: -------------------------------------------------------------------------------- 1 | 2 |Your method's parameter types (string
, boolean
, etc) determine the HTML input field types.
4 | public function typeCasting($string = 'hello', $number = 1, $boolean = true, $mixed = null) 5 | { 6 | // do something with parameters 7 | } 8 |9 | 10 |
They also enable Sketchpad to cast submitted values back to the expected type; no need for type-juggling in your methods:
11 | {!! vd($params) !!} 12 | 13 |Should you need to override determined types, you can either type-hint your DocBocks:
14 |15 | @param string $string This is a text field 16 | @param int $number This is a number field 17 | @param boolean $boolean This is a checkbox 18 | @param mixed $mixed This is a text field (but will be converted to the correct type) 19 |20 | 21 |
Or, use an additional @field
tag to provide more information to the front end:
23 | @param string $string Say hello in a chosen language 24 | @field select $string options:hello,greetings,bonjour,hola,namaste 25 | 26 | @param int $number Pick a number in a range 27 | @field number $number min:-10|max:10 28 |29 | 30 |
Click here for further information about @field
.
Like normal Laravel controllers, Sketchpad supports dependency injection via type hinting:
2 |3 | public function typeHinting(SketchpadConfig $config) 4 | { 5 | pr($config->settings->data); 6 | } 7 |8 | 9 |
Here's the output:
10 | 11 | {!! pr($config->settings->data) !!} -------------------------------------------------------------------------------- /package/views/help/methods/variables.blade.php: -------------------------------------------------------------------------------- 1 |Currently, only one variable is exposed to the view:
3 | 4 |$route |
8 | The base route the front end is accessed on (currently {{ $route }} ) useful to access assets or routes. |
9 |
This enables you to generate absolute links in your views if required:
13 |<a href="@{{ $route }}some/link">Link</a>14 |
Note that $route
is available in Blade, Markdown, Vue views.
Sketchpad namespaces your user views/
folder using the sketchpad::
identifier:
sketchpad:: |
23 | The path to your user view folder (currently {{ $views }} ) |
24 |
This allows you to reference view files quickly and easily:
29 |30 | return view('sketchpad::user/view/path'); 31 |32 | 33 |
34 | md('sketchpad::user/view/path'); 35 |36 | 37 |
The Sketchpad
service has various values pertaining to the called route:
Sketchpad::$route |
44 | The currently-called controller route (currently {{ $fullroute }} ) |
45 |
Sketchpad::$params |
48 | The same parameters passed to the current method, but as an associative array | 49 |
Sketchpad::$form |
52 | Any form data passed from the front end. Use this in place of Request::all() |
53 |
To access them, reference the class statically:
58 |59 | use davestewart\sketchpad\services\Sketchpad; 60 | 61 | public function test() 62 | { 63 | if (Sketchpad::$form) { ... } 64 | } 65 |66 | 67 | 68 | 69 |
The SketchpadConfig
class provides access to current configuration values:
$config->route |
75 | The base route the front end is accessed on (currently {{ $route }} ) |
76 |
$config->controllers |
79 | The configured list of controller folder paths | 80 |
$config->assets |
83 | The configured user assets folder | 84 |
$config->views |
87 | The configured user views folder | 88 |
$config->settings |
91 | A wrapper for the front end settings, automatically configured in storage/sketchpad/sketchpad.json |
92 |
$config->admin |
95 | The admin settings, manually configured in storage/sketchpad/admin.json |
96 |
To access them, resolve the instance via dependency injection or app()
:
102 | use davestewart\sketchpad\config\SketchpadConfig; 103 | 104 | public function test(SketchpadConfig $config) 105 | { 106 | // alternatively: $config = app(SketchpadConfig::class); 107 | if ($config->admin->settings) { ... } 108 | } 109 |110 | -------------------------------------------------------------------------------- /package/views/help/output/blade.blade.php: -------------------------------------------------------------------------------- 1 |
Sketchpad namespaces your views/
folder to sketchpad::
so you can silo and load development views separately from your app:
3 | echo view('sketchpad::some-view'); // loads from "" 4 |5 |
Note that you can still load views from your application, using the normal syntax:
6 |7 | echo view('some-view'); // loads from "resources/views/" 8 |9 | -------------------------------------------------------------------------------- /package/views/help/output/form.blade.php: -------------------------------------------------------------------------------- 1 |
Any form with an empty or missing "action" attribute will be intercepted and submitted by sketchpad:
2 |<form action=""></form>3 | 4 |
You then check the Sketchpad::$form
variable in your methods and take action appropriately:
public function form() 6 | { 7 | if (Sketchpad::$form) 8 | { 9 | foreach (Sketchpad::$form as $key => $value) { ... } 10 | } 11 | }12 | 13 |
Submit the following test form to see it loop back to the same URL:
16 | 17 | 21 | 22 | @if($form) 23 |The form data is:
24 | {!! dump($form) !!} 25 | @else 26 |Waiting for form data...
27 | @endif -------------------------------------------------------------------------------- /package/views/help/output/formatting.md: -------------------------------------------------------------------------------- 1 | Here are a selection of Sketchpad's default styles, all of which are editable or overridable via your user assets file. 2 | 3 | # This is an H1 4 | ## This is an H2 5 | ### This is an H3 6 | #### This is an H4 7 | ##### This is an H5 8 | 9 | 10 | This is a paragraph 11 | 12 | Here are **some** *styling* [examples](http://www.google.com) ... 13 | 14 | - this 15 | - is 16 | - a 17 | - list 18 | 19 | > Here is a block indent 20 | 21 | Here is some code: 22 | 23 | ```js 24 | // this is a comment 25 | for(var i = 0; i < 10 ; i++) 26 | { 27 | print(i); 28 | } 29 | ``` 30 | 31 | This is an unstyled table: 32 | 33 | | Value | Description | 34 | | ------ | ------ | 35 | | Foo | Lorem ipsum dolor sit amet, vix at adipiscing temporibus | 36 | | Bar | At eam decore utroque, vel cu alia persius | 37 | | Baz | Liberavisse euismod persequeris sit ei | 38 | 39 | This is a styled table: 40 | 41 |Value | 45 |Description | 46 |
---|---|
Foo | 51 |Lorem ipsum dolor sit amet, vix at adipiscing temporibus | 52 |
Bar | 55 |At eam decore utroque, vel cu alia persius | 56 |
Baz | 59 |Liberavisse euismod persequeris sit ei | 60 |
Relative paths run other methods:
2 |Use the special sketchpad:
protocol to link directly to methods:
Absolute paths (outside of sketchpad) run as normal:
13 |External links run as normal:
18 |The format of the function is:
2 |md($path, $data = []);3 | 4 |
Pass absolute paths or a sketchpad::
path:
md('sketchpad::path/to/view');6 | 7 |
You can also inject data into Markdown views, in a manner similar to Blade:
8 |// PHP 9 | md('sketchpad::help/helpers/text', ['value' => 100]);10 | 11 |
// Markdown 12 | The value is @{{value}} // note: no dollar sign!13 | 14 |
// Result 15 | The value is 10016 | 17 |
Note that the Sketchpad view route variable is passed along the same as Sketchpad views:
18 | 19 |// Markdown 20 | Navigate to [settings](@{{route}}settings#paths)21 | 22 |
// Result 23 | Navigate to <a href="/sketchpad/settings#paths">settings</a>24 | 25 | -------------------------------------------------------------------------------- /package/views/help/output/pagination.blade.php: -------------------------------------------------------------------------------- 1 |
Sketchpad supports both implicit and explicit pagination.
2 | 3 |Implicitly, Sketchpad intercepts all page links and passes along all GET values from the front to the back end.
4 |5 | public function pagination ($start = 1, $length = 10) 6 | { 7 | // $_GET['page'] is passed if present in the URL 8 | } 9 |10 |
This allows Laravel's built-in pagination to work without any extra input:
11 | 12 | {!! tb($items) !!} 13 | {!! $paginator !!} 14 | 15 |Explicitly, you can also add a page
parameter to your methods, which will always be part of the URL, allowing you to set pagination manually:
17 | public function pagination ($start = 1, $length = 10, $page = 1) 18 | { 19 | // manually set up pagination 20 | } 21 |-------------------------------------------------------------------------------- /package/views/help/output/vue-text.blade.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/laravel-sketchpad/ebd3365265f8844f7d400d7d530605512a20011c/package/views/help/output/vue-text.blade.php -------------------------------------------------------------------------------- /package/views/help/output/vue.vue: -------------------------------------------------------------------------------- 1 |
This is a sample Vue application; start typing to see it update:
3 |The purpose of Sketchpad is to provide a front-end wrappper around your controllers and methods.
2 | 3 |It's really quite simple:
4 | 5 |Sketchpad picks some default folders for you, which are configurable via the settings page:
13 | 14 | {!! tb($paths, 'cols:150,500|icon:enabled') !!} 15 | 16 |You can rename, reorder, add, remove, enable and disable folders, with changes immediately reflected in the UI.
17 | 18 |Note that abstract, methodless and private controllers will be ignored.
19 | -------------------------------------------------------------------------------- /package/views/help/setup/head.md: -------------------------------------------------------------------------------- 1 | Sketchpad allows you to inject custom head content via a custom [view](views) file: 2 | 3 | ```text 4 | {{head}} 5 | ``` 6 | 7 | 8 | The initial HTML is as follows: 9 | 10 | ```html 11 | {{html}} 12 | ``` 13 | Feel free to add or remove user assets, 3rd party libraries, add tracking, meta tags, etc. 14 | 15 | Note that the variables `assets` and `route` are available to use. -------------------------------------------------------------------------------- /package/views/help/setup/index.md: -------------------------------------------------------------------------------- 1 | ### Overview 2 | 3 | This section discusses setting up Sketchpad to boost your development productivity! 4 | 5 | It covers: 6 | 7 | - configuring controllers, views and custom assets 8 | - Live Reload and live coding 9 | - customising site name and top-level content 10 | - working with 3rd party libraries 11 | - preventing access to settings 12 | 13 | Most settings can be configured from the [Settings]({{route}}settings) page. 14 | -------------------------------------------------------------------------------- /package/views/help/setup/livereload.md: -------------------------------------------------------------------------------- 1 | To live-code and live-reload controllers, views and assets, install and run Sketchpad Reload. 2 | 3 | When developing, this allows you to work **almost exclusively** in your IDE, with Sketchpad responding and updating as you save files, rename elements, change, add or remove code: 4 | 5 | 6 | | Element | Event | Action 7 | | ------- | ------ | -------- 8 | | Method | Save | Console reloads 9 | | Parameters | Rename, change, add or delete | Console parameters update 10 | | Controller or method | Rename, reorder, add or delete | Navigation updates 11 | | Method | Runtime error | Console displays Laravel stack trace 12 | | Controller | Parse error | Navigation and Console display error indicator / message 13 | | Controller or method | Error correction | Navigation and Console update / reload automatically 14 | | Settings paths | Rename, change, add or delete | LiveReload updates and re-watches paths 15 | | User assets | Save | Styles or Script reloads 16 | | Custom home page | Save | Updates custom home page 17 | 18 | 19 | If using Sketchpad for **development** you should make installing Sketchpad Reload a priority: 20 | 21 | - If you've not yet using the package, visit the wiki for installation instructions 22 | - If you need to configure the package, visit the [settings]({{route}}settings#livereload) page. 23 | 24 | 25 | -------------------------------------------------------------------------------- /package/views/help/setup/middleware.md: -------------------------------------------------------------------------------- 1 | To assign middleware to Sketchpad, create a custom service provider file at: 2 | 3 | ``` 4 | app/Providers/SketchpadServiceProvider.php 5 | ``` 6 | 7 | The class should extend Sketchpad's own service provider, and needs only a single `middleware` property: 8 | 9 | ```php 10 | namespace app\Providers; 11 | 12 | class SketchpadServiceProvider extends \davestewart\sketchpad\SketchpadServiceProvider 13 | { 14 | protected $middleware = ['auth']; 15 | } 16 | ``` 17 | 18 | To ensure the new provider is loaded, update your `app.php` config: 19 | 20 | ``` 21 | // 3rd-party Service Providers... 22 | App\Providers\SketchpadServiceProvider::class 23 | ``` 24 | 25 | To assign middleware per controller, just use [Controller Middleware](https://laravel.com/docs/5.0/controllers#controller-middleware). -------------------------------------------------------------------------------- /package/views/help/setup/pages.md: -------------------------------------------------------------------------------- 1 | On installation, Sketchpad copies the following files to your [views](views) folder: 2 | 3 | ```text 4 | help.blade.php 5 | home.blade.php 6 | ``` 7 | They allow you to present different content depending on the install, so you could: 8 | 9 | - differentiate different Sketchpad installs 10 | - run Sketchpad as a standalone site with a custom home page 11 | - provide site-specific help regarding your custom content 12 | 13 | 14 | You can enable custom pages, or change the default folder, in the Site section of the [settings]({{route}}settings#site) page. 15 | -------------------------------------------------------------------------------- /package/views/help/setup/permissions.md: -------------------------------------------------------------------------------- 1 | Sketchpad has some basic functionality to set administrative privileges. 2 | 3 | The settings are stored in `admin.json` that ships with Sketchpad: 4 | 5 | storage/sketchpad/admin.json 6 | 7 | To prevent access to [settings]({{route}}settings) or [setup]({{route}}setup), edit this file then reload Sketchpad: 8 | 9 | ```json 10 | { 11 | "settings": true, 12 | "setup": true 13 | } 14 | ``` 15 | 16 | To set permissions per user, set values on the `SketchpadServiceProvider` in your `AppServiceProvider::boot()`: 17 | 18 | ```php 19 | SketchpadServiceProvider::set('admin.setup', false); 20 | SketchpadServiceProvider::set('admin.settings', false); 21 | ``` 22 | -------------------------------------------------------------------------------- /package/views/help/setup/settings.md: -------------------------------------------------------------------------------- 1 | For projects where you want control over the static content of a Sketchpad install, you can configure: 2 | 3 | - the **site name**, which will update the site and page titles 4 | - the [home page]({{route}}), using a custom Blade view 5 | - the [help page]({{route}}help), using a custom Blade view 6 | - whether to allow [search]({{route}}search) or [favourites]({{route}}favourites) pages 7 | 8 | Configure site setup on the [settings]({{route}}settings#site) page. 9 | 10 | If you choose custom home or help pages, edit their [files](pages) to suit your site. 11 | 12 | -------------------------------------------------------------------------------- /package/views/help/setup/views.md: -------------------------------------------------------------------------------- 1 | Sketchpad provides a custom folder to keep your Sketchpad views separate from your application views: 2 | 3 | ```text 4 | {{views}} 5 | ``` 6 | You can place the following Sketchpad-supported view types here: 7 | 8 | - [Blade](../output/blade) 9 | - [Markdown](../output/markdown) 10 | - [Vue](../output/vue) 11 | 12 | Load views from this folder using the [sketchpad::](../methods/variables) namespace: 13 | 14 | ``` 15 | echo view('sketchpad::somefile') 16 | ``` 17 | -------------------------------------------------------------------------------- /package/views/help/tags/css.blade.php: -------------------------------------------------------------------------------- 1 |The current method item <li>
has a suitably over-the-top class .fancy
added to it:
@css fancy3 |
It's styled (in part) with the following code in the user styles.css
stylesheet (click here to disable it):
5 | li.fancy{ 6 | border:1px solid #333; 7 | background: repeating-linear-gradient( 8 | -45deg, 9 | #222, 10 | #222 5px, 11 | #333 5px, 12 | #333 10px 13 | ); 14 | }15 |
See the assets and formatting sections for more information.
16 | 23 | -------------------------------------------------------------------------------- /package/views/help/tags/field.blade.php: -------------------------------------------------------------------------------- 1 |You can override Sketchpad's choice of input field with various HTML 5 element or input field types using the @field
tag:
4 | @field select $select options:One=1,Two=2,Three=3 5 | @field number $range min:0|max:100|step:5 6 | @field date $date 7 | @field color $color 8 |9 | 10 |
The output from the fields above can be seen here:
11 | {!! dump($params) !!} 12 | 13 |The @field
tag signature matches the @param
tag signature, making it easy to pair the two up in your DocBlocks:
17 | @param type $param text 18 | @field type $param attributes 19 |20 |
The value
and type
are derived from your method signature and @param
tag, with any specific UI requirements drawn from the @field
tag.
The format allows you to specify form element or input type, and optionally pass HTML attributes:
22 |23 | @field date $date 24 | @field color $color 25 | @field number $value min:0|max:100|step:5 26 | @field select $select options:One=1,Two=2,Three=3 27 | @field datalist $select options:foo,bar,baz 28 |29 | 30 |
The values are converted directly into HTML attributes:
31 |32 | <input type="number" value="" min="0" max="100" step="5"> 33 |34 | 35 |
The following element / input types
are supported:
type | 51 |element | 52 |output | 53 |
---|---|---|
select | 58 |HTML select element |
59 | 60 | | 65 |
datalist | 68 |HTML 5 datalist element |
69 | 70 | 71 | 76 | | 77 |
text | HTML text input | |
number | HTML 5 number input | |
color | HTML 5 color input | |
date | HTML 5 date input | |
datetime | HTML 5 datetime-local input | |
time | HTML 5 time input | |
week | HTML 5 week input | |
month | HTML 5 month input |
For select
and dataset
elements, ensure you pass an options
value as outlined above.
Note that some HTML 5 input types (such as url
and email
) are not supported, as they are used primarily for validation, which is not yet supported in Sketchpad.
The attribute syntax is similar to the Laravel validation syntax, converting values to HTML attributes or options:
94 |95 | @field type $varname value:0|values:1,2,3|options:One=1,Two=2,Three=3 96 |97 | 98 |
Note the order of splitting / grouping operators for attributes:
99 | {!! tb($splits, 'html:operator,grouping,example|cols:100,400,200') !!} 100 | 101 | 102 |As mentioned above, attribute name:value
pairs are converted directly into HTML name="value"
attributes, so you can pass whatever is relavent to the element's spec.
See the W3 Schools page for supported values for each element or input type.
105 | 106 |If your input requirements are any more complex than this, consider using forms within your methods.
-------------------------------------------------------------------------------- /package/views/help/tags/iframe.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |Sketchpad loads content inline by default, but for pages already with styling or head content, this ain't gonna fly.
81 |Consider using iframes when:
82 |To automatically size any iframes to the size of their content (and so remove scrollbars) include the following script at the end of the loaded page:
88 |<script src="{{ $route }}assets/js/iframe.js"></script>89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /package/views/help/tags/index.md: -------------------------------------------------------------------------------- 1 | ### Overview 2 | 3 | Sketchpad uses tags to express additional functionality or formatting in the front end. 4 | 5 | Just add them to your methods' doc comments, and Sketchpad automatically implements that functionality: 6 | 7 | /** 8 | * I do something amazing 9 | * 10 | * @group Awesome methods 11 | * @icon address-book 12 | * @iframe 13 | */ 14 | public function someMethod () 15 | { 16 | // I'll load in an iframe 17 | } 18 | 19 | ### Source 20 | 21 | You can view the source for this section at: 22 | 23 | vendor/davestewart/sketchpad/package/views/help/tags 24 | vendor/davestewart/sketchpad/src/help/docs/TagsController.php 25 | -------------------------------------------------------------------------------- /package/views/help/tags/order.blade.php: -------------------------------------------------------------------------------- 1 |
The tag takes a single 1-based numeric integer:
2 |@order 13 |
This is the intended position the controller should be ordered to; i.e. 1, 3, 10.
4 | -------------------------------------------------------------------------------- /package/views/help/tags/warn.blade.php: -------------------------------------------------------------------------------- 1 |Hopefully the big red lozenge didn't put you off too much:
2 |@warn3 |
When the method is finally called, the deferred task, such as sending emails, will be run.
4 |If you need to pass data to the deferred methods, your other options are:
5 |3 | 4 |
5 |Routes: {{ getCount(filter) }} | 10 ||||||
# | 13 |{{ key }} | 14 |||||
{{ $index + 1}} | 20 |{{ value }} | 21 |
Key | 10 |Value | 11 |
---|---|
{{ $key }} | 18 |>{{ $obj ? print_r($value, true) : Html::getText($value) }} | 19 |
# | 10 | @endif 11 | @foreach($keys as $x => $key) 12 |{{ $key }} | 13 | @endforeach 14 |||
---|---|---|---|
{{ $y + 1 }} | 24 | @endif 25 | @foreach($keys as $x => $key) 26 | 34 | @if($isIcon) 35 |>{!! Html::icon($value) !!} | 36 | @elseif($isHtml) 37 |>{!! Html::getText($value) !!} | 38 | @else 39 |>{{ Html::getText($value) }} | 40 | @endif 41 | @endforeach 42 |
Custom help view {{ $help }}
not found.
You can:
8 |vendor/davestewart/sketchpad/package/setup/views/help.blade.php
Custom home view {{ $home }}
not found.
You can:
8 |vendor/davestewart/sketchpad/package/setup/views/home.blade.php
If you think this is a mistake, contact your System Administrator.
34 |If you are the System Administrator see the wiki on how to enable setup.
35 |The format of the function is:
89 |vue($path, $data = []);90 | 91 |
Pass absolute paths or a sketchpad::
path along with any data array
like so:
vue('sketchpad::help/vue/form', ['name' => 'World']);
93 |
94 | The data is injected into the view as a local variable $data
, so just reference it in your view
95 | like so:
|
',
119 | 'grouping' => 'attributes',
120 | 'example' => 'min:0|max:100
',
121 | ],
122 | [
123 | 'operator' => ':
',
124 | 'grouping' => 'attribute name / attribute value',
125 | 'example' => 'step:5
',
126 | ],
127 | [
128 | 'operator' => ',
',
129 | 'grouping' => 'options (select
and datalist
only)',
130 | 'example' => 'foo,bar,baz
',
131 | ],
132 | [
133 | 'operator' => '=
',
134 | 'grouping' => 'option text / option value',
135 | 'example' => 'Yes=1
',
136 | ],
137 | ];
138 |
139 | $format = 'html:example|cols:100,400,200';
140 |
141 | $params = Sketchpad::$params;
142 |
143 | return view('sketchpad::help/tags/field', compact('types', 'attributes', 'format', 'params', 'splits'));
144 | }
145 |
146 | /**
147 | * Append (rather than replace) results to the output panel
148 | *
149 | * @append
150 | */
151 | public function append()
152 | {
153 | $value = str_pad(rand(1, 100), 3, ' ', STR_PAD_RIGHT);
154 | echo "Some new value: $value // re-run or re-save to update..."; 155 | } 156 | 157 | /** 158 | * Load the method in an iframe, rather than rendering it to the page 159 | * 160 | * @iframe 161 | */ 162 | public function iframe() 163 | { 164 | return view('sketchpad::help/tags/iframe'); 165 | } 166 | 167 | /** 168 | * Defers the calling of a method until the "Run" button is clicked 169 | * 170 | * @group Behaviour 171 | * 172 | * @defer 173 | * @param int $foo 174 | */ 175 | public function defer($foo = 1) 176 | { 177 | p('Value
$foo
was set to ' .$foo. ' at ' . date('H:i:s'));
178 | text('@defer');
179 | p('Look also at test mode for a more interactive way to test then run conditional code');
180 | }
181 |
182 | /**
183 | * Shows a warning indicator next to the method name, highlights the comment in red, and defers calling of the method.
184 | *
185 | * @warn
186 | */
187 | public function warn()
188 | {
189 | return view('sketchpad::help/tags/warn');
190 | }
191 |
192 | /**
193 | * Dims the method name in the methods list
194 | *
195 | * @archived This method is no longer used
196 | */
197 | public function archived()
198 | {
199 | p('Probably best to remove methods you no longer use, but if you want to keep them, you can mark them as archived:');
200 | text('@archived');
201 | }
202 |
203 | /**
204 | * Hides the controller or method from the Sketchpad front end
205 | *
206 | * @label private
207 | */
208 | public function hidden()
209 | {
210 | p("Both controllers and methods can be marked as private, meaning they won't be added to Sketchpad's controller list:");
211 | text('@private');
212 | p('For situations where you want to hide what might otherwise be a private method (for example, a callback) simply add the private tag.');
213 | }
214 |
215 |
216 |
217 | }
--------------------------------------------------------------------------------
/src/objects/file/File.php:
--------------------------------------------------------------------------------
1 | name = array_pop($segments);
50 | $this->path = $path;
51 | $this->route = $route;
52 | }
53 |
54 | public function exists()
55 | {
56 | return file_exists($this->path);
57 | }
58 |
59 | }
60 |
61 |
62 |
--------------------------------------------------------------------------------
/src/objects/file/Folder.php:
--------------------------------------------------------------------------------
1 | path = rtrim($this->path, '/') . '/';
56 | $this->folders = [];
57 | $this->controllers = [];
58 |
59 | // get parent routes
60 | $this->setParents();
61 |
62 | // get members
63 | $this->process($recursive);
64 | }
65 |
66 | /**
67 | * Process contained folders and controllers
68 | *
69 | * @param bool $recursive An optional flag to recursively get child folders
70 | */
71 | public function process($recursive = false)
72 | {
73 | // reset
74 | $this->folders = [];
75 | $this->controllers = [];
76 |
77 | // variables
78 | $files = array_diff(scandir($this->path), ['.', '..']);
79 |
80 | // loop
81 | foreach ($files as $file)
82 | {
83 | $path = $this->path . $file;
84 | if(is_dir($path))
85 | {
86 | $this->folders[] = new Folder($path, $recursive);
87 | }
88 | else if(is_file($path) && preg_match('/Controller.php$/', $path))
89 | {
90 | $this->controllers[] = new Controller($path);
91 | }
92 | }
93 | }
94 |
95 | /**
96 | * Build list of parent folders / routes
97 | */
98 | protected function setParents()
99 | {
100 | // variables
101 | $segments = explode('/', trim($this->route, '/'));
102 | $path = '';
103 |
104 | // build parent routes
105 | foreach($segments as $segment)
106 | {
107 | $path .= $segment . '/';
108 | $this->parents[$segment] = $path;
109 | }
110 |
111 | // remove last parent
112 | array_pop($this->parents);
113 | }
114 |
115 | /**
116 | * Specify data which should be serialized to JSON
117 | */
118 | public function jsonSerialize()
119 | {
120 | $data = (object) [];
121 | $data->type = 'folder';
122 | $data->name = $this->name;
123 | $data->route = $this->route;
124 | $data->parents = $this->parents;
125 | $data->folders = $this->folders;
126 | $data->controllers = $this->controllers;
127 | return $data;
128 | }
129 |
130 | }
131 |
--------------------------------------------------------------------------------
/src/objects/install/ClassTemplate.php:
--------------------------------------------------------------------------------
1 | trg, $matches);
32 | $this->classname = $matches[1];
33 |
34 | // namespace
35 | $this->setNamespace();
36 | }
37 |
38 | public function set($data)
39 | {
40 | $data = (array) $data;
41 | $text = $this->text;
42 | foreach($data as $key => $value)
43 | {
44 | switch ($key)
45 | {
46 | case 'namespace':
47 | $value = rtrim($value, '\\');
48 | $text = preg_replace("%namespace\\s+.+?;%", "namespace $value;", $text);
49 | break;
50 |
51 | case 'extends':
52 | case 'class':
53 | $text = preg_replace("%$key\\s+\\[w\\]+%", "$key $value", $text);
54 | break;
55 |
56 | default:
57 | $text = str_replace("%$key%", $value, $text);
58 | }
59 | }
60 | $this->text = $text;
61 | return $this;
62 | }
63 |
64 | public function setNamespace()
65 | {
66 | if( ! $this->resolver )
67 | {
68 | $this->resolver = new NamespaceResolver();
69 | }
70 | $this->resolver->loadNamespaces();
71 | $this->namespace = $this->resolver->getNamespace($this->trg, true);
72 | $data =
73 | [
74 | 'namespace' => $this->namespace
75 | ];
76 | $this->set($data);
77 | return $this;
78 | }
79 |
80 | public function getNamespace()
81 | {
82 | return $this->namespace;
83 | }
84 |
85 | public function loads()
86 | {
87 | $classpath = $this->namespace . '\\' . $this->classname;
88 | try
89 | {
90 | new ReflectionClass($classpath);
91 | return true;
92 | }
93 | catch(\Exception $e)
94 | {
95 | return false;
96 | }
97 | }
98 |
99 | }
--------------------------------------------------------------------------------
/src/objects/install/Composer.php:
--------------------------------------------------------------------------------
1 | &1', $output);
28 | return trim(implode("\n", $output));
29 | }
30 |
31 | public function hasPSR4Class($key)
32 | {
33 | $path = base_path('vendor/composer/autoload_psr4.php');
34 | $data = require($path);
35 | return array_key_exists($key, $data);
36 | }
37 | }
--------------------------------------------------------------------------------
/src/objects/install/Copier.php:
--------------------------------------------------------------------------------
1 | src = $this->makePath($src);
15 | $this->trg = $trg
16 | ? $this->getFileTargetPath($src, $this->makePath($trg))
17 | : null;
18 | }
19 |
20 | public function create()
21 | {
22 | $this->copy($this->trg);
23 | }
24 |
25 | public function copy($trg)
26 | {
27 | if (is_dir($this->src))
28 | {
29 | $this->fs->mirror($this->src, $trg);
30 | }
31 | else
32 | {
33 | $this->fs->copy($this->src, $trg);
34 | }
35 | }
36 |
37 | public function move($trg)
38 | {
39 |
40 | }
41 |
42 | public function exists()
43 | {
44 | return file_exists($this->trg);
45 | }
46 |
47 | public function remove()
48 | {
49 | $this->fs->remove($this->trg);
50 | }
51 |
52 | }
--------------------------------------------------------------------------------
/src/objects/install/FilesystemObject.php:
--------------------------------------------------------------------------------
1 | fs = new Filesystem();
16 | }
17 |
18 | /**
19 | * Utility function to return an absolute path from a relative or absolute path
20 | *
21 | * @param $path
22 | * @return string
23 | */
24 | protected function makePath($path)
25 | {
26 | $path = $this->fs->isAbsolutePath($path)
27 | ? $path
28 | : base_path($path);
29 | return Paths::fix($path);
30 | }
31 |
32 | protected function isFilename($src)
33 | {
34 | return preg_match('%[^/]+\.\w+$%', $src);
35 | }
36 |
37 | protected function getFileTargetPath($src, $trg)
38 | {
39 | // data
40 | $parts = pathinfo($src);
41 |
42 | // if the src is a file, and the target is a folder, rename the target as a file
43 | if($this->isFilename($src) && ! $this->isFilename($trg))
44 | {
45 | $trg = rtrim($trg, '/') . '/' . $parts['basename'];
46 | }
47 |
48 | // replace any pathinfo placeholders
49 | $trg = preg_replace_callback('/\{(dirname|basename|filename|extension)\}/', function($matches) use ($parts)
50 | {
51 | return $parts[$matches[1]];
52 | }, $trg);
53 |
54 | return $trg;
55 | }
56 |
57 | }
58 |
59 |
--------------------------------------------------------------------------------
/src/objects/install/Folder.php:
--------------------------------------------------------------------------------
1 | src = $this->makePath($src);
15 | }
16 |
17 | public function create()
18 | {
19 | $this->fs->mkdir($this->src);
20 | }
21 |
22 | public function exists()
23 | {
24 | return file_exists($this->src);
25 | }
26 |
27 | }
--------------------------------------------------------------------------------
/src/objects/install/JSON.php:
--------------------------------------------------------------------------------
1 | data = [];
29 | $this->load();
30 | }
31 |
32 | public function load()
33 | {
34 | if(file_exists($this->src))
35 | {
36 | $text = file_get_contents($this->src);
37 | $this->data = json_decode($text, JSON_OBJECT_AS_ARRAY);
38 | $error = json_last_error();
39 |
40 | if ($error !== JSON_ERROR_NONE)
41 | {
42 | switch ($error) {
43 | case JSON_ERROR_DEPTH:
44 | $error = 'Maximum stack depth exceeded';
45 | break;
46 | case JSON_ERROR_STATE_MISMATCH:
47 | $error = 'Underflow or the modes mismatch';
48 | break;
49 | case JSON_ERROR_CTRL_CHAR:
50 | $error = 'Unexpected control character found';
51 | break;
52 | case JSON_ERROR_SYNTAX:
53 | $error = 'Malformed JSON';
54 | break;
55 | case JSON_ERROR_UTF8:
56 | $error = 'Malformed UTF-8 characters, possibly incorrectly encoded';
57 | break;
58 | default:
59 | $error = 'Unknown error';
60 | break;
61 | }
62 | throw new \Exception("$error in '{$this->src}'");
63 | }
64 | }
65 |
66 | return $this;
67 | }
68 |
69 | public function __get($name)
70 | {
71 | if($name === 'data')
72 | {
73 | return $this->data;
74 | }
75 | }
76 |
77 | public function set($key, $value = null)
78 | {
79 | if (is_string($key))
80 | {
81 | array_set($this->data, $key, $value);
82 | }
83 | return $this;
84 | }
85 |
86 | public function get($key = '', $default = null)
87 | {
88 | return array_get($this->data, $key, $default);
89 | }
90 |
91 | public function has($key)
92 | {
93 | return array_has($this->data, $key);
94 | }
95 |
96 | public function create()
97 | {
98 | $text = json_encode($this->data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
99 | $this->_save($this->trg, $text);
100 | }
101 |
102 | function jsonSerialize()
103 | {
104 | return $this->data;
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/objects/install/NamespaceResolver.php:
--------------------------------------------------------------------------------
1 | setNamespaces($namespaces)
17 | : $this->loadNamespaces();
18 | }
19 |
20 | /**
21 | * Loads namespaces from composer.json
22 | *
23 | * @return $this
24 | */
25 | public function loadNamespaces()
26 | {
27 | $data = json_decode(file_get_contents(base_path('composer.json')), JSON_OBJECT_AS_ARRAY);
28 | $namespaces = array_get($data, 'autoload.psr-4');
29 | $this->setNamespaces($namespaces);
30 | return $this;
31 | }
32 |
33 | /**
34 | * Sets the source namespace / path array file namespaces will be resolved from
35 | *
36 | * @param $namespaces
37 | * @return mixed
38 | */
39 | public function setNamespaces($namespaces)
40 | {
41 | $this->namespaces = $namespaces;
42 | return $this;
43 | }
44 |
45 | /**
46 | * Attempts to get the namespace for a file
47 | *
48 | * @param string $file base-relative file path
49 | * @param bool $defaultToPath flag to use file path as namespace if namespace cannot be matched
50 | * @return null|string
51 | */
52 | public function getNamespace($file, $defaultToPath = false)
53 | {
54 | // massage file path into a format compatible with PSR-4 entries
55 | $file = str_replace('\\', '/', $file);
56 | $base = str_replace('\\', '/', base_path()) . '/';
57 | $file = str_replace($base, '', $file);
58 |
59 | // compare file path against existing entries
60 | foreach($this->namespaces as $ns => $path)
61 | {
62 | // convert paths to all use forward slashes
63 | $path = str_replace('\\', '/', $path);
64 |
65 | // if the file starts with the namespace path
66 | if(strpos($file, $path) === 0)
67 | {
68 | $file = substr($file, strlen($path));
69 | $file = preg_replace('%/[^/]+$%', '', $file);
70 | $file = str_replace('/', '\\', $file);
71 |
72 | // defensive trim, in case any double slashes
73 | return trim($ns . $file, '\\');
74 | }
75 | }
76 |
77 | // namespace could not be resolved
78 | $info = pathinfo($file);
79 |
80 | return $defaultToPath
81 | ? str_replace('/', '\\', $info['dirname'])
82 | : null;
83 |
84 | }
85 | }
--------------------------------------------------------------------------------
/src/objects/install/Template.php:
--------------------------------------------------------------------------------
1 | load();
24 | if($data)
25 | {
26 | $this->set($data);
27 | }
28 | }
29 |
30 | public function load()
31 | {
32 | $this->text = file_get_contents($this->src);
33 | return $this->text;
34 | }
35 |
36 | public function set($data)
37 | {
38 | $data = (array) $data;
39 | $text = $this->text;
40 | foreach($data as $key => $value)
41 | {
42 | $text = str_replace('%' . $key . '%', $value, $text);
43 | }
44 | $this->text = $text;
45 | return $this;
46 | }
47 |
48 | /**
49 | * Populates and writes the template to the target file
50 | *
51 | * @return bool
52 | */
53 | public function create()
54 | {
55 | return (bool) $this->fs->dumpFile($this->trg, $this->text);
56 | }
57 |
58 | }
--------------------------------------------------------------------------------
/src/objects/reflection/Comment.php:
--------------------------------------------------------------------------------
1 | intro = array_shift($blocks) ?: '';
94 | $this->body = array_shift($blocks) ?: '';
95 | }
96 |
97 |
98 | // ------------------------------------------------------------------------------------------------
99 | // tags
100 |
101 | // match all tags, separate of paragraph text
102 | $this->params = [];
103 | $this->tags = [];
104 |
105 | preg_match_all('/@(\w+)(.*)/', $body, $matches);
106 | foreach ($matches[0] as $index => $match)
107 | {
108 | $name = $matches[1][$index];
109 | $value = trim($matches[2][$index]);
110 | $tag = new Tag($name, $value);
111 | //dump([$name, $value, $tag]);
112 | if($name === 'param')
113 | {
114 | $this->params[$tag->name] = $tag;
115 | }
116 | if ($name === 'field')
117 | {
118 | $field = new Field($name, $value);
119 | $this->fields[$field->name] = $field;
120 | }
121 | else
122 | {
123 | if ($name === 'order')
124 | {
125 | $this->tags[$tag->name] = (int) $tag->text;
126 | }
127 | else
128 | {
129 | $this->tags[$tag->name] = $tag->text ? $tag->text : true;
130 | }
131 | }
132 | }
133 | //dump($this->tags);
134 |
135 | // fix favourites
136 | if(isset($this->tags['favorite']))
137 | {
138 | unset($this->tags['favorite']);
139 | $this->tags['favourite'] = true;
140 | }
141 | }
142 |
143 | /**
144 | * Returns a parameter by name
145 | *
146 | * @param string $name
147 | * @return Tag|null
148 | */
149 | public function getParam($name)
150 | {
151 | return isset($this->params[$name])
152 | ? $this->params[$name]
153 | : null;
154 | }
155 |
156 | /**
157 | * Returns a tag by name
158 | *
159 | * @param string $name
160 | * @return Tag|null
161 | */
162 | public function getTag($name)
163 | {
164 | return isset($this->tags[$name])
165 | ? $this->tags[$name]
166 | : null;
167 | }
168 |
169 | /**
170 | * Returns a field by name
171 | *
172 | * @param string $name
173 | * @return Field|null
174 | */
175 | public function getField($name)
176 | {
177 | return isset($this->fields[$name])
178 | ? $this->fields[$name]
179 | : null;
180 | }
181 |
182 | public function jsonSerialize()
183 | {
184 | $data = (object) [];
185 | $data->intro = $this->intro;
186 | $data->body = $this->body;
187 | return $data;
188 | }
189 |
190 | }
--------------------------------------------------------------------------------
/src/objects/reflection/Controller.php:
--------------------------------------------------------------------------------
1 | getFileName();
60 | return new self($file);
61 | }
62 |
63 | public static function fromPath($path, $route = '', $process = true)
64 | {
65 | // check file exists
66 | if(!file_exists($path))
67 | {
68 | return new ControllerError($path, $route, 'File does not exist');
69 | }
70 |
71 | // class
72 | try
73 | {
74 | return new self($path, $route, $process);
75 | }
76 |
77 | // error
78 | catch(\Exception $error)
79 | {
80 | return new ControllerError($path, $route, 'Invalid class; check file name and class name');
81 | }
82 |
83 | }
84 |
85 | public function __construct($path, $route = '', $process = true)
86 | {
87 | // parent
88 | parent::__construct($path, $route);
89 |
90 | // objects
91 | $class = getClassPath($path);
92 | $this->ref = new ReflectionClass($class);
93 |
94 | // properties
95 | $this->classname = $this->ref->getShortName();
96 | $this->classpath = $this->ref->getName();
97 | $this->label = $this->getLabel($this->classname);
98 | $this->comment = $this->getDocComment();
99 | $this->methods = [];
100 | $this->order = (int) $this->comment->getTag('order');
101 |
102 | // methods
103 | if($process)
104 | {
105 | $this->process();
106 | }
107 | }
108 |
109 | public function process()
110 | {
111 | // variables
112 | $file = $this->ref->getFileName();
113 | $methods = $this->ref->getMethods(ReflectionMethod::IS_PUBLIC);
114 | $arr = [];
115 |
116 | // get methods
117 | foreach ($methods as $m)
118 | {
119 | if($m->getFileName() === $file && $m->name !== '__construct')
120 | {
121 | $method = new Method($m, $this->route);
122 | if( ! isset($method->comment->tags['private']) )
123 | {
124 | $arr[] = $method;
125 | }
126 | }
127 | }
128 | $this->methods = $arr;
129 |
130 | // return
131 | return $this;
132 | }
133 |
134 | public function getReference()
135 | {
136 | return new ControllerReference($this->route, $this->path, $this->classpath);
137 | }
138 |
139 | public function isValid()
140 | {
141 | return !$this->ref->isAbstract()
142 | && !$this->getTag('private')
143 | && count($this->methods) > 0;
144 | }
145 |
146 | public function toArray()
147 | {
148 | $data =
149 | [
150 | 'type' => 'controller',
151 | 'class' => $this->classname,
152 | 'path' => str_replace(base_path() . '/', '', $this->path),
153 | 'name' => $this->name,
154 | 'route' => $this->route,
155 | 'folder' => preg_replace('%[^/]+$%', '', $this->route),
156 | 'order' => $this->order,
157 | 'label' => $this->label,
158 | 'comment' => $this->comment,
159 | 'methods' => $this->methods,
160 | ];
161 | return $data;
162 | }
163 |
164 | function jsonSerialize()
165 | {
166 | return $this->toArray();
167 | }
168 |
169 | }
170 |
171 | /**
172 | * Parses the class source to build a FQ class path
173 | *
174 | * @param $path
175 | * @return string
176 | */
177 | function getClassPath($path)
178 | {
179 | $file = file_get_contents($path);
180 | preg_match('/namespace\s+([\w\\\\]+)/', $file, $namespace);
181 | preg_match('/class\s+(\w+)/', $file, $class);
182 | if($namespace && $class)
183 | {
184 | return $namespace[1] . '\\' . $class[1];
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/src/objects/reflection/ControllerError.php:
--------------------------------------------------------------------------------
1 | label = $label;
24 | $this->error = $error;
25 | }
26 |
27 | public function getReference()
28 | {
29 | return new ControllerErrorReference($this->route, $this->path, $this->error);
30 | }
31 |
32 | public function toArray()
33 | {
34 | $data =
35 | [
36 | 'type' => 'controller',
37 | 'error' => $this->error,
38 | 'path' => str_replace(base_path() . '/', '', $this->path),
39 | 'folder' => preg_replace('%[^/]+$%', '', $this->route),
40 | 'route' => $this->route,
41 | 'label' => $this->label,
42 | 'methods' => []
43 | ];
44 | return $data;
45 | }
46 |
47 | function jsonSerialize()
48 | {
49 | return $this->toArray();
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/src/objects/reflection/Field.php:
--------------------------------------------------------------------------------
1 | attrs = $this->text !== ''
24 | ? Options::create($this->text)->options
25 | : null;
26 | unset($this->text);
27 | }
28 |
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/src/objects/reflection/Method.php:
--------------------------------------------------------------------------------
1 | ref = $method;
49 | $this->name = $method->name;
50 | $this->route = strtolower($route . '/' . $this->name);
51 | $this->label = $this->getLabel();
52 | $this->comment = $this->getDocComment();
53 |
54 | // params
55 | $params = $method->getParameters();
56 | $this->params = [];
57 | foreach($params as /** @var ReflectionParameter */ $param)
58 | {
59 | if($param->isOptional())
60 | {
61 | $name = $param->name;
62 | if ($name === 'run' || $name === 'update' || $name === 'save')
63 | {
64 | $this->runIf = $name;
65 | }
66 | else
67 | {
68 | $tag = $this->comment->getParam($param->name);
69 | $param = new Parameter($param, $tag);
70 | array_push($this->params, $param);
71 | }
72 | }
73 | }
74 |
75 | // fields
76 | if (!empty($this->comment->fields))
77 | {
78 | foreach($this->params as $param)
79 | {
80 | $field = $this->comment->getField($param->name);
81 | if ($field)
82 | {
83 | $param->setField($field);
84 | }
85 | }
86 | }
87 | }
88 |
89 | public function toArray($simple = false)
90 | {
91 | // base
92 | $data = (object) [];
93 | $data->name = $this->name;
94 | $data->label = $this->label;
95 | $data->route = $this->route;
96 | $data->params = $this->params;
97 | $data->comment = $this->comment;
98 | $data->tags = $this->comment->tags;
99 | $data->runIf = $this->runIf;
100 | $data->runState = $this->runState;
101 | $data->error = 0;
102 |
103 | // return
104 | return $data;
105 | }
106 |
107 | function jsonSerialize()
108 | {
109 | return $this->toArray();
110 | }
111 |
112 | }
113 |
114 |
--------------------------------------------------------------------------------
/src/objects/reflection/Parameter.php:
--------------------------------------------------------------------------------
1 | name = $param->getName();
75 | $this->optional = $param->isOptional();
76 | $this->text = $tag ? $tag->text : '';
77 |
78 | // value
79 | $value = $param->isOptional() && ! $param->isVariadic()
80 | ? $param->getDefaultValue()
81 | : $param->getName();
82 |
83 | //$value = $value === null ? 'null' : $value;
84 | //$value = $value === false ? 'false' : $value;
85 | $this->value = $value;
86 |
87 | // type
88 | $this->type = $this->getType($param, $value, $tag ? $tag->type : null);
89 | }
90 |
91 | /**
92 | * Set the field value
93 | *
94 | * @param Field $field
95 | * @return $this
96 | */
97 | public function setField ($field)
98 | {
99 | $this->field = $field;
100 | return $this;
101 | }
102 |
103 | /**
104 | * Gets the parameter type
105 | *
106 | * @param ReflectionParameter $param
107 | * @param mixed $value
108 | * @param string $type
109 | * @return string
110 | */
111 | protected function getType($param, $value, $type = null)
112 | {
113 | // attempt to get the type
114 | $type = method_exists($param, 'getType')
115 | ? $param->getType()
116 | : $type
117 | ? $type
118 | : gettype($value);
119 |
120 | // coerce type to something javascript will understand
121 | if($type == 'double' || $type == 'float' || $type == 'integer' || $type == 'int')
122 | {
123 | $type = 'number';
124 | }
125 | else if($type == 'bool')
126 | {
127 | $type = 'boolean';
128 | }
129 | else if($type == 'NULL')
130 | {
131 | $type = 'null';
132 | }
133 |
134 | // return
135 | return $type;
136 | }
137 |
138 | /**
139 | * Specify data which should be serialized to JSON
140 | */
141 | public function jsonSerialize()
142 | {
143 | // base values
144 | $values = (object)
145 | [
146 | 'name' => $this->name,
147 | 'type' => $this->type,
148 | 'value' => $this->value,
149 | 'text' => $this->text,
150 | 'field' => null,
151 | 'attrs' => (object) [],
152 | ];
153 |
154 | // override if field set
155 | if (isset($this->field))
156 | {
157 | $values->field = $this->field->type;
158 | $values->attrs = (object) $this->field->attrs;
159 |
160 | }
161 | return $values;
162 | }
163 |
164 | }
165 |
166 |
--------------------------------------------------------------------------------
/src/objects/reflection/Tag.php:
--------------------------------------------------------------------------------
1 | name = $name;
48 | $this->text = $text;
49 |
50 | // param
51 | if($name === 'param' || $name === 'field')
52 | {
53 | preg_match('/(\w+)\s+\$(\w+)(.*)/', $text, $matches);
54 | if($matches)
55 | {
56 | $this->type = $matches[1];
57 | $this->name = $matches[2];
58 | $this->text = trim($matches[3]);
59 | }
60 | }
61 |
62 | }
63 |
64 | }
65 |
66 |
--------------------------------------------------------------------------------
/src/objects/route/CallReference.php:
--------------------------------------------------------------------------------
1 | abspath, $ref->route);
36 | $call = new self($controller->route, $controller->path, $controller->classpath);
37 | foreach($controller as $key => $value)
38 | {
39 | $call->$key = $value;
40 | }
41 | return $call;
42 | }
43 |
44 | public static function fromControllerRef(ControllerReference $controller)
45 | {
46 | $call = new self($controller->route, $controller->path, $controller->class);
47 | foreach($controller as $key => $value)
48 | {
49 | $call->$key = $value;
50 | }
51 | return $call;
52 | }
53 |
54 | /**
55 | * CallReference constructor
56 | *
57 | * @param string $route
58 | * @param string $path
59 | * @param string $class
60 | */
61 | public function __construct($route, $path, $class = null)
62 | {
63 | parent::__construct('call', $route, $path);
64 | $this->class = $class;
65 | }
66 |
67 |
68 | // ------------------------------------------------------------------------------------------------
69 | // methods
70 |
71 | /**
72 | * Determines the method to call
73 | *
74 | * @param $route
75 | * @return $this
76 | */
77 | public function setMethod($route)
78 | {
79 | // variables
80 | $methodUri = trim(substr($route, strlen($this->route)), '/');
81 | $segments = explode(',', $methodUri);
82 |
83 | // properties
84 | $this->method = array_shift($segments);
85 |
86 | // return
87 | return $this;
88 | }
89 |
90 | /**
91 | * Sets the calling parameters from the submitted front end data
92 | *
93 | * @param \StdClass[] $params
94 | * @return $this
95 | */
96 | public function setParams($params)
97 | {
98 | $this->params = [];
99 | foreach ($params as $param)
100 | {
101 | // variables
102 | $name = $param['name'];
103 | $type = $param['type'];
104 | $value = $param['value'];
105 |
106 | // properties
107 | $this->params[$name] = $this->convert($type, $value);
108 | }
109 |
110 | // return
111 | return $this;
112 | }
113 |
114 |
115 | // ------------------------------------------------------------------------------------------------
116 | // utilities
117 |
118 | /**
119 | * Utility used to convert values to the correct type
120 | *
121 | * @param string $type
122 | * @param mixed $value
123 | * @return mixed
124 | */
125 | protected function convert ($type, $value)
126 | {
127 | switch ($type)
128 | {
129 | case 'string':
130 | return (string) $value;
131 |
132 | case 'number':
133 | return is_float($value)
134 | ? (float) $value
135 | : (int) $value;
136 |
137 | case 'boolean':
138 | return $value === true || $value === 'true' || $value === '1' || $value === 'on';
139 |
140 | default:
141 | if (is_float($value))
142 | {
143 | return $this->convert('number', $value);
144 | }
145 | if (is_numeric($value))
146 | {
147 | return strpos($value, '.') !== FALSE
148 | ? (float) $value
149 | : (int) $value;
150 | }
151 | if ($value === 'true' || $value === 'false')
152 | {
153 | return $value === 'true';
154 | }
155 | if ($value === '')
156 | {
157 | return null;
158 | }
159 | }
160 | return $value;
161 | }
162 | }
163 |
164 |
--------------------------------------------------------------------------------
/src/objects/route/ControllerErrorReference.php:
--------------------------------------------------------------------------------
1 | error = $error;
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/objects/route/ControllerReference.php:
--------------------------------------------------------------------------------
1 | class = $class;
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/objects/route/FolderReference.php:
--------------------------------------------------------------------------------
1 | type = $type;
24 | $this->route = $route;
25 | $this->path = str_replace(base_path() . '/', '', $abspath);
26 |
27 | }
28 |
29 | public function getName()
30 | {
31 | $segments = explode('/', trim($this->route, '/'));
32 | return array_pop($segments);
33 | }
34 |
35 | public function getDepth()
36 | {
37 | $segments = explode('/', trim($this->route, '/'));
38 | return count($segments);
39 | }
40 |
41 | public function exists()
42 | {
43 | return file_exists(base_path($this->path));
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/objects/scanners/AbstractScanner.php:
--------------------------------------------------------------------------------
1 | isController($path) )
28 | {
29 | $path = $this->scan(app_path());
30 | }
31 |
32 | if($path)
33 | {
34 | $this->path = preg_replace('%/[^/\\\\]+$%', '/', $path);
35 | $this->namespace = $this->getNamespace($path);
36 | return true;
37 | }
38 |
39 | return false;
40 | }
41 |
42 | protected function scan($path)
43 | {
44 | // variables
45 | $path = AbstractScanner::folderize($path);
46 | $files = array_diff(scandir($path), ['.', '..']);
47 | $folders = [];
48 |
49 | // loop, and process files first
50 | foreach ($files as $file)
51 | {
52 | $f = $path . $file;
53 | if(is_dir($f))
54 | {
55 | $folders[] = $f;
56 | }
57 | else if($this->isController($f))
58 | {
59 | return $f;
60 | }
61 | }
62 |
63 | // folders
64 | foreach($folders as $f)
65 | {
66 | $result = $this->scan($f);
67 | if($result)
68 | {
69 | return $result;
70 | }
71 | }
72 | }
73 |
74 | protected function getNamespace($path)
75 | {
76 | $file = file_get_contents($path);
77 | preg_match('/namespace\s+([\w\\\\]+)/', $file, $matches);
78 | if($matches)
79 | {
80 | return $matches[1];
81 | }
82 | return null;
83 | }
84 |
85 | }
--------------------------------------------------------------------------------
/src/objects/scanners/Scanner.php:
--------------------------------------------------------------------------------
1 | Controllers from the sketchpad/ folder downwards
16 | * - matching any called routes to said controllers
17 | * - creating any wildcard routes if required
18 | */
19 | class Scanner extends AbstractScanner
20 | {
21 |
22 | use GetterTrait;
23 |
24 | // -----------------------------------------------------------------------------------------------------------------
25 | // PROPERTIES
26 |
27 | /**
28 | * The base route for controllers
29 | *
30 | * @var string
31 | */
32 | public $route;
33 |
34 | /**
35 | * An array of 'route' => RouteReference instances
36 | *
37 | * These are saved to the session and are used to quickly re-scan controllers later
38 | *
39 | * @var FolderReference[]
40 | */
41 | public $routes;
42 |
43 | /**
44 | * An array of Controller instances
45 | *
46 | * These are used to build the controller JSON array
47 | *
48 | * @var FolderReference[]
49 | */
50 | public $controllers;
51 |
52 |
53 | // -----------------------------------------------------------------------------------------------------------------
54 | // INSTANTIATION
55 |
56 | /**
57 | * Router constructor.
58 | *
59 | * Sets up the Router with the values it needs determine or match routes
60 | *
61 | * @param string $path
62 | * @param string $route the base route for sketchpad routes
63 | */
64 | public function __construct($path, $route = '')
65 | {
66 | // parameters
67 | $this->path = $path;
68 | $this->route = trim($route, '/') . '/';
69 |
70 | // properties
71 | $this->routes = [];
72 | $this->controllers = [];
73 | }
74 |
75 |
76 | // -----------------------------------------------------------------------------------------------------------------
77 | // SCANNING METHODS
78 |
79 | public function start()
80 | {
81 | $this->scan();
82 | return $this;
83 | }
84 |
85 |
86 | // -----------------------------------------------------------------------------------------------------------------
87 | // PROTECTED SCANNING METHODS
88 |
89 | /**
90 | * Recursively finds all controllers and folders
91 | *
92 | * Sets controllers and folders elements as they are found
93 | *
94 | * @param string $path The sketchpad controllers path-relative path to the folder, i.e. "foo/bar/"
95 | */
96 | protected function scan($path = '')
97 | {
98 | // add folder
99 | $this->addFolder($path);
100 |
101 | // get elements
102 | $root = AbstractScanner::folderize($this->path . $path);
103 | $els = array_diff(scandir($root), ['.', '..']);
104 |
105 | // split elements
106 | $files = [];
107 | $folders = [];
108 | foreach ($els as $el)
109 | {
110 | is_dir($root . $el)
111 | ? array_push($folders, $el)
112 | : array_push($files, $el);
113 | }
114 |
115 | // get all controllers
116 | $controllers = [];
117 | $ordered = [];
118 | foreach ($files as $file)
119 | {
120 | $abspath = $root . $file;
121 | if($this->isController($abspath))
122 | {
123 | $controller = $this->addController($abspath, $path);
124 | if ($controller)
125 | {
126 | property_exists($controller, 'order') && $controller->order !== 0
127 | ? array_push($ordered, $controller)
128 | : array_push($controllers, $controller);
129 | }
130 | }
131 |
132 | }
133 |
134 | // re-insert ordered controllers
135 | uasort($ordered, function ($a, $b) { return $a->order - $b->order; });
136 | foreach($ordered as $c)
137 | {
138 | array_splice($controllers, $c->order - 1, 0, [$c]);
139 | }
140 |
141 | // add controllers
142 | $this->controllers = array_merge($this->controllers, array_values($controllers));
143 |
144 | // folders
145 | foreach ($folders as $folder)
146 | {
147 | $relpath = $path . $folder;
148 | $this->scan($relpath . '/');
149 | }
150 | }
151 |
152 | /**
153 | * Adds a folder to the internal routes array
154 | *
155 | * @param string $path The controller-root relative path to the folder
156 | */
157 | protected function addFolder($path)
158 | {
159 | $route = rtrim($this->route . $path, '/');
160 | $ref = new FolderReference($route, $this->path . $path);
161 | $this->addRoute($route, $ref);
162 | }
163 |
164 | /**
165 | * Adds a controller to the internal routes array
166 | *
167 | * @param string $abspath
168 | * @param string $route
169 | * @return Controller|\davestewart\sketchpad\objects\reflection\ControllerError
170 | */
171 | protected function addController($abspath, $route)
172 | {
173 | // variables
174 | $name = pathinfo($abspath, PATHINFO_FILENAME);
175 | $segment = preg_replace('/Controller$/', '', $name);
176 | $route = strtolower($this->route . $route . $segment);
177 |
178 | // objects
179 | $instance = Controller::fromPath($abspath, $route);
180 |
181 | // add
182 | if($instance instanceof Controller)
183 | {
184 | // controller isn't abstract, has methods, isn't private
185 | if ($instance->isValid())
186 | {
187 | $ref = $instance->getReference();
188 | $this->addRoute($route, $ref);
189 | return $instance;
190 | }
191 | }
192 |
193 | else
194 | {
195 | $ref = $instance->getReference();
196 | $this->addRoute($route, $ref);
197 | return $instance;
198 | }
199 | }
200 |
201 | /**
202 | * Adds a new RouteReference obejct
203 | *
204 | * @param string $route The URI route to be registered, i.e. "foo/bar/"
205 | * @param mixed $ref A PathReference instance
206 | */
207 | protected function addRoute($route, $ref)
208 | {
209 | //$ref->route = $route;
210 | $this->routes[$route] = $ref;
211 | }
212 |
213 | }
214 |
--------------------------------------------------------------------------------
/src/routes.php:
--------------------------------------------------------------------------------
1 | route . 'api/run/{params?}', 'ApiController@run')->where('params', '.*');
5 | Route::get ($config->route . 'api/load/{path?}', 'ApiController@load')->where('path', '.*');
6 |
7 | // api - settings
8 | Route::get ($config->route . 'api/settings', 'ApiController@settings');
9 | Route::post ($config->route . 'api/settings', 'ApiController@settings');
10 |
11 | // api - tools
12 | Route::get ($config->route . 'api/path', 'ApiController@path');
13 | Route::get ($config->route . 'api/page/{name}', 'ApiController@page');
14 |
15 | // setup
16 | Route::get ($config->route . 'setup', 'SetupController@index');
17 | Route::post ($config->route . 'setup/install', 'SetupController@submit');
18 | Route::get ($config->route . 'setup/install', 'SetupController@install');
19 | Route::get ($config->route . 'setup/test', 'SetupController@test');
20 |
21 | // assets
22 | Route::get ($config->route . 'assets/user/{file}', 'SketchpadController@userAsset')->where(['file' => '.*']);
23 | Route::get ($config->route . 'assets/{file}', 'SketchpadController@asset')->where(['file' => '.*']);
24 |
25 | // sketchpad
26 | Route::get ($config->route . '{params?}', 'SketchpadController@index')->where('params', '.*');
27 |
--------------------------------------------------------------------------------
/src/services/Router.php:
--------------------------------------------------------------------------------
1 | Controllers from the sketchpad/ folder downwards
20 | * - matching any called routes to said controllers
21 | * - creating any wildcard routes if required
22 | *
23 | * @property Scanner $scanner
24 | */
25 | class Router
26 | {
27 |
28 | use GetterTrait;
29 |
30 | // -----------------------------------------------------------------------------------------------------------------
31 | // PROPERTIES
32 |
33 | /**
34 | * @var string[]
35 | */
36 | protected $paths;
37 |
38 | /**
39 | * A cache of all routes so
40 | *
41 | * @var RouteReference[]
42 | */
43 | protected $routes;
44 |
45 | /**
46 | * An array of full controller properties to pass to the front end
47 | *
48 | * @var Controller[]
49 | */
50 | protected $controllers;
51 |
52 |
53 | // -----------------------------------------------------------------------------------------------------------------
54 | // INSTANTIATION
55 |
56 | /**
57 | * Router constructor.
58 | *
59 | * Sets up the Router with the values it needs determine or match routes
60 | *
61 | * Parameters are all from the Sketchpad config file
62 | *
63 | * @param string[] $paths an array of paths
64 | */
65 | public function __construct($paths)
66 | {
67 | $this->paths = (array) $paths;
68 | }
69 |
70 |
71 | // -----------------------------------------------------------------------------------------------------------------
72 | // METHODS
73 |
74 | public function scan()
75 | {
76 | // reset
77 | $this->routes = [];
78 | $this->controllers = [];
79 |
80 | // scan
81 | foreach ($this->paths as $name => $path)
82 | {
83 | $root = strpos($path, '/') === 0 ? $path : base_path($path);
84 | if (file_exists($root))
85 | {
86 | $scanner = new Scanner($root, $name);
87 | $scanner->start();
88 | $this->routes = array_merge($this->routes, $scanner->routes);
89 | $this->controllers = array_merge($this->controllers, $scanner->controllers);
90 | }
91 | }
92 |
93 | // save routes
94 | Session::put('sketchpad.routes', $this->routes);
95 |
96 | // save method parameter types
97 | //ParamTypeManager::create()->saveAll($this->controllers);
98 |
99 | // return
100 | return $this;
101 | }
102 |
103 | /**
104 | * Reverse route lookup
105 | *
106 | * Compares a given route against routes determined when controllers were scanned
107 | *
108 | * Determines the controller, method and parameters to call if there is a match
109 | *
110 | * @param string $route
111 | * @param $params
112 | * @return ControllerReference|FolderReference|null
113 | */
114 | public function getCall($route, array $params = [])
115 | {
116 | // variables
117 | $route = AbstractScanner::folderize($route);
118 | $routes = $this->getRoutes();
119 |
120 | // debug
121 | // pr($route, $routes);
122 |
123 | // first, attempt to find an exact match
124 | // an exact match will either be a controller or a folder
125 | if(isset($routes[$route]))
126 | {
127 | return $routes[$route];
128 | }
129 |
130 | // otherwise, the passed path will be at least a "controller/method" in which case,
131 | // we need to find the nearest partial match, then extract the component parts
132 | else
133 | {
134 | // variables
135 | /** @var CallReference $ref */
136 | $ref = null;
137 |
138 | // loop over routes and grab matches
139 | // the last full match will be the one that we want
140 | foreach($routes as $key => $value)
141 | {
142 | //pr('KEY', $key, $value);
143 | if(strpos($route, $key) === 0)
144 | {
145 | $ref = $value;
146 | }
147 | }
148 |
149 | // debug
150 | //pr('REF', $ref);
151 |
152 | // if we got a matching route, update the ref with method and params
153 | if($ref instanceof ControllerReference)
154 | {
155 | return CallReference::fromControllerRef($ref)
156 | ->setMethod($route)
157 | ->setParams($params);
158 | }
159 |
160 | if($ref instanceof ControllerErrorReference)
161 | {
162 | return CallReference::fromRef($ref)
163 | ->setMethod($route)
164 | ->setParams($params);
165 | }
166 |
167 | }
168 |
169 | // return
170 | return null;
171 | }
172 |
173 | public function getRoutes()
174 | {
175 | // existing routes
176 | if($this->routes)
177 | {
178 | return $this->routes;
179 | }
180 |
181 | // saved routes
182 | $routes = Session::get('sketchpad.routes');
183 | if($routes)
184 | {
185 | $this->routes = $routes;
186 | return $routes;
187 | }
188 |
189 | // scan routes
190 | $this->scan();
191 | return $this->routes;
192 | }
193 |
194 | public function getFolders()
195 | {
196 | return array_filter($this->routes, function($ref){ return $ref instanceof FolderReference; });
197 | }
198 |
199 | public function getControllers()
200 | {
201 | return $this->controllers;
202 | }
203 |
204 | public function getController($route)
205 | {
206 | // variables
207 | $route = strtolower($route);
208 | $routes = $this->getRoutes();
209 |
210 | // filter
211 | foreach($routes as /** @var ControllerReference */$ref)
212 | {
213 | if(strtolower($ref->route) == $route)
214 | {
215 | // TODO not sure this is needed any more
216 | // check if the file still exists
217 | if(!$ref->exists())
218 | {
219 | return (object) ["error" => "The file '{$ref->path}' does not exist"];
220 | }
221 |
222 | // otherwise, get a reference
223 | $instance = Controller::fromPath(base_path($ref->path), $ref->route);
224 |
225 | // update session
226 | Session::put('sketchpad.routes.' . $ref->route, $instance->getReference());
227 |
228 | // return
229 | return $instance;
230 | }
231 | }
232 |
233 | return (object) ["error" => "Invalid controller route '$route'"];
234 | }
235 |
236 | }
--------------------------------------------------------------------------------
/src/services/Setup.php:
--------------------------------------------------------------------------------
1 | path() !== $path
31 | ? redirect($path)
32 | : $this->view();
33 | }
34 |
35 |
36 | // ------------------------------------------------------------------------------------------------
37 | // setup
38 |
39 | /**
40 | * Shows the setup form view
41 | *
42 | * @return mixed
43 | */
44 | public function view()
45 | {
46 | // default variables
47 | $finder = new Finder();
48 | $finder->start();
49 |
50 | // config
51 | $config = app(SketchpadConfig::class);
52 |
53 | // base name
54 | $basePath = Paths::fix(base_path('/'));
55 | $baseSegments = explode('/', rtrim($basePath, '/'));
56 | $baseName = array_pop($baseSegments) . '/';
57 |
58 | // view path
59 | $viewPaths = Config::get('view.paths');
60 | $viewPath = substr(Paths::fix($viewPaths[0]), strlen($basePath));
61 |
62 | // variables
63 | $app = app();
64 | $data =
65 | [
66 | 'route' => $config->route,
67 | 'assets' => $config->route . 'assets/',
68 | 'settings' =>
69 | [
70 | 'route' => $config->route,
71 | 'basepath' => $basePath,
72 | 'basename' => $baseName,
73 | 'viewpath' => $viewPath,
74 | 'storagepath' => Paths::relative($config->settings->src),
75 | 'controllerpath' => trim(Paths::relative($finder->path), '/'),
76 | 'namespace' => method_exists($app, 'getNamespace')
77 | ? trim($app->getNamespace(), '\\')
78 | : 'App\\',
79 | 'namespaces' => (new JSON('composer.json'))->get('autoload.psr-4')
80 | ]
81 | ];
82 |
83 | // return view
84 | return view('sketchpad::setup', $data);
85 | }
86 |
87 | public function disabled()
88 | {
89 | $config = app(SketchpadConfig::class);
90 | $data =
91 | [
92 | 'route' => $config->route,
93 | 'assets' => '/' . $config->assets,
94 | 'path' => substr(storage_path('sketchpad/admin.json'), strlen(base_path()) + 1)
95 | ];
96 | die(view('sketchpad::no-setup', $data));
97 | }
98 |
99 | // ------------------------------------------------------------------------------------------------
100 | // form
101 |
102 | public function saveData($input)
103 | {
104 | $settings = new InstallerSettings();
105 | return $settings->save($input);
106 | }
107 |
108 | public function loadData()
109 | {
110 | $settings = new InstallerSettings();
111 | return $settings;
112 | }
113 |
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/src/services/Sketchpad.php:
--------------------------------------------------------------------------------
1 | value pairs
47 | *
48 | * @var mixed[]
49 | */
50 | public static $params;
51 |
52 | /**
53 | * Any submitted form data
54 | *
55 | * @var mixed[]
56 | */
57 | public static $form;
58 |
59 |
60 | // -----------------------------------------------------------------------------------------------------------------
61 | // INSTANTIATION
62 |
63 | public function __construct()
64 | {
65 | $this->config = app(SketchpadConfig::class);
66 | }
67 |
68 |
69 | // -----------------------------------------------------------------------------------------------------------------
70 | // SETUP
71 |
72 | public function init($scan = false)
73 | {
74 | $this->router = new Router($this->config->controllers);
75 | if($scan)
76 | {
77 | //pr($this->router);
78 | $this->router->scan();
79 | //pd($this->router);
80 | }
81 | return $this;
82 | }
83 |
84 | public function isInstalled ()
85 | {
86 | return $this->config->settings->exists();
87 | }
88 |
89 |
90 | // ------------------------------------------------------------------------------------------------
91 | // GETTERS
92 |
93 | /**
94 | * Returns a sketchpad\objects\reflection\Controller that can be converted to JSON
95 | *
96 | * @param string $route The relative route to the controller
97 | * @return Controller The Controller
98 | */
99 | public function getController($route = null)
100 | {
101 | $router = $this->init($route == null)->router;
102 | return $route
103 | ? $router->getController($route)
104 | : $router->getControllers();
105 | }
106 |
107 |
108 | // ------------------------------------------------------------------------------------------------
109 | // ROUTING METHODS
110 |
111 | /**
112 | * Initial function that works out the controller, method and parameters to call from the URI string
113 | *
114 | * @param string $route
115 | * @param array $params
116 | * @param array $form
117 | * @return mixed|string
118 | */
119 | public function run($route = '', array $params, array $form = null)
120 | {
121 | // set up the router, but don't scan
122 | $this->init();
123 |
124 | /** @var CallReference $ref */
125 | $ref = $this->router->getCall($route, $params);
126 |
127 | //vd([$ref, $route, $params]);
128 | //exit;
129 |
130 | // controller has method
131 | if($ref instanceof CallReference)
132 | {
133 | // test controller / method exists
134 | try
135 | {
136 | new ReflectionMethod($ref->class, $ref->method);
137 | }
138 | catch(\Exception $e)
139 | {
140 | if($e instanceof \ReflectionException)
141 | {
142 | //$sketchpad = str_replace($this->config->route, '', $ref->route) . $ref->method . '/';
143 | $this->abort($ref->route . '::' . $ref->method . '()', 'method');
144 | }
145 | }
146 |
147 | // assign static properties
148 | Sketchpad::$route = $ref->route . '/' . $ref->method . '/';
149 | Sketchpad::$params = $ref->params;
150 | Sketchpad::$form = empty($form) ? null : $form;
151 |
152 | // get controller response or content
153 | ob_start();
154 | $response = $this->exec($ref->class, $ref->method, $ref->params);
155 | $content = ob_get_contents();
156 | ob_end_clean();
157 |
158 | // handle echoed output
159 | if ($content)
160 | {
161 | return $content;
162 | }
163 |
164 | // handle laravel view responses
165 | if ($response instanceof \Illuminate\Contracts\View\View)
166 | {
167 | return $response;
168 | }
169 |
170 | // handle laravel json responses
171 | if ($response instanceof \Illuminate\Http\JsonResponse)
172 | {
173 | return json($response->getData());
174 | }
175 |
176 | // handle Arrrayable
177 | if ($response instanceof \Illuminate\Contracts\Support\Arrayable)
178 | {
179 | return json($response->toArray());
180 | }
181 |
182 | // handle Jsonable
183 | if ($response instanceof \Illuminate\Contracts\Support\Jsonable)
184 | {
185 | return json(json_decode($response->toJson()));
186 | }
187 |
188 | // anything else send as JSON (classes, objects, arrays, numbers, strings, booleans)
189 | return json($response);
190 | }
191 |
192 | // if there's not a valid controller or method, it's a 404
193 | $this->abort($route, 'path');
194 | return false;
195 | }
196 |
197 |
198 | // ------------------------------------------------------------------------------------------------
199 | // UTILITIES
200 |
201 | /**
202 | * Calls a controller and methods, resolving any dependency injection
203 | *
204 | * @param string $controller The FQ name of the controller
205 | * @param string $method The name of the method
206 | * @param array|null $params An optional array of parameters
207 | * @return mixed The result of the call
208 | */
209 | public function exec($controller, $method, $params = null)
210 | {
211 | $callable = "$controller@$method";
212 | return $params
213 | ? App::call($callable, $this->getCallParams($controller, $method, $params))
214 | : App::call($callable);
215 | }
216 |
217 | /**
218 | * Gets method parameters in the correct format to do an App:call() with dependency injection
219 | *
220 | * @param string $controller
221 | * @param string $method
222 | * @param string|array $params
223 | * @return array
224 | */
225 | protected function getCallParams($controller, $method, $params)
226 | {
227 | // variables
228 | $values = is_array($params) ? $params : explode('/', $params);
229 | $ref = new ReflectionMethod($controller, $method);
230 | $params = $ref->getParameters();
231 | $args = [];
232 |
233 | // map route segments to the method's parameters
234 | foreach ($params as /** @var \ReflectionParameter */ $param)
235 | {
236 | // parse signature [match, optional, type, name, default]
237 | preg_match('/<(required|optional)> (?:([\\\\a-z\d_]+) )?(?:\\$(\w+))(?: = (\S+))?/i', (string)$param, $matches);
238 |
239 | // assign untyped segments
240 | if ($matches[2] == null)
241 | {
242 | $args[$matches[3]] = array_shift($values);
243 | }
244 | }
245 |
246 | // append any remaining values
247 | return array_merge($args, $values);
248 | }
249 |
250 | /**
251 | * Aborts with a message
252 | *
253 | * @param string $uri The route that was called by the front end
254 | * @param string $type An object type, such as a controller, method, folder
255 | */
256 | protected function abort($uri, $type = '')
257 | {
258 | App::abort(404, "The requested Sketchpad $type '$uri' does not exist");
259 | }
260 |
261 |
262 | }
--------------------------------------------------------------------------------
/src/traits/GetterTrait.php:
--------------------------------------------------------------------------------
1 | $name;
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/src/traits/ReflectionTraits.php:
--------------------------------------------------------------------------------
1 | ref->getDocComment());
51 | }
52 |
53 | /**
54 | * Determines the label for the element
55 | *
56 | * Returns a @label parameter, if available, otherwise, humanizes the element name
57 | *
58 | * @param null $default
59 | * @return null|string
60 | */
61 | public function getLabel($default = null)
62 | {
63 | $label = $this->getTag('label');
64 | if( ! $label )
65 | {
66 | $label = $default ?: $this->ref->getName();
67 | $label = preg_replace('/^(.+)Controller$/', '$1', $label);
68 | //$label = preg_replace('/_/', ' ', $label);
69 | //$label = preg_replace('/([a-z])([A-Z0-9])/', '$1 $2', $label);
70 | //$label = strtolower($label);
71 | }
72 | return $label;
73 | }
74 |
75 | /**
76 | * Gets the first available value of a tag
77 | *
78 | * @param string $name
79 | * @return string|null
80 | */
81 | public function getTag($name)
82 | {
83 | $comment = $this->ref->getDocComment();
84 | preg_match('/@' .$name. '\s+(.+)/', $comment, $matches);
85 | return $matches ? $matches[1] : null;
86 | }
87 |
88 |
89 | }
--------------------------------------------------------------------------------
/src/traits/SaveFileTrait.php:
--------------------------------------------------------------------------------
1 | $text\n";
20 | }
21 |
22 | /**
23 | * Output the contents of an entire file
24 | *
25 | * @param string $path
26 | * @param string $format
27 | * @return string
28 | */
29 | public static function file($path, $format = '')
30 | {
31 | if ($format === '')
32 | {
33 | $format = self::getExtension($path);
34 | }
35 | $text = file_get_contents($path);
36 | return self::output($text, $format);
37 | }
38 |
39 | /**
40 | * Output a section of a single file
41 | *
42 | * @param string $path
43 | * @param int $start
44 | * @param int $end
45 | * @param bool $undent
46 | * @return string
47 | */
48 | public static function section($path, $start = 0, $end = 0, $undent = false)
49 | {
50 | $format = self::getExtension($path);
51 | $text = file_get_contents($path);
52 | if ($start !== 0 || $end !== 0)
53 | {
54 | $text = self::lines($text, $start, $end);
55 | if ($undent)
56 | {
57 | $text = self::undent($text);
58 | }
59 | }
60 | return self::output($text, $format);
61 | }
62 |
63 | /**
64 | * Output the contents of a method
65 | *
66 | * @param string $class
67 | * @param string $method
68 | * @param bool $comment
69 | * @return string
70 | */
71 | public static function method($class, $method, $comment = false)
72 | {
73 | $ref = new \ReflectionMethod($class, $method);
74 | $start = $ref->getStartLine();
75 | $end = $ref->getEndLine();
76 | $text = file_get_contents($ref->getFileName());
77 | $text = self::lines($text, $start, $end);
78 | $text = self::undent($text);
79 | if ($comment)
80 | {
81 | $text = preg_replace('/^\s+\*/m', ' *', $ref->getDocComment()) . PHP_EOL . $text;
82 | }
83 | return self::output($text, 'php');
84 | }
85 |
86 | /**
87 | * Output the contents of a class
88 | *
89 | * @param string $class
90 | * @param bool $comment
91 | * @return string
92 | */
93 | public static function classfile($class, $comment = false)
94 | {
95 | $ref = new \ReflectionClass($class);
96 | $start = $ref->getStartLine();
97 | $end = $ref->getEndLine();
98 | $text = file_get_contents($ref->getFileName());
99 | $text = self::lines($text, $start, $end);
100 | if ($comment)
101 | {
102 | $text = preg_replace('/^\s+\*/m', ' *', $ref->getDocComment()) . PHP_EOL . $text;
103 | }
104 | return self::output($text, 'php');
105 | }
106 |
107 | /**
108 | * Return a range of lines from a block of text
109 | *
110 | * @param string $text
111 | * @param int $start
112 | * @param int $end
113 | * @return string
114 | */
115 | public static function lines($text, $start, $end)
116 | {
117 | $lines = explode(PHP_EOL, $text);
118 | $code = implode(PHP_EOL, array_slice($lines, $start - 1, $end - $start + 1));
119 | return $code;
120 | }
121 |
122 | /**
123 | * Return a formatted function signature
124 | *
125 | * @param array $args
126 | * @return string
127 | */
128 | public static function signature($args = [])
129 | {
130 | $values = array_map(function ($v) {
131 | return is_string($v)
132 | ? "'$v'"
133 | : (is_bool($v)
134 | ? !! $v ? 'true' : 'false'
135 | : $v);
136 | }, $args);
137 | return '(' . implode(', ', $values) . ')';
138 | }
139 |
140 | /**
141 | * Remove any indent from a block of text, based on the first line
142 | *
143 | * @param string $text
144 | * @return string
145 | */
146 | public static function undent($text)
147 | {
148 | preg_match('/^\s+/', $text, $matches);
149 | list ($indent) = $matches;
150 | if ($indent)
151 | {
152 | $text = preg_replace("/^$indent/m", '', $text);
153 | }
154 | return $text;
155 | }
156 |
157 | /**
158 | * Gets the file extension of a path
159 | *
160 | * @param string $path
161 | * @return string
162 | */
163 | protected static function getExtension ($path)
164 | {
165 | return strpos($path, '/') !== NULL
166 | ? substr($path, strrpos($path, '.') + 1)
167 | : '';
168 | }
169 | }
--------------------------------------------------------------------------------
/src/utils/Options.php:
--------------------------------------------------------------------------------
1 | options = $this->parse($str);
23 | }
24 |
25 | public static function create ($str = '')
26 | {
27 | return new Options($str);
28 | }
29 |
30 |
31 | // ------------------------------------------------------------------------------------------------
32 | // methods
33 |
34 | public function has($name)
35 | {
36 | return array_key_exists($name, $this->options);
37 | }
38 |
39 | public function get($name, $default = null)
40 | {
41 | return array_key_exists($name, $this->options)
42 | ? $this->options[$name]
43 | : $default;
44 | }
45 |
46 | public function set($name, $value)
47 | {
48 | $this->options[$name] = $value;
49 | return $this;
50 | }
51 |
52 | public function __get($name)
53 | {
54 | return $this->get($name);
55 | }
56 |
57 | public function __set($name, $value)
58 | {
59 | $this->set($name, $value);
60 | }
61 |
62 | // -----------------------------------------------------------------------------------------------------------------
63 | // utilties
64 |
65 | /**
66 | * Converts string options to a hash
67 | *
68 | * Operation:
69 | *
70 | * - splits options by |
71 | * - splits arguments by :
72 | * - splits argument members by ,
73 | * - splits argument member names and values by =
74 | *
75 | * Example:
76 | *
77 | * index|html:path|pre:path,methods|values:One=1,Two=2,Three=3
78 | *
79 | * @param $input
80 | * @return array
81 | */
82 | public function parse($input)
83 | {
84 | $output = [];
85 | $options = explode('|', $input);
86 | foreach($options as $option)
87 | {
88 | $name = $option;
89 | $value = 1;
90 | if(strpos($option, ':') !== false)
91 | {
92 | list($name, $value) = explode(':', $option, 2);
93 | }
94 | if (strstr($value, ',') !== false)
95 | {
96 | $values = explode(',', $value);
97 | if (strstr($value, '=') !== false)
98 | {
99 | $pairs = [];
100 | foreach($values as $value)
101 | {
102 | list($n, $v) = explode('=', $value, 2);
103 | $pairs[$n] = $v;
104 | }
105 | $values = $pairs;
106 | }
107 | $value = $values;
108 | }
109 | $output[$name] = $value;
110 | }
111 | return $output;
112 | }
113 |
114 |
115 | }
--------------------------------------------------------------------------------
/src/utils/helpers.php:
--------------------------------------------------------------------------------
1 |