Skip to content

Commit fb0b72f

Browse files
author
Scrub000
committed
[WWFAustraliaBridge] Add bridge
1 parent b8064d9 commit fb0b72f

File tree

1 file changed

+242
-0
lines changed

1 file changed

+242
-0
lines changed

bridges/WWFAustraliaBridge.php

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
<?php
2+
3+
class WWFAustraliaBridge extends BridgeAbstract
4+
{
5+
const NAME = 'WWF Australia';
6+
const URI = 'https://wwf.org.au/';
7+
const DESCRIPTION = 'Latest WWF Australia news or blogs with full article content.';
8+
const MAINTAINER = 'Scrub000';
9+
const CACHE_TIMEOUT = 3600;
10+
11+
const PARAMETERS = [
12+
[
13+
'type' => [
14+
'name' => 'Content Type',
15+
'type' => 'list',
16+
'values' => [
17+
'News' => 'news',
18+
'Blogs' => 'blogs',
19+
],
20+
'defaultValue' => 'news',
21+
],
22+
],
23+
];
24+
25+
public function collectData()
26+
{
27+
$type = $this->getInput('type');
28+
$mainPage = getSimpleHTMLDOM(self::URI . $type . '/');
29+
$buildId = null;
30+
31+
foreach ($mainPage->find('script#__NEXT_DATA__') as $scriptTag) {
32+
$json = json_decode($scriptTag->innertext, true);
33+
if (isset($json['buildId'])) {
34+
$buildId = $json['buildId'];
35+
break;
36+
}
37+
}
38+
39+
if (!$buildId) {
40+
returnServerError('Unable to extract Next.js buildId from main page');
41+
}
42+
43+
$apiUrl = 'https://291t4y9i4t-dsn.algolia.net/1/indexes/wwf_website_prod_date_sorted/query';
44+
$headers = [
45+
'x-algolia-api-key: dd06aa34e50cc3f27dbd8fda34e27b88',
46+
'x-algolia-application-id: 291T4Y9I4T',
47+
'content-type: application/x-www-form-urlencoded',
48+
];
49+
50+
$recordType = $type === 'blogs' ? 'pageBlog' : 'pageNews';
51+
52+
$postData = json_encode([
53+
'query' => '',
54+
'hitsPerPage' => 10,
55+
'filters' => "recordType:'$recordType'",
56+
'attributesToHighlight' => [],
57+
'attributesToSnippet' => [],
58+
'analyticsTags' => [],
59+
]);
60+
61+
$context = stream_context_create([
62+
'http' => [
63+
'method' => 'POST',
64+
'header' => implode("\r\n", $headers),
65+
'content' => $postData,
66+
],
67+
]);
68+
69+
$response = file_get_contents($apiUrl, false, $context);
70+
71+
if ($response === false) {
72+
returnServerError('Failed to fetch data from WWF API');
73+
}
74+
75+
$data = json_decode($response, true);
76+
77+
foreach ($data['hits'] as $hit) {
78+
$item = [
79+
'uri' => $hit['url'],
80+
'title' => $hit['title'],
81+
'timestamp' => strtotime($hit['publishedDate']),
82+
'categories' => array_map(function ($tag) {
83+
$raw = is_array($tag) ? ($tag['key'] ?? '') : (string) $tag;
84+
return ucwords(str_replace('-', ' ', $raw));
85+
}, $hit['tags'] ?? []),
86+
];
87+
88+
$slug = basename($hit['url']);
89+
90+
$jsonUrl = $type === 'blogs'
91+
? "https://wwf.org.au/_next/data/$buildId/blogs/$slug.json"
92+
: "https://wwf.org.au/_next/data/$buildId/news/{$hit['publishedYear']}/$slug.json";
93+
94+
$jsonArticle = json_decode(getContents($jsonUrl), true);
95+
$articleItem = $jsonArticle['pageProps']['pagePayload']['page']['items'][0] ?? null;
96+
97+
$linkedEntries = [];
98+
99+
foreach ($articleItem['bodyContent']['links']['entries']['block'] ?? [] as $entry) {
100+
$linkedEntries[$entry['sys']['id']] = $entry;
101+
}
102+
103+
foreach ($articleItem['bodyContent']['links']['entries']['hyperlink'] ?? [] as $entry) {
104+
$linkedEntries[$entry['sys']['id']] = $entry;
105+
}
106+
107+
$fullContent = null;
108+
109+
if ($articleItem && isset($articleItem['bodyContent']['json'])) {
110+
$fullContent = $this->renderRichText($articleItem['bodyContent']['json'], $linkedEntries);
111+
}
112+
113+
$image = '';
114+
115+
if (!empty($hit['imageUrl'])) {
116+
$image = '<img src="' . htmlspecialchars($hit['imageUrl']) . '" alt="" /><br>';
117+
}
118+
119+
if (!empty($articleItem['hero']['imageSource'][0]['secure_url'])) {
120+
$imageUrl = $articleItem['hero']['imageSource'][0]['secure_url'];
121+
$altText = $articleItem['hero']['imageSource'][0]['context']['custom']['alt'] ?? '';
122+
$image = '<img src="' . htmlspecialchars($imageUrl) . '" alt="' . htmlspecialchars($altText) . '" /><br>';
123+
}
124+
125+
$item['content'] = $image . ($fullContent ?: $hit['content']);
126+
$this->items[] = $item;
127+
}
128+
}
129+
130+
private function renderRichText($json, $linkedEntries = [])
131+
{
132+
$html = '';
133+
134+
foreach ($json['content'] as $node) {
135+
switch ($node['nodeType']) {
136+
case 'paragraph':
137+
case 'heading-2':
138+
case 'heading-3':
139+
$tag = $node['nodeType'] === 'paragraph' ? 'p' :
140+
($node['nodeType'] === 'heading-2' ? 'h2' : 'h3');
141+
142+
$segment = '';
143+
144+
foreach ($node['content'] as $inline) {
145+
$segment .= $this->renderInlineNode($inline, $linkedEntries);
146+
}
147+
148+
$html .= "<$tag>$segment</$tag>";
149+
break;
150+
151+
case 'embedded-entry-block':
152+
$entryId = $node['data']['target']['sys']['id'] ?? '';
153+
if (isset($linkedEntries[$entryId])) {
154+
$block = $linkedEntries[$entryId];
155+
156+
if ($block['__typename'] === 'ImageBlock') {
157+
foreach ($block['imagesCollection']['items'] as $imageItem) {
158+
$image = $imageItem['imageSource'][0] ?? null;
159+
if ($image) {
160+
$html .= $this->renderImageHtml($image);
161+
}
162+
}
163+
} elseif ($block['__typename'] === 'MediaImage') {
164+
$image = $block['imageSource'][0] ?? null;
165+
if ($image) {
166+
$html .= $this->renderImageHtml($image);
167+
}
168+
}
169+
}
170+
break;
171+
}
172+
}
173+
174+
return $html;
175+
}
176+
177+
private function renderInlineNode($inline, $linkedEntries)
178+
{
179+
if ($inline['nodeType'] === 'text') {
180+
$text = htmlspecialchars($inline['value'] ?? '');
181+
foreach ($inline['marks'] ?? [] as $mark) {
182+
if ($mark['type'] === 'bold') {
183+
$text = "<strong>$text</strong>";
184+
} elseif ($mark['type'] === 'italic') {
185+
$text = "<em>$text</em>";
186+
}
187+
}
188+
return $text;
189+
}
190+
191+
if ($inline['nodeType'] === 'hyperlink') {
192+
$url = htmlspecialchars($inline['data']['uri'] ?? '');
193+
$linkText = '';
194+
foreach ($inline['content'] as $linkNode) {
195+
$linkText .= $this->renderInlineNode($linkNode, $linkedEntries);
196+
}
197+
return "<a href=\"$url\">$linkText</a>";
198+
}
199+
200+
if ($inline['nodeType'] === 'entry-hyperlink') {
201+
$entryId = $inline['data']['target']['sys']['id'] ?? '';
202+
$linkedEntry = $linkedEntries[$entryId] ?? null;
203+
$linkText = '';
204+
foreach ($inline['content'] as $linkNode) {
205+
$linkText .= $this->renderInlineNode($linkNode, $linkedEntries);
206+
}
207+
208+
if ($linkedEntry && isset($linkedEntry['slug'])) {
209+
$href = self::URI . 'blogs/' . $linkedEntry['slug'];
210+
return "<a href=\"$href\">$linkText</a>";
211+
}
212+
213+
return $linkText;
214+
}
215+
216+
return '';
217+
}
218+
219+
private function renderImageHtml($image)
220+
{
221+
$url = htmlspecialchars($image['secure_url'] ?? '');
222+
$alt = htmlspecialchars($image['context']['custom']['alt'] ?? '');
223+
$credit = htmlspecialchars($image['context']['custom']['credit'] ?? '');
224+
$caption = htmlspecialchars($image['context']['custom']['caption'] ?? '');
225+
226+
$html = '<div style="margin: 1em 0;">';
227+
$html .= "<img src=\"$url\" alt=\"$alt\" style=\"max-width:100%;\" />";
228+
if ($caption || $credit) {
229+
$html .= '<p style="font-size: small; color: #555;">';
230+
if ($caption) {
231+
$html .= "<em>$caption</em><br>";
232+
}
233+
if ($credit) {
234+
$html .= "Credit: $credit";
235+
}
236+
$html .= '</p>';
237+
}
238+
$html .= '</div>';
239+
240+
return $html;
241+
}
242+
}

0 commit comments

Comments
 (0)