![Advanced-UI](https://img.shields.io/static/v1?label=UI&message=Advanced&color=lightgrey) ![NOVA-UI](https://img.shields.io/static/v1?label=UI&message=NOVA&color=blue) The *Carbon Copy Plus* allows to automatically populate asset information fields with values coming from node information fields. For this the fields have to have compatible types. The node values can come from an asset's parent nodes (direct trigger) or from the nodes in its node referencing fields. In case there are multiple nodes, possibly from multiple sources, there are aggregation functions to get the desired value. By specifying a scope it can be exactly defined for which assets a configured target is applicable. An asset is updated if it is in scope and there was an asset event for it (create, update metadata, released, add to/remove from node, version add/activate/delete) or a node metadata update event where the change of the updated node leads to a change of an asset's value. However, node changes that would lead to an asset coming in scope without the node changes leading to potential asset changes cannot be detected (edge case, moving a node with some linked assets is a likely scenario that could lead to this problem, then we can either use the task or trigger a change on the assets, if the assets were moved themselves it would be detected). The value is only updated if the newly calculated value is not the same. [MINITOC] ## Properties To be configured in {home}/appserver/conf/custom.properties If a deprecated property (~~crossed out~~) has no description the next propery is a new version of the property and its description still applies. ~~By using the target name "default" default values for targets can be specified.~~ Replaced by templates in v2+. It's as simple as specifying a template and then all its data is copied before writing the other settings. Like this we have much more flexibility, e.g. we could have more than one default or even create template trees. See _*.template_ and _*.templateOnly_ properties. ### General ##### carbonCopyPlus.license > type: string, **required: yes**, default: - The license key for the plugin (product: carbonCopyPlus), provided by *brix*. ##### carbonCopyPlus.threadPoolSize > type: integer, required: no, default: 4 The number of threads in the thread pool. #### carbonCopyPlus.debounceTimeInSeconds > type: integer, required: no, default: 30, version: 2+ The number of seconds events for assets and nodes are collected before the changes are processed by Carbon Copy Plus. ##### ~~carbonCopyPlus.{assetTypeId}.assetTypeId~~ > type: comma-separated list of nodeTypeId, required: no, default: -, version: <2 ##### carbonCopyPlus.assetTypeId.{assetTypeId}.assignIfLinkedToNodeTypeIds > type: comma-separated list of nodeTypeId, required: no, default: -, version: 2+ The asset type with the {assetTypeId} is automatically assigned to an asset with no asset type if it is linked to a node with any of the listed node types. ### Sources A [target](#targets) (essentially an *information field on an asset*) has to have at least one data source (an *information field on a node or a tag*). The other important information on the source is the *trigger*. It specifies which nodes or tags are considered as data sources for an asset (either the nodes to which the asset is directly assigned or the nodes/tags in a specific node/tag referencing information field). It is called trigger because linking an asset to a node or adding a node to the specific asset information field will trigger the copy process. Because the trigger can be a node or tag referencing information field it is possible to get data from more than one node, therefore it is important to either specify or be aware of the default aggregation function when using those. It's also possible to delay the aggregation and let the target do it. The handling of empty values can be relevant for the aggregation also. ##### carbonCopyPlus.source.{name}.template > type: source name, required: no, default: -, version: 2+ A source to copy all properties from before setting the other properties. Source has no *targetOnly* property because a source that is not used in a target is automatically only a template. ##### ~~carbonCopyPlus.{targetName}.{sourceName}.source~~ > type: information field id, required: yes, default: -, version <2 ##### carbonCopyPlus.source.{name}.sourceFieldId > type: information field id, required: yes, default: -, version: 2+ Source node or tag (v2+) information field id. Has to have the same kind as the target field. ##### ~~carbonCopyPlus.{targetName}.{sourceName}.trigger~~ > type: information field id, required: no, default: - (direct assigned), version: <2 ##### carbonCopyPlus.source.{name}.trigger > type: direct (literally) or node/tag reference information field id, default: direct, version: 2+ Node or tag (v2+) reference field id or ~~direct assignment if no trigger was specified (<v2)~~ direct (literally, v2+). ##### ~~carbonCopyPlus.{targetName}.{sourceName}.recursive~~ > type: boolean, required: no, default: false, version: <2 If there is no trigger (direct assignment) then recursive looks at all the nodes in its parent paths. ##### carbonCopyPlus.source.{name}.triggerRecursive > type: string, required: no, default: none, version: 2+ This specifies how to collect values from parent nodes. - **no** doesn't look at any parent nodes. - **first** takes the node or first parent where source is not empty. - **allNonEmpty** takes all the non-empty values from all parent nodes. - **all** takes all the values from all parents node. The main difference can be seen if emptyValue is not set to ignore, then with "all" we can end up with several empty value substitutes for a single node but with "allNonEmpty" with at most one substitute if there was nothing found at all. ##### carbonCopyPlus.source.{name}.aggregation > type: string, required: yes, default: default, version: 2+ The aggregation function to use. For many of the aggregation functions the emptyValue property is very important. Special values (always applicable): - **default** uses `or` for boolean fields, `concatenate` for text fields, `any` for dropdowns, `max` for dates, `union` for tag or node referencing fields and `sum` for numbers. - **delay** collects the values without aggregation and lets the target do it. - **any** takes a random single non-empty value (if there is any). Most useful for dropdowns. Boolean fields (checkboxes): - **and** - **or** Node and tag referencing information fields: - **union** - **intersect** Text information fields (including text area and their localized version, localized just handles each language independently): - **concatenate** Number information fields (long and double): - **sum** - **multiply** - **min** - **max** - **average** Dates: - **min** - **max** ##### carbonCopyPlus.source.{name}.emptyValue > type: string, required: no, default: ignore, version: 2+ What to do with empty (blank) values: - **ignore** (default) - **default** (0 for numbers and dropdowns, false for checkboxes, empty string for text fields and empty set for tag/node referencing fields) - **{replacement}** (item id for dropdowns, integer for number fields, double for double fields, true/false for checkboxes, comma-separated list of tag/node ids for tag/node referencing information fields, string for text fields) ##### carbonCopyPlus.source.{name}.delimiter > type: string, required: no, default: `,`, version: 2+ The delimiter to use for string concatenation. Only relevant for text fields and aggregation function not delay. White-spaces at the start or the end are ignored in properties files, that's why there are the following substitutes available: - `` - `` - `` ##### carbonCopyPlus.source.{name}.order > type: asc or desc, required: no, default: asc, version: 2+ How to sort the individual parts before concatenating strings. ##### carbonCopyPlus.source.{name}.shortenSingleLineText > type: comma-separated list of options, required: no, default: `before, ellipsis, trim`, version: 2+ This defines how to shorten strings for (localized) text fields (max 255 characters). - **after** cuts everything after the 255th character (after concatenation), which is the opposite of **before** (they are mutually exclusive). Use one of those to reset the other options. - **before** cuts the parts before concatenation - **ellipsis** adds ... at the end if something is cut - **trim** before concatenating and adding the ellipsis (prevents white-spaces before the ellipsis) ##### carbonCopyPlus.source.{name}.shortenMinLength > type: integer, required: no, default: 10, version: 2+ The minimal length a text can be shortened to, if the string is still too long it will add as many texts as possible and force a delimiter and ellipsis at the end. ##### carbonCopyPlus.source.{name}.modes > type: comma-separated list of modes, required: no, default: none, version: 2+ Additional modes that can be specified: - **none** can only be used alone, it mainly exists to be able to reset the modes - **distinct** remove duplicate values, only relevant for text ### Targets Targets have an asset information fields to which the data from the specified sources is written. Scopes are also defined on the target. Other than that most properties are similar to the ones of the sources to handle the aggregation of sources. ##### carbonCopyPlus.target.{name}.template > type: target name, required: no, default: -, version: 2+ The target from which all properties are copied before setting the other ones. This property is not inherited from the template. ##### carbonCopyPlus.target.{name}.templateOnly > type: boolean, required: no, default: false, version: 2+ If this is set to true then the target is not executed and just used as template for others. This property is not inherited from the template. ##### ~~carbonCopyPlus.{targetName}.target~~ > type: information field id, required: yes, default: -, version: <2 ##### carbonCopyPlus.target.{name}.informationFieldId > type: information field id, required: yes, default: -, version: 2+ The target asset information field id. ##### carbonCopyPlus.target.{name}.infoFieldUserId > type: user id, required: no, default: API User, version: 2+ The user ID of the user used to write the target information field. ##### carbonCopyPlus.target.{name}.sources > type: comma-separated list of source names, required: yes, default: -, version: 2+ The sources for the target. ##### carbonCopyPlus.target.{name}.scope > type: search util 2 expression, required: no, default: assetId >= 1, version: 2+ Carbon Copy Plus is only triggered on assets within the specified scope. ##### carbonCopyPlus.target.{name}.scopeUserId > type: user id, required: no, default: API User, version: 2+ The user ID of the user used to check whether an asset is in scope or not. This allows for additional filter possibilities through permissions. ##### carbonCopyPlus.target.{name}.aggregation > type: string, required: yes, default: default, version: 2+ The aggregation function to use. Same as for source except that **delay** is not allowed. ##### carbonCopyPlus.target.{name}.emptyValue > type: string, required: no, default: ignore, version: 2+ What to do with empty (blank) values when aggregating the values from the sources. Same possible values as for sources. ##### ~~carbonCopyPlus.{targetName}.delimiter~~ > type: string, required: no, default: `,`, version: <2 ##### carbonCopyPlus.target.{name}.delimiter > type: string, required: no, default: `;`, version: 2+ Delimiter for text concatenation of the values from the sources, special values: ``, ``, `` ##### carbonCopyPlus.target.{name}.order > type: asc or desc, required: no, default: asc, version: 2+ How to sort the data from the sources before concatenating strings. ##### carbonCopyPlus.target.{name}.shortenSingleLineText > type: comma-separated list of options, required: no, default: `before, ellipsis, trim`, version: 2+ This defines how to shorten strings for (localized) text fields, same as for sources. ##### carbonCopyPlus.target.{name}.shortenMinLength > type: integer, required: no, default: 10, version: 2+ The minimal length a text can be shortened to, if the string is still too long it will add as many texts as possible and force a delimiter and ellipsis at the end. ##### carbonCopyPlus.target.{name}.modes > type: comma-separated list of modes, required: no, default: none, version: 2+ Additional modes for the target. - **none** is used to reset the modes (can only be used alone) - **distinct** discards duplicates before concatenation - **keep** only writes target fields if they are empty - **noClear** does not clear information fields (very dubious property, not recommended, the task on the other hand has the same property which can be useful) - **append** noderef and tag fields only (since v2.3) ##### ~~carbonCopyPlus.{targetName}.keep~~ > type: boolean, required:no, default: false, version: <2 Only change value if the target field is empty. ##### ~~carbonCopyPlus.{targetName}.mode~~ > type: string, required: yes, default: -, version: <2 How to collect values if the target has several sources or trigger fields with multiple nodes in it. - INTERSECT, UNION: for node referencing and text information fields - AND, OR: for boolean field target, i.e. checkboxes - MIN, MAX, SUM: for numbers and dates (no sum for dates) ### Task A task to re-apply the carbon copy initially or after node values have changed. ##### carbonCopyPlus.task.enabled > type: boolean, required: no, default: true, version: 2+ Set this property to false to disable the completely, this prevents someone from accidentally starting the task. ##### carbonCopyPlus.task.targets > type: comma-separated list of target names, required: no, default: - (all targets except templates), version: 2+ The targets which should be executed. It is possible to define special targets just used in tasks by defining them as templateOnly and then listing them explicitly. ##### carbonCopyPlus.task.scope > type: search util 2 expression, required: no, default: assetId >= 1, version: 2+ Define a scope for the task. This property and targets can be useful if there are a lot of assets to only update what is really needed and not everything. ##### carbonCopyPlus.task.scopeUserId > type: user id, required: no, default: API User, version: 2+ The user ID of the user used to check whether an asset is in scope or not. This allows for additional filter possibilities through permissions. ##### carbonCopyPlus.task.modes > type: comma-separated list of modes, required: no, default: noClear, version: 2+ - **clear** or **noClear**: the latter prevents the task from clearing information fields, this can be useful because there might never have been any triggers and the target field was maintained manually, in this case the field shouldn't be cleared (on the other hand if we actively remove a trigger the field should be cleared, this would be prevented by the noClear mode on the target and has nothing to do with the task) ##### ~~carbonCopyPlus.{targetName}.clearInformationFields~~ > type: boolean, required: no, default: true, version: <2 Clear information fields when a trigger becomes empty. ##### ~~carbonCopyPlus.taskOnlyFiresNonEmptyTriggers~~ > type: boolean, required: no, default: `true`, version: <2 If this property is set to true then the system task only triggers assets and targets if the target has direct assigned (recursive) sources or the target has sources with non-empty asset trigger fields. ## Template (v2) There are a lot of properties, but most of them usually don't need to be changed. Some hints for a basic configuration: - only one source per target - aggregation, empty value, trigger recursive and modes (e.g. distinct) is only set on the source (if needed at all) and not on the target - source field and target field are the same field (source on node, target on asset) - the scope is set on the target (optionally) ``` carbonCopyPlus.license= carbonCopyPlus.threadPoolSize=4 carbonCopyPlus.debounceTimeInSeconds=30 carbonCopyPlus.assetTypeId.{assetTypeId}.assignIfLinkedToNodeTypeIds= carbonCopyPlus.source.{name}.template= carbonCopyPlus.source.{name}.sourceFieldId= carbonCopyPlus.source.{name}.trigger=direct carbonCopyPlus.source.{name}.triggerRecursive=no carbonCopyPlus.source.{name}.aggregation=default carbonCopyPlus.source.{name}.emptyValue=ignore carbonCopyPlus.source.{name}.delimiter=, carbonCopyPlus.source.{name}.order=asc carbonCopyPlus.source.{name}.shortenSingleLineText=before,ellipsis,trim carbonCopyPlus.source.{name}.shortenMinLength=10 carbonCopyPlus.source.{name}.modes=none carbonCopyPlus.target.{name}.template= carbonCopyPlus.target.{name}.templateOnly=false carbonCopyPlus.target.{name}.informationFieldId= carbonCopyPlus.target.{name}.infoFieldUserId= carbonCopyPlus.target.{name}.sources= carbonCopyPlus.target.{name}.scope=assetId >= 1 carbonCopyPlus.target.{name}.sopeUserId= carbonCopyPlus.target.{name}.aggregation=default carbonCopyPlus.target.{name}.emptyValue=ignore carbonCopyPlus.target.{name}.delimiter=; carbonCopyPlus.target.{name}.order=asc carbonCopyPlus.target.{name}.shortenSingleLineText=before,ellipsis,trim carbonCopyPlus.target.{name}.shortenMinLength=10 carbonCopyPlus.target.{name}.modes=none carbonCopyPlus.task.enabled=true carbonCopyPlus.task.targets= carbonCopyPlus.task.scope=assetId >= 1 carbonCopyPlus.task.scopeUserId= carbonCopyPlus.task.modes=noClear ``` ## ~~Example (v1)~~ ```ini carbonCopyPlus.license=... carbonCopyPlus.actionMenuEnabled=false carbonCopyPlus.default.delimiter=; carbonCopyPlus.default.mode=UNION carbonCopyPlus.default.clearInformationFields=true # Copy field 104 when assigned through noderef 106 carbonCopyPlus.1.target=104 carbonCopyPlus.1.1.source=104 carbonCopyPlus.1.1.trigger=106 # Copy field 136 when assigned through noderef 106 carbonCopyPlus.2.target=136 carbonCopyPlus.2.1.source=136 carbonCopyPlus.2.1.trigger=106 # Copy from 135 to 149 when assigned through noderef 150 carbonCopyPlus.3.target=149 carbonCopyPlus.3.1.source=135 carbonCopyPlus.3.1.trigger=150 ``` ## Compatibility Matrix | Carbon Copy Plus | CELUM (min. version) | |:-----------------| :---- | | 1.0.0 | 6.4 | | 2.0.0 | 6.4 | ## Release Notes ##### 1.0.0 > Released 2022-08-25 Initial version ##### 2.0.0 > Released 2022-12-08 - All field types are supported as sources and targets - Parents, nodes in node referencing fields and tags in tag fields are supported as triggers - Assets are updated automatically on asset changes and on node changes that would lead to a different information field value for an asset, but not if only the scope changes and nothing of the previously mentioned properties. - Support for various aggregations - More flexible configuration supporting inheritance - ... ##### 2.1.0 > Released 2023-02-16 - Fixed recursive triggers