website.class.php 54 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598
  1. <?php
  2. /* Copyright (C) 2007-2018 Laurent Destailleur <eldy@users.sourceforge.net>
  3. * Copyright (C) 2014 Juanjo Menent <jmenent@2byte.es>
  4. * Copyright (C) 2015 Florian Henry <florian.henry@open-concept.pro>
  5. * Copyright (C) 2015 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
  6. * Copyright (C) 2018 Frédéric France <frederic.france@netlogic.fr>
  7. *
  8. * This program is free software; you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation; either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. */
  21. /**
  22. * \file htdocs/website/class/website.class.php
  23. * \ingroup website
  24. * \brief File for the CRUD class of website (Create/Read/Update/Delete)
  25. */
  26. // Put here all includes required by your class file
  27. require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
  28. //require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php';
  29. //require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
  30. /**
  31. * Class Website
  32. */
  33. class Website extends CommonObject
  34. {
  35. /**
  36. * @var string Id to identify managed objects
  37. */
  38. public $element = 'website';
  39. /**
  40. * @var string Name of table without prefix where object is stored
  41. */
  42. public $table_element = 'website';
  43. /**
  44. * @var array Does website support multicompany module ? 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
  45. */
  46. public $ismultientitymanaged = 1;
  47. protected $childtablesoncascade = array();
  48. /**
  49. * @var string String with name of icon for website. Must be the part after the 'object_' into object_myobject.png
  50. */
  51. public $picto = 'globe';
  52. /**
  53. * @var int Entity
  54. */
  55. public $entity;
  56. /**
  57. * @var string Ref
  58. */
  59. public $ref;
  60. /**
  61. * @var string description
  62. */
  63. public $description;
  64. /**
  65. * @var string Main language of web site
  66. */
  67. public $lang;
  68. /**
  69. * @var string List of languages of web site ('fr', 'es_MX', ...)
  70. */
  71. public $otherlang;
  72. /**
  73. * @var int Status
  74. */
  75. public $status;
  76. /**
  77. * @var integer|string date_creation
  78. */
  79. public $date_creation;
  80. /**
  81. * @var integer|string date_modification
  82. */
  83. public $date_modification;
  84. /**
  85. * @var integer
  86. */
  87. public $fk_default_home;
  88. /**
  89. * @var int User Create Id
  90. */
  91. public $fk_user_creat;
  92. /**
  93. * @var string
  94. */
  95. public $virtualhost;
  96. /**
  97. * @var int
  98. */
  99. public $use_manifest;
  100. /**
  101. * @var int
  102. */
  103. public $position;
  104. /**
  105. * List of containers
  106. *
  107. * @var array
  108. */
  109. public $lines;
  110. const STATUS_DRAFT = 0;
  111. const STATUS_VALIDATED = 1;
  112. /**
  113. * Constructor
  114. *
  115. * @param DoliDb $db Database handler
  116. */
  117. public function __construct(DoliDB $db)
  118. {
  119. $this->db = $db;
  120. return 1;
  121. }
  122. /**
  123. * Create object into database
  124. *
  125. * @param User $user User that creates
  126. * @param bool $notrigger false=launch triggers after, true=disable triggers
  127. *
  128. * @return int <0 if KO, 0 if already exists, ID of created object if OK
  129. */
  130. public function create(User $user, $notrigger = false)
  131. {
  132. global $conf, $langs;
  133. dol_syslog(__METHOD__, LOG_DEBUG);
  134. $error = 0;
  135. $now = dol_now();
  136. // Clean parameters
  137. if (isset($this->entity)) {
  138. $this->entity = (int) $this->entity;
  139. }
  140. if (isset($this->ref)) {
  141. $this->ref = trim($this->ref);
  142. }
  143. if (isset($this->description)) {
  144. $this->description = trim($this->description);
  145. }
  146. if (isset($this->status)) {
  147. $this->status = (int) $this->status;
  148. }
  149. if (empty($this->date_creation)) {
  150. $this->date_creation = $now;
  151. }
  152. if (empty($this->date_modification)) {
  153. $this->date_modification = $now;
  154. }
  155. // Remove spaces and be sure we have main language only
  156. $this->lang = preg_replace('/[_-].*$/', '', trim($this->lang)); // en_US or en-US -> en
  157. $tmparray = explode(',', $this->otherlang);
  158. if (is_array($tmparray)) {
  159. foreach ($tmparray as $key => $val) {
  160. // It possible we have empty val here if postparam WEBSITE_OTHERLANG is empty or set like this : 'en,,sv' or 'en,sv,'
  161. if (empty(trim($val))) {
  162. unset($tmparray[$key]);
  163. continue;
  164. }
  165. $tmparray[$key] = preg_replace('/[_-].*$/', '', trim($val)); // en_US or en-US -> en
  166. }
  167. $this->otherlang = join(',', $tmparray);
  168. }
  169. // Check parameters
  170. if (empty($this->entity)) {
  171. $this->entity = $conf->entity;
  172. }
  173. if (empty($this->lang)) {
  174. $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("MainLanguage"));
  175. return -1;
  176. }
  177. // Insert request
  178. $sql = 'INSERT INTO '.MAIN_DB_PREFIX.$this->table_element.'(';
  179. $sql .= 'entity,';
  180. $sql .= 'ref,';
  181. $sql .= 'description,';
  182. $sql .= 'lang,';
  183. $sql .= 'otherlang,';
  184. $sql .= 'status,';
  185. $sql .= 'fk_default_home,';
  186. $sql .= 'virtualhost,';
  187. $sql .= 'fk_user_creat,';
  188. $sql .= 'date_creation,';
  189. $sql .= 'position,';
  190. $sql .= 'tms';
  191. $sql .= ') VALUES (';
  192. $sql .= ' '.((empty($this->entity) && $this->entity != '0') ? 'NULL' : $this->entity).',';
  193. $sql .= ' '.(!isset($this->ref) ? 'NULL' : "'".$this->db->escape($this->ref)."'").',';
  194. $sql .= ' '.(!isset($this->description) ? 'NULL' : "'".$this->db->escape($this->description)."'").',';
  195. $sql .= ' '.(!isset($this->lang) ? 'NULL' : "'".$this->db->escape($this->lang)."'").',';
  196. $sql .= ' '.(!isset($this->otherlang) ? 'NULL' : "'".$this->db->escape($this->otherlang)."'").',';
  197. $sql .= ' '.(!isset($this->status) ? '1' : $this->status).',';
  198. $sql .= ' '.(!isset($this->fk_default_home) ? 'NULL' : $this->fk_default_home).',';
  199. $sql .= ' '.(!isset($this->virtualhost) ? 'NULL' : "'".$this->db->escape($this->virtualhost)."'").",";
  200. $sql .= ' '.(!isset($this->fk_user_creat) ? $user->id : $this->fk_user_creat).',';
  201. $sql .= ' '.(!isset($this->date_creation) || dol_strlen($this->date_creation) == 0 ? 'NULL' : "'".$this->db->idate($this->date_creation)."'").",";
  202. $sql .= ' '.((int) $this->position).",";
  203. $sql .= ' '.(!isset($this->date_modification) || dol_strlen($this->date_modification) == 0 ? 'NULL' : "'".$this->db->idate($this->date_modification)."'");
  204. $sql .= ')';
  205. $this->db->begin();
  206. $resql = $this->db->query($sql);
  207. if (!$resql) {
  208. $error++;
  209. $this->errors[] = 'Error '.$this->db->lasterror();
  210. dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
  211. }
  212. if (!$error) {
  213. $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
  214. // Create subdirectory per language
  215. $tmplangarray = explode(',', $this->otherlang);
  216. if (is_array($tmplangarray)) {
  217. dol_mkdir($conf->website->dir_output.'/'.$this->ref);
  218. foreach ($tmplangarray as $val) {
  219. if (trim($val) == $this->lang) {
  220. continue;
  221. }
  222. dol_mkdir($conf->website->dir_output.'/'.$this->ref.'/'.trim($val));
  223. }
  224. }
  225. // Uncomment this and change WEBSITE to your own tag if you
  226. // want this action to call a trigger.
  227. // if (!$notrigger) {
  228. // // Call triggers
  229. // $result = $this->call_trigger('WEBSITE_CREATE',$user);
  230. // if ($result < 0) $error++;
  231. // // End call triggers
  232. // }
  233. }
  234. if (!$error) {
  235. $stringtodolibarrfile = "# Some properties for Dolibarr web site CMS\n";
  236. $stringtodolibarrfile .= "param=value\n";
  237. //print $conf->website->dir_output.'/'.$this->ref.'/.dolibarr';exit;
  238. file_put_contents($conf->website->dir_output.'/'.$this->ref.'/.dolibarr', $stringtodolibarrfile);
  239. }
  240. // Commit or rollback
  241. if ($error) {
  242. $this->db->rollback();
  243. if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
  244. return 0;
  245. } else {
  246. return -1 * $error;
  247. }
  248. } else {
  249. $this->db->commit();
  250. return $this->id;
  251. }
  252. }
  253. /**
  254. * Load object in memory from the database
  255. *
  256. * @param int $id Id object
  257. * @param string $ref Ref
  258. * @return int <0 if KO, 0 if not found, >0 if OK
  259. */
  260. public function fetch($id, $ref = null)
  261. {
  262. dol_syslog(__METHOD__, LOG_DEBUG);
  263. $sql = "SELECT";
  264. $sql .= " t.rowid,";
  265. $sql .= " t.entity,";
  266. $sql .= " t.ref,";
  267. $sql .= " t.position,";
  268. $sql .= " t.description,";
  269. $sql .= " t.lang,";
  270. $sql .= " t.otherlang,";
  271. $sql .= " t.status,";
  272. $sql .= " t.fk_default_home,";
  273. $sql .= " t.use_manifest,";
  274. $sql .= " t.virtualhost,";
  275. $sql .= " t.fk_user_creat,";
  276. $sql .= " t.fk_user_modif,";
  277. $sql .= " t.date_creation,";
  278. $sql .= " t.tms as date_modification";
  279. $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as t";
  280. $sql .= " WHERE t.entity IN (".getEntity('website').")";
  281. if (!empty($ref)) {
  282. $sql .= " AND t.ref = '".$this->db->escape($ref)."'";
  283. } else {
  284. $sql .= " AND t.rowid = ".(int) $id;
  285. }
  286. $resql = $this->db->query($sql);
  287. if ($resql) {
  288. $numrows = $this->db->num_rows($resql);
  289. if ($numrows) {
  290. $obj = $this->db->fetch_object($resql);
  291. $this->id = $obj->rowid;
  292. $this->entity = $obj->entity;
  293. $this->ref = $obj->ref;
  294. $this->position = $obj->position;
  295. $this->description = $obj->description;
  296. $this->lang = $obj->lang;
  297. $this->otherlang = $obj->otherlang;
  298. $this->status = $obj->status;
  299. $this->fk_default_home = $obj->fk_default_home;
  300. $this->virtualhost = $obj->virtualhost;
  301. $this->use_manifest = $obj->use_manifest;
  302. $this->fk_user_creat = $obj->fk_user_creat;
  303. $this->fk_user_modif = $obj->fk_user_modif;
  304. $this->date_creation = $this->db->jdate($obj->date_creation);
  305. $this->date_modification = $this->db->jdate($obj->date_modification);
  306. }
  307. $this->db->free($resql);
  308. if ($numrows > 0) {
  309. // Lines
  310. $this->fetchLines();
  311. }
  312. if ($numrows > 0) {
  313. return 1;
  314. } else {
  315. return 0;
  316. }
  317. } else {
  318. $this->errors[] = 'Error '.$this->db->lasterror();
  319. dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
  320. return -1;
  321. }
  322. }
  323. /**
  324. * Load object lines in memory from the database
  325. *
  326. * @return int <0 if KO, 0 if not found, >0 if OK
  327. */
  328. public function fetchLines()
  329. {
  330. $this->lines = array();
  331. // Load lines with object MyObjectLine
  332. return count($this->lines) ? 1 : 0;
  333. }
  334. /**
  335. * Load all object in memory ($this->records) from the database
  336. *
  337. * @param string $sortorder Sort Order
  338. * @param string $sortfield Sort field
  339. * @param int $limit offset limit
  340. * @param int $offset offset limit
  341. * @param array $filter filter array
  342. * @param string $filtermode filter mode (AND or OR)
  343. *
  344. * @return int <0 if KO, >0 if OK
  345. */
  346. public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $filtermode = 'AND')
  347. {
  348. dol_syslog(__METHOD__, LOG_DEBUG);
  349. $sql = "SELECT";
  350. $sql .= " t.rowid,";
  351. $sql .= " t.entity,";
  352. $sql .= " t.ref,";
  353. $sql .= " t.description,";
  354. $sql .= " t.lang,";
  355. $sql .= " t.otherlang,";
  356. $sql .= " t.status,";
  357. $sql .= " t.fk_default_home,";
  358. $sql .= " t.virtualhost,";
  359. $sql .= " t.fk_user_creat,";
  360. $sql .= " t.fk_user_modif,";
  361. $sql .= " t.date_creation,";
  362. $sql .= " t.tms as date_modification";
  363. $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as t";
  364. $sql .= " WHERE t.entity IN (".getEntity('website').")";
  365. // Manage filter
  366. $sqlwhere = array();
  367. if (count($filter) > 0) {
  368. foreach ($filter as $key => $value) {
  369. $sqlwhere[] = $key." LIKE '%".$this->db->escape($value)."%'";
  370. }
  371. }
  372. if (count($sqlwhere) > 0) {
  373. $sql .= ' AND '.implode(' '.$this->db->escape($filtermode).' ', $sqlwhere);
  374. }
  375. if (!empty($sortfield)) {
  376. $sql .= $this->db->order($sortfield, $sortorder);
  377. }
  378. if (!empty($limit)) {
  379. $sql .= $this->db->plimit($limit, $offset);
  380. }
  381. $this->records = array();
  382. $resql = $this->db->query($sql);
  383. if ($resql) {
  384. $num = $this->db->num_rows($resql);
  385. while ($obj = $this->db->fetch_object($resql)) {
  386. $line = new self($this->db);
  387. $line->id = $obj->rowid;
  388. $line->entity = $obj->entity;
  389. $line->ref = $obj->ref;
  390. $line->description = $obj->description;
  391. $line->lang = $obj->lang;
  392. $line->otherlang = $obj->otherlang;
  393. $line->status = $obj->status;
  394. $line->fk_default_home = $obj->fk_default_home;
  395. $line->virtualhost = $obj->virtualhost;
  396. $this->fk_user_creat = $obj->fk_user_creat;
  397. $this->fk_user_modif = $obj->fk_user_modif;
  398. $line->date_creation = $this->db->jdate($obj->date_creation);
  399. $line->date_modification = $this->db->jdate($obj->date_modification);
  400. $this->records[$line->id] = $line;
  401. }
  402. $this->db->free($resql);
  403. return $num;
  404. } else {
  405. $this->errors[] = 'Error '.$this->db->lasterror();
  406. dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
  407. return -1;
  408. }
  409. }
  410. /**
  411. * Update object into database
  412. *
  413. * @param User $user User that modifies
  414. * @param bool $notrigger false=launch triggers after, true=disable triggers
  415. *
  416. * @return int <0 if KO, >0 if OK
  417. */
  418. public function update(User $user, $notrigger = false)
  419. {
  420. global $conf, $langs;
  421. $error = 0;
  422. dol_syslog(__METHOD__, LOG_DEBUG);
  423. // Clean parameters
  424. if (isset($this->entity)) {
  425. $this->entity = (int) $this->entity;
  426. }
  427. if (isset($this->ref)) {
  428. $this->ref = trim($this->ref);
  429. }
  430. if (isset($this->description)) {
  431. $this->description = trim($this->description);
  432. }
  433. if (isset($this->status)) {
  434. $this->status = (int) $this->status;
  435. }
  436. // Remove spaces and be sure we have main language only
  437. $this->lang = preg_replace('/[_-].*$/', '', trim($this->lang)); // en_US or en-US -> en
  438. $tmparray = explode(',', $this->otherlang);
  439. if (is_array($tmparray)) {
  440. foreach ($tmparray as $key => $val) {
  441. // It possible we have empty val here if postparam WEBSITE_OTHERLANG is empty or set like this : 'en,,sv' or 'en,sv,'
  442. if (empty(trim($val))) {
  443. unset($tmparray[$key]);
  444. continue;
  445. }
  446. $tmparray[$key] = preg_replace('/[_-].*$/', '', trim($val)); // en_US or en-US -> en
  447. }
  448. $this->otherlang = join(',', $tmparray);
  449. }
  450. if (empty($this->lang)) {
  451. $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("MainLanguage"));
  452. return -1;
  453. }
  454. // Check parameters
  455. // Put here code to add a control on parameters values
  456. // Update request
  457. $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element.' SET';
  458. $sql .= ' entity = '.(isset($this->entity) ? $this->entity : "null").',';
  459. $sql .= ' ref = '.(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").',';
  460. $sql .= ' description = '.(isset($this->description) ? "'".$this->db->escape($this->description)."'" : "null").',';
  461. $sql .= ' lang = '.(isset($this->lang) ? "'".$this->db->escape($this->lang)."'" : "null").',';
  462. $sql .= ' otherlang = '.(isset($this->otherlang) ? "'".$this->db->escape($this->otherlang)."'" : "null").',';
  463. $sql .= ' status = '.(isset($this->status) ? $this->status : "null").',';
  464. $sql .= ' fk_default_home = '.(($this->fk_default_home > 0) ? $this->fk_default_home : "null").',';
  465. $sql .= ' use_manifest = '.((int) $this->use_manifest).',';
  466. $sql .= ' virtualhost = '.(($this->virtualhost != '') ? "'".$this->db->escape($this->virtualhost)."'" : "null").',';
  467. $sql .= ' fk_user_modif = '.(!isset($this->fk_user_modif) ? $user->id : $this->fk_user_modif).',';
  468. $sql .= ' date_creation = '.(!isset($this->date_creation) || dol_strlen($this->date_creation) != 0 ? "'".$this->db->idate($this->date_creation)."'" : 'null').',';
  469. $sql .= ' tms = '.(dol_strlen($this->date_modification) != 0 ? "'".$this->db->idate($this->date_modification)."'" : "'".$this->db->idate(dol_now())."'");
  470. $sql .= ' WHERE rowid='.((int) $this->id);
  471. $this->db->begin();
  472. $resql = $this->db->query($sql);
  473. if (!$resql) {
  474. $error++;
  475. $this->errors[] = 'Error '.$this->db->lasterror();
  476. dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
  477. }
  478. if (!$error && !$notrigger) {
  479. // Uncomment this and change MYOBJECT to your own tag if you
  480. // want this action calls a trigger.
  481. // Create subdirectory per language
  482. $tmplangarray = explode(',', $this->otherlang);
  483. if (is_array($tmplangarray)) {
  484. dol_mkdir($conf->website->dir_output.'/'.$this->ref);
  485. foreach ($tmplangarray as $val) {
  486. if (trim($val) == $this->lang) {
  487. continue;
  488. }
  489. dol_mkdir($conf->website->dir_output.'/'.$this->ref.'/'.trim($val));
  490. }
  491. }
  492. //// Call triggers
  493. //$result=$this->call_trigger('WEBSITE_MODIFY',$user);
  494. //if ($result < 0) { $error++; //Do also what you must do to rollback action if trigger fail}
  495. //// End call triggers
  496. }
  497. // Commit or rollback
  498. if ($error) {
  499. $this->db->rollback();
  500. return -1 * $error;
  501. } else {
  502. $this->db->commit();
  503. return 1;
  504. }
  505. }
  506. /**
  507. * Delete object in database
  508. *
  509. * @param User $user User that deletes
  510. * @param bool $notrigger false=launch triggers, true=disable triggers
  511. *
  512. * @return int <0 if KO, >0 if OK
  513. */
  514. public function delete(User $user, $notrigger = false)
  515. {
  516. global $conf;
  517. dol_syslog(__METHOD__, LOG_DEBUG);
  518. $error = 0;
  519. $this->db->begin();
  520. if (!$error) {
  521. $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'website_page';
  522. $sql .= ' WHERE fk_website = '.((int) $this->id);
  523. $resql = $this->db->query($sql);
  524. if (!$resql) {
  525. $error++;
  526. $this->errors[] = 'Error '.$this->db->lasterror();
  527. dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
  528. }
  529. }
  530. // Delete common code. This include execution of trigger.
  531. $result = $this->deleteCommon($user, $notrigger);
  532. if ($result <= 0) {
  533. $error++;
  534. }
  535. if (!$error && !empty($this->ref)) {
  536. $pathofwebsite = DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/website/'.$this->ref;
  537. dol_delete_dir_recursive($pathofwebsite);
  538. }
  539. // Commit or rollback
  540. if ($error) {
  541. $this->db->rollback();
  542. return -1 * $error;
  543. } else {
  544. $this->db->commit();
  545. return 1;
  546. }
  547. }
  548. /**
  549. * Load a website its id and create a new one in database.
  550. * This copy website directories, regenerate all the pages + alias pages and recreate the medias link.
  551. *
  552. * @param User $user User making the clone
  553. * @param int $fromid Id of object to clone
  554. * @param string $newref New ref
  555. * @param string $newlang New language
  556. * @return mixed New object created, <0 if KO
  557. */
  558. public function createFromClone($user, $fromid, $newref, $newlang = '')
  559. {
  560. global $conf, $langs;
  561. global $dolibarr_main_data_root;
  562. $now = dol_now();
  563. $error = 0;
  564. dol_syslog(__METHOD__, LOG_DEBUG);
  565. $newref = dol_sanitizeFileName($newref);
  566. if (empty($newref)) {
  567. $this->error = 'ErrorBadParameter';
  568. return -1;
  569. }
  570. $object = new self($this->db);
  571. // Check no site with ref exists
  572. if ($object->fetch(0, $newref) > 0) {
  573. $this->error = 'ErrorNewRefIsAlreadyUsed';
  574. return -1;
  575. }
  576. $this->db->begin();
  577. // Load source object
  578. $object->fetch($fromid);
  579. $oldidforhome = $object->fk_default_home;
  580. $oldref = $object->ref;
  581. $pathofwebsiteold = $dolibarr_main_data_root.($conf->entity > 1 ? '/'.$conf->entity : '').'/website/'.dol_sanitizeFileName($oldref);
  582. $pathofwebsitenew = $dolibarr_main_data_root.($conf->entity > 1 ? '/'.$conf->entity : '').'/website/'.dol_sanitizeFileName($newref);
  583. dol_delete_dir_recursive($pathofwebsitenew);
  584. $fileindex = $pathofwebsitenew.'/index.php';
  585. // Reset some properties
  586. unset($object->id);
  587. unset($object->fk_user_creat);
  588. unset($object->import_key);
  589. // Clear fields
  590. $object->ref = $newref;
  591. $object->fk_default_home = 0;
  592. $object->virtualhost = '';
  593. $object->date_creation = $now;
  594. $object->fk_user_creat = $user->id;
  595. $object->position = ((int) $object->position) + 1;
  596. $object->status = self::STATUS_DRAFT;
  597. if (empty($object->lang)) {
  598. $object->lang = substr($langs->defaultlang, 0, 2); // Should not happen. Protection for corrupted site with no languages
  599. }
  600. // Create clone
  601. $object->context['createfromclone'] = 'createfromclone';
  602. $result = $object->create($user);
  603. if ($result < 0) {
  604. $error++;
  605. $this->error = $object->error;
  606. $this->errors = $object->errors;
  607. dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
  608. }
  609. if (!$error) {
  610. dolCopyDir($pathofwebsiteold, $pathofwebsitenew, $conf->global->MAIN_UMASK, 0, null, 2);
  611. // Check symlink to medias and restore it if ko
  612. $pathtomedias = DOL_DATA_ROOT.'/medias'; // Target
  613. $pathtomediasinwebsite = $pathofwebsitenew.'/medias'; // Source / Link name
  614. if (!is_link(dol_osencode($pathtomediasinwebsite))) {
  615. dol_syslog("Create symlink for ".$pathtomedias." into name ".$pathtomediasinwebsite);
  616. dol_mkdir(dirname($pathtomediasinwebsite)); // To be sure dir for website exists
  617. $result = symlink($pathtomedias, $pathtomediasinwebsite);
  618. }
  619. // Copy images and js dir
  620. $pathofmediasjsold = DOL_DATA_ROOT.'/medias/js/'.$oldref;
  621. $pathofmediasjsnew = DOL_DATA_ROOT.'/medias/js/'.$newref;
  622. dolCopyDir($pathofmediasjsold, $pathofmediasjsnew, $conf->global->MAIN_UMASK, 0);
  623. $pathofmediasimageold = DOL_DATA_ROOT.'/medias/image/'.$oldref;
  624. $pathofmediasimagenew = DOL_DATA_ROOT.'/medias/image/'.$newref;
  625. dolCopyDir($pathofmediasimageold, $pathofmediasimagenew, $conf->global->MAIN_UMASK, 0);
  626. $newidforhome = 0;
  627. // Duplicate pages
  628. $objectpages = new WebsitePage($this->db);
  629. $listofpages = $objectpages->fetchAll($fromid);
  630. foreach ($listofpages as $pageid => $objectpageold) {
  631. // Delete old file
  632. $filetplold = $pathofwebsitenew.'/page'.$pageid.'.tpl.php';
  633. dol_delete_file($filetplold);
  634. // Create new file
  635. $objectpagenew = $objectpageold->createFromClone($user, $pageid, $objectpageold->pageurl, '', 0, $object->id, 1);
  636. //print $pageid.' = '.$objectpageold->pageurl.' -> '.$objectpagenew->id.' = '.$objectpagenew->pageurl.'<br>';
  637. if (is_object($objectpagenew) && $objectpagenew->pageurl) {
  638. $filealias = $pathofwebsitenew.'/'.$objectpagenew->pageurl.'.php';
  639. $filetplnew = $pathofwebsitenew.'/page'.$objectpagenew->id.'.tpl.php';
  640. // Save page alias
  641. $result = dolSavePageAlias($filealias, $object, $objectpagenew);
  642. if (!$result) {
  643. setEventMessages('Failed to write file '.$filealias, null, 'errors');
  644. }
  645. $result = dolSavePageContent($filetplnew, $object, $objectpagenew);
  646. if (!$result) {
  647. setEventMessages('Failed to write file '.$filetplnew, null, 'errors');
  648. }
  649. if ($pageid == $oldidforhome) {
  650. $newidforhome = $objectpagenew->id;
  651. }
  652. } else {
  653. setEventMessages($objectpageold->error, $objectpageold->errors, 'errors');
  654. $error++;
  655. }
  656. }
  657. }
  658. if (!$error) {
  659. // Restore id of home page
  660. $object->fk_default_home = $newidforhome;
  661. $res = $object->update($user);
  662. if (!($res > 0)) {
  663. $error++;
  664. setEventMessages($object->error, $object->errors, 'errors');
  665. }
  666. if (!$error) {
  667. $filetpl = $pathofwebsitenew.'/page'.$newidforhome.'.tpl.php';
  668. $filewrapper = $pathofwebsitenew.'/wrapper.php';
  669. // Re-generates the index.php page to be the home page, and re-generates the wrapper.php
  670. //--------------------------------------------------------------------------------------
  671. $result = dolSaveIndexPage($pathofwebsitenew, $fileindex, $filetpl, $filewrapper, $object);
  672. }
  673. }
  674. unset($object->context['createfromclone']);
  675. // End
  676. if (!$error) {
  677. $this->db->commit();
  678. return $object;
  679. } else {
  680. $this->db->rollback();
  681. return -1;
  682. }
  683. }
  684. /**
  685. * Return a link to the user card (with optionally the picto)
  686. * Use this->id,this->lastname, this->firstname
  687. *
  688. * @param int $withpicto Include picto in link (0=No picto, 1=Include picto into link, 2=Only picto)
  689. * @param string $option On what the link point to
  690. * @param integer $notooltip 1=Disable tooltip
  691. * @param int $maxlen Max length of visible user name
  692. * @param string $morecss Add more css on link
  693. * @return string String with URL
  694. */
  695. public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $maxlen = 24, $morecss = '')
  696. {
  697. global $langs, $conf, $db;
  698. global $dolibarr_main_authentication, $dolibarr_main_demo;
  699. global $menumanager;
  700. $result = '';
  701. $companylink = '';
  702. $label = '<u>'.$langs->trans("WebSite").'</u>';
  703. $label .= '<br>';
  704. $label .= '<b>'.$langs->trans('Ref').':</b> '.$this->ref.'<br>';
  705. $label .= '<b>'.$langs->trans('MainLanguage').':</b> '.$this->lang;
  706. $linkstart = '<a href="'.DOL_URL_ROOT.'/website/card.php?id='.$this->id.'"';
  707. $linkstart .= ($notooltip ? '' : ' title="'.dol_escape_htmltag($label, 1).'" class="classfortooltip'.($morecss ? ' '.$morecss : '').'"');
  708. $linkstart .= '>';
  709. $linkend = '</a>';
  710. $linkstart = $linkend = '';
  711. if ($withpicto) {
  712. $result .= ($linkstart.img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? '' : 'class="classfortooltip"')).$linkend);
  713. if ($withpicto != 2) {
  714. $result .= ' ';
  715. }
  716. }
  717. $result .= $linkstart.$this->ref.$linkend;
  718. return $result;
  719. }
  720. /**
  721. * Retourne le libelle du status d'un user (actif, inactif)
  722. *
  723. * @param int $mode 0=libelle long, 1=libelle court, 2=Picto + Libelle court, 3=Picto, 4=Picto + Libelle long, 5=Libelle court + Picto
  724. * @return string Label of status
  725. */
  726. public function getLibStatut($mode = 0)
  727. {
  728. return $this->LibStatut($this->status, $mode);
  729. }
  730. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  731. /**
  732. * Renvoi le libelle d'un status donne
  733. *
  734. * @param int $status Id status
  735. * @param int $mode 0=libelle long, 1=libelle court, 2=Picto + Libelle court, 3=Picto, 4=Picto + Libelle long, 5=Libelle court + Picto
  736. * @return string Label of status
  737. */
  738. public function LibStatut($status, $mode = 0)
  739. {
  740. // phpcs:enable
  741. global $langs;
  742. if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
  743. global $langs;
  744. //$langs->load("mymodule");
  745. $this->labelStatus[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv('Offline');
  746. $this->labelStatus[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv('Online');
  747. $this->labelStatusShort[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv('Offline');
  748. $this->labelStatusShort[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv('Online');
  749. }
  750. $statusType = 'status5';
  751. if ($status == self::STATUS_VALIDATED) {
  752. $statusType = 'status4';
  753. }
  754. return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
  755. }
  756. /**
  757. * Initialise object with example values
  758. * Id must be 0 if object instance is a specimen
  759. *
  760. * @return void
  761. */
  762. public function initAsSpecimen()
  763. {
  764. global $user;
  765. $this->id = 0;
  766. $this->specimen = 1;
  767. $this->entity = 1;
  768. $this->ref = 'myspecimenwebsite';
  769. $this->description = 'A specimen website';
  770. $this->lang = 'en';
  771. $this->otherlang = 'fr,es';
  772. $this->status = 1;
  773. $this->fk_default_home = null;
  774. $this->virtualhost = 'http://myvirtualhost';
  775. $this->fk_user_creat = $user->id;
  776. $this->fk_user_modif = $user->id;
  777. $this->date_creation = dol_now();
  778. $this->tms = dol_now();
  779. }
  780. /**
  781. * Generate a zip with all data of web site.
  782. *
  783. * @return string Path to file with zip or '' if error
  784. */
  785. public function exportWebSite()
  786. {
  787. global $conf, $mysoc;
  788. $website = $this;
  789. if (empty($website->id) || empty($website->ref)) {
  790. setEventMessages("Website id or ref is not defined", null, 'errors');
  791. return '';
  792. }
  793. dol_syslog("Create temp dir ".$conf->website->dir_temp);
  794. dol_mkdir($conf->website->dir_temp);
  795. if (!is_writable($conf->website->dir_temp)) {
  796. setEventMessages("Temporary dir ".$conf->website->dir_temp." is not writable", null, 'errors');
  797. return '';
  798. }
  799. $destdir = $conf->website->dir_temp.'/'.$website->ref;
  800. dol_syslog("Clear temp dir ".$destdir);
  801. $count = 0; $countreallydeleted = 0;
  802. $counttodelete = dol_delete_dir_recursive($destdir, $count, 1, 0, $countreallydeleted);
  803. if ($counttodelete != $countreallydeleted) {
  804. setEventMessages("Failed to clean temp directory ".$destdir, null, 'errors');
  805. return '';
  806. }
  807. $arrayreplacementinfilename = array();
  808. $arrayreplacementincss = array();
  809. $arrayreplacementincss['file=image/'.$website->ref.'/'] = "file=image/__WEBSITE_KEY__/";
  810. $arrayreplacementincss['file=js/'.$website->ref.'/'] = "file=js/__WEBSITE_KEY__/";
  811. $arrayreplacementincss['medias/image/'.$website->ref.'/'] = "medias/image/__WEBSITE_KEY__/";
  812. $arrayreplacementincss['medias/js/'.$website->ref.'/'] = "medias/js/__WEBSITE_KEY__/";
  813. if ($mysoc->logo_small) {
  814. $arrayreplacementincss['file=logos%2Fthumbs%2F'.$mysoc->logo_small] = "file=logos%2Fthumbs%2F__LOGO_SMALL_KEY__";
  815. }
  816. if ($mysoc->logo_mini) {
  817. $arrayreplacementincss['file=logos%2Fthumbs%2F'.$mysoc->logo_mini] = "file=logos%2Fthumbs%2F__LOGO_MINI_KEY__";
  818. }
  819. if ($mysoc->logo) {
  820. $arrayreplacementincss['file=logos%2Fthumbs%2F'.$mysoc->logo] = "file=logos%2Fthumbs%2F__LOGO_KEY__";
  821. }
  822. // Create output directories
  823. dol_syslog("Create containers dir");
  824. dol_mkdir($conf->website->dir_temp.'/'.$website->ref.'/containers');
  825. dol_mkdir($conf->website->dir_temp.'/'.$website->ref.'/medias/image/websitekey');
  826. dol_mkdir($conf->website->dir_temp.'/'.$website->ref.'/medias/js/websitekey');
  827. // Copy files into 'containers'
  828. $srcdir = $conf->website->dir_output.'/'.$website->ref;
  829. $destdir = $conf->website->dir_temp.'/'.$website->ref.'/containers';
  830. dol_syslog("Copy pages from ".$srcdir." into ".$destdir);
  831. dolCopyDir($srcdir, $destdir, 0, 1, $arrayreplacementinfilename, 2, array('old', 'back'));
  832. // Copy file README.md and LICENSE from directory containers into directory root
  833. if (dol_is_file($conf->website->dir_temp.'/'.$website->ref.'/containers/README.md')) {
  834. dol_copy($conf->website->dir_temp.'/'.$website->ref.'/containers/README.md', $conf->website->dir_temp.'/'.$website->ref.'/README.md');
  835. }
  836. if (dol_is_file($conf->website->dir_temp.'/'.$website->ref.'/containers/LICENSE')) {
  837. dol_copy($conf->website->dir_temp.'/'.$website->ref.'/containers/LICENSE', $conf->website->dir_temp.'/'.$website->ref.'/LICENSE');
  838. }
  839. // Copy files into medias/image
  840. $srcdir = DOL_DATA_ROOT.'/medias/image/'.$website->ref;
  841. $destdir = $conf->website->dir_temp.'/'.$website->ref.'/medias/image/websitekey';
  842. dol_syslog("Copy content from ".$srcdir." into ".$destdir);
  843. dolCopyDir($srcdir, $destdir, 0, 1, $arrayreplacementinfilename);
  844. // Copy files into medias/js
  845. $srcdir = DOL_DATA_ROOT.'/medias/js/'.$website->ref;
  846. $destdir = $conf->website->dir_temp.'/'.$website->ref.'/medias/js/websitekey';
  847. dol_syslog("Copy content from ".$srcdir." into ".$destdir);
  848. dolCopyDir($srcdir, $destdir, 0, 1, $arrayreplacementinfilename);
  849. // Make some replacement into some files
  850. $cssindestdir = $conf->website->dir_temp.'/'.$website->ref.'/containers/styles.css.php';
  851. if (dol_is_file($cssindestdir)) {
  852. dolReplaceInFile($cssindestdir, $arrayreplacementincss);
  853. }
  854. $htmldeaderindestdir = $conf->website->dir_temp.'/'.$website->ref.'/containers/htmlheader.html';
  855. if (dol_is_file($htmldeaderindestdir)) {
  856. dolReplaceInFile($htmldeaderindestdir, $arrayreplacementincss);
  857. }
  858. // Build sql file
  859. $filesql = $conf->website->dir_temp.'/'.$website->ref.'/website_pages.sql';
  860. $fp = fopen($filesql, "w");
  861. if (empty($fp)) {
  862. setEventMessages("Failed to create file ".$filesql, null, 'errors');
  863. return '';
  864. }
  865. $objectpages = new WebsitePage($this->db);
  866. $listofpages = $objectpages->fetchAll($website->id);
  867. // Assign ->newid and ->newfk_page
  868. $i = 1;
  869. foreach ($listofpages as $pageid => $objectpageold) {
  870. $objectpageold->newid = $i;
  871. $i++;
  872. }
  873. $i = 1;
  874. foreach ($listofpages as $pageid => $objectpageold) {
  875. // Search newid
  876. $newfk_page = 0;
  877. foreach ($listofpages as $pageid2 => $objectpageold2) {
  878. if ($pageid2 == $objectpageold->fk_page) {
  879. $newfk_page = $objectpageold2->newid;
  880. break;
  881. }
  882. }
  883. $objectpageold->newfk_page = $newfk_page;
  884. $i++;
  885. }
  886. foreach ($listofpages as $pageid => $objectpageold) {
  887. $allaliases = $objectpageold->pageurl;
  888. $allaliases .= ($objectpageold->aliasalt ? ','.$objectpageold->aliasalt : '');
  889. $line = '-- File generated by Dolibarr '.DOL_VERSION.' -- '.dol_print_date(dol_now('gmt'), 'standard', 'gmt').' UTC --;';
  890. $line .= "\n";
  891. $line .= '-- Page ID '.$objectpageold->id.' -> '.$objectpageold->newid.'__+MAX_llx_website_page__ - Aliases '.$allaliases.' --;'; // newid start at 1, 2...
  892. $line .= "\n";
  893. fputs($fp, $line);
  894. // Warning: We must keep llx_ here. It is a generic SQL.
  895. $line = 'INSERT INTO llx_website_page(rowid, fk_page, fk_website, pageurl, aliasalt, title, description, lang, image, keywords, status, date_creation, tms, import_key, grabbed_from, type_container, htmlheader, content, author_alias, allowed_in_frames)';
  896. $line .= " VALUES(";
  897. $line .= $objectpageold->newid."__+MAX_llx_website_page__, ";
  898. $line .= ($objectpageold->newfk_page ? $this->db->escape($objectpageold->newfk_page)."__+MAX_llx_website_page__" : "null").", ";
  899. $line .= "__WEBSITE_ID__, ";
  900. $line .= "'".$this->db->escape($objectpageold->pageurl)."', ";
  901. $line .= "'".$this->db->escape($objectpageold->aliasalt)."', ";
  902. $line .= "'".$this->db->escape($objectpageold->title)."', ";
  903. $line .= "'".$this->db->escape($objectpageold->description)."', ";
  904. $line .= "'".$this->db->escape($objectpageold->lang)."', ";
  905. $line .= "'".$this->db->escape($objectpageold->image)."', ";
  906. $line .= "'".$this->db->escape($objectpageold->keywords)."', ";
  907. $line .= "'".$this->db->escape($objectpageold->status)."', ";
  908. $line .= "'".$this->db->idate($objectpageold->date_creation)."', ";
  909. $line .= "'".$this->db->idate($objectpageold->date_modification)."', ";
  910. $line .= ($objectpageold->import_key ? "'".$this->db->escape($objectpageold->import_key)."'" : "null").", ";
  911. $line .= "'".$this->db->escape($objectpageold->grabbed_from)."', ";
  912. $line .= "'".$this->db->escape($objectpageold->type_container)."', ";
  913. // Make substitution with a generic path into htmlheader content
  914. $stringtoexport = $objectpageold->htmlheader;
  915. $stringtoexport = str_replace(array("\r\n", "\r", "\n"), "__N__", $stringtoexport);
  916. $stringtoexport = str_replace('file=image/'.$website->ref.'/', "file=image/__WEBSITE_KEY__/", $stringtoexport);
  917. $stringtoexport = str_replace('file=js/'.$website->ref.'/', "file=js/__WEBSITE_KEY__/", $stringtoexport);
  918. $stringtoexport = str_replace('medias/image/'.$website->ref.'/', "medias/image/__WEBSITE_KEY__/", $stringtoexport);
  919. $stringtoexport = str_replace('medias/js/'.$website->ref.'/', "medias/js/__WEBSITE_KEY__/", $stringtoexport);
  920. $stringtoexport = str_replace('file=logos%2Fthumbs%2F'.$mysoc->logo_small, "file=logos%2Fthumbs%2F__LOGO_SMALL_KEY__", $stringtoexport);
  921. $stringtoexport = str_replace('file=logos%2Fthumbs%2F'.$mysoc->logo_mini, "file=logos%2Fthumbs%2F__LOGO_MINI_KEY__", $stringtoexport);
  922. $stringtoexport = str_replace('file=logos%2Fthumbs%2F'.$mysoc->logo, "file=logos%2Fthumbs%2F__LOGO_KEY__", $stringtoexport);
  923. $line .= "'".$this->db->escape(str_replace(array("\r\n", "\r", "\n"), "__N__", $stringtoexport))."', "; // Replace \r \n to have record on 1 line
  924. // Make substitution with a generic path into page content
  925. $stringtoexport = $objectpageold->content;
  926. $stringtoexport = str_replace(array("\r\n", "\r", "\n"), "__N__", $stringtoexport);
  927. $stringtoexport = str_replace('file=image/'.$website->ref.'/', "file=image/__WEBSITE_KEY__/", $stringtoexport);
  928. $stringtoexport = str_replace('file=js/'.$website->ref.'/', "file=js/__WEBSITE_KEY__/", $stringtoexport);
  929. $stringtoexport = str_replace('medias/image/'.$website->ref.'/', "medias/image/__WEBSITE_KEY__/", $stringtoexport);
  930. $stringtoexport = str_replace('medias/js/'.$website->ref.'/', "medias/js/__WEBSITE_KEY__/", $stringtoexport);
  931. $stringtoexport = str_replace('"image/'.$website->ref.'/', '"image/__WEBSITE_KEY__/', $stringtoexport); // When we have a link src="image/websiteref/file.png" into html content
  932. $stringtoexport = str_replace('"/image/'.$website->ref.'/', '"/image/__WEBSITE_KEY__/', $stringtoexport); // When we have a link src="/image/websiteref/file.png" into html content
  933. $stringtoexport = str_replace('"js/'.$website->ref.'/', '"js/__WEBSITE_KEY__/', $stringtoexport);
  934. $stringtoexport = str_replace('"/js/'.$website->ref.'/', '"/js/__WEBSITE_KEY__/', $stringtoexport);
  935. $stringtoexport = str_replace('file=logos%2Fthumbs%2F'.$mysoc->logo_small, "file=logos%2Fthumbs%2F__LOGO_SMALL_KEY__", $stringtoexport);
  936. $stringtoexport = str_replace('file=logos%2Fthumbs%2F'.$mysoc->logo_mini, "file=logos%2Fthumbs%2F__LOGO_MINI_KEY__", $stringtoexport);
  937. $stringtoexport = str_replace('file=logos%2Fthumbs%2F'.$mysoc->logo, "file=logos%2Fthumbs%2F__LOGO_KEY__", $stringtoexport);
  938. $line .= "'".$this->db->escape($stringtoexport)."', "; // Replace \r \n to have record on 1 line
  939. $line .= "'".$this->db->escape($objectpageold->author_alias)."', ";
  940. $line .= (int) $objectpageold->allowed_in_frames;
  941. $line .= ");";
  942. $line .= "\n";
  943. fputs($fp, $line);
  944. // Add line to update home page id during import
  945. //var_dump($this->fk_default_home.' - '.$objectpageold->id.' - '.$objectpageold->newid);exit;
  946. if ($this->fk_default_home > 0 && ($objectpageold->id == $this->fk_default_home) && ($objectpageold->newid > 0)) { // This is the record with home page
  947. // Warning: We must keep llx_ here. It is a generic SQL.
  948. $line = "UPDATE llx_website SET fk_default_home = ".($objectpageold->newid > 0 ? $this->db->escape($objectpageold->newid)."__+MAX_llx_website_page__" : "null")." WHERE rowid = __WEBSITE_ID__;";
  949. $line .= "\n";
  950. fputs($fp, $line);
  951. }
  952. }
  953. $line = "\n-- For Dolibarr v14+ --;\n";
  954. $line .= "UPDATE llx_website SET lang = '".$this->db->escape($this->lang)."' WHERE rowid = __WEBSITE_ID__;\n";
  955. $line .= "UPDATE llx_website SET otherlang = '".$this->db->escape($this->otherlang)."' WHERE rowid = __WEBSITE_ID__;\n";
  956. $line .= "\n";
  957. fputs($fp, $line);
  958. fclose($fp);
  959. if (!empty($conf->global->MAIN_UMASK)) {
  960. @chmod($filesql, octdec($conf->global->MAIN_UMASK));
  961. }
  962. // Build zip file
  963. $filedir = $conf->website->dir_temp.'/'.$website->ref.'/.';
  964. $fileglob = $conf->website->dir_temp.'/'.$website->ref.'/website_'.$website->ref.'-*.zip';
  965. $filename = $conf->website->dir_temp.'/'.$website->ref.'/website_'.$website->ref.'-'.dol_print_date(dol_now(), 'dayhourlog').'-V'.((float) DOL_VERSION).'.zip';
  966. dol_delete_file($fileglob, 0);
  967. $result = dol_compress_file($filedir, $filename, 'zip');
  968. if ($result > 0) {
  969. return $filename;
  970. } else {
  971. global $errormsg;
  972. $this->error = $errormsg;
  973. return '';
  974. }
  975. }
  976. /**
  977. * Open a zip with all data of web site and load it into database.
  978. *
  979. * @param string $pathtofile Full path of zip file
  980. * @return int <0 if KO, Id of new website if OK
  981. */
  982. public function importWebSite($pathtofile)
  983. {
  984. global $conf, $mysoc;
  985. $error = 0;
  986. $pathtofile = dol_sanitizePathName($pathtofile);
  987. $object = $this;
  988. if (empty($object->ref)) {
  989. $this->error = 'Function importWebSite called on object not loaded (object->ref is empty)';
  990. return -2;
  991. }
  992. dol_delete_dir_recursive($conf->website->dir_temp."/".$object->ref);
  993. dol_mkdir($conf->website->dir_temp.'/'.$object->ref);
  994. $filename = basename($pathtofile);
  995. if (!preg_match('/^website_(.*)-(.*)$/', $filename, $reg)) {
  996. $this->errors[] = 'Bad format for filename '.$filename.'. Must be website_XXX-VERSION.';
  997. return -3;
  998. }
  999. $result = dol_uncompress($pathtofile, $conf->website->dir_temp.'/'.$object->ref);
  1000. if (!empty($result['error'])) {
  1001. $this->errors[] = 'Failed to unzip file '.$pathtofile.'.';
  1002. return -4;
  1003. }
  1004. $arrayreplacement = array();
  1005. $arrayreplacement['__WEBSITE_ID__'] = $object->id;
  1006. $arrayreplacement['__WEBSITE_KEY__'] = $object->ref;
  1007. $arrayreplacement['__N__'] = $this->db->escape("\n"); // Restore \n
  1008. $arrayreplacement['__LOGO_SMALL_KEY__'] = $this->db->escape($mysoc->logo_small);
  1009. $arrayreplacement['__LOGO_MINI_KEY__'] = $this->db->escape($mysoc->logo_mini);
  1010. $arrayreplacement['__LOGO_KEY__'] = $this->db->escape($mysoc->logo);
  1011. // Copy containers
  1012. dolCopyDir($conf->website->dir_temp.'/'.$object->ref.'/containers', $conf->website->dir_output.'/'.$object->ref, 0, 1); // Overwrite if exists
  1013. // Make replacement into css and htmlheader file
  1014. $cssindestdir = $conf->website->dir_output.'/'.$object->ref.'/styles.css.php';
  1015. $result = dolReplaceInFile($cssindestdir, $arrayreplacement);
  1016. $htmldeaderindestdir = $conf->website->dir_output.'/'.$object->ref.'/htmlheader.html';
  1017. $result = dolReplaceInFile($htmldeaderindestdir, $arrayreplacement);
  1018. // Now generate the master.inc.php page
  1019. $filemaster = $conf->website->dir_output.'/'.$object->ref.'/master.inc.php';
  1020. $result = dolSaveMasterFile($filemaster);
  1021. if (!$result) {
  1022. $this->errors[] = 'Failed to write file '.$filemaster;
  1023. $error++;
  1024. }
  1025. dolCopyDir($conf->website->dir_temp.'/'.$object->ref.'/medias/image/websitekey', $conf->website->dir_output.'/'.$object->ref.'/medias/image/'.$object->ref, 0, 1); // Medias can be shared, do not overwrite if exists
  1026. dolCopyDir($conf->website->dir_temp.'/'.$object->ref.'/medias/js/websitekey', $conf->website->dir_output.'/'.$object->ref.'/medias/js/'.$object->ref, 0, 1); // Medias can be shared, do not overwrite if exists
  1027. $sqlfile = $conf->website->dir_temp."/".$object->ref.'/website_pages.sql';
  1028. $result = dolReplaceInFile($sqlfile, $arrayreplacement);
  1029. $this->db->begin();
  1030. // Search the $maxrowid because we need it later
  1031. $sqlgetrowid = 'SELECT MAX(rowid) as max from '.MAIN_DB_PREFIX.'website_page';
  1032. $resql = $this->db->query($sqlgetrowid);
  1033. if ($resql) {
  1034. $obj = $this->db->fetch_object($resql);
  1035. $maxrowid = $obj->max;
  1036. }
  1037. // Load sql record
  1038. $runsql = run_sql($sqlfile, 1, '', 0, '', 'none', 0, 1, 0, 0, 1); // The maxrowid of table is searched into this function two
  1039. if ($runsql <= 0) {
  1040. $this->errors[] = 'Failed to load sql file '.$sqlfile.' (ret='.((int) $runsql).')';
  1041. $error++;
  1042. }
  1043. $objectpagestatic = new WebsitePage($this->db);
  1044. // Make replacement of IDs
  1045. $fp = fopen($sqlfile, "r");
  1046. if ($fp) {
  1047. while (!feof($fp)) {
  1048. $reg = array();
  1049. // Warning fgets with second parameter that is null or 0 hang.
  1050. $buf = fgets($fp, 65000);
  1051. if (preg_match('/^-- Page ID (\d+)\s[^\s]+\s(\d+).*Aliases\s(.*)\s--;/i', $buf, $reg)) {
  1052. $oldid = $reg[1];
  1053. $newid = ($reg[2] + $maxrowid);
  1054. $aliasesarray = explode(',', $reg[3]);
  1055. dol_syslog("Found ID ".$oldid." to replace with ID ".$newid." and shortcut aliases to create: ".$reg[3]);
  1056. dol_move($conf->website->dir_output.'/'.$object->ref.'/page'.$oldid.'.tpl.php', $conf->website->dir_output.'/'.$object->ref.'/page'.$newid.'.tpl.php', 0, 1, 0, 0);
  1057. $objectpagestatic->fetch($newid);
  1058. // The move is not enough, so we regenerate page
  1059. $filetpl = $conf->website->dir_output.'/'.$object->ref.'/page'.$newid.'.tpl.php';
  1060. $result = dolSavePageContent($filetpl, $object, $objectpagestatic);
  1061. if (!$result) {
  1062. $this->errors[] = 'Failed to write file '.basename($filetpl);
  1063. $error++;
  1064. }
  1065. // Regenerate alternative aliases pages
  1066. if (is_array($aliasesarray)) {
  1067. foreach ($aliasesarray as $aliasshortcuttocreate) {
  1068. if (trim($aliasshortcuttocreate)) {
  1069. $filealias = $conf->website->dir_output.'/'.$object->ref.'/'.trim($aliasshortcuttocreate).'.php';
  1070. $result = dolSavePageAlias($filealias, $object, $objectpagestatic);
  1071. if (!$result) {
  1072. $this->errors[] = 'Failed to write file '.basename($filealias);
  1073. $error++;
  1074. }
  1075. }
  1076. }
  1077. }
  1078. }
  1079. }
  1080. }
  1081. // Read record of website that has been updated by the run_sql function previously called so we can get the
  1082. // value of fk_default_home that is ID of home page
  1083. $sql = "SELECT fk_default_home FROM ".MAIN_DB_PREFIX."website WHERE rowid = ".((int) $object->id);
  1084. $resql = $this->db->query($sql);
  1085. if ($resql) {
  1086. $obj = $this->db->fetch_object($resql);
  1087. if ($obj) {
  1088. $object->fk_default_home = $obj->fk_default_home;
  1089. } else {
  1090. //$this->errors[] = 'Failed to get the Home page';
  1091. //$error++;
  1092. }
  1093. }
  1094. // Regenerate index page to point to the new index page
  1095. $pathofwebsite = $conf->website->dir_output.'/'.$object->ref;
  1096. dolSaveIndexPage($pathofwebsite, $pathofwebsite.'/index.php', $pathofwebsite.'/page'.$object->fk_default_home.'.tpl.php', $pathofwebsite.'/wrapper.php', $object);
  1097. if ($error) {
  1098. $this->db->rollback();
  1099. return -1;
  1100. } else {
  1101. $this->db->commit();
  1102. return $object->id;
  1103. }
  1104. }
  1105. /**
  1106. * Rebuild all files of all the pages/containers of a website. Rebuild also the index and wrapper.php file.
  1107. * Note: Files are already regenerated during importWebSite so this function is useless when importing a website.
  1108. *
  1109. * @return int <0 if KO, >=0 if OK
  1110. */
  1111. public function rebuildWebSiteFiles()
  1112. {
  1113. global $conf;
  1114. $error = 0;
  1115. $object = $this;
  1116. if (empty($object->ref)) {
  1117. $this->error = 'Function rebuildWebSiteFiles called on object not loaded (object->ref is empty)';
  1118. return -1;
  1119. }
  1120. $objectpagestatic = new WebsitePage($this->db);
  1121. $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."website_page WHERE fk_website = ".((int) $this->id);
  1122. $resql = $this->db->query($sql);
  1123. if (!$resql) {
  1124. $this->error = $this->db->lasterror();
  1125. return -1;
  1126. }
  1127. $num = $this->db->num_rows($resql);
  1128. // Loop on each container/page
  1129. $i = 0;
  1130. while ($i < $num) {
  1131. $obj = $this->db->fetch_object($resql);
  1132. $newid = $obj->rowid;
  1133. $objectpagestatic->fetch($newid);
  1134. $aliasesarray = explode(',', $objectpagestatic->aliasalt);
  1135. $filetpl = $conf->website->dir_output.'/'.$object->ref.'/page'.$newid.'.tpl.php';
  1136. $result = dolSavePageContent($filetpl, $object, $objectpagestatic);
  1137. if (!$result) {
  1138. $this->errors[] = 'Failed to write file '.basename($filetpl);
  1139. $error++;
  1140. }
  1141. // Add main alias to list of alternative aliases
  1142. if (!empty($objectpagestatic->pageurl) && !in_array($objectpagestatic->pageurl, $aliasesarray)) {
  1143. $aliasesarray[] = $objectpagestatic->pageurl;
  1144. }
  1145. // Regenerate also all aliases pages (pages with a natural name) by calling dolSavePageAlias()
  1146. if (is_array($aliasesarray)) {
  1147. foreach ($aliasesarray as $aliasshortcuttocreate) {
  1148. if (trim($aliasshortcuttocreate)) {
  1149. $filealias = $conf->website->dir_output.'/'.$object->ref.'/'.trim($aliasshortcuttocreate).'.php';
  1150. $result = dolSavePageAlias($filealias, $object, $objectpagestatic); // This includes also a copy into sublanguage directories.
  1151. if (!$result) {
  1152. $this->errors[] = 'Failed to write file '.basename($filealias);
  1153. $error++;
  1154. }
  1155. }
  1156. }
  1157. }
  1158. $i++;
  1159. }
  1160. if (!$error) {
  1161. // Save index.php and wrapper.php
  1162. $pathofwebsite = $conf->website->dir_output.'/'.$object->ref;
  1163. $fileindex = $pathofwebsite.'/index.php';
  1164. $filetpl = '';
  1165. if ($object->fk_default_home > 0) {
  1166. $filetpl = $pathofwebsite.'/page'.$object->fk_default_home.'.tpl.php';
  1167. }
  1168. $filewrapper = $pathofwebsite.'/wrapper.php';
  1169. dolSaveIndexPage($pathofwebsite, $fileindex, $filetpl, $filewrapper, $object); // This includes also a version of index.php into sublanguage directories
  1170. }
  1171. if ($error) {
  1172. return -1;
  1173. } else {
  1174. return $num;
  1175. }
  1176. }
  1177. /**
  1178. * Return if web site is a multilanguage web site. Return false if there is only 0 or 1 language.
  1179. *
  1180. * @return boolean True if web site is a multilanguage web site
  1181. */
  1182. public function isMultiLang()
  1183. {
  1184. return (empty($this->otherlang) ? false : true);
  1185. }
  1186. /**
  1187. * Component to select language inside a container (Full CSS Only)
  1188. *
  1189. * @param array|string $languagecodes 'auto' to show all languages available for page, or language codes array like array('en','fr','de','es')
  1190. * @param Translate $weblangs Language Object
  1191. * @param string $morecss More CSS class on component
  1192. * @param string $htmlname Suffix for HTML name
  1193. * @return string HTML select component
  1194. */
  1195. public function componentSelectLang($languagecodes, $weblangs, $morecss = '', $htmlname = '')
  1196. {
  1197. global $websitepagefile, $website;
  1198. if (!is_object($weblangs)) {
  1199. return 'ERROR componentSelectLang called with parameter $weblangs not defined';
  1200. }
  1201. $arrayofspecialmainlanguages = array(
  1202. 'en'=>'en_US',
  1203. 'sq'=>'sq_AL',
  1204. 'ar'=>'ar_SA',
  1205. 'eu'=>'eu_ES',
  1206. 'bn'=>'bn_DB',
  1207. 'bs'=>'bs_BA',
  1208. 'ca'=>'ca_ES',
  1209. 'zh'=>'zh_CN',
  1210. 'cs'=>'cs_CZ',
  1211. 'da'=>'da_DK',
  1212. 'et'=>'et_EE',
  1213. 'ka'=>'ka_GE',
  1214. 'el'=>'el_GR',
  1215. 'he'=>'he_IL',
  1216. 'kn'=>'kn_IN',
  1217. 'km'=>'km_KH',
  1218. 'ko'=>'ko_KR',
  1219. 'lo'=>'lo_LA',
  1220. 'nb'=>'nb_NO',
  1221. 'fa'=>'fa_IR',
  1222. 'sr'=>'sr_RS',
  1223. 'sl'=>'sl_SI',
  1224. 'uk'=>'uk_UA',
  1225. 'vi'=>'vi_VN'
  1226. );
  1227. // Load tmppage if we have $websitepagefile defined
  1228. $tmppage = new WebsitePage($this->db);
  1229. $pageid = 0;
  1230. if (!empty($websitepagefile)) {
  1231. $websitepagefileshort = basename($websitepagefile);
  1232. if ($websitepagefileshort == 'index.php') {
  1233. $pageid = $website->fk_default_home;
  1234. } else {
  1235. $pageid = str_replace(array('.tpl.php', 'page'), array('', ''), $websitepagefileshort);
  1236. }
  1237. if ($pageid > 0) {
  1238. $tmppage->fetch($pageid);
  1239. }
  1240. }
  1241. // Fill $languagecodes array with existing translation, nothing if none
  1242. if (!is_array($languagecodes) && $pageid > 0) {
  1243. $languagecodes = array();
  1244. $sql = "SELECT wp.rowid, wp.lang, wp.pageurl, wp.fk_page";
  1245. $sql .= " FROM ".MAIN_DB_PREFIX."website_page as wp";
  1246. $sql .= " WHERE wp.fk_website = ".((int) $website->id);
  1247. $sql .= " AND (wp.fk_page = ".((int) $pageid)." OR wp.rowid = ".((int) $pageid);
  1248. if ($tmppage->fk_page > 0) {
  1249. $sql .= " OR wp.fk_page = ".((int) $tmppage->fk_page)." OR wp.rowid = ".((int) $tmppage->fk_page);
  1250. }
  1251. $sql .= ")";
  1252. $resql = $this->db->query($sql);
  1253. if ($resql) {
  1254. while ($obj = $this->db->fetch_object($resql)) {
  1255. $newlang = $obj->lang;
  1256. if ($obj->rowid == $pageid) {
  1257. $newlang = $obj->lang;
  1258. }
  1259. if (!in_array($newlang, $languagecodes)) {
  1260. $languagecodes[] = $newlang;
  1261. }
  1262. }
  1263. }
  1264. }
  1265. // Now $languagecodes is always an array. Example array('en', 'fr', 'es');
  1266. $languagecodeselected = substr($weblangs->defaultlang, 0, 2); // Because we must init with a value, but real value is the lang of main parent container
  1267. if (!empty($websitepagefile)) {
  1268. $pageid = str_replace(array('.tpl.php', 'page'), array('', ''), basename($websitepagefile));
  1269. if ($pageid > 0) {
  1270. $pagelang = substr($tmppage->lang, 0, 2);
  1271. $languagecodeselected = substr($pagelang, 0, 2);
  1272. if (!in_array($pagelang, $languagecodes)) {
  1273. $languagecodes[] = $pagelang; // We add language code of page into combo list
  1274. }
  1275. }
  1276. }
  1277. $weblangs->load('languages');
  1278. //var_dump($weblangs->defaultlang);
  1279. $url = $_SERVER["REQUEST_URI"];
  1280. $url = preg_replace('/(\?|&)l=([a-zA-Z_]*)/', '', $url); // We remove param l from url
  1281. //$url = preg_replace('/(\?|&)lang=([a-zA-Z_]*)/', '', $url); // We remove param lang from url
  1282. $url .= (preg_match('/\?/', $url) ? '&' : '?').'l=';
  1283. if (!preg_match('/^\//', $url)) {
  1284. $url = '/'.$url;
  1285. }
  1286. $HEIGHTOPTION = 40;
  1287. $MAXHEIGHT = 4 * $HEIGHTOPTION;
  1288. $nboflanguage = count($languagecodes);
  1289. $out = '<!-- componentSelectLang'.$htmlname.' -->'."\n";
  1290. $out .= '<style>';
  1291. $out .= '.componentSelectLang'.$htmlname.':hover { height: '.min($MAXHEIGHT, ($HEIGHTOPTION * $nboflanguage)).'px; overflow-x: hidden; overflow-y: '.((($HEIGHTOPTION * $nboflanguage) > $MAXHEIGHT) ? ' scroll' : 'hidden').'; }'."\n";
  1292. $out .= '.componentSelectLang'.$htmlname.' li { line-height: '.$HEIGHTOPTION.'px; }'."\n";
  1293. $out .= '.componentSelectLang'.$htmlname.' {
  1294. display: inline-block;
  1295. padding: 0;
  1296. height: '.$HEIGHTOPTION.'px;
  1297. overflow: hidden;
  1298. transition: all .3s ease;
  1299. margin: 0 0 0 0;
  1300. vertical-align: top;
  1301. }
  1302. .componentSelectLang'.$htmlname.':hover, .componentSelectLang'.$htmlname.':hover a { background-color: #fff; color: #000 !important; }
  1303. ul.componentSelectLang'.$htmlname.' { width: 150px; }
  1304. ul.componentSelectLang'.$htmlname.':hover .fa { visibility: hidden; }
  1305. .componentSelectLang'.$htmlname.' a { text-decoration: none; width: 100%; }
  1306. .componentSelectLang'.$htmlname.' li { display: block; padding: 0px 15px; margin-left: 0; margin-right: 0; }
  1307. .componentSelectLang'.$htmlname.' li:hover { background-color: #EEE; }
  1308. ';
  1309. $out .= '</style>';
  1310. $out .= '<ul class="componentSelectLang'.$htmlname.($morecss ? ' '.$morecss : '').'">';
  1311. if ($languagecodeselected) {
  1312. // Convert $languagecodeselected into a long language code
  1313. if (strlen($languagecodeselected) == 2) {
  1314. $languagecodeselected = (empty($arrayofspecialmainlanguages[$languagecodeselected]) ? $languagecodeselected.'_'.strtoupper($languagecodeselected) : $arrayofspecialmainlanguages[$languagecodeselected]);
  1315. }
  1316. $countrycode = strtolower(substr($languagecodeselected, -2));
  1317. $label = $weblangs->trans("Language_".$languagecodeselected);
  1318. if ($countrycode == 'us') {
  1319. $label = preg_replace('/\s*\(.*\)/', '', $label);
  1320. }
  1321. $out .= '<a href="'.$url.substr($languagecodeselected, 0, 2).'"><li><img height="12px" src="/medias/image/common/flags/'.$countrycode.'.png" style="margin-right: 5px;"/><span class="websitecomponentlilang">'.$label.'</span>';
  1322. $out .= '<span class="fa fa-caret-down" style="padding-left: 5px;" />';
  1323. $out .= '</li></a>';
  1324. }
  1325. $i = 0;
  1326. if (is_array($languagecodes)) {
  1327. foreach ($languagecodes as $languagecode) {
  1328. // Convert $languagecode into a long language code
  1329. if (strlen($languagecode) == 2) {
  1330. $languagecode = (empty($arrayofspecialmainlanguages[$languagecode]) ? $languagecode.'_'.strtoupper($languagecode) : $arrayofspecialmainlanguages[$languagecode]);
  1331. }
  1332. if ($languagecode == $languagecodeselected) {
  1333. continue; // Already output
  1334. }
  1335. $countrycode = strtolower(substr($languagecode, -2));
  1336. $label = $weblangs->trans("Language_".$languagecode);
  1337. if ($countrycode == 'us') {
  1338. $label = preg_replace('/\s*\(.*\)/', '', $label);
  1339. }
  1340. $out .= '<a href="'.$url.substr($languagecode, 0, 2).'"><li><img height="12px" src="/medias/image/common/flags/'.$countrycode.'.png" style="margin-right: 5px;"/><span class="websitecomponentlilang">'.$label.'</span>';
  1341. if (empty($i) && empty($languagecodeselected)) {
  1342. $out .= '<span class="fa fa-caret-down" style="padding-left: 5px;" />';
  1343. }
  1344. $out .= '</li></a>';
  1345. $i++;
  1346. }
  1347. }
  1348. $out .= '</ul>';
  1349. return $out;
  1350. }
  1351. }