<?php /** * @package FOF * @copyright 2010-2017 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU GPL version 2 or later */ namespace FOF30\Factory\Scaffolding\Layout; use FOF30\Model\DataModel; /** * Erects a scaffolding XML for browse views * * @package FOF30\Factory\Scaffolding */ class BrowseErector extends BaseErector implements ErectorInterface { public function build() { // Get a reference to the model $model = $this->model; // Create the "no records" language string $noRowsKey = strtoupper($this->builder->getContainer()->componentName) . '_COMMON_NORECORDS'; $this->addString($noRowsKey, 'There are no records to display'); // Create the attributes of the form's base element $this->xml->addAttribute('type', 'browse'); $this->xml->addAttribute('show_header', "1"); $this->xml->addAttribute('show_filters', "1"); $this->xml->addAttribute('show_pagination', "1"); $this->xml->addAttribute('norows_placeholder', $noRowsKey); // Create the headerset and fieldset sections of the form file $headerSet = $this->xml->addChild('headerset'); $fieldSet = $this->xml->addChild('fieldset'); $fieldSet->addAttribute('name', 'items'); // Get the database fields $allFields = $model->getTableFields(); // Ordering must go first if ($model->hasField('ordering')) { $this->applyOrderingField($model, $headerSet, $fieldSet, $allFields); } // Primary key field goes next $this->applyPrimaryKeyField($model, $headerSet, $fieldSet, $allFields); // Get a list of "do not display" fields $doNotShow = $this->getDoNotShow(); foreach ($allFields as $fieldName => $fieldDefinition) { // Skip the fields which shouldn't be displayed if (in_array($fieldName, $doNotShow)) { continue; } // Get the lowercase field name and prepare to handle specially named fields $lowercaseFieldName = strtolower($fieldName); // access => AccessLevel if ($model->getFieldAlias('access') == $fieldName) { $this->applyAccessLevelField($model, $headerSet, $fieldSet, $fieldName); continue; } // title => Title if ($model->getFieldAlias('title') == $fieldName) { $this->applyTitleField($model, $headerSet, $fieldSet, $fieldName); continue; } // slug => Hide if there is a title field as well if ($model->getFieldAlias('slug') == $fieldName) { $titleField = $model->getFieldAlias('title'); if (array_key_exists($titleField, $allFields)) { continue; } } // tag => Tag if ($model->getFieldAlias('tag') == $fieldName) { $this->applyTagField($model, $headerSet, $fieldSet, $fieldName); continue; } // enabled => Actions if ($model->getFieldAlias('enabled') == $fieldName) { $this->applyActionsField($model, $headerSet, $fieldSet, $fieldName); continue; } // cache_handler => CacheHandler if ($lowercaseFieldName == 'cache_handler') { $this->applyCacheHandlerField($model, $headerSet, $fieldSet, $fieldName); continue; } // component_id => Components if ($lowercaseFieldName == 'component_id') { $this->applyComponentsField($model, $headerSet, $fieldSet, $fieldName); continue; } // body, introtext, fulltext => Editor if (in_array($lowercaseFieldName, array('body', 'introtext', 'fulltext', 'description'))) { $this->applyEditorField($model, $headerSet, $fieldSet, $fieldName); continue; } // email, *_email => Email if (($lowercaseFieldName == 'email') || (substr($lowercaseFieldName, -6) == 'email')) { $this->applyEmailField($model, $headerSet, $fieldSet, $fieldName); continue; } // image, media, *_image => Media if ( in_array($lowercaseFieldName, array('image', 'media')) || (substr($lowercaseFieldName, -6) == '_image') ) { $this->applyMediaField($model, $headerSet, $fieldSet, $fieldName); continue; } // language, lang, lang_id => Language if (in_array($lowercaseFieldName, array('language', 'lang', 'lang_id'))) { $this->applyLanguageField($model, $headerSet, $fieldSet, $fieldName); continue; } // password, passwd, pass => Password if (in_array($lowercaseFieldName, array('password', 'passwd', 'pass'))) { $this->applyPasswordField($model, $headerSet, $fieldSet, $fieldName); continue; } // plugin_id => Plugins if ($lowercaseFieldName == 'plugin_id') { $this->applyPluginsField($model, $headerSet, $fieldSet, $fieldName); continue; } // asset_id => Rules (not applicable here) if ($lowercaseFieldName == 'asset_id') { continue; } // session_handler => SessionHandler if ($lowercaseFieldName == 'session_handler') { $this->applySessionHandlerField($model, $headerSet, $fieldSet, $fieldName); continue; } // tel, telephone, phone => Tel if (in_array($lowercaseFieldName, array('tel', 'telephone', 'phone'))) { $this->applyTelField($model, $headerSet, $fieldSet, $fieldName); continue; } // timezone, tz, time_zone => Timezone if (in_array($lowercaseFieldName, array('timezone', 'tz', 'time_zone'))) { $this->applyTimezoneField($model, $headerSet, $fieldSet, $fieldName); continue; } // url, link, href => Url if (in_array($lowercaseFieldName, array('url', 'link', 'href'))) { $this->applyUrlField($model, $headerSet, $fieldSet, $fieldName); continue; } // user, user_id, userid, uid => User if (in_array($lowercaseFieldName, array('user', 'user_id', 'userid', 'uid'))) { $this->applyUserField($model, $headerSet, $fieldSet, $fieldName); continue; } // group, group_id, groupid, gid => UserGroup if (in_array($lowercaseFieldName, array('group', 'group_id', 'groupid', 'gid'))) { $this->applyUserGroupField($model, $headerSet, $fieldSet, $fieldName); continue; } // Special handling for myComponent_whatever_id fields $myComponentPrefix = $this->builder->getContainer()->bareComponentName . '_'; if ((strpos($fieldName, $myComponentPrefix) === 0) && (substr($fieldName, -3) == '_id')) { $parts = explode('_', $fieldName); array_pop($parts); array_shift($parts); // myComponent_something_id => Relation or Model if (count($parts) == 1) { $foreignName = array_shift($parts); } // myComponent_something_another_id => Relation else { $foreignName1 = array_shift($parts); $foreignName1 = $this->model->getContainer()->inflector->pluralize($foreignName1); $foreignName2 = array_shift($parts); $foreignName2 = $this->model->getContainer()->inflector->pluralize($foreignName2); $modelName = $model->getName(); $modelName = $this->model->getContainer()->inflector->pluralize($modelName); $foreignName = ($foreignName1 == $modelName) ? $foreignName2 : $foreignName1; } try { $model->getRelations()->getRelation($foreignName); $this->applyRelationField($model, $headerSet, $fieldSet, $fieldName); continue; } catch (DataModel\Relation\Exception\RelationNotFound $e) { $foreignName = $this->model->getContainer()->inflector->pluralize($foreignName); try { $this->applyModelField($model, $headerSet, $fieldSet, $fieldName, $foreignName); continue; } catch (\Exception $e) { } } } // Other fields, use getFieldType $typeDef = $this->getFieldType($fieldDefinition->Type); switch ($typeDef['type']) { case 'Text': $this->applyTextField($model, $headerSet, $fieldSet, $fieldName); break; case 'Editor': $this->applyEditorField($model, $headerSet, $fieldSet, $fieldName); break; case 'Calendar': $this->applyCalendarField($model, $headerSet, $fieldSet, $fieldName); break; case 'Checkbox': $this->applyCheckboxField($model, $headerSet, $fieldSet, $fieldName); break; case 'Integer': $this->applyIntegerField($model, $headerSet, $fieldSet, $fieldName); break; case 'Numeric': $this->applyNumericField($model, $headerSet, $fieldSet, $fieldName); break; case 'GenericList': $this->applyGenericListField($model, $headerSet, $fieldSet, $fieldName, $typeDef['params']); break; } } $this->pushResults(); } /** * Apply the ordering field * * @param \FOF30\Model\DataModel $model * @param \SimpleXMLElement $headerSet * @param \SimpleXMLElement $fieldSet * @param array $allFields */ private function applyOrderingField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, array &$allFields) { $langDefs = $this->getFieldLabel('ordering'); $this->addString($langDefs['label']['key'], $langDefs['label']['value']); $this->addString($langDefs['desc']['key'], $langDefs['desc']['value']); $fieldName = $model->getFieldAlias('ordering'); $header = $headerSet->addChild('header'); $header->addAttribute('name', $fieldName); $header->addAttribute('type', 'Ordering'); $header->addAttribute('label', $langDefs['label']['key']); $header->addAttribute('sortable', 'true'); $header->addAttribute('tdwidth', '1%'); $field = $fieldSet->addChild('field'); $field->addAttribute('name', $fieldName); $field->addAttribute('type', 'Ordering'); $field->addAttribute('class', 'input-mini input-sm'); unset($allFields[$fieldName]); } /** * Apply the ordering field * * @param \FOF30\Model\DataModel $model * @param \SimpleXMLElement $headerSet * @param \SimpleXMLElement $fieldSet * @param array $allFields */ private function applyPrimaryKeyField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, array &$allFields) { $keyField = $model->getKeyName(); $langDefs = $this->getFieldLabel($keyField); $this->addString($langDefs['label']['key'], $langDefs['label']['value']); $this->addString($langDefs['desc']['key'], $langDefs['desc']['value']); $header = $headerSet->addChild('header'); $header->addAttribute('name', $keyField); $header->addAttribute('type', 'RowSelect'); $header->addAttribute('label', $langDefs['label']['key']); $header->addAttribute('sortable', 'true'); $header->addAttribute('tdwidth', '20'); $field = $fieldSet->addChild('field'); $field->addAttribute('name', $keyField); $field->addAttribute('type', 'SelectRow'); unset($allFields[$keyField]); } private function applyFieldOfType(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName, $fieldTypeHeader, $fieldTypeField, array $headerAttributes = array()) { $langDefs = $this->getFieldLabel($fieldName); $this->addString($langDefs['label']['key'], $langDefs['label']['value']); $this->addString($langDefs['desc']['key'], $langDefs['desc']['value']); $header = $headerSet->addChild('header'); $header->addAttribute('name', $fieldName); $header->addAttribute('type', $fieldTypeHeader); $header->addAttribute('label', $langDefs['label']['key']); if (!empty($headerAttributes)) { foreach ($headerAttributes as $k => $v) { $header->addAttribute($k, $v); } } $field = $fieldSet->addChild('field'); $field->addAttribute('name', $fieldName); $field->addAttribute('type', $fieldTypeField); } /** * Apply an access level field * * @param \FOF30\Model\DataModel $model * @param \SimpleXMLElement $headerSet * @param \SimpleXMLElement $fieldSet * @param string $fieldName */ private function applyAccessLevelField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName) { $this->applyFieldOfType($model, $headerSet, $fieldSet, $fieldName, 'AccessLevel', 'AccessLevel', array( 'sortable' => 'true' )); } private function applyActionsField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName) { $this->applyFieldOfType($model, $headerSet, $fieldSet, $fieldName, 'Published', 'Actions', array( 'sortable' => 'true' )); } private function applyCacheHandlerField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName) { $this->applyFieldOfType($model, $headerSet, $fieldSet, $fieldName, 'Field', 'CacheHandler', array( 'sortable' => 'true' )); } private function applyCalendarField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName) { $this->applyFieldOfType($model, $headerSet, $fieldSet, $fieldName, 'Date', 'Calendar', array( 'sortable' => 'true' )); } private function applyCheckboxField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName) { $this->applyFieldOfType($model, $headerSet, $fieldSet, $fieldName, 'Field', 'Checkbox', array( 'sortable' => 'true' )); } private function applyComponentsField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName) { $this->applyFieldOfType($model, $headerSet, $fieldSet, $fieldName, 'Field', 'Components', array( 'sortable' => 'true' )); } private function applyEditorField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName) { $this->applyFieldOfType($model, $headerSet, $fieldSet, $fieldName, 'Searchable', 'Editor', array( 'sortable' => 'true' )); } private function applyEmailField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName) { $this->applyFieldOfType($model, $headerSet, $fieldSet, $fieldName, 'Searchable', 'Email', array( 'sortable' => 'true' )); } private function applyIntegerField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName) { $this->applyFieldOfType($model, $headerSet, $fieldSet, $fieldName, 'Searchable', 'Integer', array( 'sortable' => 'true' )); } private function applyNumericField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName) { $this->applyFieldOfType($model, $headerSet, $fieldSet, $fieldName, 'Searchable', 'Numeric', array( 'sortable' => 'true' )); } private function applyMediaField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName) { $this->applyFieldOfType($model, $headerSet, $fieldSet, $fieldName, 'Field', 'Media'); } private function applyLanguageField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName) { $this->applyFieldOfType($model, $headerSet, $fieldSet, $fieldName, 'Language', 'Language', array( 'sortable' => 'true' )); } private function applyPasswordField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName) { $this->applyFieldOfType($model, $headerSet, $fieldSet, $fieldName, 'Searchable', 'Password', array( 'sortable' => 'true' )); } private function applyPluginsField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName) { $this->applyFieldOfType($model, $headerSet, $fieldSet, $fieldName, 'Field', 'Plugins', array( 'sortable' => 'true' )); } private function applySessionHandlerField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName) { $this->applyFieldOfType($model, $headerSet, $fieldSet, $fieldName, 'Field', 'SessionHandler', array( 'sortable' => 'true' )); } private function applyTelField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName) { $this->applyFieldOfType($model, $headerSet, $fieldSet, $fieldName, 'Searchable', 'Tel', array( 'sortable' => 'true' )); } private function applyTextField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName) { $this->applyFieldOfType($model, $headerSet, $fieldSet, $fieldName, 'Searchable', 'Text', array( 'sortable' => 'true' )); } private function applyTimezoneField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName) { $this->applyFieldOfType($model, $headerSet, $fieldSet, $fieldName, 'Field', 'Timezone', array( 'sortable' => 'true' )); } private function applyUrlField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName) { $this->applyFieldOfType($model, $headerSet, $fieldSet, $fieldName, 'Searchable', 'Url', array( 'sortable' => 'true' )); } private function applyUserField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName) { $this->applyFieldOfType($model, $headerSet, $fieldSet, $fieldName, 'Searchable', 'User', array( 'sortable' => 'true' )); } private function applyUserGroupField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName) { $this->applyFieldOfType($model, $headerSet, $fieldSet, $fieldName, 'Searchable', 'UserGroup', array( 'sortable' => 'true' )); } private function applyRelationField($model, $headerSet, $fieldSet, $fieldName) { $this->applyFieldOfType($model, $headerSet, $fieldSet, $fieldName, 'Field', 'Relation', array( 'sortable' => 'true' )); } private function applyTagField($model, $headerSet, $fieldSet, $fieldName) { $this->applyFieldOfType($model, $headerSet, $fieldSet, $fieldName, 'Field', 'Tag', array( 'sortable' => 'true' )); } private function applyTitleField($model, \SimpleXMLElement $headerSet, \SimpleXMLElement $fieldSet, $fieldName) { $langDefs = $this->getFieldLabel($fieldName); $this->addString($langDefs['label']['key'], $langDefs['label']['value']); $this->addString($langDefs['desc']['key'], $langDefs['desc']['value']); $header = $headerSet->addChild('header'); $header->addAttribute('name', $fieldName); $header->addAttribute('type', 'Searchable'); $header->addAttribute('label', $langDefs['label']['key']); if (!empty($headerAttributes)) { foreach ($headerAttributes as $k => $v) { $header->addAttribute($k, $v); } } $field = $fieldSet->addChild('field'); $field->addAttribute('name', $fieldName); $field->addAttribute('type', 'Sortable'); $field->addAttribute('url', 'index.php?option=' . $this->builder->getContainer()->componentName . '&view=' . $this->model->getContainer()->inflector->singularize($this->viewName) . '&id=[ITEM:ID]&[TOKEN]=1' ); } private function applyModelField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName, $modelName) { // This will fail if the model is invalid, e.g. we have example_foobar_id but no #__example_foobars table. The // error will balloon up the stack and the field will be rendered as simple numeric field instead of a Model // field. /** @var DataModel $foreignModel */ $foreignModel = $model->getContainer()->factory->model($modelName); $value_field = $foreignModel->getKeyName(); if ($foreignModel->hasField('title')) { $value_field = $foreignModel->getFieldAlias('title'); } $langDefs = $this->getFieldLabel($fieldName); $this->addString($langDefs['label']['key'], $langDefs['label']['value']); $this->addString($langDefs['desc']['key'], $langDefs['desc']['value']); $header = $headerSet->addChild('header'); $header->addAttribute('name', $fieldName); $header->addAttribute('type', 'Model'); $header->addAttribute('model', $modelName); $header->addAttribute('key_field', $foreignModel->getKeyName()); $header->addAttribute('value_field', $value_field); $header->addAttribute('label', $langDefs['label']['key']); $header->addAttribute('sortable', 'true'); $field = $fieldSet->addChild('field'); $field->addAttribute('name', $fieldName); $field->addAttribute('type', 'Model'); $field->addAttribute('model', $modelName); $field->addAttribute('key_field', $foreignModel->getKeyName()); $field->addAttribute('value_field', $value_field); } private function applyGenericListField(DataModel $model, \SimpleXMLElement &$headerSet, \SimpleXMLElement &$fieldSet, $fieldName, $options) { $displayOptions = array(); foreach ($options as $k => $v) { $langKey = $this->builder->getContainer()->componentName . '_' . $this->viewName . '_' . $fieldName . '_OPT_' . $k; $this->addString($langKey, $v); $displayOptions[$k] = $langKey; } $langDefs = $this->getFieldLabel($fieldName); $this->addString($langDefs['label']['key'], $langDefs['label']['value']); $this->addString($langDefs['desc']['key'], $langDefs['desc']['value']); $header = $headerSet->addChild('header'); $header->addAttribute('name', $fieldName); $header->addAttribute('type', 'Selectable'); $header->addAttribute('label', $langDefs['label']['key']); $header->addAttribute('sortable', 'true'); foreach ($displayOptions as $k => $v) { $header->addChild('option', $v)->addAttribute('value', $k); } $field = $fieldSet->addChild('field'); $field->addAttribute('name', $fieldName); $field->addAttribute('type', 'GenericList'); foreach ($displayOptions as $k => $v) { $field->addChild('option', $v)->addAttribute('value', $k); } } /** * Create a list of fields which should not be shown in the form. These are fields like created/modified/locked * user and time and other internal fields which should not be part of the form output. * * @return array */ private function getDoNotShow() { $return = array(); $checkFields = array('created_by', 'created_on', 'modified_by', 'modified_on', 'locked_by', 'locked_on'); foreach ($checkFields as $checkField) { $return[] = $this->model->getFieldAlias($checkField); } return $return; } }