Search by

micschk / silverstripe-cmstweaks

micschk

This package is abandoned and no longer maintained. The author suggests using the restruct/silverstripe-admintweaks package instead.

Various admin tweaks & enhancements

Package info

github.com/restruct/silverstripe-admintweaks

Language:SCSS

Type:silverstripe-vendormodule

pkg:composer/micschk/silverstripe-cmstweaks

Statistics

Installs: 33

Dependents: 0

Suggesters: 0

Stars: 2

Open Issues: 3

4.4.0 2026-10-22 15:35 UTC

README

A portable toolkit of admin UI enhancements, form field utilities, template helpers, and development conveniences for SilverStripe projects.

Namespace: Restruct\Silverstripe\AdminTweaks

Module line Silverstripe PHP Status
^4 (branch main) 6.x ^8.3 active development
^3 (branch v3) 4.13+, 5.x ^7.4 | ^8 security and bug fixes until Silverstripe 5 EOL (April 2027)

Installation

composer require restruct/silverstripe-admintweaks:^4   # Silverstripe 6
composer require restruct/silverstripe-admintweaks:^3   # Silverstripe 4 / 5

Upgrading from 3.x? See UPGRADING.md - the CMS menu behaviour and the task invocations both changed.

Quick Start

Most features are opt-in via configuration. The module provides sensible defaults for:

  • Secure session cookies
  • GDPR-compliant UserDefinedForm (no server storage)
  • Higher image quality defaults
  • 24-hour default cache, 1-hour app cache

Features Overview

Category Features Documentation
Admin UI Page icons, Bootstrap Icon classes, menu grouping, permission badges, CMS system switcher Icons
Form Fields CopyTextField, MultivalueSortField, Bootstrap styling Form Fields
GridField Editable+orderable configs, versioned ordering GridField
Templates 30+ helper methods, iterators, image placeholders Template Helpers
SiteConfig Contact info, social media, theme settings, raw HTML SiteConfig
Caching HTTP request caching, JSON/JSON-LD parsing Caching & Helpers
Email & Logging System sender from env, error email reports Email & Logging
QueuedJobs Queue report recipient filled from env; ScheduledMethodCall and cleanup-broken-jobs moved to queuedjobs-enhancements QueuedJobs

Feature Highlights

Admin UI Enhancements

  • Page Icons - Stylish icons for common page types using Bootstrap Icons (FA optional)
  • Bootstrap Icon Classes - Standard bi bi-* classes mirroring .font-icon-* pattern (CDN-compatible)
  • Menu Grouping - Groups admin sections under "Advanced" (requires symbiote/silverstripe-grouped-cms-menu)
  • Permission Badges - Shows permission codes in Security admin
  • Checkbox Fixes - Proper handling of unchecked checkboxes in editable GridFields
  • Search-popover fixes - GridField 'Search options' popover: checkboxset/optionset holders no longer collapse/overlap when a form-control class leaks onto the field holder (eg from project code that decorates scaffolded search fields for front-end reuse), and long option lists scroll within the popover (automatic, CSS-only)

Per-ModelAdmin Tweaks (opt-in via config)

ModelAdminExtension is registered on ModelAdmin; enable per subclass (or project-wide via yml on SilverStripe\Admin\ModelAdmin, with per-subclass overrides):

class ProductAdmin extends ModelAdmin
{
    // Open the GridField search bar on load instead of hiding it behind the magnifier icon
    private static bool $auto_expand_gridfield_search = true;

    // Hide the framework-scaffolded Export to CSV / Print / Import CSV buttons
    // (the scaffolded export just dumps summary_fields; the default CsvBulkLoader
    // import is a data-integrity risk on synced/managed models)
    private static bool $hide_scaffolded_csv_buttons = true;
}

hide_scaffolded_csv_buttons also switches the CSV import itself off ($showImportForm = false), not only its button: without that, ModelAdmin keeps accepting a hand-built import POST. To apply it to every ModelAdmin:

SilverStripe\Admin\ModelAdmin:
  hide_scaffolded_csv_buttons: true

