OSDN Git Service

本家Nucleus CMS 4.0のリビジョン1626をコミット
[nucleus-jp/nucleus-next.git] / nucleus / libs / skinie.php
1 <?php
2 /*
3  * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
4  * Copyright (C) 2002-2009 The Nucleus Group
5  *
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)
11  */
12 /**
13  *      This class contains two classes that can be used for importing and
14  *      exporting Nucleus skins: SKINIMPORT and SKINEXPORT
15  *
16  * @license http://nucleuscms.org/license.txt GNU General Public License
17  * @copyright Copyright (C) 2002-2009 The Nucleus Group
18  * @version $Id: skinie.php 1624 2012-01-09 11:36:20Z sakamocchi $
19  */
20
21 class SKINIMPORT {
22         // hardcoded value (see constructor). When 1, interesting info about the
23         // parsing process is sent to the output
24         var $debug;
25         
26         // parser/file pointer
27         var $parser;
28         var $fp;
29         
30         // which data has been read?
31         var $metaDataRead;
32         var $allRead;
33         
34         // extracted data
35         var $skins;
36         var $templates;
37         var $info;
38         
39         // to maintain track of where we are inside the XML file
40         var $inXml;
41         var $inData;
42         var $inMeta;
43         var $inSkin;
44         var $inTemplate;
45         var $currentName;
46         var $currentPartName;
47         var $cdata;
48         
49         /**
50          * constructor initializes data structures
51          */
52         function SKINIMPORT()
53         {
54                 // disable magic_quotes_runtime if it's turned on
55                 //set_magic_quotes_runtime(0);
56                 if ( version_compare(PHP_VERSION, '5.3.0', '<') )
57                 {
58                         ini_set('magic_quotes_runtime', '0');
59                 }
60                 
61                 // debugging mode?
62                 $this->debug = 0;
63                 
64                 $this->reset();
65         }
66
67         function reset()
68         {
69                 if ( $this->parser )
70                 {
71                         xml_parser_free($this->parser);
72                 }
73                 
74                 // XML file pointer
75                 $this->fp = 0;
76                 
77                 // which data has been read?
78                 $this->metaDataRead = 0;
79                 $this->allRead = 0;
80                 
81                 // to maintain track of where we are inside the XML file
82                 $this->inXml = 0;
83                 $this->inData = 0;
84                 $this->inMeta = 0;
85                 $this->inSkin = 0;
86                 $this->inTemplate = 0;
87                 $this->currentName = '';
88                 $this->currentPartName = '';
89                 
90                 // character data pile
91                 $this->cdata = '';
92                 
93                 // list of skinnames and templatenames (will be array of array)
94                 $this->skins = array();
95                 $this->templates = array();
96                 
97                 // extra info included in the XML files (e.g. installation notes)
98                 $this->info = '';
99                 
100                 // init XML parser
101                 $this->parser = xml_parser_create();
102                 xml_set_object($this->parser, $this);
103                 xml_set_element_handler($this->parser, 'startElement', 'endElement');
104                 xml_set_character_data_handler($this->parser, 'characterData');
105                 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
106         }
107         
108         /**
109          * Reads an XML file into memory
110          *
111          * @param $filename
112          *              Which file to read
113          * @param $metaOnly
114          *              Set to 1 when only the metadata needs to be read (optional, default 0)
115          */
116         function readFile($filename, $metaOnly = 0)
117         {
118                 // open file
119                 $this->fp = @fopen($filename, 'r');
120                 if ( !$this->fp )
121                 {
122                         return _SKINIE_ERROR_FAILEDOPEN_FILEURL;
123                 }
124                 
125                 // here we go!
126                 $this->inXml = 1;
127                 
128                 $tempbuffer = null;
129                 
130                 while ( !feof($this->fp) )
131                 {
132                         $tempbuffer .= fread($this->fp, 4096);
133                 }
134                 fclose($this->fp);
135                 
136 /*
137         [2004-08-04] dekarma - Took this out since it messes up good XML if it has skins/templates
138                                                    with CDATA sections. need to investigate consequences.
139                                                    see bug [ 999914 ] Import fails (multiple skins in XML/one of them with CDATA)
140
141                 // backwards compatibility with the non-wellformed skinbackup.xml files
142                 // generated by v2/v3 (when CDATA sections were present in skins)
143                 // split up those CDATA sections into multiple ones
144                 $tempbuffer = preg_replace_callback(
145                         "/(<!\[CDATA\[[^]]*?<!\[CDATA\[[^]]*)((?:\]\].*?<!\[CDATA.*?)*)(\]\])(.*\]\])/ms",
146                         create_function(
147                                 '$matches',
148                                 'return $matches[1] . preg_replace("/(\]\])(.*?<!\[CDATA)/ms","]]]]><![CDATA[$2",$matches[2])."]]]]><![CDATA[".$matches[4];'
149                         ),
150                         $tempbuffer
151                 );
152 */
153                 $temp = tmpfile();
154                 fwrite($temp, $tempbuffer);
155                 rewind($temp);
156                 
157                 while ( ($buffer = fread($temp, 4096) )
158                  && (!$metaOnly || ($metaOnly && !$this->metaDataRead)) )
159                  {
160                         $err = xml_parse( $this->parser, $buffer, feof($temp) );
161                         if ( !$err && $this->debug )
162                         {
163                                 echo 'ERROR: ', xml_error_string(xml_get_error_code($this->parser)), '<br />';
164                         }
165                 }
166                 
167                 // all done
168                 $this->inXml = 0;
169                 fclose($temp);
170         }
171         
172         /**
173          * Returns the list of skin names
174          */
175         function getSkinNames()
176         {
177                 return array_keys($this->skins);
178         }
179         
180         /**
181          * Returns the list of template names
182          */
183         function getTemplateNames()
184         {
185                 return array_keys($this->templates);
186         }
187         
188         /**
189          * Returns the extra information included in the XML file
190          */
191         function getInfo()
192         {
193                 return $this->info;
194         }
195         
196         /**
197          * Writes the skins and templates to the database
198          *
199          * @param $allowOverwrite
200          *              set to 1 when allowed to overwrite existing skins with the same name
201          *              (default = 0)
202          */
203         function writeToDatabase($allowOverwrite = 0)
204         {
205                 $existingSkins = $this->checkSkinNameClashes();
206                 $existingTemplates = $this->checkTemplateNameClashes();
207                 $invalidSkinNames = $this->checkSkinNamesValid();
208                 $invalidTemplateNames = $this->checkTemplateNamesValid();
209                 
210                 // if there are invalid skin or template names, stop executioin and return and error
211                 if ( (sizeof($invalidSkinNames) > 0) || (sizeof($invalidTemplateNames) > 0) )
212                 {
213                         $inames_error = "<p>"._SKINIE_INVALID_NAMES_DETECTED."</p>\n";
214                         $inames_error .= "<ul>";
215                         foreach( $invalidSkinNames as $sName )
216                         {
217                                 $inames_error .= "<li>".i18n::hsc($sName)."</li>";
218                         }
219                         foreach( $invalidTemplateNames as $sName )
220                         {
221                                 $inames_error .= "<li>".i18n::hsc($sName)."</li>";
222                         }
223                         $inames_error .= "</ul>";
224                         return $inames_error;
225                 }
226                 
227                 // if not allowed to overwrite, check if any nameclashes exists
228                 if ( !$allowOverwrite )
229                 {
230                         if ( (sizeof($existingSkins) > 0) || (sizeof($existingTemplates) > 0) )
231                         {
232                                 return _SKINIE_NAME_CLASHES_DETECTED;
233                         }
234                 }
235                 
236                 foreach ( $this->skins as $skinName => $data )
237                 {
238                         // 1. if exists: delete all part data, update desc data
239                         //    if not exists: create desc
240                         if ( in_array($skinName, $existingSkins) )
241                         {
242                                 $skinObj = SKIN::createFromName($skinName);
243                                 
244                                 // delete all parts of the skin
245                                 $skinObj->deleteAllParts();
246                                 
247                                 // update general info
248                                 $skinObj->updateGeneralInfo(
249                                         $skinName,
250                                         $data['description'],
251                                         $data['type'],
252                                         $data['includeMode'],
253                                         $data['includePrefix']
254                                 );
255                         }
256                         else
257                         {
258                                 $skinid = SKIN::createNew(
259                                         $skinName,
260                                         $data['description'],
261                                         $data['type'],
262                                         $data['includeMode'],
263                                         $data['includePrefix']
264                                 );
265                                 $skinObj = new SKIN($skinid);
266                         }
267                         
268                         // 2. add parts
269                         foreach ( $data['parts'] as $partName => $partContent )
270                         {
271                                 $skinObj->update($partName, $partContent);
272                         }
273                 }
274                 
275                 foreach ( $this->templates as $templateName => $data )
276                 {
277                         // 1. if exists: delete all part data, update desc data
278                         //    if not exists: create desc
279                         if ( in_array($templateName, $existingTemplates) )
280                         {
281                                 $templateObj = TEMPLATE::createFromName($templateName);
282                                 
283                                 // delete all parts of the template
284                                 $templateObj->deleteAllParts();
285                                 
286                                 // update general info
287                                 $templateObj->updateGeneralInfo($templateName, $data['description']);
288                         }
289                         else
290                         {
291                                 $templateid = TEMPLATE::createNew($templateName, $data['description']);
292                                 $templateObj = new TEMPLATE($templateid);
293                         }
294                         
295                         // 2. add parts
296                         foreach ( $data['parts'] as $partName => $partContent )
297                         {
298                                 $templateObj->update($partName, $partContent);
299                         }
300                 }
301         }
302         
303         /**
304           * returns an array of all the skin nameclashes (empty array when no name clashes)
305           */
306         function checkSkinNameClashes()
307         {
308                 $clashes = array();
309                 
310                 foreach ( $this->skins as $skinName => $data )
311                 {
312                         if ( SKIN::exists($skinName) )
313                         {
314                                 array_push($clashes, $skinName);
315                         }
316                 }
317                 return $clashes;
318         }
319         
320         /**
321           * returns an array of all the template nameclashes
322           * (empty array when no name clashes)
323           */
324         function checkTemplateNameClashes()
325         {
326                 $clashes = array();
327                 
328                 foreach ( $this->templates as $templateName => $data )
329                 {
330                         if ( TEMPLATE::exists($templateName) )
331                         {
332                                 array_push($clashes, $templateName);
333                         }
334                 }
335                 return $clashes;
336         }
337         
338         /**
339           * returns an array of all the invalid skin names (empty array when no invalid names )
340           */
341         function checkSkinNamesValid()
342         {
343                 $notValid = array();
344                 
345                 foreach ( $this->skins as $skinName => $data )
346                 {
347                         if ( !isValidSkinName($skinName) )
348                         {
349                                 array_push($notValid, $skinName);
350                         }
351                 }
352                 return $notValid;
353         }
354         
355         /**
356           * returns an array of all the invalid template names (empty array when no invalid names )
357           */
358         function checkTemplateNamesValid()
359         {
360                 $notValid = array();
361                 
362                 foreach ( $this->templates as $templateName => $data )
363                 {
364                         if ( !isValidTemplateName($templateName) )
365                         {
366                                 array_push($notValid, $templateName);
367                         }
368                 }
369                 return $notValid;
370         }
371         
372         /**
373          * Called by XML parser for each new start element encountered
374          */
375         function startElement($parser, $name, $attrs)
376         {
377                 foreach( $attrs as $key=>$value )
378                 {
379                         $attrs[$key] = i18n::hsc($value);
380                 }
381                 
382                 if ( $this->debug )
383                 {
384                         echo 'START: ', i18n::hsc($name), '<br />';
385                 }
386                 
387                 switch ( $name )
388                 {
389                         case 'nucleusskin':
390                                 $this->inData = 1;
391                                 break;
392                         case 'meta':
393                                 $this->inMeta = 1;
394                                 break;
395                         case 'info':
396                                 // no action needed
397                                 break;
398                         case 'skin':
399                                 if ( !$this->inMeta )
400                                 {
401                                         $this->inSkin = 1;
402                                         $this->currentName = $attrs['name'];
403                                         $this->skins[$this->currentName]['type'] = $attrs['type'];
404                                         $this->skins[$this->currentName]['includeMode'] = $attrs['includeMode'];
405                                         $this->skins[$this->currentName]['includePrefix'] = $attrs['includePrefix'];
406                                         $this->skins[$this->currentName]['parts'] = array();
407                                 }
408                                 else
409                                 {
410                                         $this->skins[$attrs['name']] = array();
411                                         $this->skins[$attrs['name']]['parts'] = array();
412                                 }
413                                 break;
414                         case 'template':
415                                 if ( !$this->inMeta )
416                                 {
417                                         $this->inTemplate = 1;
418                                         $this->currentName = $attrs['name'];
419                                         $this->templates[$this->currentName]['parts'] = array();
420                                 }
421                                 else
422                                 {
423                                         $this->templates[$attrs['name']] = array();
424                                         $this->templates[$attrs['name']]['parts'] = array();
425                                 }
426                                 break;
427                         case 'description':
428                                 // no action needed
429                                 break;
430                         case 'part':
431                                 $this->currentPartName = $attrs['name'];
432                                 break;
433                         default:
434                                 echo _SKINIE_SEELEMENT_UNEXPECTEDTAG . i18n::hsc($name) . '<br />';
435                                 break;
436                 }
437                 // character data never contains other tags
438                 $this->clearCharacterData();
439         }
440         
441         /**
442           * Called by the XML parser for each closing tag encountered
443           */
444         function endElement($parser, $name)
445         {
446                 if ( $this->debug )
447                 {
448                         echo 'END: ' . i18n::hsc($name) . '<br />';
449                 }
450                 
451                 switch ( $name )
452                 {
453                         case 'nucleusskin':
454                                 $this->inData = 0;
455                                 $this->allRead = 1;
456                                 break;
457                         case 'meta':
458                                 $this->inMeta = 0;
459                                 $this->metaDataRead = 1;
460                                 break;
461                         case 'info':
462                                 $this->info = $this->getCharacterData();
463                         case 'skin':
464                                 if ( !$this->inMeta )
465                                 {
466                                         $this->inSkin = 0;
467                                 }
468                                 break;
469                         case 'template':
470                                 if ( !$this->inMeta )
471                                 {
472                                         $this->inTemplate = 0;
473                                 }
474                                 break;
475                         case 'description':
476                                 if ( $this->inSkin )
477                                 {
478                                         $this->skins[$this->currentName]['description'] = $this->getCharacterData();
479                                 }
480                                 else
481                                 {
482                                         $this->templates[$this->currentName]['description'] = $this->getCharacterData();
483                                 }
484                                 break;
485                         case 'part':
486                                 if ( $this->inSkin )
487                                 {
488                                         $this->skins[$this->currentName]['parts'][$this->currentPartName] = $this->getCharacterData();
489                                 }
490                                 else
491                                 {
492                                         $this->templates[$this->currentName]['parts'][$this->currentPartName] = $this->getCharacterData();
493                                 }
494                                 break;
495                         default:
496                                 echo _SKINIE_SEELEMENT_UNEXPECTEDTAG . i18n::hsc($name) . '<br />';
497                                 break;
498                 }
499                 $this->clearCharacterData();
500         }
501         
502         /**
503          * Called by XML parser for data inside elements
504          */
505         function characterData ($parser, $data)
506         {
507                 if ( $this->debug )
508                 {
509                         echo 'NEW DATA: ' . i18n::hsc($data) . '<br />';
510                 }
511                 $this->cdata .= $data;
512         }
513         
514         /**
515          * Returns the data collected so far
516          */
517         function getCharacterData()
518         {
519                 return $this->cdata;
520         }
521         
522         /**
523          * Clears the data buffer
524          */
525         function clearCharacterData()
526         {
527                 $this->cdata = '';
528         }
529         
530         /**
531          * Static method that looks for importable XML files in subdirs of the given dir
532          */
533         function searchForCandidates($dir)
534         {
535                 $candidates = array();
536                 
537                 $dirhandle = opendir($dir);
538                 while ( $filename = readdir($dirhandle) )
539                 {
540                         if ( @is_dir($dir . $filename) && ($filename != '.') && ($filename != '..') )
541                         {
542                                 $xml_file = $dir . $filename . '/skinbackup.xml';
543                                 if ( file_exists($xml_file) && is_readable($xml_file) )
544                                 {
545                                         //$xml_file;
546                                         $candidates[$filename] = $filename;
547                                 }
548                                 
549                                 // backwards compatibility
550                                 $xml_file = $dir . $filename . '/skindata.xml';
551                                 if ( file_exists($xml_file) && is_readable($xml_file) )
552                                 {
553                                         //$xml_file;
554                                         $candidates[$filename] = $filename;
555                                 }
556                         }
557                 }
558                 closedir($dirhandle);
559                 return $candidates;
560         }
561 }
562
563 class SKINEXPORT {
564         var $templates;
565         var $skins;
566         var $info;
567         
568         /**
569          * Constructor initializes data structures
570          */
571         function SKINEXPORT()
572         {
573                 // list of templateIDs to export
574                 $this->templates = array();
575                 
576                 // list of skinIDs to export
577                 $this->skins = array();
578                 
579                 // extra info to be in XML file
580                 $this->info = '';
581         }
582         
583         /**
584          * Adds a template to be exported
585          *
586          * @param id
587          *              template ID
588          * @result false when no such ID exists
589          */
590         function addTemplate($id)
591         {
592                 if ( !TEMPLATE::existsID($id) )
593                 {
594                         return 0;
595                 }
596                 
597                 $this->templates[$id] = TEMPLATE::getNameFromId($id);
598                 return 1;
599         }
600         
601         /**
602          * Adds a skin to be exported
603          *
604          * @param id
605          *              skin ID
606          * @result false when no such ID exists
607          */
608         function addSkin($id)
609         {
610                 if ( !SKIN::existsID($id) )
611                 {
612                         return 0;
613                 }
614                 
615                 $this->skins[$id] = SKIN::getNameFromId($id);
616                 return 1;
617         }
618         
619         /**
620          * Sets the extra info to be included in the exported file
621          */
622         function setInfo($info)
623         {
624                 $this->info = $info;
625         }
626         
627         /**
628          * Outputs the XML contents of the export file
629          *
630          * @param $setHeaders
631          *              set to 0 if you don't want to send out headers
632          *              (optional, default 1)
633          */
634         function export($setHeaders = 1)
635         {
636                 if ( $setHeaders )
637                 {
638                         // make sure the mimetype is correct, and that the data does not show up
639                         // in the browser, but gets saved into and XML file (popup download window)
640                         header('Content-Type: text/xml');
641                         header('Content-Disposition: attachment; filename="skinbackup.xml"');
642                         header('Expires: 0');
643                         header('Pragma: no-cache');
644                 }
645                 
646                 echo "<nucleusskin>\n";
647                 
648                 // meta
649                 echo "\t<meta>\n";
650                 // skins
651                 foreach ( $this->skins as $skinId => $skinName )
652                 {
653                         echo "\t\t" . '<skin name="' . i18n::hsc($skinName) . '" />' . "\n";
654                 }
655                 // templates
656                 foreach ( $this->templates as $templateId => $templateName )
657                 {
658                         echo "\t\t" . '<template name="' . i18n::hsc($templateName) . '" />' . "\n";
659                 }
660                 // extra info
661                 if ( $this->info )
662                 {
663                         echo "\t\t<info><![CDATA[" . $this->info . "]]></info>\n";
664                 }
665                 echo "\t</meta>\n\n\n";
666                 
667                 // contents skins
668                 foreach ($this->skins as $skinId => $skinName)
669                 {
670                         $skinId = intval($skinId);
671                         $skinObj = new SKIN($skinId);
672                         
673                         echo "\t" . '<skin name="' . i18n::hsc($skinName) . '" type="' . i18n::hsc($skinObj->getContentType()) . '" includeMode="' . i18n::hsc($skinObj->getIncludeMode()) . '" includePrefix="' . i18n::hsc($skinObj->getIncludePrefix()) . '">' . "\n";
674                         echo "\t\t<description>" . i18n::hsc($skinObj->getDescription()) . "</description>\n";
675                         
676                         $res = sql_query('SELECT stype, scontent FROM '. sql_table('skin') .' WHERE sdesc=' . $skinId);
677                         while ( $partObj = sql_fetch_object($res) )
678                         {
679                                 echo "\t\t" . '<part name="',i18n::hsc($partObj->stype) . '">';
680                                 echo '<![CDATA[' . $this->escapeCDATA($partObj->scontent) . ']]>';
681                                 echo "</part>\n\n";
682                         }
683                         echo "\t</skin>\n\n\n";
684                 }
685                 
686                 // contents templates
687                 foreach ( $this->templates as $templateId => $templateName )
688                 {
689                         $templateId = intval($templateId);
690                         
691                         echo "\t" . '<template name="' . i18n::hsc($templateName) . '">' . "\n";
692                         echo "\t\t<description>" . i18n::hsc(TEMPLATE::getDesc($templateId)) . "</description>\n";
693                         
694                         $res = sql_query('SELECT tpartname, tcontent FROM '. sql_table('template') .' WHERE tdesc=' . $templateId);
695                         while ( $partObj = sql_fetch_object($res) )
696                         {
697                                 echo "\t\t" . '<part name="' . i18n::hsc($partObj->tpartname) . '">';
698                                 echo '<![CDATA[' . $this->escapeCDATA($partObj->tcontent) . ']]>';
699                                 echo "</part>\n\n";
700                         }
701                         
702                         echo "\t</template>\n\n\n";
703                 }
704                 echo '</nucleusskin>';
705         }
706         
707         /**
708          * Escapes CDATA content so it can be included in another CDATA section
709          */
710         function escapeCDATA($cdata)
711         {
712                 return preg_replace('/]]>/', ']]]]><![CDATA[>', $cdata);
713         }
714 }
715
716 ?>