Skip to content

Commit a4a5ac4

Browse files
committed
Release 2.3.0: Settings page, private repo support, navigation restructure
1 parent b5fa09e commit a4a5ac4

8 files changed

Lines changed: 218 additions & 16 deletions

README.md

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,12 @@ Dieses AddOn ermöglicht die einfache Installation von AddOns oder Plugins durch
2828

2929
## Verwendung
3030

31-
Das AddOn ist im Installer unter "ZIP Upload/GitHub" zu finden und bietet drei Möglichkeiten zur Installation:
31+
Das AddOn ist im Installer unter "ZIP Upload" zu finden. Es gliedert sich in zwei Bereiche:
32+
33+
1. **Upload**: Hier können AddOns via ZIP-Datei oder URL installiert werden. Auch die GitHub-Suche befindet sich hier.
34+
2. **Einstellungen**: Hier können der GitHub-Token und die maximale Upload-Größe konfiguriert werden.
35+
36+
Die Installation bietet drei Möglichkeiten:
3237

3338
1. **ZIP-Upload**: Direkter Upload einer ZIP-Datei eines AddOns/Plugins
3439
2. **URL-Installation**: Eingabe eines Links zu einer ZIP-Datei oder einem GitHub-Repository
@@ -53,17 +58,18 @@ Plugins werden automatisch in das entsprechende Verzeichnis des zugehörigen Add
5358
* Die `update.php` des AddOns wird nicht ausgeführt.
5459
* Der Upload ist auf 50 MB begrenzt.
5560

56-
## GitHub API-Token setzen
61+
## GitHub API-Token & Private Repositories
5762

58-
Das Add-on liefert keinen Token für die GitHub-API mit. Ohne Token sind die API-Abfragen auf 60 pro Stunde begrenzt.
59-
Ein persönlicher Zugriffstoken kann unter GitHub > Settings > Developer settings > Personal access tokens erstellt werden.
60-
Der Token benötigt mindestens 'public_repo' Berechtigung für öffentliche Repositories.
61-
Der Token kann z.B. in der install.php des project-Add-ons oder einem eigenen wie folgt updatesicher gesetzt werden:
63+
Das AddOn liefert keinen Token für die GitHub-API mit. Ohne Token sind die API-Abfragen auf 60 pro Stunde begrenzt.
6264

63-
```
64-
$addon = rex_addon::get('zip_install');
65-
$addon->setConfig('github_token', 'GitHubToken');
66-
```
65+
Ein persönlicher Zugriffstoken kann unter **GitHub > Settings > Developer settings > Personal access tokens** erstellt werden.
66+
Der Token kann bequem über die **Einstellungen-Seite** des AddOns eingetragen werden.
67+
68+
**Vorteile eines Tokens:**
69+
* Erhöhtes Rate-Limit (5000 Anfragen/Stunde statt 60).
70+
* **Zugriff auf private Repositories**: Mit einem Token, der über den Scope `repo` verfügt, können auch private Repositories installiert werden.
71+
72+
Für öffentliche Repositories reicht in der Regel ein Token ohne spezielle Scopes (oder der Scope `public_repo`).
6773

6874
## Voraussetzungen
6975

lang/de_de.lang

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,11 @@ zip_install_security_warning = Bitte beachten: Installieren Sie nur AddOns aus v
4141

4242
zip_install_settings_github_token = GitHub Token
4343
zip_install_settings_github_token_info = Optional. Ein GitHub Token erhöht das stündliche API-Limit von 60 auf 5000 Anfragen.
44+
zip_install_settings_github_token_create = GitHub Token erstellen
4445
zip_install_settings_upload_max_size = Maximale Upload-Größe (MB)
4546
zip_install_settings_upload_max_size_info = Maximale Größe von ZIP-Dateien, die hochgeladen werden können. Standard: 50 MB.
47+
zip_install_settings_upload_max_size_invalid = Die Upload-Größe muss zwischen 1 und 500 MB liegen.
48+
zip_install_settings_saved = Einstellungen wurden erfolgreich gespeichert.
4649

4750
#permissions
4851
perm_general_zip_install[] = Berechtigungen für ZIP Upload/GitHub Installation

lang/en_gb.lang

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,11 @@ zip_install_security_warning = Please note: Only install AddOns from trusted sou
4141

4242
zip_install_settings_github_token = GitHub Token
4343
zip_install_settings_github_token_info = Optional. A GitHub token increases the hourly API limit from 60 to 5000 requests.
44+
zip_install_settings_github_token_create = Create GitHub Token
4445
zip_install_settings_upload_max_size = Maximum Upload Size (MB)
4546
zip_install_settings_upload_max_size_info = Maximum size of ZIP files that can be uploaded. Default: 50 MB.
47+
zip_install_settings_upload_max_size_invalid = The upload size must be between 1 and 500 MB.
48+
zip_install_settings_saved = Settings have been saved successfully.
4649

4750
#permissions
4851
perm_general_zip_install[] = Permissions for ZIP Upload/GitHub Installation