# SecurityAdmin is a ModelAdmin too; keep its member/group CSV import if you use it
SilverStripe\Admin\SecurityAdmin:
  hide_scaffolded_csv_buttons: false
// Add Bootstrap icon to a button
FormAction::create('add', 'Add Item')->addExtraClass('bi bi-plus-circle');

CMS System Switcher (opt-in via config)

For organisations running several Silverstripe CMSes side by side (eg a public site, a document system and an e-learning platform): the admin menu header names the CMS you are in, with the environment, and opens a dropdown with links to the other CMSes.

---
Name: app-systemswitcher
---
Restruct\Silverstripe\AdminTweaks\SystemSwitcher:
  current: site            # this CMS's key below; differs per project
  systems:                 # the same block can be shared by every CMS
    site: { display: 'Example SITE', url: 'https://www.example.org/admin' }
    docs: { display: 'Example DOCS', url: 'https://docs.example.org/admin' }
    lms:  { display: 'Example LMS',  url: 'https://learn.example.org/admin' }
---
Name: app-systemswitcher-test
After: '#app-systemswitcher'  # so this fragment merges over the one above
Only:
  environment: test
---
Restruct\Silverstripe\AdminTweaks\SystemSwitcher:
  systems:                 # only the urls differ per environment; display is merged from above
    site: { url: 'https://site-test.example.org/admin' }
    docs: { url: 'https://docs-test.example.org/admin' }
    lms:  { url: 'https://learn-test.example.org/admin' }
  • Off until systems is configured: the core header ($SiteConfig.Title, linking to the site) and the admin <title> stay exactly as Silverstripe renders them.
  • Header = the current system's display plus an environment suffix: (TEST) on test, (DEV) on dev, nothing on live. Without a display for current (or without current) the name is LeftAndMain.application_name. It replaces $SiteConfig.Title in the header, so a public site's name does not show there. The suffix shows as a small badge after the name, without its surrounding spaces and one enclosing pair of brackets ( (TEST) -> TEST, [acceptance] -> acceptance); a name too long for the header wraps to two lines and is then cut off with an ellipsis, the badge never is. No suffix (live): no badge. The button's tooltip and accessible name keep the full label (Example SITE (TEST)).
  • Admin title: while the switcher is on, that same label becomes LeftAndMain.application_name for the request, so the browser tab reads Example SITE (TEST) - Pages (also after pjax navigation). The suffix is never doubled, also not when application_name already ends with it.
  • Dropdown lists the other systems, links in the same tab (switching moves you to the other CMS; ctrl/cmd-click opens a new tab as usual). Entries without a display, or with a url that is not an absolute http(s):// URL, are left out. Keyboard: the header is a button (aria-expanded), Escape closes. All configured strings are HTML-escaped.
  • Collapsed menu shows the initials of the name ("Example SITE" -> ES), with the environment badge under them; set initials on a system to choose them yourself, eg site: { display: 'Example SITE', initials: 'SI', url: ... }.
  • Suffix strings are configurable per environment type (the admin title uses them as written, the header badge as described above):
Restruct\Silverstripe\AdminTweaks\SystemSwitcher:
  environment_suffixes:
    test: ' [acceptance]'
  • With restruct/silverstripe-login-branding and its application_name_overrides_title: works the same, the switcher shows its own label in the header either way. To keep one place for the name there, set LeftAndMain.application_name and leave display out for the current system (it defaults to application_name).
  • Each system keeps its own login. The header template is an override of core's SilverStripe/Admin/Includes/LeftAndMain_MenuLogo.ss; a theme or project that overrides that include as well takes precedence, and the switcher then does not show.

Form Fields

  • CopyTextField - Read-only field with copy-to-clipboard button
  • MultivalueSortField - Sortable multi-value field
  • Bootstrap Styling - Auto-adds Bootstrap classes to form fields (opt-in)
CopyTextField::create('ApiKey', 'API Key', $apiKey)
    ->setButtonLabel('Copy')
    ->setShowAlert(true);

GridField Configurations

// Inline editing with drag-drop ordering
$config = GridFieldConfigs::editable_orderable();

