WP Manifestindependent plugin directory

RudraACF

wordpress acf

by Rudransh · github.com/hardiksolanki90/acf

0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/hardiksolanki90/acf/archive/refs/heads/main.zip

Readme

RudraACF

Production-grade WordPress custom fields framework — ACF Pro–compatible alternative built on PHP 8.1+, PSR-4, repository pattern, and object-cache-first design.


Requirements

Dependency Minimum
PHP 8.1
WordPress 6.3
Composer 2.x

Installation

# 1. Copy plugin folder into wp-content/plugins/
cp -r myacf /path/to/wp-content/plugins/

# 2. Install PHP dependencies
cd /path/to/wp-content/plugins/myacf
composer install --no-dev --optimize-autoloader

# 3. Activate via WP Admin → Plugins, or WP-CLI:
wp plugin activate myacf

Activation runs database migrations automatically (creates wp_myacf_field_groups, wp_myacf_fields, wp_myacf_field_values).


Architecture Overview

myacf/
├── src/
│   ├── Admin/          # Admin pages, meta boxes, AJAX handlers
│   ├── Api/            # FieldApi service + global helper functions
│   ├── Compatibility/  # PHP/WP version + conflict detection
│   ├── Database/       # Models, Repositories, Migrations
│   ├── Fields/         # FieldInterface, BaseField, 16 field types
│   ├── ImportExport/   # JSON Exporter + Importer
│   ├── Licensing/      # LicenseManager + WP UpdateChecker
│   ├── OptionsPages/   # Options page registry + manager
│   ├── Performance/    # BulkLoader (cache pre-warming)
│   ├── Pro/            # ConditionalLogic
│   ├── Rest/           # REST API (myacf/v1)
│   ├── Rules/          # Rules Engine + matchers
│   ├── Sdk/            # Third-party extension SDK
│   ├── Services/       # Container, Capability, Sanitizer, Escaper, Nonce
│   └── Plugin.php      # Bootstrap + DI wiring
├── tests/Unit/         # 265 PHPUnit tests
└── myacf.php           # Plugin entry point

Key patterns:

  • Repository pattern — all DB access via FieldGroupRepository, FieldRepository, FieldValueRepository; never raw $wpdb calls outside repos
  • Object cache — every repo read is cache-first (wp_cache_get/set); CACHE_GROUP constants per repo; TTL 3600 s
  • DI Container — custom Container::singleton()/make() with no Reflection; all services resolved lazily
  • FieldInterface contractgetType / getLabel / render / renderInput / validate / save / load

Field Types

Type Class Notes
text TextField Single-line
textarea TextareaField Multi-line
number NumberField min/max/step settings
email EmailField Validates format
url UrlField
password PasswordField Never echoed in output
select SelectField choices array; multiple support
checkbox CheckboxField choices array; returns string[]
radio RadioField choices array
toggle ToggleField Stores '1'/''
image ImageField Attachment ID; return setting: id\|url\|array
file FileField Attachment ID; array includes filename/mime_type
post_object PostObjectField WP_Query select; multiple support
user UserField get_users with role__in filter
taxonomy TaxonomyField get_terms; returns term ID(s)
repeater RepeaterField Rows stored as JSON; sub-fields via parent_id
flexible_content FlexibleContentField Layouts + sub-fields; stored as JSON array
clone CloneField Inlines fields from source group(s); prefix_label/prefix_name settings

Developer API

Get / Display Values

// Get a field value
$value = myacf_get_field('field_name', $post_id);
$value = myacf_get_field('field_name', $post_id, 'post', $default);

// Echo value (HTML-escaped)
myacf_the_field('field_name', $post_id);

// Check if value exists
if (myacf_has_field('field_name', $post_id)) { ... }

// Get all fields for an object
$all = myacf_get_fields($post_id);

// User fields
$bio = myacf_get_field('bio', $user_id, 'user');

// Term fields
$desc = myacf_get_field('extra_desc', $term_id, 'term');

// Options page fields
$val = myacf_get_field('site_logo', 0, 'option');

Save / Delete

myacf_update_field('field_name', $value, $post_id);
myacf_update_field('field_name', $value, $user_id, 'user');
myacf_delete_field('field_name', $post_id);

Performance: Bulk Cache Pre-warm

