Configuration Resolver

Generally speaking, there are different types of resolvers who crawl through a given TypoScript object and build some kind of result. The algorithm by which the result is resolved depends on the type of resolver and is very extendable. Extensions can register new resolvers of a given type, which are called and processed automatically. To register a new Resolver, we can use the methods in \Mediatis\Formrelay\Service\Registry, which are registering signal slots that have to implement the interface of the resolver we want to extend. Alternatively we can use the signal slots directly.

All resolvers have in common that their constructor is fed with the configuration that has to be resolved. This can be either be a scalar value or an array.

Evaluation

Interface: \Mediatis\Formrelay\ConfigurationResolver\Evaluation\EvaluationInterface

Optional abstract class to extend: \Mediatis\Formrelay\ConfigurationResolver\Evaluation\Evaluation

Resolvers of the type Evaluation will try to make some kind of decision. They can be used in multiple places, while their result can be processed differently depending on the context in which they are used.

Usually the Formrelay will start with the GeneralEvaluation which provides the methods eval and resolve.

The method eval will process the actual logic and return a boolean value. The logic has all form fields as input variables.

public function eval(array $context = [], array $keysEvaluated = []): bool;

The $context will contain the context of the current evaluation, which is at minimum the form data, which is used as input variables for the logic.

The $keysEvaluated is a list of extension keys, that have been evaluated so far. This is important for the GateEvaluation to avoid evaluation loops. Other evaluations should just pass this value to sub-evaluations, if there are any.

settings.evaluateSomeEvaluation {
  and {
    field_a = A
    field_b = B
  }
}

The method resolve will call the method eval and will return the TypoScript object path then or else depending on the result. If the corresponding object path does not exist, it will return null instead.

public function resolve(array $context, array $keysEvaluated = []): mixed|null

This input of the function is the same as for eval, but the result will either be one of the configuration paths then or else, or null if the path is not existent.

settings.resolveSomeEvaluation {
  and {
    field_a = A
    field_b = B
  }
  then = Foo
  else = { ... possibly more config to resolve ... }
}

Whether eval or resolve is called depends on the context in which the evaluation is being used. The most common context is the gate evaluation described above, where the method eval determines whether or not a destination will be triggered.

All evaluations other than GeneralEvaluation do not need to implement the method resolve. They only need to take care of the method eval in order to provide a boolean result for their evaluation.

Here are all default evaluations, shipped with EXT:formrelay.

GeneralEvaluation

This evaluation is the entry point for every evaluation. It acts as an AndEvaluation, but also provides the method resolve.

AndEvaluation

This evaluation expects a list of sub-evaluations and will process them in an and-context which means that all sub-evaluations have to evaluate to true in order for the AndEvaluation to also return true. The sub-evaluations are conjunctive.

and {
  not {
    ...
  }
  or {
    ...
  }
  field_x = value_y
  someEvaluation = ...
}

Numeric keys will be thought of as encapsulated evaluations, which will start as a new GeneralEvaluation.

and {
  10 {
    ...
  }
  20 {
    ...
  }
}

Keys that do not represent a number nor a keyword for a specific evaluation, will be recognised as field name, followed by a new evaluation. Depending on the value it will be either a GeneralEvaluation for arrays or an EqualsEvaluation for scalar values.

and {
  field_name_a = field_value_a_1
  field_name_b = field_value_b_13
}
and {
  field_name_a {
    or {
      10.equals = field_value_a_1
      20.equals = field_value_a_2
    }
  }
  field_name_b {
    not.equals = field_value_b_13
  }
}

The keyword field is interpreted as new field name for all following evaluations, very much like keys that do not represent a number nor a keyword. This is helpful for field names that are actual keywords.

and {
  # the field named "and" must be equal to "foobar"
  field = and
  equals = foobar
}
and {
  1 {
    # the field named "and" must be equal to "foobar"
    field = and
    equals = foobar
  }
  2 {
    # and the field named "not" must not be equal to "baz"
    field = not
    not.equals = baz
  }
}

