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.8 2007-03-22 03:30:14 kmorimatsu Exp $
19 * @version $NucleusJP: skinie.php,v 1.7 2007/02/04 06:28:46 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');
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 if ($this->debug) echo 'START: ', htmlspecialchars($name), '<br />';
292 if (!$this->inMeta) {
294 $this->currentName = $attrs['name'];
295 $this->skins[$this->currentName]['type'] = $attrs['type'];
296 $this->skins[$this->currentName]['includeMode'] = $attrs['includeMode'];
297 $this->skins[$this->currentName]['includePrefix'] = $attrs['includePrefix'];
298 $this->skins[$this->currentName]['parts'] = array();
300 $this->skins[$attrs['name']] = array();
301 $this->skins[$attrs['name']]['parts'] = array();
305 if (!$this->inMeta) {
306 $this->inTemplate = 1;
307 $this->currentName = $attrs['name'];
308 $this->templates[$this->currentName]['parts'] = array();
310 $this->templates[$attrs['name']] = array();
311 $this->templates[$attrs['name']]['parts'] = array();
318 $this->currentPartName = $attrs['name'];
321 echo 'UNEXPECTED TAG: ' , htmlspecialchars($name) , '<br />';
325 // character data never contains other tags
326 $this->clearCharacterData();
331 * Called by the XML parser for each closing tag encountered
333 function endElement($parser, $name) {
334 if ($this->debug) echo 'END: ', htmlspecialchars($name), '<br />';
343 $this->metaDataRead = 1;
346 $this->info = $this->getCharacterData();
348 if (!$this->inMeta) $this->inSkin = 0;
351 if (!$this->inMeta) $this->inTemplate = 0;
355 $this->skins[$this->currentName]['description'] = $this->getCharacterData();
357 $this->templates[$this->currentName]['description'] = $this->getCharacterData();
362 $this->skins[$this->currentName]['parts'][$this->currentPartName] = $this->getCharacterData();
364 $this->templates[$this->currentName]['parts'][$this->currentPartName] = $this->getCharacterData();
368 echo 'UNEXPECTED TAG: ' , htmlspecialchars($name), '<br />';
371 $this->clearCharacterData();
376 * Called by XML parser for data inside elements
378 function characterData ($parser, $data) {
379 if ($this->debug) echo 'NEW DATA: ', htmlspecialchars($data), '<br />';
380 $this->cdata .= $data;
384 * Returns the data collected so far
386 function getCharacterData() {
391 * Clears the data buffer
393 function clearCharacterData() {
398 * Static method that looks for importable XML files in subdirs of the given dir
400 function searchForCandidates($dir) {
401 $candidates = array();
403 $dirhandle = opendir($dir);
404 while ($filename = readdir($dirhandle)) {
405 if (@is_dir($dir . $filename) && ($filename != '.') && ($filename != '..')) {
406 $xml_file = $dir . $filename . '/skinbackup.xml';
407 if (file_exists($xml_file) && is_readable($xml_file)) {
408 $candidates[$filename] = $filename; //$xml_file;
411 // backwards compatibility
412 $xml_file = $dir . $filename . '/skindata.xml';
413 if (file_exists($xml_file) && is_readable($xml_file)) {
414 $candidates[$filename] = $filename; //$xml_file;
418 closedir($dirhandle);
435 * Constructor initializes data structures
437 function SKINEXPORT() {
438 // list of templateIDs to export
439 $this->templates = array();
441 // list of skinIDs to export
442 $this->skins = array();
444 // extra info to be in XML file
449 * Adds a template to be exported
453 * @result false when no such ID exists
455 function addTemplate($id) {
456 if (!TEMPLATE::existsID($id)) return 0;
458 $this->templates[$id] = TEMPLATE::getNameFromId($id);
464 * Adds a skin to be exported
468 * @result false when no such ID exists
470 function addSkin($id) {
471 if (!SKIN::existsID($id)) return 0;
473 $this->skins[$id] = SKIN::getNameFromId($id);
479 * Sets the extra info to be included in the exported file
481 function setInfo($info) {
487 * Outputs the XML contents of the export file
490 * set to 0 if you don't want to send out headers
491 * (optional, default 1)
493 function export($setHeaders = 1) {
495 // make sure the mimetype is correct, and that the data does not show up
496 // in the browser, but gets saved into and XML file (popup download window)
497 header('Content-Type: text/xml');
498 header('Content-Disposition: attachment; filename="skinbackup.xml"');
499 header('Expires: 0');
500 header('Pragma: no-cache');
503 echo "<nucleusskin>\n";
508 foreach ($this->skins as $skinId => $skinName) {
509 echo "\t\t", '<skin name="',htmlspecialchars($skinName),'" />',"\n";
512 foreach ($this->templates as $templateId => $templateName) {
513 echo "\t\t", '<template name="',htmlspecialchars($templateName),'" />',"\n";
517 echo "\t\t<info><![CDATA[",$this->info,"]]></info>\n";
518 echo "\t</meta>\n\n\n";
521 foreach ($this->skins as $skinId => $skinName) {
522 $skinId = intval($skinId);
523 $skinObj = new SKIN($skinId);
525 echo "\t", '<skin name="',htmlspecialchars($skinName),'" type="',htmlspecialchars($skinObj->getContentType()),'" includeMode="',htmlspecialchars($skinObj->getIncludeMode()),'" includePrefix="',htmlspecialchars($skinObj->getIncludePrefix()),'">',"\n";
527 echo "\t\t", '<description>',htmlspecialchars($skinObj->getDescription()),'</description>',"\n";
529 $res = sql_query('SELECT stype, scontent FROM '.sql_table('skin').' WHERE sdesc='.$skinId);
530 while ($partObj = mysql_fetch_object($res)) {
531 echo "\t\t",'<part name="',htmlspecialchars($partObj->stype),'">';
532 echo '<![CDATA[', $this->escapeCDATA($partObj->scontent),']]>';
536 echo "\t</skin>\n\n\n";
539 // contents templates
540 foreach ($this->templates as $templateId => $templateName) {
541 $templateId = intval($templateId);
543 echo "\t",'<template name="',htmlspecialchars($templateName),'">',"\n";
545 echo "\t\t",'<description>',htmlspecialchars(TEMPLATE::getDesc($templateId)),'</description>',"\n";
547 $res = sql_query('SELECT tpartname, tcontent FROM '.sql_table('template').' WHERE tdesc='.$templateId);
548 while ($partObj = mysql_fetch_object($res)) {
549 echo "\t\t",'<part name="',htmlspecialchars($partObj->tpartname),'">';
550 echo '<![CDATA[', $this->escapeCDATA($partObj->tcontent) ,']]>';
551 echo '</part>',"\n\n";
554 echo "\t</template>\n\n\n";
557 echo '</nucleusskin>';
561 * Escapes CDATA content so it can be included in another CDATA section
563 function escapeCDATA($cdata)
565 return preg_replace('/]]>/', ']]]]><![CDATA[>', $cdata);