// Call in pre_get_posts or the_post to batch-load before template loop
add_action('pre_get_posts', function($query) {
    if (!$query->is_main_query()) return;
    $ids = get_posts(['fields' => 'ids', 'posts_per_page' => 10]);
    myacf_prime_cache($ids, 'post');
});

Field Group Registration (programmatic)

Field groups can be created via WP Admin → RudraACF → Add New, or programmatically via the FieldGroupRepository:

add_action('plugins_loaded', function() {
    $repo = RudraACF\Plugin::getInstance()
        ->getContainer()
        ->make(RudraACF\Database\Repositories\FieldGroupRepository::class);

    $groupId = $repo->insert(
        title: 'Article Meta',
        key:   'group_article_meta',
        settings: [
            'rules' => [[
                ['param' => 'post_type', 'operator' => '==', 'value' => 'post'],
            ]],
            'position' => 'normal',
        ],
    );
});

Location Rules

Field groups are shown based on location rules evaluated by the Rules Engine.

param Evaluates Operators
post_type Current post type == !=
taxonomy Current taxonomy (term edit screen) == !=
user_role Current user's roles == !=
page_template Active page template filename == !=
post_status Post status == !=

Rules use OR across groups, AND within each group (same as ACF):

// Show on 'post' OR 'page' post types
'rules' => [
    [['param' => 'post_type', 'operator' => '==', 'value' => 'post']],
    [['param' => 'post_type', 'operator' => '==', 'value' => 'page']],
]

Register a custom rule matcher

add_action('myacf/register_rule_matchers', function($engine) {
    $engine->registerMatcher(new MyCustomRule());
});

// MyCustomRule implements RudraACF\Rules\RuleInterface:
//   getParam(): string
//   evaluate(string $operator, mixed $value, array $context): bool

Conditional Logic (Pro)

Show/hide individual fields based on other field values. Set in field settings:

'settings' => [
    'conditional_logic' => [
        // OR across outer array, AND within each group
        [
            ['field' => 'enable_hero', 'operator' => '==',    'value' => '1'],
            ['field' => 'layout',      'operator' => '!=',    'value' => 'minimal'],
        ],
    ],
],

Available operators: == != < <= > >= contains not_contains empty not_empty

Apply in code:

$logic  = new RudraACF\Pro\ConditionalLogic();
$values = myacf_get_fields($post_id);
$fields = $logic->filterFields($fieldDefinitions, $values);

Options Pages

// Register at plugins_loaded (before admin_menu)
add_action('plugins_loaded', function() {
    myacf_register_options_page([
        'slug'       => 'theme-settings',
        'title'      => 'Theme Settings',
        'menu_title' => 'Theme Options',
        'capability' => 'manage_options',
        'icon'       => 'dashicons-art',
        'position'   => 60,
    ]);

    // Sub-page
    myacf_register_options_page([
        'slug'   => 'theme-colors',
        'title'  => 'Color Settings',
        'parent' => 'theme-settings',
    ]);
});

// Read options page fields (object_id = 0, type = 'option')
$logo = myacf_get_field('logo', 0, 'option');

REST API

Base namespace: myacf/v1

Method Route Auth Description
GET /field-groups manage_options List all field groups
GET /field-groups/{id} manage_options Single group + fields
GET /objects/{type}/{id} per-object cap All field values
GET /objects/{type}/{id}/{field} per-object cap Single field value
POST /objects/{type}/{id}/{field} per-object cap Update field value

{type} must be one of: post user term option

Per-object capability check (IDOR protection):

type read cap write cap
post read_post edit_post
user edit_user edit_user
term read edit_term
option manage_options manage_options
# Example
curl -X GET https://site.com/wp-json/myacf/v1/objects/post/42 \
  -H "Authorization: Bearer TOKEN"

curl -X POST https://site.com/wp-json/myacf/v1/objects/post/42/hero_title \
  -H "Authorization: Bearer TOKEN" \
  -d '{"value": "New Title"}'

Import / Export

Via Admin UI

WP Admin → RudraACF → Import / Export

  • Export: downloads all (or selected) field groups as JSON
  • Import: upload JSON; checkbox "Update Existing" overwrites groups with matching key

Programmatic

