PageRenderTime 55ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/extensions/relation/Relation.php

http://phundament.googlecode.com/
PHP | 568 lines | 336 code | 72 blank | 160 comment | 61 complexity | 6a7bd45df957bbe9d2c6e69fafdba200 MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0
  1. <?php
  2. /*
  3. The Relation widget is used in forms, where the User can choose
  4. between a selection of model elements, that this models belongs to.
  5. It is able to handle BELONGS_TO, HAS_ONE and MANY_MANY Relations. The Relation
  6. type is detected automatically from the Model 'relations()' section.
  7. The Widget has different styles in which it can render the possible choices.
  8. Use the 'style' option to set the appropriate style.
  9. The following example shows how to use Relation with a minimal config,
  10. assuming we have a Model "Post" and "User", where one User belongs
  11. to a Post:
  12. <pre>
  13. $this->widget('application.components.Relation', array(
  14. 'model' => 'Post',
  15. 'relation' => 'user'
  16. 'fields' => 'username' // show the field "username" of the parent element
  17. ));
  18. </pre>
  19. Results in a drop down list in which the user can choose between
  20. all available Users in the Database. The shown field of the
  21. Table "User" is "username" in this example.
  22. You can choose the Style of your Widget in the 'style' option.
  23. Note that a Many_Many Relation always gets rendered as a Listbox,
  24. since you can select multiple Elements.
  25. 'fields' can be an array or an string.
  26. If you pass an array to 'fields', the Widget will display every field in
  27. this array. If you want to show further sub-relations, separate the values
  28. with '.', for example: 'fields' => 'array('parent.grandparent.description')
  29. Optional Parameters:
  30. You can use 'field' => 'post_userid' if the field in the model
  31. that represents the foreign model is called different than in the
  32. relation
  33. Use 'relatedPk' => 'id_of_user' if the primary Key of the Foreign
  34. Model differs from the one given in the relation.
  35. Normally you shouldn´t use this fields cause the Widget get the relations
  36. automatically from the relation.
  37. Use 'allowEmpty' to let the user be able to choose no parent. If you
  38. set this to a string, this string will be displayed with the available
  39. choices.
  40. With 'showAddButton' => 'false' you can disable the 'create new Foreignkey'
  41. Button generated beside the Selectbox.
  42. Define the AddButtonString with 'addButtonString' => 'Add...'. This string
  43. is set default to '+'
  44. When using the '+' button you most likely want to return to where you came.
  45. To accomplish this, we pass a 'returnTo' parameter by $_GET.
  46. The Controller can send the user back to where he came from this way:
  47. <pre>
  48. if($model->save())
  49. if(isset($_GET['returnTo']))
  50. $this->redirect(array(urldecode($_GET['returnTo'])));
  51. </pre>
  52. Using the 'style' option we can configure how our Widget gets rendered.
  53. The following styles are available:
  54. Selectbox (default), Listbox, Checkbox and in MANY_MANY relations 'twopane'
  55. The style is case insensitive so one can use dropdownlist or dropDownList.
  56. Use the option 'createAction' if the action to add additional foreign Model
  57. options differs from 'create'.
  58. With 'parentObjects' you can limit the Parent Elements that are being shown.
  59. It takes an array of elements that could be returned from an scope or
  60. an SQL Query.
  61. The parentObjects can be grouped, for example, with
  62. 'groupParentsBy' => 'city'
  63. Use the option 'htmlOptions' to pass any html Options to the
  64. Selectbox/Listbox form element.
  65. Full Example:
  66. <pre>
  67. $this->widget('application.components.Relation', array(
  68. 'model' => 'Post',
  69. 'field' => 'Userid',
  70. 'style' => 'ListBox',
  71. 'parentObjects' => Parentmodel::model()->findAll('userid = 17'),
  72. 'groupParentsBy' => 'city',
  73. 'relation' => 'user',
  74. 'relatedPk' => 'id_of_user',
  75. 'fields' => array( 'username', 'username.group.groupid' ),
  76. 'delimiter' => ' -> ', // default: ' | '
  77. 'returnTo' => 'model/create',
  78. 'createAction' => 'add', // default: 'create'
  79. 'addButtonString' => 'click here to add a new User', // default: ''
  80. 'htmlOptions' => array('style' => 'width: 100px;')
  81. ));
  82. </pre>
  83. @author Herbert Maschke <thyseus@gmail.com>
  84. @version 1.0rc
  85. @since 1.1
  86. */
  87. class Relation extends CWidget
  88. {
  89. // this Variable holds an instance of the Object
  90. protected $_model;
  91. // this Variable holds an instance of the related Object
  92. protected $_relatedModel;
  93. // draw the relation of which model?
  94. public $model;
  95. // which relation should be rendered?
  96. public $relation;
  97. public $field;
  98. // the Primary Key of the foreign Model
  99. public $relatedPk;
  100. // a field or an array of fields that determine which field values
  101. // should be rendered in the selection
  102. public $fields;
  103. // if this is set, the User is able to select no related model
  104. // if this is set to a string, this string will be presented
  105. public $allowEmpty = 0;
  106. // disable this to hide the Add Button
  107. // set this to a string to set the String to be displayed
  108. public $showAddButton = true;
  109. // use this to set the link where the user should return to after
  110. // clicking the add Button
  111. public $returnLink;
  112. // how should multiple fields be delimited
  113. public $delimiter = " | ";
  114. // style of the selection Widget
  115. public $style = "dropDownList";
  116. public $createAction = "create";
  117. public $htmlOptions = array();
  118. public $parentObjects = 0;
  119. public $orderParentsBy = 0;
  120. public $groupParentsBy = 0;
  121. // override this for complicated MANY_MANY relations:
  122. public $manyManyTable = '';
  123. public $manyManyTableLeft = '';
  124. public $manyManyTableRight = '';
  125. public function init()
  126. {
  127. if(!is_object($this->model))
  128. {
  129. if(!$this->_model = new $this->model)
  130. throw new CException(
  131. Yii::t('yii','Relation widget is not able to instantiate the given Model'));
  132. }
  133. else
  134. {
  135. $this->_model = $this->model;
  136. }
  137. // Instantiate Model and related Model
  138. foreach($this->_model->relations() as $key => $value)
  139. {
  140. if(strcmp($this->relation,$key) == 0)
  141. {
  142. // $key = Name of the Relation
  143. // $value[0] = Type of the Relation
  144. // $value[1] = Related Model
  145. // $value[2] = Related Field or Many_Many Table
  146. switch($value[0])
  147. {
  148. case 'CBelongsToRelation':
  149. case 'CHasOneRelation':
  150. $this->_relatedModel = new $value[1];
  151. if(!isset($this->field))
  152. {
  153. $this->field = $value[2];
  154. }
  155. break;
  156. case 'CManyManyRelation':
  157. preg_match_all('/^.*\(/', $value[2], $matches);
  158. $this->manyManyTable = substr($matches[0][0], 0, strlen($matches[0][0]) -1);
  159. preg_match_all('/\(.*,/', $value[2], $matches);
  160. $this->manyManyTableLeft = substr($matches[0][0], 1, strlen($matches[0][0]) - 2);
  161. preg_match_all('/,.*\)/', $value[2], $matches);
  162. $this->manyManyTableRight = substr($matches[0][0], 2, strlen($matches[0][0]) - 3);
  163. $this->_relatedModel = new $value[1];
  164. break;
  165. }
  166. }
  167. }
  168. if(!is_object($this->_relatedModel))
  169. throw new CException(
  170. Yii::t('yii','Relation widget cannot find the given Relation('.$this->relation.')'));
  171. if(!isset($this->relatedPk) || $this->relatedPk == "")
  172. {
  173. $this->relatedPk = $this->_relatedModel->tableSchema->primaryKey;
  174. }
  175. if(!isset($this->fields) || $this->fields == "" || $this->fields == array())
  176. throw new CException(Yii::t('yii','Widget "Relation" has been run without fields Option(string or array)'));
  177. }
  178. // Check if model-value contains '.' and generate -> directives:
  179. public function getModelData($model, $field)
  180. {
  181. if(strstr($field, '.'))
  182. {
  183. $data = explode('.', $field);
  184. $value = $model->getRelated($data[0])->$data[1];
  185. } else
  186. $value = $model->$field;
  187. return $value;
  188. }
  189. /**
  190. * This function fetches all needed data of the related Object and returns them
  191. * in an array that is prepared for use in ListData.
  192. */
  193. public function getRelatedData()
  194. {
  195. /* At first we determine, if we want to display all parent Objects, or
  196. * if the User supplied an list of Objects */
  197. if(is_object($this->parentObjects)) // a single Element
  198. {
  199. $parentobjects = array($this->parentObjects);
  200. }
  201. else if(is_array($this->parentObjects)) // Only show this elements
  202. {
  203. $parentobjects = $this->parentObjects;
  204. }
  205. else
  206. {
  207. // Show all Parent elements
  208. $parentobjects = CActiveRecord::model(get_class($this->_relatedModel))->findAll();
  209. }
  210. if($this->allowEmpty)
  211. if(is_string($this->allowEmpty))
  212. $dataArray[0] = $this->allowEmpty;
  213. else
  214. $dataArray[0] = Yii::t('app', 'None');
  215. foreach($parentobjects as $obj)
  216. {
  217. if(is_string($this->fields))
  218. {
  219. // Display only 1 field:
  220. $value = $this->getModelData($obj, $this->fields);
  221. }
  222. else if(is_array($this->fields))
  223. {
  224. // Display more than 1 field:
  225. $value = '';
  226. foreach($this->fields as $field)
  227. {
  228. $value .= $this->getModelData($obj, $field) . $this->delimiter;
  229. }
  230. }
  231. if($this->groupParentsBy != '')
  232. {
  233. $dataArray[$obj->{$this->groupParentsBy}][$obj->{$this->relatedPk}] = $value;
  234. }
  235. else
  236. {
  237. $dataArray[$obj->{$this->relatedPk}] = $value;
  238. }
  239. }
  240. if(!isset($dataArray) || !is_array($dataArray))
  241. $dataArray = array();
  242. return $dataArray;
  243. }
  244. /**
  245. * Retrieves the Assigned Objects of the MANY_MANY related Table
  246. */
  247. public function getAssignedObjects()
  248. {
  249. if(!$this->_model->id)
  250. return array();
  251. $sql = sprintf("select * from %s where %s = %s",
  252. $this->manyManyTable,
  253. $this->manyManyTableLeft,
  254. $this->_model->{$this->_model->tableSchema->primaryKey});
  255. $result = $this->_model->dbConnection->createCommand($sql)->queryAll();
  256. foreach($result as $foreignObject) {
  257. $id = $foreignObject[$this->manyManyTableRight];
  258. $objects[$id] = $this->_relatedModel->findByPk($id);
  259. }
  260. return isset($objects) ? $objects : array();
  261. }
  262. /**
  263. * Retrieves the not Assigned Objects of the MANY_MANY related Table
  264. * This is used in the two-pane style view.
  265. */
  266. public function getNotAssignedObjects()
  267. {
  268. foreach($this->getRelatedData() as $key => $value)
  269. {
  270. if(!array_key_exists($key, $this->getAssignedObjects()))
  271. {
  272. $objects[$key] = $this->_relatedModel->findByPk($key);
  273. }
  274. }
  275. return $objects ? $objects : array();
  276. }
  277. /**
  278. * Gets the Values of the given Object or Objects depending on the
  279. * $this->fields the widget requests
  280. */
  281. public function getObjectValues($objects)
  282. {
  283. if(is_array($objects)) {
  284. foreach($objects as $object)
  285. {
  286. $attributeValues[$object->primaryKey] = $object->{$this->fields};
  287. }
  288. }
  289. else if(is_object($objects))
  290. {
  291. $attributeValues[$object->primaryKey] = $objects->{$this->fields};
  292. }
  293. return isset($attributeValues) ? $attributeValues : array();
  294. }
  295. /*
  296. * How will the Listbox of the MANY_MANY Assignment be called?
  297. */
  298. public function getListBoxName($ajax = false)
  299. {
  300. if($ajax)
  301. {
  302. return sprintf('%s_%s',
  303. get_class($this->_model),
  304. get_class($this->_relatedModel)
  305. );
  306. }
  307. else
  308. {
  309. return sprintf('%s[%s]',
  310. get_class($this->_model),
  311. $this->relation
  312. );
  313. }
  314. }
  315. public function renderBelongsToSelection() {
  316. if(strcasecmp($this->style, "dropDownList") == 0)
  317. echo CHtml::ActiveDropDownList($this->_model,
  318. $this->field,
  319. $this->getRelatedData(),
  320. $this->htmlOptions);
  321. else if(strcasecmp($this->style, "listbox") == 0)
  322. echo CHtml::ActiveListBox($this->_model,
  323. $this->field,
  324. $this->getRelatedData(),
  325. $this->htmlOptions);
  326. else if(strcasecmp($this->style, "checkbox") == 0)
  327. echo CHtml::ActiveCheckBoxList($this->_model,
  328. $this->field,
  329. $this->getRelatedData(),
  330. $this->htmlOptions);
  331. }
  332. public function renderManyManySelection() {
  333. if(strcasecmp($this->style, 'twopane') == 0)
  334. $this->renderTwoPaneSelection();
  335. else if(strcasecmp($this->style, 'checkbox') == 0)
  336. $this->renderCheckBoxListSelection();
  337. else if(strcasecmp($this->style, 'dropDownList') == 0)
  338. $this->renderManyManyDropDownListSelection();
  339. else
  340. $this->renderOnePaneSelection();
  341. }
  342. /*
  343. * Renders one dropDownList per selectable related Element.
  344. * The users can add additional entries with the + and remove entries
  345. * with the - Button .
  346. */
  347. public function renderManyManyDropDownListSelection()
  348. {
  349. $i = 0;
  350. foreach($this->_relatedModel->findAll() as $obj)
  351. {
  352. $i++;
  353. $isAssigned = $this->isAssigned($obj->id);
  354. echo CHtml::openTag('div', array(
  355. 'id' => 'div' . $i,
  356. 'style' => $isAssigned ? '' : 'display:none;',
  357. ));
  358. echo CHtml::dropDownList('rel-' . $obj->id . "-" . $this->getListBoxName(),
  359. $isAssigned ? $obj->id : 0,
  360. CHtml::listData(
  361. array_merge(
  362. array('0' => $this->allowEmpty),
  363. $this->_relatedModel->findAll()),
  364. $this->relatedPk,
  365. $this->fields
  366. )
  367. );
  368. echo CHtml::closeTag('div');
  369. }
  370. $jsadd = '
  371. i = 1;
  372. maxi = '.$i.';
  373. $(\'#add\').click(function() {
  374. $(\'#div\' + i).show();
  375. if(i <= maxi) ++i;
  376. });
  377. ';
  378. $jssub = '
  379. $(\'#sub\').click(function() {
  380. if(i > 2) --i;
  381. $(\'#div\' + i).hide();
  382. });
  383. ';
  384. Yii::app()->clientScript->registerScript('addbutton', $jsadd);
  385. Yii::app()->clientScript->registerScript('subbutton', $jssub);
  386. echo CHtml::button('+', array('id' => 'add'));
  387. echo CHtml::button('-', array('id' => 'sub'));
  388. }
  389. public function isAssigned($id)
  390. {
  391. return in_array($id, array_keys($this->getAssignedObjects()));
  392. }
  393. public static function retrieveValues($data, $field)
  394. {
  395. $returnArray = array();
  396. foreach($data as $key => $value)
  397. {
  398. if(strpos($key, 'rel') !== false)
  399. {
  400. if($value[$field] != "")
  401. $returnArray[] = $value[$field];
  402. }
  403. }
  404. return $returnArray;
  405. }
  406. public function renderCheckBoxListSelection()
  407. {
  408. $keys = array_keys($this->getAssignedObjects());
  409. echo CHtml::CheckBoxList($this->getListBoxName(),
  410. $keys,
  411. $this->getRelatedData(),
  412. $this->htmlOptions);
  413. }
  414. public function renderOnePaneSelection()
  415. {
  416. $keys = array_keys($this->getAssignedObjects());
  417. echo CHtml::ListBox($this->getListBoxName(),
  418. $keys,
  419. $this->getRelatedData(),
  420. array('multiple' => 'multiple'));
  421. }
  422. public function handleAjaxRequest($_POST) {
  423. print_r($_POST);
  424. }
  425. public function renderTwoPaneSelection()
  426. {
  427. echo CHtml::ListBox($this->getListBoxName(),
  428. array(),
  429. $this->getObjectValues($this->getAssignedObjects()),
  430. array('multiple' => 'multiple'));
  431. $ajax =
  432. array(
  433. 'type'=>'POST',
  434. 'data'=>array('yeah'),
  435. 'update'=>'#' . $this->getListBoxName(true),
  436. );
  437. echo CHtml::ajaxSubmitButton('<<',
  438. array('assign'),
  439. $ajax
  440. );
  441. $ajax =
  442. array(
  443. 'type'=>'POST',
  444. 'update'=>'#not_'.$this->getListBoxName(true)
  445. );
  446. echo CHtml::ajaxSubmitButton('>>',
  447. array('assign','revoke'=>1),
  448. $ajax);//,
  449. //$data['revoke']);
  450. echo CHtml::ListBox('not_' . $this->getListBoxName(),
  451. array(),
  452. $this->getObjectValues($this->getNotAssignedObjects()),
  453. array('multiple' => 'multiple'));
  454. }
  455. public function run()
  456. {
  457. if($this->manyManyTable != '')
  458. $this->renderManyManySelection();
  459. else
  460. $this->renderBelongsToSelection();
  461. if($this->showAddButton !== false)
  462. {
  463. if(!isset($this->returnLink) or $this->returnLink == "")
  464. $this->returnLink = $this->model->tableSchema->name . "/create";
  465. echo CHtml::Link(
  466. is_string($this->showAddButton) ?
  467. $this->showAddButton : 'New' , array(
  468. P2Helper::lcfirst(get_class($this->_relatedModel)) . "/" . $this->createAction,
  469. 'returnTo' => $this->returnLink));
  470. }
  471. }
  472. }