EmptyEvaluation

This evaluation must have a context (a field name) already. It expects a scalar value which determines whether the field value is evaluated as empty or not empty.

# the field named "field_name_a" must be empty
field_name_a {
  empty = 1
}
# the field named "field_name_a" must not be empty
field_name_a.empty = 0

EqualsEvaluation

This evaluation must have a context (a field name) already. It expects a scalar value which is compared to the field value. In the context of a GeneralEvaluation it is used implicitly if the value of a context changing key is a scalar one.

# explicit call
field_name_a {
  equals = field_value_a_1
}
# implicit call
field_name_a = field_value_a_1

ExistsEvaluation

This evaluation must have a context (a field name) already. It expects a scalar value which determines whether the field value is evaluated as existent or not existent.

# the field named "field_name_a" must exist
field_name_a {
  exists = 1
}
# the field named "field_name_a" must not exist
field_name_a.exists = 0

GateEvaluation

This evaluation will load the gate configuration of the given extension (other than the one being currently evaluated) and will return its evaluation. Since Formrelay extensions can be called multiple times for one form submission (see section 1..n), we can tell the GateEvaluation which call shall be evaluated.

gate {
  extKey = tx_formrelay_some_other_extension
  index = 0
}

To see if any or all calls to one extension are triggered, we can use the keywords any or all instead of a number.

gate {
  extKey = tx_formrelay_some_other_extension
  index = any
}
gate {
  extKey = tx_formrelay_some_other_extension
  index = all
}

The shorthand to the index any is to use a scalar value for the configuration.

gate = tx_formrelay_some_other_extension

We can also list (comma-separated) multiple extension which shall be evaluated together. In such a case the GateEvaluation will return true as soon as one of the extensions evaluates to true.

gate = tx_formrelay_some_other_extension,tx_formrelay_yet_another_extension

InEvaluation

This evaluation must have a context (a field name) already. It expects either an array of values or a comma-separated list of values and will check whether the form field value is within this list.

field_name_a.in = field_value_a_1,field_value_a_2,field_value_a_3
field_name_a.in {
  1 = field_value_a_1
  2 = field_value_a_2
  3 = field_value_a_3
}

NotEvaluation

This evaluation will create a new GeneralEvaluation with its own configuration and will simply negate the result. If it is used on a scalar value, it will assume a context (a field name) and will create an EqualsEvaluation instead.

not {
  field_name_a = field_value_a_1
  or { ... }
  field_name_b.in = field_value_b_1,field_value_b_2
}
not.field_name_a = field_value_a_1
field_name_a.not = field_value_a_1

OrEvaluation

This evaluation acts exactly like the AndEvaluation except that the sub-evaluations are disjunctive. As soon as one sub-evaluation is true, the OrEvaluation becomes true.

RequiredEvaluation

This evaluation expects either an array of field names or a comma-separated list of field names. It will check whether all of them exist and contain values that do not evaluate to false (non-empty values).

required = field_name_a,field_name_b
required {
  1 = field_name_a
  2 = field_name_b
}

FieldMapper

Interface: \Mediatis\Formrelay\ConfigurationResolver\FieldMapper\FieldMapperInterface

Optional abstract class to extend: \Mediatis\Formrelay\ConfigurationResolver\FieldMapper\FieldMapper

Resolvers of the type FieldMapper will add processed form fields and their (processed) values into the result array. They can add, change and even delete multiple output fields which is why they get the current result passed as reference, so that they can modify it however needed.

The Formrelay will start with the GeneralFieldMapper which provides the method resolve.

public function resolve(array $context, array $result = []): array

All other field mappers do not have to implement the method resolve. Instead they will operate in two steps: prepare and finish.

public function prepare(&$context, &$result);
public function finish(&$context, &$result): bool;

The method prepare is used to change the context of the process for all field mappers that are operating on the current data.

