/application/protected/extensions/gtc/components/Relation.php
PHP | 560 lines | 362 code | 0 blank | 198 comment | 81 complexity | 7c697d8d1bca3f45803d1aeec21aabf8 MD5 | raw file
Possible License(s): GPL-3.0, BSD-3-Clause, CC0-1.0, BSD-2-Clause, GPL-2.0, LGPL-2.1, LGPL-3.0
- <?php
- /*
- The Relation widget is used in forms, where the User can choose
- between a selection of model elements, that this models belongs to.
- It is able to handle BELONGS_TO, HAS_ONE and MANY_MANY Relations. The Relation
- type is detected automatically from the Model 'relations()' section.
- The Widget has different styles in which it can render the possible choices.
- Use the 'style' option to set the appropriate style.
- The following example shows how to use Relation with a minimal config,
- assuming we have a Model "Post" and "User", where one User belongs
- to a Post:
- <pre>
- $this->widget('application.components.Relation', array(
- 'model' => 'Post',
- 'relation' => 'user'
- 'fields' => 'username' // show the field "username" of the parent element
- ));
- </pre>
- Results in a drop down list in which the user can choose between
- all available Users in the Database. The shown field of the
- Table "User" is "username" in this example.
- You can choose the Style of your Widget in the 'style' option.
- Note that a Many_Many Relation always gets rendered as a Listbox,
- since you can select multiple Elements.
- 'fields' can be an array or an string.
- If you pass an array to 'fields', the Widget will display every field in
- this array. If you want to show further sub-relations, separate the values
- with '.', for example: 'fields' => 'array('parent.grandparent.description')
- Optional Parameters:
- You can use 'field' => 'post_userid' if the field in the model
- that represents the foreign model is called different than in the
- relation
- Use 'relatedPk' => 'id_of_user' if the primary Key of the Foreign
- Model differs from the one given in the relation.
- Normally you shouldn´t use this fields cause the Widget get the relations
- automatically from the relation.
- Use 'allowEmpty' to let the user be able to choose no parent. If you
- set this to a string, this string will be displayed with the available
- choices.
- With 'showAddButton' => 'false' you can disable the 'create new Foreignkey'
- Button generated beside the Selectbox.
- Define the AddButtonString with 'addButtonString' => 'Add...'. This string
- is set default to '+'
- When using the '+' button you most likely want to return to where you came.
- To accomplish this, we pass a 'returnTo' parameter by $_GET.
- The Controller can send the user back to where he came from this way:
- <pre>
- if($model->save())
- if(isset($_GET['returnTo']))
- $this->redirect(array(urldecode($_GET['returnTo'])));
- </pre>
- Using the 'style' option we can configure how our Widget gets rendered.
- The following styles are available:
- Selectbox (default), Listbox, Checkbox and in MANY_MANY relations 'twopane'
- The style is case insensitive so one can use dropdownlist or dropDownList.
- Use the option 'createAction' if the action to add additional foreign Model
- options differs from 'create'.
- With 'parentObjects' you can limit the Parent Elements that are being shown.
- It takes an array of elements that could be returned from an scope or
- an SQL Query.
- The parentObjects can be grouped, for example, with
- 'groupParentsBy' => 'city'
- Use the option 'htmlOptions' to pass any html Options to the
- Selectbox/Listbox form element.
- Full Example:
- <pre>
- $this->widget('application.components.Relation', array(
- 'model' => 'Post',
- 'field' => 'Userid',
- 'style' => 'ListBox',
- 'criteria' => new CDBCriteria(array('condition' => 'id_of_user IN (1,8,20)')),
- 'parentObjects' => Parentmodel::model()->findAll('userid = 17'),
- 'groupParentsBy' => 'city',
- 'relation' => 'user',
- 'relatedPk' => 'id_of_user',
- 'fields' => array( 'username', 'username.group.groupid' ),
- 'delimiter' => ' -> ', // default: ' | '
- 'returnTo' => 'model/create',
- 'addButtonUrl' => 'othercontroller/otheraction', // default: ''
- 'showAddButton' => 'click here to add a new User', // default: ''
- 'htmlOptions' => array('style' => 'width: 100px;')
- ));
- </pre>
- @author Herbert Maschke <thyseus@gmail.com>
- @version 0.97 (after 1.0rc5)
- @since 1.1
- */
- class Relation extends CWidget {
- // this Variable holds an instance of the Object
- protected $_model;
- // this Variable holds an instance of the related Object
- protected $_relatedModel;
- // draw the relation of which model?
- public $model;
- // which relation should be rendered?
- public $relation;
- public $field;
- // the Primary Key of the foreign Model
- public $relatedPk;
- // a field or an array of fields that determine which field values
- // should be rendered in the selection
- public $fields;
- // if this is set, the User is able to select no related model
- // if this is set to a string, this string will be presented
- public $allowEmpty = 0;
- // Preselect which items?
- public $preselect = false;
- // disable this to hide the Add Button
- // set this to a string to set the String to be displayed
- public $showAddButton = FALSE;
- public $addButtonUrl = '';
- // url for the action that refreshes the dropdownlist after related
- // object is created
- public $addButtonRefreshUrl = '';
- // criteria for filtering records
- public $criteria = false;
- // How the label of a row should be rendered. {id} will be replaced by the
- // id of the model. You can also insert every field that is available in the
- // parent object.
- // Use {fields} to display all fields delimited by $this->delimiter
- // Use {myFuncName} to evaluate a user-contributed function specified in the
- // $functions array as 'myFuncName'=>'code to be evaluated'. The code for
- // these functions are evaluated under the context of the controller
- // rendering the current Relation widget ($this refers to the controller).
- // Old way, not encouraged anymore: Use {func0} to {funcX} to evaluate user-
- // contributed functions specified in the $functions array as a keyless
- // string entry of 'code to be evaluated'.
- // Example of code:
- //
- // 'template' => '#{id} : {fields} ({title}) Allowed other Models: {func0} {func1} {preferredWay}',
- // 'functions' => array(
- // "CHtml::checkBoxList('parent{id}', '', CHtml::listData(Othermodel::model()->findAll(), 'id', 'title'));",
- // '$this->funcThatReturnsText();'
- // 'preferredWay' => '$this->instructMe();'
- // ),
- public $template = '{fields}';
- // User-contributed functions, see comment for $template.
- public $functions = array();
- // If true, all the user-contributed functions in $functions will be
- // substituted in $htmlOptions['template'] as well.
- // If an array of function names, all the listed functions will be
- // substituted in $htmlOptions['template'] as well.
- public $functionsInHtmlOptionsTemplate = false;
- // how should multiple fields be delimited
- public $delimiter = " | ";
- // style of the selection Widget
- public $style = "dropDownList";
- public $htmlOptions = array();
- public $parentObjects = 0;
- public $orderParentsBy = 0;
- public $groupParentsBy = 0;
- // override this for complicated MANY_MANY relations:
- public $manyManyTable = '';
- public $manyManyTableLeft = '';
- public $manyManyTableRight = '';
- public $num = 1;
- public function init() {
- if (!is_object($this->model)) {
- if (!$this->_model = new $this->model) throw new CException(Yii::t('yii', 'Relation widget is not able to instantiate the given Model'));
- } else {
- $this->_model = $this->model;
- }
- // Instantiate Model and related Model
- foreach ($this->_model->relations() as $key => $value) {
- if (strcmp($this->relation, $key) == 0) {
- // $key = Name of the Relation
- // $value[0] = Type of the Relation
- // $value[1] = Related Model
- // $value[2] = Related Field or Many_Many Table
- switch ($value[0]) {
- case 'CBelongsToRelation':
- case 'CHasOneRelation':
- $this->_relatedModel = new $value[1];
- if (!isset($this->field)) {
- $this->field = $value[2];
- }
- break;
- case 'CManyManyRelation':
- preg_match_all('/^.*\(/', $value[2], $matches);
- $this->manyManyTable = substr($matches[0][0], 0, strlen($matches[0][0]) - 1);
- preg_match_all('/\(.*,/', $value[2], $matches);
- $this->manyManyTableLeft = substr($matches[0][0], 1, strlen($matches[0][0]) - 2);
- preg_match_all('/,.*\)/', $value[2], $matches);
- $this->manyManyTableRight = substr($matches[0][0], 2, strlen($matches[0][0]) - 3);
- $this->_relatedModel = new $value[1];
- break;
- }
- }
- }
- if (!is_object($this->_relatedModel)) throw new CException(Yii::t('yii', 'Relation widget cannot find the given Relation(' . $this->relation . ')'));
- if (!isset($this->relatedPk) || $this->relatedPk == "") {
- $this->relatedPk = $this->_relatedModel->tableSchema->primaryKey;
- }
- if (!isset($this->fields) || $this->fields == "" || $this->fields == array()) throw new CException(Yii::t('yii', 'Widget "Relation" has been run without fields Option(string or array)'));
- }
- // Check if model-value contains '.' and generate -> directives:
- public function getModelData($model, $field) {
- if (strstr($field, '.')) {
- $data = explode('.', $field);
- $value = $model->getRelated($data[0])->$data[1];
- } else $value = $model->$field;
- return $value;
- }
- /**
- * This function fetches all needed data of the related Object and returns them
- * in an array that is prepared for use in ListData.
- */
- public function getRelatedData() {
- /* At first we determine, if we want to display all parent Objects, or
- * if the User supplied an list of Objects */
- if (is_object($this->parentObjects)) // a single Element
- {
- $parentobjects = array(
- $this->parentObjects
- );
- } else if (is_array($this->parentObjects)) // Only show this elements
- {
- $parentobjects = $this->parentObjects;
- } else
- // Show all Parent elements
- {
- if ($this->criteria === false) {
- $parentobjects = CActiveRecord::model(get_class($this->_relatedModel))->findAll(array(
- 'order' => GHelper::guessNameColumn($this->_relatedModel->tableSchema->columns)
- ));
- } else {
- $parentobjects = CActiveRecord::model(get_class($this->_relatedModel));
- $parentobjects->setDbCriteria($this->criteria);
- $parentobjects = $parentobjects->findAll(array(
- 'order' => GHelper::guessNameColumn($this->_relatedModel->tableSchema->columns)
- ));
- }
- }
- if ($this->allowEmpty) if (is_string($this->allowEmpty)) $dataArray[''] = $this->allowEmpty;
- else $dataArray[''] = Yii::t('app', 'None');
- foreach ($parentobjects as $obj) {
- if (!is_array($this->fields)) $this->fields = array(
- $this->fields
- );
- $fields = '';
- $i = 0;
- foreach ($this->fields as $field) {
- $rule = sprintf('{%s}', $field);
- $rules[$rule] = $obj->$field;
- if ($i++ > 0) $fields.= $this->delimiter;
- $fields.= $this->getModelData($obj, $field);
- }
- $defaultrules = array(
- '{fields}' => $fields,
- '{id}' => $obj->{$obj->tableSchema->primaryKey}
- );
- // Look for user-contributed functions and evaluate them
- if ($this->functions != array()) {
- foreach ($this->functions as $key => $function) {
- // If the key is of type string, it's assumed to be a named function,
- // used like {myFuncName}.
- // If the key is an integer, it's assumed to be an unnamed function used
- // the old way, {funcX} where X is its index in the functions array.
- // We keep the integer support mostly for backwards compatibility, the
- // new way is encouraged.
- if (is_string($key)) {
- $funcrules[sprintf('{%s}', $key) ] = $this->controller->evaluateExpression(strtr($function, $defaultrules));
- } else {
- $funcrules[sprintf('{func%d}', $key) ] = $this->controller->evaluateExpression(strtr($function, $defaultrules));
- }
- }
- }
- // Merge the evaluated rules, if exist
- if (isset($funcrules)) $rules = array_merge($rules, $funcrules);
- // Merge the default rules into our ruleset
- $rules = array_merge($rules, $defaultrules);
- // Apply the rules to the template
- $value = strtr($this->template, $rules);
- // Apply the user contributed functions to $htmlOptions's template, if requested.
- if (isset($this->htmlOptions['template']) && $this->functionsInHtmlOptionsTemplate !== false && isset($funcrules) && is_array($funcrules)) {
- if (is_array($this->functionsInHtmlOptionsTemplate)) {
- $funcrulesToUse = array();
- foreach ($this->functionsInHtmlOptionsTemplate as $functionName) {
- $functionName = sprintf('{%s}', $functionName);
- if (isset($funcrules[$functionName])) {
- $funcrulesToUse[$functionName] = $funcrules[$functionName];
- }
- }
- $this->htmlOptions['template'] = strtr($this->htmlOptions['template'], $funcrulesToUse);
- } else {
- $this->htmlOptions['template'] = strtr($this->htmlOptions['template'], $funcrules);
- }
- }
- if ($this->groupParentsBy != '') {
- $dataArray[$obj->{$this->groupParentsBy}][$obj->{$this->relatedPk}] = $value;
- } else {
- $dataArray[$obj->{$this->relatedPk}] = $value;
- }
- }
- if (!isset($dataArray) || !is_array($dataArray)) $dataArray = array();
- return $dataArray;
- }
- /**
- * Retrieves the Assigned Objects of the MANY_MANY related Table
- */
- public function getAssignedObjects() {
- if (!$this->_model->{$this->_model->tableSchema->primaryKey}) return array();
- $sql = sprintf("select * from %s where %s = %s", $this->manyManyTable, $this->manyManyTableLeft, $this->_model->{$this->_model->tableSchema->primaryKey});
- $result = $this->_model->getDbConnection()->createCommand($sql)->queryAll();
- foreach ($result as $foreignObject) {
- $id = $foreignObject[$this->manyManyTableRight];
- $objects[$id] = $this->_relatedModel->findByPk($id);
- }
- foreach ($this->_model->{$this->relation} as $relobj) $objects[$relobj->id] = $relobj;
- return isset($objects) ? $objects : array();
- }
- /**
- * Retrieves the not Assigned Objects of the MANY_MANY related Table
- * This is used in the two-pane style view.
- */
- public function getNotAssignedObjects() {
- foreach ($this->getRelatedData() as $key => $value) {
- if (!array_key_exists($key, $this->getAssignedObjects())) {
- $objects[$key] = $this->_relatedModel->findByPk($key);
- }
- }
- return $objects ? $objects : array();
- }
- /**
- * Gets the Values of the given Object or Objects depending on the
- * $this->fields the widget requests
- */
- public function getObjectValues($objects) {
- if (is_array($objects)) {
- foreach ($objects as $object) {
- $attributeValues[$object->primaryKey] = $object->{$this->fields};
- }
- } else if (is_object($objects)) {
- $attributeValues[$object->primaryKey] = $objects->{$this->fields};
- }
- return isset($attributeValues) ? $attributeValues : array();
- }
- /*
- * How will the Listbox of the MANY_MANY Assignment be called?
- */
- public function getListBoxName($ajax = false) {
- if ($ajax) {
- return sprintf('%s_%s', get_class($this->_model) , get_class($this->_relatedModel));
- } else {
- return sprintf('%s[%s]', get_class($this->_model) , get_class($this->_relatedModel));
- }
- }
- public function renderBelongsToSelection() {
- CWidget::render(strtolower($this->style) , array(
- 'id' => $this->relation . '_options',
- 'model' => $this->_model,
- 'field' => $this->field,
- 'data' => $this->getRelatedData() ,
- 'htmlOptions' => $this->htmlOptions
- ));
- }
- public function renderManyManySelection() {
- if (strcasecmp($this->style, 'twopane') == 0) $this->renderTwoPaneSelection();
- else if (strcasecmp($this->style, 'checkbox') == 0) $this->renderCheckBoxListSelection();
- else if (strcasecmp($this->style, 'dropDownList') == 0) $this->renderManyManyDropDownListSelection();
- else if (strcasecmp($this->style, 'radiobutton') == 0) $this->renderManyManyRadioButtonListSelection();
- else $this->renderOnePaneSelection();
- }
- // when only one of the MANY_MANY related Objects should be choosable
- public function renderManyManyRadioButtonListSelection() {
- echo CHtml::RadiobuttonList(get_class($this->_model) . '[' . get_class($this->_relatedModel) . '][]', $this->_model{$this->relation}[0]->{$this->_model{$this->relation}[0]->tableSchema->primaryKey
- }
- , $this->getRelatedData() , $this->htmlOptions);
- }
- /*
- * Renders one dropDownList per selectable related Element.
- * The users can add additional entries with the + and remove entries
- * with the - Button .
- */
- public function renderManyManyDropDownListSelection() {
- $uniqueid = $this->_relatedModel->tableSchema->name;
- if ($this->parentObjects != 0) $relatedmodels = $this->parentObjects;
- else $relatedmodels = $this->_relatedModel->findAll();
- $addbutton = sprintf('i' . $this->num . ' = %d; maxi' . $this->num . ' = %d;', count($this->getAssignedObjects()) + 1, count($relatedmodels));
- Yii::app()->clientScript->registerScript('addbutton_' . $uniqueid . '_' . $this->num, $addbutton);
- $i = 0;
- foreach ($relatedmodels as $obj) {
- $i++;
- $isAssigned = $this->isAssigned($obj->{$this->relatedPk});
- echo CHtml::openTag('div', array(
- 'id' => sprintf('div_%s_%d', $uniqueid, $i) ,
- 'style' => $isAssigned ? '' : 'display:none;',
- 'class' => 'relation'
- ));
- echo CHtml::dropDownList(sprintf('%s[%d]', $this->getListBoxName() , $i) , $isAssigned ? $obj->id : 0, CHtml::listData(array_merge(array(
- '0' => $this->allowEmpty
- ) , $relatedmodels) , $this->relatedPk, $this->fields));
- echo CHtml::button('-', array(
- 'id' => sprintf('sub_%s_%d', $uniqueid, $i)
- ));
- echo CHtml::closeTag('div');
- $jsadd = '
- $(\'#add_' . $uniqueid . '\').click(function() {
- $(\'#div_' . $uniqueid . '_\' + i' . $this->num . ').show();
- if(i' . $this->num . ' <= maxi' . $this->num . ') i' . $this->num . '++;
- });
- ';
- $jssub = '
- $(\'#sub_' . $uniqueid . '_' . $i . '\').click(function() {
- $(\'#div_' . $uniqueid . '_' . $i . '\').hide();
- $("select[name=\'' . $this->getListBoxName() . '[' . $i . ']\']").val(\'\');
- if(i' . $this->num . ' >= 1) i--;
- });
- ';
- Yii::app()->clientScript->registerScript('addbutton_' . $uniqueid, $jsadd);
- Yii::app()->clientScript->registerScript('subbutton_' . $uniqueid, $jssub);
- }
- echo ' ';
- echo CHtml::button('+', array(
- 'id' => sprintf('add_%s', $uniqueid)
- ));
- }
- public function isAssigned($id) {
- return in_array($id, array_keys($this->getAssignedObjects()));
- }
- public static function retrieveValues($data) {
- $returnArray = array();
- $i = 0;
- foreach ($data as $value) {
- if ($value != 0) $returnArray[(int)$i] = (int)$value;
- $i++;
- }
- return $returnArray;
- }
- public function renderCheckBoxListSelection() {
- $keys = array_keys($this->getAssignedObjects());
- if (isset($this->preselect) && $this->preselect != false) $keys = $this->preselect;
- $id = $this->relation . '_options';
- echo CHtml::openTag('div', array(
- 'id' => $id,
- 'class' => 'relation'
- ));
- echo CHtml::CheckBoxList($this->getListBoxName() , $keys, $this->getRelatedData() , $this->htmlOptions);
- echo CHtml::closeTag('div');
- }
- public function renderOnePaneSelection() {
- $keys = array_keys($this->getAssignedObjects());
- echo CHtml::ListBox($this->getListBoxName() , $keys, $this->getRelatedData() , array(
- 'multiple' => 'multiple'
- ));
- }
- public function handleAjaxRequest($_POST) {
- print_r($_POST);
- }
- public function renderTwoPaneSelection() {
- echo CHtml::ListBox($this->getListBoxName() , array() , $this->getObjectValues($this->getAssignedObjects()) , array(
- 'multiple' => 'multiple'
- ));
- $ajax = array(
- 'type' => 'POST',
- 'data' => array(
- 'yeah'
- ) ,
- 'update' => '#' . $this->getListBoxName(true) ,
- );
- echo CHtml::ajaxSubmitButton('<<', array(
- 'assign'
- ) , $ajax);
- $ajax = array(
- 'type' => 'POST',
- 'update' => '#not_' . $this->getListBoxName(true)
- );
- echo CHtml::ajaxSubmitButton('>>', array(
- 'assign',
- 'revoke' => 1
- ) , $ajax); //,
- //$data['revoke']);
- echo CHtml::ListBox('not_' . $this->getListBoxName() , array() , $this->getObjectValues($this->getNotAssignedObjects()) , array(
- 'multiple' => 'multiple'
- ));
- }
- public function run() {
- if ($this->manyManyTable != '') $this->renderManyManySelection();
- else $this->renderBelongsToSelection();
- if ($this->showAddButton) $this->renderAddButton();
- }
- protected function renderAddButton() {
- $model = strtolower(get_class($this->_model));
- $relatedModel = strtolower(get_class($this->_relatedModel));
- $relations = $this->_model->relations();
- $controller = GHelper::resolveController($relations[$this->relation]);
- if ($this->addButtonUrl != '') $link = $this->addButtonUrl;
- else $link = $this->controller->createUrl($controller . '/create', array(
- 'relation' => $this->relation
- ));
- if ($this->addButtonRefreshUrl == '') $this->addButtonRefreshUrl = $this->controller->createUrl($model . '/getOptions', array(
- 'relation' => $this->relation,
- 'style' => $this->style,
- 'fields' => $this->fields
- ));
- $string = Yii::t('app', 'Add new {model}', array(
- '{model}' => $relatedModel
- ));
- $dialog = 'zii.widgets.jui.CJuiDialog';
- $this->beginWidget($dialog, array(
- 'id' => $this->relation . '_dialog',
- 'options' => array(
- 'autoOpen' => false,
- 'modal' => true,
- 'title' => $string,
- 'width' => 800,
- 'height' => 600
- )
- ));
- $this->endWidget($dialog);
- echo CHtml::AjaxButton(is_string($this->showAddButton) ? $this->showAddButton : $string, $link, array(
- 'success' => "function(html) {
- jQuery('#" . $this->relation . "_dialog').html(html);
- $('#" . $this->relation . "_dialog').dialog('open');
- }"
- ) , array(
- 'id' => $this->relation . '_create'
- ));
- // prepare the Submit button that is not loaded into the DOM yet
- Yii::app()->clientScript->registerScript($this->relation . '_submit', "jQuery('body').delegate('#submit_" . $this->relation . "','click',function(){
- jQuery.ajax({'url':'" . $link . "',
- 'cache':false,
- 'type':'POST',
- 'data':jQuery(this).parents('form').serialize(),
- 'success':function(html){
- jQuery('#" . $this->relation . "_dialog').html(html)}});
- return false;});");
- Yii::app()->clientScript->registerScript($this->relation . '_done', "jQuery('body').delegate('#" . $this->relation . "_done','click',function(){
- jQuery.ajax({'url':'" . $this->addButtonRefreshUrl . "',
- 'cache':false,
- 'success':function(html){
- jQuery('#" . $this->relation . "_options').html(html)}});
- $('#" . $this->relation . "_dialog').dialog('close');
- return false;});");
- }
- }