/web/concrete/models/attribute/types/select/controller.php

https://github.com/shin2/concrete5 · PHP · 586 lines · 491 code · 74 blank · 21 comment · 84 complexity · f259896ba9d3fde3450f3d63289e654d MD5 · raw file

  1. <?php defined('C5_EXECUTE') or die("Access Denied.");
  2. class SelectAttributeTypeController extends AttributeTypeController {
  3. private $akSelectAllowMultipleValues;
  4. private $akSelectAllowOtherValues;
  5. private $akSelectOptionDisplayOrder;
  6. protected $searchIndexFieldDefinition = 'X NULL';
  7. public function type_form() {
  8. $path1 = $this->getView()->getAttributeTypeURL('type_form.js');
  9. $path2 = $this->getView()->getAttributeTypeURL('type_form.css');
  10. $this->addHeaderItem(Loader::helper('html')->javascript($path1));
  11. $this->addHeaderItem(Loader::helper('html')->css($path2));
  12. $this->set('form', Loader::helper('form'));
  13. $this->load();
  14. //$akSelectValues = $this->getSelectValuesFromPost();
  15. //$this->set('akSelectValues', $akSelectValues);
  16. if ($this->isPost()) {
  17. $akSelectValues = $this->getSelectValuesFromPost();
  18. $this->set('akSelectValues', $akSelectValues);
  19. } else if (isset($this->attributeKey)) {
  20. $options = $this->getOptions();
  21. $this->set('akSelectValues', $options);
  22. } else {
  23. $this->set('akSelectValues', array());
  24. }
  25. }
  26. protected function load() {
  27. $ak = $this->getAttributeKey();
  28. if (!is_object($ak)) {
  29. return false;
  30. }
  31. $db = Loader::db();
  32. $row = $db->GetRow('select akSelectAllowMultipleValues, akSelectOptionDisplayOrder, akSelectAllowOtherValues from atSelectSettings where akID = ?', $ak->getAttributeKeyID());
  33. $this->akSelectAllowMultipleValues = $row['akSelectAllowMultipleValues'];
  34. $this->akSelectAllowOtherValues = $row['akSelectAllowOtherValues'];
  35. $this->akSelectOptionDisplayOrder = $row['akSelectOptionDisplayOrder'];
  36. $this->set('akSelectAllowMultipleValues', $this->akSelectAllowMultipleValues);
  37. $this->set('akSelectAllowOtherValues', $this->akSelectAllowOtherValues);
  38. $this->set('akSelectOptionDisplayOrder', $this->akSelectOptionDisplayOrder);
  39. }
  40. public function duplicateKey($newAK) {
  41. $this->load();
  42. $db = Loader::db();
  43. $db->Execute('insert into atSelectSettings (akID, akSelectAllowMultipleValues, akSelectOptionDisplayOrder, akSelectAllowOtherValues) values (?, ?, ?, ?)', array($newAK->getAttributeKeyID(), $this->akSelectAllowMultipleValues, $this->akSelectOptionDisplayOrder, $this->akSelectAllowOtherValues));
  44. $r = $db->Execute('select value, displayOrder, isEndUserAdded from atSelectOptions where akID = ?', $this->getAttributeKey()->getAttributeKeyID());
  45. while ($row = $r->FetchRow()) {
  46. $db->Execute('insert into atSelectOptions (akID, value, displayOrder, isEndUserAdded) values (?, ?, ?, ?)', array(
  47. $newAK->getAttributeKeyID(),
  48. $row['value'],
  49. $row['displayOrder'],
  50. $row['isEndUserAdded']
  51. ));
  52. }
  53. }
  54. private function getSelectValuesFromPost() {
  55. $options = new SelectAttributeTypeOptionList();
  56. $displayOrder = 0;
  57. foreach($_POST as $key => $value) {
  58. if( !strstr($key,'akSelectValue_') || $value=='TEMPLATE' ) continue;
  59. $opt = false;
  60. // strip off the prefix to get the ID
  61. $id = substr($key, 14);
  62. // now we determine from the post whether this is a new option
  63. // or an existing. New ones have this value from in the akSelectValueNewOption_ post field
  64. if ($_POST['akSelectValueNewOption_' . $id] == $id) {
  65. $opt = new SelectAttributeTypeOption(0, $value, $displayOrder);
  66. $opt->tempID = $id;
  67. } else if ($_POST['akSelectValueExistingOption_' . $id] == $id) {
  68. $opt = new SelectAttributeTypeOption($id, $value, $displayOrder);
  69. }
  70. if (is_object($opt)) {
  71. $options->add($opt);
  72. $displayOrder++;
  73. }
  74. }
  75. return $options;
  76. }
  77. public function form() {
  78. $this->load();
  79. $options = $this->getSelectedOptions();
  80. $selectedOptions = array();
  81. foreach($options as $opt) {
  82. $selectedOptions[] = $opt->getSelectAttributeOptionID();
  83. $selectedOptionValues[$opt->getSelectAttributeOptionID()] = $opt->getSelectAttributeOptionValue();
  84. }
  85. $this->set('selectedOptionValues',$selectedOptionValues);
  86. $this->set('selectedOptions', $selectedOptions);
  87. $this->addHeaderItem(Loader::helper('html')->javascript('jquery.ui.js'));
  88. $this->addHeaderItem(Loader::helper('html')->css('jquery.ui.css'));
  89. }
  90. public function search() {
  91. $this->load();
  92. $selectedOptions = $this->request('atSelectOptionID');
  93. if (!is_array($selectedOptions)) {
  94. $selectedOptions = array();
  95. }
  96. $this->set('selectedOptions', $selectedOptions);
  97. }
  98. public function deleteValue() {
  99. $db = Loader::db();
  100. $db->Execute('delete from atSelectOptionsSelected where avID = ?', array($this->getAttributeValueID()));
  101. }
  102. public function deleteKey() {
  103. $db = Loader::db();
  104. $db->Execute('delete from atSelectSettings where akID = ?', array($this->attributeKey->getAttributeKeyID()));
  105. $r = $db->Execute('select ID from atSelectOptions where akID = ?', array($this->attributeKey->getAttributeKeyID()));
  106. while ($row = $r->FetchRow()) {
  107. $db->Execute('delete from atSelectOptionsSelected where atSelectOptionID = ?', array($row['ID']));
  108. }
  109. $db->Execute('delete from atSelectOptions where akID = ?', array($this->attributeKey->getAttributeKeyID()));
  110. }
  111. public function saveForm($data) {
  112. $this->load();
  113. if ($this->akSelectAllowOtherValues && is_array($data['atSelectNewOption'])) {
  114. $options = $this->getOptions();
  115. foreach($data['atSelectNewOption'] as $newoption) {
  116. // check for duplicates
  117. $existing = false;
  118. foreach($options as $opt) {
  119. if(strtolower(trim($newoption)) == strtolower(trim($opt->getSelectAttributeOptionValue()))) {
  120. $existing = $opt;
  121. break;
  122. }
  123. }
  124. if($existing instanceof SelectAttributeTypeOption) {
  125. $data['atSelectOptionID'][] = $existing->getSelectAttributeOptionID();
  126. } else {
  127. $optobj = SelectAttributeTypeOption::add($this->attributeKey, $newoption, 1);
  128. $data['atSelectOptionID'][] = $optobj->getSelectAttributeOptionID();
  129. }
  130. }
  131. }
  132. if(is_array($data['atSelectOptionID'])) {
  133. $data['atSelectOptionID'] = array_unique($data['atSelectOptionID']);
  134. }
  135. $db = Loader::db();
  136. $db->Execute('delete from atSelectOptionsSelected where avID = ?', array($this->getAttributeValueID()));
  137. if (is_array($data['atSelectOptionID'])) {
  138. foreach($data['atSelectOptionID'] as $optID) {
  139. if ($optID > 0) {
  140. $db->Execute('insert into atSelectOptionsSelected (avID, atSelectOptionID) values (?, ?)', array($this->getAttributeValueID(), $optID));
  141. if ($this->akSelectAllowMultipleValues == false) {
  142. break;
  143. }
  144. }
  145. }
  146. }
  147. }
  148. // Sets select options for a particular attribute
  149. // If the $value == string, then 1 item is selected
  150. // if array, then multiple, but only if the attribute in question is a select multiple
  151. // Note, items CANNOT be added to the pool (even if the attribute allows it) through this process.
  152. public function saveValue($value) {
  153. $db = Loader::db();
  154. $this->load();
  155. $options = array();
  156. if (is_array($value) && $this->akSelectAllowMultipleValues) {
  157. foreach($value as $v) {
  158. $opt = SelectAttributeTypeOption::getByValue($v, $this->attributeKey);
  159. if (is_object($opt)) {
  160. $options[] = $opt;
  161. }
  162. }
  163. } else {
  164. if (is_array($value)) {
  165. $value = $value[0];
  166. }
  167. $opt = SelectAttributeTypeOption::getByValue($value, $this->attributeKey);
  168. if (is_object($opt)) {
  169. $options[] = $opt;
  170. }
  171. }
  172. $db->Execute('delete from atSelectOptionsSelected where avID = ?', array($this->getAttributeValueID()));
  173. if (count($options) > 0) {
  174. foreach($options as $opt) {
  175. $db->Execute('insert into atSelectOptionsSelected (avID, atSelectOptionID) values (?, ?)', array($this->getAttributeValueID(), $opt->getSelectAttributeOptionID()));
  176. if ($this->akSelectAllowMultipleValues == false) {
  177. break;
  178. }
  179. }
  180. }
  181. }
  182. public function getDisplayValue() {
  183. $list = $this->getSelectedOptions();
  184. $html = '';
  185. foreach($list as $l) {
  186. $html .= $l . '<br/>';
  187. }
  188. return $html;
  189. }
  190. public function getDisplaySanitizedValue() {
  191. $list = $this->getSelectedOptions();
  192. $html = '';
  193. foreach($list as $l) {
  194. $html .= $l->getSelectAttributeOptionValue() . '<br/>';
  195. }
  196. return $html;
  197. }
  198. public function validateForm($p) {
  199. $this->load();
  200. $options = $this->request('atSelectOptionID');
  201. if ($this->akSelectAllowMultipleValues) {
  202. return count($options) > 0;
  203. } else {
  204. if ($options[0] != false) {
  205. return $options[0] > 0;
  206. }
  207. }
  208. return false;
  209. }
  210. public function searchForm($list) {
  211. $options = $this->request('atSelectOptionID');
  212. $optionText = array();
  213. $db = Loader::db();
  214. $tbl = $this->attributeKey->getIndexedSearchTable();
  215. if (!is_array($options)) {
  216. return $list;
  217. }
  218. foreach($options as $id) {
  219. if ($id > 0) {
  220. $opt = SelectAttributeTypeOption::getByID($id);
  221. if (is_object($opt)) {
  222. $optionText[] = $opt->getSelectAttributeOptionValue(true);
  223. $optionQuery[] = $opt->getSelectAttributeOptionValue(false);
  224. }
  225. }
  226. }
  227. if (count($optionText) == 0) {
  228. return false;
  229. }
  230. $i = 0;
  231. foreach($optionQuery as $val) {
  232. $val = $db->quote('%||' . $val . '||%');
  233. $multiString .= 'REPLACE(' . $tbl . '.ak_' . $this->attributeKey->getAttributeKeyHandle() . ', "\n", "||") like ' . $val . ' ';
  234. if (($i + 1) < count($optionQuery)) {
  235. $multiString .= 'OR ';
  236. }
  237. $i++;
  238. }
  239. $list->filter(false, '(' . $multiString . ')');
  240. return $list;
  241. }
  242. public function getValue() {
  243. $list = $this->getSelectedOptions();
  244. return $list;
  245. }
  246. public function getSearchIndexValue() {
  247. $str = "\n";
  248. $list = $this->getSelectedOptions();
  249. foreach($list as $l) {
  250. $l = (is_object($l) && method_exists($l,'__toString')) ? $l->__toString() : $l;
  251. $str .= $l . "\n";
  252. }
  253. return $str;
  254. }
  255. public function getSelectedOptions() {
  256. if (!isset($this->akSelectOptionDisplayOrder)) {
  257. $this->load();
  258. }
  259. $db = Loader::db();
  260. switch($this->akSelectOptionDisplayOrder) {
  261. case 'popularity_desc':
  262. $options = $db->GetAll("select ID, value, displayOrder, (select count(s2.atSelectOptionID) from atSelectOptionsSelected s2 where s2.atSelectOptionID = ID) as total from atSelectOptionsSelected inner join atSelectOptions on atSelectOptionsSelected.atSelectOptionID = atSelectOptions.ID where avID = ? order by total desc, value asc", array($this->getAttributeValueID()));
  263. break;
  264. case 'alpha_asc':
  265. $options = $db->GetAll("select ID, value, displayOrder from atSelectOptionsSelected inner join atSelectOptions on atSelectOptionsSelected.atSelectOptionID = atSelectOptions.ID where avID = ? order by value asc", array($this->getAttributeValueID()));
  266. break;
  267. default:
  268. $options = $db->GetAll("select ID, value, displayOrder from atSelectOptionsSelected inner join atSelectOptions on atSelectOptionsSelected.atSelectOptionID = atSelectOptions.ID where avID = ? order by displayOrder asc", array($this->getAttributeValueID()));
  269. break;
  270. }
  271. $db = Loader::db();
  272. $list = new SelectAttributeTypeOptionList();
  273. foreach($options as $row) {
  274. $opt = new SelectAttributeTypeOption($row['ID'], $row['value'], $row['displayOrder']);
  275. $list->add($opt);
  276. }
  277. return $list;
  278. }
  279. public function action_load_autocomplete_values() {
  280. $this->load();
  281. $values = array();
  282. // now, if the current instance of the attribute key allows us to do autocomplete, we return all the values
  283. if ($this->akSelectAllowMultipleValues && $this->akSelectAllowOtherValues) {
  284. $options = $this->getOptions($_GET['term'] . '%');
  285. foreach($options as $opt) {
  286. $values[] = $opt->getSelectAttributeOptionValue();
  287. }
  288. }
  289. print Loader::helper('json')->encode($values);
  290. }
  291. public function getOptionUsageArray($parentPage = false, $limit = 9999) {
  292. $db = Loader::db();
  293. $q = "select atSelectOptions.value, atSelectOptionID, count(atSelectOptionID) as total from Pages inner join CollectionVersions on (Pages.cID = CollectionVersions.cID and CollectionVersions.cvIsApproved = 1) inner join CollectionAttributeValues on (CollectionVersions.cID = CollectionAttributeValues.cID and CollectionVersions.cvID = CollectionAttributeValues.cvID) inner join atSelectOptionsSelected on (atSelectOptionsSelected.avID = CollectionAttributeValues.avID) inner join atSelectOptions on atSelectOptionsSelected.atSelectOptionID = atSelectOptions.ID where CollectionAttributeValues.akID = ? ";
  294. $v = array($this->attributeKey->getAttributeKeyID());
  295. if (is_object($parentPage)) {
  296. $v[] = $parentPage->getCollectionID();
  297. $q .= "and cParentID = ?";
  298. }
  299. $q .= " group by atSelectOptionID order by total desc limit " . $limit;
  300. $r = $db->Execute($q, $v);
  301. $list = new SelectAttributeTypeOptionList();
  302. $i = 0;
  303. while ($row = $r->FetchRow()) {
  304. $opt = new SelectAttributeTypeOption($row['atSelectOptionID'], $row['value'], $i, $row['total']);
  305. $list->add($opt);
  306. $i++;
  307. }
  308. return $list;
  309. }
  310. /**
  311. * returns a list of available options optionally filtered by an sql $like statement ex: startswith%
  312. * @param string $like
  313. * @return SelectAttributeTypeOptionList
  314. */
  315. public function getOptions($like = NULL) {
  316. if (!isset($this->akSelectOptionDisplayOrder)) {
  317. $this->load();
  318. }
  319. $db = Loader::db();
  320. switch($this->akSelectOptionDisplayOrder) {
  321. case 'popularity_desc':
  322. if(isset($like) && strlen($like)) {
  323. $r = $db->Execute('select ID, value, displayOrder, count(atSelectOptionsSelected.atSelectOptionID) as total
  324. from atSelectOptions left join atSelectOptionsSelected on (atSelectOptions.ID = atSelectOptionsSelected.atSelectOptionID)
  325. where akID = ? AND atSelectOptions.value LIKE ? group by ID order by total desc, value asc', array($this->attributeKey->getAttributeKeyID(),$like));
  326. } else {
  327. $r = $db->Execute('select ID, value, displayOrder, count(atSelectOptionsSelected.atSelectOptionID) as total
  328. from atSelectOptions left join atSelectOptionsSelected on (atSelectOptions.ID = atSelectOptionsSelected.atSelectOptionID)
  329. where akID = ? group by ID order by total desc, value asc', array($this->attributeKey->getAttributeKeyID()));
  330. }
  331. break;
  332. case 'alpha_asc':
  333. if(isset($like) && strlen($like)) {
  334. $r = $db->Execute('select ID, value, displayOrder from atSelectOptions where akID = ? AND atSelectOptions.value LIKE ? order by value asc', array($this->attributeKey->getAttributeKeyID(),$like));
  335. } else {
  336. $r = $db->Execute('select ID, value, displayOrder from atSelectOptions where akID = ? order by value asc', array($this->attributeKey->getAttributeKeyID()));
  337. }
  338. break;
  339. default:
  340. if(isset($like) && strlen($like)) {
  341. $r = $db->Execute('select ID, value, displayOrder from atSelectOptions where akID = ? AND atSelectOptions.value LIKE ? order by displayOrder asc', array($this->attributeKey->getAttributeKeyID(),$like));
  342. } else {
  343. $r = $db->Execute('select ID, value, displayOrder from atSelectOptions where akID = ? order by displayOrder asc', array($this->attributeKey->getAttributeKeyID()));
  344. }
  345. break;
  346. }
  347. $options = new SelectAttributeTypeOptionList();
  348. while ($row = $r->FetchRow()) {
  349. $opt = new SelectAttributeTypeOption($row['ID'], $row['value'], $row['displayOrder']);
  350. $options->add($opt);
  351. }
  352. return $options;
  353. }
  354. public function saveKey($data) {
  355. $ak = $this->getAttributeKey();
  356. $db = Loader::db();
  357. $initialOptionSet = $this->getOptions();
  358. $selectedPostValues = $this->getSelectValuesFromPost();
  359. $akSelectAllowMultipleValues = $data['akSelectAllowMultipleValues'];
  360. $akSelectAllowOtherValues = $data['akSelectAllowOtherValues'];
  361. $akSelectOptionDisplayOrder = $data['akSelectOptionDisplayOrder'];
  362. if ($data['akSelectAllowMultipleValues'] != 1) {
  363. $akSelectAllowMultipleValues = 0;
  364. }
  365. if ($data['akSelectAllowOtherValues'] != 1) {
  366. $akSelectAllowOtherValues = 0;
  367. }
  368. if (!in_array($data['akSelectOptionDisplayOrder'], array('display_asc', 'alpha_asc', 'popularity_desc'))) {
  369. $akSelectOptionDisplayOrder = 'display_asc';
  370. }
  371. // now we have a collection attribute key object above.
  372. $db->Replace('atSelectSettings', array(
  373. 'akID' => $ak->getAttributeKeyID(),
  374. 'akSelectAllowMultipleValues' => $akSelectAllowMultipleValues,
  375. 'akSelectAllowOtherValues' => $akSelectAllowOtherValues,
  376. 'akSelectOptionDisplayOrder' => $akSelectOptionDisplayOrder
  377. ), array('akID'), true);
  378. // Now we add the options
  379. $newOptionSet = new SelectAttributeTypeOptionList();
  380. $displayOrder = 0;
  381. foreach($selectedPostValues as $option) {
  382. $opt = $option->saveOrCreate($ak);
  383. if ($akSelectOptionDisplayOrder == 'display_asc') {
  384. $opt->setDisplayOrder($displayOrder);
  385. }
  386. $newOptionSet->add($opt);
  387. $displayOrder++;
  388. }
  389. // Now we remove all options that appear in the
  390. // old values list but not in the new
  391. foreach($initialOptionSet as $iopt) {
  392. if (!$newOptionSet->contains($iopt)) {
  393. $iopt->delete();
  394. }
  395. }
  396. }
  397. }
  398. class SelectAttributeTypeOption extends Object {
  399. public function __construct($ID, $value, $displayOrder, $usageCount = false) {
  400. $this->ID = $ID;
  401. $this->value = $value;
  402. $this->th = Loader::helper('text');
  403. $this->displayOrder = $displayOrder;
  404. $this->usageCount = $usageCount;
  405. }
  406. public function getSelectAttributeOptionID() {return $this->ID;}
  407. public function getSelectAttributeOptionUsageCount() {return $this->usageCount;}
  408. public function getSelectAttributeOptionValue($sanitize = true) {
  409. if (!$sanitize) {
  410. return $this->value;
  411. } else {
  412. return $this->th->entities($this->value);
  413. }
  414. }
  415. public function getSelectAttributeOptionDisplayOrder() {return $this->displayOrder;}
  416. public function getSelectAttributeOptionTemporaryID() {return $this->tempID;}
  417. public function __toString() {return $this->value;}
  418. public static function add($ak, $option, $isEndUserAdded = 0) {
  419. $db = Loader::db();
  420. // this works because displayorder starts at zero. So if there are three items, for example, the display order of the NEXT item will be 3.
  421. $displayOrder = $db->GetOne('select count(ID) from atSelectOptions where akID = ?', array($ak->getAttributeKeyID()));
  422. $v = array($ak->getAttributeKeyID(), $displayOrder, $option, $isEndUserAdded);
  423. $db->Execute('insert into atSelectOptions (akID, displayOrder, value, isEndUserAdded) values (?, ?, ?, ?)', $v);
  424. return SelectAttributeTypeOption::getByID($db->Insert_ID());
  425. }
  426. public function setDisplayOrder($num) {
  427. $db = Loader::db();
  428. $db->Execute('update atSelectOptions set displayOrder = ? where ID = ?', array($num, $this->ID));
  429. }
  430. public static function getByID($id) {
  431. $db = Loader::db();
  432. $row = $db->GetRow("select ID, displayOrder, value from atSelectOptions where ID = ?", array($id));
  433. if (isset($row['ID'])) {
  434. $obj = new SelectAttributeTypeOption($row['ID'], $row['value'], $row['displayOrder']);
  435. return $obj;
  436. }
  437. }
  438. public static function getByValue($value, $ak = false) {
  439. $db = Loader::db();
  440. if (is_object($ak)) {
  441. $row = $db->GetRow("select ID, displayOrder, value from atSelectOptions where value = ? and akID = ?", array($value, $ak->getAttributeKeyID()));
  442. } else {
  443. $row = $db->GetRow("select ID, displayOrder, value from atSelectOptions where value = ?", array($value));
  444. }
  445. if (isset($row['ID'])) {
  446. $obj = new SelectAttributeTypeOption($row['ID'], $row['value'], $row['displayOrder']);
  447. return $obj;
  448. }
  449. }
  450. public function delete() {
  451. $db = Loader::db();
  452. $db->Execute('delete from atSelectOptions where ID = ?', array($this->ID));
  453. $db->Execute('delete from atSelectOptionsSelected where atSelectOptionID = ?', array($this->ID));
  454. }
  455. public function saveOrCreate($ak) {
  456. if ($this->tempID != false || $this->ID==0) {
  457. return SelectAttributeTypeOption::add($ak, $this->value);
  458. } else {
  459. $db = Loader::db();
  460. $db->Execute('update atSelectOptions set value = ? where ID = ?', array($this->value, $this->ID));
  461. return SelectAttributeTypeOption::getByID($this->ID);
  462. }
  463. }
  464. }
  465. class SelectAttributeTypeOptionList extends Object implements Iterator {
  466. private $options = array();
  467. public function add(SelectAttributeTypeOption $opt) {
  468. $this->options[] = $opt;
  469. }
  470. public function rewind() {
  471. reset($this->options);
  472. }
  473. public function current() {
  474. return current($this->options);
  475. }
  476. public function key() {
  477. return key($this->options);
  478. }
  479. public function next() {
  480. next($this->options);
  481. }
  482. public function valid() {
  483. return $this->current() !== false;
  484. }
  485. public function count() {return count($this->options);}
  486. public function contains(SelectAttributeTypeOption $opt) {
  487. foreach($this->options as $o) {
  488. if ($o->getSelectAttributeOptionID() == $opt->getSelectAttributeOptionID()) {
  489. return true;
  490. }
  491. }
  492. return false;
  493. }
  494. public function get($index) {
  495. return $this->options[$index];
  496. }
  497. public function getOptions() {
  498. return $this->options;
  499. }
  500. public function __toString() {
  501. $str = '';
  502. $i = 0;
  503. foreach($this->options as $opt) {
  504. $str .= $opt->getSelectAttributeOptionValue();
  505. $i++;
  506. if ($i < count($this->options)) {
  507. $str .= "\n";
  508. }
  509. }
  510. return $str;
  511. }
  512. }