├── .editorconfig
├── .gitignore
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── build.xml
├── composer.json
├── composer.lock
├── controller.php
├── phpcs.xml
└── src
├── Blade.php
├── Blade
├── Coder.php
└── Debugger.php
├── Controller.php
├── Loader.php
├── Module
├── Acf.php
└── Tree.php
└── Utils.php
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 |
3 | root = true
4 |
5 | indent_style = space
6 | indent_size = 4
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor
2 | dist
3 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### 2.1.2:
2 | * Fixes
3 |
4 | ### 2.1.1:
5 | * Update Brain/Hierarchy to 2.4.0 for CPT templates [#86](https://github.com/soberwp/controller/issues/86)
6 | * Add filter sober/controller/sage/namespace to allow for a custom Sage namespace [#104](https://github.com/soberwp/controller/issues/104)
7 | * Update ACF class to support taxonomy fields by default by using get_queried_object() [#101](https://github.com/soberwp/controller/issues/101)
8 | * Bug fix for ACF class if no fields on page/post [#102](https://github.com/soberwp/controller/issues/102)
9 | * Allow interaction in Controller with the $post object by using $this->post vs $this->data['post']
10 |
11 | ### 2.1.0:
12 | * Update deps
13 | * Pass in field data from Acf Options under App class
14 | * Change $this->data from private to protected param
15 | * Fix $post bug not appearing in the $this->data
16 | * Fix Controller overriding filter $data
17 | * Add filter to return Acf data as array
18 | * Add __before and __after lifecycles
19 | * @code and @codeif
20 |
21 | ### 2.0.1:
22 | * Fix bug assuming Controllers/ folder name
23 |
24 | ### 2.0.0:
25 | * PSR4 loading
26 | * Template overrides for those underscores
27 | * Pass in field data from Acf automatically
28 | * Debugger to include static methods
29 | * Improve Debugger results
30 | * Dependency injection
31 | * Bug fixes
32 | * Change default path from resources/controllers to app/controllers
33 |
34 | ### 9.0.0-beta.3:
35 | * Changed to Composer package
36 | * Fix for app Controller bug
37 | * Rename base to app
38 | * Change default path from src/controllers to resources/controllers
39 |
40 | ### 9.0.0-beta.2.1:
41 | * Fix for base Controller bug
42 |
43 | ### 9.0.0-beta.2:
44 | * Align with Sage9 versioning
45 | * Enable the use of __construct within the child Class
46 |
47 | ### 1.0.2:
48 | * Prevent public static methods from being passed onto data
49 | * Class alias for use in template
50 |
51 | ### 1.0.1:
52 | * Pass on default post data for posts
53 | * Show $post in the controller debugger
54 |
55 | ### 1.0.0:
56 | * Release
57 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) Darren Jacoby
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Controller
2 |
3 | WordPress package to enable a controller when using Blade with [Sage 9](https://roots.io/sage/) (Please note, Sage 10 uses Composers and not this package.)
4 |
5 | * [Installation](#installation)
6 | * [Setup](#setup)
7 | * [Usage](#usage)
8 | * [Overview](#overview)
9 | * [Basic Controller](#basic-controller)
10 | * [Using Functions](#using-functions)
11 | * [Using Components](#using-components)
12 | * [Inheriting the Tree/Hierarchy](#inheriting-the-treehierarchy)
13 | * [Creating Global Properties](#creating-global-properties)
14 | * [Advanced Custom Fields Module](#advanced-custom-fields-module)
15 | * [Template Override Option](#template-override-option)
16 | * [Lifecycles](#lifecycles)
17 | * [Disable Option](#disable-option)
18 | * [Blade Debugger](#blade-debugger)
19 | * [Blade Coder](#blade-coder)
20 |
21 |
22 |
23 | ## Installation
24 |
25 | ### Composer:
26 |
27 | [Sage](https://roots.io/sage/) ships with Controller. However, should you need to install, browse into the Sage theme directory and run;
28 |
29 | ```shell
30 | $ composer require soberwp/controller:2.1.2
31 | ```
32 |
33 | ### Upgrading to 2.x.x:
34 |
35 | Please note that versions 2.x.x are newer releases than 9.x.x-beta. The 9 was used to match Sage 9 versioning at the time.
36 |
37 | Controller 2.x.x uses [PSR4 autoloading](https://www.php-fig.org/psr/psr-4/) to load Controller classes. This is considered best practice. You will need to [update the following files](https://github.com/roots/sage/pull/2025/files) from 9.0.0-beta versions.
38 |
39 | Folder `controllers/` changes to `Controllers/`, class file names changes to camelcase `App.php` and `FrontPage.php`. Controller namespaces changes to `namespace App\Controllers;`
40 |
41 | ### Requirements:
42 |
43 | * [PHP](http://php.net/manual/en/install.php) >= 7.0
44 |
45 | ## Setup
46 |
47 | By default Controller uses namespace `Controllers`.
48 |
49 | Controller takes advantage of [PSR-4 autoloading](https://www.php-fig.org/psr/psr-4/). To change the namespace, use the filter below within `functions.php`
50 |
51 | ```php
52 |
53 | add_filter('sober/controller/namespace', function () {
54 | return 'Data';
55 | });
56 | ```
57 |
58 | ## Usage
59 |
60 | ### Overview:
61 |
62 | * Controller class names follow the same hierarchy as WordPress.
63 | * The Controller class name should match the filename
64 | * For example `App.php` should define class as `class App extends Controller`
65 | * Create methods within the Controller Class;
66 | * Use `public function` to return data to the Blade views/s
67 | * The method name becomes the variable name in Blade
68 | * Camel case is converted to snake case. `public function ExampleForUser` in the Controller becomes `$example_for_user` in the Blade template
69 | * If the same method name is declared twice, the latest instance will override the previous
70 | * Use `public static function` to use run the method from your Blade template which returns data. This is useful for loops
71 | * The method name is not converted to snake case
72 | * You access the method using the class name, followed by the method. `public static function Example` in `App.php` can be run in Blade using `App::Example()`
73 | * If the same method name is declared twice, the latest instance will override the previous
74 | * Use `protected function` for internal methods. These will not be exposed to Blade. You can run them within `__construct`
75 | * Dependency injection with type hinting is available through `__construct`
76 |
77 |
78 | The above may sound complicated on first read, so let's take a look at some examples to see how simple Controller is to use.
79 |
80 | ### Basic Controller;
81 |
82 | The following example will expose `$images` to `resources/views/single.blade.php`
83 |
84 | **app/Controllers/Single.php**
85 |
86 | ```php
87 |
112 | @foreach($images as $image)
113 |
114 | @endforeach
115 |
116 | @endif
117 | ```
118 |
119 | ### Using Functions;
120 |
121 | You can use static methods to run a function from within your view.
122 |
123 | This is useful if you are within the loop and want to return data for each post item.
124 |
125 | **app/Controllers/Archive.php**
126 |
127 | ```php
128 | post_title;
139 | }
140 | }
141 | ```
142 |
143 | **resources/views/archive.php**
144 |
145 | ```php
146 | @extends('layouts.app')
147 |
148 | @section('content')
149 |
150 | @while (have_posts()) @php the_post() @endphp
151 | {{ Archive::title() }}
152 | @endwhile
153 |
154 | @endsection
155 | ```
156 |
157 | ### Using Components;
158 |
159 | You can also create reusable components and include them in any Controller class using PHP traits.
160 |
161 | **app/Controllers/Partials/Images.php**
162 |
163 | ```php
164 | data is set up, but before the class methods are run
320 | }
321 |
322 | public function __after()
323 | {
324 | // runs after all the class methods have run
325 | }
326 | ```
327 |
328 | ### Disable Option;
329 |
330 | ```php
331 | protected $active = false;
332 | ```
333 |
334 | ### Blade Debugger;
335 |
336 | In your Blade views, `resources/views`, you can use the following to assist with debugging;
337 |
338 | * `@debug`
339 | * `@dump(__var__)`
340 |
341 | ### Blade Coder;
342 |
343 | In your Blade views, `resources/views`, you can use the following to assist with jump-starting coding;
344 |
345 | * `@code`
346 | * `@code('__name of variable as string__')`
347 |
348 | To wrap the code in if statements, use `@codeif`
349 |
350 | * `@codeif`
351 | * `@codeif('__name of variable as string__')`
352 |
353 | ## Support
354 |
355 | * Follow [@withjacoby](https://twitter.com/withjacoby) on Twitter
356 | * Buy me a beer or pay my rent, [paypal.me/darrenjacoby](https://paypal.me/darrenjacoby)
357 |
358 | ## Updates
359 |
360 | * Change the composer.json version to 2.1.2
361 | * Check [CHANGELOG.md](CHANGELOG.md) for any breaking changes before updating.
362 |
363 | ```shell
364 | $ composer update
365 | ```
366 |
--------------------------------------------------------------------------------
/build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "soberwp/controller",
3 | "type": "package",
4 | "license": "MIT",
5 | "description": "WordPress package to enable a basic controller when using Blade with Sage 9",
6 | "homepage": "https://github.com/soberwp",
7 | "authors": [
8 | {
9 | "name": "Darren Jacoby",
10 | "email": "darren@jacoby.co.za",
11 | "homepage": "https://github.com/darrenjacoby"
12 | }
13 | ],
14 | "keywords": [
15 | "wordpress"
16 | ],
17 | "support": {
18 | "issues": "https://github.com/soberwp/controller/issues"
19 | },
20 | "require": {
21 | "php": ">=5.6.0",
22 | "brain/hierarchy": "^2.4.0"
23 | },
24 | "require-dev": {
25 | "squizlabs/php_codesniffer": "^3.2"
26 | },
27 | "autoload": {
28 | "psr-4": {
29 | "Sober\\Controller\\": "src/"
30 | },
31 | "files": [
32 | "controller.php"
33 | ]
34 | },
35 | "scripts": {
36 | "test": [
37 | "phpcs --extensions=php --ignore=vendor/ ."
38 | ]
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "4e348d5744a8bb335cceb88e0ecf3804",
8 | "packages": [
9 | {
10 | "name": "brain/hierarchy",
11 | "version": "2.4.0",
12 | "source": {
13 | "type": "git",
14 | "url": "https://github.com/Brain-WP/Hierarchy.git",
15 | "reference": "d0970df66ca36c8a86115401462800d2fe206fd9"
16 | },
17 | "dist": {
18 | "type": "zip",
19 | "url": "https://api.github.com/repos/Brain-WP/Hierarchy/zipball/d0970df66ca36c8a86115401462800d2fe206fd9",
20 | "reference": "d0970df66ca36c8a86115401462800d2fe206fd9",
21 | "shasum": ""
22 | },
23 | "require": {
24 | "php": ">=5.5"
25 | },
26 | "require-dev": {
27 | "antecedent/patchwork": "~1.3.0",
28 | "brain/monkey": "~1.2",
29 | "gmazzap/andrew": "~1.0",
30 | "mockery/mockery": "0.9.3",
31 | "phpunit/phpunit": "~4.8",
32 | "symfony/finder": "~2.7.0"
33 | },
34 | "suggest": {
35 | "symfony/finder": "Allows loading of templates using Symfony finder component."
36 | },
37 | "type": "library",
38 | "extra": {
39 | "branch-alias": {
40 | "dev-master": "1.2.x-dev",
41 | "dev-dev": "2.0.x-dev"
42 | }
43 | },
44 | "autoload": {
45 | "psr-4": {
46 | "Brain\\Hierarchy\\": "src/"
47 | }
48 | },
49 | "notification-url": "https://packagist.org/downloads/",
50 | "license": [
51 | "MIT"
52 | ],
53 | "authors": [
54 | {
55 | "name": "Giuseppe Mazzapica",
56 | "email": "giuseppe.mazzapica@gmail.com"
57 | }
58 | ],
59 | "description": "No-dependencies package that embodies WordPress template hierarchy",
60 | "keywords": [
61 | "wordpress"
62 | ],
63 | "time": "2018-11-02T07:14:19+00:00"
64 | }
65 | ],
66 | "packages-dev": [
67 | {
68 | "name": "squizlabs/php_codesniffer",
69 | "version": "3.3.2",
70 | "source": {
71 | "type": "git",
72 | "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
73 | "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e"
74 | },
75 | "dist": {
76 | "type": "zip",
77 | "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/6ad28354c04b364c3c71a34e4a18b629cc3b231e",
78 | "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e",
79 | "shasum": ""
80 | },
81 | "require": {
82 | "ext-simplexml": "*",
83 | "ext-tokenizer": "*",
84 | "ext-xmlwriter": "*",
85 | "php": ">=5.4.0"
86 | },
87 | "require-dev": {
88 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
89 | },
90 | "bin": [
91 | "bin/phpcs",
92 | "bin/phpcbf"
93 | ],
94 | "type": "library",
95 | "extra": {
96 | "branch-alias": {
97 | "dev-master": "3.x-dev"
98 | }
99 | },
100 | "notification-url": "https://packagist.org/downloads/",
101 | "license": [
102 | "BSD-3-Clause"
103 | ],
104 | "authors": [
105 | {
106 | "name": "Greg Sherwood",
107 | "role": "lead"
108 | }
109 | ],
110 | "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
111 | "homepage": "http://www.squizlabs.com/php-codesniffer",
112 | "keywords": [
113 | "phpcs",
114 | "standards"
115 | ],
116 | "time": "2018-09-23T23:08:17+00:00"
117 | }
118 | ],
119 | "aliases": [],
120 | "minimum-stability": "stable",
121 | "stability-flags": [],
122 | "prefer-stable": false,
123 | "prefer-lowest": false,
124 | "platform": {
125 | "php": ">=5.6.0"
126 | },
127 | "platform-dev": []
128 | }
129 |
--------------------------------------------------------------------------------
/controller.php:
--------------------------------------------------------------------------------
1 | getClassesToRun() as $class) {
48 | // Create the class on the DI container
49 | $controller = $container->make($class);
50 |
51 | // Set the params required for template param
52 | $controller->__setParams();
53 |
54 | // Determine template location to expose data
55 | $location = "sage/template/{$controller->__getTemplateParam()}-data/data";
56 |
57 | // Pass data to filter
58 | add_filter($location, function ($data) use ($container, $class) {
59 | // Recreate the class so that $post is included
60 | $controller = $container->make($class);
61 |
62 | // Params
63 | $controller->__setParams();
64 |
65 | // Lifecycle
66 | $controller->__before();
67 |
68 | // Data
69 | $controller->__setData($data);
70 |
71 | // Lifecycle
72 | $controller->__after();
73 |
74 | // Return
75 | return $controller->__getData();
76 | }, 10, 2);
77 | }
78 | }
79 |
80 | /**
81 | * Blade
82 | */
83 | function blade()
84 | {
85 | // Get Sage function
86 | $sage = sage();
87 |
88 | // Return if function does not exist
89 | if (!$sage) {
90 | return;
91 | }
92 |
93 | // Debugger
94 | $sage('blade')->compiler()->directive('debug', function () {
95 | return '';
96 | });
97 |
98 | $sage('blade')->compiler()->directive('dump', function ($param) {
99 | return "dump({$param}); ?>";
100 | });
101 |
102 | // Coder
103 | $sage('blade')->compiler()->directive('code', function ($param) {
104 | $param = ($param) ? $param : 'false';
105 | return "";
106 | });
107 |
108 | $sage('blade')->compiler()->directive('codeif', function ($param) {
109 | $param = ($param) ? $param : 'false';
110 | return "";
111 | });
112 | }
113 |
114 | /**
115 | * Hooks
116 | */
117 | if (function_exists('add_action')) {
118 | add_action('init', __NAMESPACE__ . '\loader');
119 | add_action('init', __NAMESPACE__ . '\blade');
120 | }
121 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Sober Coding Standards
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Blade.php:
--------------------------------------------------------------------------------
1 | data = $data['__data']['__blade'];
18 |
19 | // Only worry about rewriting data if we have more than one class
20 | if (count($this->data) > 1) {
21 | // Get first item from data array
22 | $first = reset($this->data);
23 |
24 | // Get last item from data array
25 | $last = end($this->data);
26 |
27 | // If last item does not inherit tree and first class is App
28 | if (!$last->tree && $first->class === 'App') {
29 | // Rewrite $this->data with first (App) and last item in array
30 | $this->data = [$first, $last];
31 | // Else if $last does not inherit tree
32 | } elseif (!$last->tree) {
33 | // Rewrite $this->data with last item in array
34 | $this->data = $last;
35 | }
36 | }
37 |
38 | return $this;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Blade/Coder.php:
--------------------------------------------------------------------------------
1 | codeif = $codeif;
24 |
25 | // Set data from @code('var')
26 | $this->setIncludeData($includes);
27 |
28 | // Set data from $data['__data']['__blade']
29 | $this->setBladeData($data);
30 |
31 | // Render to view
32 | $this->render();
33 | }
34 |
35 | private function setIncludeData($includes)
36 | {
37 | $this->includes = $includes;
38 |
39 | if (is_string($this->includes)) {
40 | $this->includes = [$this->includes];
41 | }
42 | }
43 |
44 | /**
45 | * Increase Indentation
46 | *
47 | * Add two spaces to $this->indentation
48 | */
49 | private function increaseIndentation()
50 | {
51 | $this->indentation = "{$this->indentation} ";
52 | }
53 |
54 | /**
55 | * Decrease Indentation
56 | *
57 | * Remove two spaces from $this->indentation
58 | */
59 | private function decreaseIndentation()
60 | {
61 | $this->indentation = substr($this->indentation, 0, -2);
62 | }
63 |
64 | /**
65 | * Render
66 | *
67 | * Loop through $this->data and echo code
68 | */
69 | private function render()
70 | {
71 | // Map the data results to exclude static methods
72 | $this->data = array_map(function ($item) {
73 | return $item->data;
74 | }, $this->data);
75 |
76 | // Remove the first level of the array so that we are left with flat variables
77 | $this->data = call_user_func_array('array_merge', $this->data);
78 |
79 | // Remove $post by default
80 | unset($this->data['post']);
81 |
82 | // Start @code block
83 | $type = ($this->codeif ? '@codeif' : '@code');
84 | echo "{$type}
";
85 |
86 | // Run through each item
87 | foreach ($this->data as $name => $value) {
88 | // Remove the method/returned for data methods
89 | $value = (isset($value->method) ? $value->returned : $value);
90 |
91 | // Router
92 | // @code('var')
93 | if ($this->includes && in_array($name, $this->includes)) {
94 | $this->router($name, $value);
95 | }
96 |
97 | // @code
98 | if (!$this->includes) {
99 | $this->router($name, $value);
100 | }
101 | }
102 |
103 | // End @code block
104 | echo '
';
105 | }
106 |
107 | /**
108 | * Router
109 | *
110 | * Route data types to correct methods
111 | */
112 | private function router($name, $val)
113 | {
114 | // Route object
115 | if (is_object($val)) {
116 | $this->renderObj($name, $val);
117 | }
118 |
119 | // Route indexed array
120 | if (is_array($val) && Utils::isArrayIndexed($val)) {
121 | $this->renderArrIndexed($name, $val);
122 | }
123 |
124 | // Route array with keys
125 | if (is_array($val) && !Utils::isArrayIndexed($val)) {
126 | $this->renderArrKeys($name, $val);
127 | }
128 |
129 | // Route strings/other
130 | if (!is_array($val) && !is_object($val)) {
131 | // Add to $this->code
132 | $this->renderResult($name, $val);
133 |
134 | // Clear out $this-code
135 | $this->code = '';
136 |
137 | // Exit
138 | return;
139 | }
140 | }
141 |
142 | /**
143 | * Render Object
144 | *
145 | * Render an object
146 | */
147 | private function renderObj($name, $val)
148 | {
149 | // Get props of object
150 | $props = get_object_vars($val);
151 |
152 | // For each of those props
153 | foreach ($props as $prop_name => $prop_val) {
154 | // Add to $this->code
155 | $this->code = "{$this->code}{$name}->";
156 |
157 | // Route new values
158 | $this->router($prop_name, $prop_val);
159 | }
160 | }
161 |
162 | /**
163 | * Render Indexed Array
164 | *
165 | * Render an indexed array
166 | */
167 | private function renderArrIndexed($name, $val)
168 | {
169 | if ($this->codeif) {
170 | // Echo if
171 | echo "{$this->indentation}@if (\${$this->code}$name)
";
172 |
173 | // Increase indentation
174 | $this->increaseIndentation();
175 | }
176 |
177 | // Start foreach
178 | echo "{$this->indentation}@foreach (\${$this->code}$name as \$item)
";
179 |
180 | // Clear $this->code
181 | $this->code = '';
182 |
183 | // Increase indentation
184 | $this->increaseIndentation();
185 |
186 | // Route next value
187 | foreach ($val as $key_index => $key_val) {
188 | if (count($val) > 1) {
189 | echo "{$this->indentation}[{$key_index}]
";
190 | }
191 | $this->router('item', $key_val);
192 | }
193 |
194 | // Decrease indentation
195 | $this->decreaseIndentation();
196 |
197 | // End foreach
198 | echo "{$this->indentation}@endforeach
";
199 |
200 | if ($this->codeif) {
201 | // Decrease indentation
202 | $this->decreaseIndentation();
203 |
204 | // Echo endif
205 | echo "{$this->indentation}@endif
";
206 | }
207 | }
208 |
209 | /**
210 | * Render Array Keys
211 | *
212 | * Render an array with keys
213 | */
214 | private function renderArrKeys($name, $val)
215 | {
216 | // Foreach value add key
217 | foreach ($val as $key_name => $key_val) {
218 | $this->router("{$this->code}{$name}['{$key_name}']", $key_val);
219 | }
220 | }
221 |
222 | /**
223 | * Render Result
224 | *
225 | * Render the final result
226 | */
227 | private function renderResult($name, $val)
228 | {
229 | $this->code = "\${$this->code}{$name}";
230 |
231 | if ($this->codeif) {
232 | // Echo if
233 | echo "{$this->indentation}@if ({$this->code})
";
234 |
235 | // Increase indentation
236 | $this->increaseIndentation();
237 | }
238 |
239 | // Wrap with {{ }} or {!! !!}
240 | if (Utils::doesStringContainMarkup($val)) {
241 | $this->code = "{!! {$this->code} !!}";
242 | } else {
243 | $this->code = "{{ {$this->code} }}";
244 | }
245 |
246 | // Echo code
247 | echo "{$this->indentation}{$this->code}
";
248 |
249 | if ($this->codeif) {
250 | // Increase indentation
251 | $this->decreaseIndentation();
252 |
253 | // Echo endif
254 | echo "{$this->indentation}@endif
";
255 | }
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/src/Blade/Debugger.php:
--------------------------------------------------------------------------------
1 | setBladeData($data);
21 |
22 | // Render to view
23 | $this->render();
24 | }
25 |
26 | /**
27 | * Render
28 | *
29 | * Loop through $this->data and echo debugger information
30 | */
31 | private function render()
32 | {
33 | echo '
34 | ';
43 |
44 | echo '@debug
';
45 |
46 | foreach ($this->data as $index => $controller) {
47 | // Set the class params for each Controller
48 | $this->controller = $controller;
49 |
50 | // Echo the Controller class name
51 | echo $controller->class;
52 |
53 | // Echo extends tree if the Controller implements tree
54 | if ($controller->tree) {
55 | echo 'extends tree';
56 | }
57 |
58 | echo '
';
59 |
60 | // Render data
61 | if ($controller->data) {
62 | $this->renderData();
63 | }
64 |
65 | // Render methods
66 | if ($controller->methods) {
67 | $this->renderMethods();
68 | }
69 |
70 | echo '
';
71 | }
72 |
73 | echo '';
74 | }
75 |
76 | /**
77 | * Render Data
78 | *
79 | * Render the current Controller item data
80 | */
81 | private function renderData()
82 | {
83 | echo 'Data';
84 |
85 | foreach ($this->controller->data as $name => $data) {
86 | // Remove previous overrides
87 | $override = false;
88 |
89 | // Search the data log array for this name
90 | $key = array_search($name, array_column($this->controllerDataLog, 'name'));
91 |
92 | // If the name exists in the data log array then get the class
93 | if ($key !== false) {
94 | $override = $this->controllerDataLog[$key]['class'];
95 | }
96 |
97 | // Update data log array with this items name and class
98 | $this->controllerDataLog[] = [
99 | 'name' => $name,
100 | 'class' => $this->controller->class
101 | ];
102 |
103 | // Get data type
104 | $dataType = (isset($data->method) ? gettype($data->returned) : gettype($data));
105 |
106 | // Echo
107 | echo "- {$name}";
108 |
109 | // Override
110 | if ($override) {
111 | echo "overrides {$override}";
112 | }
113 |
114 | // Data type
115 | echo "{$dataType}";
116 |
117 | // Method lines
118 | if (isset($data->method)) {
119 | echo "line {$data->method->getStartLine()}—{$data->method->getEndLine()}";
120 | }
121 |
122 | echo '
';
123 | }
124 |
125 | echo '
';
126 | }
127 |
128 | /**
129 | * Render Methods
130 | *
131 | * Render the current Controller item methods
132 | */
133 | private function renderMethods()
134 | {
135 | echo 'Methods';
136 |
137 | foreach ($this->controller->methods as $method) {
138 | echo "- {$method->name}line {$method->getStartLine()}—{$method->getEndLine()}
";
139 | }
140 |
141 | echo '
';
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/Controller.php:
--------------------------------------------------------------------------------
1 | class
56 | $this->class = new \ReflectionClass($this);
57 |
58 | // $this->classAcf
59 | if (class_exists('Acf')) {
60 | $this->classAcf = new Acf();
61 | }
62 |
63 | // $this->template
64 | if (!$this->template) {
65 | $this->template = Utils::convertToKebabCase($this->class->getShortName());
66 | }
67 |
68 | // $this->tree
69 | if ($this->class->implementsInterface('\Sober\Controller\Module\Tree')) {
70 | $this->tree = true;
71 | }
72 | }
73 |
74 | /**
75 | * Set Controller Data
76 | *
77 | * Set the Controller raw data for this Controller
78 | * @return $this
79 | */
80 | final public function __setData($incomingData)
81 | {
82 | $this->incomingData = $incomingData;
83 |
84 | // Set the data from the WordPress post if singular to $this->data
85 | $this->__setDataFromPost();
86 |
87 | // Set the data from Advanced Custom Fields to $this->data
88 | $this->__setDataFromModuleAcf();
89 |
90 | // Set incoming filter data from Sage to App before Debugger
91 | $this->__setDataFromFilter();
92 |
93 | // Set the public methods from the class to $this->methods
94 | $this->__setDataFromMethods();
95 |
96 | // Set debugger data first to use only the raw data from the Controller
97 | $this->__setBladeData();
98 |
99 | // Set app data to $this->data['__app'] or merge with current data
100 | $this->__setAppData();
101 |
102 | // Set tree data to $this->data['__tree'] or merge with current data
103 | $this->__setTreeData();
104 | }
105 |
106 | /**
107 | * Set Data From Post
108 | *
109 | * Set the WordPress post
110 | */
111 | private function __setDataFromPost()
112 | {
113 | // Only set data from $post to App class
114 | if ($this->template !== 'app') {
115 | return;
116 | }
117 |
118 | // Only continue if $post is available
119 | if (!is_singular()) {
120 | return;
121 | }
122 |
123 | // Set $this->post to allow users to use $this->post->post_title and others
124 | $this->post = get_post();
125 |
126 | // Set the post array to be included in $this->data
127 | $this->data['post'] = $this->post;
128 | }
129 |
130 | /**
131 | * Set Data From Module Acf
132 | *
133 | * Set the Advanced Custom Fields data automatically
134 | */
135 | private function __setDatafromModuleAcf()
136 | {
137 | // If $this->acf is not set then return
138 | if (!$this->acf) {
139 | return;
140 | }
141 |
142 | // Set the fields data passed in from Controller
143 | $this->classAcf->setData($this->acf);
144 |
145 | // Get the options page is $this->acf is set to true on App
146 | if ($this->template === 'app' && is_bool($this->acf)) {
147 | $this->classAcf->setDataOptionsPage();
148 | }
149 |
150 | // Deterime if acf/array filter is enabled and return correct format
151 | $this->classAcf->setDataReturnFormat();
152 |
153 | // If there is no data return
154 | if (!$this->classAcf->getData()) {
155 | return;
156 | }
157 |
158 | // Merge the data from Acf module
159 | $this->data = array_merge($this->data, $this->classAcf->getData());
160 | }
161 |
162 | /**
163 | * Set Sage Filter Data
164 | *
165 | * Merge $this->data with $this->incomingData as Sage filters run before -data class filters
166 | */
167 | private function __setDataFromFilter()
168 | {
169 | if ($this->template === 'app') {
170 | // Merge all incoming data from app to allow Sage add_filter support
171 | $this->data = array_merge($this->data, $this->incomingData);
172 | }
173 | }
174 |
175 | /**
176 | * Set Methods
177 | *
178 | * Set $this->methods with all public methods
179 | */
180 | private function __setDataFromMethods()
181 | {
182 | // Get all public methods from class
183 | $this->methods = $this->class->getMethods(\ReflectionMethod::IS_PUBLIC);
184 |
185 | // Remove __contruct, __init, __finalize and this class methods from $this->methods
186 | $this->methods = array_filter($this->methods, function ($method) {
187 | return
188 | $method->class !== 'Sober\Controller\Controller' &&
189 | $method->name !== '__construct' &&
190 | $method->name !== '__before' &&
191 | $method->name !== '__after';
192 | });
193 |
194 | // Get all public static methods from class
195 | $this->staticMethods = $this->class->getMethods(\ReflectionMethod::IS_STATIC);
196 |
197 | // Remove $this->staticMethods from $this->methods using array_diff
198 | $this->dataMethods = array_diff($this->methods, $this->staticMethods);
199 |
200 | // Filter the remaining data methods
201 | $this->dataMethods = array_filter($this->dataMethods, function ($method) {
202 | return $method = $method->name;
203 | });
204 |
205 | // For each method convert method name to snake case and add to data[key => value]
206 | foreach ($this->dataMethods as $method) {
207 | // Convert method name to snake case
208 | $var = Utils::convertToSnakeCase($method->name);
209 |
210 | // Add var method name to data[]
211 | $this->data[$var] = $this->{$method->name}();
212 | }
213 | }
214 |
215 | /**
216 | * Set Blade Data
217 | *
218 | * Update $this->data with __blade
219 | */
220 | private function __setBladeData()
221 | {
222 | // Get the data
223 | $debuggerData = $this->data;
224 |
225 | // Loop through each data method
226 | foreach ($this->dataMethods as $dataMethod) {
227 | // Convert the key to snake case to find in $debuggerData
228 | $key = Utils::convertToSnakeCase($dataMethod->name);
229 |
230 | // Save the returned value from the above key
231 | $returned = $debuggerData[$key];
232 |
233 | // Recreate the key with the method included
234 | $debuggerData[$key] = (object) [
235 | 'method' => $dataMethod,
236 | 'returned' => $returned
237 | ];
238 | }
239 |
240 | // Create the final debugger object
241 | $debugger = (object) [
242 | 'class' => $this->class->getShortName(),
243 | 'tree' => $this->tree,
244 | 'methods' => $this->staticMethods,
245 | 'data' => $debuggerData
246 | ];
247 |
248 | // Include current debugger data in existing debugger array
249 | $this->incomingData['__blade'][] = $debugger;
250 |
251 | // Set the updated array to $this->data for @debug use
252 | $this->data['__blade'] = $this->incomingData['__blade'];
253 | }
254 |
255 | /**
256 | * Set App Data
257 | *
258 | * Update $this->data with __app
259 | */
260 | private function __setAppData()
261 | {
262 | if ($this->template === 'app') {
263 | // Save the app data in $this->data['__app']
264 | $this->data['__app'] = $this->data;
265 | // Esc the function
266 | return;
267 | }
268 |
269 | // Save the app data to this $this->data['__app'] for next Controller
270 | $this->data['__app'] = $this->incomingData['__app'];
271 |
272 | // Include the app data with this current items data
273 | $this->data = array_merge($this->data['__app'], $this->data);
274 | }
275 |
276 | /**
277 | * Set Tree Data
278 | *
279 | * Update $this->data with tree data if required and store existing data
280 | */
281 | private function __setTreeData()
282 | {
283 | if ($this->tree) {
284 | // Include existing data with this Controller data
285 | $this->data = array_merge($this->incomingData['__store'], $this->data);
286 | }
287 |
288 | // Save updated data to $this->data['__store'] for next Controller
289 | $this->data['__store'] = array_merge($this->incomingData, $this->data);
290 | }
291 |
292 | /**
293 | * Get Template Param
294 | *
295 | * @return string
296 | */
297 | final public function __getTemplateParam()
298 | {
299 | return ($this->active ? $this->template : false);
300 | }
301 |
302 | /**
303 | * Get Controller Data
304 | *
305 | * @return array
306 | */
307 | final public function __getData()
308 | {
309 | return ($this->active ? $this->data : []);
310 | }
311 | }
312 |
--------------------------------------------------------------------------------
/src/Loader.php:
--------------------------------------------------------------------------------
1 | hierarchy = $hierarchy;
30 |
31 | // Set the default or custom namespace used for Controller files
32 | $this->setNamespace();
33 |
34 | // Set the path using $this->namespace assuming PSR4 autoloading
35 | $this->setPath();
36 |
37 | // Return if there are no Controller files
38 | if (!file_exists($this->path)) {
39 | return;
40 | }
41 |
42 | // Set the list of files from the Controller files namespace/path
43 | $this->setListOfFiles();
44 |
45 | // Set the classes to run from the list of files
46 | $this->setClassesToRun();
47 |
48 | // Set the aliases for static functions from the list of classes to run
49 | $this->setClassesAlias();
50 |
51 | // Add the -data body classes for the Blade filter
52 | $this->addBodyDataClasses();
53 | }
54 |
55 | /**
56 | * Set Namespace
57 | *
58 | * Set the namespace from the filter or use the default
59 | */
60 | protected function setNamespace()
61 | {
62 | $this->namespace =
63 | (has_filter('sober/controller/namespace')
64 | ? apply_filters('sober/controller/namespace', rtrim($this->namespace))
65 | : 'App\Controllers');
66 | }
67 |
68 | /**
69 | * Set Path
70 | *
71 | * Set the path assuming PSR4 autoloading from $this->namespace
72 | */
73 | protected function setPath()
74 | {
75 | $reflection = new \ReflectionClass($this->namespace .'\App');
76 | $this->path = dirname($reflection->getFileName());
77 | }
78 |
79 | /**
80 | * Set File List
81 | *
82 | * Recursively get file list and place into array
83 | */
84 | protected function setListOfFiles()
85 | {
86 | $this->listOfFiles = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->path));
87 | }
88 |
89 | /**
90 | * Set Class Instances
91 | *
92 | * Load each Class instance and store in $instances[]
93 | */
94 | protected function setClassesToRun()
95 | {
96 | foreach ($this->listOfFiles as $filename => $file) {
97 | // Exclude non-PHP files
98 | if (!Utils::isFilePhp($filename)) {
99 | continue;
100 | }
101 |
102 | // Exclude non-Controller classes
103 | if (!Utils::doesFileContain($filename, 'extends Controller')) {
104 | continue;
105 | }
106 |
107 | // Set the classes to run
108 | $this->classesToRun[] = $this->namespace . '\\' . pathinfo($filename, PATHINFO_FILENAME);
109 | }
110 | }
111 |
112 | /**
113 | * Set Class Alias
114 | *
115 | * Remove namespace from static functions
116 | */
117 | public function setClassesAlias()
118 | {
119 | // Alias each class from $this->classesToRun
120 | foreach ($this->classesToRun as $class) {
121 | class_alias($class, (new \ReflectionClass($class))->getShortName());
122 | }
123 | }
124 |
125 | /**
126 | * Set Document Classes
127 | *
128 | * Set the classes required for the blade filter to pass on data
129 | * @return array
130 | */
131 | protected function addBodyDataClasses()
132 | {
133 | add_filter('body_class', function ($body) {
134 | global $wp_query;
135 |
136 | // Get the template hierarchy from WordPress
137 | $templates = $this->hierarchy->getTemplates($wp_query);
138 |
139 | // Reverse the templates returned from $this->hierarchy
140 | $templates = array_reverse($templates);
141 |
142 | // Add app-data to classes array
143 | $classes[] = 'app-data';
144 |
145 | foreach ($templates as $template) {
146 | // Exclude .blade.php and index.php
147 | if (strpos($template, '.blade.php') || $template === 'index.php') {
148 | continue;
149 | }
150 |
151 | // Exclude index as we use app
152 | if ($template === 'index') {
153 | $template = 'index.php';
154 | }
155 |
156 | // Replace .php with -data and add to the classes array
157 | $classes[] = basename(str_replace('.php', '-data', $template));
158 | }
159 |
160 | // Return the new body class list for WordPress
161 | return array_merge($body, $classes);
162 | });
163 | }
164 |
165 | /**
166 | * Get Classes To Run
167 | *
168 | * @return array
169 | */
170 | public function getClassesToRun()
171 | {
172 | return $this->classesToRun;
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/src/Module/Acf.php:
--------------------------------------------------------------------------------
1 | setReturnFilter();
22 | }
23 |
24 | /**
25 | * Set Return Filter
26 | *
27 | * Return filter sober/controller/acf-array
28 | */
29 | private function setReturnFilter()
30 | {
31 | $this->returnArrayFormat =
32 | (has_filter('sober/controller/acf/array')
33 | ? apply_filters('sober/controller/acf/array', $this->returnArrayFormat)
34 | : false);
35 | }
36 |
37 | /**
38 | * Iterates over array and adds a new snake cased key, with orignial value, for each kebab cased key
39 | *
40 | * Return void
41 | */
42 | private function recursiveSnakeCase(&$data) {
43 | if(!is_array($data))
44 | return;
45 |
46 | foreach ($data as $key => $val) {
47 | if (is_array($val)) {
48 | $this->recursiveSnakeCase($val);
49 | } else {
50 | $data[Utils::convertKebabCaseToSnakeCase($key)] = $val;
51 | }
52 | }
53 | }
54 |
55 | /**
56 | * Set Data Return Format
57 | *
58 | * Return object from array if acf/array filter is not set to true
59 | */
60 | public function setDataReturnFormat()
61 | {
62 | if ($this->returnArrayFormat) {
63 | return;
64 | }
65 |
66 | if ($this->data) {
67 | foreach ($this->data as $key => $item) {
68 | $this->data[$key] = json_decode(json_encode($item));
69 | }
70 | }
71 | }
72 |
73 | /**
74 | * Set Data Options Page
75 | *
76 | * Set data from the options page
77 | */
78 | public function setDataOptionsPage()
79 | {
80 | if (!function_exists('acf_add_options_page')) {
81 | return [];
82 | }
83 |
84 | if (get_fields('options')) {
85 | $this->data['acf_options'] = get_fields('options');
86 | } else {
87 | return [];
88 | }
89 | }
90 |
91 | /**
92 | * Set Data
93 | *
94 | * Set data from passed in field keys
95 | */
96 | public function setData($acf)
97 | {
98 | $query = get_queried_object();
99 |
100 | if (!acf_get_valid_post_id($query)) {
101 | return;
102 | }
103 |
104 | if (is_bool($acf)) {
105 | $this->data = get_fields($query);
106 | }
107 |
108 | if (is_string($acf)) {
109 | $this->data = [$acf => get_field($acf, $query)];
110 | }
111 |
112 | if (is_array($acf)) {
113 | foreach ($acf as $item) {
114 | $this->data[$item] = get_field($item, $query);
115 | }
116 | }
117 |
118 | $this->recursiveSnakeCase($this->data);
119 | }
120 |
121 | /**
122 | * Get Data
123 | *
124 | * Return the data
125 | * @return array
126 | */
127 | public function getData()
128 | {
129 | return is_array($this->data) ? $this->data : [];
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/Module/Tree.php:
--------------------------------------------------------------------------------
1 |