# NAME

RT::Extension::ConditionalCustomFields - CF conditioned by the value of another CF

# DESCRIPTION

This plugin provides the ability to display/edit a [custom field](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) – called the "conditioned by [custom field](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html)" throughout this documentation – conditioned by the value of another [custom field](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) – the "condition [custom field](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html)" – for the same object, which can be anything that can have custom fields ([ticket](https://docs.bestpractical.com/rt/6.0.2/RT/Ticket.html), [queue](https://docs.bestpractical.com/rt/6.0.2/RT/Queue.html), [ticket transaction](https://docs.bestpractical.com/rt/6.0.2/RT/Transaction.html), [user](https://docs.bestpractical.com/rt/6.0.2/RT/User.html), [group](https://docs.bestpractical.com/rt/6.0.2/RT/Group.html), [article](https://docs.bestpractical.com/rt/6.0.2/RT/Article.html), [class](https://docs.bestpractical.com/rt/6.0.2/RT/Class.html) or [asset](https://docs.bestpractical.com/rt/6.0.2/RT/Asset.html)).

The condition can be setup on the Admin page for editing the conditioned by [custom field](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html). From version 0.99, any [custom field](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) can be chosen as the condition [custom field](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) (whereas for earlier version, only `Select` [custom fields](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) were eligible), and you can specify which operator is to be applied against which value(s) for the condition to be met.

Available operators are:

- `is`

    The condition is met if and only if the current value of the [instanciated conditioned by custom field](https://docs.bestpractical.com/rt/6.0.2/RT/ObjectCustomField.html) is equal to the value (or one of the values, see below for multivalued condition) setup for this condition. With `isn't` operator described below, `is` operator is the only one which is eligible for selectable [custom fields](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) – with `Select`, `Combobox` or `Autocomplete` type –, since their values are to be selected from a set of values. For `Date` and `DateTime` [custom fields](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html), this operator is named `on`.

- `isn't`

    The condition is met if and only if the current value of the [instanciated conditioned by custom field](https://docs.bestpractical.com/rt/6.0.2/RT/Queue.html) is different from the value (or none of the values, see below for multivalued condition) setup for this condition. With `is` operator described above, `isn't` operator is the only one which is eligible for selectable [custom fields](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) – with `Select`, `Combobox` or `Autocomplete` type –, since their values are to be selected from a set of values. For `Date` and `DateTime` [custom fields](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html), this operator is named `not on`.

- `match`

    The condition is met if and only if the current value of the [instanciated conditioned by custom field](https://docs.bestpractical.com/rt/6.0.2/RT/ObjectCustomField.html) is included in the value setup for this condition, typically if the current value is a substring of the condition value. As said above, selectable [custom fields](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) – with `Select`, `Combobox` or `Autocomplete` type are not eligible for this operator. Also, `Date` and `DateTime` [custom fields](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) are not eligible for this operator.

- `doesn't match`

    The condition is met if and only if the current value of the [instanciated conditioned by custom field](https://docs.bestpractical.com/rt/6.0.2/RT/ObjectCustomField.html) isn't included in the value setup for this condition, typically if the current value isn't a substring of the condition value. As said above, selectable [custom fields](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) – with `Select`, `Combobox` or `Autocomplete` type are not eligible for this operator. Also, `Date` and `DateTime` [custom fields](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) are not eligible for this operator.

- `less than`

    The condition is met if and only if the current value of the [instanciated conditioned by custom field](https://docs.bestpractical.com/rt/6.0.2/RT/ObjectCustomField.html) is less than or equal to the value setup for this condition. The comparison is achieved according to some kind of [natural sort order](https://en.wikipedia.org/wiki/Natural_sort_order), that is: number values are compared as numbers, strings are compared alphabetically, insensitive to case and accents (`a = á`, `a = A`). Moreover, IP Adresses (IPv4 and IPv6) are expanded to be compared as expected. As said above, selectable [custom fields](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) – with `Select`, `Combobox` or `Autocomplete` type are not eligible for this operator.

- `greater than`

    The condition is met if and only if the current value of the [instanciated conditioned by custom field](https://docs.bestpractical.com/rt/6.0.2/RT/ObjectCustomField.html) is greater than or equal to the value setup for this condition. The comparison is achieved according to some kind of [natural sort order](https://en.wikipedia.org/wiki/Natural_sort_order), that is: number values are compared as numbers, strings are compared alphabetically, insensitive to case and accents (`a = á`, `a = A`), and dates with or without times are compared chronogically. Moreover, IP Adresses (IPv4 and IPv6) are expanded to be compared as expected. As said above, selectable [custom fields](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) – with `Select`, `Combobox` or `Autocomplete` type are not eligible for this operator.

- `between`

    The condition is met if and only if the current value of the [instanciated conditioned by custom field](https://docs.bestpractical.com/rt/6.0.2/RT/ObjectCustomField.html) is greater than or equal to the first value setup for this condition and is less than or equal to the second value setup for this condition. That means that when this operator is selected, two values have to be entered. The comparison is achieved according to some kind of [natural sort order](https://en.wikipedia.org/wiki/Natural_sort_order), that is: number values are compared as numbers, strings are compared alphabetically, insensitive to case and accents (`a = á`, `a = A`), and dates with or without times are compared chronogically. Moreover, IP Adresses (IPv4 and IPv6) are expanded to be compared as expected. As said above, selectable [custom fields](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) – with `Select`, `Combobox` or `Autocomplete` type are not eligible for this operator.

As an exception, `IPAddressRange` [custom fields](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) are not eligible as condition [custom fields](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html), since there is not really any sense in comparing two ranges of IP addresses. `IPAddress` [custom fields](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html), combined with `between` operator, should be sufficient for most cases checking whether an IP address is included in a range.

If the condition [custom field](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) is selectable – with `Select`, `Combobox` or `Autocomplete` type – it can be multivalued. Then, the condition for an object is met as soon as the condition is met by at least one value of the [instanciated conditioned by custom field](https://docs.bestpractical.com/rt/6.0.2/RT/ObjectCustomField.html) for this object.

If a [custom field](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) is based on another (parent) [custom field](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) which is conditioned by, this (child) [custom field](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) will of course also be conditioned by (with the same condition as its parent). Nevertheless, there is a caveheat in  display mode: the order matters! That is the parent [custom field](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) should have a lower sort order than the child [custom field](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html).

From version 0.07, the condition can be multivalued, that is: the conditioned [custom field](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) can be displayed/edited if the condition [custom field](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) has one of these values (In other words: there is an `OR` bewteen the values of the condition). The condition [custom field](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) can be a select custom field with values defined by [CustomFieldValues](https://docs.bestpractical.com/rt/6.0.2/RT/CustomFieldValues.html) or an [external custom field](https://docs.bestpractical.com/rt/6.0.2/extending/external_custom_fields.html).

_Note that version 0.07 is a complete redesign: the API described below has changed; also, the way that ConditionedBy property is store has changed. If you upgrade from a previous version, you have to reconfigure the custom fields which are conditionned by._

Version 0.99 is also a complete redesign, with API changed, but backward compatibility with previously configured [custom fields](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html), assuming the default condition operator is `is`.

From version 1.13, `HTML` [custom fields](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) introduced in RT 5.0.3 can be defined as condition [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html).

# RT VERSION

Works with RT 6 (use v1.x versions of this module for previous version of RT)

# INSTALLATION

- `perl Makefile.PL`
- `make`
- `make install`

    May need root permissions

- Edit your `/opt/rt6/etc/RT_SiteConfig.pm`

    Add this line:

        Plugin('RT::Extension::ConditionalCustomFields');

- Clear your mason cache

        rm -rf /opt/rt6/var/mason_data/obj

- Restart your webserver

# CONFIGURATION

Usually, groupings of custom fields, as defined in `$CustomFieldGroupings` configuration variable, is _not_ enabled in SelfService. This is the case if you use RT Core. Anyway, some RT instances could have overridden this restriction to enable groupings of custom fields in SelfService.

In this case, you should add to your configuration file (`/opt/rt6/etc/RT_SiteConfig.pm`) the following line, setting `$SelfServiceCustomFieldGroupings` configuration variable to a true value:

    Set($SelfServiceCustomFieldGroupings, 1);

# METHODS

`ConditionalCustomFields` adds a `ConditionedBy` property, that is a condition [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html), an operator and one or more values, along with the following methods, to conditioned by [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) objects:

## SetConditionedBy CF, OP, VALUE

Set the `ConditionedBy` property for this [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) object to [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) `CF` with operator set to `OP` and value set to `VALUE`. `CF` should be an existing [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) object or the id of an existing [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) object, or the name of an unambiguous existing [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) object. `OP` should be `is`, `isn't`, `match`, `doesn't match`, `less than`, `greater than` or `between`. `VALUE` should be a string or an anonymous array of strings (for selectable [custom fields](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) or `between` operator). Current user should have `SeeCustomField` and `ModifyCustomField` rights for this conditioned by [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) and `SeeCustomField` right for the condition [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html). Returns (1, 'Status message') on success and (0, 'Error Message') on failure.

## ConditionedBy

Returns the current `ConditionedBy` property for this conditioned by [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) object as a hash with keys `CF` containing the id of the condition [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html), `op` and `vals` containing the condition operator as string, and the condition value as an array of strings (so we can store several values for selectable [custom fields](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) or `between` operator, but generally the `vals` array includes only one string). If neither this conditioned by [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) nor one of its ancestor is conditioned by the `CF` condition [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html), that is: if their `ConditionedBy` property is not (recursively) defined, returns `undef`. Current user should have `SeeCustomField` right for both this conditioned by [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) and the condition [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) which this [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) is conditioned recursively by. _"Recursively"_ means that this method will search for a `ConditionedBy` property for this [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) object, then for the [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) this one is `BasedOn`, and so on until it finds an ancestor `Category` with a `ConditionedBy` property or, the [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) which is being looked up, is not based on any ancestor `Category`.

# INITIALDATA

Also, `ConditionalCustomFields` allows to set the `ConditionedBy` property when creating [CustomFields](https://docs.bestpractical.com/rt/6.0.2/RT/CustomFields.html) from an `initialdata` file, with one of the following syntaxes:

    @CustomFields = (
        {
            Name => 'Condition',
            Type => 'SelectSingle',
            RenderType => 'Dropdown',
            Queue => [ 'General' ],
            LookupType => 'RT::Queue-RT::Ticket',
            Values => [
                { Name => 'Passed', SortOrder => 0 },
                { Name => 'Failed', SortOrder => 1 },
                { Name => 'Schrödingerized', SortOrder => 2 },
            ],
            Pattern => '(?#Mandatory).',
            DefaultValues => [ 'Failed' ],
        },
        {
            Name => 'Conditioned with cf name and value',
            Type => 'FreeformSingle',
            Queue => [ 'General' ],
            LookupType => 'RT::Queue-RT::Ticket',
            ConditionedByCF => 'Condition',
            ConditionedBy => 'Passed',
        },
        {
            Name => 'Conditioned with cf id and value',
            Type => 'FreeformSingle',
            Queue => [ 'General' ],
            LookupType => 'RT::Queue-RT::Ticket',
            ConditionedByCF => 66,
            ConditionOp => "isn't",
            ConditionedBy => 'Failed',
        },
        {
            Name => 'Conditioned with multiple values',
            Type => 'Freeform',
            MaxValues => 1,
            Queue => [ 'General' ],
            LookupType => 'RT::Queue-RT::Ticket',
            ConditionedByCF => 'Condition',
            ConditionedBy => ['Passed', 'Schrödingerized'],
        },
    );

This examples creates a `Select` condition [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html), named `Condition` and three conditioned by [CustomFields](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html). [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) `Condition` should have the value `Passed`, for [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) `Conditioned with cf name and value` to be displayed or edited. [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) `Condition` should not have the value `Failed` for [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) `Conditioned with cf id and value` to be displayed or edited. [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) `Condition` should have one of the values `Passed` or `Schrödingerized` for [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) `Conditioned with multiple values` to be displayed or edited.

Additional fields for an element of `@CustomFields` are:

- `ConditonedByCF`

    The condition [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) that this new [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) should conditioned by. It can be either the `id` or the `Name` of a previously created [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html). This implies that the condition [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) should be declared before this one in the `initialdata` file, or it should already exist. When `ConditionedByCF` attribute is set, `ConditionedBy` field should always also be set.

- `ConditonedBy`

    The value as a `string` of the condition [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) defined by the `ConditionedByCF` field (which is mandatory).

- `ConditonOp`

    The operator as a `string` to use for comparison, either `is`, `isn't`, `match`, `doesn't match`, `less than`, `greater than` or `between`. This field is optional, defaults to `is`.

# TEST SUITE

`ConditionalCustomFields` comes with a test suite. As for every [RT extention](https://docs.bestpractical.com/rt/6.0.2/writing_extensions.html#Tests), to run it, you will need a installed `RT`, set up in [development mode](https://docs.bestpractical.com/rt/6.0.2/hacking.html#Test-suite).

But, since `ConditionalCustomFields` operates dynamically to show or hide [custom fields](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html), most of its magic happens in `Javascript`. Therefore, the test suite requires a scriptable headless browser with `Javascript` capabilities. In previous versions of `ConditionalCustomFields`, you would also need to install [PhantomJS](http://phantomjs.org/), along with [WWW::Mechanize::PhantomJS](https://metacpan.org/pod/WWW%3A%3AMechanize%3A%3APhantomJS) and [Selenium::Remote::Driver](https://metacpan.org/pod/Selenium%3A%3ARemote%3A%3ADriver). Unfortunately [PhantomJS](http://phantomjs.org/) has been deprecated for years and was unable to cope with javascript librairies, mostly `bootstrap.js`, used by `RT 5` core, and moreover with `htmx.js` used by `RT 6` core. Therefore, the test suite has been reduced to include only javascript free tests.

# CAVEATS

`HTML` [custom fields](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html) can still be defined as condition [CustomField](https://docs.bestpractical.com/rt/6.0.2/RT/CustomField.html). But due to the upgrade to `CKEditor 5`, introduced in RT 6, changed cannot be dynamically detected. That means that if a `CF B` is conditionned by an HTML `CF A`, inline changes in `CF A` won't trigger showing or hidding `CF B`. Still, these changes to `CF A` will affect `CF B` when the inline editing is saved.

# AUTHOR

Gérald Sédrati <gibus@easter-eggs.com>

# REPOSITORY

[https://github.com/gibus/RT-Extension-ConditionalCustomFields](https://github.com/gibus/RT-Extension-ConditionalCustomFields)

# BUGS

All bugs should be reported via email to

[bug-RT-Extension-ConditionalCustomFields@rt.cpan.org](mailto:bug-RT-Extension-ConditionalCustomFields@rt.cpan.org)

or via the web at

[rt.cpan.org](http://rt.cpan.org/Public/Dist/Display.html?Name=RT-Extension-ConditionalCustomFields).

# LICENSE AND COPYRIGHT

This software is Copyright (c) 2017-2025 by Gérald Sédrati, Easter-Eggs

This is free software, licensed under:

The GNU General Public License, Version 3, June 2007
