3 * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
4 * Copyright (C) 2002-2009 The Nucleus Group
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 * (see nucleus/documentation/index.html#license for more info)
13 * This class contains two classes that can be used for importing and
14 * exporting Nucleus skins: SKINIMPORT and SKINEXPORT
16 * @license http://nucleuscms.org/license.txt GNU General Public License
17 * @copyright Copyright (C) 2002-2009 The Nucleus Group
19 * @version $NucleusJP: skinie.php,v 1.9.2.1 2007/09/05 07:46:30 kimitake Exp $
24 // hardcoded value (see constructor). When 1, interesting info about the
25 // parsing process is sent to the output
28 // parser/file pointer
32 // which data has been read?
41 // to maintain track of where we are inside the XML file
54 * constructor initializes data structures
56 function SKINIMPORT() {
57 // disable magic_quotes_runtime if it's turned on
58 set_magic_quotes_runtime(0);
69 xml_parser_free($this->parser);
74 // which data has been read?
75 $this->metaDataRead = 0;
78 // to maintain track of where we are inside the XML file
83 $this->inTemplate = 0;
84 $this->currentName = '';
85 $this->currentPartName = '';
87 // character data pile
90 // list of skinnames and templatenames (will be array of array)
91 $this->skins = array();
92 $this->templates = array();
94 // extra info included in the XML files (e.g. installation notes)
98 $this->parser = xml_parser_create();
99 xml_set_object($this->parser, $this);
100 xml_set_element_handler($this->parser, 'startElement', 'endElement');
101 xml_set_character_data_handler($this->parser, 'characterData');
102 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
107 * Reads an XML file into memory
112 * Set to 1 when only the metadata needs to be read (optional, default 0)
114 function readFile($filename, $metaOnly = 0) {
116 $this->fp = @fopen($filename, 'r');
118 return _SKINIE_ERROR_FAILEDOPEN_FILEURL;
126 while (!feof($this->fp)) {
127 $tempbuffer .= fread($this->fp, 4096);
132 [2004-08-04] dekarma - Took this out since it messes up good XML if it has skins/templates
133 with CDATA sections. need to investigate consequences.
134 see bug [ 999914 ] Import fails (multiple skins in XML/one of them with CDATA)
136 // backwards compatibility with the non-wellformed skinbackup.xml files
137 // generated by v2/v3 (when CDATA sections were present in skins)
138 // split up those CDATA sections into multiple ones
139 $tempbuffer = preg_replace_callback(
140 "/(<!\[CDATA\[[^]]*?<!\[CDATA\[[^]]*)((?:\]\].*?<!\[CDATA.*?)*)(\]\])(.*\]\])/ms",
143 'return $matches[1] . preg_replace("/(\]\])(.*?<!\[CDATA)/ms","]]]]><![CDATA[$2",$matches[2])."]]]]><![CDATA[".$matches[4];'
149 // fwrite($temp, $tempbuffer);
150 if (!function_exists('mb_convert_encoding')) {
151 fwrite($temp, $tempbuffer);
153 if (strtoupper(_CHARSET) == 'ISO-8859-1') {
154 fwrite($temp, $tempbuffer);
156 mb_detect_order("ASCII, JIS, SJIS, UTF-8, EUC-JP, ISO-8859-1");
157 $temp_encode = mb_detect_encoding($tempbuffer);
158 fwrite($temp, mb_convert_encoding($tempbuffer, 'UTF-8', $temp_encode));
163 while ( ($buffer = fread($temp, 4096) ) && (!$metaOnly || ($metaOnly && !$this->metaDataRead))) {
164 $err = xml_parse( $this->parser, $buffer, feof($temp) );
165 if (!$err && $this->debug) {
166 echo _ERROR . ': ' . xml_error_string(xml_get_error_code($this->parser)) . '<br />';
176 * Returns the list of skin names
178 function getSkinNames() {
179 return array_keys($this->skins);
183 * Returns the list of template names
185 function getTemplateNames() {
186 return array_keys($this->templates);
190 * Returns the extra information included in the XML file
197 * Writes the skins and templates to the database
199 * @param $allowOverwrite
200 * set to 1 when allowed to overwrite existing skins with the same name
203 function writeToDatabase($allowOverwrite = 0) {
204 $existingSkins = $this->checkSkinNameClashes();
205 $existingTemplates = $this->checkTemplateNameClashes();
207 // if not allowed to overwrite, check if any nameclashes exists
208 if (!$allowOverwrite) {
209 if ((sizeof($existingSkins) > 0) || (sizeof($existingTemplates) > 0)) {
210 return _SKINIE_NAME_CLASHES_DETECTED;
214 foreach ($this->skins as $skinName => $data) {
215 // 1. if exists: delete all part data, update desc data
216 // if not exists: create desc
217 if (in_array($skinName, $existingSkins)) {
218 $skinObj = SKIN::createFromName($skinName);
220 // delete all parts of the skin
221 $skinObj->deleteAllParts();
223 // update general info
224 $skinObj->updateGeneralInfo(
226 $data['description'],
228 $data['includeMode'],
229 $data['includePrefix']
232 $skinid = SKIN::createNew(
234 $data['description'],
236 $data['includeMode'],
237 $data['includePrefix']
239 $skinObj = new SKIN($skinid);
243 foreach ($data['parts'] as $partName => $partContent) {
244 $skinObj->update($partName, $partContent);
248 foreach ($this->templates as $templateName => $data) {
249 // 1. if exists: delete all part data, update desc data
250 // if not exists: create desc
251 if (in_array($templateName, $existingTemplates)) {
252 $templateObj = TEMPLATE::createFromName($templateName);
254 // delete all parts of the template
255 $templateObj->deleteAllParts();
257 // update general info
258 $templateObj->updateGeneralInfo($templateName, $data['description']);
260 $templateid = TEMPLATE::createNew($templateName, $data['description']);
261 $templateObj = new TEMPLATE($templateid);
265 foreach ($data['parts'] as $partName => $partContent) {
266 $templateObj->update($partName, $partContent);
274 * returns an array of all the skin nameclashes (empty array when no name clashes)
276 function checkSkinNameClashes() {
279 foreach ($this->skins as $skinName => $data) {
280 if (SKIN::exists($skinName)) {
281 array_push($clashes, $skinName);
289 * returns an array of all the template nameclashes
290 * (empty array when no name clashes)
292 function checkTemplateNameClashes() {
295 foreach ($this->templates as $templateName => $data) {
296 if (TEMPLATE::exists($templateName)) {
297 array_push($clashes, $templateName);
305 * Called by XML parser for each new start element encountered
307 function startElement($parser, $name, $attrs) {
308 foreach($attrs as $key=>$value) {
309 $attrs[$key] = htmlspecialchars($value, ENT_QUOTES);
313 echo 'START: ' . htmlspecialchars($name, ENT_QUOTES) . '<br />';
327 if (!$this->inMeta) {
329 $this->currentName = $attrs['name'];
330 $this->skins[$this->currentName]['type'] = $attrs['type'];
331 $this->skins[$this->currentName]['includeMode'] = $attrs['includeMode'];
332 $this->skins[$this->currentName]['includePrefix'] = $attrs['includePrefix'];
333 $this->skins[$this->currentName]['parts'] = array();
335 $this->skins[$attrs['name']] = array();
336 $this->skins[$attrs['name']]['parts'] = array();
340 if (!$this->inMeta) {
341 $this->inTemplate = 1;
342 $this->currentName = $attrs['name'];
343 $this->templates[$this->currentName]['parts'] = array();
345 $this->templates[$attrs['name']] = array();
346 $this->templates[$attrs['name']]['parts'] = array();
353 $this->currentPartName = $attrs['name'];
356 echo _SKINIE_SEELEMENT_UNEXPECTEDTAG . htmlspecialchars($name, ENT_QUOTES) . '<br />';
360 // character data never contains other tags
361 $this->clearCharacterData();
366 * Called by the XML parser for each closing tag encountered
368 function endElement($parser, $name) {
370 echo 'END: ' . htmlspecialchars($name, ENT_QUOTES) . '<br />';
380 $this->metaDataRead = 1;
383 $this->info = $this->getCharacterData();
385 if (!$this->inMeta) {
390 if (!$this->inMeta) {
391 $this->inTemplate = 0;
396 $this->skins[$this->currentName]['description'] = $this->getCharacterData();
398 $this->templates[$this->currentName]['description'] = $this->getCharacterData();
403 $this->skins[$this->currentName]['parts'][$this->currentPartName] = $this->getCharacterData();
405 $this->templates[$this->currentName]['parts'][$this->currentPartName] = $this->getCharacterData();
409 echo _SKINIE_SEELEMENT_UNEXPECTEDTAG . htmlspecialchars($name, ENT_QUOTES) . '<br />';
412 $this->clearCharacterData();
417 * Called by XML parser for data inside elements
419 function characterData ($parser, $data) {
421 echo 'NEW DATA: ' . htmlspecialchars($data, ENT_QUOTES) . '<br />';
423 $this->cdata .= $data;
427 * Returns the data collected so far
429 function getCharacterData() {
430 // echo $this->cdata;
431 if ( (strtoupper(_CHARSET) == 'UTF-8')
432 or (strtoupper(_CHARSET) == 'ISO-8859-1')
433 or (!function_exists('mb_convert_encoding')) ) {
436 return mb_convert_encoding($this->cdata, _CHARSET ,'UTF-8');
441 * Clears the data buffer
443 function clearCharacterData() {
448 * Static method that looks for importable XML files in subdirs of the given dir
450 function searchForCandidates($dir) {
451 $candidates = array();
453 $dirhandle = opendir($dir);
454 while ($filename = readdir($dirhandle)) {
455 if (@is_dir($dir . $filename) && ($filename != '.') && ($filename != '..')) {
456 $xml_file = $dir . $filename . '/skinbackup.xml';
457 if (file_exists($xml_file) && is_readable($xml_file)) {
458 $candidates[$filename] = $filename; //$xml_file;
461 // backwards compatibility
462 $xml_file = $dir . $filename . '/skindata.xml';
463 if (file_exists($xml_file) && is_readable($xml_file)) {
464 $candidates[$filename] = $filename; //$xml_file;
468 closedir($dirhandle);
485 * Constructor initializes data structures
487 function SKINEXPORT() {
488 // list of templateIDs to export
489 $this->templates = array();
491 // list of skinIDs to export
492 $this->skins = array();
494 // extra info to be in XML file
499 * Adds a template to be exported
503 * @result false when no such ID exists
505 function addTemplate($id) {
506 if (!TEMPLATE::existsID($id)) {
511 $this->templates[$id] = TEMPLATE::getNameFromId($id);
517 * Adds a skin to be exported
521 * @result false when no such ID exists
523 function addSkin($id) {
524 if (!SKIN::existsID($id)) {
528 $this->skins[$id] = SKIN::getNameFromId($id);
534 * Sets the extra info to be included in the exported file
536 function setInfo($info) {
542 * Outputs the XML contents of the export file
545 * set to 0 if you don't want to send out headers
546 * (optional, default 1)
548 function export($setHeaders = 1) {
550 // make sure the mimetype is correct, and that the data does not show up
551 // in the browser, but gets saved into and XML file (popup download window)
552 header('Content-Type: text/xml');
553 header('Content-Disposition: attachment; filename="skinbackup.xml"');
554 header('Expires: 0');
555 header('Pragma: no-cache');
558 echo "<nucleusskin>\n";
563 foreach ($this->skins as $skinId => $skinName) {
564 $skinName = htmlspecialchars($skinName, ENT_QUOTES);
565 if (_CHARSET != 'UTF-8') {
566 $skinName = mb_convert_encoding($skinName, 'UTF-8', _CHARSET);
568 echo "\t\t" . '<skin name="' . $skinName . '" />' . "\n";
571 foreach ($this->templates as $templateId => $templateName) {
572 $templateName = htmlspecialchars($templateName, ENT_QUOTES);
573 if (_CHARSET != 'UTF-8') {
574 $templateName = mb_convert_encoding($templateName, 'UTF-8', _CHARSET);
576 echo "\t\t" . '<template name="' . $templateName . '" />' . "\n";
580 if (_CHARSET != 'UTF-8') {
581 $skin_info = mb_convert_encoding($this->info, 'UTF-8', _CHARSET);
583 $skin_info = $this->info;
585 echo "\t\t<info><![CDATA[" . $skin_info . "]]></info>\n";
587 echo "\t</meta>\n\n\n";
590 foreach ($this->skins as $skinId => $skinName) {
591 $skinId = intval($skinId);
592 $skinObj = new SKIN($skinId);
593 $skinName = htmlspecialchars($skinName, ENT_QUOTES);
594 $contentT = htmlspecialchars($skinObj->getContentType(), ENT_QUOTES);
595 $incMode = htmlspecialchars($skinObj->getIncludeMode(), ENT_QUOTES);
596 $incPrefx = htmlspecialchars($skinObj->getIncludePrefix(), ENT_QUOTES);
597 $skinDesc = htmlspecialchars($skinObj->getDescription(), ENT_QUOTES);
598 if (_CHARSET != 'UTF-8') {
599 $skinName = mb_convert_encoding($skinName, 'UTF-8', _CHARSET);
600 $contentT = mb_convert_encoding($contentT, 'UTF-8', _CHARSET);
601 $incMode = mb_convert_encoding($incMode, 'UTF-8', _CHARSET);
602 $incPrefx = mb_convert_encoding($incPrefx, 'UTF-8', _CHARSET);
603 $skinDesc = mb_convert_encoding($skinDesc, 'UTF-8', _CHARSET);
606 echo "\t" . '<skin name="' . $skinName . '" type="' . $contentT . '" includeMode="' . $incMode . '" includePrefix="' . $incPrefx . '">' . "\n";
608 echo "\t\t" . '<description>' . $skinDesc . '</description>' . "\n";
616 . ' sdesc = ' . $skinId;
617 $res = sql_query($que);
618 while ($partObj = sql_fetch_object($res)) {
619 $type = htmlspecialchars($partObj->stype, ENT_QUOTES);
620 $cdata = $this->escapeCDATA($partObj->scontent);
621 if (_CHARSET != 'UTF-8') {
622 $type = mb_convert_encoding($type, 'UTF-8', _CHARSET);
623 $cdata = mb_convert_encoding($cdata, 'UTF-8', _CHARSET);
625 echo "\t\t" . '<part name="' . $type . '">';
626 echo '<![CDATA[' . $cdata . ']]>';
630 echo "\t</skin>\n\n\n";
633 // contents templates
634 foreach ($this->templates as $templateId => $templateName) {
635 $templateId = intval($templateId);
636 $templateName = htmlspecialchars($templateName, ENT_QUOTES);
637 $templateDesc = htmlspecialchars(TEMPLATE::getDesc($templateId), ENT_QUOTES);
638 if (_CHARSET != 'UTF-8') {
639 $templateName = mb_convert_encoding($templateName, 'UTF-8', _CHARSET);
640 $templateDesc = mb_convert_encoding($templateDesc, 'UTF-8', _CHARSET);
643 echo "\t" . '<template name="' . $templateName . '">' . "\n";
645 echo "\t\t" . '<description>' . $templateDesc . "</description>\n";
651 . sql_table('template')
653 . ' tdesc = ' . $templateId;
654 $res = sql_query($que);
655 while ($partObj = sql_fetch_object($res)) {
656 $type = htmlspecialchars($partObj->tpartname, ENT_QUOTES);
657 $cdata = $this->escapeCDATA($partObj->tcontent);
658 if (_CHARSET != 'UTF-8') {
659 $type = mb_convert_encoding($type, 'UTF-8', _CHARSET);
660 $cdata = mb_convert_encoding($cdata, 'UTF-8', _CHARSET);
662 echo "\t\t" . '<part name="' . $type . '">';
663 echo '<![CDATA[' . $cdata . ']]>';
664 echo '</part>' . "\n\n";
667 echo "\t</template>\n\n\n";
670 echo '</nucleusskin>';
674 * Escapes CDATA content so it can be included in another CDATA section
676 function escapeCDATA($cdata)
678 return preg_replace('/]]>/', ']]]]><![CDATA[>', $cdata);