The method finish will apply the actual changes on the result object. The return value true indicates, that the processing is complete and no further field mappers should be called for the current form field. The return value false indicates that the field mapper was not able to finish the processing of the field an the following field mappers should be called.

Here are all default field mappers, shipped with EXT:formrelay.

GeneralFieldMapper

This field mapper is the entry point and provides the method resolve. It is used on the settings objects settings.fields.mapping.<field_name> and settings.fields.unmapped.

It is applying the method prepare on all sub-field mappers. After that it is applying the method finish on all sub-field mappers until one of them reports success (returns true).

In general the GeneralFieldMapper is using the order of field mappers as they appear in the configuration, but it makes two exceptions.

The PlainFieldMapper will be applied last so that specialised field mappers can overwrite its behaviour.

The IfFieldMapper will be applied first because it shall be able to overwrite every other field mapper.

PlainFieldMapper

This field mapper simply maps a form field name to an external field name.

form_field_name_a = external_field_name_a

Be careful: If two form fields are mapped to the same external field (without any other mappers being involved), they will overwrite each other.

AppendKeyValueFieldMapper

This field mapper has to be combined with another field mapper, that is providing an actual mapping (like the PlainFieldMapper). It will then not blindly write the form field’s value into the result field but rather append it. Also, it will append the pair of field name and field value. Not just the field value.

form_field_name_a = external_field_name_a
form_field_name_a.appendKeyValue = 1

You can also configure the separators used between field name and field value as well as the separators used between different name-value-pairs.

This example shows the default values, where \s is mapped to the space character and \n is mapped to a line break character.

form_field_name_a = external_field_name_a
form_field_name_a.appendKeyValue {
  separator = \n
  keyValueSeparator = \s=\s
}

AppendValueFieldMapper

This field mapper has to be combined with another field mapper, that is providing an actual mapping (like the PlainFieldMapper). It will then not blindly write the form field’s value into the result field but rather append it.

form_field_name_a = external_field_name_a
form_field_name_a.appendValue = 1

You can also configure the separator used between the values of different form fields (mapped to this result field). This example shows the default value, where \s is mapped to the space character and \n is mapped to a line break character.

form_field_name_a = external_field_name_a
form_field_name_a.separator = \n

DiscreteFieldFieldMapper

This field mapper has to be combined with another field mapper, that is providing an actual mapping (like PlainFieldMapper). It will make sure that the result field will be a DiscreteMultiValueFormField (described above). If the result field already holds data, it will be converted into a DiscreteMultiValueFormField (if necessary) and then the field value or values are appended to it.

form_field_name_a = external_field_name_a
form_field_name_a.discreteField = 1

DistributeFieldMapper

This field mapper expects multiple external field names and will write the form field value into all those result fields.

We can configure the external fields simply as comma separated list.

form_field_name_a.distribute = external_field_name_a_1,external_field_name_a_2

Or we can configure them as array.

form_field_name_a.distribute {
  1 = external_field_name_a_1
  2 = external_field_name_a_2
}

Having the external fields as array gives the opportunity to use sub-configurations as well.

form_field_name_a.distribute {
  1 = external_field_name_a_1
  2 = external_field_name_a_2
  2.appendValue = 1
  2.negate = 1
  3 = external_field_name_a_3
  3.discreteField = 1
}

IfEmptyFieldMapper

This field mapper will abort the processing if the result field already contains a value.

form_field_name_a = external_field_name_a
form_field_name_a.ifEmpty = 1

Note

This feature exists for legacy reasons only. It should not be used anymore, since it relies on the consistent order of the form data which can not be guaranteed. A better way to get this behaviour is to check the other input values that want to write to the same result field.

form_field_name_a = external_form_field_a
form_field_name_b = external_form_field_a
form_field_name_b.if {
  form_field_name_a.not.empty = 1
  then.ignore = 1
}

IfFieldMapper

