This is the documentation for concrete5 version 5.6 and earlier. View Current Documentation

Attribute Keys are the bits of data you wish to store against types of data. Each category of Attribute Keys must be created before they can be added through the dashboard (and store data against objects.)

Let's say you have a custom Widget object, and you'd like to be able to use concrete5 attributes against the Widget object. You've created an Attribute Key Category for Widget. Now you need to do a few things.

  1. Create database tables that store the data necessary.
  2. Create a class that extends AttributeKey and AttributeValue core classes
  3. Create a dashboard interface for adding these Attribute Keys.

Create a Database File for Your Attribute Keys

Next, you will need to create the database table WidgetAttributeValues. Make sure this table has whatever columns necessary to accurately bind an attribute value to your object. The avID (attribute value ID) should be an unsigned integer. Your table would probably look like:

	create table WidgetAttributeValues ( 		widgetID int unsigned not null default 0, 		akID int unsigned not null default 0, 		avID int unsigned not null default 0, 		primary key (widgetID, avID, akID) 	);

This is pretty simple. widgetID is the primary key of your Widget object, akID is the primary key of a WidgetAttributeKey object and avID is the primary key of a WidgetAttributeValue object.

Additionally, if your Attribute Key stores any special data apart from what is already stored in the AttributeKeys table, you will need to create a WidgetAttributeKeys table. It will use akID as its primary key, and store any custom data alongside it. To see an example of what type of data you might store here, check out the UserAttributeKeys table, which contains data that only the UserAttributeKey object supports or cares about.

Create the class file

