3 * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
4 * Copyright (C) 2002-2007 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-2007 The Nucleus Group
18 * @version $Id: skinie.php,v 1.9 2007-04-20 08:43:25 kimitake Exp $
19 * @version $NucleusJP: skinie.php,v 1.8 2007/03/22 03:30:14 kmorimatsu 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');
117 if (!$this->fp) return 'Failed to open file/URL';
124 while (!feof($this->fp)) {
125 $tempbuffer .= fread($this->fp, 4096);
130 [2004-08-04] dekarma - Took this out since it messes up good XML if it has skins/templates
131 with CDATA sections. need to investigate consequences.
132 see bug [ 999914 ] Import fails (multiple skins in XML/one of them with CDATA)
134 // backwards compatibility with the non-wellformed skinbackup.xml files
135 // generated by v2/v3 (when CDATA sections were present in skins)
136 // split up those CDATA sections into multiple ones
137 $tempbuffer = preg_replace_callback(
138 "/(<!\[CDATA\[[^]]*?<!\[CDATA\[[^]]*)((?:\]\].*?<!\[CDATA.*?)*)(\]\])(.*\]\])/ms",
141 'return $matches[1] . preg_replace("/(\]\])(.*?<!\[CDATA)/ms","]]]]><![CDATA[$2",$matches[2])."]]]]><![CDATA[".$matches[4];'
147 fwrite($temp, $tempbuffer);
150 while ( ($buffer = fread($temp, 4096) ) && (!$metaOnly || ($metaOnly && !$this->metaDataRead))) {
151 $err = xml_parse( $this->parser, $buffer, feof($temp) );
152 if (!$err && $this->debug)
153 echo 'ERROR: ', xml_error_string(xml_get_error_code($this->parser)), '<br />';
162 * Returns the list of skin names
164 function getSkinNames() {
165 return array_keys($this->skins);
169 * Returns the list of template names
171 function getTemplateNames() {
172 return array_keys($this->templates);
176 * Returns the extra information included in the XML file
183 * Writes the skins and templates to the database
185 * @param $allowOverwrite
186 * set to 1 when allowed to overwrite existing skins with the same name
189 function writeToDatabase($allowOverwrite = 0) {
190 $existingSkins = $this->checkSkinNameClashes();
191 $existingTemplates = $this->checkTemplateNameClashes();
193 // if not allowed to overwrite, check if any nameclashes exists
194 if (!$allowOverwrite) {
195 if ((sizeof($existingSkins) > 0) || (sizeof($existingTemplates) > 0))
196 return 'Name clashes detected, re-run with allowOverwrite = 1 to force overwrite';
199 foreach ($this->skins as $skinName => $data) {
200 // 1. if exists: delete all part data, update desc data
201 // if not exists: create desc
202 if (in_array($skinName, $existingSkins)) {
203 $skinObj = SKIN::createFromName($skinName);
205 // delete all parts of the skin
206 $skinObj->deleteAllParts();
208 // update general info
209 $skinObj->updateGeneralInfo($skinName, $data['description'], $data['type'], $data['includeMode'], $data['includePrefix']);
211 $skinid = SKIN::createNew($skinName, $data['description'], $data['type'], $data['includeMode'], $data['includePrefix']);
212 $skinObj = new SKIN($skinid);
216 foreach ($data['parts'] as $partName => $partContent) {
217 $skinObj->update($partName, $partContent);
221 foreach ($this->templates as $templateName => $data) {
222 // 1. if exists: delete all part data, update desc data
223 // if not exists: create desc
224 if (in_array($templateName, $existingTemplates)) {
225 $templateObj = TEMPLATE::createFromName($templateName);
227 // delete all parts of the template
228 $templateObj->deleteAllParts();
230 // update general info
231 $templateObj->updateGeneralInfo($templateName, $data['description']);
233 $templateid = TEMPLATE::createNew($templateName, $data['description']);
234 $templateObj = new TEMPLATE($templateid);
238 foreach ($data['parts'] as $partName => $partContent) {
239 $templateObj->update($partName, $partContent);
247 * returns an array of all the skin nameclashes (empty array when no name clashes)
249 function checkSkinNameClashes() {
252 foreach ($this->skins as $skinName => $data) {
253 if (SKIN::exists($skinName))
254 array_push($clashes, $skinName);
261 * returns an array of all the template nameclashes
262 * (empty array when no name clashes)
264 function checkTemplateNameClashes() {
267 foreach ($this->templates as $templateName => $data) {
268 if (TEMPLATE::exists($templateName))
269 array_push($clashes, $templateName);
276 * Called by XML parser for each new start element encountered
278 function startElement($parser, $name, $attrs) {
279 foreach($attrs as $key=>$value) $attrs[$key]=htmlspecialchars($value,ENT_QUOTES);
281 if ($this->debug) echo 'START: ', htmlspecialchars($name), '<br />';
294 if (!$this->inMeta) {
296 $this->currentName = $attrs['name'];
297 $this->skins[$this->currentName]['type'] = $attrs['type'];
298 $this->skins[$this->currentName]['includeMode'] = $attrs['includeMode'];
299 $this->skins[$this->currentName]['includePrefix'] = $attrs['includePrefix'];
300 $this->skins[$this->currentName]['parts'] = array();
302 $this->skins[$attrs['name']] = array();
303 $this->skins[$attrs['name']]['parts'] = array();
307 if (!$this->inMeta) {
308 $this->inTemplate = 1;
309 $this->currentName = $attrs['name'];
310 $this->templates[$this->currentName]['parts'] = array();
312 $this->templates[$attrs['name']] = array();
313 $this->templates[$attrs['name']]['parts'] = array();
320 $this->currentPartName = $attrs['name'];
323 echo 'UNEXPECTED TAG: ' , htmlspecialchars($name) , '<br />';
327 // character data never contains other tags
328 $this->clearCharacterData();
333 * Called by the XML parser for each closing tag encountered
335 function endElement($parser, $name) {
336 if ($this->debug) echo 'END: ', htmlspecialchars($name), '<br />';
345 $this->metaDataRead = 1;
348 $this->info = $this->getCharacterData();
350 if (!$this->inMeta) $this->inSkin = 0;
353 if (!$this->inMeta) $this->inTemplate = 0;
357 $this->skins[$this->currentName]['description'] = $this->getCharacterData();
359 $this->templates[$this->currentName]['description'] = $this->getCharacterData();
364 $this->skins[$this->currentName]['parts'][$this->currentPartName] = $this->getCharacterData();
366 $this->templates[$this->currentName]['parts'][$this->currentPartName] = $this->getCharacterData();
370 echo 'UNEXPECTED TAG: ' , htmlspecialchars($name), '<br />';
373 $this->clearCharacterData();
378 * Called by XML parser for data inside elements
380 function characterData ($parser, $data) {
381 if ($this->debug) echo 'NEW DATA: ', htmlspecialchars($data), '<br />';
382 $this->cdata .= $data;
386 * Returns the data collected so far
388 function getCharacterData() {
393 * Clears the data buffer
395 function clearCharacterData() {
400 * Static method that looks for importable XML files in subdirs of the given dir
402 function searchForCandidates($dir) {
403 $candidates = array();
405 $dirhandle = opendir($dir);
406 while ($filename = readdir($dirhandle)) {
407 if (@is_dir($dir . $filename) && ($filename != '.') && ($filename != '..')) {
408 $xml_file = $dir . $filename . '/skinbackup.xml';
409 if (file_exists($xml_file) && is_readable($xml_file)) {
410 $candidates[$filename] = $filename; //$xml_file;
413 // backwards compatibility
414 $xml_file = $dir . $filename . '/skindata.xml';
415 if (file_exists($xml_file) && is_readable($xml_file)) {
416 $candidates[$filename] = $filename; //$xml_file;
420 closedir($dirhandle);
437 * Constructor initializes data structures
439 function SKINEXPORT() {
440 // list of templateIDs to export
441 $this->templates = array();
443 // list of skinIDs to export
444 $this->skins = array();
446 // extra info to be in XML file
451 * Adds a template to be exported
455 * @result false when no such ID exists
457 function addTemplate($id) {
458 if (!TEMPLATE::existsID($id)) return 0;
460 $this->templates[$id] = TEMPLATE::getNameFromId($id);
466 * Adds a skin to be exported
470 * @result false when no such ID exists
472 function addSkin($id) {
473 if (!SKIN::existsID($id)) return 0;
475 $this->skins[$id] = SKIN::getNameFromId($id);
481 * Sets the extra info to be included in the exported file
483 function setInfo($info) {
489 * Outputs the XML contents of the export file
492 * set to 0 if you don't want to send out headers
493 * (optional, default 1)
495 function export($setHeaders = 1) {
497 // make sure the mimetype is correct, and that the data does not show up
498 // in the browser, but gets saved into and XML file (popup download window)
499 header('Content-Type: text/xml');
500 header('Content-Disposition: attachment; filename="skinbackup.xml"');
501 header('Expires: 0');
502 header('Pragma: no-cache');
505 echo "<nucleusskin>\n";
510 foreach ($this->skins as $skinId => $skinName) {
511 echo "\t\t", '<skin name="',htmlspecialchars($skinName),'" />',"\n";
514 foreach ($this->templates as $templateId => $templateName) {
515 echo "\t\t", '<template name="',htmlspecialchars($templateName),'" />',"\n";
519 echo "\t\t<info><![CDATA[",$this->info,"]]></info>\n";
520 echo "\t</meta>\n\n\n";
523 foreach ($this->skins as $skinId => $skinName) {
524 $skinId = intval($skinId);
525 $skinObj = new SKIN($skinId);
527 echo "\t", '<skin name="',htmlspecialchars($skinName),'" type="',htmlspecialchars($skinObj->getContentType()),'" includeMode="',htmlspecialchars($skinObj->getIncludeMode()),'" includePrefix="',htmlspecialchars($skinObj->getIncludePrefix()),'">',"\n";
529 echo "\t\t", '<description>',htmlspecialchars($skinObj->getDescription()),'</description>',"\n";
531 $res = sql_query('SELECT stype, scontent FROM '.sql_table('skin').' WHERE sdesc='.$skinId);
532 while ($partObj = mysql_fetch_object($res)) {
533 echo "\t\t",'<part name="',htmlspecialchars($partObj->stype),'">';
534 echo '<![CDATA[', $this->escapeCDATA($partObj->scontent),']]>';
538 echo "\t</skin>\n\n\n";
541 // contents templates
542 foreach ($this->templates as $templateId => $templateName) {
543 $templateId = intval($templateId);
545 echo "\t",'<template name="',htmlspecialchars($templateName),'">',"\n";
547 echo "\t\t",'<description>',htmlspecialchars(TEMPLATE::getDesc($templateId)),'</description>',"\n";
549 $res = sql_query('SELECT tpartname, tcontent FROM '.sql_table('template').' WHERE tdesc='.$templateId);
550 while ($partObj = mysql_fetch_object($res)) {
551 echo "\t\t",'<part name="',htmlspecialchars($partObj->tpartname),'">';
552 echo '<![CDATA[', $this->escapeCDATA($partObj->tcontent) ,']]>';
553 echo '</part>',"\n\n";
556 echo "\t</template>\n\n\n";
559 echo '</nucleusskin>';
563 * Escapes CDATA content so it can be included in another CDATA section
565 function escapeCDATA($cdata)
567 return preg_replace('/]]>/', ']]]]><![CDATA[>', $cdata);