diff --git a/.editorconfig b/.editorconfig
index ecbdec66..11241731 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -14,5 +14,11 @@ trim_trailing_whitespace = false
[*.php]
indent_size = 4
+[src/Console/stubs/*.stub]
+indent_size = 4
+
+[src/Console/stubs/views/*.stub]
+indent_size = 2
+
[*.blade.php]
indent_size = 2
diff --git a/LICENSE.md b/LICENSE.md
index ea7da0e7..9d66fdb7 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2019 Brandon Nifong
+Copyright (c) 2020 Brandon Nifong
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 4faa80a0..c090ad87 100644
--- a/README.md
+++ b/README.md
@@ -4,34 +4,129 @@


-ACF Composer assists you with ~~creating~~ **composing** Fields and Blocks using [ACF Builder](https://github.com/stoutlogic/acf-builder) alongside [Sage 10](https://github.com/roots/sage).
+ACF Composer assists you with ~~creating~~ **composing** Fields, Blocks, Widgets, and Options pages using [ACF Builder](https://github.com/stoutlogic/acf-builder) alongside [Sage 10](https://github.com/roots/sage).
## Requirements
- [Sage](https://github.com/roots/sage) >= 10.0
- [ACF](https://www.advancedcustomfields.com/) >= 5.8.0
+- [PHP](https://secure.php.net/manual/en/install.php) >= 7.2
+- [Composer](https://getcomposer.org/download/)
## Installation
+Install via Composer:
+
```bash
$ composer require log1x/acf-composer
```
## Usage
-### Publish Config
+### Basic Usage
+
+Start by publishing the `config/acf.php` configuration file using Acorn:
```bash
$ wp acorn vendor:publish --provider="Log1x\AcfComposer\Providers\AcfComposerServiceProvider"
```
-Initialize fields, blocks, and default field type values in `config/acf.php`.
+Looking at the config file, you will see documented keys for configuration. When creating a field group, simply add each class to their respective type.
+
+### Generating a Field
+
+Generating fields with ACF Composer is done using Acorn.
+
+To create your first field, start by running the following command from your theme directory:
+
+```bash
+$ wp acorn acf:field Example
+```
+
+This will create `src/Fields/Example.php` which is where you will create and manage your field group.
+
+Once finished, follow up by uncommenting `App\Fields\Example::class` in `acf.php`.
+
+Taking a glance at the generated `Example.php` stub, you will notice that it has a simple list configured.
+
+Proceed by checking the `Add Post` for the field to ensure things are working as intended– and then [get to work](https://github.com/Log1x/acf-builder-cheatsheet).
+
+### Generating a Block
+
+Generating a Block is generally the same as generating a field as seen above.
+
+Start by creating the Block field using Acorn:
+
+```bash
+$ wp acorn acf:block Example
+```
+
+Optionally, you may pass `--full` to the command above to generate a stub that contains additional configuration examples.
+
+```bash
+$ wp acorn acf:block Example --full
+```
+
+Once finished, similarily to Fields, simply add the new block, `App\Blocks\Example::class` to `config/acf.php`.
-### Create a Block or Field
+When running the ACF Block generator, one difference to a generic field is an accompanied View is generated in the `resources/views/blocks` directory.
+
+Like the Field generator, the example block contains a simple list repeater and is working out of the box.
+
+### Generating a Widget
+
+Creating a sidebar widget using ACF Composer is extremely easy. Widgets are automatically loaded and rendered with Blade. Batteries included.
+
+Start by creating a Widget using Acorn:
```bash
-$ wp acorn acf:block MyBlock
-$ wp acorn acf:field MyField
+$ wp acorn acf:widget Example
+```
+
+Once finished, simply add `App\Widgets\Example::class` to the `widgets` key in `config/acf.php`
+
+Similar to Blocks, Widgets are also accompanied by a view generated in `resources/views/widgets`.
+
+Out of the box, the Example widget is ready to go and should appear in the backend.
+
+### Generating an Options Page
+
+When creating a field, you have the option of populating the `$options` variable automatically generating an Options page as well as setting the field group location.
+
+Start by creating an option page using Acorn:
+
+```bash
+$ wp acorn acf:options Options
+```
+
+Outside of the `$options` variable being set in the options stub, it is effectively a Field. That being said, `App\Fields\Options::class` should be registered in the `fields` array in `config/acf.php`
+
+### Field Defaults
+
+One of my personal favorite features of ACF Composer is thet ability to set field defaults.
+
+Taking a look at `config/acf.php`, you will see a few pre-configured defaults:
+
+```php
+'defaults' => [
+ 'trueFalse' => ['ui' => 1],
+ 'select' => ['ui' => 1],
+],
+```
+
+When setting `trueFalse` and `select` to have their `ui` set to `1` by default, it is no longer necessary to repeatedly set `'ui' => 1` on your fields. This takes effect globally and can be overridden by simply setting a different value on a field.
+
+Here are a few others that I personally use:
+
+```php
+'defaults' => [
+ 'fieldGroup' => ['instruction_placement' => 'acfe_instructions_tooltip'],
+ 'repeater' => ['layout' => 'block', 'acfe_repeater_stylised_button' => 1],
+ 'postObject' => ['ui' => 1, 'return_format' => 'object'],
+ 'accordion' => ['multi_expand' => 1],
+ 'group' => ['layout' => 'table', 'acfe_group_modal' => 1],
+ 'tab' => ['placement' => 'left'],
+],
```
## Bug Reports
diff --git a/composer.json b/composer.json
index 6de1f9e1..2459c790 100644
--- a/composer.json
+++ b/composer.json
@@ -1,7 +1,7 @@
{
"name": "log1x/acf-composer",
"type": "package",
- "description": "ACF Composer assists you with creating Fields and Blocks with ACF Builder and Roots Sage",
+ "description": "Create fields, blocks, and more using ACF Builder and Roots Sage",
"license": "MIT",
"authors": [
{
@@ -22,7 +22,8 @@
},
"suggest": {
"stoutlogic/acf-builder": "Provides an essential fluid interface for creating ACF fields.",
- "log1x/sage-directives": "Provides Sage-specific Blade directives for use with WordPress and ACF within your views."
+ "log1x/sage-directives": "Provides Sage-specific Blade directives for use with WordPress and ACF within your views.",
+ "log1x/modern-acf-options": "Gives ACF option pages a much needed design overhaul."
},
"extra": {
"acorn": {
diff --git a/config/acf.php b/config/acf.php
index a97a7170..d5e262f0 100644
--- a/config/acf.php
+++ b/config/acf.php
@@ -30,6 +30,20 @@
// App\Blocks\Example::class,
],
+ /*
+ |--------------------------------------------------------------------------
+ | Sidebar Widgets
+ |--------------------------------------------------------------------------
+ |
+ | The widgets listed here will be automatically loaded on the
+ | request to your application.
+ |
+ */
+
+ 'widgets' => [
+ // App\Widgets\Example::class,
+ ],
+
/*
|--------------------------------------------------------------------------
| Default Field Type Settings
diff --git a/resources/views/view-404.blade.php b/resources/views/view-404.blade.php
deleted file mode 100644
index f27112a2..00000000
--- a/resources/views/view-404.blade.php
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
- View not found.
-
-
-
- {{ $view }}
-
-
diff --git a/src/Block.php b/src/Block.php
index c22a4382..2e21ba1a 100644
--- a/src/Block.php
+++ b/src/Block.php
@@ -2,28 +2,52 @@
namespace Log1x\AcfComposer;
-use Roots\Acorn\Application;
use Illuminate\Support\Str;
use Illuminate\Support\Arr;
-use function Roots\asset;
-use function Roots\view;
-
-abstract class Block
+abstract class Block extends Composer
{
/**
- * The application instance.
+ * The block properties.
+ *
+ * @var array
+ */
+ protected $block;
+
+ /**
+ * The block content.
+ *
+ * @var string
+ */
+ protected $content;
+
+ /**
+ * The block preview status.
+ *
+ * @var bool
+ */
+ protected $preview;
+
+ /**
+ * The current post ID.
+ *
+ * @param int
+ */
+ protected $post;
+
+ /**
+ * The block prefix.
*
- * @var \Roots\Acorn\Application
+ * @var string
*/
- protected $app;
+ protected $prefix = 'acf/';
/**
- * Default field type settings.
+ * The block namespace.
*
- * @return array
+ * @var string
*/
- protected $defaults = [];
+ protected $namespace;
/**
* The display name of the block.
@@ -96,285 +120,76 @@ abstract class Block
protected $supports = [];
/**
- * Assets enqueued when the block is shown.
- *
- * @var array
- */
- protected $assets = [];
-
- /**
- * The blocks status.
- *
- * @var boolean
- */
- protected $enabled = true;
-
- /**
- * The block field groups.
- *
- * @var array
- */
- protected $fields;
-
- /**
- * The block namespace.
- *
- * @var string
- */
- protected $namespace;
-
- /**
- * The block prefix.
- *
- * @var string
- */
- protected $prefix = 'acf/';
-
- /**
- * The block properties.
- *
- * @var array
- */
- protected $block;
-
- /**
- * The block content.
- *
- * @var string
- */
- protected $content;
-
- /**
- * The block preview status.
- *
- * @var bool
- */
- protected $preview;
-
- /**
- * The current post ID.
- *
- * @param int
- */
- protected $post;
-
- /**
- * Create a new Block instance.
- *
- * @param \Roots\Acorn\Application $app
- * @return void
- */
- public function __construct(Application $app)
- {
- $this->app = $app;
- }
-
- /**
- * Compose the block.
+ * Compose and register the defined field groups with ACF.
*
+ * @param callback $callback
* @return void
*/
- public function compose()
+ public function compose($callback = null)
{
- if (! $this->register() || ! function_exists('acf')) {
+ if (empty($this->name)) {
return;
}
- collect($this->register())->each(function ($value, $name) {
- $this->{$name} = $value;
- });
-
- $this->defaults = collect(
- $this->app->config->get('acf.defaults')
- )->merge($this->defaults)->mapWithKeys(function ($value, $key) {
- return [Str::snake($key) => $value];
- });
-
- $this->slug = Str::slug($this->name);
- $this->namespace = $this->prefix . $this->slug;
- $this->fields = $this->fields();
-
- if (! $this->enabled) {
- return;
+ if (empty($this->namespace)) {
+ $this->namespace = Str::start($this->slug, $this->prefix);
}
- add_action('init', function () {
- acf_register_block([
- 'name' => $this->slug,
- 'title' => $this->name,
- 'description' => $this->description,
- 'category' => $this->category,
- 'icon' => $this->icon,
- 'keywords' => $this->keywords,
- 'post_types' => $this->post_types,
- 'mode' => $this->mode,
- 'align' => $this->align,
- 'supports' => $this->supports,
- 'enqueue_assets' => [$this, 'assets'],
- 'render_callback' => function ($block, $content = '', $preview = false, $post = 0) {
- $this->block = (object) $block;
- $this->content = $content;
- $this->preview = $preview;
- $this->post = $post;
-
- echo $this->view();
- }
- ]);
-
- if (! empty($this->fields)) {
- if ($this->defaults->has('field_group')) {
- $this->fields = array_merge($this->fields, $this->defaults->get('field_group'));
- }
-
- if (! Arr::has($this->fields, 'location.0.0')) {
- Arr::set($this->fields, 'location.0.0', [
- 'param' => 'block',
- 'operator' => '==',
- 'value' => $this->namespace,
- ]);
- }
-
- acf_add_local_field_group($this->build());
+ parent::compose(function () {
+ if (! Arr::has($this->fields, 'location.0.0')) {
+ Arr::set($this->fields, 'location.0.0', [
+ 'param' => 'block',
+ 'operator' => '==',
+ 'value' => $this->namespace,
+ ]);
}
- }, 20);
- }
-
- /**
- * Build the field group with our default field type settings.
- *
- * @param array $fields
- * @return array
- */
- protected function build($fields = [])
- {
- return collect($fields ?: $this->fields)->map(function ($value, $key) use ($fields) {
- if (
- ! Str::contains($key, ['fields', 'sub_fields', 'layouts']) ||
- (Str::is($key, 'type') && ! $this->defaults->has($value))
- ) {
- return $value;
- }
-
- return array_map(function ($field) {
- if (collect($field)->keys()->intersect(['fields', 'sub_fields', 'layouts'])->isNotEmpty()) {
- return $this->build($field);
- }
+ });
- return array_merge($this->defaults->get($field['type'], []), $field);
- }, $value);
- })->all();
+ acf_register_block([
+ 'name' => $this->slug,
+ 'title' => $this->name,
+ 'description' => $this->description,
+ 'category' => $this->category,
+ 'icon' => $this->icon,
+ 'keywords' => $this->keywords,
+ 'post_types' => $this->post_types,
+ 'mode' => $this->mode,
+ 'align' => $this->align,
+ 'supports' => $this->supports,
+ 'enqueue_assets' => [$this,'assets'],
+ 'render_callback' => [$this, 'render']
+ ]);
}
/**
- * URI for the block.
+ * Render the ACF block.
*
- * @return string
+ * @param array $block
+ * @param string $content
+ * @param bool $preview
+ * @param int $post
+ * @return void
*/
- protected function uri($path = '')
+ public function render($block, $content = '', $preview = false, $post = 0)
{
- return str_replace(
- get_theme_file_path(),
- get_theme_file_uri(),
- home_url($path)
+ $this->block = (object) $block;
+ $this->content = $content;
+ $this->preview = $preview;
+ $this->post = $post;
+
+ echo $this->view(
+ Str::finish('views.blocks.', $this->slug),
+ ['block' => $this]
);
}
/**
- * View used for rendering the block.
- *
- * @return string
- */
- public function view()
- {
- if (view($view = $this->app->resourcePath("views/blocks/{$this->slug}.blade.php"))) {
- return view(
- $view,
- array_merge($this->with(), ['block' => $this->block])
- )->render();
- }
-
- if (view()->exists($this->app->resourcePath('views/blocks/view-404.blade.php'))) {
- return view(
- $this->app->resourcePath('views/blocks/view-404.blade.php'),
- ['view' => $view]
- )->render();
- }
-
- return view(
- __DIR__ . '/../resources/views/view-404.blade.php',
- ['view' => $view]
- )->render();
- }
-
- /**
- * Assets used when rendering the block.
+ * Assets enqueued when rendering the block.
*
* @return void
*/
- public function assets()
- {
- $styles = [
- 'css/blocks/' . $this->slug . '.css',
- 'styles/blocks/' . $this->slug . '.css',
- ];
-
- $scripts = [
- 'js/blocks/' . $this->slug . '.js',
- 'scripts/blocks/' . $this->slug . '.js',
- ];
-
- if (! empty($this->assets)) {
- foreach ($this->assets as $asset) {
- if (Str::endsWith($asset, '.css')) {
- $styles = Arr::prepend($styles, $asset);
- }
-
- if (Str::endsWith($asset, '.js')) {
- $scripts = Arr::prepend($scripts, $asset);
- }
- }
- }
-
- foreach ($styles as $style) {
- if (asset($style)->exists()) {
- wp_enqueue_style($this->namespace, asset($style)->uri(), false, null);
- }
- }
-
- foreach ($scripts as $script) {
- if (asset($script)->exists()) {
- wp_enqueue_script($this->namespace, asset($script)->uri(), null, null, true);
- }
- }
- }
-
- /**
- * Data to be passed to the block before registering.
- *
- * @return array
- */
- public function register()
- {
- return [];
- }
-
- /**
- * Fields to be attached to the block.
- *
- * @return array
- */
- public function fields()
- {
- return [];
- }
-
- /**
- * Data to be passed to the rendered block.
- *
- * @return array
- */
- public function with()
+ public function enqueue()
{
- return [];
+ //
}
}
diff --git a/src/Composer.php b/src/Composer.php
new file mode 100644
index 00000000..f240dc5e
--- /dev/null
+++ b/src/Composer.php
@@ -0,0 +1,172 @@
+app = $app;
+
+ $this->defaults = collect(
+ $this->app->config->get('acf.defaults')
+ )->merge($this->defaults)->mapWithKeys(function ($value, $key) {
+ return [Str::snake($key) => $value];
+ });
+
+ if (! empty($this->name) && empty($this->slug)) {
+ $this->slug = Str::slug($this->name);
+ }
+
+ $this->fields = $this->fields();
+ }
+
+ /**
+ * Compose and register the defined field groups with ACF.
+ *
+ * @param callback $callback
+ * @return void
+ */
+ public function compose($callback = null)
+ {
+ if (! $this->fields) {
+ return;
+ }
+
+ add_action('init', function () use ($callback) {
+ if ($this->defaults->has('field_group')) {
+ $this->fields = array_merge($this->fields, $this->defaults->get('field_group'));
+ }
+
+ if ($callback) {
+ $callback();
+ }
+
+ acf_add_local_field_group($this->build());
+ }, 20);
+ }
+
+ /**
+ * Build the field group with the default field type settings.
+ *
+ * @param array $fields
+ * @return array
+ */
+ protected function build($fields = [])
+ {
+ return collect($fields ?: $this->fields)->map(function ($value, $key) {
+ if (
+ ! Str::contains($key, ['fields', 'sub_fields', 'layouts']) ||
+ (Str::is($key, 'type') && ! $this->defaults->has($value))
+ ) {
+ return $value;
+ }
+
+ return array_map(function ($field) {
+ if (collect($field)->keys()->intersect(['fields', 'sub_fields', 'layouts'])->isNotEmpty()) {
+ return $this->build($field);
+ }
+
+ return array_merge($this->defaults->get($field['type'], []), $field);
+ }, $value);
+ })->all();
+ }
+
+ /**
+ * Render the view using Blade.
+ *
+ * @param string $view
+ * @param array $with
+ * @return string
+ */
+ public function view($view, $with = [])
+ {
+ $view = $this->app->resourcePath(
+ Str::finish(
+ str_replace('.', '/', basename($view, '.blade.php')),
+ '.blade.php'
+ )
+ );
+
+ if (! file_exists($view)) {
+ return;
+ }
+
+ return $this->app->make('view')->file(
+ $view,
+ array_merge($this->with(), $with)
+ )->render();
+ }
+
+ /**
+ * Get field partial if it exists.
+ *
+ * @param string $name
+ * @param string $path
+ * @return mixed
+ */
+ public function get($name = '', $path = 'Fields')
+ {
+ $name = strtr($name, [
+ '.php' => '',
+ '.' => '/'
+ ]);
+
+ include $this->app->path(
+ Str::finish(
+ Str::finish($path, '/'),
+ Str::finish($name, '.php')
+ ),
+ );
+ }
+
+ /**
+ * The field group.
+ *
+ * @return array
+ */
+ public function fields()
+ {
+ return [];
+ }
+
+ /**
+ * Data to be passed to the rendered view.
+ *
+ * @return array
+ */
+ public function with()
+ {
+ return [];
+ }
+}
diff --git a/src/Console/BlockMakeCommand.php b/src/Console/BlockMakeCommand.php
index a43f8a64..d595271d 100644
--- a/src/Console/BlockMakeCommand.php
+++ b/src/Console/BlockMakeCommand.php
@@ -13,7 +13,6 @@ class BlockMakeCommand extends GeneratorCommand
* @var string
*/
protected $signature = 'acf:block {name* : The name of the block}
- {--with-view : Create a corresponding view for the block}
{--full : Scaffold a block that contains the complete configuration.}';
/**
@@ -21,7 +20,7 @@ class BlockMakeCommand extends GeneratorCommand
*
* @var string
*/
- protected $description = 'Create a new ACF block.';
+ protected $description = 'Create a new block using ACF.';
/**
* The type of class being generated.
@@ -39,10 +38,6 @@ public function handle()
{
parent::handle();
- if (! $this->option('with-view')) {
- return;
- }
-
$view = Str::finish(str_replace('.', '/', Str::slug(head($this->argument('name')))), '.blade.php');
$path = $this->getPaths() . '/blocks/';
@@ -97,7 +92,7 @@ protected function getStub()
*/
protected function getViewStub()
{
- return __DIR__ . '/stubs/views/block.stub';
+ return __DIR__ . '/stubs/views/repeater.stub';
}
/**
diff --git a/src/Console/OptionsMakeCommand.php b/src/Console/OptionsMakeCommand.php
new file mode 100644
index 00000000..6913e538
--- /dev/null
+++ b/src/Console/OptionsMakeCommand.php
@@ -0,0 +1,50 @@
+argument('name')))), '.blade.php');
+ $path = $this->getPaths() . '/widgets/';
+
+ if (! $this->files->exists($path)) {
+ $this->files->makeDirectory($path);
+ }
+
+ if ($this->files->exists($path . $view)) {
+ return $this->error("File {$view} already exists!");
+ }
+
+ $this->files->put($path . $view, $this->files->get($this->getViewStub()));
+
+ return $this->info("File {$view} created.");
+ }
+
+ /**
+ * Return the applications view path.
+ *
+ * @param string $name
+ * @return void
+ */
+ protected function getPaths()
+ {
+ $paths = $this->app['view.finder']->getPaths();
+
+ if (count($paths) === 1) {
+ return head($paths);
+ }
+
+ return $this->choice('Where do you want to create the view(s)?', $paths, head($paths));
+ }
+
+ /**
+ * Get the stub file for the generator.
+ *
+ * @return string
+ */
+ protected function getStub()
+ {
+ return __DIR__ . '/stubs/widget.stub';
+ }
+
+ /**
+ * Get the view stub file for the generator.
+ *
+ * @return string
+ */
+ protected function getViewStub()
+ {
+ return __DIR__ . '/stubs/views/repeater.stub';
+ }
+
+ /**
+ * Get the default namespace for the class.
+ *
+ * @param string $rootNamespace
+ * @return string
+ */
+ protected function getDefaultNamespace($rootNamespace)
+ {
+ return $rootNamespace . '\Widgets';
+ }
+}
diff --git a/src/Console/stubs/block.full.stub b/src/Console/stubs/block.full.stub
index 0c3d68e1..2575fe15 100644
--- a/src/Console/stubs/block.full.stub
+++ b/src/Console/stubs/block.full.stub
@@ -5,69 +5,102 @@ namespace DummyNamespace;
use Log1x\AcfComposer\Block;
use StoutLogic\AcfBuilder\FieldsBuilder;
-class DummyClass extends Block {
+class DummyClass extends Block
+{
/**
- * Default field type configuration.
+ * The display name of the block.
*
- * @return array
+ * @var string
+ */
+ protected $name = 'DummyClass';
+
+ /**
+ * The description of the block.
+ *
+ * @var string
+ */
+ protected $description = 'Lorem ipsum...';
+
+ /**
+ * The category this block belongs to.
+ *
+ * @var string
+ */
+ protected $category = 'common';
+
+ /**
+ * The icon of this block.
+ *
+ * @var string|array
+ */
+ protected $icon = 'star-half';
+
+ /**
+ * An array of block keywords.
+ *
+ * @var array
+ */
+ protected $keywords = [];
+
+ /**
+ * An array of post types the block will be available to.
+ *
+ * @var array
+ */
+ protected $post_types = ['post', 'page'];
+
+ /**
+ * The default display mode of the block that is shown to the user.
+ *
+ * @var string
+ */
+ protected $mode = 'preview';
+
+ /**
+ * The block alignment class.
+ *
+ * @var string
*/
- protected $defaults = [];
+ protected $align = '';
/**
- * Data to be passed to the block before registering.
+ * Features supported by the block.
+ *
+ * @var array
+ */
+ protected $supports = [];
+
+ /**
+ * Data to be passed to the block before rendering.
*
* @return array
*/
- public function register()
+ public function with()
{
return [
- 'name' => 'DummyClass', // Block name
- 'description' => 'Lorem ipsum', // Block description
- 'category' => 'formatting', // Block category
- 'icon' => '', // Block icon (Dashicons or SVG)
- 'keywords' => [], // Block keywords
- 'post_types' => ['post', 'page'], // Block post types
- 'mode' => 'preview', // Default block mode
- 'align' => '', // Default block alignment
- 'assets' => [
- 'scripts/blocks/DummySlug.js', // Assets enqueued when the block is shown.
- 'styles/blocks/DummySlug.css', // Defaults to the blocks/DummySlug.{js,css} if they exist.
- ],
- 'enabled' => true, // Block status
+ 'items' => $this->items(),
];
}
/**
- * Fields to be attached to the block.
+ * The block field group.
*
* @return array
*/
public function fields()
{
- $example = new FieldsBuilder('example');
+ $DummySlug = new FieldsBuilder('DummySlug');
- $example
+ $DummySlug
->addRepeater('items')
->addText('item')
->endRepeater();
- return $example->build();
- }
-
- /**
- * Data to be passed to the rendered block.
- *
- * @return array
- */
- public function with()
- {
- return [
- 'items' => $this->items(),
- ];
+ return $DummySlug->build();
}
/**
- * Returns the items field.
+ * Return the items field.
*
* @return array
*/
diff --git a/src/Console/stubs/block.stub b/src/Console/stubs/block.stub
index 8b29a788..a238b624 100644
--- a/src/Console/stubs/block.stub
+++ b/src/Console/stubs/block.stub
@@ -5,30 +5,50 @@ namespace DummyNamespace;
use Log1x\AcfComposer\Block;
use StoutLogic\AcfBuilder\FieldsBuilder;
-class DummyClass extends Block {
+class DummyClass extends Block
+{
/**
- * Default field type configuration.
+ * The name of the block.
*
- * @return array
+ * @var string
+ */
+ protected $name = 'DummyClass';
+
+ /**
+ * The block description.
+ *
+ * @var string
+ */
+ protected $description = 'Lorem ipsum...';
+
+ /**
+ * The block category.
+ *
+ * @var string
+ */
+ protected $category = 'common';
+
+ /**
+ * The icon of this block.
+ *
+ * @var string|array
*/
- protected $defaults = [];
+ protected $icon = 'star-half';
/**
- * Data to be passed to the block before registering.
+ * Data to be passed to the block before rendering.
*
* @return array
*/
- public function register()
+ public function with()
{
return [
- 'name' => 'DummyClass',
- 'description' => 'Lorem ipsum',
- 'category' => 'formatting',
+ 'items' => $this->items(),
];
}
/**
- * Fields to be attached to the block.
+ * The block field group.
*
* @return array
*/
@@ -45,19 +65,7 @@ class DummyClass extends Block {
}
/**
- * Data to be passed to the rendered block.
- *
- * @return array
- */
- public function with()
- {
- return [
- 'items' => $this->items(),
- ];
- }
-
- /**
- * Returns the items field.
+ * Return the items field.
*
* @return array
*/
diff --git a/src/Console/stubs/field.stub b/src/Console/stubs/field.stub
index 317db6d9..13e4ec58 100644
--- a/src/Console/stubs/field.stub
+++ b/src/Console/stubs/field.stub
@@ -5,16 +5,10 @@ namespace DummyNamespace;
use Log1x\AcfComposer\Field;
use StoutLogic\AcfBuilder\FieldsBuilder;
-class DummyClass extends Field {
+class DummyClass extends Field
+{
/**
- * Default field type configuration.
- *
- * @return array
- */
- protected $defaults = [];
-
- /**
- * Fields to be registered with the application.
+ * The field group.
*
* @return array
*/
@@ -23,9 +17,9 @@ class DummyClass extends Field {
$DummySlug = new FieldsBuilder('DummySlug');
$DummySlug
- ->addTrueFalse('enabled')
- ->addText('label')
- ->addTextarea('description')
+ ->setLocation('post_type', '==', 'post');
+
+ $DummySlug
->addRepeater('items')
->addText('item')
->endRepeater();
diff --git a/src/Console/stubs/options.stub b/src/Console/stubs/options.stub
new file mode 100644
index 00000000..efa11e7b
--- /dev/null
+++ b/src/Console/stubs/options.stub
@@ -0,0 +1,33 @@
+addRepeater('items')
+ ->addText('item')
+ ->endRepeater();
+
+ return $DummySlug->build();
+ }
+}
diff --git a/src/Console/stubs/views/block.stub b/src/Console/stubs/views/repeater.stub
similarity index 100%
rename from src/Console/stubs/views/block.stub
rename to src/Console/stubs/views/repeater.stub
diff --git a/src/Console/stubs/widget.stub b/src/Console/stubs/widget.stub
new file mode 100644
index 00000000..6857b3c0
--- /dev/null
+++ b/src/Console/stubs/widget.stub
@@ -0,0 +1,74 @@
+ $this->items(),
+ ];
+ }
+
+ /**
+ * The title of the widget.
+ *
+ * @return string
+ */
+ public function title() {
+ return get_field('title', $this->widget->id);
+ }
+
+ /**
+ * The widget field group.
+ *
+ * @return array
+ */
+ public function fields()
+ {
+ $DummySlug = new FieldsBuilder('DummySlug');
+
+ $DummySlug
+ ->addText('title');
+
+ $DummySlug
+ ->addRepeater('items')
+ ->addText('item')
+ ->endRepeater();
+
+ return $DummySlug->build();
+ }
+
+ /**
+ * Return the items field.
+ *
+ * @return array
+ */
+ public function items()
+ {
+ return get_field('items') ?: [];
+ }
+}
diff --git a/src/Field.php b/src/Field.php
index 11cd9d0a..7f90d6c5 100644
--- a/src/Field.php
+++ b/src/Field.php
@@ -2,125 +2,88 @@
namespace Log1x\AcfComposer;
-use Roots\Acorn\Application;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
-use function Roots\app_path;
-
-abstract class Field
+abstract class Field extends Composer
{
/**
- * The application instance.
- *
- * @var \Roots\Acorn\Application
- */
- protected $app;
-
- /**
- * The field group.
- *
- * @var array
- */
- protected $fields;
-
- /**
- * Default field type settings.
+ * Create an options page for this field group.
*
- * @return array
+ * @param string|array|bool
*/
- protected $defaults = [];
+ protected $options = false;
/**
- * Create a new Field instance.
+ * Compose and register the defined field groups with ACF.
*
- * @param \Roots\Acorn\Application $app
+ * @param callback $callback
* @return void
*/
- public function __construct(Application $app)
+ public function compose($callback = null)
{
- $this->app = $app;
- }
-
- /**
- * Compose the field.
- *
- * @return void
- */
- public function compose()
- {
- if (! $this->fields() || ! function_exists('acf')) {
- return;
+ if (empty($this->options)) {
+ return parent::compose();
}
- $this->fields = $this->fields();
+ if (is_array($this->options)) {
+ $this->options = collect($this->options);
- $this->defaults = collect(
- $this->app->config->get('acf.defaults')
- )->merge($this->defaults)->mapWithKeys(function ($value, $key) {
- return [Str::snake($key) => $value];
- });
+ if (! $this->options->get('menu_title')) {
+ return parent::compose();
+ }
+ }
- if (! empty($this->fields)) {
- add_action('init', function () {
- if ($this->defaults->has('field_group')) {
- $this->fields = array_merge($this->fields, $this->defaults->get('field_group'));
- }
+ if (is_string($this->options)) {
+ $this->options = collect([
+ 'menu_title' => Str::title(Str::slug($this->options, ' ')),
+ 'menu_slug' => Str::slug($this->options),
+ ]);
+ }
- acf_add_local_field_group($this->build());
- }, 20);
+ if (! $this->options->get('menu_slug')) {
+ $this->options->put('menu_slug', Str::slug($this->options->get('menu_title')));
}
- }
- /**
- * Build the field group with our default field type settings.
- *
- * @param array $fields
- * @return array
- */
- protected function build($fields = [])
- {
- return collect($fields ?: $this->fields)->map(function ($value, $key) use ($fields) {
- if (
- ! Str::contains($key, ['fields', 'sub_fields', 'layouts']) ||
- (Str::is($key, 'type') && ! $this->defaults->has($value))
- ) {
- return $value;
+ $this->options = array_merge([
+ 'page_title' => get_bloginfo('name', 'display'),
+ 'capability' => 'edit_theme_options',
+ 'position' => PHP_INT_MAX,
+ 'autoload' => true
+ ], $this->options->all());
+
+ parent::compose(function () {
+ acf_add_options_page($this->options);
+
+ if (! Arr::has($this->fields, 'location.0.0')) {
+ Arr::set($this->fields, 'location.0.0', [
+ 'param' => 'options_page',
+ 'operator' => '==',
+ 'value' => $this->options['menu_slug'],
+ ]);
}
-
- return array_map(function ($field) {
- if (collect($field)->keys()->intersect(['fields', 'sub_fields', 'layouts'])->isNotEmpty()) {
- return $this->build($field);
- }
-
- return array_merge($this->defaults->get($field['type'], []), $field);
- }, $value);
- })->all();
+ });
}
/**
- * Get field partial if it exists.
+ * A simple helper method for creating an options page.
*
* @param string $name
- * @return mixed
- */
- protected function get($name = '')
- {
- $name = strtr($name, [
- '.php' => '',
- '.' => '/'
- ]);
-
- return include app_path("Fields/{$name}.php");
- }
-
- /**
- * Fields to be attached to the field.
- *
- * @return array
+ * @param array $options
+ * @return void
*/
- public function fields()
+ public function options($name, $options = [])
{
- return [];
+ acf_add_options_page(
+ collect([
+ 'page_title' => get_bloginfo('name'),
+ 'menu_title' => Str::title($name),
+ 'menu_slug' => Str::slug($name),
+ 'update_button' => 'Update Options',
+ 'capability' => 'edit_theme_options',
+ 'position' => '999',
+ 'autoload' => true
+ ])->merge($options)->all()
+ );
}
}
diff --git a/src/Providers/AcfComposerServiceProvider.php b/src/Providers/AcfComposerServiceProvider.php
index b8517f2a..aa804c31 100644
--- a/src/Providers/AcfComposerServiceProvider.php
+++ b/src/Providers/AcfComposerServiceProvider.php
@@ -7,24 +7,25 @@
class AcfComposerServiceProvider extends ServiceProvider
{
/**
- * Register and compose fields.
+ * Register any application services.
*
* @return void
*/
public function register()
{
- collect($this->app->config->get('acf.blocks'))
- ->each(function ($block) {
- if (is_string($block)) {
- $block = new $block($this->app);
- }
-
- $block->compose();
- });
+ if (! function_exists('acf')) {
+ return;
+ }
collect($this->app->config->get('acf.fields'))
+ ->merge($this->app->config->get('acf.blocks'))
+ ->merge($this->app->config->get('acf.widgets'))
->each(function ($field) {
if (is_string($field)) {
+ if (! class_exists($field)) {
+ return;
+ }
+
$field = new $field($this->app);
}
@@ -41,14 +42,13 @@ public function boot()
{
$this->publishes([
__DIR__ . '/../../config/acf.php' => $this->app->configPath('acf.php'),
- __DIR__ . '/../../resources/views/view-404.blade.php' => $this->app->resourcePath(
- 'views/blocks/view-404.blade.php'
- ),
], 'acf-composer');
$this->commands([
- \Log1x\AcfComposer\Console\BlockMakeCommand::class,
\Log1x\AcfComposer\Console\FieldMakeCommand::class,
+ \Log1x\AcfComposer\Console\BlockMakeCommand::class,
+ \Log1x\AcfComposer\Console\WidgetMakeCommand::class,
+ \Log1x\AcfComposer\Console\OptionsMakeCommand::class,
]);
}
}
diff --git a/src/Widget.php b/src/Widget.php
new file mode 100644
index 00000000..b490a4f7
--- /dev/null
+++ b/src/Widget.php
@@ -0,0 +1,157 @@
+name)) {
+ return;
+ }
+
+ parent::compose(function () {
+ $this->widget = (object) collect(
+ Arr::get($GLOBALS, 'wp_registered_widgets')
+ )->filter(function ($value) {
+ return $value['name'] == $this->name;
+ })->pop();
+
+ $this->widget->id = Str::start($this->widget->id, 'widget_');
+ $this->id = $this->widget->id;
+
+ if (! Arr::has($this->fields, 'location.0.0')) {
+ Arr::set($this->fields, 'location.0.0', [
+ 'param' => 'widget',
+ 'operator' => '==',
+ 'value' => $this->slug,
+ ]);
+ }
+ });
+
+ add_filter('widgets_init', function () {
+ register_widget($this->widget());
+ }, 20);
+ }
+
+ /**
+ * Returns an instance of WP_Widget used to register the widget.
+ *
+ * @return WP_Widget
+ */
+ public function widget()
+ {
+ return (new class ($this) extends WP_Widget {
+ /**
+ * Create a new WP_Widget instance.
+ *
+ * @param \Log1x\AcfComposer\Widget $widget
+ * @return void
+ */
+ public function __construct($widget)
+ {
+ $this->widget = $widget;
+
+ parent::__construct(
+ $this->widget->slug,
+ $this->widget->name,
+ ['description' => $this->widget->description]
+ );
+ }
+
+ /**
+ * Render the widget for WordPress.
+ *
+ * @param array $args
+ * @param array $instance
+ * @return void
+ */
+ public function widget($args, $instance)
+ {
+ echo Arr::get($args, 'before_widget');
+
+ if (! empty($this->widget->title())) {
+ echo collect([
+ Arr::get($args, 'before_title'),
+ $this->widget->title(),
+ Arr::get($args, 'after_title')
+ ])->implode('');
+ }
+
+ echo $this->widget->view(
+ Str::finish('views.widgets.', $this->widget->slug),
+ ['widget' => $this->widget]
+ );
+
+ echo Arr::get($args, 'after_widget');
+ }
+
+ /**
+ * Output the widget settings update form.
+ * This is intentionally blank due to it being set by ACF.
+ *
+ * @param array $instance
+ * @return void
+ */
+ public function form($instance)
+ {
+ //
+ }
+ });
+ }
+}