Attribute Keys

Improvements?

Let us know by posting here.

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:

  1. Create package directories:

    mkdir packages/property_manager
    mkdir packages/property_manager/attributes/
    
  2. Move entities from application/src/Entity to packages/property_manager/src/Concrete/Entity, changing namespaces from PropCo\Property\Location to Concrete\Package\PropertyManager\Entity.

  3. Transfer property_location attribute type:

    mv application/blocks/property_location packages/property_manager/attributes/property_location
    
  4. In the package's install() method, instantiate Concrete\Core\Attribute\TypeFactory using Concrete\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);
       }       
    }
    
  5. 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:

  1. 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);
    }
    
  2. Retrieve the attribute set entity:

    $factory = $this->app->make('Concrete\Core\Attribute\SetFactory');
    $navSet = $factory->getByHandle('navigation'); // the built-in navigation set
    
  3. 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:

  1. 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
    }
    
  2. 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);
    
  3. 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 attribute
  • category [required] is the category handle of the attribute key. These are the the default category handles:
    • collection: for page attributes
    • file for file attributes
    • site for site attributes
    • user for user attributes
  • type [required] is the handle of type of the attribute values. These are the default attribute type handles:
    • address: address attribute value
    • boolean: checkbox attribute value
    • date_time: date/time attribute value
    • email: email attribute value
    • express: express entity attribute value
    • image_file: image/file attribute value
    • number: number attribute value
    • page_selector: page selector attribute value
    • rating: rating attribute value
    • select: option list attribute value
    • social_links: social links attribute value
    • telephone: phone number attribute value
    • text: text attribute value
    • textarea: text area attribute value
    • topics: topics attribute value
    • url: URL attribute value
  • handle [required] is the handle of the attribute key
  • name [required] is the name of the attribute key
  • indexed [optional, defaults to 0] set to 1 to include the attribute in the search by keywords
  • searchable [optional, defaults to 0] set to 1 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 to 0] set to 1 to show the attribute in public profile pages
  • member-list-displayed [optional, defaults to 0] set to 1 to show the attribute in member list pages
  • profile-editable [optional, defaults to 0] set to 1 to make the attribute editable in profile pages
  • profile-required [optional, defaults to 0] set to 1 to make the attribute editable and required in profile pages
  • register-editable [optional, defaults to 0] set to 1 to show the attribute on registration forms
  • register-required [optional, defaults to 0] set to 1 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 country
  • geolocate-country: if set to 1 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 default
  • checkbox-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 to date_time]: set to date_time for both date and time, date for date only, text for generic input fields
  • use-now-if-empty: if set to 1 Concrete will suggest the current date/time if empty
  • time-resolution: [defaults to 60] The time resolution in seconds to be used when mode is date_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 to html_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 to 1, multiple options can be chosen
  • display-multiple-values: if set to 1, Concrete will display the full option list
  • display-order [defaults to display_asc]: set to display_asc to use the display order, alpha_asc for an alphabetical order, popularity_desc to show the most popular first
  • allow-other-values if set to 1, 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 to text]: if set to rich_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 to 1, multiple nodes can be chosen
  • name: the name of a tree
  • path: 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 set
  • category [required]: the category handle of the attribute keys. These are the the default category handles:
    • collection: for page attributes
    • file for file attributes
    • site for site attributes
    • user for user attributes
  • handle [required]: the attribute set handle
  • name [required]: the name of the attribute set
  • locked [optional]: if set to 1 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>