This field mapper applies a GeneralEvaluation (using its resolve method). If the result is not null (if the corresponding configuration path then or else exists) it will be used for a new GeneralFieldMapper to resolve the field mapping.

form_field_name_a.if {
  form_field_name_b = xyz
  then = external_form_field_a_2
  else = external_form_field_a_1
}

If not both paths then and else exist, the IfFieldMapper may be ignored, depending on the evaluation.

form_field_name_a = external_form_field_a_2
form_field_name_a.if {
  form_field_name_b = xyz
  then = external_form_field_a_1
}

All evaluations can be used, so we can create arbitrary, complex conditions.

form_field_name_a = external_form_field_a_1
form_field_name_a.if {
  or {
    form_field_name_b = xzy
    form_field_name_c.not = abc
    not {
      or {
        1 = and {
          form_field_name_d = 123
          form_field_name_e = 456
        }
        2 = and {
          form_field_name_f = 789
          form_field_name_g = 321
        }
      }
    }
  }
  then.if {
    or {
      gate = tx_formrelay_foo
      not.gate = tx_formrelay_bar
      form_field_name_b.in = 42,666,foo
    }
    form_field_name_b.not = 666
    then = external_form_field_a_2
    then.appendKeyValue = 1
    else.ignore = 1
  }
  else = external_form_field_a_999
}

IgnoreFieldMapper

This field mapper stops the processing of the form field completely.

It can be used to ignore specific form fields explicitly.

settings.fields.mapping.form_field_name_a.ignore = 1

And it can be used to implicitly ignore all fields that are not specified in the mapping object.

settings.fields.unmapped.ignore = 1

It can also be used in conditioned field mappers.

form_field_name_a = external_field_name_a
form_field_name_a.if {
  form_field_name_b = form_field_value_b
  then.ignore = 1
}

JoinFieldMapper

This field mapper has to be combined with another field mapper, that is providing an actual mapping (like the PlainFieldMapper). It will check whether the given form field value is a MultiValueFormField and will then implode the values to a single string.

form_field_name_a = external_field_name_a
form_field_name_a.join = 1

We can also configure the glue of the implode call. The example below shows the default, where \s is replaced with the space character and \n is replaced with a line break character.

form_field_name_a = external_field_name_a
form_field_name_a.join = 1
form_field_name_a.join {
  glue = \n
}

If we configure the join field mapper, there is actually no need anymore to explicitly enable it.

form_field_name_a = external_field_name_a
form_field_name_a.join.glue = \n

NegateFieldMapper

This field mapper has to be combined with another field mapper, that is providing an actual mapping (like the PlainFieldMapper). It will evaluate the given form field value to a boolean value and return a negated version.

By default it will return 0 for values, that evaluate to true, and 1 for values, that evaluate to false.

form_field_name_a = external_field_name_a
form_field_name_a.negate = 1

We can also configure the negated values.

form_field_name_a = external_field_name_a
form_field_name_a.negate = 1
form_field_name_a.negate {
  true = foo
  false = bar
}

If we configure the negate field mapper, there is actually no need anymore to explicitly enable it.

form_field_name_a = external_field_name_a
form_field_name_a.negate {
  true = foo
  false = bar
}

PassthroughFieldMapper

This field mapper will use the original form field name as external field name for the current destination.

form_field_name_a.passthrough = 1

It is pretty much the same as an identity mapping.

form_field_name_a = form_field_name_a

However, it comes in handy on the settings object settings.fields.unmapped which applies to all form fields that do not have an explicit mapping.

settings.fields.unmapped.passthrough = 1

SplitFieldMapper

This field mapper splits the given form field value and distributes the result among different external fields.

form_field_name_a.split.fields {
  1 = external_field_name_a_1
  2 = external_field_name_a_2
  3 = external_field_name_a_3
}

We can omit the array and give a comma-separated list instead.

form_field_name_a.split.fields = external_field_name_a_1,external_field_name_a_2,external_field_name_a_3