Each class derived from AttributeKey must live at models/attribute/categories/{ATTRIBUTE_CATEGORY_HANDLE}.php (either in the root, or within your package's directory.) Each attribute category file must be setup in a certain way. It must contain two classes, WidgetAttributeKey and WidgetAttributeValue. The former must extend AttributeKey, the latter AttributeValue.

WidgetAttributeKey Class

Generally, your class *must* contain the following properties and methods.

$searchIndexFieldDefinition

This is protected class $string which defines the data type for the primary key in the search index table. This will be in the ADODB Data Dictionary format.

	protected $searchIndexFieldDefinition = 'orderID I(11) UNSIGNED NOTNULL DEFAULT 0 PRIMARY';

This line defines the primary key of the indexed search table for this attribute category as orderID, an unsigned integer.

If your attribute key category isn't going to be searchable, you don't need to provide this.

getIndexedSearchTable()

This needs to return the name of the indexed search table that will hold all attributes for this entity. If your attribute key category isn't going to be searchable, you don't need to provide this. In this example, this would likely return 'WidgetSearchIndexAttributes.'

getAttributes($primaryKey, $method = 'getValue')

Generally, this method takes as many initial arguments as possible to bind the attribute value to an object. So, for users, it looks like this

getAttributes($uID, $method = 'getValue')

while for pages it looks like this

getAttributes($pageID, $pageVersionID, $method = 'getValue') 

Basically, whatever object the attribute key category is going to bind to, this will need to get all attributes for that object.

This object instantiates an AttributeValueList, stores all keys within that list, and runs the method against each one.

getColumnHeaderList()

This method simply needs to return

return parent::getList('widget', array('akIsColumnHeader' => 1));

getSearchableIndexedList()

This method simply needs to return

return parent::getList('widget', array('akIsSearchableIndexed' => 1)); 

getSearchableList()

This method simply needs to return

return parent::getList('widget', array('akIsSearchable' => 1)); 

getAttributeValue($avID, $method = 'getValue')

This method simply needs to implement

$av = WidgetAttributeValue::getByID($avID); 	$av->setAttributeKey($this); 	return call_user_func_array(array($av, $method), array());

getByID($akID)

This method simply needs to implement

	$ak = new WidgetAttributeKey(); 	$ak->load($akID); 	if ($ak->getAttributeKeyID() > 0) { 		return $ak;	 	} 

getByHandle($akHandle)

This method simply needs to query the database for the ID of the matching handle and return getByID($akID)

getList()

This method simply needs to return parent::getList('widget');

saveAttribute($object, $value = false)

This method should implement $av = $object->getAttributeValueObject($this, true), and then run parent::saveAttribute($av, $value); Then, insert all information into your WidgetAttributeValues table.

add($type, $args, $pkg = false)

Here you add any special information about an attribute category into your own WidgetAttributeKeys table (if it exists), as well as run parent::add($type, $args, $pkg).

update($args)

Here you add any special information about an attribute category into your own WidgetAttributeKeys table (if it exists), as well as run parent::update($args).

delete()

First run

	parent::delete()

Then run the following command:

		$db = Loader::db(); 		//$db->Execute('delete from WidgetAttributeKeys where akID = ?', array($this->getAttributeKeyID())); 		$r = $db->Execute('select avID from WidgetAttributeValues where akID = ?', array($this->getAttributeKeyID())); 		while ($row = $r->FetchRow()) { 			$db->Execute('delete from AttributeValues where avID = ?', array($row['avID'])); 		} 		$db->Execute('delete from WidgetAttributeValues where akID = ?', array($this->getAttributeKeyID()));

WidgetAttributeValue Class

set*

Whatever object you're binding these attribute to, you'll want to create a setter for this object. e.g. The CollectionAttributeValue category has a setter named setCollection. If you were binding to the Widget object, you'd create

	public function setWidget($widget) { 		$this->widget = $widget; 	} 

getByID($avID)

Place this within the method

	$cav = new WidgetAttributeValue(); 	$cav->load($avID); 	if ($cav->getAttributeValueID() == $avID) { 		return $cav; 	}

delete()

Implement this:

$db = Loader::db(); 	$db->Execute('delete from WidgetAttributeValues where yourKeyID = ? and akID = ? and avID = ?', array( 		$this->widget->getWidgetID(),  		$this->attributeKey->getAttributeKeyID(), 		$this->getAttributeValueID() 	)); 		 	// Before we run delete() on the parent object, we make sure that attribute value isn't being referenced in the table anywhere else 	$num = $db->GetOne('select count(avID) from WidgetAttributeValues where avID = ?', array($this->getAttributeValueID())); 	if ($num < 1) { 	parent::delete(); 	} 

Create the Attribute Category View Page

Now that the code is done, you'll need an interface where your new attributes may be created. This typically takes place in the dashboard. Your view interface will need to list all the attributes of the category, and allow for adding and editing. Additionally, it will need to include the attribute/type_form_required.php element and dashboard/attributes_table.php element.

This documentation is meant to serve as a starting point. If you wish to build a custom attribute category for your application, it's highly recommended you check out how the collection and/or user attribute categories are built.

Adding Attribute Capabilities to Your Objects

Attributes require additions to the objects they support in several ways. The following methods should be added to the supporting objects (Widget, in this example):

	public function setAttribute($ak, $value) { 		Loader::model('attribute/categories/widget'); 		if (!is_object($ak)) { 			$ak = WidgetAttributeKey::getByHandle($ak); 		} 		$ak->setAttribute($this, $value); 		$this->reindex(); 	}  	public function reindex() { 		$attribs = WidgetAttributeKey::getAttributes($this->getWidgetID(), 'getSearchIndexValue'); 		$db = Loader::db();  		$db->Execute('delete from WidgetSearchIndexAttributes where widgetID = ?', array($this->getWidgetID())); 		$searchableAttributes = array('widgetID' => $this->getProductID()); 		$rs = $db->Execute('select * from WidgetSearchIndexAttributes where widgetID = -1'); 		AttributeKey::reindex('WidgetSearchIndexAttributes', $searchableAttributes, $attribs, $rs); 	} 	 	public function getAttribute($ak, $displayMode = false) { 		Loader::model('attribute/categories/widget'); 		if (!is_object($ak)) { 			$ak = WidgetAttributeKey::getByHandle($ak); 		} 		if (is_object($ak)) { 			$av = $this->getAttributeValueObject($ak); 			if (is_object($av)) { 				return $av->getValue($displayMode); 			} 		} 	} 	 	public function getAttributeValueObject($ak, $createIfNotFound = false) { 		$db = Loader::db(); 		$av = false; 		$v = array($this->getProductID(), $ak->getAttributeKeyID()); 		$avID = $db->GetOne("select avID from WidgetAttributeValues where widgetID = ? and akID = ?", $v); 		if ($avID > 0) { 			$av = WidgetAttributeValue::getByID($avID); 			if (is_object($av)) { 				$av->setWidget($this); 				$av->setAttributeKey($ak); 			} 		} 		 		if ($createIfNotFound) { 			$cnt = 0; 		 			// Is this avID in use ? 			if (is_object($av)) { 				$cnt = $db->GetOne("select count(avID) from WidgetAttributeValues where avID = ?", $av->getAttributeValueID()); 			} 			 			if ((!is_object($av)) || ($cnt > 1)) { 				$av = $ak->addAttributeValue(); 			} 		} 		 		return $av; 	}
Loading Conversation