fromholdio/silverstripe-cms-fields-scaffolder

Installs: 329

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 1

Forks: 0

Open Issues: 0

Type:silverstripe-vendormodule

pkg:composer/fromholdio/silverstripe-cms-fields-scaffolder

2.0.0 2025-10-08 23:43 UTC

This package is auto-updated.

Last update: 2025-10-08 23:43:59 UTC


README

A SilverStripe module for scaffolding CMS field tab structures and placement via YAML configuration. This module simplifies the process of organizing CMS fields into custom tab structures across different contexts (CMS edit forms, Settings forms, custom forms).

Features

  • Section-based configuration: Define different tab structures for different contexts (cms, settings, or custom sections)
  • Declarative tab structure: Define your entire tab hierarchy in YAML
  • Automatic tab creation: TabSets and Tabs are created automatically based on configuration
  • Field label integration: Tab titles automatically use your DataObject's field_labels configuration
  • Content field relocation: Move the default Content field to a custom tab location
  • Root.Main tab relocation: Move fields from the default Root.Main tab to a custom location
  • Empty tab cleanup: Automatically remove empty tabs and tabsets
  • Field removal: Remove unwanted fields by name

Installation

composer require fromholdio/silverstripe-cms-fields-scaffolder

Requirements

  • SilverStripe ^4.1 || ^5.0 || ^6.0

Usage

Basic Setup

  1. Apply the extension to your DataObject:
Page:
  extensions:
    - Fromholdio\CMSFieldsScaffolder\CMSFieldsScaffolder
  1. Configure your tab structure:
Page:
  scaffolder:
    cms:
      tabs:
        MainTabSet:
          - MainTab
          - DetailsTab
        ContentTabSet:
          - ContentMainTab
          - ContentBlocksTab
  field_labels:
    MainTabSet: Main
    MainTab: Main
    DetailsTab: Details
    ContentTabSet: Content
    ContentMainTab: Main
    ContentBlocksTab: Blocks
  1. Integrate into your getCMSFields() method:
public function getCMSFields()
{
    $this->beforeUpdateCMSFields(function (FieldList $fields) {
        return $this->runCMSFieldsScaffolderBeforeUpdate($fields);
    });

    $this->afterUpdateCMSFields(function (FieldList $fields) {
        return $this->runCMSFieldsScaffolderAfterUpdate($fields, 'cms', false);
    });

    $fields = parent::getCMSFields();

    // Your custom field additions here

    $fields = $this->clearCMSFieldsScaffolderEmptyTabs($fields, 'cms');
    $fields = $this->removeCMSFieldsScaffolderFields($fields, 'cms');

    return $fields;
}

Section Keys

The module supports section-based configuration, allowing you to define different tab structures for different contexts. Common section keys include:

  • cms - For the main CMS edit form (getCMSFields)
  • settings - For the Settings tab (getSettingsFields)
  • Custom keys - For any other form context you define

Each section can have its own independent configuration.

Configuration Options

Each section supports the following configuration options:

tabs

Defines the tab structure. Can be a simple array of tab names, or a nested structure for TabSets:

Page:
  scaffolder:
    cms:
      tabs:
        # Simple TabSet with child Tabs
        MainTabSet:
          - MainTab
          - DetailsTab
        # Another TabSet
        ContentTabSet:
          - ContentMainTab

do_clear_empty_tabs

Boolean. When true, automatically removes empty tabs and tabsets after all fields have been added. Default: true

Page:
  scaffolder:
    cms:
      do_clear_empty_tabs: true

content_field_tab_path

Specifies where to move the default Content field. Set to false to remove it entirely, or provide a tab path:

Page:
  scaffolder:
    cms:
      content_field_tab_path: 'Root.ContentTabSet.ContentMainTab'
      # Or to remove it:
      # content_field_tab_path: false

root_main_tab_path

Specifies where to move fields from the default Root.Main tab:

Page:
  scaffolder:
    cms:
      root_main_tab_path: 'Root.MainTabSet.MainTab'

