Skip to content

Commit ae72f1b

Browse files
committed
initial test code
1 parent b60fb01 commit ae72f1b

10 files changed

+246
-1
lines changed

.gitignore

100644100755
File mode changed.

composer.json

100644100755
+15-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "artemeon/confluence-api",
2+
"name": "artemeon/confluence",
33
"description": "Connect to the Atlassian Confluence API",
44
"license": "MIT",
55
"type": "library",
@@ -8,5 +8,19 @@
88
"guzzlehttp/guzzle": "~7.4",
99
"ext-json": "*",
1010
"ext-mbstring": "*"
11+
},
12+
"require-dev": {
13+
"phpunit/phpunit": "^8.0",
14+
"vimeo/psalm": "^3.16"
15+
},
16+
"autoload": {
17+
"psr-4": {
18+
"Artemeon\\Confluence\\": "src/"
19+
}
20+
},
21+
"autoload-dev": {
22+
"psr-4": {
23+
"Artemeon\\Confluence\\Tests\\": "tests/"
24+
}
1125
}
1226
}

src/ConfluencePageDownloader.php

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Artemeon\Confluence;
6+
7+
use Artemeon\Confluence\MacroReplacer\MacroReplacerInterface;
8+
use Exception;
9+
use GuzzleHttp\Client;
10+
11+
class ConfluencePageDownloader
12+
{
13+
private string $confluenceUrl;
14+
15+
private string $username;
16+
17+
private string $password;
18+
19+
private string $pageId;
20+
21+
private string $downloadFolder;
22+
23+
private array $macroReplacers;
24+
25+
public function __construct(string $confluenceUrl, string $username, string $password, string $pageId, string $downloadFolder, array $macroReplacers = [])
26+
{
27+
$this->confluenceUrl = $confluenceUrl;
28+
$this->username = $username;
29+
$this->password = $password;
30+
$this->pageId = $pageId;
31+
$this->downloadFolder = $downloadFolder . '/' . $this->pageId;
32+
$this->macroReplacers = $macroReplacers;
33+
}
34+
35+
public function downloadPageWithAttachments(): void
36+
{
37+
$client = new Client();
38+
39+
if (!$this->checkDownloadFolder()) {
40+
echo 'Fehler: Der Download-Ordner existiert nicht oder konnte nicht erstellt werden.';
41+
42+
return;
43+
}
44+
45+
try {
46+
$pageData = $this->downloadPageContent($client);
47+
$htmlContent = $pageData['body']['storage']['value'];
48+
49+
foreach ($this->macroReplacers as $macroReplacer) {
50+
if ($macroReplacer instanceof MacroReplacerInterface) {
51+
$htmlContent = $macroReplacer->replace($htmlContent);
52+
}
53+
}
54+
55+
$this->savePageContent($htmlContent);
56+
57+
// Herunterladen von Attachments über "descendants.attachment"
58+
$attachments = $this->getDescendantAttachments($this->pageId, $client);
59+
foreach ($attachments as $attachment) {
60+
$this->downloadAttachment($client, $attachment);
61+
}
62+
63+
echo 'Der Seiteninhalt und die Attachments wurden erfolgreich heruntergeladen und im Ordner "' . $this->downloadFolder . '" gespeichert.';
64+
} catch (Exception $e) {
65+
echo 'Ein Fehler ist aufgetreten: ' . $e->getMessage();
66+
}
67+
}
68+
69+
private function downloadPageContent(Client $client): array
70+
{
71+
// Verwende die Confluence Content API, um den Seiteninhalt abzurufen
72+
$apiUrl = $this->confluenceUrl . '/wiki/rest/api/content/' . $this->pageId . '?expand=body.storage,version,space,metadata.labels';
73+
$response = $client->request('GET', $apiUrl, ['auth' => [$this->username, $this->password]]);
74+
75+
if ($response->getStatusCode() === 200) {
76+
return json_decode($response->getBody()->getContents(), true);
77+
} else {
78+
throw new Exception('Fehler beim Abrufen des Seiteninhalts. HTTP-Statuscode: ' . $response->getStatusCode());
79+
}
80+
}
81+
82+
private function getDescendantAttachments(string $pageId, Client $client): array
83+
{
84+
// Verwende "descendants.attachment" in der Content API, um Attachments zu erhalten
85+
$apiUrl = $this->confluenceUrl . '/wiki/rest/api/content/' . $pageId . '/child/attachment';
86+
$response = $client->request('GET', $apiUrl, ['auth' => [$this->username, $this->password]]);
87+
88+
if ($response->getStatusCode() === 200) {
89+
$attachmentsData = json_decode($response->getBody()->getContents(), true);
90+
$attachments = $attachmentsData['results'];
91+
92+
return $attachments;
93+
} else {
94+
throw new Exception('Fehler beim Abrufen der Attachments. HTTP-Statuscode: ' . $response->getStatusCode());
95+
}
96+
}
97+
98+
private function checkDownloadFolder(): bool
99+
{
100+
if (!is_dir($this->downloadFolder)) {
101+
return mkdir($this->downloadFolder, 0755, true);
102+
}
103+
104+
return true;
105+
}
106+
107+
private function savePageContent(string $htmlContent): void
108+
{
109+
$htmlFile = $this->downloadFolder . '/page.html';
110+
file_put_contents($htmlFile, $htmlContent);
111+
}
112+
113+
private function downloadAttachment(Client $client, array $attachment): void
114+
{
115+
// Verwende den relativen Pfad aus der API, um das Attachment herunterzuladen
116+
$attachmentPath = $this->confluenceUrl . '/wiki' . $attachment['_links']['download'];
117+
$attachmentContent = $client->request('GET', $attachmentPath, ['auth' => [$this->username, $this->password]])->getBody()->getContents();
118+
119+
$attachmentFolder = $this->downloadFolder;
120+
121+
if (!is_dir($attachmentFolder)) {
122+
mkdir($attachmentFolder, 0755, true);
123+
}
124+
125+
$attachmentFilePath = $attachmentFolder . '/' . $attachment['title'];
126+
file_put_contents($attachmentFilePath, $attachmentContent);
127+
}
128+
}

