*/ private $sources = []; /** * Alias map from legacy keys to canonical keys. * * @var array */ private $aliases = []; /** * Constructor. */ public function __construct() { $this->register_builtin_sources(); /** * Register first-class variable source objects. * * @param Hundred_Prints_Variable_Source_Registry $registry Registry instance. */ do_action( '100prints_register_variable_sources', $this ); } /** * Register a source object. * * @param Hundred_Prints_Variable_Source_Interface $source Source object. * @param array $aliases Optional alias keys. * @return self */ public function register( Hundred_Prints_Variable_Source_Interface $source, array $aliases = [] ) { $key = sanitize_key( $source->get_key() ); if ( '' === $key ) { return $this; } $this->sources[ $key ] = $source; foreach ( $aliases as $alias ) { $alias = sanitize_key( $alias ); if ( '' !== $alias ) { $this->aliases[ $alias ] = $key; } } return $this; } /** * Get all canonical source objects. * * @return array */ public function get_sources() { return $this->sources; } /** * Get a source object by canonical key or alias. * * @param string $source_key Source key. * @return Hundred_Prints_Variable_Source_Interface|null */ public function get_source( $source_key ) { $source_key = sanitize_key( $source_key ); $source_key = isset( $this->aliases[ $source_key ] ) ? $this->aliases[ $source_key ] : $source_key; return isset( $this->sources[ $source_key ] ) ? $this->sources[ $source_key ] : null; } /** * Get editor-facing source metadata. * * @return array */ public function get_editor_definitions() { $definitions = []; foreach ( $this->sources as $source ) { $definitions[] = [ 'label' => $source->get_label(), 'value' => $source->get_key(), 'fields' => $source->get_fields(), 'editorData' => method_exists( $source, 'get_editor_data' ) ? $source->get_editor_data() : [], ]; } return $definitions; } /** * Sanitize one source-specific mapping rule. * * @param array $rule Raw mapping rule. * @return array */ public function sanitize_rule( array $rule ) { $rule = $this->normalize_legacy_rule( $rule ); $source_key = isset( $rule['source'] ) ? sanitize_key( $rule['source'] ) : 'manual'; $source = $this->get_source( $source_key ); if ( $source ) { return $source->sanitize_rule( $rule ); } $sanitized = [ 'source' => $source_key, ]; foreach ( $rule as $key => $value ) { $key = sanitize_key( $key ); if ( '' === $key || 'source' === $key ) { continue; } $sanitized[ $key ] = is_array( $value ) ? map_deep( $value, 'sanitize_text_field' ) : sanitize_text_field( wp_unslash( $value ) ); } return $sanitized; } /** * Resolve one mapping rule. * * @param array $rule Mapping rule. * @param array $context Runtime context. * @return mixed */ public function resolve_rule( array $rule, array $context ) { $rule = $this->normalize_legacy_rule( $rule ); $source_key = isset( $rule['source'] ) ? sanitize_key( $rule['source'] ) : 'manual'; $source = $this->get_source( $source_key ); if ( ! $source ) { return ''; } return $source->resolve( $source->sanitize_rule( $rule ), $context ); } /** * Build shortcode/default mappings as the registry's opinion, not shortcode logic. * * @param array $variables Template variable names. * @return array */ public function get_default_variable_map( array $variables ) { $variable_map = []; foreach ( $variables as $variable ) { $variable = sanitize_text_field( $variable ); if ( '' === $variable ) { continue; } $variable_map[] = $this->get_default_rule_for_variable( $variable ); } return $variable_map; } /** * Sanitize a variable mapping field value based on schema. * * @param array $field Field schema. * @param mixed $value Raw value. * @return mixed */ public static function sanitize_field_value( array $field, $value ) { $field_type = isset( $field['type'] ) ? sanitize_key( $field['type'] ) : 'text'; if ( 'manual' === $field_type ) { return is_array( $value ) ? map_deep( $value, 'sanitize_text_field' ) : []; } if ( is_array( $value ) ) { return map_deep( $value, 'sanitize_text_field' ); } $value = wp_unslash( $value ); switch ( $field_type ) { case 'select': $allowed_values = []; if ( ! empty( $field['options'] ) && is_array( $field['options'] ) ) { foreach ( $field['options'] as $option ) { if ( is_array( $option ) && isset( $option['value'] ) ) { $allowed_values[] = sanitize_key( $option['value'] ); } } } $value = sanitize_key( $value ); return in_array( $value, $allowed_values, true ) ? $value : ( isset( $field['default'] ) ? $field['default'] : '' ); case 'url': return esc_url_raw( trim( sanitize_text_field( $value ) ) ); case 'number': return is_numeric( $value ) ? 0 + $value : ( isset( $field['default'] ) ? $field['default'] : 0 ); case 'checkbox': return ! empty( $value ); case 'textarea': return sanitize_textarea_field( $value ); case 'text': default: return sanitize_text_field( $value ); } } /** * Register core-owned sources. */ private function register_builtin_sources() { $this->register( new Hundred_Prints_Variable_Source_Current_User() ); $this->register( new Hundred_Prints_Variable_Source_Post_Data() ); $this->register( new Hundred_Prints_Variable_Source_Site_Info(), [ 'site_settings' ] ); $this->register( new Hundred_Prints_Variable_Source_System(), [ 'system_date', 'system_time' ] ); $this->register( new Hundred_Prints_Variable_Source_Query_Param() ); $this->register( new Hundred_Prints_Variable_Source_Request_Context() ); $this->register( new Hundred_Prints_Variable_Source_Static() ); $this->register( new Hundred_Prints_Variable_Source_Form() ); } /** * Convert older saved mapping keys into current source object rules. * * @param array $rule Mapping rule. * @return array */ private function normalize_legacy_rule( array $rule ) { $source_key = isset( $rule['source'] ) ? sanitize_key( $rule['source'] ) : 'manual'; $legacy_rules = [ 'current_user_display_name' => [ 'source' => 'current_user', 'userField' => 'display_name' ], 'current_user_email' => [ 'source' => 'current_user', 'userField' => 'user_email' ], 'current_user_id' => [ 'source' => 'current_user', 'userField' => 'user_id' ], 'site_name' => [ 'source' => 'site_info', 'siteField' => 'name' ], 'site_url' => [ 'source' => 'site_info', 'siteField' => 'url' ], 'system_date' => [ 'source' => 'system', 'systemField' => 'current_date' ], 'system_time' => [ 'source' => 'system', 'systemField' => 'current_time' ], ]; if ( isset( $legacy_rules[ $source_key ] ) ) { return array_merge( $rule, $legacy_rules[ $source_key ] ); } if ( isset( $this->aliases[ $source_key ] ) ) { $rule['source'] = $this->aliases[ $source_key ]; } return $rule; } /** * Get the registry's default mapping for one variable. * * @param string $variable Variable name. * @return array */ private function get_default_rule_for_variable( $variable ) { $normalized = strtolower( preg_replace( '/[^a-z0-9]+/', '_', $variable ) ); if ( in_array( $normalized, [ 'name', 'full_name', 'fullname', 'display_name' ], true ) ) { $rule = [ 'key' => $variable, 'source' => 'current_user', 'userField' => 'display_name' ]; } elseif ( in_array( $normalized, [ 'email', 'user_email' ], true ) ) { $rule = [ 'key' => $variable, 'source' => 'current_user', 'userField' => 'user_email' ]; } elseif ( in_array( $normalized, [ 'date', 'created_at' ], true ) ) { $rule = [ 'key' => $variable, 'source' => 'system', 'systemField' => 'current_date_long' ]; } else { $rule = [ 'key' => $variable, 'source' => 'static', 'value' => '[' . $variable . ']' ]; } /** * Filter the default mapping rule used by fallback renderers. * * @param array $rule Mapping rule. * @param string $variable Variable name. * @param Hundred_Prints_Variable_Source_Registry $registry Registry instance. */ return apply_filters( '100prints_default_variable_mapping_rule', $rule, $variable, $this ); } }