fields_to_remove

Array of field names to remove from the form:

Page:
  scaffolder:
    cms:
      fields_to_remove:
        - UnwantedField
        - AnotherField

Complete Example

Here's a complete example showing multiple sections:

Page:
  extensions:
    - Fromholdio\CMSFieldsScaffolder\CMSFieldsScaffolder
  scaffolder:
    cms:
      do_clear_empty_tabs: true
      content_field_tab_path: 'Root.ContentTabSet.ContentMainTab'
      root_main_tab_path: 'Root.MainTabSet.MainTab'
      tabs:
        MainTabSet:
          - MainTab
          - DetailsTab
        ContentTabSet:
          - ContentMainTab
          - ContentBlocksTab
        SEOTabSet:
          - SEOMainTab
    settings:
      do_clear_empty_tabs: true
      root_main_tab_path: 'Root.MainTabSet.MainTab'
      tabs:
        MainTabSet:
          - MainTab
        AccessTabSet:
          - AccessMainTab
  field_labels:
    MainTabSet: Main
    MainTab: Main
    DetailsTab: Details
    ContentTabSet: Content
    ContentMainTab: Main
    ContentBlocksTab: Blocks
    SEOTabSet: SEO
    SEOMainTab: SEO
    AccessTabSet: Access
    AccessMainTab: Main

Integration Patterns

For getCMSFields()

public function getCMSFields()
{
    $this->beforeUpdateCMSFields(function (FieldList $fields) {
        return $this->runCMSFieldsScaffolderBeforeUpdate($fields);
        // Or with explicit section: $this->runCMSFieldsScaffolderBeforeUpdate($fields, 'cms');
    });

    $this->afterUpdateCMSFields(function (FieldList $fields) {
        return $this->runCMSFieldsScaffolderAfterUpdate($fields, 'cms', false);
    });

    $fields = parent::getCMSFields();

    // Add your custom fields here

    $fields = $this->clearCMSFieldsScaffolderEmptyTabs($fields, 'cms');
    $fields = $this->removeCMSFieldsScaffolderFields($fields, 'cms');

    return $fields;
}

For getSettingsFields()

public function getSettingsFields()
{
    $this->beforeExtending('updateSettingsFields', function (FieldList $fields) {
        return $this->runCMSFieldsScaffolderBeforeUpdate($fields, 'settings');
    });

    $this->afterExtending('updateSettingsFields', function (FieldList $fields) {
        return $this->runCMSFieldsScaffolderAfterUpdate($fields, 'settings', false);
    });

    $fields = parent::getSettingsFields();

    // Add your custom fields here

    $fields = $this->clearCMSFieldsScaffolderEmptyTabs($fields, 'settings');
    $fields = $this->removeCMSFieldsScaffolderFields($fields, 'settings');

    return $fields;
}

API Methods

runCMSFieldsScaffolderBeforeUpdate(FieldList $fields, string $key = 'cms'): FieldList

Runs before extensions update fields. Creates tabs and relocates the Content field.

runCMSFieldsScaffolderAfterUpdate(FieldList $fields, string $key = 'cms', bool $doClearTabs = true): FieldList

Runs after extensions update fields. Relocates Root.Main tab fields and optionally clears empty tabs.

clearCMSFieldsScaffolderEmptyTabs(FieldList $fields, string $key = 'cms'): FieldList

Removes empty tabs and tabsets based on the section's do_clear_empty_tabs setting.

removeCMSFieldsScaffolderFields(FieldList $fields, string $key = 'cms'): FieldList

Removes fields specified in the section's fields_to_remove configuration.

getCMSFieldsScaffolderSetting(string $name, string $key = 'cms')

Retrieves a specific configuration setting for a section.

applyCMSFieldsScaffolderTabs(FieldList $fields, string $key = 'cms'): FieldList

Creates the tab structure defined in the section's tabs configuration.

applyCMSFieldsScaffolderContentFieldTabPath(FieldList $fields, string $key = 'cms'): FieldList