The split token is the space character by default, but it can be configured, too, where \s is replaced with the space character and \n is replaced with a line break character.

form_field_name_a.split {
  token = .
  fields = external_field_name_a_1,external_field_name_a_2
}

If we do not want to overwrite the split token, we can also omit the fields object.

form_field_name_a.split = external_field_name_a_1,external_field_name_a_2

Example:

name.split = first_name,last_name
name === "Foo Bar"
result === [first_name => "Foo", last_name => "Bar"]

If the form field value can not be split into enough parts, only the external fields that have a (split) value, will be used.

name === "Foo"
result === [first_name => "Foo"]

If the form field value can be split into more parts than external fields are configured, the last field will get all remaining (split) values.

name === "Foo Bar Baz"
result === [first_name => "Foo", last_name => "Bar Baz"]

ValueMapFieldMapper

This field mapper can update the field values, by applying a ValueMapper during the field mapping.

form_field_name_a = external_field_name_a
form_field_name_a.valueMap {
  form_field_a_value_1 = external_field_a_value_1
  form_field_a_value_2 = external_field_a_value_2
}

While this seems redundant because we already have a value mapping in the settings under settings.values.mapping, this opens up the possibility to provide different value mappings for multiple external fields, e.g. reached via the distribute field mapper.

form_field_name_a.distribute {
  10 = external_field_name_a1
  10.valueMap {
    form_field_a_value_1 = external_field_a1_value_1
    form_field_a_value_2 = external_field_a1_value_2
  }
  20 = external_field_name_a2
  20.valueMap {
    form_field_a_value_1 = external_field_a2_value_1
    form_field_a_value_2 = external_field_a2_value_2
  }
}

Note: Even if a general value mapping via settings.values.mapping is provided, the value-map field mapper will use the original form field values. This means that a field-specific value mapping is overwriting the general value mapping completely. This is intended because the original form field values will guarantee maximum availability of information, while the general value mapping may already have reduced the information, e.g. by mapping two different form field values to the same external value.

settings {
  values.mapping {
    form_field_name_a {
      form_field_a_value_1 = general_external_field_a_value_1
      form_field_a_value_2 = general_external_field_a_value_2
    }
  }
  fields.mapping {
    form_field_name_a.distribute {

      # this mapping will work
      # since it overwrites all mapped values of the general value mapping
      10 = external_field_name_a1
      10.valueMap {
        form_field_a_value_1 = external_field_a1_value_1
        form_field_a_value_2 = external_field_a1_value_2
      }

      # this mapping will NOT work
      # since it tries to map the generally mapped values instead of the original form field values
      # and this will actually overwrite the general mapping
      # so the original form field values will be used
      20 = external_field_name_a2
      20.valueMap {
        general_external_field_a_value_1 = external_field_a2_value_1
        general_external_field_a_value_2 = external_field_a2_value_2
      }

      # this will work in principle
      # but all unmapped fields will NOT fall back to the general mapping
      # and instead their original form field values will be used
      30 = external_field_name_a3
      30.valueMap {
        form_field_a_value_2 = external_field_a3_value_2
      }

      # this will work since
      # it copies the general value mapping first
      # and is then changing the mapping specific for this external field
      40 = external_field_name_a4
      40.valueMap < plugin.tx_formrelay_xyz.settings.values.mapping.form_field_name_a
      40.valueMap {
        form_field_a_value_2 = external_field_a4_value_2
      }

      # without an explicit mapping, the general mapping will be used
      50 = external_field_name_a5
    }
  }
}

The above settings will result in this behaviour:

form_field_name_a === "form_field_a_value_1"
result === [
  external_field_name_a1 => "external_field_a1_value_1",
  external_field_name_a2 => "form_field_a_value_1",
  external_field_name_a3 => "form_field_a_value_1",
  external_field_name_a4 => "general_external_field_a_value_1",
  external_field_name_a5 => "general_external_field_a_value_1",
]

