_t('attribute'), 'NAME_PLURAL' => _t('attributes'), 'FIELDS' => array( 'attribute_id' => array( 'FIELD_TYPE' => FT_NUMBER, 'DISPLAY_TYPE' => DT_HIDDEN, 'IDENTITY' => true, 'DISPLAY_WIDTH' => 10, 'DISPLAY_HEIGHT' => 1, 'IS_NULL' => false, 'DEFAULT' => '', 'LABEL' => 'Attribute id', 'DESCRIPTION' => 'Identifier for Attribute' ), 'element_id' => array( 'FIELD_TYPE' => FT_NUMBER, 'DISPLAY_TYPE' => DT_FIELD, 'DISPLAY_WIDTH' => 10, 'DISPLAY_HEIGHT' => 1, 'IS_NULL' => false, 'DEFAULT' => '', 'LABEL' => 'Element id', 'DESCRIPTION' => 'Identifier for Element' ), 'locale_id' => array( 'FIELD_TYPE' => FT_NUMBER, 'DISPLAY_TYPE' => DT_SELECT, 'DISPLAY_WIDTH' => 40, 'DISPLAY_HEIGHT' => 1, 'IS_NULL' => true, 'DISPLAY_FIELD' => array('ca_locales.name'), 'DEFAULT' => '', 'LABEL' => _t('Locale'), 'DESCRIPTION' => _t('The locale best describing the origin of this information.') ), 'table_num' => array( 'FIELD_TYPE' => FT_NUMBER, 'DISPLAY_TYPE' => DT_OMIT, 'DISPLAY_WIDTH' => 10, 'DISPLAY_HEIGHT' => 1, 'IS_NULL' => false, 'DEFAULT' => '', 'LABEL' => 'Table', 'DESCRIPTION' => 'Table to which this attribute is applied.', 'BOUNDS_VALUE' => array(1,255) ), 'row_id' => array( 'FIELD_TYPE' => FT_NUMBER, 'DISPLAY_TYPE' => DT_OMIT, 'DISPLAY_WIDTH' => 10, 'DISPLAY_HEIGHT' => 1, 'IS_NULL' => false, 'DEFAULT' => '', 'LABEL' => 'Row id', 'DESCRIPTION' => 'Identifier of row to which this attibute is applied.' ) ) ); class ca_attributes extends BaseModel { # --------------------------------- # --- Object attribute properties # --------------------------------- # Describe structure of content object's properties - eg. database fields and their # associated types, what modes are supported, et al. # # ------------------------------------------------------ # --- Basic object parameters # ------------------------------------------------------ # what table does this class represent? protected $TABLE = 'ca_attributes'; # what is the primary key of the table? protected $PRIMARY_KEY = 'attribute_id'; # ------------------------------------------------------ # --- Properties used by standard editing scripts # # These class properties allow generic scripts to properly display # records from the table represented by this class # # ------------------------------------------------------ # Array of fields to display in a listing of records from this table protected $LIST_FIELDS = array(); # When the list of "list fields" above contains more than one field, # the LIST_DELIMITER text is displayed between fields as a delimiter. # This is typically a comma or space, but can be any string you like protected $LIST_DELIMITER = ' '; # What you'd call a single record from this table (eg. a "person") protected $NAME_SINGULAR; # What you'd call more than one record from this table (eg. "people") protected $NAME_PLURAL; # List of fields to sort listing of records by; you can use # SQL 'ASC' and 'DESC' here if you like. protected $ORDER_BY = array(); # Maximum number of record to display per page in a listing protected $MAX_RECORDS_PER_PAGE = 20; # How do you want to page through records in a listing: by number pages ordered # according to your setting above? Or alphabetically by the letters of the first # LIST_FIELD? protected $PAGE_SCHEME = 'alpha'; # alpha [alphabetical] or num [numbered pages; default] # If you want to order records arbitrarily, add a numeric field to the table and place # its name here. The generic list scripts can then use it to order table records. protected $RANK = ''; # ------------------------------------------------------ # Hierarchical table properties # ------------------------------------------------------ protected $HIERARCHY_TYPE = null; protected $HIERARCHY_LEFT_INDEX_FLD = null; protected $HIERARCHY_RIGHT_INDEX_FLD = null; protected $HIERARCHY_PARENT_ID_FLD = null; protected $HIERARCHY_DEFINITION_TABLE = null; protected $HIERARCHY_ID_FLD = null; protected $HIERARCHY_POLY_TABLE = null; # ------------------------------------------------------ # Change logging # ------------------------------------------------------ protected $UNIT_ID_FIELD = null; protected $LOG_CHANGES_TO_SELF = false; protected $LOG_CHANGES_USING_AS_SUBJECT = array( "FOREIGN_KEYS" => array( ), "RELATED_TABLES" => array( ) ); static $s_attribute_cache_size = 125; static $s_get_attributes_cache = array(); static $s_ca_attributes_element_instance_cache = array(); # ------------------------------------------------------ # $FIELDS contains information about each field in the table. The order in which the fields # are listed here is the order in which they will be returned using getFields() protected $FIELDS; # ------------------------------------------------------ # --- Constructor # # This is a function called when a new instance of this object is created. This # standard constructor supports three calling modes: # # 1. If called without parameters, simply creates a new, empty objects object # 2. If called with a single, valid primary key value, creates a new objects object and loads # the record identified by the primary key value # # ------------------------------------------------------ public function __construct($pn_id=null) { parent::__construct($pn_id); # call superclass constructor } # ------------------------------------------------------ /** * Stub out indexing for this table - it is never indexed */ public function doSearchIndexing() { return; } # ------------------------------------------------------ /** * */ public function addAttribute($pn_table_num, $pn_row_id, $pm_element_code_or_id, $pa_values) { require_once(__CA_MODELS_DIR__.'/ca_metadata_elements.php'); // defer inclusion until runtime to ensure baseclasses are already loaded, otherwise you get circular dependencies unset(ca_attributes::$s_get_attributes_cache[$pn_table_num.'/'.$pn_row_id]); $t_element = ca_attributes::getElementInstance($pm_element_code_or_id); $vb_already_in_transaction = $this->inTransaction(); if (!$vb_already_in_transaction) { $o_trans = new Transaction(); $this->setTransaction($o_trans); } else { $o_trans = $this->getTransaction(); } // create new attribute row $this->set('element_id', $vn_attribute_id = $t_element->getPrimaryKey()); $this->set('locale_id', $pa_values['locale_id']); // TODO: verify table_num/row_id combo $this->set('table_num', $pn_table_num); $this->set('row_id', $pn_row_id); $this->setMode(ACCESS_WRITE); $this->insert(); if ($this->numErrors()) { if (!$vb_already_in_transaction) { $o_trans->rollback(); } $vs_errors = join('; ', $this->getErrors()); $this->clearErrors(); $this->postError(1971, $vs_errors, 'ca_attributes->addAttribute()'); return false; } $t_attr_val = new ca_attribute_values(); $t_attr_val->purify($this->purify()); $t_attr_val->setTransaction($o_trans); $t_attr_val->setMode(ACCESS_WRITE); $vn_attribute_id = $this->getPrimaryKey(); $va_elements = $t_element->getElementsInSet(); $vb_dont_create_attribute = true; foreach($va_elements as $va_element) { if ($va_element['datatype'] == 0) { continue; } // 0 is always 'container' ... if(isset($pa_values[$va_element['element_id']])) { $vm_value = $pa_values[$va_element['element_id']]; } else { $vm_value = isset($pa_values[$va_element['element_code']]) ? $pa_values[$va_element['element_code']] : null; } if (($vb_status = $t_attr_val->addValue($vm_value, $va_element, $vn_attribute_id)) === false) { $this->postError(1972, join('; ', $t_attr_val->getErrors()), 'ca_attributes->addAttribute()'); break; } if (!is_null($vb_status)) { $vb_dont_create_attribute = false; } } if ($vb_dont_create_attribute) { // // If we're here it means that all attribute values returned null, which indicates that // we should simply skip the attribute without error. This behavior is typically used to allow // empty values to pass without complaint. // if (!$vb_already_in_transaction) { $o_trans->rollback(); } return null; // we return null so the caller understands not to throw errors } if ($this->numErrors()) { if (!$vb_already_in_transaction) { $o_trans->rollback(); } else { $va_errors = $this->errors(); $this->delete(true); $this->errors = $va_errors; } return false; } if (!$vb_already_in_transaction) { $o_trans->commit(); } return $this->getPrimaryKey(); } # ------------------------------------------------------ /** * */ public function editAttribute($pa_values) { if (!$this->getPrimaryKey()) { return null; } $vb_already_in_transaction = $this->inTransaction(); if (!$vb_already_in_transaction) { $o_trans = new Transaction(); $this->setTransaction($o_trans); } else { $o_trans = $this->getTransaction(); } unset(ca_attributes::$s_get_attributes_cache[$this->get('table_num').'/'.$this->get('row_id')]); $this->setMode(ACCESS_WRITE); $this->set('locale_id', $pa_values['locale_id']); $this->update(); if ($this->numErrors()) { if (!$vb_already_in_transaction) { $o_trans->rollback(); } $vs_errors = join('; ', $this->getErrors()); $this->clearErrors(); $this->postError(1971, $vs_errors, 'ca_attributes->editAttribute()'); return false; } $t_attr_val = new ca_attribute_values(); $t_attr_val->purify($this->purify()); $t_attr_val->setTransaction($o_trans); $t_attr_val->setMode(ACCESS_WRITE); $t_element = ca_attributes::getElementInstance($this->get('element_id')); $va_elements = $t_element->getElementsInSet(); $va_attr_vals = $this->getAttributeValues(); foreach($va_attr_vals as $o_attr_val) { $vn_element_id = intval($o_attr_val->getElementID()); if ($t_attr_val->load($o_attr_val->getValueID())) { if(isset($pa_values[$vn_element_id])) { $vm_value = $pa_values[$vn_element_id]; } else { $vm_value = $pa_values[$o_attr_val->getElementCode()]; } if ($t_attr_val->editValue($vm_value) === false) { $this->postError(1973, join('; ', $t_attr_val->getErrors()), 'ca_attributes->editAttribute()'); } foreach($va_elements as $vn_i => $va_element_info) { if ($va_element_info['element_id'] == $vn_element_id) { unset($va_elements[$vn_i]); } } } } $vn_attribute_id = $this->getPrimaryKey(); // Add values that don't already exist (added after the fact?) foreach($va_elements as $vn_index => $va_element) { if ($va_element['datatype'] == 0) { continue; } // skip containers $vn_element_id = $va_element['element_id']; if(isset($pa_values[$vn_element_id])) { $vm_value = $pa_values[$vn_element_id]; } else { $vm_value = $pa_values[$va_element_info['element_code']]; } if ($t_attr_val->addValue($vm_value, $va_element, $vn_attribute_id) === false) { $this->postError(1972, join('; ', $t_attr_val->getErrors()), 'ca_attributes->editAttribute()'); break; } } if ($this->numErrors()) { if (!$vb_already_in_transaction) { $o_trans->rollback(); } return false; } if (!$vb_already_in_transaction) { $o_trans->commit(); } return true; } # ------------------------------------------------------ /** * */ public function removeAttribute() { if (!$this->getPrimaryKey()) { return null; } $this->setMode(ACCESS_WRITE); unset(ca_attributes::$s_get_attributes_cache[$this->get('table_num').'/'.$this->get('row_id')]); $vn_rc= $this->delete(true); if ($this->numErrors()) { $vs_errors = join('; ', $this->getErrors()); $this->clearErrors(); $this->postError(1974, $vs_errors, 'ca_attributes->removeAttribute()'); return false; } return $vn_rc; } # ------------------------------------------------------ /** * */ public function getAttributeValues() { if (!$this->getPrimaryKey()) { return null; } $o_db = $this->getDb(); $qr_attrs = $o_db->query(" SELECT * FROM ca_attribute_values cav INNER JOIN ca_metadata_elements AS cme ON cme.element_id = cav.element_id WHERE cav.attribute_id = ? ", (int)$this->getPrimaryKey()); $o_attr = new Attribute($this->getFieldValuesArray()); while($qr_attrs->nextRow()) { $va_raw_row = $qr_attrs->getRow(); $o_attr->addValueFromRow($va_raw_row); } return $o_attr->getValues(); } # ------------------------------------------------------ # Static methods # ------------------------------------------------------ /** * */ static public function getElementInstance($pm_element_code_or_id) { if (isset(ca_attributes::$s_ca_attributes_element_instance_cache[$pm_element_code_or_id])) { return ca_attributes::$s_ca_attributes_element_instance_cache[$pm_element_code_or_id]; } require_once(__CA_MODELS_DIR__.'/ca_metadata_elements.php'); // defer inclusion until runtime to ensure baseclasses are already loaded, otherwise you get circular dependencies $t_element = new ca_metadata_elements(); if (!is_numeric($pm_element_code_or_id)) { if ($t_element->load(array('element_code' => $pm_element_code_or_id))) { return ca_attributes::$s_ca_attributes_element_instance_cache[$pm_element_code_or_id] = ca_attributes::$s_ca_attributes_element_instance_cache[$t_element->getPrimaryKey()] = $t_element; } } if ($t_element->load($pm_element_code_or_id)) { return ca_attributes::$s_ca_attributes_element_instance_cache[$pm_element_code_or_id] = $t_element; } else { //$this->postError(1950, _t("Element code or id is invalid"), "ca_attributes::getElementInstance()"); return false; } } # ------------------------------------------------------ /** * */ static public function attributeHtmlFormElement($pa_element_info, $pa_options=null) { if (isset($pa_options['config']) && is_object($pa_options['config'])) { $o_config = $pa_options['config']; } else { $o_config = Configuration::load(); } // TODO: does {fieldNamePrefix} need to be hardcoded? Does it matter? // TODO: use appropriate element labels to label elements $vn_width = 25; $vn_max_length = 255; $vs_element = Attribute::valueHTMLFormElement($pa_element_info['datatype'], $pa_element_info, $pa_options); // TODO: formalize, cleanup and document formatting code $ps_format = isset($pa_options['format']) ? $pa_options['format'] : null; $vs_label = isset($pa_options['label']) ? $pa_options['label'] : ''; $vs_description = isset($pa_options['description']) ? $pa_options['description'] : ''; if (isset($pa_options['field_errors']) && is_array($pa_options['field_errors']) && sizeof($pa_options['field_errors'])) { $ps_format = $o_config->get('form_element_error_display_format'); $va_field_errors = array(); foreach($pa_options['field_errors'] as $o_e) { $va_field_errors[] = $o_e->getErrorDescription(); } $vs_errors = join('; ', $va_field_errors); } else { if (!$ps_format) { if ($vs_label) { $ps_format = $o_config->get('form_element_display_format'); } else { $ps_format = $o_config->get('form_element_display_format_without_label'); } } $vs_errors = ''; } $ps_formatted_element = str_replace("^LABEL", "{$vs_label}", $ps_format); $ps_formatted_element = str_replace("^ELEMENT", $vs_element, $ps_formatted_element); $ps_formatted_element = str_replace("^DESCRIPTION", "", $ps_formatted_element); $ps_formatted_element = str_replace("^EXTRA", "", $ps_formatted_element); if ($vs_description) { TooltipManager::add('#_attribute_value_'.$pa_element_info['element_code'], "