Opml.class.php 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. <?php
  2. /*
  3. @nom: Opml
  4. @auteur: Sbgodin (christophe.henry@sbgodin.fr)
  5. @description: Classe de gestion de l'import/export au format OPML
  6. */
  7. require_once("common.php");
  8. class Opml {
  9. // liens déjà connus, déjà abonnés, au moment de l'importation
  10. public $alreadyKnowns = array();
  11. /**
  12. * Met à jour les données des flux.
  13. */
  14. protected function update() {
  15. global $feedManager, $folderManager;
  16. $this->feeds = $feedManager->populate('name');
  17. $this->folders = $folderManager->loadAll(array('parent'=>-1),'name');
  18. }
  19. /**
  20. * Convertit les caractères qui interfèrent avec le XML
  21. */
  22. protected function escapeXml($string) {
  23. /** Les entités sont utiles pour deux raisons : encodage et
  24. échappement. L'encodage n'est pas un problème, l'application travaille
  25. nativement avec Unicode. L'échappement dans XML impose d'échapper les
  26. esperluettes (&) et les guillemets. Ces derniers sont utilisés comme
  27. séparateur de chaine. Les simples cotes restent intactes.
  28. * On retire toutes les entités de sorte à obtenir une chaîne totalement
  29. en UTF-8. Elle peut alors contenir des & et des ", nocifs pour XML.
  30. * On échappe les caractères & et " de sorte à ce que le contenu dans le
  31. XML soit correct. On suppose qu'à la lecture, cet échappement est
  32. annulé.
  33. * Accessoirement, on remplace les espaces non signifiants par une seule
  34. espace. C'est le cas des retours chariots physiques, non
  35. interprétables.
  36. */
  37. // Retire toutes les entités, &amp; &eacute; etc.
  38. $string = html_entity_decode($string, ENT_COMPAT, 'UTF-8' );
  39. // Remet les entités HTML comme &amp; mais ne touche pas aux accents.
  40. $string = htmlspecialchars($string, ENT_COMPAT, 'UTF-8');
  41. // Supprime les blancs non signifiants comme les sauts de ligne.
  42. $string = preg_replace('/\s+/', ' ', $string);
  43. return $string;
  44. }
  45. /**
  46. * Exporte récursivement les flux.
  47. */
  48. protected function exportRecursive($folders, $identLevel=0) {
  49. $_ = ' ';
  50. $__ = ''; for($i=0;$i<$identLevel;$i++) $__.=$_;
  51. $xmlStream = '';
  52. foreach($folders as $folder) {
  53. // Pas utilisé, vu qu'il n'y a qu'un seul niveau de dossiers.
  54. $xmlStream .= $this->exportRecursive(
  55. $folder->getFolders(), $identLevel+1
  56. );
  57. $feeds = $folder->getFeeds();
  58. if (empty($feeds)) continue;
  59. $text = $this->escapeXml($folder->getName());
  60. $xmlStream .= "{$__}<outline text=\"$text\">\n";
  61. foreach($feeds as $feed){
  62. $url = $this->escapeXml($feed->getUrl());
  63. $website = $this->escapeXml($feed->getWebsite());
  64. $title = $this->escapeXml($feed->getName());
  65. $text = $title;
  66. $description = $this->escapeXml($feed->getDescription());
  67. $xmlStream .= "{$__}{$_}<outline "
  68. ."type=\"rss\" "
  69. ."xmlUrl=\"$url\" "
  70. ."htmlUrl=\"$website\" "
  71. ."text=\"$text\" "
  72. ."title=\"$title\" "
  73. ."description=\"$description\""
  74. ."/>\n";
  75. }
  76. $xmlStream .= "{$__}</outline>\n";
  77. }
  78. return $xmlStream;
  79. }
  80. /**
  81. * Exporte l'ensemble des flux et sort les en-têtes.
  82. */
  83. function export() {
  84. $this->update();
  85. $date = date('D, d M Y H:i:s O');
  86. $xmlStream = "<?xml version=\"1.0\" encoding=\"utf-8\"?>
  87. <opml version=\"2.0\">
  88. <head>
  89. <title>Leed export</title>
  90. <ownerName>Leed</ownerName>
  91. <dateCreated>$date</dateCreated>
  92. </head>
  93. <body>\n";
  94. $xmlStream .= $this->exportRecursive($this->folders, 2);
  95. $xmlStream .= " </body>\n</opml>\n";
  96. return $xmlStream;
  97. }
  98. protected function importRec($folder, $folderId=1){
  99. $folderManager = new Folder();
  100. $feedManager = new Feed();
  101. foreach($folder as $item) {
  102. // Cela varie selon les implémentations d'OPML.
  103. $feedName = $item['text'] ? 'text' : 'title';
  104. if (isset($item->outline[0])) { // un dossier
  105. $folder = $folderManager->load(array('name'=>$item[$feedName]));
  106. $folder = (!$folder?new Folder():$folder);
  107. $folder->setName($item[$feedName]);
  108. $folder->setParent(($folderId==1?-1:$folderId));
  109. $folder->setIsopen(0);
  110. if($folder->getId()=='') $folder->save();
  111. $this->importRec($item->outline,$folder->getId());
  112. } else { // un flux
  113. $newFeed = $feedManager->load(array('url'=>$item[0]['xmlUrl']));
  114. $newFeed = (!$newFeed?new Feed():$newFeed);
  115. if($newFeed->getId()=='') {
  116. /* Ne télécharge pas à nouveau le même lien, même s'il est
  117. dans un autre dossier. */
  118. $newFeed->setName($item[0][$feedName]);
  119. $newFeed->setUrl($item[0]['xmlUrl']);
  120. $newFeed->setDescription($item[0]['description']);
  121. $newFeed->setWebsite($item[0]['htmlUrl']);
  122. $newFeed->setFolder($folderId);
  123. $newFeed->save();
  124. // $newFeed->parse();
  125. } else {
  126. $this->alreadyKnowns[]= (object) array(
  127. 'description' => $newFeed->getDescription(),
  128. 'feedName' => $newFeed->getName(),
  129. 'xmlUrl' => $newFeed->getUrl()
  130. );
  131. }
  132. }
  133. }
  134. }
  135. /**
  136. * Importe les flux.
  137. */
  138. function import() {
  139. require_once("SimplePie.class.php");
  140. $file = $_FILES['newImport']['tmp_name'];
  141. $internalErrors = libxml_use_internal_errors(true);
  142. $xml = @simplexml_load_file($file);
  143. $errorOutput = array();
  144. foreach (libxml_get_errors() as $error) {
  145. $errorOutput []= "{$error->message} (line {$error->line})";
  146. }
  147. libxml_clear_errors();
  148. libxml_use_internal_errors($internalErrors);
  149. if (!empty($xml) && empty($errorOutput)) {
  150. $this->importRec($xml->body->outline);
  151. }
  152. return $errorOutput;
  153. }
  154. }
  155. ?>