// Filterable, orderable with record editor
$config = GridFieldConfigs::filterable_orderable_recordeditor();

Template Helpers

<!-- Environment checks -->
<% if $IsDev %>Debug mode<% end_if %>

<!-- Image placeholder SVG -->
<% include ImagePlaceholder W=180, H=50, Label='logo' %>

<!-- Theme resource URL -->
<video src="{$themeDirResourceURL('my-theme')}/video.mp4"></video>

<!-- Extra iterators -->
<% loop $Items %>
  <div class="col-{$GroupSize}-of-4">$Title</div>
<% end_loop %>

Cached HTTP Requests

use Restruct\Silverstripe\AdminTweaks\Helpers\CacheHelpers;

// Cached JSON API request (1 hour TTL)
$data = CacheHelpers::cached_json_request('https://api.example.com/data', 'GET', [], 3600);

// Extract JSON-LD from a webpage
$jsonLd = CacheHelpers::cached_jsonLD_request('https://example.com/product');

QueuedJobs

ScheduledMethodCall and the cleanup-broken-jobs task are no longer part of this module. Both live in restruct/silverstripe-queuedjobs-enhancements (namespace Restruct\QueuedJobsEnhancements, requires Silverstripe 5.4+), as Restruct\QueuedJobsEnhancements\Jobs\ScheduledMethodCall and sake dev/tasks/cleanup-broken-jobs (Silverstripe 5) / sake tasks:cleanup-broken-jobs (Silverstripe 6). See that module's README for usage.

