3 * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
\r
4 * Copyright (C) 2002-2005 The Nucleus Group
\r
6 * This program is free software; you can redistribute it and/or
\r
7 * modify it under the terms of the GNU General Public License
\r
8 * as published by the Free Software Foundation; either version 2
\r
9 * of the License, or (at your option) any later version.
\r
10 * (see nucleus/documentation/index.html#license for more info)
\r
12 * This class contains two classes that can be used for importing and
\r
13 * exporting Nucleus skins: SKINIMPORT and SKINEXPORT
\r
15 * $Id: skinie.php,v 1.3 2005-03-16 08:10:35 kimitake Exp $
\r
16 * $NucleusJP: skinie.php,v 1.3 2005/03/12 06:19:05 kimitake Exp $
\r
21 // hardcoded value (see constructor). When 1, interesting info about the
\r
22 // parsing process is sent to the output
\r
25 // parser/file pointer
\r
29 // which data has been read?
\r
38 // to maintain track of where we are inside the XML file
\r
45 var $currentPartName;
\r
51 * constructor initializes data structures
\r
53 function SKINIMPORT() {
\r
54 // disable magic_quotes_runtime if it's turned on
\r
55 set_magic_quotes_runtime(0);
\r
66 xml_parser_free($this->parser);
\r
71 // which data has been read?
\r
72 $this->metaDataRead = 0;
\r
75 // to maintain track of where we are inside the XML file
\r
80 $this->inTemplate = 0;
\r
81 $this->currentName = '';
\r
82 $this->currentPartName = '';
\r
84 // character data pile
\r
87 // list of skinnames and templatenames (will be array of array)
\r
88 $this->skins = array();
\r
89 $this->templates = array();
\r
91 // extra info included in the XML files (e.g. installation notes)
\r
95 $this->parser = xml_parser_create();
\r
96 xml_set_object($this->parser, $this);
\r
97 xml_set_element_handler($this->parser, 'startElement', 'endElement');
\r
98 xml_set_character_data_handler($this->parser, 'characterData');
\r
99 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
\r
104 * Reads an XML file into memory
\r
107 * Which file to read
\r
109 * Set to 1 when only the metadata needs to be read (optional, default 0)
\r
111 function readFile($filename, $metaOnly = 0) {
\r
113 $this->fp = @fopen($filename, 'r');
\r
114 if (!$this->fp) return 'Failed to open file/URL';
\r
119 while (!feof($this->fp)) {
\r
120 $tempbuffer .= fread($this->fp, 4096);
\r
125 [2004-08-04] dekarma - Took this out since it messes up good XML if it has skins/templates
\r
126 with CDATA sections. need to investigate consequences.
\r
127 see bug [ 999914 ] Import fails (multiple skins in XML/one of them with CDATA)
\r
129 // backwards compatibility with the non-wellformed skinbackup.xml files
\r
130 // generated by v2/v3 (when CDATA sections were present in skins)
\r
131 // split up those CDATA sections into multiple ones
\r
132 $tempbuffer = preg_replace_callback(
\r
133 "/(<!\[CDATA\[[^]]*?<!\[CDATA\[[^]]*)((?:\]\].*?<!\[CDATA.*?)*)(\]\])(.*\]\])/ms",
\r
136 'return $matches[1] . preg_replace("/(\]\])(.*?<!\[CDATA)/ms","]]]]><![CDATA[$2",$matches[2])."]]]]><![CDATA[".$matches[4];'
\r
142 fwrite($temp, $tempbuffer);
\r
145 while ( ($buffer = fread($temp, 4096) ) && (!$metaOnly || ($metaOnly && !$this->metaDataRead))) {
\r
146 $err = xml_parse( $this->parser, $buffer, feof($temp) );
\r
147 if (!$err && $this->debug)
\r
148 echo 'ERROR: ', xml_error_string(xml_get_error_code($this->parser)), '<br />';
\r
157 * Returns the list of skin names
\r
159 function getSkinNames() {
\r
160 return array_keys($this->skins);
\r
164 * Returns the list of template names
\r
166 function getTemplateNames() {
\r
167 return array_keys($this->templates);
\r
171 * Returns the extra information included in the XML file
\r
173 function getInfo() {
\r
174 return $this->info;
\r
178 * Writes the skins and templates to the database
\r
180 * @param $allowOverwrite
\r
181 * set to 1 when allowed to overwrite existing skins with the same name
\r
184 function writeToDatabase($allowOverwrite = 0) {
\r
185 $existingSkins = $this->checkSkinNameClashes();
\r
186 $existingTemplates = $this->checkTemplateNameClashes();
\r
188 // if not allowed to overwrite, check if any nameclashes exists
\r
189 if (!$allowOverwrite) {
\r
190 if ((sizeof($existingSkins) > 0) || (sizeof($existingTemplates) > 0))
\r
191 return 'Name clashes detected, re-run with allowOverwrite = 1 to force overwrite';
\r
194 foreach ($this->skins as $skinName => $data) {
\r
195 // 1. if exists: delete all part data, update desc data
\r
196 // if not exists: create desc
\r
197 if (in_array($skinName, $existingSkins)) {
\r
198 $skinObj = SKIN::createFromName($skinName);
\r
200 // delete all parts of the skin
\r
201 $skinObj->deleteAllParts();
\r
203 // update general info
\r
204 $skinObj->updateGeneralInfo($skinName, $data['description'], $data['type'], $data['includeMode'], $data['includePrefix']);
\r
206 $skinid = SKIN::createNew($skinName, $data['description'], $data['type'], $data['includeMode'], $data['includePrefix']);
\r
207 $skinObj = new SKIN($skinid);
\r
211 foreach ($data['parts'] as $partName => $partContent) {
\r
212 $skinObj->update($partName, $partContent);
\r
216 foreach ($this->templates as $templateName => $data) {
\r
217 // 1. if exists: delete all part data, update desc data
\r
218 // if not exists: create desc
\r
219 if (in_array($templateName, $existingTemplates)) {
\r
220 $templateObj = TEMPLATE::createFromName($templateName);
\r
222 // delete all parts of the template
\r
223 $templateObj->deleteAllParts();
\r
225 // update general info
\r
226 $templateObj->updateGeneralInfo($templateName, $data['description']);
\r
228 $templateid = TEMPLATE::createNew($templateName, $data['description']);
\r
229 $templateObj = new TEMPLATE($templateid);
\r
233 foreach ($data['parts'] as $partName => $partContent) {
\r
234 $templateObj->update($partName, $partContent);
\r
242 * returns an array of all the skin nameclashes (empty array when no name clashes)
\r
244 function checkSkinNameClashes() {
\r
245 $clashes = array();
\r
247 foreach ($this->skins as $skinName => $data) {
\r
248 if (SKIN::exists($skinName))
\r
249 array_push($clashes, $skinName);
\r
256 * returns an array of all the template nameclashes
\r
257 * (empty array when no name clashes)
\r
259 function checkTemplateNameClashes() {
\r
260 $clashes = array();
\r
262 foreach ($this->templates as $templateName => $data) {
\r
263 if (TEMPLATE::exists($templateName))
\r
264 array_push($clashes, $templateName);
\r
271 * Called by XML parser for each new start element encountered
\r
273 function startElement($parser, $name, $attrs) {
\r
274 if ($this->debug) echo 'START: ', $name, '<br />';
\r
277 case 'nucleusskin':
\r
284 // no action needed
\r
287 if (!$this->inMeta) {
\r
289 $this->currentName = $attrs['name'];
\r
290 $this->skins[$this->currentName]['type'] = $attrs['type'];
\r
291 $this->skins[$this->currentName]['includeMode'] = $attrs['includeMode'];
\r
292 $this->skins[$this->currentName]['includePrefix'] = $attrs['includePrefix'];
\r
293 $this->skins[$this->currentName]['parts'] = array();
\r
295 $this->skins[$attrs['name']] = array();
\r
296 $this->skins[$attrs['name']]['parts'] = array();
\r
300 if (!$this->inMeta) {
\r
301 $this->inTemplate = 1;
\r
302 $this->currentName = $attrs['name'];
\r
303 $this->templates[$this->currentName]['parts'] = array();
\r
305 $this->templates[$attrs['name']] = array();
\r
306 $this->templates[$attrs['name']]['parts'] = array();
\r
309 case 'description':
\r
310 // no action needed
\r
313 $this->currentPartName = $attrs['name'];
\r
316 echo 'UNEXPECTED TAG: ' , $name , '<br />';
\r
320 // character data never contains other tags
\r
321 $this->clearCharacterData();
\r
326 * Called by the XML parser for each closing tag encountered
\r
328 function endElement($parser, $name) {
\r
329 if ($this->debug) echo 'END: ', $name, '<br />';
\r
332 case 'nucleusskin':
\r
334 $this->allRead = 1;
\r
338 $this->metaDataRead = 1;
\r
341 $this->info = $this->getCharacterData();
\r
343 if (!$this->inMeta) $this->inSkin = 0;
\r
346 if (!$this->inMeta) $this->inTemplate = 0;
\r
348 case 'description':
\r
349 if ($this->inSkin) {
\r
350 $this->skins[$this->currentName]['description'] = $this->getCharacterData();
\r
352 $this->templates[$this->currentName]['description'] = $this->getCharacterData();
\r
356 if ($this->inSkin) {
\r
357 $this->skins[$this->currentName]['parts'][$this->currentPartName] = $this->getCharacterData();
\r
359 $this->templates[$this->currentName]['parts'][$this->currentPartName] = $this->getCharacterData();
\r
363 echo 'UNEXPECTED TAG: ' , $name, '<br />';
\r
366 $this->clearCharacterData();
\r
371 * Called by XML parser for data inside elements
\r
373 function characterData ($parser, $data) {
\r
374 if ($this->debug) echo 'NEW DATA: ', htmlspecialchars($data), '<br />';
\r
375 $this->cdata .= $data;
\r
379 * Returns the data collected so far
\r
381 function getCharacterData() {
\r
382 return $this->cdata;
\r
386 * Clears the data buffer
\r
388 function clearCharacterData() {
\r
393 * Static method that looks for importable XML files in subdirs of the given dir
\r
395 function searchForCandidates($dir) {
\r
396 $candidates = array();
\r
398 $dirhandle = opendir($dir);
\r
399 while ($filename = readdir($dirhandle)) {
\r
400 if (@is_dir($dir . $filename) && ($filename != '.') && ($filename != '..')) {
\r
401 $xml_file = $dir . $filename . '/skinbackup.xml';
\r
402 if (file_exists($xml_file) && is_readable($xml_file)) {
\r
403 $candidates[$filename] = $filename; //$xml_file;
\r
406 // backwards compatibility
\r
407 $xml_file = $dir . $filename . '/skindata.xml';
\r
408 if (file_exists($xml_file) && is_readable($xml_file)) {
\r
409 $candidates[$filename] = $filename; //$xml_file;
\r
413 closedir($dirhandle);
\r
415 return $candidates;
\r
430 * Constructor initializes data structures
\r
432 function SKINEXPORT() {
\r
433 // list of templateIDs to export
\r
434 $this->templates = array();
\r
436 // list of skinIDs to export
\r
437 $this->skins = array();
\r
439 // extra info to be in XML file
\r
444 * Adds a template to be exported
\r
448 * @result false when no such ID exists
\r
450 function addTemplate($id) {
\r
451 if (!TEMPLATE::existsID($id)) return 0;
\r
453 $this->templates[$id] = TEMPLATE::getNameFromId($id);
\r
459 * Adds a skin to be exported
\r
463 * @result false when no such ID exists
\r
465 function addSkin($id) {
\r
466 if (!SKIN::existsID($id)) return 0;
\r
468 $this->skins[$id] = SKIN::getNameFromId($id);
\r
474 * Sets the extra info to be included in the exported file
\r
476 function setInfo($info) {
\r
477 $this->info = $info;
\r
482 * Outputs the XML contents of the export file
\r
484 * @param $setHeaders
\r
485 * set to 0 if you don't want to send out headers
\r
486 * (optional, default 1)
\r
488 function export($setHeaders = 1) {
\r
490 // make sure the mimetype is correct, and that the data does not show up
\r
491 // in the browser, but gets saved into and XML file (popup download window)
\r
492 header('Content-Type: text/xml');
\r
493 header('Content-Disposition: attachment; filename="skinbackup.xml"');
\r
494 header('Expires: 0');
\r
495 header('Pragma: no-cache');
\r
498 echo "<nucleusskin>\n";
\r
503 foreach ($this->skins as $skinId => $skinName) {
\r
504 echo "\t\t", '<skin name="',htmlspecialchars($skinName),'" />',"\n";
\r
507 foreach ($this->templates as $templateId => $templateName) {
\r
508 echo "\t\t", '<template name="',htmlspecialchars($templateName),'" />',"\n";
\r
512 echo "\t\t<info><![CDATA[",$this->info,"]]></info>\n";
\r
513 echo "\t</meta>\n\n\n";
\r
516 foreach ($this->skins as $skinId => $skinName) {
\r
517 $skinId = intval($skinId);
\r
518 $skinObj = new SKIN($skinId);
\r
520 echo "\t", '<skin name="',htmlspecialchars($skinName),'" type="',htmlspecialchars($skinObj->getContentType()),'" includeMode="',htmlspecialchars($skinObj->getIncludeMode()),'" includePrefix="',htmlspecialchars($skinObj->getIncludePrefix()),'">',"\n";
\r
522 echo "\t\t", '<description>',htmlspecialchars($skinObj->getDescription()),'</description>',"\n";
\r
524 $res = sql_query('SELECT stype, scontent FROM '.sql_table('skin').' WHERE sdesc='.$skinId);
\r
525 while ($partObj = mysql_fetch_object($res)) {
\r
526 echo "\t\t",'<part name="',htmlspecialchars($partObj->stype),'">';
\r
527 echo '<![CDATA[', $this->escapeCDATA($partObj->scontent),']]>';
\r
528 echo "</part>\n\n";
\r
531 echo "\t</skin>\n\n\n";
\r
534 // contents templates
\r
535 foreach ($this->templates as $templateId => $templateName) {
\r
536 $templateId = intval($templateId);
\r
538 echo "\t",'<template name="',htmlspecialchars($templateName),'">',"\n";
\r
540 echo "\t\t",'<description>',htmlspecialchars(TEMPLATE::getDesc($templateId)),'</description>',"\n";
\r
542 $res = sql_query('SELECT tpartname, tcontent FROM '.sql_table('template').' WHERE tdesc='.$templateId);
\r
543 while ($partObj = mysql_fetch_object($res)) {
\r
544 echo "\t\t",'<part name="',htmlspecialchars($partObj->tpartname),'">';
\r
545 echo '<![CDATA[', $this->escapeCDATA($partObj->tcontent) ,']]>';
\r
546 echo '</part>',"\n\n";
\r
549 echo "\t</template>\n\n\n";
\r
552 echo '</nucleusskin>';
\r
556 * Escapes CDATA content so it can be included in another CDATA section
\r
558 function escapeCDATA($cdata)
\r
560 return preg_replace('/]]>/', ']]]]><![CDATA[>', $cdata);
\r