form_field_name_a === "form_field_a_value_2"
result === [
  external_field_name_a1 => "external_field_a1_value_2",
  external_field_name_a2 => "form_field_a_value_2",
  external_field_name_a3 => "external_field_a3_value_2",
  external_field_name_a4 => "external_field_a4_value_2",
  external_field_name_a5 => "general_external_field_a_value_2",
]

ValueMapper

Interface: \Mediatis\Formrelay\ConfigurationResolver\ValueMapper\ValueMapperInterface

Optional abstract class to extend: \Mediatis\Formrelay\ConfigurationResolver\ValueMapper\ValueMapper

Resolvers of the type ValueMapper will map the value of a given form field to an appropriate value of the current destination.

If there is no mapping provided the original form field value is used.

The Formrelay will use the GeneralValueMapper to resolve the configuration settings.values.mapping.<field_name>.

All value mappers must implement the method resolve.

/**
 * @param array $context
 * @return string|null
 */
public function resolve(array $context);

The parameter $context provides all necessary context data, at minimum the form data and the name of the field whose value is to be mapped.

GeneralValueMapper

This value mapper processes its configuration in search for sub-value mappers that will eventually return a mapped value.

Generally speaking the configuration is processed in order of its appearance, but there are two exceptions.

The PlainValueMapper will be applied last so that specialised value mappers can overwrite its behaviour.

The IfValueMapper will be applied first because it shall be able to overwrite every other value mapper.

If a configuration key can be resolved as sub-value mapper, it will be resolved and used if its result is not null. Otherwise the configuration processing will continue.

settings.values.mapping.form_field_name_a {
  keyword_a { ... sub config ... }
  keyword_b = ... scalar config ...
  ...
}

If a configuration key can not be resolved as sub-value mapper, it will instead be compared to the value of the current form field. If they equal each other, its sub configuration will be used to resolve a new GeneralValueMapper.

settings.values.mapping.form_field_name_a {
  form_field_value_a_1 { ... sub config ... }
  form_field_value_a_2 = ... sub config ...
  ...
}

If a scalar configuration is found (processed after all other value mappers), the PlainValueMapper is used to determine the mapped value. This is actually the default use case. A simple internal-value-external-value mapping.

settings.values.mapping.form_field_name_a {
  form_field_value_a_1 = external_field_value_a_1
  form_field_value_a_2 = external_field_value_a_2
  ...
}

If there is no value mapper found, that is returning a valid value (not equal to null), then the original value is returned.

PlainValueMapper

This value mapper is simply returning the (scalar) value it has received as configuration. It has already been described at the GeneralValueMapper.

Theoretically speaking it can be applied to the field itself, though this doesn’t make a lot of sense, since all values of that field would be mapped to the same value.

settings.values.mapping.form_field_name_a = constant_external_value_a_for_all_internal_values

It is more common to use it for specific internal values or as result for other sub-value mappers.

settings.values.mapping.form_field_name_a {
        form_field_value_a_1 = external_field_value_a_1
}

IfValueMapper

This value mapper applies a GeneralEvaluation (using its resolve method). If the result is not null (if the corresponding configuration path then or else exists) it will be used for a new GeneralValueMapper to resolve the value mapping.

If the corresponding path (then or else) does not exist, it will return null (as does the GeneralEvaluation) and therefore will be ignored by its parent value mapper.

settings.values.mapping.form_field_name_a {
  form_field_value_a_1 = external_field_value_a_1
  form_field_value_a_1.if {
    form_field_name_b = form_field_value_b_1
    then = external_field_value_a_2
  }
}

It can also be applied on the field itself.

settings.values.mapping.form_field_name_a.if {
  form_field_name_b = form_field_value_b_1
  then {
    form_field_value_a_1 = external_field_value_a_1a
    form_field_value_a_2 = external_field_value_a_2a
  }
  else {
    form_field_value_a_2 = external_field_value_a_2b
    form_field_value_a_1 = external_field_value_a_1b
  }
}