$container = RudraACF\Plugin::getInstance()->getContainer();

// Export
$exporter = $container->make(RudraACF\ImportExport\Exporter::class);
$json     = $exporter->export();           // all groups
$json     = $exporter->export([1, 3]);     // specific group IDs

// Import
$importer = $container->make(RudraACF\ImportExport\Importer::class);
$stats    = $importer->import($json, updateExisting: true);
// $stats = ['imported' => 2, 'skipped' => 0, 'errors' => []]

Third-Party SDK

Hook into myacf/sdk/ready to register extensions after all services boot:

add_action('myacf/sdk/ready', function(RudraACF\Sdk\Sdk $sdk) {

    // Register a custom field type
    $sdk->registerFieldType(new MyPlugin\Fields\ColorPickerField());

    // Register a custom rule matcher
    $sdk->registerRuleMatcher(new MyPlugin\Rules\MembershipRule());

    // Register an options page
    $sdk->registerOptionsPage([
        'slug'  => 'my-plugin-settings',
        'title' => 'My Plugin Settings',
    ]);

    // Raw container access (escape hatch)
    $container = $sdk->getContainer();
});

Custom Field Type

use RudraACF\Fields\BaseField;

final class ColorPickerField extends BaseField
{
    public function getType(): string  { return 'color_picker'; }
    public function getLabel(): string { return 'Color Picker'; }

    public function renderInput(array $field, mixed $value): void
    {
        $name = $this->inputName($field);
        $id   = $this->inputId($field);
        echo '<input type="color" name="' . esc_attr($name) . '" '
           . 'id="' . esc_attr($id) . '" '
           . 'value="' . esc_attr((string) $value) . '">';
    }

    public function validate(mixed $value, array $field): true|string
    {
        if (!empty($field['settings']['required']) && empty($value)) {
            return 'This field is required.';
        }
        if ($value !== '' && !preg_match('/^#[0-9a-fA-F]{3,6}$/', (string) $value)) {
            return 'Must be a valid hex color (e.g. #ff0000).';
        }
        return true;
    }

    public function save(mixed $value, array $field): mixed { return sanitize_hex_color($value); }
    public function load(mixed $value, array $field): mixed { return $value; }
}

Licensing (Pro)

WP Admin → RudraACF → License

Enter license key to activate. Key is validated against MYACF_LICENSE_SERVER_URL (define in wp-config.php for Pro builds):

// wp-config.php
define('MYACF_LICENSE_SERVER_URL', 'https://licenses.yoursite.com/api');

Programmatic:

$license = $container->make(RudraACF\Licensing\LicenseManager::class);
$result  = $license->activate('YOUR-KEY-HERE');
// ['success' => true, 'message' => 'License activated successfully.']

$license->isActive();     // bool
$license->getLicenseKey(); // string
$license->check();         // bool — re-validates against server (24h cache)
$license->deactivate();    // frees the activation slot

The UpdateChecker automatically injects plugin update info into WP's update system when an active license is present.


Running Tests

cd myacf
composer install
./vendor/bin/phpunit --testsuite Unit

265 tests, 409 assertions. No real WordPress or database needed — full stub environment in tests/bootstrap.php.


Compatibility

On plugin activation, CompatibilityChecker blocks activation and shows an error if:

  • PHP < 8.1
  • WordPress < 6.3
  • Advanced Custom Fields (free or Pro) is active — field name conflicts

Storage Schema

-- Field groups
wp_myacf_field_groups (id, title, key, settings JSON, created_at, updated_at)

-- Field definitions
wp_myacf_fields (id, group_id, parent_id, type, name, label, instructions, settings JSON, sort_order)

-- Field values
wp_myacf_field_values (id, object_type, object_id, field_id, value LONGTEXT)

parent_id links sub-fields to their Repeater/FlexContent/Clone parent. object_type is one of post user term option. For options pages object_id = 0.


Hooks Reference

Hook Type Description
myacf/sdk/ready action Fires after all services boot; receives Sdk instance
myacf/register_field_types action Fires inside FieldTypeRegistry build; receives FieldTypeRegistry
myacf/register_rule_matchers action Fires inside RulesEngine build; receives RulesEngine
myacf/rules/context filter Filter the rules context array before evaluation

Read the full README on GitHub →