Stored fields whose type is not available.
*/
private static $unavailable = [];
/**
* @var bool
*/
private static $noticeHooked = false;
/**
* Register the fields of a page on admin_init. Fields of a type that is not registered (an add-on that is
* not active) are skipped; the page still works and an administrator sees a notice.
*
* @param iterable $fields Stored fields.
* @param array|null $page The page the fields are on; then only fields the user may edit are registered
* (Access::fieldMode()).
*/
public static function hook(iterable $fields, ?array $page = null): void
{
foreach ($fields as $field) {
// The type is looked up on admin_init, after add-ons had their chance to register it.
add_action('admin_init', function () use ($field, $page) {
if ($page !== null && Access::fieldMode($page, $field, wp_get_current_user()) !== 'edit') {
return;
}
self::registerField($field);
});
add_action('admin_enqueue_scripts', function () use ($field) {
$type = FieldRegistry::instance()->get($field['type'] ?? '');
if ($type !== null && $type->usesAssets()) {
$type->enqueue($field);
}
});
}
if (!self::$noticeHooked) {
self::$noticeHooked = true;
add_action('admin_notices', [self::class, 'printNotice']);
}
}
/**
* @param array $field A stored field.
*/
public static function registerField(array $field): void
{
$type = FieldRegistry::instance()->get($field['type'] ?? '');
if ($type === null) {
self::$unavailable[] = ['type' => (string) ($field['type'] ?? ''), 'name' => (string) ($field['field_name'] ?? '')];
return;
}
$page = $field['page_name'];
$sectionId = $type->sectionId($field);
if (!$type->hasOption()) {
// Layout: the section itself prints the field.
add_settings_section($sectionId, '', function () use ($type, $field) {
$type->render($field, null);
}, $page);
return;
}
$name = $field['field_name'];
$class = isset($field['class_attribute']) ? $field['class_attribute'] : '';
register_setting($page, $name, [
'type' => $type->optionType(),
'group' => $page,
'sanitize_callback' => function ($value) use ($field) {
return self::sanitizeValue($field, $value);
},
]);
add_settings_section($sectionId, '', '', $page);
$args = ['class' => $class];
if ($type->labelFor()) {
$args = ['label_for' => $name, 'class' => $class];
}
add_settings_field(
$name,
isset($field['field_label']) ? stripslashes($field['field_label']) : '',
function () use ($type, $field, $name) {
$type->render($field, get_option($name));
},
$page,
$sectionId,
$args
);
}
/**
* What an option of a field becomes when it is saved: the field type sanitizes it, unless a filter on
* `aop_sanitize_option_` decides. The result then passes `aop_sanitized_option` and
* `aop_sanitized_option_`, which can change it without having to sanitize it again. The options page
* and the import both come through here.
*
* @param array $field A stored field of a type that is available and has an option.
* @param mixed $value
*
* @return mixed
*/
public static function sanitizeValue(array $field, $value)
{
$name = $field['field_name'];
if (has_filter(Plugin::PREFIX_ . 'sanitize_option_' . $name)) {
$sanitized = apply_filters(Plugin::PREFIX_ . 'sanitize_option_' . $name, $value);
} else {
$sanitized = FieldRegistry::instance()->get($field['type'])->sanitizeValue($value, $field);
}
/**
* Change the value of any field after it was sanitized, just before it is stored.
*
* @since 1.0.0
*
* @param mixed $sanitized The value as the field type (or `aop_sanitize_option_`) made it.
* @param array $field The stored field: type, field_name and its settings.
* @param mixed $value The value as it was posted (or imported). Not safe: never store it as is.
*/
$sanitized = apply_filters(Plugin::PREFIX_ . 'sanitized_option', $sanitized, $field, $value);
/**
* The same, for the option of one field: `aop_sanitized_option_
', array_map('esc_html', array_unique($messages))));
}
/**
* Forget what was collected (tests).
*/
public static function reset(): void
{
self::$unavailable = [];
self::$noticeHooked = false;
}
}