Such conditions can also be nested in any order with any value mapper, that is triggering a new GeneralValueMapper.

settings.values.mapping.form_field_name_a.if {
  form_field_name_b = form_field_value_b_1
  then {
    form_field_value_a_1 = external_field_value_a_1a
    form_field_value_a_2 = external_field_value_a_2a
  }
  else {
    form_field_value_a_2 = external_field_value_a_2b
    form_field_value_a_1.if {
      form_field_name_c = form_field_value_c_1
      then.if {
        form_field_name_d.not = form_field_value_d_1
        not.gate = tx_formrelay_foo,tx_formrelay_bar
        then = external_field_value_a_1b
        else.negate = 1
      }
      else = external_field_value_a_1c
    }
  }
}

NegateValueMapper

This value mapper will evaluate the given form field value to a boolean value and return a negated version.

By default it will return 0 for values, that evaluate to true, and 1 for values, that evaluate to false.

settings.values.mapping.form_field_name_a {
  negate = 1
}

Those values can also be configured. In the example below the result will be no if the form value evaluates to true and yes if the value evaluates to false.

settings.values.mapping.form_field_name_a {
  negate = 1
  negate {
    true = yes
    false = no
  }
}

If the negate value mapper is configured, we do not need to enable it specifically.

settings.values.mapping.form_field_name_a {
  negate {
    true = yes
    false = no
  }
}

The negate value mapper triggers actually a new GeneralValueMapper which means that there can even be sub-value mappers.

As the GeneralValueMapper does, the original form value is used if no sub-value mapper exists (or no sub-value mapper returns a valid result).

settings.values.mapping.form_field_name_a {
  negate {
    form_field_value_a_1 = 0
    form_field_value_a_1.if {
      form_field_name_b = form_field_value_b
      then = 1
    }
  }
}

RawValueMapper

This value mapper behaves like a GeneralValueMapper except that it will not search for any keywords for sub-value mappers.

It is helpful if the form values may contain keywords of value mappers.

settings.values.mapping.form_field_name_a.raw {
    negate = some_external_value_negieren
    if = some_external_value_for_if
}

SwitchValueMapper

This value mapper allows field values to be TypoScript configuration values instead of configuration keys. This is helpful for values which do not follow the rules of TypoScript keys.

The configuration of a switch value mapper is an array of cases where each has one value case which is compared to the actual form field value and one value value which is the result if the comparison succeeds.

settings.values.mapping.form_field_name_a.switch {
  10 {
    case = form.field.value.a.1
    value = external_field_value_a_1
  }
  20 {
    case = form field \value.a 2
    value = external_field_value_a_2
  }
}

The configuration value case can also be applied as scalar value of the switch object.

settings.values.mapping.form_field_name_a.switch {
  10 = form.field.value.a.1
  10.value = external_field_value_a_1
  20 = form field \value.a 2
  20.value = external_field_value_a_2
}

The value of a case is actually processed as a new GeneralValueMapper which leaves room for sub-value mappers.

settings.values.mapping.form_field_name_a.switch {
  10 = form.field.value.a.1
  10.value = external_field_value_a_1
  10.value.if {
    form_field_name_b = form_field_value_b
    then = external_field_value_a_2
    else.negate = 1
  }
  20 ...
}

ContentResolver

Interface: \Mediatis\Formrelay\ConfigurationResolver\ContentResolver\ContentResolverInterface

Optional abstract class to extend: \Mediatis\Formrelay\ConfigurationResolver\ContentResolver\ContentResolver

Resolvers of the type ContentResolver will generate a string by parsing the given configuration as a kind of template.

EXT:formrelay will not use the content resolver itself, but other extension can use it, to process parts of the configuration. For example, a mail-formrelay can use a content resolver to insert form data into the email, e.g. as receiver, subject, mail-body.

The entry point will always be the GeneralContentResolver which provides the method resolve.

public function resolve(array $context): string