What this module still does for queuedjobs:

  • Fills Email.queued_job_admin_email (the recipient of queuedjobs' broken/stalled job reports) from APP_LOG_MAIL_RECIPIENT, only when the project has not set it (_config.php). Set it to false to opt out of those mails.
  • Records the running job and its memory use in $_SERVER, so error mails from the queue runner name the job (QueuedJobContextExtension, deprecated: queuedjobs-enhancements 1.7.0+ registers its own copy and this one then stays off).

Configuration

Email & SMTP (via .env)

APP_SYSTEM_EMAIL_* fills Email.admin_email when the project has not set it. SMTP is configured with Symfony Mailer's MAILER_DSN; the APP_SMTP_* variables of the 3.x line (SwiftMailer, Silverstripe 4 only) do nothing here.

APP_SYSTEM_EMAIL_SENDER="My App"
APP_SYSTEM_EMAIL_ADDRESS="noreply@example.com"

MAILER_DSN="smtp://postmaster%40mg.example.com:secret@smtp.mailgun.org:587"

# Error email logging (omit on dev/test to disable)
APP_LOG_MAIL_RECIPIENT="admin@example.com"
APP_LOG_MAIL_SUBJECT="Error on MyApp"
APP_LOG_MAIL_SENDER="noreply@example.com"
APP_LOG_MAIL_LEVEL="error"

SiteConfig Extension (opt-in)

SilverStripe\SiteConfig\SiteConfig:
  extensions:
    - Restruct\Silverstripe\AdminTweaks\Extensions\SiteConfigExtension
  enable_contact_social_media_fields: true
  enable_raw_head_body_fields: true
  enable_browser_color_theme_field: true

Bootstrap Form Classes (opt-in)

SilverStripe\Forms\FormField:
  extensions:
    - Restruct\Silverstripe\AdminTweaks\Extensions\FormFieldBootstrapExtension

Optional Module Integration

The module enhances functionality when these modules are installed:

Module Enhancement
symbiote/silverstripe-grouped-cms-menu Groups admin sections under "Advanced"
symbiote/silverstripe-queuedjobs Job context in error mails; report recipient from env (see QueuedJobs)
wilr/silverstripe-googlesitemaps Auto-activates sitemap generation
silverstripe/mimevalidator Auto-activates MIME upload validation
wedevelopnl/silverstripe-webp-images Activates WEBP format support (no Silverstripe 6 release yet)
restruct/silverstripe-shortcodable Registers CurrentYear/FeaturedImage shortcodes

Traits

EnforceCMSPermission

Ties a DataObject's canView / canEdit / canDelete / canCreate to Pages section access: each returns Permission::check('CMS_ACCESS_CMSMain') for the current user. The $member argument is ignored, so canEdit($someOtherMember) still answers for whoever is logged in. Holders of CMS_ACCESS_LeftAndMain (all CMS sections) and ADMIN pass as well, as Permission::check() treats those as implying CMS_ACCESS_CMSMain.

use Restruct\Silverstripe\AdminTweaks\Traits\EnforceCMSPermission;

class MyDataObject extends DataObject
{
    use EnforceCMSPermission;
}

Default Behaviors

These are applied automatically:

  • Session.cookie_secure: true - Secure session cookies
  • UserDefinedForm submissions disabled by default (GDPR)
  • Image backend quality 90 (Image_Backend.Quality)
  • URL segments: & becomes -et- instead of core's -and- (no other replacement changed)
  • Password policy (_config/passwords.yml): swaps core's default EntropyPasswordValidator for RulesPasswordValidator with minimum length 12, history 6 and minimum test score 3 on live; minimum length 5 and no history check on dev/test
  • Cache lifetimes: cacheblock (template <% cached %>) 24 hours; appcache and adminCache 1 hour
  • GoogleSitemap enabled when wilr/silverstripe-googlesitemaps is installed (1000 objects per sitemap, Google/Bing notification on, honours ShowInSearch)
  • GridFieldPageCount removed from the GridField configs that fire updateConfig (GridFieldConfig_Base, _RecordEditor, _RecordViewer, _RelationEditor and their subclasses)
  • From env, only when the project has not set them: Email.admin_email from APP_SYSTEM_EMAIL_ADDRESS + APP_SYSTEM_EMAIL_SENDER, Email.queued_job_admin_email from APP_LOG_MAIL_RECIPIENT

Hiding the Reports and Campaigns CMS sections is opt-in since 4.1 (#54), and applied at runtime so no After: ordering is needed to override it:

SilverStripe\Admin\LeftAndMain:
  hide_rarely_used_menu_sections: true

SS3/SS4 → SS5 migration repair tasks

Three BuildTasks that fix asset artifacts left behind by a migration to SS5. All are dry-run by default — pass --apply to write. Run them in this order:

# Task Fixes
1 sake tasks:fix-folder-filefilename /admin/assets dies with HashFileIDHelper::buildFileID requires an $hash value. Folder rows must have an EMPTY FileFilename (a folder derives its path from the parent chain); a populated value makes the folder's visibility lookup build a file ID with no hash. Also reports empty-FileHash File rows, which trip the same exception.
2 sake tasks:fix-misclassified-images Image files carried over with ClassName = File instead of Image. Blank tiles in the asset-admin grid — but more importantly, a has_one Image relation pointing at such a record renders nothing on the front end, because Fill()/FitMax()/ScaleWidth() don't exist on File. Resolves the target class via File::get_class_for_file_extension(), so .svg correctly becomes your registered SVG image class rather than Image.
3 sake tasks:generate-cms-thumbnails Blank tiles in the asset-admin file grid. The grid is served by GraphQL and asset-admin deliberately injects a non-generating thumbnail generator (ThumbnailGenerator.graphql → Generates: false), so it emits the __FitMax[...] URL but never creates the variant. Normal uploads generate variants on save; migrated files never went through SS5, so their variants don't exist and the <img> 404s. SS4 ran ImageThumbnailHelper inside MigrateFileTask — SS5 removed the task but kept the helper. This runs it.

Order matters: (2) before (3), because the thumbnail helper skips anything whose getIsImage() is false — a file still stuck on ClassName = File would be passed over.

Prerequisite for (3): the files must actually be migrated (a populated FileHash). On a database where FileHash is empty there are no stored bytes to make a thumbnail from, and the task will correctly report that it generated nothing.

Task (3) requires silverstripe/asset-admin; it hides itself if that isn't installed.

Building Assets

cd _dev/admintweaks
npm install
npm run dev        # Development build
npm run production # Production build
npm run watch      # Watch mode

Detailed Documentation

License

BSD-3-Clause