opml.phtml 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * @param array<FreshRSS_Feed> $feeds
  5. * @return list<array<string,string|bool|int>>
  6. */
  7. function feedsToOutlines(array $feeds, bool $excludeMutedFeeds = false): array {
  8. $outlines = [];
  9. foreach ($feeds as $feed) {
  10. if ($feed->mute() && $excludeMutedFeeds) {
  11. continue;
  12. }
  13. $outline = [
  14. 'text' => htmlspecialchars_decode($feed->name(), ENT_QUOTES),
  15. 'type' => FreshRSS_Export_Service::TYPE_RSS_ATOM,
  16. 'xmlUrl' => htmlspecialchars_decode($feed->url(), ENT_QUOTES),
  17. 'htmlUrl' => htmlspecialchars_decode($feed->website(), ENT_QUOTES),
  18. 'description' => htmlspecialchars_decode($feed->description(), ENT_QUOTES),
  19. ];
  20. switch ($feed->kind()) {
  21. case FreshRSS_Feed::KIND_HTML_XPATH:
  22. $outline['type'] = FreshRSS_Export_Service::TYPE_HTML_XPATH;
  23. break;
  24. case FreshRSS_Feed::KIND_XML_XPATH:
  25. $outline['type'] = FreshRSS_Export_Service::TYPE_XML_XPATH;
  26. break;
  27. case FreshRSS_Feed::KIND_JSON_DOTNOTATION:
  28. $outline['type'] = FreshRSS_Export_Service::TYPE_JSON_DOTNOTATION;
  29. break;
  30. case FreshRSS_Feed::KIND_JSONFEED:
  31. $outline['type'] = FreshRSS_Export_Service::TYPE_JSONFEED;
  32. break;
  33. case FreshRSS_Feed::KIND_HTML_XPATH_JSON_DOTNOTATION:
  34. $outline['type'] = FreshRSS_Export_Service::TYPE_HTML_XPATH_JSON_DOTNOTATION;
  35. break;
  36. }
  37. $outline['frss:priority'] = match ($feed->priority()) {
  38. FreshRSS_Feed::PRIORITY_IMPORTANT => FreshRSS_Export_Service::PRIORITY_IMPORTANT,
  39. FreshRSS_Feed::PRIORITY_MAIN_STREAM => null, // Default
  40. FreshRSS_Feed::PRIORITY_CATEGORY => FreshRSS_Export_Service::PRIORITY_CATEGORY,
  41. FreshRSS_Feed::PRIORITY_FEED => FreshRSS_Export_Service::PRIORITY_FEED,
  42. FreshRSS_Feed::PRIORITY_HIDDEN => FreshRSS_Export_Service::PRIORITY_HIDDEN,
  43. default => null,
  44. };
  45. if ($feed->attributeString('unicityCriteria') != '' && $feed->attributeString('unicityCriteria') !== 'id') {
  46. $outline['frss:unicityCriteria'] = $feed->attributeString('unicityCriteria');
  47. }
  48. if ($feed->attributeBoolean('unicityCriteriaForced')) {
  49. $outline['frss:unicityCriteriaForced'] = 'true';
  50. }
  51. if ($feed->kind() === FreshRSS_Feed::KIND_HTML_XPATH || $feed->kind() === FreshRSS_Feed::KIND_XML_XPATH) {
  52. /** @var array<string,string> */
  53. $xPathSettings = $feed->attributeArray('xpath') ?? [];
  54. $outline['frss:xPathItem'] = $xPathSettings['item'] ?? null;
  55. $outline['frss:xPathItemTitle'] = $xPathSettings['itemTitle'] ?? null;
  56. $outline['frss:xPathItemContent'] = $xPathSettings['itemContent'] ?? null;
  57. $outline['frss:xPathItemUri'] = $xPathSettings['itemUri'] ?? null;
  58. $outline['frss:xPathItemAuthor'] = $xPathSettings['itemAuthor'] ?? null;
  59. $outline['frss:xPathItemTimestamp'] = $xPathSettings['itemTimestamp'] ?? null;
  60. $outline['frss:xPathItemTimeFormat'] = $xPathSettings['itemTimeFormat'] ?? null;
  61. $outline['frss:xPathItemThumbnail'] = $xPathSettings['itemThumbnail'] ?? null;
  62. $outline['frss:xPathItemCategories'] = $xPathSettings['itemCategories'] ?? null;
  63. $outline['frss:xPathItemUid'] = $xPathSettings['itemUid'] ?? null;
  64. } elseif ($feed->kind() === FreshRSS_Feed::KIND_JSON_DOTNOTATION || $feed->kind() === FreshRSS_Feed::KIND_HTML_XPATH_JSON_DOTNOTATION) {
  65. /** @var array<string,string> */
  66. $jsonSettings = $feed->attributeArray('json_dotnotation') ?? [];
  67. $outline['frss:jsonItem'] = $jsonSettings['item'] ?? null;
  68. $outline['frss:jsonItemTitle'] = $jsonSettings['itemTitle'] ?? null;
  69. $outline['frss:jsonItemContent'] = $jsonSettings['itemContent'] ?? null;
  70. $outline['frss:jsonItemUri'] = $jsonSettings['itemUri'] ?? null;
  71. $outline['frss:jsonItemAuthor'] = $jsonSettings['itemAuthor'] ?? null;
  72. $outline['frss:jsonItemTimestamp'] = $jsonSettings['itemTimestamp'] ?? null;
  73. $outline['frss:jsonItemTimeFormat'] = $jsonSettings['itemTimeFormat'] ?? null;
  74. $outline['frss:jsonItemThumbnail'] = $jsonSettings['itemThumbnail'] ?? null;
  75. $outline['frss:jsonItemCategories'] = $jsonSettings['itemCategories'] ?? null;
  76. $outline['frss:jsonItemUid'] = $jsonSettings['itemUid'] ?? null;
  77. if ($feed->kind() === FreshRSS_Feed::KIND_HTML_XPATH_JSON_DOTNOTATION) {
  78. $outline['frss:xPathToJson'] = $feed->attributeString('xPathToJson');
  79. }
  80. }
  81. if (!empty($feed->filtersAction('read'))) {
  82. $filters = '';
  83. foreach ($feed->filtersAction('read') as $filterRead) {
  84. $filters .= $filterRead->toString(expandUserQueries: false) . "\n";
  85. }
  86. $filters = trim($filters);
  87. $outline['frss:filtersActionRead'] = $filters;
  88. }
  89. if ($feed->pathEntries() != '') {
  90. $outline['frss:cssFullContent'] = htmlspecialchars_decode($feed->pathEntries(), ENT_QUOTES);
  91. }
  92. if (!empty($feed->attributeArray('path_entries_conditions'))) {
  93. $conditions = '';
  94. foreach ($feed->attributeArray('path_entries_conditions') as $condition) {
  95. if (is_string($condition)) {
  96. $conditions .= $condition . "\n";
  97. }
  98. }
  99. $conditions = trim($conditions);
  100. $outline['frss:cssFullContentConditions'] = $conditions;
  101. }
  102. if ($feed->attributeString('path_entries_filter') != '') {
  103. $outline['frss:cssContentFilter'] = $feed->attributeString('path_entries_filter');
  104. }
  105. $curl_params = $feed->attributeArray('curl_params');
  106. if (!empty($curl_params)) {
  107. $outline['frss:CURLOPT_COOKIE'] = $curl_params[CURLOPT_COOKIE] ?? null;
  108. $outline['frss:CURLOPT_COOKIEFILE'] = $curl_params[CURLOPT_COOKIEFILE] ?? null;
  109. $outline['frss:CURLOPT_FOLLOWLOCATION'] = $curl_params[CURLOPT_FOLLOWLOCATION] ?? null;
  110. $outline['frss:CURLOPT_MAXREDIRS'] = $curl_params[CURLOPT_MAXREDIRS] ?? null;
  111. $outline['frss:CURLOPT_POST'] = $curl_params[CURLOPT_POST] ?? null;
  112. $outline['frss:CURLOPT_POSTFIELDS'] = $curl_params[CURLOPT_POSTFIELDS] ?? null;
  113. $outline['frss:CURLOPT_PROXY'] = $curl_params[CURLOPT_PROXY] ?? null;
  114. $outline['frss:CURLOPT_PROXYTYPE'] = $curl_params[CURLOPT_PROXYTYPE] ?? null;
  115. if ($outline['frss:CURLOPT_PROXYTYPE'] === 3) { // Legacy for NONE
  116. $outline['frss:CURLOPT_PROXYTYPE'] = -1;
  117. }
  118. $outline['frss:CURLOPT_USERAGENT'] = $curl_params[CURLOPT_USERAGENT] ?? null;
  119. if (!empty($curl_params[CURLOPT_HTTPHEADER]) && is_array($curl_params[CURLOPT_HTTPHEADER])) {
  120. $headers = '';
  121. foreach ($curl_params[CURLOPT_HTTPHEADER] as $header) {
  122. if (is_string($header)) {
  123. $headers .= $header . "\n";
  124. }
  125. }
  126. $headers = trim($headers);
  127. $outline['frss:CURLOPT_HTTPHEADER'] = $headers;
  128. }
  129. }
  130. // Remove null or invalid attributes
  131. $outline = array_filter($outline, static function ($value) { return (is_string($value) || is_int($value) || is_bool($value)) && $value !== ''; });
  132. $outlines[] = $outline;
  133. }
  134. return $outlines;
  135. }
  136. /** @var FreshRSS_View $this */
  137. $opml_array = [
  138. 'namespaces' => [
  139. 'frss' => FreshRSS_Export_Service::FRSS_NAMESPACE,
  140. ],
  141. 'head' => [
  142. 'title' => FreshRSS_Context::systemConf()->title,
  143. 'dateCreated' => new DateTime(),
  144. ],
  145. 'body' => [],
  146. ];
  147. if (!empty($this->categories)) {
  148. foreach ($this->categories as $cat) {
  149. $outline = [
  150. 'text' => htmlspecialchars_decode($cat->name(), ENT_QUOTES),
  151. '@outlines' => feedsToOutlines($cat->feeds(), $this->excludeMutedFeeds),
  152. ];
  153. if ($cat->kind() === FreshRSS_Category::KIND_DYNAMIC_OPML) {
  154. $outline['frss:opmlUrl'] = $cat->attributeString('opml_url');
  155. }
  156. $opml_array['body'][] = $outline;
  157. }
  158. }
  159. if (!empty($this->feeds)) {
  160. $opml_array['body'] = array_merge($opml_array['body'], feedsToOutlines($this->feeds, $this->excludeMutedFeeds));
  161. }
  162. $libopml = new \marienfressinaud\LibOpml\LibOpml(true);
  163. $opml = $libopml->render($opml_array);
  164. /** @var string $opml */
  165. echo $opml;