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/Entity
topackages/property_manager/src/Concrete/Entity
, changing namespaces fromPropCo\Property\Location
toConcrete\Package\PropertyManager\Entity
.Transfer
property_location
attribute type:mv application/blocks/property_location packages/property_manager/attributes/property_location
In the package's
install()
method, instantiateConcrete\Core\Attribute\TypeFactory
usingConcrete\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 set
Associate 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 attributesfile
for file attributessite
for site attributesuser
for 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 to1
to include the attribute in the search by keywordssearchable
[optional, defaults to0
] set to1
to 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 to1
to show the attribute in public profile pagesmember-list-displayed
[optional, defaults to0
] set to1
to show the attribute in member list pagesprofile-editable
[optional, defaults to0
] set to1
to make the attribute editable in profile pagesprofile-required
[optional, defaults to0
] set to1
to make the attribute editable and required in profile pagesregister-editable
[optional, defaults to0
] set to1
to show the attribute on registration formsregister-required
[optional, defaults to0
] set to1
to 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 to1
Concrete 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,Yes
will 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_time
for both date and time,date
for date only,text
for generic input fieldsuse-now-if-empty
: if set to1
Concrete will suggest the current date/time if emptytime-resolution
: [defaults to60
] The time resolution in seconds to be used whenmode
isdate_time
(for instance:60
implies 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_asc
to use the display order,alpha_asc
for an alphabetical order,popularity_desc
to show the most popular firstallow-other-values
if 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
name
attribute 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 attributesfile
for file attributessite
for site attributesuser
for user attributes
handle
[required]: the attribute set handlename
[required]: the name of the attribute setlocked
[optional]: if set to1
the 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>