lib/zip_install.php

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,8 @@ public function getGitHubRepos(string $username): array
265265
if (isset($http_response_header)) {
266266
foreach ($http_response_header as $header) {
267267
if (stripos($header, 'X-RateLimit-Remaining:') === 0) {
268-
$remaining = intval(trim(substr($header, 21)));
268+
// Extract value after "X-RateLimit-Remaining: " (22 chars + space = 23)
269+
$remaining = intval(trim(substr($header, 23)));
269270
if ($remaining <= 5) {
270271
// Using warning level for rate limit notifications
271272
\rex_logger::factory()->log(\Psr\Log\LogLevel::WARNING,
@@ -351,6 +352,15 @@ protected function isValidUrl(string $url): bool
351352
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
352353
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
353354

355+
// Add Authorization header if token is available
356+
$token = $this->addon->getConfig('github_token');
357+
$host = parse_url($url, PHP_URL_HOST);
358+
if ($token && $host && ($host === 'github.com' || str_ends_with($host, '.github.qkg1.top'))) {
359+
curl_setopt($ch, CURLOPT_HTTPHEADER, [
360+
'Authorization: Bearer ' . $token
361+
]);
362+
}
363+
354364
curl_exec($ch);
355365
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
356366
$error = curl_error($ch);
@@ -389,10 +399,20 @@ protected function downloadFile(string $url, string $destination): bool
389399
curl_setopt($ch, CURLOPT_USERAGENT, 'REDAXOZipInstall/2.2.1');
390400
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
391401
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
392-
curl_setopt($ch, CURLOPT_HTTPHEADER, [
402+
403+
$headers = [
393404
'Accept: application/zip, application/octet-stream, */*',
394405
'Cache-Control: no-cache'
395-
]);
406+
];
407+
408+
// Add Authorization header if token is available
409+
$token = $this->addon->getConfig('github_token');
410+
$host = parse_url($url, PHP_URL_HOST);
411+
if ($token && $host && ($host === 'github.com' || str_ends_with($host, '.github.qkg1.top'))) {
412+
$headers[] = 'Authorization: Bearer ' . $token;
413+
}
414+
415+
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
396416

397417
$content = curl_exec($ch);
398418
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
@@ -615,4 +635,52 @@ public function getAddonKeyFromUrl(string $url): ?string
615635
}
616636
return null;
617637
}
638+
639+
/**
640+
* Get GitHub API Rate Limit
641+
*
642+
* @return array|null Returns array with limit, remaining, reset or null on error
643+
*/
644+
public function getRateLimit(): ?array
645+
{
646+
$token = $this->addon->getConfig('github_token');
647+
648+
$headers = [
649+
'User-Agent: REDAXOZipInstall/2.0',
650+
'Accept: application/vnd.github.v3+json'
651+
];
652+
653+
if ($token) {
654+
$headers[] = 'Authorization: Bearer ' . $token;
655+
}
656+
657+
$options = [
658+
'http' => [
659+
'method' => 'GET',
660+
'header' => implode("\r\n", $headers),
661+
'timeout' => 5
662+
]
663+
];
664+
665+
$context = stream_context_create($options);
666+
667+
try {
668+
$response = @file_get_contents('https://api.github.com/rate_limit', false, $context);
669+
670+
if ($response !== false) {
671+
$data = json_decode($response, true);
672+
if (isset($data['rate'])) {
673+
return [
674+
'limit' => $data['rate']['limit'],
675+
'remaining' => $data['rate']['remaining'],
676+
'reset' => $data['rate']['reset']
677+
];
678+
}
679+
}
680+
} catch (Exception $e) {
681+
// Ignore errors
682+
}
683+
684+
return null;
685+
}
618686
}

package.yml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package: zip_install
2-
version: '2.2.4'
2+
version: '2.3.0'
33
author: Friends Of REDAXO
44
supportpage: github.com/FriendsOfREDAXO/zip_install
55
live_mode: false
@@ -11,11 +11,20 @@ requires:
1111
extensions: [fileinfo, zip]
1212

1313
pages:
14-
install/packages/zip_install:
14+
install/zip_install:
1515
title: translate:title
16-
icon: rex-icon fa-upload
16+
icon: rex-icon fa-solid fa-file-arrow-up
1717
perm: admin[]
1818
pjax: true
19+
subpages:
20+
upload:
21+
title: translate:zip_install_file_upload
22+
icon: rex-icon fa-solid fa-file-arrow-up
23+
perm: admin[]
24+
settings:
25+
title: translate:settings
26+
icon: rex-icon fa-cog
27+
perm: admin[]
1928

2029
conflicts:
2130
packages:

pages/install.zip_install.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
$package = rex_addon::get('zip_install');
4+
5+
echo rex_view::title($package->i18n('title'));
6+
7+
rex_be_controller::includeCurrentPageSubPath();
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
use FriendsOfRedaxo\ZipInstall\ZipInstall;
4+
5+
$addon = rex_addon::get('zip_install');
6+
$csrf = rex_csrf_token::factory('zip_install_settings');
7+
8+
// Handle form submission
9+
if (rex_post('save', 'boolean')) {
10+
if ($csrf->isValid()) {
11+
$githubToken = rex_post('github_token', 'string', '');
12+
$uploadMaxSize = rex_post('upload_max_size', 'int', 50);
13+
14+
// Validate upload size
15+
if ($uploadMaxSize < 1 || $uploadMaxSize > 500) {
16+
echo rex_view::error(rex_i18n::msg('zip_install_settings_upload_max_size_invalid'));
17+
} else {
18+
// Save settings
19+
rex_config::set('zip_install', 'github_token', $githubToken);
20+
rex_config::set('zip_install', 'upload_max_size', $uploadMaxSize);
21+
22+
echo rex_view::success(rex_i18n::msg('zip_install_settings_saved'));
23+
}
24+
} else {
25+
echo rex_view::error(rex_i18n::msg('csrf_token_invalid'));
26+
}
27+
}
28+
29+
// Get current settings
30+
$githubToken = rex_config::get('zip_install', 'github_token', '');
31+
$uploadMaxSize = rex_config::get('zip_install', 'upload_max_size', 50);
32+
33+
// Get Rate Limit Info
34+
$rateLimitInfo = '';
35+
try {
36+
$installer = new ZipInstall();
37+
$rateLimit = $installer->getRateLimit();
38+
39+
if ($rateLimit) {
40+
$resetTime = date('H:i:s', $rateLimit['reset']);
41+
42+
$rateLimitInfo = '<div class="alert alert-info" style="margin-top: 10px;">';
43+
$rateLimitInfo .= '<strong>GitHub API Status:</strong><br>';
44+
$rateLimitInfo .= 'Limit: ' . $rateLimit['limit'] . ' Requests/h<br>';
45+
$rateLimitInfo .= 'Remaining: <strong>' . $rateLimit['remaining'] . '</strong><br>';
46+
$rateLimitInfo .= 'Reset: ' . $resetTime;
47+
$rateLimitInfo .= '</div>';
48+
}
49+
} catch (Exception $e) {
50+
// Ignore
51+
}
52+
53+
// Build form
54+
$content = '';
55+
$content .= '<fieldset>';
56+
57+
// GitHub Token field
58+
$formElements = [];
59+
60+
$n = [];
61+
$n['label'] = '<label for="github_token">' . rex_i18n::msg('zip_install_settings_github_token') . '</label>';
62+
$n['field'] = '<input class="form-control" type="text" id="github_token" name="github_token" value="' . rex_escape($githubToken) . '" placeholder="ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" />';
63+
$n['note'] = rex_i18n::msg('zip_install_settings_github_token_info') . '<br><a href="https://github.com/settings/tokens/new" target="_blank" rel="noopener noreferrer"><i class="rex-icon fa-external-link"></i> ' . rex_i18n::msg('zip_install_settings_github_token_create') . '</a>';
64+
if ($rateLimitInfo) {
65+
$n['note'] .= $rateLimitInfo;
66+
}
67+
$formElements[] = $n;
68+
69+
// Upload Max Size field
70+
$n = [];
71+
$n['label'] = '<label for="upload_max_size">' . rex_i18n::msg('zip_install_settings_upload_max_size') . '</label>';
72+
$n['field'] = '<input class="form-control" type="number" id="upload_max_size" name="upload_max_size" value="' . $uploadMaxSize . '" min="1" max="500" />';
73+
$n['note'] = rex_i18n::msg('zip_install_settings_upload_max_size_info');
74+
$formElements[] = $n;
75+
76+
$fragment = new rex_fragment();
77+
$fragment->setVar('elements', $formElements, false);
78+
$content .= $fragment->parse('core/form/form.php');
79+
80+
$content .= '</fieldset>';
81+
82+
// Form buttons
83+
$formElements = [];
84+
$n = [];
85+
$n['field'] = '<button class="btn btn-save rex-form-aligned" type="submit" name="save" value="1">' . rex_i18n::msg('zip_install_settings_save') . '</button>';
86+
$formElements[] = $n;
87+
88+
$fragment = new rex_fragment();
89+
$fragment->setVar('elements', $formElements, false);
90+
$buttons = $fragment->parse('core/form/submit.php');
91+
$content .= $buttons;
92+
93+
// Wrap in form
94+
$fragment = new rex_fragment();
95+
$fragment->setVar('class', 'edit', false);
96+
$fragment->setVar('title', rex_i18n::msg('zip_install_settings'), false);
97+
$fragment->setVar('body', $content, false);
98+
$content = $fragment->parse('core/page/section.php');
99+
100+
$content = '
101+
<form action="' . rex_url::currentBackendPage() . '" method="post">
102+
' . $csrf->getHiddenField() . '
103+
' . $content . '
104+
</form>';
105+
106+
echo $content;

0 commit comments

Comments
 (0)