src/Endpoint/AttachmentDownloader.php

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Artemeon\Confluence\Endpoint;
6+
7+
class AttachmentDownloader
8+
{
9+
10+
}

src/Endpoint/Content.php

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Artemeon\Confluence\Endpoint;
5+
6+
class Content
7+
{
8+
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Artemeon\Confluence\MacroReplacer;
6+
7+
/**
8+
* Ersetze <ac:image> durch HTML <img> oder <video> Tags, abhängig vom Dateityp
9+
*/
10+
class ImageAndVideoMacroReplacer implements MacroReplacerInterface
11+
{
12+
public function replace(string $haystack): string
13+
{
14+
return preg_replace_callback(
15+
'/<ac:image[^>]*>.*?<ri:attachment\s+ri:filename="([^"]+)"[^>]*>.*?<\/ac:image>/is',
16+
function ($match) {
17+
$attachmentFileName = $match[1];
18+
$fileExtension = pathinfo($attachmentFileName, PATHINFO_EXTENSION);
19+
20+
// Unterscheide zwischen Bildern und Videos
21+
if (in_array($fileExtension, ['jpg', 'jpeg', 'png', 'gif'])) {
22+
return '<img src="' . $attachmentFileName . '">';
23+
} elseif (in_array($fileExtension, ['mp4', 'avi', 'mkv', 'mov'])) {
24+
return '<video controls><source src="' . $attachmentFileName . '" type="video/' . $fileExtension . '">Your browser does not support the video tag.</video>';
25+
} else {
26+
// Standardmäßig wird ein Link zum Anhang erstellt
27+
return '<a href="' . $attachmentFileName . '">' . $attachmentFileName . '</a>';
28+
}
29+
},
30+
$haystack
31+
);
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Artemeon\Confluence\MacroReplacer;
6+
7+
interface MacroReplacerInterface
8+
{
9+
public function replace(string $haystack) : string;
10+
}
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Artemeon\Confluence\MacroReplacer;
6+
7+
/**
8+
* Entferne alle übrigen Macros aus dem Input
9+
*/
10+
class OtherMacroRemover implements MacroReplacerInterface
11+
{
12+
13+
public function replace(string $haystack): string
14+
{
15+
return preg_replace('/<ac:[^>]*>.*?<\/ac:[^>]*>/is', '', $haystack);
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Artemeon\Confluence\MacroReplacer;
6+
7+
/**
8+
* Ersetze <ac:structured-macro> durch <div> mit Klasse
9+
*/
10+
class StructuredMacroReplacer implements MacroReplacerInterface
11+
{
12+
public function replace(string $haystack): string
13+
{
14+
return preg_replace_callback(
15+
'/<ac:structured-macro\s+ac:name="([^"]+)"[^>]*>(.*?)<\/ac:structured-macro>/is',
16+
function ($match) {
17+
$macroName = $match[1];
18+
$macroContent = $match[2];
19+
return '<div class="' . $macroName . '">' . $macroContent . '</div>';
20+
},
21+
$haystack
22+
);
23+
}
24+
}

tests/.gitkeep

Whitespace-only changes.

0 commit comments

Comments
 (0)