OSDN Git Service

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