WordPress Plugins¶
Table of Contents¶
Advanced Custom Fields (ACF)¶
Advanced Custom Fields is a premium WordPress plugin that allows you to add custom fields to posts, pages, custom post types, and more.
Installation from Lemone Repository¶
At Lemone, we maintain ACF Pro in our own Codepot repository for version control and consistent deployments.
Steps:
-
Go to the ACF repository and check the most recent version.
-
Add this configuration to your
composer.json:
{
"repositories": [
{
"type": "package",
"package": {
"name": "paid-plugin/advanced-custom-fields-pro",
"version": "6.3.3",
"type": "wordpress-muplugin",
"source": {
"type": "git",
"url": "git@codepot.nl:developers/plugins/paid/advanced-custom-fields.git",
"reference": "tags/v6.3.3"
}
}
}
],
"require": {
"paid-plugin/advanced-custom-fields-pro": "v6.3.3"
}
}
Important: Make sure the version number matches the most recent version (or the specific version you need).
- Install with Composer:
ACF Best Practices¶
Field groups organization: - Group related fields together logically - Use descriptive field names (not just "content" or "text") - Set proper field keys for consistency across environments
Field naming convention:
// Good: descriptive and clear
$hero_title = get_field('hero_title');
$cta_button_text = get_field('cta_button_text');
// Bad: vague and unclear
$content = get_field('content');
$text = get_field('text');
Location rules: - Be specific with location rules to avoid clutter - Use post types and page templates appropriately - Consider user role restrictions for admin-only fields
Common ACF Usage Patterns¶
Getting field values:
<?php
// Simple field
$title = get_field('title');
// With default fallback
$subtitle = get_field('subtitle') ?: 'Default Subtitle';
// Image field (returns array)
$image = get_field('hero_image');
if ($image) {
echo wp_get_attachment_image($image['id'], 'large');
}
// Repeater field
if (have_rows('team_members')) {
while (have_rows('team_members')) {
the_row();
$name = get_sub_field('name');
$role = get_sub_field('role');
echo "<p>{$name} - {$role}</p>";
}
}
?>
Flexible content:
<?php
if (have_rows('content_blocks')) {
while (have_rows('content_blocks')) {
the_row();
switch (get_row_layout()) {
case 'text_block':
get_template_part('blocks/text', null, [
'content' => get_sub_field('content')
]);
break;
case 'image_block':
get_template_part('blocks/image', null, [
'image' => get_sub_field('image')
]);
break;
case 'cta_block':
get_template_part('blocks/cta', null, [
'title' => get_sub_field('title'),
'button' => get_sub_field('button')
]);
break;
}
}
}
?>
Options page:
<?php
// Get option from ACF Options page
$site_phone = get_field('phone_number', 'option');
$footer_text = get_field('footer_copyright', 'option');
?>
ACF JSON Sync¶
Enable JSON sync for version control and team collaboration.
In your theme's functions.php:
// Save ACF field groups to theme
add_filter('acf/settings/save_json', function($path) {
return get_stylesheet_directory() . '/acf-json';
});
// Load ACF field groups from theme
add_filter('acf/settings/load_json', function($paths) {
unset($paths[0]);
$paths[] = get_stylesheet_directory() . '/acf-json';
return $paths;
});
Create the directory:
Workflow:
1. Create/edit field groups in WordPress admin
2. Field groups are automatically saved to acf-json/ folder
3. Commit the JSON files to git
4. On other environments, ACF will detect and sync changes
Lemone MU-Plugin¶
The Lemone MU-plugin is our custom must-use plugin that provides shared functionality across all Lemone WordPress projects.
Architecture¶
The plugin uses a Service Provider pattern for organizing functionality into modular services.
Directory structure:
lemone-mu-plugin/
├── lemone-mu-plugin.php (main file)
├── src/
│ ├── ServiceProvider.php (abstract base class)
│ ├── Services/
│ │ ├── ACF.php
│ │ ├── Assets.php
│ │ ├── Authentication.php
│ │ ├── Cleanup.php
│ │ ├── CustomPostTypes.php
│ │ ├── DisableEditor.php
│ │ ├── DisablePlugins.php
│ │ ├── EmailSMTP.php
│ │ ├── ErrorLogging.php
│ │ ├── Media.php
│ │ ├── Menus.php
│ │ ├── SEO.php
│ │ ├── Security.php
│ │ ├── SVG.php
│ │ └── Theme.php
│ └── Plugin.php (main plugin class)
Service Provider Pattern¶
Each service extends the ServiceProvider abstract class:
<?php
namespace Lemone\Services;
use Lemone\ServiceProvider;
class YourService extends ServiceProvider
{
/**
* Register the service
* Called when service is instantiated
*/
public function register(): void
{
// Register hooks, filters, etc.
add_action('init', [$this, 'init']);
add_filter('some_filter', [$this, 'filterCallback']);
}
/**
* Initialize the service
*/
public function init(): void
{
// Initialization logic
}
/**
* Example filter callback
*/
public function filterCallback($value)
{
return $value;
}
}
Registering Services¶
Services are registered in Plugin.php:
<?php
namespace Lemone;
class Plugin
{
/**
* Required services (always loaded)
*/
protected array $services = [
Services\Security::class,
Services\Cleanup::class,
Services\Theme::class,
];
/**
* Optional services (can be disabled by themes)
*/
protected array $optionalServices = [
Services\ACF::class,
Services\EmailSMTP::class,
Services\Media::class,
Services\SVG::class,
];
/**
* Boot the plugin
*/
public function boot(): void
{
$this->registerServices();
$this->registerOptionalServices();
}
/**
* Register required services
*/
protected function registerServices(): void
{
foreach ($this->services as $service) {
(new $service())->register();
}
}
/**
* Register optional services
*/
protected function registerOptionalServices(): void
{
foreach ($this->optionalServices as $service) {
if ($this->isServiceEnabled($service)) {
(new $service())->register();
}
}
}
/**
* Check if service is enabled
*/
protected function isServiceEnabled(string $service): bool
{
$className = class_basename($service);
return apply_filters("lemone/service/{$className}/enabled", true);
}
}
Disabling Optional Services¶
Themes can disable optional services using filters in functions.php:
<?php
// Disable specific optional service
add_filter('lemone/service/EmailSMTP/enabled', '__return_false');
// Disable multiple services
add_filter('lemone/service/ACF/enabled', '__return_false');
add_filter('lemone/service/Media/enabled', '__return_false');
Common Services¶
Security Service (Required): - Removes WordPress version from head - Disables XML-RPC - Adds security headers - Removes unnecessary meta tags
Cleanup Service (Required):
- Removes emoji scripts
- Cleans up <head> section
- Disables RSS feeds (if not needed)
- Removes unnecessary WordPress features
Theme Service (Required): - Registers theme support features - Manages image sizes - Handles theme customizations
ACF Service (Optional): - Sets up ACF options pages - Configures ACF settings - Registers ACF blocks
EmailSMTP Service (Optional): - Configures SMTP for transactional emails - Integrates with Postmark or similar services
Media Service (Optional): - Handles media library customizations - Image optimization settings - Custom media handling
SVG Service (Optional): - Enables SVG uploads - Sanitizes SVG files for security - Adds SVG mime type support
Creating a New Service¶
- Create new file in
src/Services/YourService.php:
<?php
namespace Lemone\Services;
use Lemone\ServiceProvider;
class YourService extends ServiceProvider
{
/**
* Register the service
*/
public function register(): void
{
add_action('init', [$this, 'init']);
}
/**
* Initialize
*/
public function init(): void
{
// Your logic here
}
}
- Add to
Plugin.php:
// For required service:
protected array $services = [
// ...
Services\YourService::class,
];
// For optional service:
protected array $optionalServices = [
// ...
Services\YourService::class,
];
Service Examples¶
Custom Post Type Service:
<?php
namespace Lemone\Services;
use Lemone\ServiceProvider;
class CustomPostTypes extends ServiceProvider
{
public function register(): void
{
add_action('init', [$this, 'registerPostTypes']);
}
public function registerPostTypes(): void
{
register_post_type('project', [
'labels' => [
'name' => 'Projects',
'singular_name' => 'Project',
],
'public' => true,
'has_archive' => true,
'menu_icon' => 'dashicons-portfolio',
'supports' => ['title', 'editor', 'thumbnail'],
]);
}
}
Disable Editor Service:
<?php
namespace Lemone\Services;
use Lemone\ServiceProvider;
class DisableEditor extends ServiceProvider
{
protected array $disabledTemplates = [
'template-homepage.php',
'template-contact.php',
];
public function register(): void
{
add_action('init', [$this, 'disableEditorForTemplates']);
}
public function disableEditorForTemplates(): void
{
$pageTemplate = get_page_template_slug();
if (in_array($pageTemplate, $this->disabledTemplates)) {
remove_post_type_support('page', 'editor');
}
}
}
Benefits of Service Provider Pattern¶
- Modularity: Each feature is self-contained
- Reusability: Services can be easily reused across projects
- Maintainability: Easy to locate and modify functionality
- Testability: Services can be unit tested independently
- Flexibility: Optional services can be enabled/disabled per theme
- Organization: Clear structure for team collaboration
See Also¶
- WordPress Development - General WordPress development
- WordPress Roots Stack - Bedrock, Sage, Trellis
- Git Development Guide - Version control for ACF JSON
- Paid Plugins Management - Managing premium plugins