Attribute Keys
Attribute Keys in a Package
Review the theme packaging documentation for understanding Concrete CMS package format and controller file.
To add a custom attribute type like 'Property Location' to a 'Property Manager' package with handle property_manager:
Create package directories:
mkdir packages/property_manager mkdir packages/property_manager/attributes/Move entities from
application/src/Entitytopackages/property_manager/src/Concrete/Entity, changing namespaces fromPropCo\Property\LocationtoConcrete\Package\PropertyManager\Entity.Transfer
property_locationattribute type:mv application/blocks/property_location packages/property_manager/attributes/property_locationIn the package's
install()method, instantiateConcrete\Core\Attribute\TypeFactoryusingConcrete\Core\Application\Application:public function install() { $pkg = parent::install(); $factory = $this->app->make('Concrete\Core\Attribute\TypeFactory'); $type = $factory->getByHandle('property_location'); if (!is_object($type)) { $type = $factory->add('property_location', 'Property Location', $pkg); } }Link attribute type with pages:
$service = $this->app->make('Concrete\Core\Attribute\Category\CategoryService'); $category = $service->getByHandle('collection')->getController(); $category->associateAttributeKeyType($type);
$service->getByHandle('collection') retrieves a Concrete\Core\Entity\Attribute\Category object. Use getController for the Concrete\Core\Attribute\Category\PageCategory class.
Grouping Attribute Keys into Sets
Add attribute keys to sets during package installation for organization or integration with existing sets.
For an existing "Exclude Subpages from Nav" attribute:
Access the key object:
$key = $category->getByHandle('exclude_subpages_from_nav'); if (!is_object($key)) { $key = new PageKey(); $key->setAttributeKeyHandle('exclude_subpages_from_nav'); $key->setAttributeKeyName('Exclude Subpages from Nav'); $key = $category->add('boolean', $key, null, $pkg); }Retrieve the attribute set entity:
$factory = $this->app->make('Concrete\Core\Attribute\SetFactory'); $navSet = $factory->getByHandle('navigation'); // the built-in navigation setAssociate the key to the set:
$service = $this->app->make('Concrete\Core\Attribute\Category\CategoryService'); $categoryEntity = $service->getByHandle('collection'); $category = $categoryEntity->getController(); $setManager = $category->getSetManager(); $setManager->addKey($navSet, $key);
To create a new set and add keys:
Check if the set exists:
$factory = $this->app->make('Concrete\Core\Attribute\SetFactory'); $set = $factory->getByHandle('fancy_new_set'); if (!is_object($set)) { // Set doesn't exist }Add the set if not present:
$service = $this->app->make('Concrete\Core\Attribute\Category\CategoryService'); $categoryEntity = $service->getByHandle('collection'); $category = $categoryEntity->getController(); $setManager = $category->getSetManager(); $set = $setManager->addSet('fancy_new_set', 'Fancy New Set', $pkg);Associate keys to the new set:
$setManager->addKey($set, $key);
Validating Attribute Keys and Values
Complex attribute types need validation before saving to prevent database issues. This applies to both keys and values, ensuring they meet specific criteria.
Validating Attribute Keys
For validation, consider an attribute key with a custom select menu. Implement a validateKey() method in the controller to ensure an option is selected. Add an empty option to the type_form.php select menu and then validate:
<fieldset>
<legend><?= t('Location Settings') ?></legend>
<div class="form-group">
<label class="control-label" for="formDisplayMethod">Form Display Method</label>
<select class="form-control" name="formDisplayMethod">
<option value="">** Choose a Display Method</option>
<!-- Other options here -->
</select>
</div>
</fieldset>
public function validateKey($data = false)
{
$e = $this->app->make('error');
if (!isset($data['formDisplayMethod']) || !in_array($data['formDisplayMethod'], ['select', 'radio_list'])) {
$e->add(t('You must specify a display method.'));
}
return $e;
}
Validating Attribute Value Forms
Similarly, add validation for an attribute's form submission. Use validateForm($data) in the attribute type controller:
// Form with None option and other selections
public function validateForm($data)
{
if (!isset($data['propertyLocationID']) || !$data['propertyLocationID']) {
return new FieldNotPresentError(new AttributeField($this->getAttributeKey()));
}
$propertyLocationID = intval($data['propertyLocationID']);
if (!in_array($propertyLocationID, [1, 2, 3, 4])) {
return new Error(t('Invalid property location ID.'), new AttributeField($this->getAttributeKey()));
}
return true;
}
Validating Attributes in Current State, Not Form Submission
To validate attributes based on their current value objects rather than form submissions, use validateValue():
public function validateValue()
{
$location = $this->getValue();
if (!is_object($location)) {
return new Error(t('Property Location missing.'), new AttributeField($this->getAttributeKey()));
}
if (!$location->getLocationID()) {
return new Error(t('Invalid property location ID.'), new AttributeField($this->getAttributeKey()));
}
return true;
}
This process ensures attributes are validated in various contexts.
CIF Example: Attribute Keys
Attribute keys are defined inside an attributekeys tag right under the main concrete5-cif tag.
Every attribute key is defined by an attributekey tag.
<?xml version="1.0"?>
<concrete5-cif version="1.0">
<attributekeys>
<attributekey ... />
<attributekey ... />
...
</attributekeys>
</concrete5-cif>
Common attributekey attributes
Any type of attribute key have these attributes:
package[required] is the handle of the package that installs the attributecategory[required] is the category handle of the attribute key. These are the the default category handles:collection: for page attributesfilefor file attributessitefor site attributesuserfor user attributes
type[required] is the handle of type of the attribute values. These are the default attribute type handles:address: address attribute valueboolean: checkbox attribute valuedate_time: date/time attribute valueemail: email attribute valueexpress: express entity attribute valueimage_file: image/file attribute valuenumber: number attribute valuepage_selector: page selector attribute valuerating: rating attribute valueselect: option list attribute valuesocial_links: social links attribute valuetelephone: phone number attribute valuetext: text attribute valuetextarea: text area attribute valuetopics: topics attribute valueurl: URL attribute value
handle[required] is the handle of the attribute keyname[required] is the name of the attribute keyindexed[optional, defaults to0] set to1to include the attribute in the search by keywordssearchable[optional, defaults to0] set to1to make the attribute available in custom dashboard searches
Attributes for the user attribute keys
In addition to the common attributes, you can also use these attributes:
profile-displayed[optional, defaults to0] set to1to show the attribute in public profile pagesmember-list-displayed[optional, defaults to0] set to1to show the attribute in member list pagesprofile-editable[optional, defaults to0] set to1to make the attribute editable in profile pagesprofile-required[optional, defaults to0] set to1to make the attribute editable and required in profile pagesregister-editable[optional, defaults to0] set to1to show the attribute on registration formsregister-required[optional, defaults to0] set to1to require the attribute on registration forms
Example:
<attributekey
package="my_package_handle"
category="user"
type="text"
handle="first_last_name"
name="First and last name"
indexed="1"
searchable="1"
profile-displayed="1"
member-list-displayed="1"
profile-editable="1"
profile-required="1"
register-editable="1"
register-required="1"
/>
Configuring attribute types
Some of the attribute types can be configured, as described below.
address attribute types
You can add a type element as a child of the attributekey element with these attributes:
default-country: the two-letter ISO 3166 code of the default countrygeolocate-country: if set to1Concrete CMS should try to automatically determine the country given the visitors IP address
If you want to restrict the selectable countries, you can add a custom-countries attribute with a value of 1, and add as many country elements as in the example below:
<attributekey type="address" ...>
<type default-country="DE" geolocate-country="1" custom-countries="1">
<countries>
<country>DE</country>
<country>IT</country>
<country>US</country>
</countries>
</type>
</attributekey>
boolean attribute types
You can add a type element as a child of the attributekey element with these attributes:
checked: if specified and not empty, the checkbox will be checked by defaultcheckbox-label: the text of the label to be displayed next to the checkbox (if empty or not specified,Yeswill be displayed).
Example
<attributekey type="boolean" ...>
<type checked="1" checkbox-label="yes!" />
</attributekey>
date_time attribute types
You can add a type element as a child of the attributekey element with these attributes:
mode[defaults todate_time]: set todate_timefor both date and time,datefor date only,textfor generic input fieldsuse-now-if-empty: if set to1Concrete will suggest the current date/time if emptytime-resolution: [defaults to60] The time resolution in seconds to be used whenmodeisdate_time(for instance:60implies that users will be able to insert times without seconds)
Example
<attributekey type="date_time" ...>
<type mode="date_time" use-now-if-empty="0" time-resolution="300" />
</attributekey>
image_file attribute types
You can add a type element as a child of the attributekey element with these attributes:
mode: if set tohtml_input, users will use a standard HTML input to upload a file, otherwise they will use a file manager selector to pick a file.
Example
<attributekey type="image_file" ...>
<type mode="html_input" />
</attributekey>
select attribute types
You can add a type element as a child of the attributekey element with these attributes:
allow-multiple-values: if set to1, multiple options can be chosendisplay-multiple-values: if set to1, Concrete will display the full option listdisplay-order[defaults todisplay_asc]: set todisplay_ascto use the display order,alpha_ascfor an alphabetical order,popularity_descto show the most popular firstallow-other-valuesif set to1, users will be able to add new values to the list
You can specify the option value by using option tags inside a options tag, like in the following example:
<attributekey type="select" ...>
<type allow-multiple-values="1" display-order="alpha_asc" display-multiple-values="1" allow-other-values="1">
<options>
<option value="Option 1" />
<option value="Option 2" />
<option value="Option 3" />
</options>
</type>
</attributekey>
text attribute types
You can add a type element as a child of the attributekey element with these attributes:
placeholder: the placeholder text
You can specify the option value by using option tags inside a options tag, like in the following example:
<attributekey type="text" ...>
<type placeholder="Please enter your name" />
</attributekey>
textarea attribute types
You can add a type element as a child of the attributekey element with these attributes:
mode[defaults totext]: if set torich_text, users will be able to enter formatted text using the HTML editor, otherwise they will be presented a plain text area
You can specify the option value by using option tags inside a options tag, like in the following example:
<attributekey type="textarea" ...>
<type mode="rich_text" />
</attributekey>
topics attribute types
You have to add a tree element as a child of the attributekey element with these attributes:
allow-multiple-values: if set to1, multiple nodes can be chosenname: the name of a treepath: the path to the default parent node (always prefixed with a/)
You can create topic trees with a tree tag inside a trees tag (children of concrete5-cif).
The tree tag must have:
- a
type="topic"attribute - a
nameattribute that defines the topic tree name
The topic tree is defined by topic tags, possibly grouped in category tags. Both topic and category tags needs to have a name attribute.
Here's an example:
<?xml version="1.0"?>
<concrete5-cif version="1.0">
<trees>
<tree type="topic" name="Your Topics Tree">
<category name="Category 1">
<category name="Category-1.1">
<topic name="Topic-1.1-1"/>
<topic name="Topic-1.1-2"/>
</category>
<topic name="Topic-1-2"/>
</category>
<category name="Category-2">
<topic name="Topic-2-1"/>
<topic name="Topic-2-2"/>
</category>
<topic name="Topic-3"/>
</tree>
</trees>
<attributekeys>
<attributekey type="topics" ...>
<tree name="Your Topics Tree" path="/" allow-multiple-values="1" />
</attributekey>
<attributekey type="topics" ...>
<tree name="Your Topics Tree" path="/Category-1/Topic-1-2" />
</attributekey>
</attributekeys>
</concrete5-cif>
Grouping attributes in sets
You can define sets of attributes by using attributeset tags under a attributesets tag (child of the root concrete5-cif tag).
Every attributeset tag may have these attributes:
package[required]: the handle of the package that installs the attribute setcategory[required]: the category handle of the attribute keys. These are the the default category handles:collection: for page attributesfilefor file attributessitefor site attributesuserfor user attributes
handle[required]: the attribute set handlename[required]: the name of the attribute setlocked[optional]: if set to1the attribute set can't be deleted and its handle can't be renamed
Every attributeset tag has one or more attributekey children, whose handle property is the handle of the attribute key to be added to the set.
Example:
<?xml version="1.0"?>
<concrete5-cif version="1.0">
<attributesets>
<attributeset package="my_package_handle" category="user" handle="my_set" name="My Set" locked="1">
<attributekey handle="my_attribute_1" />
<attributekey handle="my_attribute_2" />
<attributekey handle="my_attribute_3" />
</attributeset>
</attributesets>
</concrete5-cif>