All other content resolvers do not need to implement this method. Instead their work is split into two steps: build and finish.

public function build(array &$context): string;
public function finish(array &$context, string &$result): bool;

The method build will create the actual result, while the method finish can modify the accumulated result of all content resolvers, that are involved, afterwards. Also a content resolver can abort the finishing of all remaining content resolvers by returning the value true.

GeneralContentResolver

This content resolver is just processing its configuration and reacting to everything that it finds.

A scalar configuration is triggering a PlainContentResolver which will just use the configuration itself to build the result

output = This is a scalar configuration that is also the output of the content resolver.

Numeric keys will indicate sub-content resolvers, each starting as a new GeneralContentResolver.

output {
  10 = This is a scalar configuration...
  20 = in two parts.
}
# result === This is a scalar configuration...in two parts.

The keyword glue will be used to set a glue string that is written between the (non-empty) results of its sub-content resolvers, where \s is replaced with the space character and \n is replaced with a line break character.

The default glue is an empty string.

output {
  glue = \s
  10 = This is a scalar configuraiton
  20 = in two parts.
}
# result === This is a scalar configuration in two parts.

Any actual keyword of a sub-content resolver will trigger its build and finish methods.

output{
  field = form_field_name_a
  trim = 1
}
output = {first_name}
output.insertData = 1

PlainContentResolver

This content resolver has already been described by the GeneralContentResolver for scalar configurations. It will simply build a result from its configuraiton.

output = Hello World

FieldContentResolver

This content resolver will try to find a field with the name of its configuration in the form data and will output its value.

output.field = form_field_name_a

IfContentResolver

This content resolver applies a GeneralEvaluation (using its resolve method). If the result is not null (if the corresponding configuration path then or else exists) it will be used for a new GeneralContentResolver to build the content.

output = Hello World
output.if {
  form_field_name_a = form_field_value_a_1
  then = Hello Universe
}

If the IfContentResolver produces any output, it will disable all other content resolvers on this configuration. All content resolvers that want to apply in this case, will have to be set in the then or else path of the if structure. If the corresponding path (then or else) does not exist, the content resolver won’t do anything.

# In the example below the then-part does need to set insertData = 1 again,
# because the outer configuration will be disabled completely.
# Inside the if-structure there will also be no trimming, unless we set it there.

output = Hello {name}
output.insertData = 1
output.if {
    form_field_name_a = form_field_value_a_1
    then = Hello {other_name}
    then.insertData = 1
}
output.trim = 1

As usual we can nest the evaluations and content resolver however it suits us.

output {
  10 = Hello
  10.if {
    language = de
    then = Hallo
    else.if {
      language = es
      then = Hola
    }
  }
  20.if {
    form_field_name_a = form_field_value_a_1
    then {
      glue = \s
      10 = Hello
      20 = World
      30.field = form_field_name_b
      trim = 1
    }
    else {
      10 = Foo
      20.if {
        form_field_name_c = form_field_value_c_1
        else = foo {bar} baz
        else.insertData = 1
      }
    }
  }
}

InsertDataContentResolver

This content resolver is modifying the result that is built by all content resolvers, that are involved. It is going through all form fields form_field_name_x and is replacing all occurrences of the string {form_field_name_x} with the value of the form field.

Afterwards it will also remove all such placeholders {...} which were not replaced.

And it will replace all \s with the space character and all \n with a line break character.

output = \s\s\sfoobar\s\s\s
output.insertData = 1
# result === "   foobar   "
output = foo {bar} baz
# result === "foo <value_of_field_bar> baz"
output = foo{some_field_name_that_does_not_exist}bar
output.insertData = 1
# result === "foobar"

TrimContentResolver

This content resolver is modifying the result that is built ba yll content resolvers, that are involved. It is trimming the result, removing all whitespace characters at the beginning and the end of the result.

output = \s\s\sfoobar\s\s\s
output.insertData = 1
output.trim = 1
# result === "foobar"