OSDN Git Service

Merge branch 'skinnable-master'
[nucleus-jp/nucleus-next.git] / nucleus / libs / skinie.php
1 <<<<<<< HEAD
2 <?php\r
3 /*\r
4  * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)\r
5  * Copyright (C) 2002-2012 The Nucleus Group\r
6  *\r
7  * This program is free software; you can redistribute it and/or\r
8  * modify it under the terms of the GNU General Public License\r
9  * as published by the Free Software Foundation; either version 2\r
10  * of the License, or (at your option) any later version.\r
11  * (see nucleus/documentation/index.html#license for more info)\r
12  */\r
13 /**\r
14  *      This class contains two classes that can be used for importing and\r
15  *      exporting Nucleus skins: SKINIMPORT and SKINEXPORT\r
16  *\r
17  * @license http://nucleuscms.org/license.txt GNU General Public License\r
18  * @copyright Copyright (C) 2002-2012 The Nucleus Group\r
19  * @version $Id: skinie.php 1624 2012-01-09 11:36:20Z sakamocchi $\r
20  */\r
21 \r
22 class SkinImport\r
23 {\r
24         // hardcoded value (see constructor). When 1, interesting info about the\r
25         // parsing process is sent to the output\r
26         private $debug;\r
27         \r
28         // parser/file pointer\r
29         private $parser;\r
30         private $fp;\r
31         \r
32         // parset internal charset, US-ASCII/ISO-8859-1/UTF-8\r
33         private $parse_charset = 'UTF-8';\r
34         \r
35         // which data has been read?\r
36         private $metaDataRead;\r
37         private $allRead;\r
38         \r
39         // extracted data\r
40         private $skins;\r
41         private $templates;\r
42         private $info;\r
43         \r
44         // to maintain track of where we are inside the XML file\r
45         private $inXml;\r
46         private $inData;\r
47         private $inMeta;\r
48         private $inSkin;\r
49         private $inTemplate;\r
50         private $currentName;\r
51         private $currentPartName;\r
52         private $cdata;\r
53         \r
54         /**\r
55          * constructor initializes data structures\r
56          */\r
57         public function __construct()\r
58         {\r
59                 // disable magic_quotes_runtime if it's turned on\r
60                 //set_magic_quotes_runtime(0);\r
61                 if ( version_compare(PHP_VERSION, '5.3.0', '<') )\r
62                 {\r
63                         ini_set('magic_quotes_runtime', '0');\r
64                 }\r
65                 \r
66                 // debugging mode?\r
67                 $this->debug = 0;\r
68                 \r
69                 $this->reset();\r
70                 return;\r
71         }\r
72         \r
73         public function __destruct()\r
74         {\r
75                 return;\r
76         }\r
77         \r
78         public function reset()\r
79         {\r
80                 if ( $this->parser )\r
81                 {\r
82                         xml_parser_free($this->parser);\r
83                 }\r
84                 \r
85                 // XML file pointer\r
86                 $this->fp = 0;\r
87                 \r
88                 // which data has been read?\r
89                 $this->metaDataRead = 0;\r
90                 $this->allRead = 0;\r
91                 \r
92                 // to maintain track of where we are inside the XML file\r
93                 $this->inXml = 0;\r
94                 $this->inData = 0;\r
95                 $this->inMeta = 0;\r
96                 $this->inSkin = 0;\r
97                 $this->inTemplate = 0;\r
98                 $this->currentName = '';\r
99                 $this->currentPartName = '';\r
100                 \r
101                 // character data pile\r
102                 $this->cdata = '';\r
103                 \r
104                 // list of skinnames and templatenames (will be array of array)\r
105                 $this->skins = array();\r
106                 $this->templates = array();\r
107                 \r
108                 // extra info included in the XML files (e.g. installation notes)\r
109                 $this->info = '';\r
110                 \r
111                 // init XML parser, this parser deal with characters as encoded by UTF-8\r
112                 $this->parser = xml_parser_create($this->parse_charset);\r
113                 xml_set_object($this->parser, $this);\r
114                 xml_set_element_handler($this->parser, 'start_element', 'end_element');\r
115                 xml_set_character_data_handler($this->parser, 'character_data');\r
116                 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);\r
117                 \r
118                 return;\r
119         }\r
120         \r
121         /**\r
122          * Reads an XML file into memory\r
123          *\r
124          * @param $filename\r
125          *              Which file to read\r
126          * @param $metaOnly\r
127          *              Set to 1 when only the metadata needs to be read (optional, default 0)\r
128          */\r
129         public function readFile($filename, $metaOnly = 0)\r
130         {\r
131                 // open file\r
132                 $this->fp = @fopen($filename, 'r');\r
133                 if ( !$this->fp )\r
134                 {\r
135                         return _SKINIE_ERROR_FAILEDOPEN_FILEURL;\r
136                 }\r
137                 \r
138                 // here we go!\r
139                 $this->inXml = 1;\r
140                 \r
141                 $tempbuffer = null;\r
142                 \r
143                 while ( !feof($this->fp) )\r
144                 {\r
145                         $tempbuffer .= fread($this->fp, 4096);\r
146                 }\r
147                 fclose($this->fp);\r
148                 \r
149                 /*\r
150                  * NOTE: conver character set.\r
151                  * We hope all characters in the file also includes UTF-8 coded character set,\r
152                  *  because this PHP extension implements support for James Clark's expat in PHP\r
153                  *   and it supports juust US-ASCII, ISO-8859-1, UTF-8 character coding scheme.\r
154                  */\r
155                 if ( i18n::get_current_charset() != $this->parse_charset )\r
156                 {\r
157                         $tempbuffer = i18n::convert($tempbuffer, i18n::get_current_charset(), $this->parse_charset);\r
158                 }\r
159                 \r
160                 $temp = tmpfile();\r
161                 fwrite($temp, $tempbuffer);\r
162                 rewind($temp);\r
163                 \r
164                 while ( ($buffer = fread($temp, 4096) )\r
165                  && (!$metaOnly || ($metaOnly && !$this->metaDataRead)) )\r
166                  {\r
167                         $err = xml_parse( $this->parser, $buffer, feof($temp) );\r
168                         if ( !$err && $this->debug )\r
169                         {\r
170                                 echo 'ERROR: ', xml_error_string(xml_get_error_code($this->parser)), '<br />';\r
171                         }\r
172                 }\r
173                 \r
174                 // all done\r
175                 $this->inXml = 0;\r
176                 fclose($temp);\r
177                 \r
178                 return;\r
179         }\r
180         \r
181         /**\r
182          * Returns the list of skin names\r
183          */\r
184         public function getSkinNames()\r
185         {\r
186                 return array_keys($this->skins);\r
187         }\r
188         \r
189         /**\r
190          * Returns the list of template names\r
191          */\r
192         public function getTemplateNames()\r
193         {\r
194                 return array_keys($this->templates);\r
195         }\r
196         \r
197         /**\r
198          * Returns the extra information included in the XML file\r
199          */\r
200         public function getInfo()\r
201         {\r
202                 return $this->info;\r
203         }\r
204         \r
205         /**\r
206          * Writes the skins and templates to the database\r
207          *\r
208          * @param $allowOverwrite\r
209          *              set to 1 when allowed to overwrite existing skins with the same name\r
210          *              (default = 0)\r
211          */\r
212         public function writeToDatabase($allowOverwrite = 0)\r
213         {\r
214                 $existingSkins = $this->checkSkinNameClashes();\r
215                 $existingTemplates = $this->checkTemplateNameClashes();\r
216                 $invalidSkinNames = $this->checkSkinNamesValid();\r
217                 $invalidTemplateNames = $this->checkTemplateNamesValid();\r
218                 \r
219                 // if there are invalid skin or template names, stop executioin and return and error\r
220                 if ( (sizeof($invalidSkinNames) > 0) || (sizeof($invalidTemplateNames) > 0) )\r
221                 {\r
222                         $inames_error = "<p>"._SKINIE_INVALID_NAMES_DETECTED."</p>\n";\r
223                         $inames_error .= "<ul>";\r
224                         foreach( $invalidSkinNames as $sName )\r
225                         {\r
226                                 $inames_error .= "<li>".Entity::hsc($sName)."</li>";\r
227                         }\r
228                         foreach( $invalidTemplateNames as $sName )\r
229                         {\r
230                                 $inames_error .= "<li>".Entity::hsc($sName)."</li>";\r
231                         }\r
232                         $inames_error .= "</ul>";\r
233                         return $inames_error;\r
234                 }\r
235                 \r
236                 // if not allowed to overwrite, check if any nameclashes exists\r
237                 if ( !$allowOverwrite )\r
238                 {\r
239                         if ( (sizeof($existingSkins) > 0) || (sizeof($existingTemplates) > 0) )\r
240                         {\r
241                                 return _SKINIE_NAME_CLASHES_DETECTED;\r
242                         }\r
243                 }\r
244                 \r
245                 foreach ( $this->skins as $skinName => $data )\r
246                 {\r
247                         // 1. if exists: delete all part data, update desc data\r
248                         //    if not exists: create desc\r
249                         if ( in_array($skinName, $existingSkins) )\r
250                         {\r
251                                 $skinObj = SKIN::createFromName($skinName);\r
252                                 \r
253                                 // delete all parts of the skin\r
254                                 $skinObj->deleteAllParts();\r
255                                 \r
256                                 // update general info\r
257                                 $skinObj->updateGeneralInfo(\r
258                                         $skinName,\r
259                                         $data['description'],\r
260                                         $data['type'],\r
261                                         $data['includeMode'],\r
262                                         $data['includePrefix']\r
263                                 );\r
264                         }\r
265                         else\r
266                         {\r
267                                 $skinid = SKIN::createNew(\r
268                                         $skinName,\r
269                                         $data['description'],\r
270                                         $data['type'],\r
271                                         $data['includeMode'],\r
272                                         $data['includePrefix']\r
273                                 );\r
274                                 $skinObj = new SKIN($skinid);\r
275                         }\r
276                         \r
277                         // 2. add parts\r
278                         foreach ( $data['parts'] as $partName => $partContent )\r
279                         {\r
280                                 $skinObj->update($partName, $partContent);\r
281                         }\r
282                 }\r
283                 \r
284                 foreach ( $this->templates as $templateName => $data )\r
285                 {\r
286                         // 1. if exists: delete all part data, update desc data\r
287                         //    if not exists: create desc\r
288                         if ( in_array($templateName, $existingTemplates) )\r
289                         {\r
290                                 $templateObj = Template::createFromName($templateName);\r
291                                 \r
292                                 // delete all parts of the template\r
293                                 $templateObj->deleteAllParts();\r
294                                 \r
295                                 // update general info\r
296                                 $templateObj->updateGeneralInfo($templateName, $data['description']);\r
297                         }\r
298                         else\r
299                         {\r
300                                 $templateid = Template::createNew($templateName, $data['description']);\r
301                                 $templateObj = new Template($templateid);\r
302                         }\r
303                         \r
304                         // 2. add parts\r
305                         foreach ( $data['parts'] as $partName => $partContent )\r
306                         {\r
307                                 $templateObj->update($partName, $partContent);\r
308                         }\r
309                 }\r
310                 return;\r
311         }\r
312         \r
313         /**\r
314           * returns an array of all the skin nameclashes (empty array when no name clashes)\r
315           */\r
316         public function checkSkinNameClashes()\r
317         {\r
318                 $clashes = array();\r
319                 \r
320                 foreach ( $this->skins as $skinName => $data )\r
321                 {\r
322                         if ( SKIN::exists($skinName) )\r
323                         {\r
324                                 array_push($clashes, $skinName);\r
325                         }\r
326                 }\r
327                 return $clashes;\r
328         }\r
329         \r
330         /**\r
331           * returns an array of all the template nameclashes\r
332           * (empty array when no name clashes)\r
333           */\r
334         public function checkTemplateNameClashes()\r
335         {\r
336                 $clashes = array();\r
337                 \r
338                 foreach ( $this->templates as $templateName => $data )\r
339                 {\r
340                         if ( Template::exists($templateName) )\r
341                         {\r
342                                 array_push($clashes, $templateName);\r
343                         }\r
344                 }\r
345                 return $clashes;\r
346         }\r
347         \r
348         /**\r
349           * returns an array of all the invalid skin names (empty array when no invalid names )\r
350           */\r
351         private function checkSkinNamesValid()\r
352         {\r
353                 $notValid = array();\r
354                 \r
355                 foreach ( $this->skins as $skinName => $data )\r
356                 {\r
357                         if ( !isValidSkinName($skinName) )\r
358                         {\r
359                                 array_push($notValid, $skinName);\r
360                         }\r
361                 }\r
362                 return $notValid;\r
363         }\r
364         \r
365         /**\r
366           * returns an array of all the invalid template names (empty array when no invalid names )\r
367           */\r
368         private function checkTemplateNamesValid()\r
369         {\r
370                 $notValid = array();\r
371                 \r
372                 foreach ( $this->templates as $templateName => $data )\r
373                 {\r
374                         if ( !isValidTemplateName($templateName) )\r
375                         {\r
376                                 array_push($notValid, $templateName);\r
377                         }\r
378                 }\r
379                 return $notValid;\r
380         }\r
381         \r
382         /**\r
383          * Called by XML parser for each new start element encountered\r
384          */\r
385         private function start_element($parser, $name, $attrs)\r
386         {\r
387                 foreach( $attrs as $key=>$value )\r
388                 {\r
389                         if ( $this->parse_charset != i18n::get_current_charset() )\r
390                         {\r
391                                 $name = i18n::convert($name, $this->parse_charset, i18n::get_current_charset());\r
392                                 $value = i18n::convert($value, $this->parse_charset, i18n::get_current_charset());\r
393                         }\r
394                         \r
395                         $attrs[$key] = $value;\r
396                 }\r
397                 \r
398                 if ( $this->debug )\r
399                 {\r
400                         echo 'START: ', Entity::hsc($name), '<br />';\r
401                 }\r
402                 \r
403                 switch ( $name )\r
404                 {\r
405                         case 'nucleusskin':\r
406                                 $this->inData = 1;\r
407                                 break;\r
408                         case 'meta':\r
409                                 $this->inMeta = 1;\r
410                                 break;\r
411                         case 'info':\r
412                                 // no action needed\r
413                                 break;\r
414                         case 'skin':\r
415                                 if ( !$this->inMeta )\r
416                                 {\r
417                                         $this->inSkin = 1;\r
418                                         $this->currentName = $attrs['name'];\r
419                                         $this->skins[$this->currentName]['type'] = $attrs['type'];\r
420                                         $this->skins[$this->currentName]['includeMode'] = $attrs['includeMode'];\r
421                                         $this->skins[$this->currentName]['includePrefix'] = $attrs['includePrefix'];\r
422                                         $this->skins[$this->currentName]['parts'] = array();\r
423                                 }\r
424                                 else\r
425                                 {\r
426                                         $this->skins[$attrs['name']] = array();\r
427                                         $this->skins[$attrs['name']]['parts'] = array();\r
428                                 }\r
429                                 break;\r
430                         case 'template':\r
431                                 if ( !$this->inMeta )\r
432                                 {\r
433                                         $this->inTemplate = 1;\r
434                                         $this->currentName = $attrs['name'];\r
435                                         $this->templates[$this->currentName]['parts'] = array();\r
436                                 }\r
437                                 else\r
438                                 {\r
439                                         $this->templates[$attrs['name']] = array();\r
440                                         $this->templates[$attrs['name']]['parts'] = array();\r
441                                 }\r
442                                 break;\r
443                         case 'description':\r
444                                 // no action needed\r
445                                 break;\r
446                         case 'part':\r
447                                 $this->currentPartName = $attrs['name'];\r
448                                 break;\r
449                         default:\r
450                                 echo _SKINIE_SEELEMENT_UNEXPECTEDTAG . Entity::hsc($name) . '<br />';\r
451                                 break;\r
452                 }\r
453                 // character data never contains other tags\r
454                 $this->clear_character_data();\r
455                 return;\r
456         }\r
457         \r
458         /**\r
459           * Called by the XML parser for each closing tag encountered\r
460           */\r
461         private function end_element($parser, $name)\r
462         {\r
463                 if ( $this->debug )\r
464                 {\r
465                         echo 'END: ' . Entity::hsc($name) . '<br />';\r
466                 }\r
467                 \r
468                 if ( $this->parse_charset != i18n::get_current_charset() )\r
469                 {\r
470                         $name = i18n::convert($name, $this->parse_charset, i18n::get_current_charset());\r
471                         $charset_data = i18n::convert($this->get_character_data(), $this->parse_charset, i18n::get_current_charset());\r
472                 }\r
473                 else\r
474                 {\r
475                         $charset_data = $this->get_character_data();\r
476                 }\r
477                 \r
478                 switch ( $name )\r
479                 {\r
480                         case 'nucleusskin':\r
481                                 $this->inData = 0;\r
482                                 $this->allRead = 1;\r
483                                 break;\r
484                         case 'meta':\r
485                                 $this->inMeta = 0;\r
486                                 $this->metaDataRead = 1;\r
487                                 break;\r
488                         case 'info':\r
489                                 $this->info = $charset_data;\r
490                         case 'skin':\r
491                                 if ( !$this->inMeta )\r
492                                 {\r
493                                         $this->inSkin = 0;\r
494                                 }\r
495                                 break;\r
496                         case 'template':\r
497                                 if ( !$this->inMeta )\r
498                                 {\r
499                                         $this->inTemplate = 0;\r
500                                 }\r
501                                 break;\r
502                         case 'description':\r
503                                 if ( $this->inSkin )\r
504                                 {\r
505                                         $this->skins[$this->currentName]['description'] = $charset_data;\r
506                                 }\r
507                                 else\r
508                                 {\r
509                                         $this->templates[$this->currentName]['description'] = $charset_data;\r
510                                 }\r
511                                 break;\r
512                         case 'part':\r
513                                 if ( $this->inSkin )\r
514                                 {\r
515                                         $this->skins[$this->currentName]['parts'][$this->currentPartName] = $charset_data;\r
516                                 }\r
517                                 else\r
518                                 {\r
519                                         $this->templates[$this->currentName]['parts'][$this->currentPartName] = $charset_data;\r
520                                 }\r
521                                 break;\r
522                         default:\r
523                                 echo _SKINIE_SEELEMENT_UNEXPECTEDTAG . Entity::hsc($name) . '<br />';\r
524                                 break;\r
525                 }\r
526                 $this->clear_character_data();\r
527                 return;\r
528         }\r
529         \r
530         /**\r
531          * Called by XML parser for data inside elements\r
532          */\r
533         private function character_data ($parser, $data)\r
534         {\r
535                 if ( $this->debug )\r
536                 {\r
537                         echo 'NEW DATA: ' . Entity::hsc($data) . '<br />';\r
538                 }\r
539                 $this->cdata .= $data;\r
540                 return;\r
541         }\r
542         \r
543         /**\r
544          * Returns the data collected so far\r
545          */\r
546         private function get_character_data()\r
547         {\r
548                 return $this->cdata;\r
549         }\r
550         \r
551         /**\r
552          * Clears the data buffer\r
553          */\r
554         private function clear_character_data()\r
555         {\r
556                 $this->cdata = '';\r
557                 return;\r
558         }\r
559         \r
560         /**\r
561          * Static method that looks for importable XML files in subdirs of the given dir\r
562          */\r
563         static public function searchForCandidates($dir)\r
564         {\r
565                 $candidates = array();\r
566                 \r
567                 $dirhandle = opendir($dir);\r
568                 while ( $filename = readdir($dirhandle) )\r
569                 {\r
570                         if ( @is_dir($dir . $filename) && ($filename != '.') && ($filename != '..') )\r
571                         {\r
572                                 $xml_file = $dir . $filename . '/skinbackup.xml';\r
573                                 if ( file_exists($xml_file) && is_readable($xml_file) )\r
574                                 {\r
575                                         //$xml_file;\r
576                                         $candidates[$filename] = $filename;\r
577                                 }\r
578                                 \r
579                                 // backwards compatibility\r
580                                 $xml_file = $dir . $filename . '/skindata.xml';\r
581                                 if ( file_exists($xml_file) && is_readable($xml_file) )\r
582                                 {\r
583                                         //$xml_file;\r
584                                         $candidates[$filename] = $filename;\r
585                                 }\r
586                         }\r
587                 }\r
588                 closedir($dirhandle);\r
589                 return $candidates;\r
590         }\r
591 }\r
592 \r
593 class SkinExport\r
594 {\r
595         private $templates;\r
596         private $skins;\r
597         private $info;\r
598         \r
599         /**\r
600          * Constructor initializes data structures\r
601          */\r
602         public function __construct()\r
603         {\r
604                 // list of templateIDs to export\r
605                 $this->templates = array();\r
606                 \r
607                 // list of skinIDs to export\r
608                 $this->skins = array();\r
609                 \r
610                 // extra info to be in XML file\r
611                 $this->info = '';\r
612         }\r
613         \r
614         /**\r
615          * Adds a template to be exported\r
616          *\r
617          * @param id\r
618          *              template ID\r
619          * @result false when no such ID exists\r
620          */\r
621         public function addTemplate($id)\r
622         {\r
623                 if ( !Template::existsID($id) )\r
624                 {\r
625                         return 0;\r
626                 }\r
627                 \r
628                 $this->templates[$id] = Template::getNameFromId($id);\r
629                 return 1;\r
630         }\r
631         \r
632         /**\r
633          * Adds a skin to be exported\r
634          *\r
635          * @param id\r
636          *              skin ID\r
637          * @result false when no such ID exists\r
638          */\r
639         public function addSkin($id)\r
640         {\r
641                 if ( !SKIN::existsID($id) )\r
642                 {\r
643                         return 0;\r
644                 }\r
645                 \r
646                 $this->skins[$id] = SKIN::getNameFromId($id);\r
647                 return 1;\r
648         }\r
649         \r
650         /**\r
651          * Sets the extra info to be included in the exported file\r
652          */\r
653         public function setInfo($info)\r
654         {\r
655                 $this->info = $info;\r
656         }\r
657         \r
658         /**\r
659          * Outputs the XML contents of the export file\r
660          *\r
661          * @param $setHeaders\r
662          *              set to 0 if you don't want to send out headers\r
663          *              (optional, default 1)\r
664          */\r
665         public function export($setHeaders = 1)\r
666         {\r
667                 if ( $setHeaders )\r
668                 {\r
669                         // make sure the mimetype is correct, and that the data does not show up\r
670                         // in the browser, but gets saved into and XML file (popup download window)\r
671                         header('Content-Type: text/xml; charset=' . i18n::get_current_charset());\r
672                         header('Content-Disposition: attachment; filename="skinbackup.xml"');\r
673                         header('Expires: 0');\r
674                         header('Pragma: no-cache');\r
675                 }\r
676                 \r
677                 echo "<nucleusskin>\n";\r
678                 \r
679                 // meta\r
680                 echo "\t<meta>\n";\r
681                 // skins\r
682                 foreach ( $this->skins as $skinId => $skinName )\r
683                 {\r
684                         echo "\t\t" . '<skin name="' . Entity::hsc($skinName) . '" />' . "\n";\r
685                 }\r
686                 // templates\r
687                 foreach ( $this->templates as $templateId => $templateName )\r
688                 {\r
689                         echo "\t\t" . '<template name="' . Entity::hsc($templateName) . '" />' . "\n";\r
690                 }\r
691                 // extra info\r
692                 if ( $this->info )\r
693                 {\r
694                         echo "\t\t<info><![CDATA[" . $this->info . "]]></info>\n";\r
695                 }\r
696                 echo "\t</meta>\n\n\n";\r
697                 \r
698                 // contents skins\r
699                 foreach ($this->skins as $skinId => $skinName)\r
700                 {\r
701                         $skinId = intval($skinId);\r
702                         $skinObj = new SKIN($skinId);\r
703                         \r
704                         echo "\t" . '<skin name="' . Entity::hsc($skinName) . '" type="' . Entity::hsc($skinObj->getContentType()) . '" includeMode="' . Entity::hsc($skinObj->getIncludeMode()) . '" includePrefix="' . Entity::hsc($skinObj->getIncludePrefix()) . '">' . "\n";\r
705                         echo "\t\t<description>" . Entity::hsc($skinObj->getDescription()) . "</description>\n";\r
706                         \r
707                         $res = DB::getResult('SELECT stype, scontent FROM '. sql_table('skin') .' WHERE sdesc=' . $skinId);\r
708                         foreach ( $res as $row )\r
709                         {\r
710                                 echo "\t\t" . '<part name="',Entity::hsc($row['stype']) . '">';\r
711                                 echo '<![CDATA[' . $this->escapeCDATA($row['scontent']) . ']]>';\r
712                                 echo "</part>\n\n";\r
713                         }\r
714                         echo "\t</skin>\n\n\n";\r
715                 }\r
716                 \r
717                 // contents templates\r
718                 foreach ( $this->templates as $templateId => $templateName )\r
719                 {\r
720                         $templateId = intval($templateId);\r
721                         \r
722                         echo "\t" . '<template name="' . Entity::hsc($templateName) . '">' . "\n";\r
723                         echo "\t\t<description>" . Entity::hsc(Template::getDesc($templateId)) . "</description>\n";\r
724                         \r
725                         $res = DB::getResult('SELECT tpartname, tcontent FROM '. sql_table('template') .' WHERE tdesc=' . $templateId);\r
726                         foreach ( $res as $row )\r
727                         {\r
728                                 echo "\t\t" . '<part name="' . Entity::hsc($row['tpartname']) . '">';\r
729                                 echo '<![CDATA[' . $this->escapeCDATA($row['tcontent']) . ']]>';\r
730                                 echo "</part>\n\n";\r
731                         }\r
732                         \r
733                         echo "\t</template>\n\n\n";\r
734                 }\r
735                 echo '</nucleusskin>';\r
736         }\r
737         \r
738         /**\r
739          * Escapes CDATA content so it can be included in another CDATA section\r
740          */\r
741         private function escapeCDATA($cdata)\r
742         {\r
743                 return preg_replace('/]]>/', ']]]]><![CDATA[>', $cdata);\r
744         }\r
745 }\r
746 =======
747 <?php
748 /*
749  * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
750  * Copyright (C) 2002-2009 The Nucleus Group
751  *
752  * This program is free software; you can redistribute it and/or
753  * modify it under the terms of the GNU General Public License
754  * as published by the Free Software Foundation; either version 2
755  * of the License, or (at your option) any later version.
756  * (see nucleus/documentation/index.html#license for more info)
757  */
758 /**
759  *      This class contains two classes that can be used for importing and
760  *      exporting Nucleus skins: SKINIMPORT and SKINEXPORT
761  *
762  * @license http://nucleuscms.org/license.txt GNU General Public License
763  * @copyright Copyright (C) 2002-2009 The Nucleus Group
764  * @version $Id: skinie.php 1883 2012-06-17 07:55:47Z sakamocchi $
765  */
766
767 class SkinImport
768 {
769         // hardcoded value (see constructor). When 1, interesting info about the
770         // parsing process is sent to the output
771         private $debug;
772         
773         // parser/file pointer
774         private $parser;
775         private $fp;
776         
777         // parset internal charset, US-ASCII/ISO-8859-1/UTF-8
778         private $parse_charset = 'UTF-8';
779         
780         // which data has been read?
781         private $metaDataRead;
782         private $allRead;
783         
784         // extracted data
785         private $skins;
786         private $templates;
787         private $info;
788         
789         // to maintain track of where we are inside the XML file
790         private $inXml;
791         private $inData;
792         private $inMeta;
793         private $inSkin;
794         private $inTemplate;
795         private $currentName;
796         private $currentPartName;
797         private $cdata;
798         
799         /**
800          * constructor initializes data structures
801          */
802         public function __construct()
803         {
804                 // disable magic_quotes_runtime if it's turned on
805                 //set_magic_quotes_runtime(0);
806                 if ( version_compare(PHP_VERSION, '5.3.0', '<') )
807                 {
808                         ini_set('magic_quotes_runtime', '0');
809                 }
810                 
811                 // debugging mode?
812                 $this->debug = 0;
813                 
814                 $this->reset();
815                 return;
816         }
817         
818         public function __destruct()
819         {
820                 return;
821         }
822         
823         public function reset()
824         {
825                 if ( $this->parser )
826                 {
827                         xml_parser_free($this->parser);
828                 }
829                 
830                 // XML file pointer
831                 $this->fp = 0;
832                 
833                 // which data has been read?
834                 $this->metaDataRead = 0;
835                 $this->allRead = 0;
836                 
837                 // to maintain track of where we are inside the XML file
838                 $this->inXml = 0;
839                 $this->inData = 0;
840                 $this->inMeta = 0;
841                 $this->inSkin = 0;
842                 $this->inTemplate = 0;
843                 $this->currentName = '';
844                 $this->currentPartName = '';
845                 
846                 // character data pile
847                 $this->cdata = '';
848                 
849                 // list of skinnames and templatenames (will be array of array)
850                 $this->skins = array();
851                 $this->templates = array();
852                 
853                 // extra info included in the XML files (e.g. installation notes)
854                 $this->info = '';
855                 
856                 // init XML parser, this parser deal with characters as encoded by UTF-8
857                 $this->parser = xml_parser_create($this->parse_charset);
858                 xml_set_object($this->parser, $this);
859                 xml_set_element_handler($this->parser, 'start_element', 'end_element');
860                 xml_set_character_data_handler($this->parser, 'character_data');
861                 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
862                 
863                 return;
864         }
865         
866         /**
867          * Reads an XML file into memory
868          *
869          * @param $filename
870          *              Which file to read
871          * @param $metaOnly
872          *              Set to 1 when only the metadata needs to be read (optional, default 0)
873          */
874         public function readFile($filename, $metaOnly = 0)
875         {
876                 // open file
877                 $this->fp = @fopen($filename, 'r');
878                 if ( !$this->fp )
879                 {
880                         return _SKINIE_ERROR_FAILEDOPEN_FILEURL;
881                 }
882                 
883                 // here we go!
884                 $this->inXml = 1;
885                 
886                 $tempbuffer = null;
887                 
888                 while ( !feof($this->fp) )
889                 {
890                         $tempbuffer .= fread($this->fp, 4096);
891                 }
892                 fclose($this->fp);
893                 
894                 /*
895                  * NOTE: conver character set.
896                  * We hope all characters in the file also includes UTF-8 coded character set,
897                  *  because this PHP extension implements support for James Clark's expat in PHP
898                  *   and it supports juust US-ASCII, ISO-8859-1, UTF-8 character coding scheme.
899                  */
900                 if ( i18n::get_current_charset() != $this->parse_charset )
901                 {
902                         $tempbuffer = i18n::convert($tempbuffer, i18n::get_current_charset(), $this->parse_charset);
903                 }
904                 
905                 $temp = tmpfile();
906                 fwrite($temp, $tempbuffer);
907                 rewind($temp);
908                 
909                 while ( ($buffer = fread($temp, 4096) )
910                  && (!$metaOnly || ($metaOnly && !$this->metaDataRead)) )
911                  {
912                         $err = xml_parse( $this->parser, $buffer, feof($temp) );
913                         if ( !$err && $this->debug )
914                         {
915                                 echo 'ERROR: ', xml_error_string(xml_get_error_code($this->parser)), '<br />';
916                         }
917                 }
918                 
919                 // all done
920                 $this->inXml = 0;
921                 fclose($temp);
922                 
923                 return;
924         }
925         
926         /**
927          * Returns the list of skin names
928          */
929         public function getSkinNames()
930         {
931                 return array_keys($this->skins);
932         }
933         
934         /**
935          * Returns the list of template names
936          */
937         public function getTemplateNames()
938         {
939                 return array_keys($this->templates);
940         }
941         
942         /**
943          * Returns the extra information included in the XML file
944          */
945         public function getInfo()
946         {
947                 return $this->info;
948         }
949         
950         /**
951          * Writes the skins and templates to the database
952          *
953          * @param $allowOverwrite
954          *              set to 1 when allowed to overwrite existing skins with the same name
955          *              (default = 0)
956          */
957         public function writeToDatabase($allowOverwrite = 0)
958         {
959                 global $manager;
960                 
961                 $existingSkins = $this->checkSkinNameClashes();
962                 $existingTemplates = $this->checkTemplateNameClashes();
963                 $invalidSkinNames = $this->checkSkinNamesValid();
964                 $invalidTemplateNames = $this->checkTemplateNamesValid();
965                 
966                 // if there are invalid skin or template names, stop executioin and return and error
967                 if ( (sizeof($invalidSkinNames) > 0) || (sizeof($invalidTemplateNames) > 0) )
968                 {
969                         $inames_error = "<p>"._SKINIE_INVALID_NAMES_DETECTED."</p>\n";
970                         $inames_error .= "<ul>";
971                         foreach( $invalidSkinNames as $sName )
972                         {
973                                 $inames_error .= "<li>".Entity::hsc($sName)."</li>";
974                         }
975                         foreach( $invalidTemplateNames as $sName )
976                         {
977                                 $inames_error .= "<li>".Entity::hsc($sName)."</li>";
978                         }
979                         $inames_error .= "</ul>";
980                         return $inames_error;
981                 }
982                 
983                 // if not allowed to overwrite, check if any nameclashes exists
984                 if ( !$allowOverwrite )
985                 {
986                         if ( (sizeof($existingSkins) > 0) || (sizeof($existingTemplates) > 0) )
987                         {
988                                 return _SKINIE_NAME_CLASHES_DETECTED;
989                         }
990                 }
991                 
992                 foreach ( $this->skins as $skinName => $data )
993                 {
994                         // 1. if exists: delete all part data, update desc data
995                         //    if not exists: create desc
996                         if ( in_array($skinName, $existingSkins) )
997                         {
998                                 $skinObj = SKIN::createFromName($skinName);
999                                 
1000                                 // delete all parts of the skin
1001                                 $skinObj->deleteAllParts();
1002                                 
1003                                 // update general info
1004                                 $skinObj->updateGeneralInfo(
1005                                         $skinName,
1006                                         $data['description'],
1007                                         $data['type'],
1008                                         $data['includeMode'],
1009                                         $data['includePrefix']
1010                                 );
1011                         }
1012                         else
1013                         {
1014                                 $skinid = SKIN::createNew(
1015                                         $skinName,
1016                                         $data['description'],
1017                                         $data['type'],
1018                                         $data['includeMode'],
1019                                         $data['includePrefix']
1020                                 );
1021                                 $skinObj =& $manager->getSkin($skinid);
1022                         }
1023                         
1024                         // 2. add parts
1025                         foreach ( $data['parts'] as $partName => $partContent )
1026                         {
1027                                 $skinObj->update($partName, $partContent);
1028                         }
1029                 }
1030                 
1031                 foreach ( $this->templates as $templateName => $data )
1032                 {
1033                         // 1. if exists: delete all part data, update desc data
1034                         //    if not exists: create desc
1035                         if ( in_array($templateName, $existingTemplates) )
1036                         {
1037                                 $templateObj = Template::createFromName($templateName);
1038                                 
1039                                 // delete all parts of the template
1040                                 $templateObj->deleteAllParts();
1041                                 
1042                                 // update general info
1043                                 $templateObj->updateGeneralInfo($templateName, $data['description']);
1044                         }
1045                         else
1046                         {
1047                                 $templateid = Template::createNew($templateName, $data['description']);
1048                                 $templateObj = new Template($templateid);
1049                         }
1050                         
1051                         // 2. add parts
1052                         foreach ( $data['parts'] as $partName => $partContent )
1053                         {
1054                                 $templateObj->update($partName, $partContent);
1055                         }
1056                 }
1057                 return;
1058         }
1059         
1060         /**
1061           * returns an array of all the skin nameclashes (empty array when no name clashes)
1062           */
1063         public function checkSkinNameClashes()
1064         {
1065                 $clashes = array();
1066                 
1067                 foreach ( $this->skins as $skinName => $data )
1068                 {
1069                         if ( SKIN::exists($skinName) )
1070                         {
1071                                 array_push($clashes, $skinName);
1072                         }
1073                 }
1074                 return $clashes;
1075         }
1076         
1077         /**
1078           * returns an array of all the template nameclashes
1079           * (empty array when no name clashes)
1080           */
1081         public function checkTemplateNameClashes()
1082         {
1083                 $clashes = array();
1084                 
1085                 foreach ( $this->templates as $templateName => $data )
1086                 {
1087                         if ( Template::exists($templateName) )
1088                         {
1089                                 array_push($clashes, $templateName);
1090                         }
1091                 }
1092                 return $clashes;
1093         }
1094         
1095         /**
1096           * returns an array of all the invalid skin names (empty array when no invalid names )
1097           */
1098         private function checkSkinNamesValid()
1099         {
1100                 $notValid = array();
1101                 
1102                 foreach ( $this->skins as $skinName => $data )
1103                 {
1104                         if ( !isValidSkinName($skinName) )
1105                         {
1106                                 array_push($notValid, $skinName);
1107                         }
1108                 }
1109                 return $notValid;
1110         }
1111         
1112         /**
1113           * returns an array of all the invalid template names (empty array when no invalid names )
1114           */
1115         private function checkTemplateNamesValid()
1116         {
1117                 $notValid = array();
1118                 
1119                 foreach ( $this->templates as $templateName => $data )
1120                 {
1121                         if ( !isValidTemplateName($templateName) )
1122                         {
1123                                 array_push($notValid, $templateName);
1124                         }
1125                 }
1126                 return $notValid;
1127         }
1128         
1129         /**
1130          * Called by XML parser for each new start element encountered
1131          */
1132         private function start_element($parser, $name, $attrs)
1133         {
1134                 foreach( $attrs as $key=>$value )
1135                 {
1136                         if ( $this->parse_charset != i18n::get_current_charset() )
1137                         {
1138                                 $name = i18n::convert($name, $this->parse_charset, i18n::get_current_charset());
1139                                 $value = i18n::convert($value, $this->parse_charset, i18n::get_current_charset());
1140                         }
1141                         
1142                         $attrs[$key] = $value;
1143                 }
1144                 
1145                 if ( $this->debug )
1146                 {
1147                         echo 'START: ', Entity::hsc($name), '<br />';
1148                 }
1149                 
1150                 switch ( $name )
1151                 {
1152                         case 'nucleusskin':
1153                                 $this->inData = 1;
1154                                 break;
1155                         case 'meta':
1156                                 $this->inMeta = 1;
1157                                 break;
1158                         case 'info':
1159                                 // no action needed
1160                                 break;
1161                         case 'skin':
1162                                 if ( !$this->inMeta )
1163                                 {
1164                                         $this->inSkin = 1;
1165                                         $this->currentName = $attrs['name'];
1166                                         $this->skins[$this->currentName]['type'] = $attrs['type'];
1167                                         $this->skins[$this->currentName]['includeMode'] = $attrs['includeMode'];
1168                                         $this->skins[$this->currentName]['includePrefix'] = $attrs['includePrefix'];
1169                                         $this->skins[$this->currentName]['parts'] = array();
1170                                 }
1171                                 else
1172                                 {
1173                                         $this->skins[$attrs['name']] = array();
1174                                         $this->skins[$attrs['name']]['parts'] = array();
1175                                 }
1176                                 break;
1177                         case 'template':
1178                                 if ( !$this->inMeta )
1179                                 {
1180                                         $this->inTemplate = 1;
1181                                         $this->currentName = $attrs['name'];
1182                                         $this->templates[$this->currentName]['parts'] = array();
1183                                 }
1184                                 else
1185                                 {
1186                                         $this->templates[$attrs['name']] = array();
1187                                         $this->templates[$attrs['name']]['parts'] = array();
1188                                 }
1189                                 break;
1190                         case 'description':
1191                                 // no action needed
1192                                 break;
1193                         case 'part':
1194                                 $this->currentPartName = $attrs['name'];
1195                                 break;
1196                         default:
1197                                 echo _SKINIE_SEELEMENT_UNEXPECTEDTAG . Entity::hsc($name) . '<br />';
1198                                 break;
1199                 }
1200                 // character data never contains other tags
1201                 $this->clear_character_data();
1202                 return;
1203         }
1204         
1205         /**
1206           * Called by the XML parser for each closing tag encountered
1207           */
1208         private function end_element($parser, $name)
1209         {
1210                 if ( $this->debug )
1211                 {
1212                         echo 'END: ' . Entity::hsc($name) . '<br />';
1213                 }
1214                 
1215                 if ( $this->parse_charset != i18n::get_current_charset() )
1216                 {
1217                         $name = i18n::convert($name, $this->parse_charset, i18n::get_current_charset());
1218                         $charset_data = i18n::convert($this->get_character_data(), $this->parse_charset, i18n::get_current_charset());
1219                 }
1220                 else
1221                 {
1222                         $charset_data = $this->get_character_data();
1223                 }
1224                 
1225                 switch ( $name )
1226                 {
1227                         case 'nucleusskin':
1228                                 $this->inData = 0;
1229                                 $this->allRead = 1;
1230                                 break;
1231                         case 'meta':
1232                                 $this->inMeta = 0;
1233                                 $this->metaDataRead = 1;
1234                                 break;
1235                         case 'info':
1236                                 $this->info = $charset_data;
1237                         case 'skin':
1238                                 if ( !$this->inMeta )
1239                                 {
1240                                         $this->inSkin = 0;
1241                                 }
1242                                 break;
1243                         case 'template':
1244                                 if ( !$this->inMeta )
1245                                 {
1246                                         $this->inTemplate = 0;
1247                                 }
1248                                 break;
1249                         case 'description':
1250                                 if ( $this->inSkin )
1251                                 {
1252                                         $this->skins[$this->currentName]['description'] = $charset_data;
1253                                 }
1254                                 else
1255                                 {
1256                                         $this->templates[$this->currentName]['description'] = $charset_data;
1257                                 }
1258                                 break;
1259                         case 'part':
1260                                 if ( $this->inSkin )
1261                                 {
1262                                         $this->skins[$this->currentName]['parts'][$this->currentPartName] = $charset_data;
1263                                 }
1264                                 else
1265                                 {
1266                                         $this->templates[$this->currentName]['parts'][$this->currentPartName] = $charset_data;
1267                                 }
1268                                 break;
1269                         default:
1270                                 echo _SKINIE_SEELEMENT_UNEXPECTEDTAG . Entity::hsc($name) . '<br />';
1271                                 break;
1272                 }
1273                 $this->clear_character_data();
1274                 return;
1275         }
1276         
1277         /**
1278          * Called by XML parser for data inside elements
1279          */
1280         private function character_data ($parser, $data)
1281         {
1282                 if ( $this->debug )
1283                 {
1284                         echo 'NEW DATA: ' . Entity::hsc($data) . '<br />';
1285                 }
1286                 $this->cdata .= $data;
1287                 return;
1288         }
1289         
1290         /**
1291          * Returns the data collected so far
1292          */
1293         private function get_character_data()
1294         {
1295                 return $this->cdata;
1296         }
1297         
1298         /**
1299          * Clears the data buffer
1300          */
1301         private function clear_character_data()
1302         {
1303                 $this->cdata = '';
1304                 return;
1305         }
1306         
1307         /**
1308          * Static method that looks for importable XML files in subdirs of the given dir
1309          */
1310         static public function searchForCandidates($dir)
1311         {
1312                 $candidates = array();
1313                 
1314                 $dirhandle = opendir($dir);
1315                 while ( $filename = readdir($dirhandle) )
1316                 {
1317                         if ( @is_dir($dir . $filename) && ($filename != '.') && ($filename != '..') )
1318                         {
1319                                 $xml_file = $dir . $filename . '/skinbackup.xml';
1320                                 if ( file_exists($xml_file) && is_readable($xml_file) )
1321                                 {
1322                                         //$xml_file;
1323                                         $candidates[$filename] = $filename;
1324                                 }
1325                                 
1326                                 // backwards compatibility
1327                                 $xml_file = $dir . $filename . '/skindata.xml';
1328                                 if ( file_exists($xml_file) && is_readable($xml_file) )
1329                                 {
1330                                         //$xml_file;
1331                                         $candidates[$filename] = $filename;
1332                                 }
1333                         }
1334                 }
1335                 closedir($dirhandle);
1336                 return $candidates;
1337         }
1338 }
1339
1340 class SkinExport
1341 {
1342         private $templates;
1343         private $skins;
1344         private $info;
1345         
1346         /**
1347          * Constructor initializes data structures
1348          */
1349         public function __construct()
1350         {
1351                 // list of templateIDs to export
1352                 $this->templates = array();
1353                 
1354                 // list of skinIDs to export
1355                 $this->skins = array();
1356                 
1357                 // extra info to be in XML file
1358                 $this->info = '';
1359         }
1360         
1361         /**
1362          * Adds a template to be exported
1363          *
1364          * @param id
1365          *              template ID
1366          * @result false when no such ID exists
1367          */
1368         public function addTemplate($id)
1369         {
1370                 if ( !Template::existsID($id) )
1371                 {
1372                         return 0;
1373                 }
1374                 
1375                 $this->templates[$id] = Template::getNameFromId($id);
1376                 return 1;
1377         }
1378         
1379         /**
1380          * Adds a skin to be exported
1381          *
1382          * @param id
1383          *              skin ID
1384          * @result false when no such ID exists
1385          */
1386         public function addSkin($id)
1387         {
1388                 if ( !SKIN::existsID($id) )
1389                 {
1390                         return 0;
1391                 }
1392                 
1393                 $this->skins[$id] = SKIN::getNameFromId($id);
1394                 return 1;
1395         }
1396         
1397         /**
1398          * Sets the extra info to be included in the exported file
1399          */
1400         public function setInfo($info)
1401         {
1402                 $this->info = $info;
1403         }
1404         
1405         /**
1406          * Outputs the XML contents of the export file
1407          *
1408          * @param $setHeaders
1409          *              set to 0 if you don't want to send out headers
1410          *              (optional, default 1)
1411          */
1412         public function export($setHeaders = 1)
1413         {
1414                 global $manager;
1415                 
1416                 if ( $setHeaders )
1417                 {
1418                         // make sure the mimetype is correct, and that the data does not show up
1419                         // in the browser, but gets saved into and XML file (popup download window)
1420                         header('Content-Type: text/xml; charset=' . i18n::get_current_charset());
1421                         header('Content-Disposition: attachment; filename="skinbackup.xml"');
1422                         header('Expires: 0');
1423                         header('Pragma: no-cache');
1424                 }
1425                 
1426                 echo "<nucleusskin>\n";
1427                 
1428                 // meta
1429                 echo "\t<meta>\n";
1430                 // skins
1431                 foreach ( $this->skins as $skinId => $skinName )
1432                 {
1433                         echo "\t\t" . '<skin name="' . Entity::hsc($skinName) . '" />' . "\n";
1434                 }
1435                 // templates
1436                 foreach ( $this->templates as $templateId => $templateName )
1437                 {
1438                         echo "\t\t" . '<template name="' . Entity::hsc($templateName) . '" />' . "\n";
1439                 }
1440                 // extra info
1441                 if ( $this->info )
1442                 {
1443                         echo "\t\t<info><![CDATA[" . $this->info . "]]></info>\n";
1444                 }
1445                 echo "\t</meta>\n\n\n";
1446                 
1447                 // contents skins
1448                 foreach ($this->skins as $skinId => $skinName)
1449                 {
1450                         $skinId = (integer) $skinId;
1451                         $skinObj =& $manager->getSkin($skinId);
1452                         
1453                         echo "\t" . '<skin name="' . Entity::hsc($skinName) . '" type="' . Entity::hsc($skinObj->getContentType()) . '" includeMode="' . Entity::hsc($skinObj->getIncludeMode()) . '" includePrefix="' . Entity::hsc($skinObj->getIncludePrefix()) . '">' . "\n";
1454                         echo "\t\t<description>" . Entity::hsc($skinObj->getDescription()) . "</description>\n";
1455                         
1456                         $res = DB::getResult('SELECT stype, scontent FROM '. sql_table('skin') .' WHERE sdesc=' . $skinId);
1457                         foreach ( $res as $row )
1458                         {
1459                                 echo "\t\t" . '<part name="',Entity::hsc($row['stype']) . '">';
1460                                 echo '<![CDATA[' . $this->escapeCDATA($row['scontent']) . ']]>';
1461                                 echo "</part>\n\n";
1462                         }
1463                         echo "\t</skin>\n\n\n";
1464                 }
1465                 
1466                 // contents templates
1467                 foreach ( $this->templates as $templateId => $templateName )
1468                 {
1469                         $templateId = intval($templateId);
1470                         
1471                         echo "\t" . '<template name="' . Entity::hsc($templateName) . '">' . "\n";
1472                         echo "\t\t<description>" . Entity::hsc(Template::getDesc($templateId)) . "</description>\n";
1473                         
1474                         $res = DB::getResult('SELECT tpartname, tcontent FROM '. sql_table('template') .' WHERE tdesc=' . $templateId);
1475                         foreach ( $res as $row )
1476                         {
1477                                 echo "\t\t" . '<part name="' . Entity::hsc($row['tpartname']) . '">';
1478                                 echo '<![CDATA[' . $this->escapeCDATA($row['tcontent']) . ']]>';
1479                                 echo "</part>\n\n";
1480                         }
1481                         
1482                         echo "\t</template>\n\n\n";
1483                 }
1484                 echo '</nucleusskin>';
1485         }
1486         
1487         /**
1488          * Escapes CDATA content so it can be included in another CDATA section
1489          */
1490         private function escapeCDATA($cdata)
1491         {
1492                 return preg_replace('/]]>/', ']]]]><![CDATA[>', $cdata);
1493         }
1494 }
1495 >>>>>>> skinnable-master