Relocates or removes the Content field based on the section's content_field_tab_path configuration.

applyCMSFieldsScaffolderRootMainTabPath(FieldList $fields, string $key = 'cms'): FieldList

Relocates fields from Root.Main (or Root.Settings) to the path specified in root_main_tab_path.

Version History & Breaking Changes

Version 2.x (Current - With Section Keys)

The current version uses section-based configuration where each context (cms, settings, etc.) has its own configuration block.

Configuration structure:

Page:
  scaffolder:
    cms:                    # Section key
      tabs: ...
      do_clear_empty_tabs: true
    settings:               # Another section key
      tabs: ...

Method signatures:

runCMSFieldsScaffolderBeforeUpdate(FieldList $fields, string $key = 'cms'): FieldList
runCMSFieldsScaffolderAfterUpdate(FieldList $fields, string $key = 'cms', bool $doClearTabs = true): FieldList
clearCMSFieldsScaffolderEmptyTabs(FieldList $fields, string $key = 'cms'): FieldList
removeCMSFieldsScaffolderFields(FieldList $fields, string $key = 'cms'): FieldList
getCMSFieldsScaffolderSetting(string $name, string $key = 'cms')

Default section configurations:

private static $scaffolder = [
    'cms' => [
        'tabs' => null,
        'do_clear_empty_tabs' => true,
        'content_field_tab_path' => 'Root.ContentTabSet.ContentMainTab',
        'root_main_tab_path' => 'Root.MainTabSet.MainTab',
        'fields_to_remove' => null,
    ],
    'settings' => [
        'tabs' => null,
        'do_clear_empty_tabs' => true,
        'content_field_tab_path' => 'Root.ContentTabSet.ContentMainTab',
        'root_main_tab_path' => 'Root.MainTabSet.MainTab',
        'fields_to_remove' => null,
    ],
];

Version 1.x (Legacy - Without Section Keys)

The legacy version used a flat configuration structure without section keys.

Configuration structure:

Page:
  scaffolder:
    tabs: ...              # Direct configuration, no section key
    do_clear_empty_tabs: true

Method signatures:

runCMSFieldsScaffolderBeforeUpdate(FieldList $fields): FieldList
runCMSFieldsScaffolderAfterUpdate(FieldList $fields): FieldList
clearCMSFieldsScaffolderEmptyTabs(FieldList $fields): FieldList
removeCMSFieldsScaffolderFields(FieldList $fields): FieldList
getCMSFieldsScaffolderSetting(string $key)

Default configuration:

private static $scaffolder = [
    'tabs' => null,
    'do_clear_empty_tabs' => true,
    'content_field_tab_path' => 'Root.ContentTabSet.ContentMainTab',
    'root_main_tab_path' => 'Root.MainTabSet.MainTab',
    'fields_to_remove' => null
];

Migrating from 1.x to 2.x

If you're upgrading from version 1.x, you'll need to:

  1. Update your YAML configuration to nest settings under section keys:
# Old (1.x)
Page:
  scaffolder:
    tabs:
      MainTabSet:
        - MainTab

# New (2.x)
Page:
  scaffolder:
    cms:                    # Add section key
      tabs:
        MainTabSet:
          - MainTab
  1. Update method calls to include the section parameter:
// Old (1.x)
$this->runCMSFieldsScaffolderBeforeUpdate($fields);
$this->runCMSFieldsScaffolderAfterUpdate($fields);
$this->clearCMSFieldsScaffolderEmptyTabs($fields);

// New (2.x)
$this->runCMSFieldsScaffolderBeforeUpdate($fields, 'cms');
$this->runCMSFieldsScaffolderAfterUpdate($fields, 'cms', false);
$this->clearCMSFieldsScaffolderEmptyTabs($fields, 'cms');

Note: The section parameter defaults to 'cms', so you can omit it for CMS forms if desired.

License

BSD-3-Clause

Maintainer

Luke Fromhold - https://fromhold.io