OptionForm.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. <?php
  2. namespace app\common\services;
  3. use Option;
  4. use ReflectionClass;
  5. use Illuminate\Support\Arr;
  6. use Illuminate\Support\Str;
  7. use BadMethodCallException;
  8. class OptionForm
  9. {
  10. /**
  11. * Pass this value to tell generator to
  12. * load text from language files automatically.
  13. */
  14. const AUTO_DETECT = 0x97ab1;
  15. protected $id;
  16. protected $title;
  17. protected $hint;
  18. protected $type = 'primary';
  19. protected $items = [];
  20. protected $values = [];
  21. protected $buttons = [];
  22. protected $messages = [];
  23. protected $alwaysCallback = null;
  24. protected $renderWithOutTable = false;
  25. protected $renderInputTagsOnly = false;
  26. protected $renderWithOutSubmitButton = false;
  27. /**
  28. * Create a new option form instance.
  29. *
  30. * @param string $id
  31. * @param string $title
  32. * @return void
  33. */
  34. public function __construct($id, $title)
  35. {
  36. $this->id = $id;
  37. if ($title == self::AUTO_DETECT) {
  38. $this->title = trans("options.$this->id.title");
  39. } else {
  40. $this->title = $title;
  41. }
  42. }
  43. /**
  44. * Add option item to the form dynamically.
  45. *
  46. * @param string $method
  47. * @param array $params
  48. * @return OptionItem
  49. *
  50. * @throws \BadMethodCallException
  51. */
  52. public function __call($method, $params)
  53. {
  54. if (!in_array($method, ['text', 'checkbox', 'textarea', 'select', 'group'])) {
  55. throw new BadMethodCallException("Method [$method] does not exist on option form.");
  56. }
  57. // assign name for option item
  58. if (!isset($params[1]) || Arr::get($params, 1) == OptionForm::AUTO_DETECT) {
  59. $params[1] = Arr::get(trans("options.$this->id.$params[0]"), 'title', trans("options.$this->id.$params[0]"));
  60. }
  61. $class = new ReflectionClass('app\common\services\OptionForm'.Str::title($method));
  62. // use ReflectionClass to create a new OptionFormItem instance
  63. $item = $class->newInstanceArgs($params);
  64. $item->setParentId($this->id);
  65. $this->items[] = $item;
  66. return $item;
  67. }
  68. /**
  69. * Set the box type of option form.
  70. *
  71. * @param string $type
  72. * @return $this
  73. */
  74. public function type($type)
  75. {
  76. $this->type = $type;
  77. return $this;
  78. }
  79. /**
  80. * Add a hint to option form.
  81. *
  82. * @param array $info
  83. * @return $this
  84. */
  85. public function hint($hintContent = self::AUTO_DETECT)
  86. {
  87. if ($hintContent == self::AUTO_DETECT) {
  88. $hintContent = trans("options.$this->id.hint");
  89. }
  90. $this->hint = view('vendor.option-form.hint')->with('hint', $hintContent)->render();
  91. return $this;
  92. }
  93. /**
  94. * Add a piece of data to the option form.
  95. *
  96. * @param string|array $key
  97. * @param mixed $value
  98. * @return $this
  99. */
  100. public function with($key, $value = null)
  101. {
  102. if (is_array($key)) {
  103. $this->values = array_merge($this->values, $key);
  104. } else {
  105. $this->values[$key] = $value;
  106. }
  107. return $this;
  108. }
  109. /**
  110. * Add a button at the footer of option form.
  111. *
  112. * @param array $info
  113. * @return $this
  114. */
  115. public function addButton(array $info)
  116. {
  117. $info = array_merge([
  118. 'style' => 'default',
  119. 'class' => [],
  120. 'href' => '',
  121. 'text' => 'BUTTON',
  122. 'type' => 'button',
  123. 'name' => ''
  124. ], $info);
  125. $classes = "btn btn-{$info['style']} ".implode(' ', (array) Arr::get($info, 'class'));
  126. if ($info['href']) {
  127. $this->buttons[] = "<a href='{$info['href']}' class='$classes'>{$info['text']}</a>";
  128. } else {
  129. $this->buttons[] = "<button type='{$info['type']}' name='{$info['name']}' class='$classes'>{$info['text']}</button>";
  130. }
  131. return $this;
  132. }
  133. /**
  134. * Add a message to the top of option form.
  135. *
  136. * @param string $msg
  137. * @param string $style
  138. * @return $this
  139. */
  140. public function addMessage($msg = self::AUTO_DETECT, $style = "info")
  141. {
  142. if ($msg == self::AUTO_DETECT) {
  143. $msg = trans("options.$this->id.message");
  144. }
  145. $this->messages[] = "<div class='callout callout-$style'>$msg</div>";
  146. return $this;
  147. }
  148. /**
  149. * Add callback which will be always executed.
  150. *
  151. * @param callable $callback
  152. * @return $this
  153. */
  154. public function always(callable $callback)
  155. {
  156. $this->alwaysCallback = $callback;
  157. return $this;
  158. }
  159. /**
  160. * Parse id formatted as *[*]. Return id & offset when succeed.
  161. *
  162. * @param string $id
  163. * @return bool|array
  164. */
  165. protected function parseIdWithOffset($id)
  166. {
  167. preg_match('/(.*)\[(.*)\]/', $id, $matches);
  168. if (isset($matches[2])) {
  169. return [
  170. 'id' => $matches[1],
  171. 'offset' => $matches[2]
  172. ];
  173. }
  174. return false;
  175. }
  176. /**
  177. * Handle the HTTP post request and update modified options.
  178. *
  179. * @param callable $callback
  180. * @return $this
  181. */
  182. public function handle(callable $callback = null)
  183. {
  184. if (Arr::get($_POST, 'option') == $this->id) {
  185. if (!is_null($callback)) {
  186. call_user_func($callback, $this);
  187. }
  188. $postOptionQueue = [];
  189. $arrayOptionQueue = [];
  190. foreach ($this->items as $item) {
  191. if ($item instanceof OptionFormGroup) {
  192. foreach ($item->items as $innerItem) {
  193. if ($innerItem['type'] == "text") {
  194. $postOptionQueue[] = new OptionFormText($innerItem['id']);
  195. }
  196. }
  197. continue;
  198. }
  199. // push item to the queue
  200. $postOptionQueue[] = $item;
  201. }
  202. foreach ($postOptionQueue as $item) {
  203. if ($item instanceof OptionFormCheckbox && !isset($_POST[$item->id])) {
  204. // preset value for checkboxes which are not checked
  205. $_POST[$item->id] = "false";
  206. }
  207. // Str::is('*[*]', $item->id)
  208. if (false !== ($result = $this->parseIdWithOffset($item->id))) {
  209. // Push array option value to cache.
  210. // Values of post ids like *[*] is collected as arrays in $_POST
  211. // automatically by Laravel.
  212. $arrayOptionQueue[$result['id']] = $_POST[$result['id']];
  213. continue;
  214. }
  215. // compare with raw option value
  216. if (($data = Arr::get($_POST, $item->id)) != option($item->id, null, true)) {
  217. Option::set($item->id, $data);
  218. }
  219. }
  220. foreach ($arrayOptionQueue as $key => $value) {
  221. Option::set($key, serialize($value));
  222. }
  223. $this->addMessage(trans('options.option-saved'), 'success');
  224. }
  225. return $this;
  226. }
  227. /**
  228. * Load value from $this->values & options by given id.
  229. *
  230. * @param string $id
  231. * @return mixed
  232. */
  233. protected function getValueById($id)
  234. {
  235. if (false === ($result = $this->parseIdWithOffset($id))) {
  236. return Arr::get($this->values, $id, option($id));
  237. } else {
  238. $option = Arr::get(
  239. $this->values,
  240. $result['id'],
  241. // fallback to load from options
  242. @unserialize(option($result['id']))
  243. );
  244. return Arr::get($option, $result['offset']);
  245. }
  246. }
  247. /**
  248. * Assign value for option items whose value haven't been set.
  249. *
  250. * @return void
  251. */
  252. protected function assignValues()
  253. {
  254. // load values for items if not set manually
  255. foreach ($this->items as $item) {
  256. if ($item instanceof OptionFormGroup) {
  257. foreach ($item->items as &$groupItem) {
  258. if ($groupItem['id'] && is_null($groupItem['value'])) {
  259. $groupItem['value'] = $this->getValueById($groupItem['id']);
  260. }
  261. }
  262. continue;
  263. }
  264. if (is_null($item->value)) {
  265. $item->value = $this->getValueById($item->id);
  266. }
  267. }
  268. }
  269. public function renderWithOutTable()
  270. {
  271. $this->renderWithOutTable = true;
  272. return $this;
  273. }
  274. public function renderInputTagsOnly()
  275. {
  276. $this->renderInputTagsOnly = true;
  277. return $this;
  278. }
  279. public function renderWithOutSubmitButton()
  280. {
  281. $this->renderWithOutSubmitButton = true;
  282. return $this;
  283. }
  284. /**
  285. * Get the string contents of the option form.
  286. *
  287. * @return string
  288. */
  289. public function render()
  290. {
  291. if (!is_null($this->alwaysCallback)) {
  292. call_user_func($this->alwaysCallback, $this);
  293. }
  294. // attach submit button to the form
  295. if (!$this->renderWithOutSubmitButton) {
  296. $this->addButton([
  297. 'style' => 'primary',
  298. 'text' => trans('general.submit'),
  299. 'type' => 'submit',
  300. 'name' => 'submit'
  301. ]);
  302. }
  303. $this->assignValues();
  304. return view('vendor.option-form.main')->with(array_merge(get_object_vars($this)))->render();
  305. }
  306. /**
  307. * Get the string contents of the option form.
  308. *
  309. * @return string
  310. */
  311. public function __toString()
  312. {
  313. return $this->render();
  314. }
  315. }
  316. class OptionFormItem
  317. {
  318. public $id;
  319. public $name;
  320. public $hint;
  321. public $value = null;
  322. public $disabled;
  323. public $description;
  324. protected $parentId;
  325. public function __construct($id, $name = null)
  326. {
  327. $this->id = $id;
  328. $this->name = $name;
  329. }
  330. public function setParentId($id)
  331. {
  332. $this->parentId = $id;
  333. return $this;
  334. }
  335. public function value($value)
  336. {
  337. $this->value = $value;
  338. return $this;
  339. }
  340. public function hint($hintContent = OptionForm::AUTO_DETECT)
  341. {
  342. if ($hintContent == OptionForm::AUTO_DETECT) {
  343. $hintContent = trans("options.$this->parentId.$this->id.hint");
  344. }
  345. $this->hint = view('vendor.option-form.hint')->with('hint', $hintContent)->render();
  346. return $this;
  347. }
  348. public function disabled($disabled = "disabled")
  349. {
  350. $this->disabled = "disabled=\"$disabled\"";
  351. return $this;
  352. }
  353. public function description($description = OptionForm::AUTO_DETECT)
  354. {
  355. if ($description == OptionForm::AUTO_DETECT) {
  356. $description = trans("options.$this->parentId.$this->id.description");
  357. }
  358. $this->description = $description;
  359. return $this;
  360. }
  361. /**
  362. * Render option item. Should be extended.
  363. *
  364. * @return \Illuminate\View\View|string
  365. */
  366. public function render()
  367. {
  368. return;
  369. }
  370. }
  371. class OptionFormText extends OptionFormItem
  372. {
  373. public function render()
  374. {
  375. return view('vendor.option-form.text')->with([
  376. 'id' => $this->id,
  377. 'value' => $this->value,
  378. 'disabled' => $this->disabled
  379. ]);
  380. }
  381. }
  382. class OptionFormCheckbox extends OptionFormItem
  383. {
  384. protected $label;
  385. public function label($label = OptionForm::AUTO_DETECT)
  386. {
  387. if ($label == OptionForm::AUTO_DETECT) {
  388. $label = trans("options.$this->parentId.$this->id.label");
  389. }
  390. $this->label = $label;
  391. return $this;
  392. }
  393. public function render()
  394. {
  395. return view('vendor.option-form.checkbox')->with([
  396. 'id' => $this->id,
  397. 'value' => $this->value,
  398. 'label' => $this->label,
  399. 'disabled' => $this->disabled
  400. ]);
  401. }
  402. }
  403. class OptionFormTextarea extends OptionFormItem
  404. {
  405. protected $rows = 3;
  406. public function rows($rows)
  407. {
  408. $this->rows = $rows;
  409. return $this;
  410. }
  411. public function render()
  412. {
  413. return view('vendor.option-form.textarea')->with([
  414. 'id' => $this->id,
  415. 'rows' => $this->rows,
  416. 'value' => $this->value,
  417. 'disabled' => $this->disabled
  418. ]);
  419. }
  420. }
  421. class OptionFormSelect extends OptionFormItem
  422. {
  423. protected $options;
  424. public function option($value, $name)
  425. {
  426. $this->options[] = compact('value', 'name');
  427. return $this;
  428. }
  429. public function render()
  430. {
  431. return view('vendor.option-form.select')->with([
  432. 'id' => $this->id,
  433. 'options' => $this->options,
  434. 'selected' => $this->value,
  435. 'disabled' => $this->disabled
  436. ]);
  437. }
  438. }
  439. class OptionFormGroup extends OptionFormItem
  440. {
  441. public $items = [];
  442. public function text($id, $value = null)
  443. {
  444. $this->items[] = ['type' => 'text', 'id' => $id, 'value' => $value];
  445. return $this;
  446. }
  447. public function addon($value = OptionForm::AUTO_DETECT)
  448. {
  449. if ($value == OptionForm::AUTO_DETECT) {
  450. $value = trans("options.$this->parentId.$this->id.addon");
  451. }
  452. $this->items[] = ['type' => 'addon', 'id' => null, 'value' => $value];
  453. return $this;
  454. }
  455. public function render()
  456. {
  457. $rendered = [];
  458. foreach ($this->items as $item) {
  459. if ($item['id'] && is_null($item['value'])) {
  460. $item['value'] = option($item['id']);
  461. }
  462. $rendered[] = view('vendor.option-form.'.$item['type'])->with([
  463. 'id' => $item['id'],
  464. 'value' => $item['value']
  465. ]);
  466. }
  467. return view('vendor.option-form.group')->with('items', $rendered);
  468. }
  469. }