JsonableBehavior.php 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. <?php
  2. App::uses('ModelBehavior', 'Model');
  3. /**
  4. * Copyright 2011, PJ Hile (http://www.pjhile.com)
  5. *
  6. * Licensed under The MIT License
  7. * Redistributions of files must retain the above copyright notice.
  8. *
  9. * @version 0.1
  10. * @license http://www.opensource.org/licenses/mit-license.php The MIT License
  11. */
  12. /**
  13. * A behavior that will json_encode (and json_decode) fields if they contain an array or specific pattern.
  14. *
  15. * Requres: PHP 5 >= 5.2.0 or PECL json >= 1.2.0
  16. *
  17. * This is a port of the Serializeable behavior by Matsimitsu (http://www.matsimitsu.nl)
  18. */
  19. /**
  20. * Modified by Mark Scherer (http://www.dereuromark.de)
  21. *
  22. * Now supports different input/output formats:
  23. * - "list" is useful as some kind of pseudo enums or simple lists
  24. * - "params" is useful for multiple key/value pairs
  25. * - can be used to create dynamic forms (and tables)
  26. *
  27. * Also automatically cleans lists and works with custom separators etc
  28. *
  29. * 2011-07-04 ms
  30. */
  31. class JsonableBehavior extends ModelBehavior {
  32. public $decoded = null;
  33. public $settings = array();
  34. /**
  35. * //TODO: json input/ouput directly, clean
  36. * @access protected
  37. */
  38. public $_defaultSettings = array(
  39. 'fields' => array(), # empty => only works with array!!!
  40. 'input' => 'array', # json, array, param, list (param/list only works with specific fields)
  41. 'output' => 'array', # json, array, param, list (param/list only works with specific fields)
  42. 'separator' => '|', # only for param or list
  43. 'keyValueSeparator' => ':', # only for param
  44. 'leftBound' => '{', # only for list
  45. 'rightBound' => '}', # only for list
  46. 'clean' => true, # only for param or list (autoclean values on insert)
  47. 'sort' => false, # only for list
  48. 'unique' => true, # only for list (autoclean values on insert),
  49. 'map' => array(), # map on a different DB field
  50. );
  51. public function setup(Model $Model, $config = array()) {
  52. $this->settings[$Model->alias] = Set::merge($this->_defaultSettings, $config);
  53. //extract ($this->settings[$Model->alias]);
  54. if (!is_array($this->settings[$Model->alias]['fields'])) {
  55. $this->settings[$Model->alias]['fields'] = (array)$this->settings[$Model->alias]['fields'];
  56. }
  57. if (!is_array($this->settings[$Model->alias]['map'])) {
  58. $this->settings[$Model->alias]['map'] = (array)$this->settings[$Model->alias]['map'];
  59. }
  60. }
  61. /**
  62. * Decodes the fields
  63. *
  64. * @param object $Model
  65. * @param array $results
  66. * @return array
  67. * @access public
  68. */
  69. public function afterFind(Model $Model, $results) {
  70. $results = $this->decodeItems($Model, $results);
  71. return $results;
  72. }
  73. /**
  74. * Decodes the fields of an array (if the value itself was encoded)
  75. *
  76. * @param array $arr
  77. * @return array
  78. * @access public
  79. */
  80. public function decodeItems(Model $Model, $arr) {
  81. foreach ($arr as $akey => $val) {
  82. if (!isset($val[$Model->alias])) {
  83. return $arr;
  84. }
  85. $fields = $this->settings[$Model->alias]['fields'];
  86. foreach ($val[$Model->alias] as $key => $v) {
  87. if (empty($fields) && !is_array($v) || !in_array($key, $fields)) {
  88. continue;
  89. }
  90. if ($this->isEncoded($Model, $v)) {
  91. if (!empty($this->settings[$Model->alias]['map'])) {
  92. $keys = array_keys($this->settings[$Model->alias]['fields'], $key);
  93. if (!empty($keys)) {
  94. $key = $this->settings[$Model->alias]['map'][array_shift($keys)];
  95. }
  96. }
  97. $arr[$akey][$Model->alias][$key] = $this->decoded;
  98. }
  99. }
  100. }
  101. return $arr;
  102. }
  103. /**
  104. * Saves all fields that do not belong to the current Model into 'with' helper model.
  105. *
  106. * @param object $Model
  107. * @access public
  108. */
  109. public function beforeSave(Model $Model) {
  110. $data = $Model->data[$Model->alias];
  111. $usedFields = $this->settings[$Model->alias]['fields'];
  112. $mappedFields = $this->settings[$Model->alias]['map'];
  113. if (empty($mappedFields)) {
  114. $mappedFields = $usedFields;
  115. }
  116. $fields = array();
  117. foreach ($mappedFields as $index => $map) {
  118. if (empty($map) || $map == $usedFields[$index]) {
  119. $fields[$usedFields[$index]] = $usedFields[$index];
  120. continue;
  121. }
  122. $fields[$map] = $usedFields[$index];
  123. }
  124. foreach ($data as $key => $val) {
  125. if (!empty($fields) && !array_key_exists($key, $fields)) {
  126. continue;
  127. }
  128. if (!empty($fields)) {
  129. $key = $fields[$key];
  130. }
  131. if (!empty($this->settings[$Model->alias]['fields']) || is_array($val)) {
  132. $Model->data[$Model->alias][$key] = $this->_encode($Model, $val);
  133. }
  134. }
  135. return true;
  136. }
  137. public function _encode(Model $Model, $val) {
  138. if (!empty($this->settings[$Model->alias]['fields'])) {
  139. if ($this->settings[$Model->alias]['input'] == 'param') {
  140. $val = $this->_fromParam($Model, $val);
  141. } elseif ($this->settings[$Model->alias]['input'] == 'list') {
  142. $val = $this->_fromList($Model, $val);
  143. if ($this->settings[$Model->alias]['unique']) {
  144. $val = array_unique($val);
  145. }
  146. if ($this->settings[$Model->alias]['sort']) {
  147. sort($val);
  148. }
  149. }
  150. }
  151. if (is_array($val)) {
  152. $val = json_encode($val);
  153. }
  154. return $val;
  155. }
  156. /**
  157. * fields are absolutely necessary to function properly!
  158. * 2011-06-18 ms
  159. */
  160. public function _decode(Model $Model, $val) {
  161. $decoded = json_decode($val);
  162. if ($decoded === false) {
  163. return false;
  164. }
  165. $decoded = (array)$decoded;
  166. if ($this->settings[$Model->alias]['output'] == 'param') {
  167. $decoded = $this->_toParam($Model, $decoded);
  168. } elseif ($this->settings[$Model->alias]['output'] == 'list') {
  169. $decoded = $this->_toList($Model, $decoded);
  170. }
  171. return $decoded;
  172. }
  173. /**
  174. * array() => param1:value1|param2:value2|...
  175. */
  176. public function _toParam(Model $Model, $val) {
  177. $res = array();
  178. foreach ($val as $key => $v) {
  179. $res[] = $key.$this->settings[$Model->alias]['keyValueSeparator'].$v;
  180. }
  181. return implode($this->settings[$Model->alias]['separator'], $res);
  182. }
  183. public function _fromParam(Model $Model, $val) {
  184. $leftBound = $this->settings[$Model->alias]['leftBound'];
  185. $rightBound = $this->settings[$Model->alias]['rightBound'];
  186. $separator = $this->settings[$Model->alias]['separator'];
  187. $res = array();
  188. $pieces = String::tokenize($val, $separator, $leftBound, $rightBound);
  189. foreach ($pieces as $piece) {
  190. $subpieces = String::tokenize($piece, $this->settings[$Model->alias]['keyValueSeparator'], $leftBound, $rightBound);
  191. if (count($subpieces) < 2) {
  192. continue;
  193. }
  194. $res[$subpieces[0]] = $subpieces[1];
  195. }
  196. return $res;
  197. }
  198. /**
  199. * array() => value1|value2|value3|...
  200. */
  201. public function _toList(Model $Model, $val) {
  202. return implode($this->settings[$Model->alias]['separator'], $val);
  203. }
  204. public function _fromList(Model $Model, $val) {
  205. extract($this->settings[$Model->alias]);
  206. return String::tokenize($val, $separator, $leftBound, $rightBound);
  207. }
  208. /**
  209. * Checks if string is encoded array/object
  210. *
  211. * @param string string to check
  212. * @access public
  213. * @return boolean
  214. */
  215. public function isEncoded(Model $Model, $str) {
  216. $this->decoded = $this->_decode($Model, $str);
  217. if ($this->decoded !== false) {
  218. return true;
  219. }
  220. return false;
  221. }
  222. }