PageRenderTime 35ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 0ms

/core/libraries/dataform.php

http://rapyd-framework.googlecode.com/
PHP | 475 lines | 317 code | 22 blank | 136 comment | 45 complexity | d23b894897457475bcab0c9413918fef MD5 | raw file
  1. <?php
  2. if (!defined('RAPYD_PATH'))
  3. exit('No direct script access allowed');
  4. /**
  5. * Dataform library
  6. *
  7. * @todo optimizations.. see yii/kohana widgets
  8. * @todo a dataform basically is a sub-application, i can build a controller and use "rpd::run(..)"?
  9. * @package Core
  10. * @author Felice Ostuni
  11. * @copyright (c) 2011 Rapyd Team
  12. * @license http://www.rapyd.com/license
  13. */
  14. class dataform_library extends rpd_component_library
  15. {
  16. public $model;
  17. public $output = "";
  18. protected $source;
  19. public $fields = array();
  20. public $hash = "";
  21. protected $errors = array();
  22. //form action, enctype, scripts
  23. protected $process_url = "";
  24. protected $multipart = false;
  25. protected $default_group;
  26. public $attributes = array('class' => 'form');
  27. protected $error_string;
  28. protected $form_scripts;
  29. /**
  30. * it get an identifier, instance a validation library and check if a model is passed (in the config-array)
  31. *
  32. * @param array $config
  33. */
  34. public function __construct($config = array())
  35. {
  36. parent::__construct($config);
  37. $this->cid = parent::get_identifier();
  38. $this->validation = new rpd_validation_library();
  39. $url = ($this->url != '') ? $this->url : rpd_url_helper::get_url();
  40. $this->process_url = rpd_url_helper::append('process', 1, $url);
  41. if (isset($this->model))
  42. {
  43. $this->status = "create";
  44. if (isset($this->model))
  45. {
  46. $this->status = "create";
  47. }
  48. }
  49. }
  50. /**
  51. * internal, it build fields, called only if $config is passed to the constructor
  52. *
  53. * @param type $fields
  54. */
  55. protected function set_fields($fields)
  56. {
  57. foreach ($fields as $field)
  58. {
  59. $this->set_field($field);
  60. }
  61. }
  62. /**
  63. * called automagically by field($field) or field($type, $name, $label)
  64. *
  65. * @return object field class
  66. */
  67. public function set_field()
  68. {
  69. $field = array();
  70. if (func_num_args() == 3)
  71. {
  72. list($field['type'], $field['name'], $field['label']) = func_get_args();
  73. }
  74. if (func_num_args() == 1)
  75. {
  76. $field = func_get_arg(0);
  77. }
  78. if (isset($field['field']))
  79. {
  80. list($field['type'], $field['name'], $field['label']) = explode('|', $field['field']);
  81. }
  82. $field_name = $field["name"];
  83. //load and instance field
  84. $field_file = strtolower($field["type"]);
  85. $field_class = $field_file . '_field';
  86. $field_obj = new $field_class($field);
  87. if ($field_obj->type == "upload")
  88. {
  89. $this->multipart = true;
  90. if (!isset($this->upload))
  91. {
  92. $this->upload = new rpd_upload_helper();
  93. }
  94. $field_obj->upload = $this->upload;
  95. }
  96. //share model
  97. if (isset($this->model))
  98. {
  99. $field_obj->model = $this->model;
  100. }
  101. //default group
  102. if (isset($this->default_group) && !isset($field_obj->group))
  103. {
  104. $field_obj->group = $this->default_group;
  105. }
  106. $this->fields[$field_name] = $field_obj;
  107. return $field_obj; //method chaining
  108. }
  109. public function &get_field($field_name)
  110. {
  111. if (isset($this->fields[$field_name]))
  112. {
  113. return $this->fields[$field_name];
  114. }else {
  115. $this->show_error('datamodel non valido ' . get_class($source));
  116. die();
  117. }
  118. }
  119. /**
  120. * set css style
  121. * @todo search online for a better way to work with html/css for example using a dom-api like phpquery
  122. * @param string $style
  123. */
  124. protected function set_style($style)
  125. {
  126. $this->set_attributes(array('style' => $style));
  127. }
  128. /**
  129. * source can be a a table-name or a database-model
  130. * if you pass a table-name a datamodel will be instanced
  131. * if the form already contains fields the model will be shared with each field
  132. *
  133. * @param mixed $source
  134. * @return object datamodel
  135. */
  136. public function set_source($source)
  137. {
  138. //instance or reuse a model
  139. if (is_object($source) and (get_class($source) == 'rpd_datamodel_model' OR is_subclass_of($source, "rpd_datamodel_model")))
  140. {
  141. $this->model = $source;
  142. } elseif (is_string($source))
  143. {
  144. $this->model = new rpd_datamodel_model($source);
  145. } else
  146. {
  147. $this->show_error('datamodel non valido ' . get_class($source));
  148. die();
  149. }
  150. if (count($this->fields))
  151. {
  152. foreach ($this->fields as $field_obj)
  153. {
  154. if (in_array($field_obj->name, $this->model->field_names()))
  155. {
  156. $field_obj->model = $this->model;
  157. }
  158. }
  159. }
  160. $this->validation->model = $this->model;
  161. return $this->model;
  162. }
  163. /**
  164. * shortcut for model->load
  165. *
  166. * @param mixed $id
  167. */
  168. public function load($id)
  169. {
  170. if (isset($this->model))
  171. {
  172. $this->model->load($id);
  173. }
  174. }
  175. /**
  176. * internal, build each field
  177. *
  178. */
  179. public function build_fields()
  180. {
  181. foreach ($this->fields as $field)
  182. {
  183. //share status
  184. $field->status = $this->status;
  185. $field->build();
  186. }
  187. }
  188. /**
  189. * usage:
  190. * <code>
  191. * $edit->pre_process(array('update'), array($this, 'some_method'));
  192. * ..
  193. * function some_method($model)
  194. * {
  195. * //do checks.. etc..
  196. * $model->set('afield', 'avalue');
  197. * }
  198. * </code>
  199. *
  200. * @todo replace/rename it using ->callback() or something similar
  201. * @param type $action
  202. * @param type $function
  203. * @param type $arr_values
  204. */
  205. public function pre_process($action, $function, $arr_values = array())
  206. {
  207. $this->model->pre_process($action, $function, $arr_values);
  208. }
  209. /**
  210. * usage:
  211. * <code>
  212. * $edit->post_process(array('insert'), array($this, 'some_method'));
  213. * ..
  214. * function some_method($model)
  215. * {
  216. * //do checks.. etc..
  217. * $model->set('afield', 'avalue');
  218. * }
  219. * </code>
  220. *
  221. * @param type $action
  222. * @param type $function
  223. * @param type $arr_values
  224. */
  225. public function post_process($action, $function, $arr_values = array())
  226. {
  227. $this->model->post_process($action, $function, $arr_values);
  228. }
  229. /**
  230. * internal, build each field then form
  231. *
  232. * @return string compiled form
  233. */
  234. protected function build_form()
  235. {
  236. rpd_html_helper::css('dataform.css');
  237. $data = get_object_vars($this);
  238. $data['container'] = $this->button_containers();
  239. $form_type = 'open';
  240. // See if we need a multipart form
  241. foreach ($this->fields as $field_obj)
  242. {
  243. if ($field_obj instanceof upload_field)
  244. {
  245. $form_type = 'open_multipart';
  246. break;
  247. }
  248. }
  249. // Set the form open and close
  250. if ($this->status_is('show'))
  251. {
  252. $data['form_begin'] = '<div class="form">';
  253. $data['form_end'] = '</div>';
  254. } else
  255. {
  256. $data['form_begin'] = rpd_form_helper::$form_type($this->process_url, $this->attributes);
  257. $data['form_end'] = rpd_form_helper::close();
  258. }
  259. $data['fields'] = $this->fields;
  260. return rpd::view('dataform', $data);
  261. }
  262. /**
  263. * main method it detect status, exec action and build output
  264. *
  265. * @param string $method
  266. */
  267. public function build($method = 'form')
  268. {
  269. $this->process_url = $this->process_url . $this->hash;
  270. //detect form status (output)
  271. if (isset($this->model))
  272. {
  273. $this->status = ($this->model->loaded) ? "modify" : "create";
  274. } else
  275. {
  276. $this->status = "create";
  277. }
  278. //build fields
  279. $this->build_fields();
  280. //process only if instance is a dataform
  281. if (is_a($this, 'dataform_library'))
  282. {
  283. //build buttons
  284. $this->build_buttons();
  285. //sniff action
  286. if (isset($_POST) && (rpd_url_helper::value('process')))
  287. {
  288. $this->action = ($this->status == "modify") ? "update" : "insert";
  289. }
  290. //process
  291. $this->process();
  292. }
  293. $method = 'build_' . $method;
  294. $this->output = $this->$method();
  295. }
  296. /**
  297. * internal, run form validation and return boolean result
  298. *
  299. * @return bool
  300. */
  301. protected function is_valid()
  302. {
  303. //some fields mode can disable or change some rules.
  304. foreach ($this->fields as $field)
  305. {
  306. $field->action = $this->action;
  307. $field->get_mode();
  308. if (isset($field->rule))
  309. {
  310. if (($field->type != "upload") && $field->apply_rules)
  311. {
  312. $fieldnames[$field->name] = $field->label;
  313. $rules[$field->name] = $field->rule;
  314. } else
  315. {
  316. $field->required = false;
  317. }
  318. }
  319. }
  320. if (isset($rules))
  321. {
  322. $this->validation->set_rules($rules);
  323. $this->validation->set_fields($fieldnames);
  324. if (count($_POST) < 1)
  325. {
  326. $_POST = array(1);
  327. }
  328. } else
  329. {
  330. return true;
  331. }
  332. $result = $this->validation->run();
  333. $this->error_string = $this->validation->error_string;
  334. return $result;
  335. }
  336. /**
  337. * internal, process form, it there is a model try to save data
  338. *
  339. *
  340. * @todo redirect after profess are all in javascript (window.location), this is really bad, but there is some reason i don't remember
  341. * @todo move language specific messages to i18n files
  342. * @return boolean
  343. */
  344. protected function process()
  345. {
  346. //database save
  347. switch ($this->action)
  348. {
  349. case "update":
  350. case "insert":
  351. //validation failed
  352. if (!$this->is_valid())
  353. {
  354. $this->process_status = "error";
  355. foreach ($this->fields as $field)
  356. {
  357. $field->action = "idle";
  358. }
  359. return false;
  360. } else
  361. {
  362. $this->process_status = "success";
  363. }
  364. foreach ($this->fields as $field)
  365. {
  366. $field->action = $this->action;
  367. $result = $field->auto_update();
  368. if (!$result)
  369. {
  370. $this->process_status = "error";
  371. $this->error_string = $field->save_error;
  372. return false;
  373. }
  374. }
  375. if (isset($this->model))
  376. {
  377. $return = $this->model->save();
  378. } else
  379. {
  380. $return = true;
  381. }
  382. if (!$return)
  383. {
  384. if ($this->model->preprocess_result === false)
  385. {
  386. if ($this->action_is("update"))
  387. {
  388. $this->error_string.= $this->model->error_string;
  389. } else
  390. {
  391. $this->error_string.= $this->model->error_string;
  392. }
  393. }
  394. $this->process_status = "error";
  395. }
  396. return $return;
  397. break;
  398. case "delete":
  399. $return = $this->model->delete();
  400. if (!$return)
  401. {
  402. if ($this->model->preprocess_result === false)
  403. {
  404. $this->error_string.= $this->model->error_string;
  405. }
  406. $this->process_status = "error";
  407. } else
  408. {
  409. $this->process_status = "success";
  410. }
  411. break;
  412. case "idle":
  413. $this->process_status = "show";
  414. return true;
  415. break;
  416. default:
  417. return false;
  418. }
  419. }
  420. /**
  421. * internal, add a submit button
  422. *
  423. * @param array $config
  424. */
  425. public function save_button($config = null)
  426. {
  427. $caption = (isset($config['caption'])) ? $config['caption'] : rpd::lang('btn.save');
  428. $this->submit("btn_submit", $caption, "BL");
  429. }
  430. /**
  431. * internal, used to join in a single string all error messages
  432. *
  433. * @param array $message
  434. */
  435. protected function show_error($message)
  436. {
  437. echo '<p>' . implode('</p><p>', (!is_array($message)) ? array($message) : $message) . '</p>';
  438. }
  439. /**
  440. * nest a content inside a form, it can be called onlt after a build() bacause it work on compiled output
  441. *
  442. * @param string $field_id id of container
  443. * @param string $content content to be nested
  444. */
  445. public function nest($field_id, $content)
  446. {
  447. if ($this->output != "")
  448. {
  449. $nesting_point = 'id="' . $field_id . '">';
  450. $this->output = str_replace($nesting_point, $nesting_point . $content, $this->output);
  451. }
  452. }
  453. }