3 * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
\r
4 * Copyright (C) 2002-2012 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
13 * This class contains two classes that can be used for importing and
\r
14 * exporting Nucleus skins: SKINIMPORT and SKINEXPORT
\r
16 * @license http://nucleuscms.org/license.txt GNU General Public License
\r
17 * @copyright Copyright (C) 2002-2012 The Nucleus Group
\r
18 * @version $Id: skinie.php 1624 2012-01-09 11:36:20Z sakamocchi $
\r
23 // hardcoded value (see constructor). When 1, interesting info about the
\r
24 // parsing process is sent to the output
\r
27 // parser/file pointer
\r
31 // parset internal charset, US-ASCII/ISO-8859-1/UTF-8
\r
32 private $parse_charset = 'UTF-8';
\r
34 // which data has been read?
\r
35 private $metaDataRead;
\r
43 // to maintain track of where we are inside the XML file
\r
48 private $inTemplate;
\r
49 private $currentName;
\r
50 private $currentPartName;
\r
54 * constructor initializes data structures
\r
56 public function __construct()
\r
58 // disable magic_quotes_runtime if it's turned on
\r
59 //set_magic_quotes_runtime(0);
\r
60 if ( version_compare(PHP_VERSION, '5.3.0', '<') )
\r
62 ini_set('magic_quotes_runtime', '0');
\r
72 public function __destruct()
\r
77 public function reset()
\r
79 if ( $this->parser )
\r
81 xml_parser_free($this->parser);
\r
87 // which data has been read?
\r
88 $this->metaDataRead = 0;
\r
91 // to maintain track of where we are inside the XML file
\r
96 $this->inTemplate = 0;
\r
97 $this->currentName = '';
\r
98 $this->currentPartName = '';
\r
100 // character data pile
\r
103 // list of skinnames and templatenames (will be array of array)
\r
104 $this->skins = array();
\r
105 $this->templates = array();
\r
107 // extra info included in the XML files (e.g. installation notes)
\r
110 // init XML parser, this parser deal with characters as encoded by UTF-8
\r
111 $this->parser = xml_parser_create($this->parse_charset);
\r
112 xml_set_object($this->parser, $this);
\r
113 xml_set_element_handler($this->parser, 'start_element', 'end_element');
\r
114 xml_set_character_data_handler($this->parser, 'character_data');
\r
115 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
\r
121 * Reads an XML file into memory
\r
124 * Which file to read
\r
126 * Set to 1 when only the metadata needs to be read (optional, default 0)
\r
128 public function readFile($filename, $metaOnly = 0)
\r
131 $this->fp = @fopen($filename, 'r');
\r
134 return _SKINIE_ERROR_FAILEDOPEN_FILEURL;
\r
140 $tempbuffer = null;
\r
142 while ( !feof($this->fp) )
\r
144 $tempbuffer .= fread($this->fp, 4096);
\r
149 * NOTE: conver character set.
\r
150 * We hope all characters in the file also includes UTF-8 coded character set,
\r
151 * because this PHP extension implements support for James Clark's expat in PHP
\r
152 * and it supports juust US-ASCII, ISO-8859-1, UTF-8 character coding scheme.
\r
154 if ( i18n::get_current_charset() != $this->parse_charset )
\r
156 $tempbuffer = i18n::convert($tempbuffer, i18n::get_current_charset(), $this->parse_charset);
\r
160 fwrite($temp, $tempbuffer);
\r
163 while ( ($buffer = fread($temp, 4096) )
\r
164 && (!$metaOnly || ($metaOnly && !$this->metaDataRead)) )
\r
166 $err = xml_parse( $this->parser, $buffer, feof($temp) );
\r
167 if ( !$err && $this->debug )
\r
169 echo 'ERROR: ', xml_error_string(xml_get_error_code($this->parser)), '<br />';
\r
181 * Returns the list of skin names
\r
183 public function getSkinNames()
\r
185 return array_keys($this->skins);
\r
189 * Returns the list of template names
\r
191 public function getTemplateNames()
\r
193 return array_keys($this->templates);
\r
197 * Returns the extra information included in the XML file
\r
199 public function getInfo()
\r
201 return $this->info;
\r
205 * Writes the skins and templates to the database
\r
207 * @param $allowOverwrite
\r
208 * set to 1 when allowed to overwrite existing skins with the same name
\r
211 public function writeToDatabase($allowOverwrite = 0)
\r
213 $existingSkins = $this->checkSkinNameClashes();
\r
214 $existingTemplates = $this->checkTemplateNameClashes();
\r
215 $invalidSkinNames = $this->checkSkinNamesValid();
\r
216 $invalidTemplateNames = $this->checkTemplateNamesValid();
\r
218 // if there are invalid skin or template names, stop executioin and return and error
\r
219 if ( (sizeof($invalidSkinNames) > 0) || (sizeof($invalidTemplateNames) > 0) )
\r
221 $inames_error = "<p>"._SKINIE_INVALID_NAMES_DETECTED."</p>\n";
\r
222 $inames_error .= "<ul>";
\r
223 foreach( $invalidSkinNames as $sName )
\r
225 $inames_error .= "<li>".Entity::hsc($sName)."</li>";
\r
227 foreach( $invalidTemplateNames as $sName )
\r
229 $inames_error .= "<li>".Entity::hsc($sName)."</li>";
\r
231 $inames_error .= "</ul>";
\r
232 return $inames_error;
\r
235 // if not allowed to overwrite, check if any nameclashes exists
\r
236 if ( !$allowOverwrite )
\r
238 if ( (sizeof($existingSkins) > 0) || (sizeof($existingTemplates) > 0) )
\r
240 return _SKINIE_NAME_CLASHES_DETECTED;
\r
244 foreach ( $this->skins as $skinName => $data )
\r
246 // 1. if exists: delete all part data, update desc data
\r
247 // if not exists: create desc
\r
248 if ( in_array($skinName, $existingSkins) )
\r
250 $skinObj = SKIN::createFromName($skinName);
\r
252 // delete all parts of the skin
\r
253 $skinObj->deleteAllParts();
\r
255 // update general info
\r
256 $skinObj->updateGeneralInfo(
\r
258 $data['description'],
\r
260 $data['includeMode'],
\r
261 $data['includePrefix']
\r
266 $skinid = SKIN::createNew(
\r
268 $data['description'],
\r
270 $data['includeMode'],
\r
271 $data['includePrefix']
\r
273 $skinObj = new SKIN($skinid);
\r
277 foreach ( $data['parts'] as $partName => $partContent )
\r
279 $skinObj->update($partName, $partContent);
\r
283 foreach ( $this->templates as $templateName => $data )
\r
285 // 1. if exists: delete all part data, update desc data
\r
286 // if not exists: create desc
\r
287 if ( in_array($templateName, $existingTemplates) )
\r
289 $templateObj = Template::createFromName($templateName);
\r
291 // delete all parts of the template
\r
292 $templateObj->deleteAllParts();
\r
294 // update general info
\r
295 $templateObj->updateGeneralInfo($templateName, $data['description']);
\r
299 $templateid = Template::createNew($templateName, $data['description']);
\r
300 $templateObj = new Template($templateid);
\r
304 foreach ( $data['parts'] as $partName => $partContent )
\r
306 $templateObj->update($partName, $partContent);
\r
313 * returns an array of all the skin nameclashes (empty array when no name clashes)
\r
315 public function checkSkinNameClashes()
\r
317 $clashes = array();
\r
319 foreach ( $this->skins as $skinName => $data )
\r
321 if ( SKIN::exists($skinName) )
\r
323 array_push($clashes, $skinName);
\r
330 * returns an array of all the template nameclashes
\r
331 * (empty array when no name clashes)
\r
333 public function checkTemplateNameClashes()
\r
335 $clashes = array();
\r
337 foreach ( $this->templates as $templateName => $data )
\r
339 if ( Template::exists($templateName) )
\r
341 array_push($clashes, $templateName);
\r
348 * returns an array of all the invalid skin names (empty array when no invalid names )
\r
350 private function checkSkinNamesValid()
\r
352 $notValid = array();
\r
354 foreach ( $this->skins as $skinName => $data )
\r
356 if ( !isValidSkinName($skinName) )
\r
358 array_push($notValid, $skinName);
\r
365 * returns an array of all the invalid template names (empty array when no invalid names )
\r
367 private function checkTemplateNamesValid()
\r
369 $notValid = array();
\r
371 foreach ( $this->templates as $templateName => $data )
\r
373 if ( !isValidTemplateName($templateName) )
\r
375 array_push($notValid, $templateName);
\r
382 * Called by XML parser for each new start element encountered
\r
384 private function start_element($parser, $name, $attrs)
\r
386 foreach( $attrs as $key=>$value )
\r
388 if ( $this->parse_charset != i18n::get_current_charset() )
\r
390 $name = i18n::convert($name, $this->parse_charset, i18n::get_current_charset());
\r
391 $value = i18n::convert($value, $this->parse_charset, i18n::get_current_charset());
\r
394 $attrs[$key] = $value;
\r
397 if ( $this->debug )
\r
399 echo 'START: ', Entity::hsc($name), '<br />';
\r
404 case 'nucleusskin':
\r
411 // no action needed
\r
414 if ( !$this->inMeta )
\r
417 $this->currentName = $attrs['name'];
\r
418 $this->skins[$this->currentName]['type'] = $attrs['type'];
\r
419 $this->skins[$this->currentName]['includeMode'] = $attrs['includeMode'];
\r
420 $this->skins[$this->currentName]['includePrefix'] = $attrs['includePrefix'];
\r
421 $this->skins[$this->currentName]['parts'] = array();
\r
425 $this->skins[$attrs['name']] = array();
\r
426 $this->skins[$attrs['name']]['parts'] = array();
\r
430 if ( !$this->inMeta )
\r
432 $this->inTemplate = 1;
\r
433 $this->currentName = $attrs['name'];
\r
434 $this->templates[$this->currentName]['parts'] = array();
\r
438 $this->templates[$attrs['name']] = array();
\r
439 $this->templates[$attrs['name']]['parts'] = array();
\r
442 case 'description':
\r
443 // no action needed
\r
446 $this->currentPartName = $attrs['name'];
\r
449 echo _SKINIE_SEELEMENT_UNEXPECTEDTAG . Entity::hsc($name) . '<br />';
\r
452 // character data never contains other tags
\r
453 $this->clear_character_data();
\r
458 * Called by the XML parser for each closing tag encountered
\r
460 private function end_element($parser, $name)
\r
462 if ( $this->debug )
\r
464 echo 'END: ' . Entity::hsc($name) . '<br />';
\r
467 if ( $this->parse_charset != i18n::get_current_charset() )
\r
469 $name = i18n::convert($name, $this->parse_charset, i18n::get_current_charset());
\r
470 $charset_data = i18n::convert($this->get_character_data(), $this->parse_charset, i18n::get_current_charset());
\r
474 $charset_data = $this->get_character_data();
\r
479 case 'nucleusskin':
\r
481 $this->allRead = 1;
\r
485 $this->metaDataRead = 1;
\r
488 $this->info = $charset_data;
\r
490 if ( !$this->inMeta )
\r
496 if ( !$this->inMeta )
\r
498 $this->inTemplate = 0;
\r
501 case 'description':
\r
502 if ( $this->inSkin )
\r
504 $this->skins[$this->currentName]['description'] = $charset_data;
\r
508 $this->templates[$this->currentName]['description'] = $charset_data;
\r
512 if ( $this->inSkin )
\r
514 $this->skins[$this->currentName]['parts'][$this->currentPartName] = $charset_data;
\r
518 $this->templates[$this->currentName]['parts'][$this->currentPartName] = $charset_data;
\r
522 echo _SKINIE_SEELEMENT_UNEXPECTEDTAG . Entity::hsc($name) . '<br />';
\r
525 $this->clear_character_data();
\r
530 * Called by XML parser for data inside elements
\r
532 private function character_data ($parser, $data)
\r
534 if ( $this->debug )
\r
536 echo 'NEW DATA: ' . Entity::hsc($data) . '<br />';
\r
538 $this->cdata .= $data;
\r
543 * Returns the data collected so far
\r
545 private function get_character_data()
\r
547 return $this->cdata;
\r
551 * Clears the data buffer
\r
553 private function clear_character_data()
\r
560 * Static method that looks for importable XML files in subdirs of the given dir
\r
562 static public function searchForCandidates($dir)
\r
564 $candidates = array();
\r
566 $dirhandle = opendir($dir);
\r
567 while ( $filename = readdir($dirhandle) )
\r
569 if ( @is_dir($dir . $filename) && ($filename != '.') && ($filename != '..') )
\r
571 $xml_file = $dir . $filename . '/skinbackup.xml';
\r
572 if ( file_exists($xml_file) && is_readable($xml_file) )
\r
575 $candidates[$filename] = $filename;
\r
578 // backwards compatibility
\r
579 $xml_file = $dir . $filename . '/skindata.xml';
\r
580 if ( file_exists($xml_file) && is_readable($xml_file) )
\r
583 $candidates[$filename] = $filename;
\r
587 closedir($dirhandle);
\r
588 return $candidates;
\r
594 private $templates;
\r
599 * Constructor initializes data structures
\r
601 public function __construct()
\r
603 // list of templateIDs to export
\r
604 $this->templates = array();
\r
606 // list of skinIDs to export
\r
607 $this->skins = array();
\r
609 // extra info to be in XML file
\r
614 * Adds a template to be exported
\r
618 * @result false when no such ID exists
\r
620 public function addTemplate($id)
\r
622 if ( !Template::existsID($id) )
\r
627 $this->templates[$id] = Template::getNameFromId($id);
\r
632 * Adds a skin to be exported
\r
636 * @result false when no such ID exists
\r
638 public function addSkin($id)
\r
640 if ( !SKIN::existsID($id) )
\r
645 $this->skins[$id] = SKIN::getNameFromId($id);
\r
650 * Sets the extra info to be included in the exported file
\r
652 public function setInfo($info)
\r
654 $this->info = $info;
\r
658 * Outputs the XML contents of the export file
\r
660 * @param $setHeaders
\r
661 * set to 0 if you don't want to send out headers
\r
662 * (optional, default 1)
\r
664 public function export($setHeaders = 1)
\r
668 // make sure the mimetype is correct, and that the data does not show up
\r
669 // in the browser, but gets saved into and XML file (popup download window)
\r
670 header('Content-Type: text/xml; charset=' . i18n::get_current_charset());
\r
671 header('Content-Disposition: attachment; filename="skinbackup.xml"');
\r
672 header('Expires: 0');
\r
673 header('Pragma: no-cache');
\r
676 echo "<nucleusskin>\n";
\r
681 foreach ( $this->skins as $skinId => $skinName )
\r
683 echo "\t\t" . '<skin name="' . Entity::hsc($skinName) . '" />' . "\n";
\r
686 foreach ( $this->templates as $templateId => $templateName )
\r
688 echo "\t\t" . '<template name="' . Entity::hsc($templateName) . '" />' . "\n";
\r
693 echo "\t\t<info><![CDATA[" . $this->info . "]]></info>\n";
\r
695 echo "\t</meta>\n\n\n";
\r
698 foreach ($this->skins as $skinId => $skinName)
\r
700 $skinId = intval($skinId);
\r
701 $skinObj = new SKIN($skinId);
\r
703 echo "\t" . '<skin name="' . Entity::hsc($skinName) . '" type="' . Entity::hsc($skinObj->getContentType()) . '" includeMode="' . Entity::hsc($skinObj->getIncludeMode()) . '" includePrefix="' . Entity::hsc($skinObj->getIncludePrefix()) . '">' . "\n";
\r
704 echo "\t\t<description>" . Entity::hsc($skinObj->getDescription()) . "</description>\n";
\r
706 $res = DB::getResult('SELECT stype, scontent FROM '. sql_table('skin') .' WHERE sdesc=' . $skinId);
\r
707 foreach ( $res as $row )
\r
709 echo "\t\t" . '<part name="',Entity::hsc($row['stype']) . '">';
\r
710 echo '<![CDATA[' . $this->escapeCDATA($row['scontent']) . ']]>';
\r
711 echo "</part>\n\n";
\r
713 echo "\t</skin>\n\n\n";
\r
716 // contents templates
\r
717 foreach ( $this->templates as $templateId => $templateName )
\r
719 $templateId = intval($templateId);
\r
721 echo "\t" . '<template name="' . Entity::hsc($templateName) . '">' . "\n";
\r
722 echo "\t\t<description>" . Entity::hsc(Template::getDesc($templateId)) . "</description>\n";
\r
724 $res = DB::getResult('SELECT tpartname, tcontent FROM '. sql_table('template') .' WHERE tdesc=' . $templateId);
\r
725 foreach ( $res as $row )
\r
727 echo "\t\t" . '<part name="' . Entity::hsc($row['tpartname']) . '">';
\r
728 echo '<![CDATA[' . $this->escapeCDATA($row['tcontent']) . ']]>';
\r
729 echo "</part>\n\n";
\r
732 echo "\t</template>\n\n\n";
\r
734 echo '</nucleusskin>';
\r
738 * Escapes CDATA content so it can be included in another CDATA section
\r
740 private function escapeCDATA($cdata)
\r
742 return preg_replace('/]]>/', ']]]]><![CDATA[>', $cdata);
\r