forked from friendica/friendica-addons
Compare commits
74 Commits
410613d7a0
...
b87588e371
Author | SHA1 | Date |
---|---|---|
Hypolite Petovan | b87588e371 | |
Michael | 96c70489f5 | |
Hypolite Petovan | 77ad52d1f4 | |
Michael | 96a354bc65 | |
heluecht | f32c90dc9f | |
Hypolite Petovan | 3e74af9775 | |
Hypolite Petovan | 9daa11eb10 | |
Hypolite Petovan | 011edb711c | |
Michael | cabdd924d0 | |
Michael | 8ba44cf5c6 | |
heluecht | dad3d477d3 | |
Tobias Diekershoff | 3a063f999d | |
Hypolite Petovan | fa01357445 | |
Michael | a41a676bfb | |
Hypolite Petovan | 3b518462ab | |
loma-one | d53ad98af2 | |
loma-one | 372e75a91c | |
loma-one | f45f6ba992 | |
loma-one | 26983977c4 | |
loma-one | 90d897f4fa | |
loma-one | 668ea972cc | |
loma-one | dc2d00b6c6 | |
loma-one | 0f65c23490 | |
loma-one | c98caaf417 | |
loma-one | f46980c736 | |
loma-one | ebf5ff1276 | |
loma-one | 9d932e6fa0 | |
loma-one | 46fdcc1c0e | |
loma-one | eadbcc069f | |
loma-one | 2c2a813324 | |
loma-one | 9315b185e8 | |
loma-one | d685663ac0 | |
loma-one | 727eca1ce7 | |
Hypolite Petovan | 3b5e8901dc | |
Michael | 50d8d44489 | |
Hypolite Petovan | b6d575c37f | |
Michael | 397282cbb3 | |
Hypolite Petovan | 2c6add7aa1 | |
Michael | 22bf23b833 | |
Hypolite Petovan | ed8c5945da | |
Michael | 14fd900628 | |
Michael | 48cde643f6 | |
Michael | e62f6a9586 | |
Hypolite Petovan | c2dfda5d72 | |
Michael | 9595760800 | |
Hypolite Petovan | 1c91ee200e | |
Michael | 00e30b5c2b | |
heluecht | 5f5c53ab49 | |
Benjamin Lorteau | 6a46d05bca | |
Hypolite Petovan | 8d3d0f267b | |
Michael | 66fdd31915 | |
Hypolite Petovan | 607cc9238c | |
Michael | 2a782b512e | |
Hypolite Petovan | a75c9ba373 | |
Michael | 77765ff6ed | |
heluecht | 9c53c0c8d1 | |
Hypolite Petovan | 43c46ae6d9 | |
Hypolite Petovan | c7e06bfa53 | |
Michael | 6948a15f1c | |
Hypolite Petovan | 74c56c32b0 | |
Philipp Holzer | 9bdaa8092e | |
Hypolite Petovan | b11538d195 | |
Michael | 73c6a0ff0c | |
Hypolite Petovan | fbafa80815 | |
Michael | 92251f4a6c | |
Michael | 18266ea6ef | |
Michael | 80ce855189 | |
Michael | 0eda161e04 | |
Michael | 981e6821d0 | |
Philipp Holzer | a5ed02ed23 | |
Philipp Holzer | 7a8f8fcbd2 | |
Philipp Holzer | 30b9f73f5e | |
Hypolite Petovan | be8d8b9c10 | |
Michael | 16d99dbdfc |
|
@ -1,15 +1,13 @@
|
|||
matrix:
|
||||
include:
|
||||
- PHP_MAJOR_VERSION: 7.3
|
||||
PHP_VERSION: 7.3.33
|
||||
- PHP_MAJOR_VERSION: 7.4
|
||||
PHP_VERSION: 7.4.33
|
||||
- PHP_MAJOR_VERSION: 8.0
|
||||
PHP_VERSION: 8.0.29
|
||||
PHP_VERSION: 8.0.30
|
||||
- PHP_MAJOR_VERSION: 8.1
|
||||
PHP_VERSION: 8.1.21
|
||||
PHP_VERSION: 8.1.23
|
||||
- PHP_MAJOR_VERSION: 8.2
|
||||
PHP_VERSION: 8.2.8
|
||||
PHP_VERSION: 8.2.11
|
||||
|
||||
# This forces PHP Unit executions at the "opensocial" labeled location (because of much more power...)
|
||||
labels:
|
||||
|
|
|
@ -455,7 +455,7 @@ function advancedcontentfilter_prepare_item_row(array $item_row): array
|
|||
$item_row['tags'] = $tags['tags'];
|
||||
$item_row['hashtags'] = $tags['hashtags'];
|
||||
$item_row['mentions'] = $tags['mentions'];
|
||||
$item_row['attachments'] = Post\Media::splitAttachments($item_row['uri-id']);
|
||||
$item_row['attachments'] = DI::postMediaRepository()->splitAttachments($item_row['uri-id']);
|
||||
|
||||
return $item_row;
|
||||
}
|
||||
|
|
|
@ -32,15 +32,16 @@ use Friendica\Core\Hook;
|
|||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Core\Worker;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\GServer;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\ItemURI;
|
||||
use Friendica\Model\Photo;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
|
||||
use Friendica\Object\Image;
|
||||
|
@ -50,9 +51,23 @@ use Friendica\Util\DateTimeFormat;
|
|||
use Friendica\Util\Strings;
|
||||
|
||||
const BLUESKY_DEFAULT_POLL_INTERVAL = 10; // given in minutes
|
||||
const BLUESKY_HOST = 'https://bsky.app'; // Hard wired until Bluesky will run on multiple systems
|
||||
const BLUESKY_IMAGE_SIZE = [1000000, 500000, 100000, 50000];
|
||||
|
||||
const BLUEKSY_STATUS_UNKNOWN = 0;
|
||||
const BLUEKSY_STATUS_TOKEN_OK = 1;
|
||||
const BLUEKSY_STATUS_SUCCESS = 2;
|
||||
const BLUEKSY_STATUS_API_FAIL = 10;
|
||||
const BLUEKSY_STATUS_DID_FAIL = 11;
|
||||
const BLUEKSY_STATUS_PDS_FAIL = 12;
|
||||
const BLUEKSY_STATUS_TOKEN_FAIL = 13;
|
||||
|
||||
/*
|
||||
* (Currently) hard wired paths for Bluesky services
|
||||
*/
|
||||
const BLUESKY_DIRECTORY = 'https://plc.directory'; // Path to the directory server service to fetch the PDS of a given DID
|
||||
const BLUESKY_PDS = 'https://bsky.social'; // Path to the personal data server service (PDS) to fetch the DID for a given handle
|
||||
const BLUESKY_WEB = 'https://bsky.app'; // Path to the web interface with the user profile and posts
|
||||
|
||||
function bluesky_install()
|
||||
{
|
||||
Hook::register('load_config', __FILE__, 'bluesky_load_config');
|
||||
|
@ -107,8 +122,8 @@ function bluesky_probe_detect(array &$hookData)
|
|||
|
||||
if (parse_url($hookData['uri'], PHP_URL_SCHEME) == 'did') {
|
||||
$did = $hookData['uri'];
|
||||
} elseif (preg_match('#^' . BLUESKY_HOST . '/profile/(.+)#', $hookData['uri'], $matches)) {
|
||||
$did = bluesky_get_did($pconfig['uid'], $matches[1]);
|
||||
} elseif (preg_match('#^' . BLUESKY_WEB . '/profile/(.+)#', $hookData['uri'], $matches)) {
|
||||
$did = bluesky_get_did($matches[1]);
|
||||
if (empty($did)) {
|
||||
return;
|
||||
}
|
||||
|
@ -128,6 +143,8 @@ function bluesky_probe_detect(array &$hookData)
|
|||
|
||||
$hookData['result'] = bluesky_get_contact_fields($data, 0, false);
|
||||
|
||||
$hookData['result']['baseurl'] = bluesky_get_pds($did);
|
||||
|
||||
// Preparing probe data. This differs slightly from the contact array
|
||||
$hookData['result']['about'] = HTML::toBBCode($data->description ?? '');
|
||||
$hookData['result']['photo'] = $data->avatar ?? '';
|
||||
|
@ -153,11 +170,11 @@ function bluesky_item_by_link(array &$hookData)
|
|||
return;
|
||||
}
|
||||
|
||||
if (!preg_match('#^' . BLUESKY_HOST . '/profile/(.+)/post/(.+)#', $hookData['uri'], $matches)) {
|
||||
if (!preg_match('#^' . BLUESKY_WEB . '/profile/(.+)/post/(.+)#', $hookData['uri'], $matches)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$did = bluesky_get_did($hookData['uid'], $matches[1]);
|
||||
$did = bluesky_get_did($matches[1]);
|
||||
if (empty($did)) {
|
||||
return;
|
||||
}
|
||||
|
@ -166,7 +183,7 @@ function bluesky_item_by_link(array &$hookData)
|
|||
|
||||
$uri = 'at://' . $did . '/app.bsky.feed.post/' . $matches[2];
|
||||
|
||||
$uri = bluesky_fetch_missing_post($uri, $hookData['uid'], 0, 0);
|
||||
$uri = bluesky_fetch_missing_post($uri, $hookData['uid'], $hookData['uid'], 0, 0);
|
||||
Logger::debug('Got post', ['profile' => $matches[1], 'cid' => $matches[2], 'result' => $uri]);
|
||||
if (!empty($uri)) {
|
||||
$item = Post::selectFirst(['id'], ['uri' => $uri, 'uid' => $hookData['uid']]);
|
||||
|
@ -307,26 +324,24 @@ function bluesky_settings(array &$data)
|
|||
|
||||
$enabled = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'post') ?? false;
|
||||
$def_enabled = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'post_by_default') ?? false;
|
||||
$host = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'host') ?: 'https://bsky.social';
|
||||
$pds = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'pds');
|
||||
$handle = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'handle');
|
||||
$did = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'did');
|
||||
$token = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'access_token');
|
||||
$import = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'import') ?? false;
|
||||
$import_feeds = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'import_feeds') ?? false;
|
||||
|
||||
$status = $token ? DI::l10n()->t("You are authenticated to Bluesky. For security reasons the password isn't stored.") : DI::l10n()->t('You are not authenticated. Please enter the app password.');
|
||||
|
||||
$t = Renderer::getMarkupTemplate('connector_settings.tpl', 'addon/bluesky/');
|
||||
$html = Renderer::replaceMacros($t, [
|
||||
'$enable' => ['bluesky', DI::l10n()->t('Enable Bluesky Post Addon'), $enabled],
|
||||
'$bydefault' => ['bluesky_bydefault', DI::l10n()->t('Post to Bluesky by default'), $def_enabled],
|
||||
'$import' => ['bluesky_import', DI::l10n()->t('Import the remote timeline'), $import],
|
||||
'$import_feeds' => ['bluesky_import_feeds', DI::l10n()->t('Import the pinned feeds'), $import_feeds, DI::l10n()->t('When activated, Posts will be imported from all the feeds that you pinned in Bluesky.')],
|
||||
'$host' => ['bluesky_host', DI::l10n()->t('Bluesky host'), $host, '', '', 'readonly'],
|
||||
'$pds' => ['bluesky_pds', DI::l10n()->t('Personal Data Server'), $pds, DI::l10n()->t('The personal data server (PDS) is the system that hosts your profile.'), '', 'readonly'],
|
||||
'$handle' => ['bluesky_handle', DI::l10n()->t('Bluesky handle'), $handle],
|
||||
'$did' => ['bluesky_did', DI::l10n()->t('Bluesky DID'), $did, DI::l10n()->t('This is the unique identifier. It will be fetched automatically, when the handle is entered.'), '', 'readonly'],
|
||||
'$password' => ['bluesky_password', DI::l10n()->t('Bluesky app password'), '', DI::l10n()->t("Please don't add your real password here, but instead create a specific app password in the Bluesky settings.")],
|
||||
'$status' => $status
|
||||
'$status' => bluesky_get_status($handle, $did, $pds, $token),
|
||||
]);
|
||||
|
||||
$data = [
|
||||
|
@ -338,35 +353,92 @@ function bluesky_settings(array &$data)
|
|||
];
|
||||
}
|
||||
|
||||
function bluesky_get_status(string $handle = null, string $did = null, string $pds = null, string $token = null): string
|
||||
{
|
||||
if (empty($handle)) {
|
||||
return DI::l10n()->t('You are not authenticated. Please enter your handle and the app password.');
|
||||
}
|
||||
|
||||
$status = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'status') ?? BLUEKSY_STATUS_UNKNOWN;
|
||||
|
||||
// Fallback mechanism for connection that had been established before the introduction of the status
|
||||
if ($status == BLUEKSY_STATUS_UNKNOWN) {
|
||||
if (empty($did)) {
|
||||
$status = BLUEKSY_STATUS_DID_FAIL;
|
||||
} elseif (empty($pds)) {
|
||||
$status = BLUEKSY_STATUS_PDS_FAIL;
|
||||
} elseif (!empty($token)) {
|
||||
$status = BLUEKSY_STATUS_TOKEN_OK;
|
||||
} else {
|
||||
$status = BLUEKSY_STATUS_TOKEN_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($status) {
|
||||
case BLUEKSY_STATUS_TOKEN_OK:
|
||||
return DI::l10n()->t("You are authenticated to Bluesky. For security reasons the password isn't stored.");
|
||||
case BLUEKSY_STATUS_SUCCESS:
|
||||
return DI::l10n()->t('The communication with the personal data server service (PDS) is established.');
|
||||
case BLUEKSY_STATUS_API_FAIL;
|
||||
return DI::l10n()->t('Communication issues with the personal data server service (PDS).');
|
||||
case BLUEKSY_STATUS_DID_FAIL:
|
||||
return DI::l10n()->t('The DID for the provided handle could not be detected. Please check if you entered the correct handle.');
|
||||
case BLUEKSY_STATUS_PDS_FAIL:
|
||||
return DI::l10n()->t('The personal data server service (PDS) could not be detected.');
|
||||
case BLUEKSY_STATUS_TOKEN_FAIL:
|
||||
return DI::l10n()->t('The authentication with the provided handle and password failed. Please check if you entered the correct password.');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function bluesky_settings_post(array &$b)
|
||||
{
|
||||
if (empty($_POST['bluesky-submit'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$old_host = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'host');
|
||||
$old_pds = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'pds');
|
||||
$old_handle = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'handle');
|
||||
$old_did = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'did');
|
||||
|
||||
$host = $_POST['bluesky_host'];
|
||||
$handle = $_POST['bluesky_handle'];
|
||||
$handle = trim($_POST['bluesky_handle'], ' @');
|
||||
|
||||
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'post', intval($_POST['bluesky']));
|
||||
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'post_by_default', intval($_POST['bluesky_bydefault']));
|
||||
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'host', $host);
|
||||
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'handle', $handle);
|
||||
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'import', intval($_POST['bluesky_import']));
|
||||
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'import_feeds', intval($_POST['bluesky_import_feeds']));
|
||||
|
||||
if (!empty($host) && !empty($handle)) {
|
||||
if (empty($old_did) || $old_host != $host || $old_handle != $handle) {
|
||||
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'did', bluesky_get_did(DI::userSession()->getLocalUserId(), DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'handle')));
|
||||
if (!empty($handle)) {
|
||||
if (empty($old_did) || $old_handle != $handle) {
|
||||
$did = bluesky_get_did(DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'handle'));
|
||||
if (empty($did)) {
|
||||
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'status', BLUEKSY_STATUS_DID_FAIL);
|
||||
}
|
||||
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'did', $did);
|
||||
} else {
|
||||
$did = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'did');
|
||||
}
|
||||
if (!empty($did) && (empty($old_pds) || $old_handle != $handle)) {
|
||||
$pds = bluesky_get_pds($did);
|
||||
if (empty($pds)) {
|
||||
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'status', BLUEKSY_STATUS_PDS_FAIL);
|
||||
}
|
||||
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'pds', $pds);
|
||||
} else {
|
||||
$pds = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'pds');
|
||||
}
|
||||
} else {
|
||||
DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'bluesky', 'did');
|
||||
DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'bluesky', 'pds');
|
||||
DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'bluesky', 'access_token');
|
||||
DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'bluesky', 'refresh_token');
|
||||
DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'bluesky', 'token_created');
|
||||
DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'bluesky', 'status');
|
||||
}
|
||||
|
||||
if (!empty($_POST['bluesky_password'])) {
|
||||
if (!empty($did) && !empty($pds) && !empty($_POST['bluesky_password'])) {
|
||||
bluesky_create_token(DI::userSession()->getLocalUserId(), $_POST['bluesky_password']);
|
||||
}
|
||||
}
|
||||
|
@ -391,7 +463,7 @@ function bluesky_jot_nets(array &$jotnets_fields)
|
|||
|
||||
function bluesky_cron()
|
||||
{
|
||||
$last = DI::keyValue()->get('bluesky_last_poll');
|
||||
$last = (int)DI::keyValue()->get('bluesky_last_poll');
|
||||
|
||||
$poll_interval = intval(DI::config()->get('bluesky', 'poll_interval'));
|
||||
if (!$poll_interval) {
|
||||
|
@ -426,13 +498,13 @@ function bluesky_cron()
|
|||
// Refresh the token now, so that it doesn't need to be refreshed in parallel by the following workers
|
||||
bluesky_get_token($pconfig['uid']);
|
||||
|
||||
Worker::add(['priority' => Worker::PRIORITY_MEDIUM, 'force_priority' => true], 'addon/bluesky/bluesky_timeline.php', $pconfig['uid']);
|
||||
Worker::add(['priority' => Worker::PRIORITY_MEDIUM, 'force_priority' => true], 'addon/bluesky/bluesky_notifications.php', $pconfig['uid']);
|
||||
Worker::add(['priority' => Worker::PRIORITY_MEDIUM, 'force_priority' => true], 'addon/bluesky/bluesky_timeline.php', $pconfig['uid'], $last);
|
||||
Worker::add(['priority' => Worker::PRIORITY_MEDIUM, 'force_priority' => true], 'addon/bluesky/bluesky_notifications.php', $pconfig['uid'], $last);
|
||||
|
||||
if (DI::pConfig()->get($pconfig['uid'], 'bluesky', 'import_feeds')) {
|
||||
$feeds = bluesky_get_feeds($pconfig['uid']);
|
||||
foreach ($feeds as $feed) {
|
||||
Worker::add(['priority' => Worker::PRIORITY_MEDIUM, 'force_priority' => true], 'addon/bluesky/bluesky_feed.php', $pconfig['uid'], $feed);
|
||||
Worker::add(['priority' => Worker::PRIORITY_MEDIUM, 'force_priority' => true], 'addon/bluesky/bluesky_feed.php', $pconfig['uid'], $feed, $last);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -611,6 +683,13 @@ function bluesky_create_post(array $item, stdClass $root = null, stdClass $paren
|
|||
return;
|
||||
}
|
||||
|
||||
// Try to fetch the language from the post itself
|
||||
if (!empty($item['language'])) {
|
||||
$language = array_key_first(json_decode($item['language'], true));
|
||||
} else {
|
||||
$language = '';
|
||||
}
|
||||
|
||||
$did = DI::pConfig()->get($uid, 'bluesky', 'did');
|
||||
$urls = bluesky_get_urls(Post\Media::removeFromBody($item['body']));
|
||||
$item['body'] = $urls['body'];
|
||||
|
@ -622,10 +701,14 @@ function bluesky_create_post(array $item, stdClass $root = null, stdClass $paren
|
|||
|
||||
$record = [
|
||||
'text' => $facets['body'],
|
||||
'$type' => 'app.bsky.feed.post',
|
||||
'createdAt' => DateTimeFormat::utcNow(DateTimeFormat::ATOM),
|
||||
'$type' => 'app.bsky.feed.post'
|
||||
];
|
||||
|
||||
if (!empty($language)) {
|
||||
$record['langs'] = [$language];
|
||||
}
|
||||
|
||||
if (!empty($facets['facets'])) {
|
||||
$record['facets'] = $facets['facets'];
|
||||
}
|
||||
|
@ -672,11 +755,20 @@ function bluesky_create_post(array $item, stdClass $root = null, stdClass $paren
|
|||
function bluesky_get_urls(string $body): array
|
||||
{
|
||||
// Remove all hashtag and mention links
|
||||
$body = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', $body);
|
||||
$body = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', $body);
|
||||
|
||||
$body = BBCode::expandVideoLinks($body);
|
||||
$urls = [];
|
||||
|
||||
// Search for hash tags
|
||||
if (preg_match_all("/#\[url\=(https?:.*?)\](.*?)\[\/url\]/ism", $body, $matches, PREG_SET_ORDER)) {
|
||||
foreach ($matches as $match) {
|
||||
$text = '#' . $match[2];
|
||||
$urls[] = ['tag' => $match[2], 'text' => $text, 'hash' => $text];
|
||||
$body = str_replace($match[0], $text, $body);
|
||||
}
|
||||
}
|
||||
|
||||
// Search for pure links
|
||||
if (preg_match_all("/\[url\](https?:.*?)\[\/url\]/ism", $body, $matches, PREG_SET_ORDER)) {
|
||||
foreach ($matches as $match) {
|
||||
|
@ -742,9 +834,17 @@ function bluesky_get_facets(string $body, array $urls): array
|
|||
$facet->index->byteStart = $pos;
|
||||
|
||||
$feature = new stdClass;
|
||||
$feature->uri = $url['url'];
|
||||
|
||||
$type = '$type';
|
||||
$feature->$type = 'app.bsky.richtext.facet#link';
|
||||
if (!empty($url['tag'])) {
|
||||
$feature->tag = $url['tag'];
|
||||
$feature->$type = 'app.bsky.richtext.facet#tag';
|
||||
} elseif (!empty($url['url'])) {
|
||||
$feature->uri = $url['url'];
|
||||
$feature->$type = 'app.bsky.richtext.facet#link';
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
$facet->features = [$feature];
|
||||
$facets[] = $facet;
|
||||
|
@ -830,7 +930,7 @@ function bluesky_delete_post(string $uri, int $uid)
|
|||
Logger::debug('Deleted', ['parts' => $parts]);
|
||||
}
|
||||
|
||||
function bluesky_fetch_timeline(int $uid)
|
||||
function bluesky_fetch_timeline(int $uid, int $last_poll)
|
||||
{
|
||||
$data = bluesky_xrpc_get($uid, 'app.bsky.feed.getTimeline');
|
||||
if (empty($data)) {
|
||||
|
@ -842,7 +942,7 @@ function bluesky_fetch_timeline(int $uid)
|
|||
}
|
||||
|
||||
foreach (array_reverse($data->feed) as $entry) {
|
||||
bluesky_process_post($entry->post, $uid, Item::PR_NONE, 0);
|
||||
bluesky_process_post($entry->post, $uid, Item::PR_NONE, 0, $last_poll);
|
||||
if (!empty($entry->reason)) {
|
||||
bluesky_process_reason($entry->reason, bluesky_get_uri($entry->post), $uid);
|
||||
}
|
||||
|
@ -893,7 +993,7 @@ function bluesky_process_reason(stdClass $reason, string $uri, int $uid)
|
|||
}
|
||||
}
|
||||
|
||||
function bluesky_fetch_notifications(int $uid)
|
||||
function bluesky_fetch_notifications(int $uid, int $last_poll)
|
||||
{
|
||||
$data = bluesky_xrpc_get($uid, 'app.bsky.notification.listNotifications');
|
||||
if (empty($data->notifications)) {
|
||||
|
@ -912,7 +1012,7 @@ function bluesky_fetch_notifications(int $uid)
|
|||
$item['gravity'] = Item::GRAVITY_ACTIVITY;
|
||||
$item['body'] = $item['verb'] = Activity::LIKE;
|
||||
$item['thr-parent'] = bluesky_get_uri($notification->record->subject);
|
||||
$item['thr-parent'] = bluesky_fetch_missing_post($item['thr-parent'], $uid, $item['contact-id'], 0);
|
||||
$item['thr-parent'] = bluesky_fetch_missing_post($item['thr-parent'], $uid, $uid, $item['contact-id'], 0, $last_poll);
|
||||
if (!empty($item['thr-parent'])) {
|
||||
$data = Item::insert($item);
|
||||
Logger::debug('Got like', ['uid' => $uid, 'result' => $data, 'uri' => $uri]);
|
||||
|
@ -926,7 +1026,7 @@ function bluesky_fetch_notifications(int $uid)
|
|||
$item['gravity'] = Item::GRAVITY_ACTIVITY;
|
||||
$item['body'] = $item['verb'] = Activity::ANNOUNCE;
|
||||
$item['thr-parent'] = bluesky_get_uri($notification->record->subject);
|
||||
$item['thr-parent'] = bluesky_fetch_missing_post($item['thr-parent'], $uid, $item['contact-id'], 0);
|
||||
$item['thr-parent'] = bluesky_fetch_missing_post($item['thr-parent'], $uid, $uid, $item['contact-id'], 0, $last_poll);
|
||||
if (!empty($item['thr-parent'])) {
|
||||
$data = Item::insert($item);
|
||||
Logger::debug('Got repost', ['uid' => $uid, 'result' => $data, 'uri' => $uri]);
|
||||
|
@ -941,17 +1041,17 @@ function bluesky_fetch_notifications(int $uid)
|
|||
break;
|
||||
|
||||
case 'mention':
|
||||
$data = bluesky_process_post($notification, $uid, Item::PR_PUSHED, 0);
|
||||
$data = bluesky_process_post($notification, $uid, Item::PR_PUSHED, 0, $last_poll);
|
||||
Logger::debug('Got mention', ['uid' => $uid, 'result' => $data, 'uri' => $uri]);
|
||||
break;
|
||||
|
||||
case 'reply':
|
||||
$data = bluesky_process_post($notification, $uid, Item::PR_PUSHED, 0);
|
||||
$data = bluesky_process_post($notification, $uid, Item::PR_PUSHED, 0, $last_poll);
|
||||
Logger::debug('Got reply', ['uid' => $uid, 'result' => $data, 'uri' => $uri]);
|
||||
break;
|
||||
|
||||
case 'quote':
|
||||
$data = bluesky_process_post($notification, $uid, Item::PR_PUSHED, 0);
|
||||
$data = bluesky_process_post($notification, $uid, Item::PR_PUSHED, 0, $last_poll);
|
||||
Logger::debug('Got quote', ['uid' => $uid, 'result' => $data, 'uri' => $uri]);
|
||||
break;
|
||||
|
||||
|
@ -962,7 +1062,7 @@ function bluesky_fetch_notifications(int $uid)
|
|||
}
|
||||
}
|
||||
|
||||
function bluesky_fetch_feed(int $uid, string $feed)
|
||||
function bluesky_fetch_feed(int $uid, string $feed, int $last_poll)
|
||||
{
|
||||
$data = bluesky_xrpc_get($uid, 'app.bsky.feed.getFeed', ['feed' => $feed]);
|
||||
if (empty($data)) {
|
||||
|
@ -983,15 +1083,22 @@ function bluesky_fetch_feed(int $uid, string $feed)
|
|||
}
|
||||
|
||||
foreach (array_reverse($data->feed) as $entry) {
|
||||
if (!Relay::isWantedLanguage($entry->post->record->text)) {
|
||||
$contact = bluesky_get_contact($entry->post->author, 0, $uid);
|
||||
$languages = $entry->post->record->langs ?? [];
|
||||
|
||||
if (!Relay::isWantedLanguage($entry->post->record->text, 0, $contact['id'] ?? 0, $languages)) {
|
||||
Logger::debug('Unwanted language detected', ['text' => $entry->post->record->text]);
|
||||
continue;
|
||||
}
|
||||
$id = bluesky_process_post($entry->post, $uid, Item::PR_TAG, 0);
|
||||
$id = bluesky_process_post($entry->post, $uid, Item::PR_TAG, 0, $last_poll);
|
||||
if (!empty($id)) {
|
||||
$post = Post::selectFirst(['uri-id'], ['id' => $id]);
|
||||
$stored = Post\Category::storeFileByURIId($post['uri-id'], $uid, Post\Category::SUBCRIPTION, $feedname, $feedurl);
|
||||
Logger::debug('Stored tag subscription for user', ['uri-id' => $post['uri-id'], 'uid' => $uid, 'name' => $feedname, 'url' => $feedurl, 'stored' => $stored]);
|
||||
if (!empty($post['uri-id'])) {
|
||||
$stored = Post\Category::storeFileByURIId($post['uri-id'], $uid, Post\Category::SUBCRIPTION, $feedname, $feedurl);
|
||||
Logger::debug('Stored tag subscription for user', ['uri-id' => $post['uri-id'], 'uid' => $uid, 'name' => $feedname, 'url' => $feedurl, 'stored' => $stored]);
|
||||
} else {
|
||||
Logger::notice('Post not found', ['id' => $id, 'entry' => $entry]);
|
||||
}
|
||||
}
|
||||
if (!empty($entry->reason)) {
|
||||
bluesky_process_reason($entry->reason, bluesky_get_uri($entry->post), $uid);
|
||||
|
@ -999,31 +1106,35 @@ function bluesky_fetch_feed(int $uid, string $feed)
|
|||
}
|
||||
}
|
||||
|
||||
function bluesky_process_post(stdClass $post, int $uid, int $post_reason, $level): int
|
||||
function bluesky_process_post(stdClass $post, int $uid, int $post_reason, int $level, int $last_poll): int
|
||||
{
|
||||
$uri = bluesky_get_uri($post);
|
||||
|
||||
if ($id = Post::selectFirst(['id'], ['uri' => $uri, 'uid' => $uid]) || $id = Post::selectFirst(['id'], ['extid' => $uri, 'uid' => $uid])) {
|
||||
return $id;
|
||||
if ($id = Post::selectFirst(['id'], ['uri' => $uri, 'uid' => $uid])) {
|
||||
return $id['id'];
|
||||
}
|
||||
|
||||
if ($id = Post::selectFirst(['id'], ['extid' => $uri, 'uid' => $uid])) {
|
||||
return $id['id'];
|
||||
}
|
||||
|
||||
Logger::debug('Importing post', ['uid' => $uid, 'indexedAt' => $post->indexedAt, 'uri' => $post->uri, 'cid' => $post->cid, 'root' => $post->record->reply->root ?? '']);
|
||||
|
||||
$item = bluesky_get_header($post, $uri, $uid, $uid);
|
||||
$item = bluesky_get_content($item, $post->record, $uri, $uid, $level);
|
||||
$item = bluesky_get_content($item, $post->record, $uri, $uid, $uid, $level, $last_poll);
|
||||
if (empty($item)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!empty($post->embed)) {
|
||||
$item = bluesky_add_media($post->embed, $item, $uid, $level);
|
||||
$item = bluesky_add_media($post->embed, $item, $uid, $level, $last_poll);
|
||||
}
|
||||
|
||||
if (empty($item['post-reason'])) {
|
||||
$item['post-reason'] = $post_reason;
|
||||
}
|
||||
|
||||
return item::insert($item);
|
||||
return Item::insert($item);
|
||||
}
|
||||
|
||||
function bluesky_get_header(stdClass $post, string $uri, int $uid, int $fetch_uid): array
|
||||
|
@ -1061,7 +1172,7 @@ function bluesky_get_header(stdClass $post, string $uri, int $uid, int $fetch_ui
|
|||
return $item;
|
||||
}
|
||||
|
||||
function bluesky_get_content(array $item, stdClass $record, string $uri, int $uid, int $level): array
|
||||
function bluesky_get_content(array $item, stdClass $record, string $uri, int $uid, int $fetch_uid, int $level, int $last_poll): array
|
||||
{
|
||||
if (empty($item)) {
|
||||
return [];
|
||||
|
@ -1070,7 +1181,7 @@ function bluesky_get_content(array $item, stdClass $record, string $uri, int $ui
|
|||
if (!empty($record->reply)) {
|
||||
$item['parent-uri'] = bluesky_get_uri($record->reply->root);
|
||||
if ($item['parent-uri'] != $uri) {
|
||||
$item['parent-uri'] = bluesky_fetch_missing_post($item['parent-uri'], $uid, $item['contact-id'], $level);
|
||||
$item['parent-uri'] = bluesky_fetch_missing_post($item['parent-uri'], $uid, $fetch_uid, $item['contact-id'], $level, $last_poll);
|
||||
if (empty($item['parent-uri'])) {
|
||||
return [];
|
||||
}
|
||||
|
@ -1078,21 +1189,27 @@ function bluesky_get_content(array $item, stdClass $record, string $uri, int $ui
|
|||
|
||||
$item['thr-parent'] = bluesky_get_uri($record->reply->parent);
|
||||
if (!in_array($item['thr-parent'], [$uri, $item['parent-uri']])) {
|
||||
$item['thr-parent'] = bluesky_fetch_missing_post($item['thr-parent'], $uid, $item['contact-id'], $level, $item['parent-uri']);
|
||||
$item['thr-parent'] = bluesky_fetch_missing_post($item['thr-parent'], $uid, $fetch_uid, $item['contact-id'], $level, $last_poll, $item['parent-uri']);
|
||||
if (empty($item['thr-parent'])) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$item['body'] = bluesky_get_text($record);
|
||||
$item['body'] = bluesky_get_text($record, $item['uri-id']);
|
||||
$item['created'] = DateTimeFormat::utc($record->createdAt, DateTimeFormat::MYSQL);
|
||||
$item['transmitted-languages'] = $record->langs ?? [];
|
||||
|
||||
if (($last_poll != 0) && strtotime($item['created']) > $last_poll) {
|
||||
$item['received'] = $item['created'];
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
function bluesky_get_text(stdClass $record): string
|
||||
function bluesky_get_text(stdClass $record, int $uri_id): string
|
||||
{
|
||||
$text = $record->text;
|
||||
$text = $record->text ?? '';
|
||||
|
||||
if (empty($record->facets)) {
|
||||
return $text;
|
||||
|
@ -1129,8 +1246,14 @@ function bluesky_get_text(stdClass $record): string
|
|||
}
|
||||
break;
|
||||
|
||||
case 'app.bsky.richtext.facet#tag';
|
||||
Tag::store($uri_id, Tag::HASHTAG, $feature->tag);
|
||||
$url = DI::baseUrl() . '/search?tag=' . urlencode($feature->tag);
|
||||
$linktext = '#' . $feature->tag;
|
||||
break;
|
||||
|
||||
default:
|
||||
Logger::notice('Unhandled feature type', ['type' => $feature->$type, 'record' => $record]);
|
||||
Logger::notice('Unhandled feature type', ['type' => $feature->$type, 'feature' => $feature, 'record' => $record]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1141,7 +1264,7 @@ function bluesky_get_text(stdClass $record): string
|
|||
return $text;
|
||||
}
|
||||
|
||||
function bluesky_add_media(stdClass $embed, array $item, int $fetch_uid, int $level): array
|
||||
function bluesky_add_media(stdClass $embed, array $item, int $fetch_uid, int $level, int $last_poll): array
|
||||
{
|
||||
$type = '$type';
|
||||
switch ($embed->$type) {
|
||||
|
@ -1178,18 +1301,17 @@ function bluesky_add_media(stdClass $embed, array $item, int $fetch_uid, int $le
|
|||
break;
|
||||
}
|
||||
$shared = bluesky_get_header($embed->record, $uri, 0, $fetch_uid);
|
||||
$shared = bluesky_get_content($shared, $embed->record->value, $uri, $item['uid'], $level);
|
||||
$shared = bluesky_get_content($shared, $embed->record->value, $uri, $item['uid'], $fetch_uid, $level, $last_poll);
|
||||
if (!empty($shared)) {
|
||||
if (!empty($embed->record->embeds)) {
|
||||
foreach ($embed->record->embeds as $single) {
|
||||
$shared = bluesky_add_media($single, $shared, $fetch_uid, $level);
|
||||
$shared = bluesky_add_media($single, $shared, $fetch_uid, $level, $last_poll);
|
||||
}
|
||||
}
|
||||
$id = Item::insert($shared);
|
||||
$shared = Post::selectFirst(['uri-id'], ['id' => $id]);
|
||||
Item::insert($shared);
|
||||
}
|
||||
}
|
||||
if (!empty($shared)) {
|
||||
if (!empty($shared['uri-id'])) {
|
||||
$item['quote-uri-id'] = $shared['uri-id'];
|
||||
}
|
||||
break;
|
||||
|
@ -1199,24 +1321,22 @@ function bluesky_add_media(stdClass $embed, array $item, int $fetch_uid, int $le
|
|||
$shared = Post::selectFirst(['uri-id'], ['uri' => $uri, 'uid' => $item['uid']]);
|
||||
if (empty($shared)) {
|
||||
$shared = bluesky_get_header($embed->record->record, $uri, 0, $fetch_uid);
|
||||
$shared = bluesky_get_content($shared, $embed->record->record->value, $uri, $item['uid'], $level);
|
||||
$shared = bluesky_get_content($shared, $embed->record->record->value, $uri, $item['uid'], $fetch_uid, $level, $last_poll);
|
||||
if (!empty($shared)) {
|
||||
if (!empty($embed->record->record->embeds)) {
|
||||
foreach ($embed->record->record->embeds as $single) {
|
||||
$shared = bluesky_add_media($single, $shared, $fetch_uid, $level);
|
||||
$shared = bluesky_add_media($single, $shared, $fetch_uid, $level, $last_poll);
|
||||
}
|
||||
}
|
||||
|
||||
$id = Item::insert($shared);
|
||||
$shared = Post::selectFirst(['uri-id'], ['id' => $id]);
|
||||
Item::insert($shared);
|
||||
}
|
||||
}
|
||||
if (!empty($shared)) {
|
||||
if (!empty($shared['uri-id'])) {
|
||||
$item['quote-uri-id'] = $shared['uri-id'];
|
||||
}
|
||||
|
||||
if (!empty($embed->media)) {
|
||||
$item = bluesky_add_media($embed->media, $item, $fetch_uid, $level);
|
||||
$item = bluesky_add_media($embed->media, $item, $fetch_uid, $level, $last_poll);
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -1230,7 +1350,7 @@ function bluesky_add_media(stdClass $embed, array $item, int $fetch_uid, int $le
|
|||
function bluesky_get_uri(stdClass $post): string
|
||||
{
|
||||
if (empty($post->cid)) {
|
||||
Logger::info('Invalid URI', ['post' => $post, 'callstack' => System::callstack(10, 0, true)]);
|
||||
Logger::info('Invalid URI', ['post' => $post]);
|
||||
return '';
|
||||
}
|
||||
return $post->uri . ':' . $post->cid;
|
||||
|
@ -1279,7 +1399,7 @@ function bluesky_get_uri_parts(string $uri): ?stdClass
|
|||
return $class;
|
||||
}
|
||||
|
||||
function bluesky_fetch_missing_post(string $uri, int $uid, int $causer, int $level, string $fallback = ''): string
|
||||
function bluesky_fetch_missing_post(string $uri, int $uid, int $fetch_uid, int $causer, int $level, int $last_poll = 0, string $fallback = ''): string
|
||||
{
|
||||
$fetched_uri = bluesky_fetch_post($uri, $uid);
|
||||
if (!empty($fetched_uri)) {
|
||||
|
@ -1297,13 +1417,13 @@ function bluesky_fetch_missing_post(string $uri, int $uid, int $causer, int $lev
|
|||
$fetch_uri = $class->uri;
|
||||
|
||||
Logger::debug('Fetch missing post', ['level' => $level, 'uid' => $uid, 'uri' => $uri]);
|
||||
$data = bluesky_xrpc_get($uid, 'app.bsky.feed.getPostThread', ['uri' => $fetch_uri]);
|
||||
$data = bluesky_xrpc_get($fetch_uid, 'app.bsky.feed.getPostThread', ['uri' => $fetch_uri]);
|
||||
if (empty($data)) {
|
||||
Logger::info('Thread was not fetched', ['level' => $level, 'uid' => $uid, 'uri' => $uri, 'fallback' => $fallback]);
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
Logger::debug('Reply count', ['replies' => $data->thread->post->replyCount, 'level' => $level, 'uid' => $uid, 'uri' => $uri]);
|
||||
Logger::debug('Reply count', ['level' => $level, 'uid' => $uid, 'uri' => $uri]);
|
||||
|
||||
if ($causer != 0) {
|
||||
$cdata = Contact::getPublicAndUserContactID($causer, $uid);
|
||||
|
@ -1311,7 +1431,7 @@ function bluesky_fetch_missing_post(string $uri, int $uid, int $causer, int $lev
|
|||
$cdata = [];
|
||||
}
|
||||
|
||||
return bluesky_process_thread($data->thread, $uid, $cdata, $level);
|
||||
return bluesky_process_thread($data->thread, $uid, $fetch_uid, $cdata, $level, $last_poll);
|
||||
}
|
||||
|
||||
function bluesky_fetch_post(string $uri, int $uid): string
|
||||
|
@ -1329,14 +1449,19 @@ function bluesky_fetch_post(string $uri, int $uid): string
|
|||
return '';
|
||||
}
|
||||
|
||||
function bluesky_process_thread(stdClass $thread, int $uid, array $cdata, int $level): string
|
||||
function bluesky_process_thread(stdClass $thread, int $uid, int $fetch_uid, array $cdata, int $level, int $last_poll): string
|
||||
{
|
||||
if (empty($thread->post)) {
|
||||
Logger::info('Invalid post', ['post' => $thread]);
|
||||
return '';
|
||||
}
|
||||
$uri = bluesky_get_uri($thread->post);
|
||||
|
||||
$fetched_uri = bluesky_fetch_post($uri, $uid);
|
||||
if (empty($fetched_uri)) {
|
||||
Logger::debug('Process missing post', ['uri' => $uri]);
|
||||
$item = bluesky_get_header($thread->post, $uri, $uid, $uid);
|
||||
$item = bluesky_get_content($item, $thread->post->record, $uri, $uid, $level);
|
||||
$item = bluesky_get_content($item, $thread->post->record, $uri, $uid, $fetch_uid, $level, $last_poll);
|
||||
if (!empty($item)) {
|
||||
$item['post-reason'] = Item::PR_FETCHED;
|
||||
|
||||
|
@ -1345,7 +1470,7 @@ function bluesky_process_thread(stdClass $thread, int $uid, array $cdata, int $l
|
|||
}
|
||||
|
||||
if (!empty($thread->post->embed)) {
|
||||
$item = bluesky_add_media($thread->post->embed, $item, $uid, $level);
|
||||
$item = bluesky_add_media($thread->post->embed, $item, $uid, $level, $last_poll);
|
||||
}
|
||||
$id = Item::insert($item);
|
||||
if (!$id) {
|
||||
|
@ -1363,7 +1488,7 @@ function bluesky_process_thread(stdClass $thread, int $uid, array $cdata, int $l
|
|||
}
|
||||
|
||||
foreach ($thread->replies ?? [] as $reply) {
|
||||
$reply_uri = bluesky_process_thread($reply, $uid, $cdata, $level);
|
||||
$reply_uri = bluesky_process_thread($reply, $uid, $fetch_uid, $cdata, $level, $last_poll);
|
||||
Logger::debug('Reply has been processed', ['uri' => $uri, 'reply' => $reply_uri]);
|
||||
}
|
||||
|
||||
|
@ -1428,10 +1553,9 @@ function bluesky_get_contact_fields(stdClass $author, int $uid, bool $update): a
|
|||
'blocked' => false,
|
||||
'readonly' => false,
|
||||
'pending' => false,
|
||||
'baseurl' => BLUESKY_HOST,
|
||||
'url' => $author->did,
|
||||
'nurl' => $author->did,
|
||||
'alias' => BLUESKY_HOST . '/profile/' . $author->handle,
|
||||
'alias' => BLUESKY_WEB . '/profile/' . $author->handle,
|
||||
'name' => $author->displayName ?? $author->handle,
|
||||
'nick' => $author->handle,
|
||||
'addr' => $author->handle,
|
||||
|
@ -1442,6 +1566,12 @@ function bluesky_get_contact_fields(stdClass $author, int $uid, bool $update): a
|
|||
return $fields;
|
||||
}
|
||||
|
||||
$fields['baseurl'] = bluesky_get_pds($author->did);
|
||||
if (!empty($fields['baseurl'])) {
|
||||
GServer::check($fields['baseurl'], Protocol::BLUESKY);
|
||||
$fields['gsid'] = GServer::getID($fields['baseurl'], true);
|
||||
}
|
||||
|
||||
$data = bluesky_xrpc_get($uid, 'app.bsky.actor.getProfile', ['actor' => $author->did]);
|
||||
if (empty($data)) {
|
||||
Logger::debug('Error fetching contact fields', ['uid' => $uid, 'url' => $fields['url']]);
|
||||
|
@ -1500,9 +1630,9 @@ function bluesky_get_preferences(int $uid): stdClass
|
|||
return $data;
|
||||
}
|
||||
|
||||
function bluesky_get_did(int $uid, string $handle): string
|
||||
function bluesky_get_did(string $handle): string
|
||||
{
|
||||
$data = bluesky_get($uid, '/xrpc/com.atproto.identity.resolveHandle?handle=' . urlencode($handle));
|
||||
$data = bluesky_get(BLUESKY_PDS . '/xrpc/com.atproto.identity.resolveHandle?handle=' . urlencode($handle));
|
||||
if (empty($data)) {
|
||||
return '';
|
||||
}
|
||||
|
@ -1510,6 +1640,38 @@ function bluesky_get_did(int $uid, string $handle): string
|
|||
return $data->did;
|
||||
}
|
||||
|
||||
function bluesky_get_user_pds(int $uid): string
|
||||
{
|
||||
$pds = DI::pConfig()->get($uid, 'bluesky', 'pds');
|
||||
if (!empty($pds)) {
|
||||
return $pds;
|
||||
}
|
||||
$did = DI::pConfig()->get($uid, 'bluesky', 'did');
|
||||
if (empty($did)) {
|
||||
Logger::notice('Empty did for user', ['uid' => $uid]);
|
||||
return '';
|
||||
}
|
||||
$pds = bluesky_get_pds($did);
|
||||
DI::pConfig()->set($uid, 'bluesky', 'pds', $pds);
|
||||
return $pds;
|
||||
}
|
||||
|
||||
function bluesky_get_pds(string $did): ?string
|
||||
{
|
||||
$data = bluesky_get(BLUESKY_DIRECTORY . '/' . $did);
|
||||
if (empty($data) || empty($data->service)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($data->service as $service) {
|
||||
if (($service->id == '#atproto_pds') && ($service->type == 'AtprotoPersonalDataServer') && !empty($service->serviceEndpoint)) {
|
||||
return $service->serviceEndpoint;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function bluesky_get_token(int $uid): string
|
||||
{
|
||||
$token = DI::pConfig()->get($uid, 'bluesky', 'access_token');
|
||||
|
@ -1546,6 +1708,7 @@ function bluesky_create_token(int $uid, string $password): string
|
|||
|
||||
$data = bluesky_post($uid, '/xrpc/com.atproto.server.createSession', json_encode(['identifier' => $did, 'password' => $password]), ['Content-type' => 'application/json']);
|
||||
if (empty($data)) {
|
||||
DI::pConfig()->set($uid, 'bluesky', 'status', BLUEKSY_STATUS_TOKEN_FAIL);
|
||||
return '';
|
||||
}
|
||||
|
||||
|
@ -1553,6 +1716,7 @@ function bluesky_create_token(int $uid, string $password): string
|
|||
DI::pConfig()->set($uid, 'bluesky', 'access_token', $data->accessJwt);
|
||||
DI::pConfig()->set($uid, 'bluesky', 'refresh_token', $data->refreshJwt);
|
||||
DI::pConfig()->set($uid, 'bluesky', 'token_created', time());
|
||||
DI::pConfig()->set($uid, 'bluesky', 'status', BLUEKSY_STATUS_TOKEN_OK);
|
||||
return $data->accessJwt;
|
||||
}
|
||||
|
||||
|
@ -1564,17 +1728,20 @@ function bluesky_xrpc_post(int $uid, string $url, $parameters): ?stdClass
|
|||
function bluesky_post(int $uid, string $url, string $params, array $headers): ?stdClass
|
||||
{
|
||||
try {
|
||||
$curlResult = DI::httpClient()->post(DI::pConfig()->get($uid, 'bluesky', 'host') . $url, $params, $headers);
|
||||
$curlResult = DI::httpClient()->post(bluesky_get_user_pds($uid) . $url, $params, $headers);
|
||||
} catch (\Exception $e) {
|
||||
Logger::notice('Exception on post', ['exception' => $e]);
|
||||
DI::pConfig()->set($uid, 'bluesky', 'status', BLUEKSY_STATUS_API_FAIL);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$curlResult->isSuccess()) {
|
||||
Logger::notice('API Error', ['error' => json_decode($curlResult->getBody()) ?: $curlResult->getBody()]);
|
||||
DI::pConfig()->set($uid, 'bluesky', 'status', BLUEKSY_STATUS_API_FAIL);
|
||||
return null;
|
||||
}
|
||||
|
||||
DI::pConfig()->set($uid, 'bluesky', 'status', BLUEKSY_STATUS_SUCCESS);
|
||||
return json_decode($curlResult->getBody());
|
||||
}
|
||||
|
||||
|
@ -1584,13 +1751,16 @@ function bluesky_xrpc_get(int $uid, string $url, array $parameters = []): ?stdCl
|
|||
$url .= '?' . http_build_query($parameters);
|
||||
}
|
||||
|
||||
return bluesky_get($uid, '/xrpc/' . $url, HttpClientAccept::JSON, [HttpClientOptions::HEADERS => ['Authorization' => ['Bearer ' . bluesky_get_token($uid)]]]);
|
||||
$data = bluesky_get(bluesky_get_user_pds($uid) . '/xrpc/' . $url, HttpClientAccept::JSON, [HttpClientOptions::HEADERS => ['Authorization' => ['Bearer ' . bluesky_get_token($uid)]]]);
|
||||
DI::pConfig()->set($uid, 'bluesky', 'status', is_null($data) ? BLUEKSY_STATUS_API_FAIL : BLUEKSY_STATUS_SUCCESS);
|
||||
return $data;
|
||||
|
||||
}
|
||||
|
||||
function bluesky_get(int $uid, string $url, string $accept_content = HttpClientAccept::DEFAULT, array $opts = []): ?stdClass
|
||||
function bluesky_get(string $url, string $accept_content = HttpClientAccept::DEFAULT, array $opts = []): ?stdClass
|
||||
{
|
||||
try {
|
||||
$curlResult = DI::httpClient()->get(DI::pConfig()->get($uid, 'bluesky', 'host') . $url, $accept_content, $opts);
|
||||
$curlResult = DI::httpClient()->get($url, $accept_content, $opts);
|
||||
} catch (\Exception $e) {
|
||||
Logger::notice('Exception on get', ['exception' => $e]);
|
||||
return null;
|
||||
|
|
|
@ -6,11 +6,11 @@ function bluesky_feed_run($argv, $argc)
|
|||
{
|
||||
require_once 'addon/bluesky/bluesky.php';
|
||||
|
||||
if ($argc != 3) {
|
||||
if ($argc != 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::debug('Importing feed - start', ['user' => $argv[1], 'feed' => $argv[2]]);
|
||||
bluesky_fetch_feed($argv[1], $argv[2]);
|
||||
Logger::debug('Importing feed - done', ['user' => $argv[1], 'feed' => $argv[2]]);
|
||||
Logger::debug('Importing feed - start', ['user' => $argv[1], 'feed' => $argv[2], 'last_poll' => $argv[3]]);
|
||||
bluesky_fetch_feed($argv[1], $argv[2], $argv[3]);
|
||||
Logger::debug('Importing feed - done', ['user' => $argv[1], 'feed' => $argv[2], 'last_poll' => $argv[3]]);
|
||||
}
|
||||
|
|
|
@ -6,11 +6,11 @@ function bluesky_notifications_run($argv, $argc)
|
|||
{
|
||||
require_once 'addon/bluesky/bluesky.php';
|
||||
|
||||
if ($argc != 2) {
|
||||
if ($argc != 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::notice('importing notifications - start', ['user' => $argv[1]]);
|
||||
bluesky_fetch_notifications($argv[1]);
|
||||
Logger::notice('importing notifications - done', ['user' => $argv[1]]);
|
||||
Logger::notice('importing notifications - start', ['user' => $argv[1], 'last_poll' => $argv[2]]);
|
||||
bluesky_fetch_notifications($argv[1], $argv[2]);
|
||||
Logger::notice('importing notifications - done', ['user' => $argv[1], 'last_poll' => $argv[2]]);
|
||||
}
|
||||
|
|
|
@ -6,11 +6,11 @@ function bluesky_timeline_run($argv, $argc)
|
|||
{
|
||||
require_once 'addon/bluesky/bluesky.php';
|
||||
|
||||
if ($argc != 2) {
|
||||
if ($argc != 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::notice('importing timeline - start', ['user' => $argv[1]]);
|
||||
bluesky_fetch_timeline($argv[1]);
|
||||
Logger::notice('importing timeline - done', ['user' => $argv[1]]);
|
||||
Logger::notice('importing timeline - start', ['user' => $argv[1], 'last_poll' => $argv[2]]);
|
||||
bluesky_fetch_timeline($argv[1], $argv[2]);
|
||||
Logger::notice('importing timeline - done', ['user' => $argv[1], 'last_poll' => $argv[2]]);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-06-05 04:34+0000\n"
|
||||
"POT-Creation-Date: 2023-12-06 06:30+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -17,70 +17,100 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: bluesky.php:314
|
||||
msgid ""
|
||||
"You are authenticated to Bluesky. For security reasons the password isn't "
|
||||
"stored."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:314
|
||||
msgid "You are not authenticated. Please enter the app password."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:318
|
||||
#: bluesky.php:336
|
||||
msgid "Enable Bluesky Post Addon"
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:319
|
||||
#: bluesky.php:337
|
||||
msgid "Post to Bluesky by default"
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:320
|
||||
#: bluesky.php:338
|
||||
msgid "Import the remote timeline"
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:321
|
||||
#: bluesky.php:339
|
||||
msgid "Import the pinned feeds"
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:321
|
||||
#: bluesky.php:339
|
||||
msgid ""
|
||||
"When activated, Posts will be imported from all the feeds that you pinned in "
|
||||
"Bluesky."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:322
|
||||
msgid "Bluesky host"
|
||||
#: bluesky.php:340
|
||||
msgid "Personal Data Server"
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:323
|
||||
#: bluesky.php:340
|
||||
msgid "The personal data server (PDS) is the system that hosts your profile."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:341
|
||||
msgid "Bluesky handle"
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:324
|
||||
#: bluesky.php:342
|
||||
msgid "Bluesky DID"
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:324
|
||||
#: bluesky.php:342
|
||||
msgid ""
|
||||
"This is the unique identifier. It will be fetched automatically, when the "
|
||||
"handle is entered."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:325
|
||||
#: bluesky.php:343
|
||||
msgid "Bluesky app password"
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:325
|
||||
#: bluesky.php:343
|
||||
msgid ""
|
||||
"Please don't add your real password here, but instead create a specific app "
|
||||
"password in the Bluesky settings."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:331
|
||||
#: bluesky.php:349
|
||||
msgid "Bluesky Import/Export"
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:382
|
||||
#: bluesky.php:359
|
||||
msgid ""
|
||||
"You are not authenticated. Please enter your handle and the app password."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:379
|
||||
msgid ""
|
||||
"You are authenticated to Bluesky. For security reasons the password isn't "
|
||||
"stored."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:381
|
||||
msgid ""
|
||||
"The communication with the personal data server service (PDS) is established."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:383
|
||||
msgid "Communication issues with the personal data server service (PDS)."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:385
|
||||
msgid ""
|
||||
"The DID for the provided handle could not be detected. Please check if you "
|
||||
"entered the correct handle."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:387
|
||||
msgid "The personal data server service (PDS) could not be detected."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:389
|
||||
msgid ""
|
||||
"The authentication with the provided handle and password failed. Please "
|
||||
"check if you entered the correct password."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:457
|
||||
msgid "Post to Bluesky"
|
||||
msgstr ""
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{{include file="field_checkbox.tpl" field=$bydefault}}
|
||||
{{include file="field_checkbox.tpl" field=$import}}
|
||||
{{include file="field_checkbox.tpl" field=$import_feeds}}
|
||||
{{include file="field_input.tpl" field=$host}}
|
||||
{{include file="field_input.tpl" field=$pds}}
|
||||
{{include file="field_input.tpl" field=$handle}}
|
||||
{{include file="field_input.tpl" field=$did}}
|
||||
{{include file="field_input.tpl" field=$password}}
|
|
@ -5,8 +5,7 @@
|
|||
#
|
||||
# Translators:
|
||||
# Vladimir Núñez <lapoubelle111@gmail.com>, 2019
|
||||
# Walter Bulbazor, 2021
|
||||
# Hypolite Petovan <hypolite@mrpetovan.com>, 2022
|
||||
# Florent C., 2023
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
|
@ -15,8 +14,8 @@ msgstr ""
|
|||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-11-21 19:14-0500\n"
|
||||
"PO-Revision-Date: 2018-04-07 05:23+0000\n"
|
||||
"Last-Translator: Hypolite Petovan <hypolite@mrpetovan.com>, 2022\n"
|
||||
"Language-Team: French (https://www.transifex.com/Friendica/teams/12172/fr/)\n"
|
||||
"Last-Translator: Florent C., 2023\n"
|
||||
"Language-Team: French (https://app.transifex.com/Friendica/teams/12172/fr/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
@ -25,7 +24,7 @@ msgstr ""
|
|||
|
||||
#: catavatar.php:48
|
||||
msgid "Set default profile avatar or randomize the cat."
|
||||
msgstr "Mettre l'avatar par défaut ou tirer au sort le Chat."
|
||||
msgstr "Mettre l'avatar par défaut ou tirer au sort le chat."
|
||||
|
||||
#: catavatar.php:53
|
||||
msgid "Cat Avatar Settings"
|
||||
|
@ -33,15 +32,15 @@ msgstr "Paramètres de Chat avatar"
|
|||
|
||||
#: catavatar.php:56
|
||||
msgid "Use Cat as Avatar"
|
||||
msgstr "Utiliser Chat comme avatar"
|
||||
msgstr "Utiliser ce Chat"
|
||||
|
||||
#: catavatar.php:57
|
||||
msgid "Another random Cat!"
|
||||
msgstr "Un autre chat aléatoire !"
|
||||
msgstr "Un autre Chat aléatoire !"
|
||||
|
||||
#: catavatar.php:58
|
||||
msgid "Reset to email Cat"
|
||||
msgstr "Réinitialiser à Chat courriel"
|
||||
msgstr "Revenir au Chat par défaut"
|
||||
|
||||
#: catavatar.php:77
|
||||
msgid "The cat hadn't found itself."
|
||||
|
|
|
@ -5,11 +5,11 @@ function string_plural_select_fr($n){
|
|||
$n = intval($n);
|
||||
if (($n == 0 || $n == 1)) { return 0; } else if ($n != 0 && $n % 1000000 == 0) { return 1; } else { return 2; }
|
||||
}}
|
||||
$a->strings['Set default profile avatar or randomize the cat.'] = 'Mettre l\'avatar par défaut ou tirer au sort le Chat.';
|
||||
$a->strings['Set default profile avatar or randomize the cat.'] = 'Mettre l\'avatar par défaut ou tirer au sort le chat.';
|
||||
$a->strings['Cat Avatar Settings'] = 'Paramètres de Chat avatar';
|
||||
$a->strings['Use Cat as Avatar'] = 'Utiliser Chat comme avatar';
|
||||
$a->strings['Another random Cat!'] = 'Un autre chat aléatoire !';
|
||||
$a->strings['Reset to email Cat'] = 'Réinitialiser à Chat courriel';
|
||||
$a->strings['Use Cat as Avatar'] = 'Utiliser ce Chat';
|
||||
$a->strings['Another random Cat!'] = 'Un autre Chat aléatoire !';
|
||||
$a->strings['Reset to email Cat'] = 'Revenir au Chat par défaut';
|
||||
$a->strings['The cat hadn\'t found itself.'] = 'Le Chat ne s\'y est pas retrouvé';
|
||||
$a->strings['There was an error, the cat ran away.'] = 'Il y a eu une erreur et le chat s\'est enfui';
|
||||
$a->strings['Profile Photos'] = 'Photos de profil';
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
Compact Language Detector
|
||||
===
|
||||
CLD2 is an advanced language dectection library with a high reliability.
|
||||
|
||||
This addon depends on the CLD PHP module which is not included in any Linux distribution.
|
||||
It needs to be built and installed by hand, which is not totally straightforward.
|
||||
|
||||
Prerequisite
|
||||
---
|
||||
To be able to build the extension, you need the CLD module and the files for the PHP module development.
|
||||
On Debian you install the packages php-dev, libcld2-dev and libcld2-0.
|
||||
Make sure to have installed the correct PHP version.
|
||||
Means: When you have got both PHP 8.0 and 8.2 on your system, you have to install php8.0-dev as well.
|
||||
|
||||
Installation
|
||||
---
|
||||
The original PHP extension is https://github.com/fntlnz/cld2-php-ext.
|
||||
However, it doesn't support PHP8.
|
||||
So https://github.com/hiteule/cld2-php-ext/tree/support-php8 has to be used.
|
||||
|
||||
Download the source code:
|
||||
```
|
||||
wget https://github.com/hiteule/cld2-php-ext/archive/refs/heads/support-php8.zip
|
||||
```
|
||||
|
||||
Unzip it:
|
||||
```
|
||||
unzip support-php8.zip
|
||||
```
|
||||
|
||||
Change into the folder:
|
||||
```
|
||||
cd cld2-php-ext-support-php8/
|
||||
```
|
||||
|
||||
Configure for the PHP Api version:
|
||||
```
|
||||
phpize
|
||||
```
|
||||
(if you have got several PHP versions on your system, execute the command with the version that you run Friendica with, e.g. `phpize8.0`)
|
||||
|
||||
Create the Makefile:
|
||||
```
|
||||
./configure --with-cld2=/usr/include/cld2
|
||||
```
|
||||
|
||||
Have a look at the line `checking for PHP includes`.
|
||||
When the output (for example `/usr/include/php/20220829` doesn't match the API version that you got from `phpize`, then you have to change all the version codes in your `Makefile` afterwards)
|
||||
|
||||
Create the module:
|
||||
```
|
||||
make -j
|
||||
```
|
||||
|
||||
Install it:
|
||||
```
|
||||
sudo make install
|
||||
```
|
||||
|
||||
Change to the folder with the available modules. When you use PHP 8.2 on Debian it is:
|
||||
```
|
||||
cd /etc/php/8.2/mods-available
|
||||
```
|
||||
|
||||
Create the file `cld2.ini` with this content:
|
||||
```
|
||||
; configuration for php cld2 module
|
||||
; priority=20
|
||||
extension=cld2.so
|
||||
```
|
||||
|
||||
Enable the module for all versions and all sapi:
|
||||
```
|
||||
phpenmod -v ALL -s ALL cld2
|
||||
```
|
||||
|
||||
Then restart the apache or fpm (or whatever you use) to load the changed configuration.
|
||||
|
||||
Call `/admin/phpinfo` on your webserver.
|
||||
You then see the PHP Info.
|
||||
Search for "cld2".
|
||||
The module is installed, when you find it here.
|
||||
**Only proceed when the module is installed**
|
||||
|
||||
Now you can enable the addon.
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
/**
|
||||
* Name: Compact Language Detector
|
||||
* Description: Improved language detection
|
||||
* Version: 0.1
|
||||
* Author: Michael Vogel <heluecht@pirati.ca>
|
||||
*/
|
||||
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\DI;
|
||||
|
||||
function cld_install()
|
||||
{
|
||||
Hook::register('detect_languages', __FILE__, 'cld_detect_languages');
|
||||
}
|
||||
|
||||
function cld_detect_languages(array &$data)
|
||||
{
|
||||
if (!in_array('cld2', get_loaded_extensions())) {
|
||||
Logger::warning('CLD2 is not installed.');
|
||||
return;
|
||||
}
|
||||
|
||||
$cld2 = new \CLD2Detector();
|
||||
|
||||
$cld2->setEncodingHint(CLD2Encoding::UTF8); // optional, hints about text encoding
|
||||
$cld2->setPlainText(true);
|
||||
|
||||
$result = $cld2->detect($data['text']);
|
||||
|
||||
if ($data['detected']) {
|
||||
$original = array_key_first($data['detected']);
|
||||
} else {
|
||||
$original = '';
|
||||
}
|
||||
|
||||
$detected = DI::l10n()->toISO6391($result['language_code']);
|
||||
|
||||
// languages that aren't supported via the base language detection or tend to false detections
|
||||
if ((strlen($detected) == 3) || in_array($detected, ['ht', 'kk', 'ku', 'ky', 'lg', 'mg', 'mk', 'mt', 'ny', 'rw', 'st', 'su', 'tg', 'ts', 'xx'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$result['is_reliable']) {
|
||||
Logger::debug('Unreliable detection', ['uri-id' => $data['uri-id'], 'original' => $original, 'detected' => $detected, 'name' => $result['language_name'], 'probability' => $result['language_probability'], 'text' => $data['text']]);
|
||||
if (($original == $detected) && ($data['detected'][$original] < $result['language_probability'] / 100)) {
|
||||
$data['detected'][$original] = $result['language_probability'] / 100;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$available = array_keys(DI::l10n()->getLanguageCodes());
|
||||
|
||||
if (!in_array($detected, $available)) {
|
||||
Logger::debug('Unsupported language', ['uri-id' => $data['uri-id'], 'original' => $original, 'detected' => $detected, 'name' => $result['language_name'], 'probability' => $result['language_probability'], 'text' => $data['text']]);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($original != $detected) {
|
||||
Logger::debug('Detected different language', ['uri-id' => $data['uri-id'], 'original' => $original, 'detected' => $detected, 'name' => $result['language_name'], 'probability' => $result['language_probability'], 'text' => $data['text']]);
|
||||
}
|
||||
|
||||
$length = count($data['detected']);
|
||||
if ($length > 0) {
|
||||
unset($data['detected'][$detected]);
|
||||
$data['detected'] = array_merge([$detected => $result['language_probability'] / 100], array_slice($data['detected'], 0, $length - 1));
|
||||
} else {
|
||||
$data['detected'] = [$detected => $result['language_probability'] / 100];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
invidious Addon for Friendica
|
||||
==========================
|
||||
|
||||
This addon will replace "youtube.com" with the chosen Invidious instance
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
/*
|
||||
* Name: invidious
|
||||
* Description: Replaces links to youtube.com to an invidious instance in all displays of postings on a node.
|
||||
* Version: 0.3
|
||||
* Author: Matthias Ebers <https://loma.ml/profile/feb>
|
||||
* Author: Michael Vogel <https://pirati.ca/profile/heluecht>
|
||||
*
|
||||
*/
|
||||
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\DI;
|
||||
|
||||
CONST INVIDIOUS_DEFAULT = 'https://invidio.us';
|
||||
|
||||
function invidious_install()
|
||||
{
|
||||
Hook::register('prepare_body_final', __FILE__, 'invidious_render');
|
||||
Hook::register('addon_settings', __FILE__, 'invidious_settings');
|
||||
Hook::register('addon_settings_post', __FILE__, 'invidious_settings_post');
|
||||
}
|
||||
|
||||
/* Handle the send data from the admin settings
|
||||
*/
|
||||
function invidious_addon_admin_post()
|
||||
{
|
||||
DI::config()->set('invidious', 'server', trim($_POST['invidiousserver'], " \n\r\t\v\x00/"));
|
||||
}
|
||||
|
||||
/* Hook into the admin settings to let the admin choose an
|
||||
* invidious server to use for the replacement.
|
||||
*/
|
||||
function invidious_addon_admin(string &$o)
|
||||
{
|
||||
$invidiousserver = DI::config()->get('invidious', 'server', INVIDIOUS_DEFAULT);
|
||||
$t = Renderer::getMarkupTemplate('admin.tpl', 'addon/invidious/');
|
||||
$o = Renderer::replaceMacros($t, [
|
||||
'$settingdescription' => DI::l10n()->t('Which Invidious server shall be used for the replacements in the post bodies? Use the URL with servername and protocol. See %s for a list of available public Invidious servers.', 'https://redirect.invidious.io'),
|
||||
'$invidiousserver' => ['invidiousserver', DI::l10n()->t('Invidious server'), $invidiousserver, DI::l10n()->t('See %s for a list of available Invidious servers.', '<a href="https://api.invidious.io/">https://api.invidious.io/</a>')],
|
||||
'$submit' => DI::l10n()->t('Save Settings'),
|
||||
]);
|
||||
}
|
||||
|
||||
function invidious_settings(array &$data)
|
||||
{
|
||||
if (!DI::userSession()->getLocalUserId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$enabled = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'invidious', 'enabled');
|
||||
$server = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'invidious', 'server', DI::config()->get('invidious', 'server', INVIDIOUS_DEFAULT));
|
||||
|
||||
$t = Renderer::getMarkupTemplate('settings.tpl', 'addon/invidious/');
|
||||
$html = Renderer::replaceMacros($t, [
|
||||
'$enabled' => ['enabled', DI::l10n()->t('Replace Youtube links with links to an Invidious server'), $enabled, DI::l10n()->t('If enabled, Youtube links are replaced with the links to the specified Invidious server.')],
|
||||
'$server' => ['server', DI::l10n()->t('Invidious server'), $server, DI::l10n()->t('See %s for a list of available Invidious servers.', '<a href="https://api.invidious.io/">https://api.invidious.io/</a>')],
|
||||
]);
|
||||
|
||||
$data = [
|
||||
'addon' => 'invidious',
|
||||
'title' => DI::l10n()->t('Invidious Settings'),
|
||||
'html' => $html,
|
||||
];
|
||||
}
|
||||
|
||||
function invidious_settings_post(array &$b)
|
||||
{
|
||||
if (!DI::userSession()->getLocalUserId() || empty($_POST['invidious-submit'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'invidious', 'enabled', (bool)$_POST['enabled']);
|
||||
|
||||
$server = trim($_POST['server'], " \n\r\t\v\x00/");
|
||||
if ($server != DI::config()->get('invidious', 'server', INVIDIOUS_DEFAULT) && !empty($server)) {
|
||||
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'invidious', 'server', $server);
|
||||
} else {
|
||||
DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'invidious', 'server');
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* replace "youtube.com" with the chosen Invidious instance
|
||||
*/
|
||||
function invidious_render(array &$b)
|
||||
{
|
||||
if (!DI::userSession()->getLocalUserId() || !DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'invidious', 'enabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$original = $b['html'];
|
||||
$server = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'invidious', 'server', DI::config()->get('invidious', 'server', INVIDIOUS_DEFAULT));
|
||||
|
||||
$b['html'] = preg_replace("/https?:\/\/www.youtube.com\/watch\?v\=(.*?)/ism", $server . '/watch?v=$1', $b['html']);
|
||||
$b['html'] = preg_replace("/https?:\/\/www.youtube.com\/embed\/(.*?)/ism", $server . '/embed/$1', $b['html']);
|
||||
$b['html'] = preg_replace("/https?:\/\/www.youtube.com\/shorts\/(.*?)/ism", $server . '/shorts/$1', $b['html']);
|
||||
$b['html'] = preg_replace("/https?:\/\/youtu.be\/(.*?)/ism", $server . '/watch?v=$1', $b['html']);
|
||||
|
||||
if ($original != $b['html']) {
|
||||
$b['html'] .= '<hr><p><small>' . DI::l10n()->t('(Invidious addon enabled: YouTube links via %s)', $server) . '</small></p>';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
# ADDON invidious
|
||||
# Copyright (C)
|
||||
# This file is distributed under the same license as the Friendica invidious addon package.
|
||||
#
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-12-18 17:23+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: invidious.php:39
|
||||
#, php-format
|
||||
msgid ""
|
||||
"Which Invidious server shall be used for the replacements in the post "
|
||||
"bodies? Use the URL with servername and protocol. See %s for a list of "
|
||||
"available public Invidious servers."
|
||||
msgstr ""
|
||||
|
||||
#: invidious.php:40 invidious.php:57
|
||||
msgid "Invidious server"
|
||||
msgstr ""
|
||||
|
||||
#: invidious.php:40 invidious.php:57
|
||||
#, php-format
|
||||
msgid "See %s for a list of available Invidious servers."
|
||||
msgstr ""
|
||||
|
||||
#: invidious.php:41
|
||||
msgid "Save Settings"
|
||||
msgstr ""
|
||||
|
||||
#: invidious.php:56
|
||||
msgid "Replace Youtube links with links to an Invidious server"
|
||||
msgstr ""
|
||||
|
||||
#: invidious.php:56
|
||||
msgid ""
|
||||
"If enabled, Youtube links are replaced with the links to the specified "
|
||||
"Invidious server."
|
||||
msgstr ""
|
||||
|
||||
#: invidious.php:62
|
||||
msgid "Invidious Settings"
|
||||
msgstr ""
|
||||
|
||||
#: invidious.php:101
|
||||
#, php-format
|
||||
msgid "(Invidious addon enabled: YouTube links via %s)"
|
||||
msgstr ""
|
|
@ -0,0 +1,5 @@
|
|||
<p>{{$settingdescription}}</p>
|
||||
|
||||
{{include file="field_input.tpl" field=$invidiousserver}}
|
||||
|
||||
<div class="submit"><input type="submit" name="page_site" value="{{$submit}}" /></div>
|
|
@ -0,0 +1,2 @@
|
|||
{{include file="field_checkbox.tpl" field=$enabled}}
|
||||
{{include file="field_input.tpl" field=$server}}
|
|
@ -4,63 +4,59 @@
|
|||
#
|
||||
#
|
||||
# Translators:
|
||||
# Alexander An <ravnina@gmail.com>, 2020
|
||||
# Alexander An <ravnina@gmail.com>, 2020,2023
|
||||
# Stanislav N. <pztrn@pztrn.name>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: friendica\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-10-03 22:02-0400\n"
|
||||
"PO-Revision-Date: 2020-10-09 17:48+0000\n"
|
||||
"Last-Translator: Alexander An <ravnina@gmail.com>\n"
|
||||
"Language-Team: Russian (http://www.transifex.com/Friendica/friendica/language/ru/)\n"
|
||||
"POT-Creation-Date: 2021-11-21 19:15-0500\n"
|
||||
"PO-Revision-Date: 2015-07-25 08:05+0000\n"
|
||||
"Last-Translator: Alexander An <ravnina@gmail.com>, 2020,2023\n"
|
||||
"Language-Team: Russian (http://app.transifex.com/Friendica/friendica/language/ru/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: ru\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
|
||||
|
||||
#: langfilter.php:52
|
||||
msgid "Language Filter"
|
||||
msgstr "Языковой фильтр"
|
||||
|
||||
#: langfilter.php:53
|
||||
#: langfilter.php:49
|
||||
msgid ""
|
||||
"This addon tries to identify the language posts are written in. If it does "
|
||||
"not match any language specified below, posts will be hidden by collapsing "
|
||||
"them."
|
||||
msgstr "Это дополнение пытается идентифицировать язык, на котором написаны посты. Если язык не соответствует ни одному, указанному ниже, то такие посты будут скрыты."
|
||||
msgstr "Это дополнение пытается идентифицировать язык, на котором сделаны записи. Если язык не соответствует ни одному, указанному ниже, то такие посты будут свёрнуты."
|
||||
|
||||
#: langfilter.php:54
|
||||
#: langfilter.php:50
|
||||
msgid "Use the language filter"
|
||||
msgstr "Использовать языковой фильтр"
|
||||
|
||||
#: langfilter.php:55
|
||||
#: langfilter.php:51
|
||||
msgid "Able to read"
|
||||
msgstr "Возможность читать"
|
||||
|
||||
#: langfilter.php:55
|
||||
#: langfilter.php:51
|
||||
msgid ""
|
||||
"List of abbreviations (ISO 639-1 codes) for languages you speak, comma "
|
||||
"separated. For example \"de,it\"."
|
||||
msgstr "Список аббревиатур (кодов по ISO 639-1 ) для языков, на которых вы говорите. Например, \"ru,en\"."
|
||||
|
||||
#: langfilter.php:56
|
||||
#: langfilter.php:52
|
||||
msgid "Minimum confidence in language detection"
|
||||
msgstr "Минимальная уверенность в определении языка"
|
||||
|
||||
#: langfilter.php:56
|
||||
#: langfilter.php:52
|
||||
msgid ""
|
||||
"Minimum confidence in language detection being correct, from 0 to 100. Posts"
|
||||
" will not be filtered when the confidence of language detection is below "
|
||||
"this percent value."
|
||||
msgstr "Минимальная уверенность в правильном определении языка, от 0 до 100. Посты не будут скрыты, если уверенность в правильном определении языка в процентах ниже этого значения."
|
||||
|
||||
#: langfilter.php:57
|
||||
#: langfilter.php:53
|
||||
msgid "Minimum length of message body"
|
||||
msgstr "Минимальная длина тела сообщения"
|
||||
|
||||
#: langfilter.php:57
|
||||
#: langfilter.php:53
|
||||
msgid ""
|
||||
"Minimum number of characters in message body for filter to be used. Posts "
|
||||
"shorter than this will not be filtered. Note: Language detection is "
|
||||
|
@ -68,10 +64,14 @@ msgid ""
|
|||
msgstr "Минимальное количество знаков в теле сообщения для применения фильтрации. Посты, длина которых меньше указанного значения, не будут отфильтрованы. Обратите внимание, что определение языка работает ненадежно для небольших постов (<200 символов)."
|
||||
|
||||
#: langfilter.php:58
|
||||
msgid "Language Filter"
|
||||
msgstr "Языковой фильтр"
|
||||
|
||||
#: langfilter.php:60
|
||||
msgid "Save Settings"
|
||||
msgstr "Сохранить настройки"
|
||||
|
||||
#: langfilter.php:189
|
||||
#: langfilter.php:193
|
||||
#, php-format
|
||||
msgid "Filtered language: %s"
|
||||
msgstr "Отфильтрованный язык: %s"
|
||||
|
|
|
@ -5,8 +5,7 @@ function string_plural_select_ru($n){
|
|||
$n = intval($n);
|
||||
if ($n%10==1 && $n%100!=11) { return 0; } else if ($n%10>=2 && $n%10<=4 && ($n%100<12 || $n%100>14)) { return 1; } else if ($n%10==0 || ($n%10>=5 && $n%10<=9) || ($n%100>=11 && $n%100<=14)) { return 2; } else { return 3; }
|
||||
}}
|
||||
$a->strings['Language Filter'] = 'Языковой фильтр';
|
||||
$a->strings['This addon tries to identify the language posts are written in. If it does not match any language specified below, posts will be hidden by collapsing them.'] = 'Это дополнение пытается идентифицировать язык, на котором написаны посты. Если язык не соответствует ни одному, указанному ниже, то такие посты будут скрыты.';
|
||||
$a->strings['This addon tries to identify the language posts are written in. If it does not match any language specified below, posts will be hidden by collapsing them.'] = 'Это дополнение пытается идентифицировать язык, на котором сделаны записи. Если язык не соответствует ни одному, указанному ниже, то такие посты будут свёрнуты.';
|
||||
$a->strings['Use the language filter'] = 'Использовать языковой фильтр';
|
||||
$a->strings['Able to read'] = 'Возможность читать';
|
||||
$a->strings['List of abbreviations (ISO 639-1 codes) for languages you speak, comma separated. For example "de,it".'] = 'Список аббревиатур (кодов по ISO 639-1 ) для языков, на которых вы говорите. Например, "ru,en".';
|
||||
|
@ -14,5 +13,6 @@ $a->strings['Minimum confidence in language detection'] = 'Минимальна
|
|||
$a->strings['Minimum confidence in language detection being correct, from 0 to 100. Posts will not be filtered when the confidence of language detection is below this percent value.'] = 'Минимальная уверенность в правильном определении языка, от 0 до 100. Посты не будут скрыты, если уверенность в правильном определении языка в процентах ниже этого значения.';
|
||||
$a->strings['Minimum length of message body'] = 'Минимальная длина тела сообщения';
|
||||
$a->strings['Minimum number of characters in message body for filter to be used. Posts shorter than this will not be filtered. Note: Language detection is unreliable for short content (<200 characters).'] = 'Минимальное количество знаков в теле сообщения для применения фильтрации. Посты, длина которых меньше указанного значения, не будут отфильтрованы. Обратите внимание, что определение языка работает ненадежно для небольших постов (<200 символов).';
|
||||
$a->strings['Language Filter'] = 'Языковой фильтр';
|
||||
$a->strings['Save Settings'] = 'Сохранить настройки';
|
||||
$a->strings['Filtered language: %s'] = 'Отфильтрованный язык: %s';
|
||||
|
|
|
@ -163,7 +163,7 @@ function langfilter_prepare_body_content_filter(&$hook_data)
|
|||
return;
|
||||
}
|
||||
|
||||
$lang = $iso639->languageByCode1($iso2);
|
||||
$lang = $iso639->languageByCode1(substr($iso2, 0, 2));
|
||||
} else {
|
||||
$opts = $hook_data['item']['postopts'];
|
||||
if (!$opts) {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#
|
||||
#
|
||||
# Translators:
|
||||
# Florent C., 2023
|
||||
# Nicolas Derive, 2022
|
||||
# ea1cd8241cb389ffb6f92bc6891eff5d_dc12308 <70dced5587d47e18d88f9298024d96f8_93383>, 2015
|
||||
# StefOfficiel <pichard.stephane@free.fr>, 2015
|
||||
|
@ -13,8 +14,8 @@ msgstr ""
|
|||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-11-21 19:15-0500\n"
|
||||
"PO-Revision-Date: 2014-06-23 09:54+0000\n"
|
||||
"Last-Translator: Nicolas Derive, 2022\n"
|
||||
"Language-Team: French (http://www.transifex.com/Friendica/friendica/language/fr/)\n"
|
||||
"Last-Translator: Florent C., 2023\n"
|
||||
"Language-Team: French (http://app.transifex.com/Friendica/friendica/language/fr/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
@ -33,68 +34,72 @@ msgstr "Adresse de courriel de laquelle les éléments du flux sembleront proven
|
|||
msgid "Save Settings"
|
||||
msgstr "Sauvegarder les paramètres"
|
||||
|
||||
#: mailstream.php:301
|
||||
#: mailstream.php:311
|
||||
msgid "Re:"
|
||||
msgstr "Re :"
|
||||
|
||||
#: mailstream.php:314 mailstream.php:317
|
||||
#: mailstream.php:324 mailstream.php:327
|
||||
msgid "Friendica post"
|
||||
msgstr "Message Friendica"
|
||||
|
||||
#: mailstream.php:320
|
||||
#: mailstream.php:330
|
||||
msgid "Diaspora post"
|
||||
msgstr "Message Diaspora"
|
||||
|
||||
#: mailstream.php:330
|
||||
#: mailstream.php:340
|
||||
msgid "Feed item"
|
||||
msgstr "Élément du flux"
|
||||
|
||||
#: mailstream.php:333
|
||||
#: mailstream.php:343
|
||||
msgid "Email"
|
||||
msgstr "Courriel"
|
||||
|
||||
#: mailstream.php:335
|
||||
#: mailstream.php:345
|
||||
msgid "Friendica Item"
|
||||
msgstr "Élément de Friendica"
|
||||
|
||||
#: mailstream.php:404
|
||||
#: mailstream.php:419
|
||||
msgid "Upstream"
|
||||
msgstr "En amont"
|
||||
|
||||
#: mailstream.php:405
|
||||
#: mailstream.php:420
|
||||
msgid "URI"
|
||||
msgstr "URI"
|
||||
|
||||
#: mailstream.php:421
|
||||
msgid "Local"
|
||||
msgstr "Local"
|
||||
|
||||
#: mailstream.php:481
|
||||
#: mailstream.php:499
|
||||
msgid "Enabled"
|
||||
msgstr "Activer"
|
||||
|
||||
#: mailstream.php:486
|
||||
#: mailstream.php:504
|
||||
msgid "Email Address"
|
||||
msgstr "Adresse de courriel"
|
||||
|
||||
#: mailstream.php:488
|
||||
#: mailstream.php:506
|
||||
msgid "Leave blank to use your account email address"
|
||||
msgstr "Laissez vide pour utiliser l'adresse de courriel de votre compte"
|
||||
|
||||
#: mailstream.php:492
|
||||
#: mailstream.php:510
|
||||
msgid "Exclude Likes"
|
||||
msgstr "Exclure les \"j'aime\""
|
||||
|
||||
#: mailstream.php:494
|
||||
#: mailstream.php:512
|
||||
msgid "Check this to omit mailing \"Like\" notifications"
|
||||
msgstr "Cochez ceci pour éviter d'envoyer les notifications des \"J'aime\""
|
||||
|
||||
#: mailstream.php:498
|
||||
#: mailstream.php:516
|
||||
msgid "Attach Images"
|
||||
msgstr "Attacher les images"
|
||||
|
||||
#: mailstream.php:500
|
||||
#: mailstream.php:518
|
||||
msgid ""
|
||||
"Download images in posts and attach them to the email. Useful for reading "
|
||||
"email while offline."
|
||||
msgstr "Télécharger les images des messages et les attacher au courriel. Utile pour les les courriels hors-ligne."
|
||||
|
||||
#: mailstream.php:507
|
||||
#: mailstream.php:525
|
||||
msgid "Mail Stream Settings"
|
||||
msgstr "Paramètres de Mail Stream"
|
||||
|
|
|
@ -15,6 +15,7 @@ $a->strings['Feed item'] = 'Élément du flux';
|
|||
$a->strings['Email'] = 'Courriel';
|
||||
$a->strings['Friendica Item'] = 'Élément de Friendica';
|
||||
$a->strings['Upstream'] = 'En amont';
|
||||
$a->strings['URI'] = 'URI';
|
||||
$a->strings['Local'] = 'Local';
|
||||
$a->strings['Enabled'] = 'Activer';
|
||||
$a->strings['Email Address'] = 'Adresse de courriel';
|
||||
|
|
|
@ -10,7 +10,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: friendica\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-02-01 18:15+0100\n"
|
||||
"POT-Creation-Date: 2023-06-03 15:50-0400\n"
|
||||
"PO-Revision-Date: 2014-06-23 10:26+0000\n"
|
||||
"Last-Translator: Aditoo, 2018\n"
|
||||
"Language-Team: Czech (http://app.transifex.com/Friendica/friendica/language/cs/)\n"
|
||||
|
@ -29,48 +29,48 @@ msgid "Tips for New Members"
|
|||
msgstr "Tipy pro nové členy"
|
||||
|
||||
#: newmemberwidget.php:33
|
||||
msgid "Global Support Forum"
|
||||
msgstr "Globální fórum podpory"
|
||||
msgid "Global Support Group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:37
|
||||
msgid "Local Support Forum"
|
||||
msgstr "Místní fórum podpory"
|
||||
msgid "Local Support Group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:65
|
||||
#: newmemberwidget.php:62
|
||||
msgid "Save Settings"
|
||||
msgstr "Uložit nastavení"
|
||||
|
||||
#: newmemberwidget.php:66
|
||||
#: newmemberwidget.php:63
|
||||
msgid "Message"
|
||||
msgstr "Zpráva"
|
||||
|
||||
#: newmemberwidget.php:66
|
||||
#: newmemberwidget.php:63
|
||||
msgid "Your message for new members. You can use bbcode here."
|
||||
msgstr "Vaše zpráva pro nové členy. Zde můžete použít BBCode."
|
||||
|
||||
#: newmemberwidget.php:67
|
||||
msgid "Add a link to global support forum"
|
||||
msgstr "Přidejte odkaz na globální fórum podpory"
|
||||
#: newmemberwidget.php:64
|
||||
msgid "Add a link to global support group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:67
|
||||
msgid "Should a link to the global support forum be displayed?"
|
||||
msgstr "Má být zobrazen odkaz na globální fórum podpory?"
|
||||
#: newmemberwidget.php:64
|
||||
msgid "Should a link to the global support group be displayed?"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:68
|
||||
msgid "Add a link to the local support forum"
|
||||
msgstr "Přidejte odkaz na místní fórum podpory"
|
||||
#: newmemberwidget.php:65
|
||||
msgid "Add a link to the local support group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:68
|
||||
#: newmemberwidget.php:65
|
||||
msgid ""
|
||||
"If you have a local support forum and want to have a link displayed in the "
|
||||
"If you have a local support group and want to have a link displayed in the "
|
||||
"widget, check this box."
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:69
|
||||
#: newmemberwidget.php:66
|
||||
msgid "Name of the local support group"
|
||||
msgstr "Název místního fóra podpory"
|
||||
|
||||
#: newmemberwidget.php:69
|
||||
#: newmemberwidget.php:66
|
||||
msgid ""
|
||||
"If you checked the above, specify the <em>nickname</em> of the local support"
|
||||
" group here (i.e. helpers)"
|
||||
|
|
|
@ -7,13 +7,8 @@ function string_plural_select_cs($n){
|
|||
}}
|
||||
$a->strings['New Member'] = 'Nový člen';
|
||||
$a->strings['Tips for New Members'] = 'Tipy pro nové členy';
|
||||
$a->strings['Global Support Forum'] = 'Globální fórum podpory';
|
||||
$a->strings['Local Support Forum'] = 'Místní fórum podpory';
|
||||
$a->strings['Save Settings'] = 'Uložit nastavení';
|
||||
$a->strings['Message'] = 'Zpráva';
|
||||
$a->strings['Your message for new members. You can use bbcode here.'] = 'Vaše zpráva pro nové členy. Zde můžete použít BBCode.';
|
||||
$a->strings['Add a link to global support forum'] = 'Přidejte odkaz na globální fórum podpory';
|
||||
$a->strings['Should a link to the global support forum be displayed?'] = 'Má být zobrazen odkaz na globální fórum podpory?';
|
||||
$a->strings['Add a link to the local support forum'] = 'Přidejte odkaz na místní fórum podpory';
|
||||
$a->strings['Name of the local support group'] = 'Název místního fóra podpory';
|
||||
$a->strings['If you checked the above, specify the <em>nickname</em> of the local support group here (i.e. helpers)'] = 'Pokud jste zaškrtl/a výše uvedenou možnost, specifikujte zde <em>přezdívku</em> místní skupiny podpory (např. pomocnici)';
|
||||
|
|
|
@ -4,15 +4,16 @@
|
|||
#
|
||||
#
|
||||
# Translators:
|
||||
# Raroun, 2023
|
||||
# Tobias Diekershoff <tobias.diekershoff@gmx.net>, 2021
|
||||
# Ulf Rompe <transifex.com@rompe.org>, 2019
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: friendica\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-02-01 18:15+0100\n"
|
||||
"POT-Creation-Date: 2023-06-03 15:50-0400\n"
|
||||
"PO-Revision-Date: 2014-06-23 10:26+0000\n"
|
||||
"Last-Translator: Tobias Diekershoff <tobias.diekershoff@gmx.net>, 2021\n"
|
||||
"Last-Translator: Raroun, 2023\n"
|
||||
"Language-Team: German (http://app.transifex.com/Friendica/friendica/language/de/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
@ -29,48 +30,48 @@ msgid "Tips for New Members"
|
|||
msgstr "Tipps für neue Nutzer"
|
||||
|
||||
#: newmemberwidget.php:33
|
||||
msgid "Global Support Forum"
|
||||
msgstr "Globales Forum für Hilfsanfragen"
|
||||
msgid "Global Support Group"
|
||||
msgstr "Globale Support-Gruppe"
|
||||
|
||||
#: newmemberwidget.php:37
|
||||
msgid "Local Support Forum"
|
||||
msgstr "Lokales Forum für Hilfsanfragen"
|
||||
msgid "Local Support Group"
|
||||
msgstr "Lokale Support-Gruppe"
|
||||
|
||||
#: newmemberwidget.php:65
|
||||
#: newmemberwidget.php:62
|
||||
msgid "Save Settings"
|
||||
msgstr "Einstellungen speichern"
|
||||
|
||||
#: newmemberwidget.php:66
|
||||
#: newmemberwidget.php:63
|
||||
msgid "Message"
|
||||
msgstr "Nachricht"
|
||||
|
||||
#: newmemberwidget.php:66
|
||||
#: newmemberwidget.php:63
|
||||
msgid "Your message for new members. You can use bbcode here."
|
||||
msgstr "Deine Nachricht für neue Nutzer. BBCode kann verwendet werden."
|
||||
|
||||
#: newmemberwidget.php:67
|
||||
msgid "Add a link to global support forum"
|
||||
msgstr "Link zum globalen Support-Forum anzeigen"
|
||||
#: newmemberwidget.php:64
|
||||
msgid "Add a link to global support group"
|
||||
msgstr "Fügen Sie einen Link der globalen Support-Gruppe hinzu"
|
||||
|
||||
#: newmemberwidget.php:67
|
||||
msgid "Should a link to the global support forum be displayed?"
|
||||
msgstr "Soll ein Link zum globalen Support-Forum angezeigt werden?"
|
||||
#: newmemberwidget.php:64
|
||||
msgid "Should a link to the global support group be displayed?"
|
||||
msgstr "Soll ein Link zur globalen Support-Gruppe angezeigt werden?"
|
||||
|
||||
#: newmemberwidget.php:68
|
||||
msgid "Add a link to the local support forum"
|
||||
msgstr "Link zum lokalen Support-Forum anzeigen"
|
||||
#: newmemberwidget.php:65
|
||||
msgid "Add a link to the local support group"
|
||||
msgstr "Fügen Sie einen Link der lokalen Support-Gruppe hinzu"
|
||||
|
||||
#: newmemberwidget.php:68
|
||||
#: newmemberwidget.php:65
|
||||
msgid ""
|
||||
"If you have a local support forum and want to have a link displayed in the "
|
||||
"If you have a local support group and want to have a link displayed in the "
|
||||
"widget, check this box."
|
||||
msgstr "Wenn du ein lokales Support-Forum eingerichtet hast und ein Link darauf angezeigt werden soll, schalte dies ein."
|
||||
msgstr "Wenn Sie eine lokale Support-Gruppe haben und einen Link im Widget anzeigen lassen möchten, markieren Sie dieses Feld."
|
||||
|
||||
#: newmemberwidget.php:69
|
||||
#: newmemberwidget.php:66
|
||||
msgid "Name of the local support group"
|
||||
msgstr "Name des lokalen Support-Forums"
|
||||
|
||||
#: newmemberwidget.php:69
|
||||
#: newmemberwidget.php:66
|
||||
msgid ""
|
||||
"If you checked the above, specify the <em>nickname</em> of the local support"
|
||||
" group here (i.e. helpers)"
|
||||
|
|
|
@ -7,14 +7,14 @@ function string_plural_select_de($n){
|
|||
}}
|
||||
$a->strings['New Member'] = 'Neue Nutzer';
|
||||
$a->strings['Tips for New Members'] = 'Tipps für neue Nutzer';
|
||||
$a->strings['Global Support Forum'] = 'Globales Forum für Hilfsanfragen';
|
||||
$a->strings['Local Support Forum'] = 'Lokales Forum für Hilfsanfragen';
|
||||
$a->strings['Global Support Group'] = 'Globale Support-Gruppe';
|
||||
$a->strings['Local Support Group'] = 'Lokale Support-Gruppe';
|
||||
$a->strings['Save Settings'] = 'Einstellungen speichern';
|
||||
$a->strings['Message'] = 'Nachricht';
|
||||
$a->strings['Your message for new members. You can use bbcode here.'] = 'Deine Nachricht für neue Nutzer. BBCode kann verwendet werden.';
|
||||
$a->strings['Add a link to global support forum'] = 'Link zum globalen Support-Forum anzeigen';
|
||||
$a->strings['Should a link to the global support forum be displayed?'] = 'Soll ein Link zum globalen Support-Forum angezeigt werden?';
|
||||
$a->strings['Add a link to the local support forum'] = 'Link zum lokalen Support-Forum anzeigen';
|
||||
$a->strings['If you have a local support forum and want to have a link displayed in the widget, check this box.'] = 'Wenn du ein lokales Support-Forum eingerichtet hast und ein Link darauf angezeigt werden soll, schalte dies ein.';
|
||||
$a->strings['Add a link to global support group'] = 'Fügen Sie einen Link der globalen Support-Gruppe hinzu';
|
||||
$a->strings['Should a link to the global support group be displayed?'] = 'Soll ein Link zur globalen Support-Gruppe angezeigt werden?';
|
||||
$a->strings['Add a link to the local support group'] = 'Fügen Sie einen Link der lokalen Support-Gruppe hinzu';
|
||||
$a->strings['If you have a local support group and want to have a link displayed in the widget, check this box.'] = 'Wenn Sie eine lokale Support-Gruppe haben und einen Link im Widget anzeigen lassen möchten, markieren Sie dieses Feld.';
|
||||
$a->strings['Name of the local support group'] = 'Name des lokalen Support-Forums';
|
||||
$a->strings['If you checked the above, specify the <em>nickname</em> of the local support group here (i.e. helpers)'] = 'Wenn der Link angezeigt werden soll, dann trage hier den <em>Spitznamen</em> des Forums ein (z.B. helpers)';
|
||||
|
|
|
@ -10,15 +10,15 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: friendica\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-02-01 18:15+0100\n"
|
||||
"PO-Revision-Date: 2021-04-06 01:46+0000\n"
|
||||
"Last-Translator: Senex Petrovic <javierruizo@hotmail.com>\n"
|
||||
"Language-Team: Spanish (http://www.transifex.com/Friendica/friendica/language/es/)\n"
|
||||
"POT-Creation-Date: 2023-06-03 15:50-0400\n"
|
||||
"PO-Revision-Date: 2014-06-23 10:26+0000\n"
|
||||
"Last-Translator: Senex Petrovic <javierruizo@hotmail.com>, 2021\n"
|
||||
"Language-Team: Spanish (http://app.transifex.com/Friendica/friendica/language/es/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: es\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
||||
|
||||
#: newmemberwidget.php:29
|
||||
msgid "New Member"
|
||||
|
@ -29,48 +29,48 @@ msgid "Tips for New Members"
|
|||
msgstr "Consejos para Nuevos Miembros"
|
||||
|
||||
#: newmemberwidget.php:33
|
||||
msgid "Global Support Forum"
|
||||
msgstr "Foro de Soporte Global"
|
||||
msgid "Global Support Group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:37
|
||||
msgid "Local Support Forum"
|
||||
msgstr "Foro de Soporte Local"
|
||||
msgid "Local Support Group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:65
|
||||
#: newmemberwidget.php:62
|
||||
msgid "Save Settings"
|
||||
msgstr "Guardar Ajustes"
|
||||
|
||||
#: newmemberwidget.php:66
|
||||
#: newmemberwidget.php:63
|
||||
msgid "Message"
|
||||
msgstr "Mensaje"
|
||||
|
||||
#: newmemberwidget.php:66
|
||||
#: newmemberwidget.php:63
|
||||
msgid "Your message for new members. You can use bbcode here."
|
||||
msgstr "Su mensaje para los nuevos miembros. Puede usar bbcode aquí"
|
||||
|
||||
#: newmemberwidget.php:67
|
||||
msgid "Add a link to global support forum"
|
||||
msgstr "Añadir un enlace al foro de soporte global"
|
||||
#: newmemberwidget.php:64
|
||||
msgid "Add a link to global support group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:67
|
||||
msgid "Should a link to the global support forum be displayed?"
|
||||
msgstr "¿Debería mostrarse un enlace al foro de soporte global?"
|
||||
#: newmemberwidget.php:64
|
||||
msgid "Should a link to the global support group be displayed?"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:68
|
||||
msgid "Add a link to the local support forum"
|
||||
msgstr "Añadir un enlace al foro de soporte local"
|
||||
#: newmemberwidget.php:65
|
||||
msgid "Add a link to the local support group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:68
|
||||
#: newmemberwidget.php:65
|
||||
msgid ""
|
||||
"If you have a local support forum and want to have a link displayed in the "
|
||||
"If you have a local support group and want to have a link displayed in the "
|
||||
"widget, check this box."
|
||||
msgstr "Si tiene foro de soporte local y desea que se muestre un enlace en el widget, marque esta casilla."
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:69
|
||||
#: newmemberwidget.php:66
|
||||
msgid "Name of the local support group"
|
||||
msgstr "Nombre del grupo de soporte local"
|
||||
|
||||
#: newmemberwidget.php:69
|
||||
#: newmemberwidget.php:66
|
||||
msgid ""
|
||||
"If you checked the above, specify the <em>nickname</em> of the local support"
|
||||
" group here (i.e. helpers)"
|
||||
|
|
|
@ -3,18 +3,12 @@
|
|||
if(! function_exists("string_plural_select_es")) {
|
||||
function string_plural_select_es($n){
|
||||
$n = intval($n);
|
||||
return intval($n != 1);
|
||||
if ($n == 1) { return 0; } else if ($n != 0 && $n % 1000000 == 0) { return 1; } else { return 2; }
|
||||
}}
|
||||
$a->strings['New Member'] = 'Nuevo Miembro';
|
||||
$a->strings['Tips for New Members'] = 'Consejos para Nuevos Miembros';
|
||||
$a->strings['Global Support Forum'] = 'Foro de Soporte Global';
|
||||
$a->strings['Local Support Forum'] = 'Foro de Soporte Local';
|
||||
$a->strings['Save Settings'] = 'Guardar Ajustes';
|
||||
$a->strings['Message'] = 'Mensaje';
|
||||
$a->strings['Your message for new members. You can use bbcode here.'] = 'Su mensaje para los nuevos miembros. Puede usar bbcode aquí';
|
||||
$a->strings['Add a link to global support forum'] = 'Añadir un enlace al foro de soporte global';
|
||||
$a->strings['Should a link to the global support forum be displayed?'] = '¿Debería mostrarse un enlace al foro de soporte global?';
|
||||
$a->strings['Add a link to the local support forum'] = 'Añadir un enlace al foro de soporte local';
|
||||
$a->strings['If you have a local support forum and want to have a link displayed in the widget, check this box.'] = 'Si tiene foro de soporte local y desea que se muestre un enlace en el widget, marque esta casilla.';
|
||||
$a->strings['Name of the local support group'] = 'Nombre del grupo de soporte local';
|
||||
$a->strings['If you checked the above, specify the <em>nickname</em> of the local support group here (i.e. helpers)'] = 'Si chequeó arriba, especifique el <em>apodo</em> del grupo de soporte local aquí (asistentes)';
|
||||
|
|
|
@ -9,67 +9,67 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: friendica\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-06-01 14:12+0200\n"
|
||||
"PO-Revision-Date: 2019-04-16 05:07+0000\n"
|
||||
"Last-Translator: Rain Hawk\n"
|
||||
"Language-Team: Estonian (http://www.transifex.com/Friendica/friendica/language/et/)\n"
|
||||
"POT-Creation-Date: 2023-06-03 15:50-0400\n"
|
||||
"PO-Revision-Date: 2014-06-23 10:26+0000\n"
|
||||
"Last-Translator: Rain Hawk, 2019\n"
|
||||
"Language-Team: Estonian (http://app.transifex.com/Friendica/friendica/language/et/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: et\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: newmemberwidget.php:21
|
||||
#: newmemberwidget.php:29
|
||||
msgid "New Member"
|
||||
msgstr "Uus liige"
|
||||
|
||||
#: newmemberwidget.php:22
|
||||
#: newmemberwidget.php:30
|
||||
msgid "Tips for New Members"
|
||||
msgstr "Nippe uutele liikmetele"
|
||||
|
||||
#: newmemberwidget.php:24
|
||||
msgid "Global Support Forum"
|
||||
msgstr "Globaalne tugifoorum"
|
||||
#: newmemberwidget.php:33
|
||||
msgid "Global Support Group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:26
|
||||
msgid "Local Support Forum"
|
||||
msgstr "Lokaalne tugifoorum"
|
||||
#: newmemberwidget.php:37
|
||||
msgid "Local Support Group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:49
|
||||
#: newmemberwidget.php:62
|
||||
msgid "Save Settings"
|
||||
msgstr "Salvesta sätted"
|
||||
|
||||
#: newmemberwidget.php:50
|
||||
#: newmemberwidget.php:63
|
||||
msgid "Message"
|
||||
msgstr "Sõnum"
|
||||
|
||||
#: newmemberwidget.php:50
|
||||
#: newmemberwidget.php:63
|
||||
msgid "Your message for new members. You can use bbcode here."
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:51
|
||||
msgid "Add a link to global support forum"
|
||||
#: newmemberwidget.php:64
|
||||
msgid "Add a link to global support group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:51
|
||||
msgid "Should a link to the global support forum be displayed?"
|
||||
#: newmemberwidget.php:64
|
||||
msgid "Should a link to the global support group be displayed?"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:52
|
||||
msgid "Add a link to the local support forum"
|
||||
#: newmemberwidget.php:65
|
||||
msgid "Add a link to the local support group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:52
|
||||
#: newmemberwidget.php:65
|
||||
msgid ""
|
||||
"If you have a local support forum and wand to have a link displayed in the "
|
||||
"If you have a local support group and want to have a link displayed in the "
|
||||
"widget, check this box."
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:53
|
||||
#: newmemberwidget.php:66
|
||||
msgid "Name of the local support group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:53
|
||||
#: newmemberwidget.php:66
|
||||
msgid ""
|
||||
"If you checked the above, specify the <em>nickname</em> of the local support"
|
||||
" group here (i.e. helpers)"
|
||||
|
|
|
@ -7,7 +7,5 @@ function string_plural_select_et($n){
|
|||
}}
|
||||
$a->strings['New Member'] = 'Uus liige';
|
||||
$a->strings['Tips for New Members'] = 'Nippe uutele liikmetele';
|
||||
$a->strings['Global Support Forum'] = 'Globaalne tugifoorum';
|
||||
$a->strings['Local Support Forum'] = 'Lokaalne tugifoorum';
|
||||
$a->strings['Save Settings'] = 'Salvesta sätted';
|
||||
$a->strings['Message'] = 'Sõnum';
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#
|
||||
#
|
||||
# Translators:
|
||||
# Florent C., 2023
|
||||
# Hypolite Petovan <hypolite@mrpetovan.com>, 2022
|
||||
# Nicolas Derive, 2022
|
||||
# StefOfficiel <pichard.stephane@free.fr>, 2015
|
||||
|
@ -11,10 +12,10 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: friendica\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-02-01 18:15+0100\n"
|
||||
"POT-Creation-Date: 2023-06-03 15:50-0400\n"
|
||||
"PO-Revision-Date: 2014-06-23 10:26+0000\n"
|
||||
"Last-Translator: Nicolas Derive, 2022\n"
|
||||
"Language-Team: French (http://www.transifex.com/Friendica/friendica/language/fr/)\n"
|
||||
"Last-Translator: Florent C., 2023\n"
|
||||
"Language-Team: French (http://app.transifex.com/Friendica/friendica/language/fr/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
@ -30,48 +31,48 @@ msgid "Tips for New Members"
|
|||
msgstr "Conseils aux nouveaux venus"
|
||||
|
||||
#: newmemberwidget.php:33
|
||||
msgid "Global Support Forum"
|
||||
msgstr "Forum de support global"
|
||||
msgid "Global Support Group"
|
||||
msgstr "Groupe de support global"
|
||||
|
||||
#: newmemberwidget.php:37
|
||||
msgid "Local Support Forum"
|
||||
msgstr "Forum de support local"
|
||||
msgid "Local Support Group"
|
||||
msgstr "Groupe de support local"
|
||||
|
||||
#: newmemberwidget.php:65
|
||||
#: newmemberwidget.php:62
|
||||
msgid "Save Settings"
|
||||
msgstr "Enregistrer les paramètres"
|
||||
|
||||
#: newmemberwidget.php:66
|
||||
#: newmemberwidget.php:63
|
||||
msgid "Message"
|
||||
msgstr "Message"
|
||||
|
||||
#: newmemberwidget.php:66
|
||||
#: newmemberwidget.php:63
|
||||
msgid "Your message for new members. You can use bbcode here."
|
||||
msgstr "Votre messages aux nouveaux venus. Vous pouvez utiliser des BBCodes."
|
||||
|
||||
#: newmemberwidget.php:67
|
||||
msgid "Add a link to global support forum"
|
||||
msgstr "Ajouter un lien vers le forum de support global"
|
||||
#: newmemberwidget.php:64
|
||||
msgid "Add a link to global support group"
|
||||
msgstr "Ajouter un lien vers le groupe de support global"
|
||||
|
||||
#: newmemberwidget.php:67
|
||||
msgid "Should a link to the global support forum be displayed?"
|
||||
msgstr "Montrer un lien vers le forum de support global?"
|
||||
#: newmemberwidget.php:64
|
||||
msgid "Should a link to the global support group be displayed?"
|
||||
msgstr "Montrer un lien vers le groupe de support global ?"
|
||||
|
||||
#: newmemberwidget.php:68
|
||||
msgid "Add a link to the local support forum"
|
||||
msgstr "Ajouter un lien vers le forum de support local"
|
||||
#: newmemberwidget.php:65
|
||||
msgid "Add a link to the local support group"
|
||||
msgstr "Ajouter un lien vers le groupe de support local"
|
||||
|
||||
#: newmemberwidget.php:68
|
||||
#: newmemberwidget.php:65
|
||||
msgid ""
|
||||
"If you have a local support forum and want to have a link displayed in the "
|
||||
"If you have a local support group and want to have a link displayed in the "
|
||||
"widget, check this box."
|
||||
msgstr "Si vous avez un forum d'assistance local et désirez avoir un lien affiché dans l'appliquette/widget, cochez cette case."
|
||||
msgstr "Si vous avez un groupe de support local et désirez avoir un lien affiché dans l'appliquette/widget, cochez cette case."
|
||||
|
||||
#: newmemberwidget.php:69
|
||||
#: newmemberwidget.php:66
|
||||
msgid "Name of the local support group"
|
||||
msgstr "Nom du groupe de support local"
|
||||
|
||||
#: newmemberwidget.php:69
|
||||
#: newmemberwidget.php:66
|
||||
msgid ""
|
||||
"If you checked the above, specify the <em>nickname</em> of the local support"
|
||||
" group here (i.e. helpers)"
|
||||
|
|
|
@ -7,14 +7,14 @@ function string_plural_select_fr($n){
|
|||
}}
|
||||
$a->strings['New Member'] = 'Nouveau Membre';
|
||||
$a->strings['Tips for New Members'] = 'Conseils aux nouveaux venus';
|
||||
$a->strings['Global Support Forum'] = 'Forum de support global';
|
||||
$a->strings['Local Support Forum'] = 'Forum de support local';
|
||||
$a->strings['Global Support Group'] = 'Groupe de support global';
|
||||
$a->strings['Local Support Group'] = 'Groupe de support local';
|
||||
$a->strings['Save Settings'] = 'Enregistrer les paramètres';
|
||||
$a->strings['Message'] = 'Message';
|
||||
$a->strings['Your message for new members. You can use bbcode here.'] = 'Votre messages aux nouveaux venus. Vous pouvez utiliser des BBCodes.';
|
||||
$a->strings['Add a link to global support forum'] = 'Ajouter un lien vers le forum de support global';
|
||||
$a->strings['Should a link to the global support forum be displayed?'] = 'Montrer un lien vers le forum de support global?';
|
||||
$a->strings['Add a link to the local support forum'] = 'Ajouter un lien vers le forum de support local';
|
||||
$a->strings['If you have a local support forum and want to have a link displayed in the widget, check this box.'] = 'Si vous avez un forum d\'assistance local et désirez avoir un lien affiché dans l\'appliquette/widget, cochez cette case.';
|
||||
$a->strings['Add a link to global support group'] = 'Ajouter un lien vers le groupe de support global';
|
||||
$a->strings['Should a link to the global support group be displayed?'] = 'Montrer un lien vers le groupe de support global ?';
|
||||
$a->strings['Add a link to the local support group'] = 'Ajouter un lien vers le groupe de support local';
|
||||
$a->strings['If you have a local support group and want to have a link displayed in the widget, check this box.'] = 'Si vous avez un groupe de support local et désirez avoir un lien affiché dans l\'appliquette/widget, cochez cette case.';
|
||||
$a->strings['Name of the local support group'] = 'Nom du groupe de support local';
|
||||
$a->strings['If you checked the above, specify the <em>nickname</em> of the local support group here (i.e. helpers)'] = 'Si vous avez coché la case ci-dessus, spécifiez le <em>nom d\'utilisateur</em> du groupe de support local (par ex. "helpers")';
|
||||
|
|
|
@ -4,15 +4,15 @@
|
|||
#
|
||||
#
|
||||
# Translators:
|
||||
# Balázs Úr, 2020-2021
|
||||
# Balázs Úr, 2020-2021,2023
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: friendica\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-02-01 18:15+0100\n"
|
||||
"POT-Creation-Date: 2023-06-03 15:50-0400\n"
|
||||
"PO-Revision-Date: 2014-06-23 10:26+0000\n"
|
||||
"Last-Translator: Balázs Úr, 2020-2021\n"
|
||||
"Language-Team: Hungarian (http://www.transifex.com/Friendica/friendica/language/hu/)\n"
|
||||
"Last-Translator: Balázs Úr, 2020-2021,2023\n"
|
||||
"Language-Team: Hungarian (http://app.transifex.com/Friendica/friendica/language/hu/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
@ -28,48 +28,48 @@ msgid "Tips for New Members"
|
|||
msgstr "Tippek új tagoknak"
|
||||
|
||||
#: newmemberwidget.php:33
|
||||
msgid "Global Support Forum"
|
||||
msgstr "Globális támogató fórum"
|
||||
msgid "Global Support Group"
|
||||
msgstr "Globális támogatási csoport"
|
||||
|
||||
#: newmemberwidget.php:37
|
||||
msgid "Local Support Forum"
|
||||
msgstr "Helyi támogató fórum"
|
||||
msgid "Local Support Group"
|
||||
msgstr "Helyi támogatási csoport"
|
||||
|
||||
#: newmemberwidget.php:65
|
||||
#: newmemberwidget.php:62
|
||||
msgid "Save Settings"
|
||||
msgstr "Beállítások mentése"
|
||||
|
||||
#: newmemberwidget.php:66
|
||||
#: newmemberwidget.php:63
|
||||
msgid "Message"
|
||||
msgstr "Üzenet"
|
||||
|
||||
#: newmemberwidget.php:66
|
||||
#: newmemberwidget.php:63
|
||||
msgid "Your message for new members. You can use bbcode here."
|
||||
msgstr "Az Ön üzenete az új tagoknak. Itt használhat BBCode-ot."
|
||||
|
||||
#: newmemberwidget.php:67
|
||||
msgid "Add a link to global support forum"
|
||||
msgstr "A globális támogató fórumra mutató hivatkozás hozzáadása"
|
||||
#: newmemberwidget.php:64
|
||||
msgid "Add a link to global support group"
|
||||
msgstr "A globális támogatási csoportra mutató hivatkozás hozzáadása"
|
||||
|
||||
#: newmemberwidget.php:67
|
||||
msgid "Should a link to the global support forum be displayed?"
|
||||
msgstr "Meg kell jeleníteni a globális támogató fórumra mutató hivatkozást?"
|
||||
#: newmemberwidget.php:64
|
||||
msgid "Should a link to the global support group be displayed?"
|
||||
msgstr "Meg kell jeleníteni a globális támogatási csoportra mutató hivatkozást?"
|
||||
|
||||
#: newmemberwidget.php:68
|
||||
msgid "Add a link to the local support forum"
|
||||
msgstr "A helyi támogató fórumra mutató hivatkozás hozzáadása"
|
||||
#: newmemberwidget.php:65
|
||||
msgid "Add a link to the local support group"
|
||||
msgstr "A helyi támogatási csoportra mutató hivatkozás hozzáadása"
|
||||
|
||||
#: newmemberwidget.php:68
|
||||
#: newmemberwidget.php:65
|
||||
msgid ""
|
||||
"If you have a local support forum and want to have a link displayed in the "
|
||||
"If you have a local support group and want to have a link displayed in the "
|
||||
"widget, check this box."
|
||||
msgstr "Ha van helyi támogató fóruma és szeretne egy hivatkozást megjeleníteni a felületi elemben, akkor jelölje be azt a négyzetet."
|
||||
msgstr "Ha van helyi támogatási csoportja és meg szeretne jeleníteni egy hivatkozást a felületi elemben, akkor jelölje be ezt a négyzetet."
|
||||
|
||||
#: newmemberwidget.php:69
|
||||
#: newmemberwidget.php:66
|
||||
msgid "Name of the local support group"
|
||||
msgstr "A helyi támogató csoport neve"
|
||||
|
||||
#: newmemberwidget.php:69
|
||||
#: newmemberwidget.php:66
|
||||
msgid ""
|
||||
"If you checked the above, specify the <em>nickname</em> of the local support"
|
||||
" group here (i.e. helpers)"
|
||||
|
|
|
@ -7,14 +7,14 @@ function string_plural_select_hu($n){
|
|||
}}
|
||||
$a->strings['New Member'] = 'Új tag';
|
||||
$a->strings['Tips for New Members'] = 'Tippek új tagoknak';
|
||||
$a->strings['Global Support Forum'] = 'Globális támogató fórum';
|
||||
$a->strings['Local Support Forum'] = 'Helyi támogató fórum';
|
||||
$a->strings['Global Support Group'] = 'Globális támogatási csoport';
|
||||
$a->strings['Local Support Group'] = 'Helyi támogatási csoport';
|
||||
$a->strings['Save Settings'] = 'Beállítások mentése';
|
||||
$a->strings['Message'] = 'Üzenet';
|
||||
$a->strings['Your message for new members. You can use bbcode here.'] = 'Az Ön üzenete az új tagoknak. Itt használhat BBCode-ot.';
|
||||
$a->strings['Add a link to global support forum'] = 'A globális támogató fórumra mutató hivatkozás hozzáadása';
|
||||
$a->strings['Should a link to the global support forum be displayed?'] = 'Meg kell jeleníteni a globális támogató fórumra mutató hivatkozást?';
|
||||
$a->strings['Add a link to the local support forum'] = 'A helyi támogató fórumra mutató hivatkozás hozzáadása';
|
||||
$a->strings['If you have a local support forum and want to have a link displayed in the widget, check this box.'] = 'Ha van helyi támogató fóruma és szeretne egy hivatkozást megjeleníteni a felületi elemben, akkor jelölje be azt a négyzetet.';
|
||||
$a->strings['Add a link to global support group'] = 'A globális támogatási csoportra mutató hivatkozás hozzáadása';
|
||||
$a->strings['Should a link to the global support group be displayed?'] = 'Meg kell jeleníteni a globális támogatási csoportra mutató hivatkozást?';
|
||||
$a->strings['Add a link to the local support group'] = 'A helyi támogatási csoportra mutató hivatkozás hozzáadása';
|
||||
$a->strings['If you have a local support group and want to have a link displayed in the widget, check this box.'] = 'Ha van helyi támogatási csoportja és meg szeretne jeleníteni egy hivatkozást a felületi elemben, akkor jelölje be ezt a négyzetet.';
|
||||
$a->strings['Name of the local support group'] = 'A helyi támogató csoport neve';
|
||||
$a->strings['If you checked the above, specify the <em>nickname</em> of the local support group here (i.e. helpers)'] = 'Ha bejelölte a fentit, akkor itt adja meg a helyi támogató csoport <em>becenevét</em> (például segítők)';
|
||||
|
|
|
@ -10,7 +10,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: friendica\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-02-01 18:15+0100\n"
|
||||
"POT-Creation-Date: 2023-06-03 15:50-0400\n"
|
||||
"PO-Revision-Date: 2014-06-23 10:26+0000\n"
|
||||
"Last-Translator: Sylke Vicious <silkevicious@gmail.com>, 2020-2021\n"
|
||||
"Language-Team: Italian (http://app.transifex.com/Friendica/friendica/language/it/)\n"
|
||||
|
@ -29,48 +29,48 @@ msgid "Tips for New Members"
|
|||
msgstr "Consigli per i Nuovi Utenti"
|
||||
|
||||
#: newmemberwidget.php:33
|
||||
msgid "Global Support Forum"
|
||||
msgstr "Forum Globale di Supporto"
|
||||
msgid "Global Support Group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:37
|
||||
msgid "Local Support Forum"
|
||||
msgstr "Forum Locale di Supporto"
|
||||
msgid "Local Support Group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:65
|
||||
#: newmemberwidget.php:62
|
||||
msgid "Save Settings"
|
||||
msgstr "Salva Impostazioni"
|
||||
|
||||
#: newmemberwidget.php:66
|
||||
#: newmemberwidget.php:63
|
||||
msgid "Message"
|
||||
msgstr "Messaggio"
|
||||
|
||||
#: newmemberwidget.php:66
|
||||
#: newmemberwidget.php:63
|
||||
msgid "Your message for new members. You can use bbcode here."
|
||||
msgstr "Il tuo messaggio per i nuovi utenti. Puoi usare BBCode"
|
||||
|
||||
#: newmemberwidget.php:67
|
||||
msgid "Add a link to global support forum"
|
||||
msgstr "Aggiunge un collegamento al forum di supporto globale"
|
||||
#: newmemberwidget.php:64
|
||||
msgid "Add a link to global support group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:67
|
||||
msgid "Should a link to the global support forum be displayed?"
|
||||
msgstr "Mostrare il collegamento al forum di supporto globale?"
|
||||
#: newmemberwidget.php:64
|
||||
msgid "Should a link to the global support group be displayed?"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:68
|
||||
msgid "Add a link to the local support forum"
|
||||
msgstr "Aggiunge un collegamento al forum di supporto locale"
|
||||
#: newmemberwidget.php:65
|
||||
msgid "Add a link to the local support group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:68
|
||||
#: newmemberwidget.php:65
|
||||
msgid ""
|
||||
"If you have a local support forum and want to have a link displayed in the "
|
||||
"If you have a local support group and want to have a link displayed in the "
|
||||
"widget, check this box."
|
||||
msgstr "Se hai un forum di supporto locale e vuoi che sia mostrato il collegamento nel widget, seleziona questo box."
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:69
|
||||
#: newmemberwidget.php:66
|
||||
msgid "Name of the local support group"
|
||||
msgstr "Nome del gruppo locale di supporto"
|
||||
|
||||
#: newmemberwidget.php:69
|
||||
#: newmemberwidget.php:66
|
||||
msgid ""
|
||||
"If you checked the above, specify the <em>nickname</em> of the local support"
|
||||
" group here (i.e. helpers)"
|
||||
|
|
|
@ -7,14 +7,8 @@ function string_plural_select_it($n){
|
|||
}}
|
||||
$a->strings['New Member'] = 'Nuovi Utenti';
|
||||
$a->strings['Tips for New Members'] = 'Consigli per i Nuovi Utenti';
|
||||
$a->strings['Global Support Forum'] = 'Forum Globale di Supporto';
|
||||
$a->strings['Local Support Forum'] = 'Forum Locale di Supporto';
|
||||
$a->strings['Save Settings'] = 'Salva Impostazioni';
|
||||
$a->strings['Message'] = 'Messaggio';
|
||||
$a->strings['Your message for new members. You can use bbcode here.'] = 'Il tuo messaggio per i nuovi utenti. Puoi usare BBCode';
|
||||
$a->strings['Add a link to global support forum'] = 'Aggiunge un collegamento al forum di supporto globale';
|
||||
$a->strings['Should a link to the global support forum be displayed?'] = 'Mostrare il collegamento al forum di supporto globale?';
|
||||
$a->strings['Add a link to the local support forum'] = 'Aggiunge un collegamento al forum di supporto locale';
|
||||
$a->strings['If you have a local support forum and want to have a link displayed in the widget, check this box.'] = 'Se hai un forum di supporto locale e vuoi che sia mostrato il collegamento nel widget, seleziona questo box.';
|
||||
$a->strings['Name of the local support group'] = 'Nome del gruppo locale di supporto';
|
||||
$a->strings['If you checked the above, specify the <em>nickname</em> of the local support group here (i.e. helpers)'] = 'Se hai selezionato il box sopra, specifica qui il <em>nome utente</em> del gruppo locale di supporto (e.s. \'supporto\')';
|
||||
|
|
|
@ -10,10 +10,10 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: friendica\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-02-01 18:15+0100\n"
|
||||
"POT-Creation-Date: 2023-06-03 15:50-0400\n"
|
||||
"PO-Revision-Date: 2014-06-23 10:26+0000\n"
|
||||
"Last-Translator: Piotr Strębski <strebski@gmail.com>, 2022\n"
|
||||
"Language-Team: Polish (http://www.transifex.com/Friendica/friendica/language/pl/)\n"
|
||||
"Language-Team: Polish (http://app.transifex.com/Friendica/friendica/language/pl/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
@ -29,48 +29,48 @@ msgid "Tips for New Members"
|
|||
msgstr "Wskazówki dla nowych użytkowników"
|
||||
|
||||
#: newmemberwidget.php:33
|
||||
msgid "Global Support Forum"
|
||||
msgstr "Globalne forum pomocy technicznej"
|
||||
msgid "Global Support Group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:37
|
||||
msgid "Local Support Forum"
|
||||
msgstr "Lokalne Forum Wsparcia"
|
||||
msgid "Local Support Group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:65
|
||||
#: newmemberwidget.php:62
|
||||
msgid "Save Settings"
|
||||
msgstr "Zapisz ustawienia"
|
||||
|
||||
#: newmemberwidget.php:66
|
||||
#: newmemberwidget.php:63
|
||||
msgid "Message"
|
||||
msgstr "Wiadomość"
|
||||
|
||||
#: newmemberwidget.php:66
|
||||
#: newmemberwidget.php:63
|
||||
msgid "Your message for new members. You can use bbcode here."
|
||||
msgstr "Twoja wiadomość dla nowych członków. Możesz tutaj użyć bbcode."
|
||||
|
||||
#: newmemberwidget.php:67
|
||||
msgid "Add a link to global support forum"
|
||||
msgstr "Dodaj odnośnik do globalnego forum pomocy technicznej"
|
||||
#: newmemberwidget.php:64
|
||||
msgid "Add a link to global support group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:67
|
||||
msgid "Should a link to the global support forum be displayed?"
|
||||
msgstr "Czy powinien być wyświetlany odnośnik do globalnego forum pomocy technicznej?"
|
||||
#: newmemberwidget.php:64
|
||||
msgid "Should a link to the global support group be displayed?"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:68
|
||||
msgid "Add a link to the local support forum"
|
||||
msgstr "Dodaj odnośnik do lokalnego forum pomocy technicznej"
|
||||
#: newmemberwidget.php:65
|
||||
msgid "Add a link to the local support group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:68
|
||||
#: newmemberwidget.php:65
|
||||
msgid ""
|
||||
"If you have a local support forum and want to have a link displayed in the "
|
||||
"If you have a local support group and want to have a link displayed in the "
|
||||
"widget, check this box."
|
||||
msgstr "Jeżeli masz lokalne wsparcie forum i chcesz mieć łącze wyświetlane w widżecie, zaznacz to pole wyboru."
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:69
|
||||
#: newmemberwidget.php:66
|
||||
msgid "Name of the local support group"
|
||||
msgstr "Nazwa grupy lokalnej pomocy technicznej"
|
||||
|
||||
#: newmemberwidget.php:69
|
||||
#: newmemberwidget.php:66
|
||||
msgid ""
|
||||
"If you checked the above, specify the <em>nickname</em> of the local support"
|
||||
" group here (i.e. helpers)"
|
||||
|
|
|
@ -7,14 +7,8 @@ function string_plural_select_pl($n){
|
|||
}}
|
||||
$a->strings['New Member'] = 'Nowy użytkownik';
|
||||
$a->strings['Tips for New Members'] = 'Wskazówki dla nowych użytkowników';
|
||||
$a->strings['Global Support Forum'] = 'Globalne forum pomocy technicznej';
|
||||
$a->strings['Local Support Forum'] = 'Lokalne Forum Wsparcia';
|
||||
$a->strings['Save Settings'] = 'Zapisz ustawienia';
|
||||
$a->strings['Message'] = 'Wiadomość';
|
||||
$a->strings['Your message for new members. You can use bbcode here.'] = 'Twoja wiadomość dla nowych członków. Możesz tutaj użyć bbcode.';
|
||||
$a->strings['Add a link to global support forum'] = 'Dodaj odnośnik do globalnego forum pomocy technicznej';
|
||||
$a->strings['Should a link to the global support forum be displayed?'] = 'Czy powinien być wyświetlany odnośnik do globalnego forum pomocy technicznej?';
|
||||
$a->strings['Add a link to the local support forum'] = 'Dodaj odnośnik do lokalnego forum pomocy technicznej';
|
||||
$a->strings['If you have a local support forum and want to have a link displayed in the widget, check this box.'] = 'Jeżeli masz lokalne wsparcie forum i chcesz mieć łącze wyświetlane w widżecie, zaznacz to pole wyboru.';
|
||||
$a->strings['Name of the local support group'] = 'Nazwa grupy lokalnej pomocy technicznej';
|
||||
$a->strings['If you checked the above, specify the <em>nickname</em> of the local support group here (i.e. helpers)'] = 'Jeśli zaznaczyłeś powyższe, określ tutaj pseudonim lokalnej grupy wsparcia (np. Pomocnicy)';
|
||||
|
|
|
@ -9,67 +9,67 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: friendica\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-06-01 14:12+0200\n"
|
||||
"PO-Revision-Date: 2020-04-23 14:23+0000\n"
|
||||
"Last-Translator: Alexander An <ravnina@gmail.com>\n"
|
||||
"Language-Team: Russian (http://www.transifex.com/Friendica/friendica/language/ru/)\n"
|
||||
"POT-Creation-Date: 2023-06-03 15:50-0400\n"
|
||||
"PO-Revision-Date: 2014-06-23 10:26+0000\n"
|
||||
"Last-Translator: Alexander An <ravnina@gmail.com>, 2020\n"
|
||||
"Language-Team: Russian (http://app.transifex.com/Friendica/friendica/language/ru/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: ru\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
|
||||
|
||||
#: newmemberwidget.php:21
|
||||
#: newmemberwidget.php:29
|
||||
msgid "New Member"
|
||||
msgstr "Новичок"
|
||||
|
||||
#: newmemberwidget.php:22
|
||||
#: newmemberwidget.php:30
|
||||
msgid "Tips for New Members"
|
||||
msgstr "Советы новичкам"
|
||||
|
||||
#: newmemberwidget.php:24
|
||||
msgid "Global Support Forum"
|
||||
msgstr "Общий форум поддержки"
|
||||
#: newmemberwidget.php:33
|
||||
msgid "Global Support Group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:26
|
||||
msgid "Local Support Forum"
|
||||
msgstr "Местный форум поддержки"
|
||||
#: newmemberwidget.php:37
|
||||
msgid "Local Support Group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:49
|
||||
#: newmemberwidget.php:62
|
||||
msgid "Save Settings"
|
||||
msgstr "Сохранить настройки"
|
||||
|
||||
#: newmemberwidget.php:50
|
||||
#: newmemberwidget.php:63
|
||||
msgid "Message"
|
||||
msgstr "Сообщение"
|
||||
|
||||
#: newmemberwidget.php:50
|
||||
#: newmemberwidget.php:63
|
||||
msgid "Your message for new members. You can use bbcode here."
|
||||
msgstr "Ваше сообщение новичкам. Вы можете использовать BBCode."
|
||||
|
||||
#: newmemberwidget.php:51
|
||||
msgid "Add a link to global support forum"
|
||||
msgstr "Добавить ссылку на общий форум поддержки"
|
||||
#: newmemberwidget.php:64
|
||||
msgid "Add a link to global support group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:51
|
||||
msgid "Should a link to the global support forum be displayed?"
|
||||
msgstr "Показывать ссылку на общий форум поддержки?"
|
||||
#: newmemberwidget.php:64
|
||||
msgid "Should a link to the global support group be displayed?"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:52
|
||||
msgid "Add a link to the local support forum"
|
||||
msgstr "Добавить ссылку на местный форум поддержки"
|
||||
#: newmemberwidget.php:65
|
||||
msgid "Add a link to the local support group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:52
|
||||
#: newmemberwidget.php:65
|
||||
msgid ""
|
||||
"If you have a local support forum and wand to have a link displayed in the "
|
||||
"If you have a local support group and want to have a link displayed in the "
|
||||
"widget, check this box."
|
||||
msgstr "Если у вас есть местный форум поддержки и вы хотите добавить ссылку на него, включите это."
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:53
|
||||
#: newmemberwidget.php:66
|
||||
msgid "Name of the local support group"
|
||||
msgstr "Название местной группы поддержки"
|
||||
|
||||
#: newmemberwidget.php:53
|
||||
#: newmemberwidget.php:66
|
||||
msgid ""
|
||||
"If you checked the above, specify the <em>nickname</em> of the local support"
|
||||
" group here (i.e. helpers)"
|
||||
|
|
|
@ -7,14 +7,8 @@ function string_plural_select_ru($n){
|
|||
}}
|
||||
$a->strings['New Member'] = 'Новичок';
|
||||
$a->strings['Tips for New Members'] = 'Советы новичкам';
|
||||
$a->strings['Global Support Forum'] = 'Общий форум поддержки';
|
||||
$a->strings['Local Support Forum'] = 'Местный форум поддержки';
|
||||
$a->strings['Save Settings'] = 'Сохранить настройки';
|
||||
$a->strings['Message'] = 'Сообщение';
|
||||
$a->strings['Your message for new members. You can use bbcode here.'] = 'Ваше сообщение новичкам. Вы можете использовать BBCode.';
|
||||
$a->strings['Add a link to global support forum'] = 'Добавить ссылку на общий форум поддержки';
|
||||
$a->strings['Should a link to the global support forum be displayed?'] = 'Показывать ссылку на общий форум поддержки?';
|
||||
$a->strings['Add a link to the local support forum'] = 'Добавить ссылку на местный форум поддержки';
|
||||
$a->strings['If you have a local support forum and wand to have a link displayed in the widget, check this box.'] = 'Если у вас есть местный форум поддержки и вы хотите добавить ссылку на него, включите это.';
|
||||
$a->strings['Name of the local support group'] = 'Название местной группы поддержки';
|
||||
$a->strings['If you checked the above, specify the <em>nickname</em> of the local support group here (i.e. helpers)'] = 'Если вы включили настройку выше, укажите <em>ник</em>местной группы поддержки пользователей.';
|
||||
|
|
|
@ -9,10 +9,10 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: friendica\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-02-01 18:15+0100\n"
|
||||
"PO-Revision-Date: 2022-01-16 00:48+0000\n"
|
||||
"Last-Translator: Kristoffer Grundström <lovaren@gmail.com>\n"
|
||||
"Language-Team: Swedish (http://www.transifex.com/Friendica/friendica/language/sv/)\n"
|
||||
"POT-Creation-Date: 2023-06-03 15:50-0400\n"
|
||||
"PO-Revision-Date: 2014-06-23 10:26+0000\n"
|
||||
"Last-Translator: Kristoffer Grundström <lovaren@gmail.com>, 2022\n"
|
||||
"Language-Team: Swedish (http://app.transifex.com/Friendica/friendica/language/sv/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
@ -28,48 +28,48 @@ msgid "Tips for New Members"
|
|||
msgstr "Tips för nya medlemmar"
|
||||
|
||||
#: newmemberwidget.php:33
|
||||
msgid "Global Support Forum"
|
||||
msgid "Global Support Group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:37
|
||||
msgid "Local Support Forum"
|
||||
msgstr "Lokalt hjälpforum"
|
||||
msgid "Local Support Group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:65
|
||||
#: newmemberwidget.php:62
|
||||
msgid "Save Settings"
|
||||
msgstr "Spara inställningar"
|
||||
|
||||
#: newmemberwidget.php:66
|
||||
#: newmemberwidget.php:63
|
||||
msgid "Message"
|
||||
msgstr "Meddelande"
|
||||
|
||||
#: newmemberwidget.php:66
|
||||
#: newmemberwidget.php:63
|
||||
msgid "Your message for new members. You can use bbcode here."
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:67
|
||||
msgid "Add a link to global support forum"
|
||||
#: newmemberwidget.php:64
|
||||
msgid "Add a link to global support group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:67
|
||||
msgid "Should a link to the global support forum be displayed?"
|
||||
#: newmemberwidget.php:64
|
||||
msgid "Should a link to the global support group be displayed?"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:68
|
||||
msgid "Add a link to the local support forum"
|
||||
#: newmemberwidget.php:65
|
||||
msgid "Add a link to the local support group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:68
|
||||
#: newmemberwidget.php:65
|
||||
msgid ""
|
||||
"If you have a local support forum and want to have a link displayed in the "
|
||||
"If you have a local support group and want to have a link displayed in the "
|
||||
"widget, check this box."
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:69
|
||||
#: newmemberwidget.php:66
|
||||
msgid "Name of the local support group"
|
||||
msgstr ""
|
||||
|
||||
#: newmemberwidget.php:69
|
||||
#: newmemberwidget.php:66
|
||||
msgid ""
|
||||
"If you checked the above, specify the <em>nickname</em> of the local support"
|
||||
" group here (i.e. helpers)"
|
||||
|
|
|
@ -7,6 +7,5 @@ function string_plural_select_sv($n){
|
|||
}}
|
||||
$a->strings['New Member'] = 'Ny medlem';
|
||||
$a->strings['Tips for New Members'] = 'Tips för nya medlemmar';
|
||||
$a->strings['Local Support Forum'] = 'Lokalt hjälpforum';
|
||||
$a->strings['Save Settings'] = 'Spara inställningar';
|
||||
$a->strings['Message'] = 'Meddelande';
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#
|
||||
#
|
||||
# Translators:
|
||||
# Florent C., 2023
|
||||
# Nicolas Derive, 2022-2023
|
||||
# StefOfficiel <pichard.stephane@free.fr>, 2015
|
||||
# Vincent Vindarel <vindarel@mailz.org>, 2018
|
||||
|
@ -13,7 +14,7 @@ msgstr ""
|
|||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-12-10 14:42-0500\n"
|
||||
"PO-Revision-Date: 2014-06-23 10:34+0000\n"
|
||||
"Last-Translator: Nicolas Derive, 2022-2023\n"
|
||||
"Last-Translator: Florent C., 2023\n"
|
||||
"Language-Team: French (http://app.transifex.com/Friendica/friendica/language/fr/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
@ -41,7 +42,7 @@ msgstr "Liste de mots-clés - séparés par des virgules - à cacher"
|
|||
msgid ""
|
||||
"Use /expression/ to provide regular expressions, #tag to specfically match "
|
||||
"hashtags (case-insensitive), or regular words (case-sensitive)"
|
||||
msgstr "Utiliser /expression/ pour fournir des expressions régulières, #tag pour correspondre à un mot-dièse (hashtag, insensible à la casse), ou des mots classiques (sensible à la casse)"
|
||||
msgstr "Utiliser /expression/ pour fournir des expressions régulières, #tag pour correspondre à un tag (insensible à la casse), ou des mots classiques (sensible à la casse)"
|
||||
|
||||
#: nsfw.php:72
|
||||
msgid "Content Filter (NSFW and more)"
|
||||
|
@ -55,7 +56,7 @@ msgstr "La compilation de l'expression régulière \"%s\" a échoué"
|
|||
#: nsfw.php:154
|
||||
#, php-format
|
||||
msgid "Filtered tag: %s"
|
||||
msgstr "Tag filtré: %s"
|
||||
msgstr "Tag filtré : %s"
|
||||
|
||||
#: nsfw.php:156
|
||||
#, php-format
|
||||
|
|
|
@ -8,8 +8,8 @@ function string_plural_select_fr($n){
|
|||
$a->strings['This addon searches for specified words/text in posts and collapses them. It can be used to filter content tagged with for instance #NSFW that may be deemed inappropriate at certain times or places, such as being at work. It is also useful for hiding irrelevant or annoying content from direct view.'] = 'Cette extension recherche des mots/textes spécifiés dans les publications et les masque. Elle peut être utilisée pour filtrer le contenu étiqueté par exemple avec #NSFW qui peut être considéré comme inapproprié à certains moments ou endroits, comme par exemple au travail. Elle est aussi utile pour cacher du contenu non pertinent ou ennuyeux d\'une vue directe.';
|
||||
$a->strings['Enable Content filter'] = 'Activer le filtrage de contenu';
|
||||
$a->strings['Comma separated list of keywords to hide'] = 'Liste de mots-clés - séparés par des virgules - à cacher';
|
||||
$a->strings['Use /expression/ to provide regular expressions, #tag to specfically match hashtags (case-insensitive), or regular words (case-sensitive)'] = 'Utiliser /expression/ pour fournir des expressions régulières, #tag pour correspondre à un mot-dièse (hashtag, insensible à la casse), ou des mots classiques (sensible à la casse)';
|
||||
$a->strings['Use /expression/ to provide regular expressions, #tag to specfically match hashtags (case-insensitive), or regular words (case-sensitive)'] = 'Utiliser /expression/ pour fournir des expressions régulières, #tag pour correspondre à un tag (insensible à la casse), ou des mots classiques (sensible à la casse)';
|
||||
$a->strings['Content Filter (NSFW and more)'] = 'Filtre de contenu (NSFW et autres)';
|
||||
$a->strings['Regular expression "%s" fails to compile'] = 'La compilation de l\'expression régulière "%s" a échoué';
|
||||
$a->strings['Filtered tag: %s'] = 'Tag filtré: %s';
|
||||
$a->strings['Filtered tag: %s'] = 'Tag filtré : %s';
|
||||
$a->strings['Filtered word: %s'] = 'Mot filtré: %s';
|
||||
|
|
|
@ -20,4 +20,9 @@
|
|||
width: 100%;
|
||||
margin-top: 25px;
|
||||
font-size: 20px;
|
||||
/* The pageheader box */
|
||||
padding: 20px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 2px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
|
|
@ -13,5 +13,5 @@ $a->strings['Absolute path to your Matomo (Piwik) installation. (without protoco
|
|||
$a->strings['Site ID'] = 'Site ID';
|
||||
$a->strings['Show opt-out cookie link?'] = 'Show opt-out cookie link?';
|
||||
$a->strings['Asynchronous tracking'] = 'Asynchronous tracking';
|
||||
$a->strings['Shortcut path to the script (\'/js/\' instead of \'/piwik.js\')'] = 'Shortcut path to the script (\'/js/\' instead of \'/piwik.js\')';
|
||||
$a->strings['Settings updated.'] = 'Settings updated.';
|
||||
$a->strings["Shortcut path to the script ('/js/' instead of '/piwik.js')"] = "Shortcut path to the script ('/js/' instead of '/piwik.js')";
|
|
@ -4,6 +4,7 @@
|
|||
#
|
||||
#
|
||||
# Translators:
|
||||
# Florent C., 2023
|
||||
# Hypolite Petovan <hypolite@mrpetovan.com>, 2022
|
||||
# Nicolas Derive, 2022
|
||||
# ea1cd8241cb389ffb6f92bc6891eff5d_dc12308 <70dced5587d47e18d88f9298024d96f8_93383>, 2015
|
||||
|
@ -12,23 +13,23 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: friendica\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-02-01 18:15+0100\n"
|
||||
"POT-Creation-Date: 2023-05-01 07:39+0200\n"
|
||||
"PO-Revision-Date: 2014-06-23 11:18+0000\n"
|
||||
"Last-Translator: Nicolas Derive, 2022\n"
|
||||
"Language-Team: French (http://www.transifex.com/Friendica/friendica/language/fr/)\n"
|
||||
"Last-Translator: Florent C., 2023\n"
|
||||
"Language-Team: French (http://app.transifex.com/Friendica/friendica/language/fr/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: fr\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
||||
|
||||
#: piwik.php:87
|
||||
#: piwik.php:96
|
||||
msgid ""
|
||||
"This website is tracked using the <a href='http://www.matomo.org'>Matomo</a>"
|
||||
" analytics tool."
|
||||
msgstr "Ce site Internet utilise <a href='http://www.matomo.org'>Matomo</a> pour mesurer son audience."
|
||||
|
||||
#: piwik.php:90
|
||||
#: piwik.php:99
|
||||
#, php-format
|
||||
msgid ""
|
||||
"If you do not want that your visits are logged in this way you <a "
|
||||
|
@ -36,28 +37,32 @@ msgid ""
|
|||
"visits of the site</a> (opt-out)."
|
||||
msgstr "Si vous ne désirez pas que vos visites soient journalisées de cette manière, vous <a href='%s'>pouvez définir un cookie pour empêcher Matomo/Piwik de surveiller de prochaines visites sur le site</a> (opt-out)"
|
||||
|
||||
#: piwik.php:97
|
||||
#: piwik.php:108
|
||||
msgid "Save Settings"
|
||||
msgstr "Sauvegarder les paramètres"
|
||||
|
||||
#: piwik.php:98
|
||||
#: piwik.php:109
|
||||
msgid "Matomo (Piwik) Base URL"
|
||||
msgstr "URL de base de Matomo (Piwik)"
|
||||
|
||||
#: piwik.php:98
|
||||
#: piwik.php:109
|
||||
msgid ""
|
||||
"Absolute path to your Matomo (Piwik) installation. (without protocol "
|
||||
"(http/s), with trailing slash)"
|
||||
msgstr "Chemin absolu vers votre installation Matomo (Piwik) (sans protocole (http/s), avec un slash à la fin)."
|
||||
|
||||
#: piwik.php:99
|
||||
#: piwik.php:110
|
||||
msgid "Site ID"
|
||||
msgstr "ID du site"
|
||||
|
||||
#: piwik.php:100
|
||||
#: piwik.php:111
|
||||
msgid "Show opt-out cookie link?"
|
||||
msgstr "Montrer le lien d'opt-out pour les cookies ?"
|
||||
|
||||
#: piwik.php:101
|
||||
#: piwik.php:112
|
||||
msgid "Asynchronous tracking"
|
||||
msgstr "Suivi asynchrone"
|
||||
|
||||
#: piwik.php:113
|
||||
msgid "Shortcut path to the script ('/js/' instead of '/piwik.js')"
|
||||
msgstr "Chemin réduit vers le script ('/js/' au lieu de '/piwik.js') "
|
||||
|
|
|
@ -13,3 +13,4 @@ $a->strings['Absolute path to your Matomo (Piwik) installation. (without protoco
|
|||
$a->strings['Site ID'] = 'ID du site';
|
||||
$a->strings['Show opt-out cookie link?'] = 'Montrer le lien d\'opt-out pour les cookies ?';
|
||||
$a->strings['Asynchronous tracking'] = 'Suivi asynchrone';
|
||||
$a->strings['Shortcut path to the script (\'/js/\' instead of \'/piwik.js\')'] = 'Chemin réduit vers le script (\'/js/\' au lieu de \'/piwik.js\') ';
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#
|
||||
#
|
||||
# Translators:
|
||||
# Florent C., 2023
|
||||
# Hypolite Petovan <hypolite@mrpetovan.com>, 2022
|
||||
# ea1cd8241cb389ffb6f92bc6891eff5d_dc12308 <70dced5587d47e18d88f9298024d96f8_93383>, 2015
|
||||
# StefOfficiel <pichard.stephane@free.fr>, 2015
|
||||
|
@ -11,86 +12,86 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: friendica\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-11-21 19:17-0500\n"
|
||||
"POT-Creation-Date: 2023-06-03 15:50-0400\n"
|
||||
"PO-Revision-Date: 2014-06-23 11:30+0000\n"
|
||||
"Last-Translator: Hypolite Petovan <hypolite@mrpetovan.com>, 2022\n"
|
||||
"Language-Team: French (http://www.transifex.com/Friendica/friendica/language/fr/)\n"
|
||||
"Last-Translator: Florent C., 2023\n"
|
||||
"Language-Team: French (http://app.transifex.com/Friendica/friendica/language/fr/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: fr\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
||||
|
||||
#: pumpio.php:57
|
||||
#: pumpio.php:62
|
||||
msgid "Permission denied."
|
||||
msgstr "Permission refusée."
|
||||
|
||||
#: pumpio.php:152
|
||||
#: pumpio.php:156
|
||||
#, php-format
|
||||
msgid "Unable to register the client at the pump.io server '%s'."
|
||||
msgstr "Impossible d'enregistrer le client sur le serveur pump.io \"%s\"."
|
||||
|
||||
#: pumpio.php:192
|
||||
#: pumpio.php:196
|
||||
msgid "You are now authenticated to pumpio."
|
||||
msgstr "Vous êtes maintenant authentifié sur pump.io."
|
||||
|
||||
#: pumpio.php:193
|
||||
#: pumpio.php:197
|
||||
msgid "return to the connector page"
|
||||
msgstr "Retourner à la page du connecteur"
|
||||
|
||||
#: pumpio.php:213
|
||||
#: pumpio.php:217
|
||||
msgid "Post to pumpio"
|
||||
msgstr "Publier sur pump.io"
|
||||
|
||||
#: pumpio.php:237
|
||||
#: pumpio.php:241
|
||||
msgid "Save Settings"
|
||||
msgstr "Sauvegarder les paramètres"
|
||||
|
||||
#: pumpio.php:239
|
||||
#: pumpio.php:243
|
||||
msgid "Delete this preset"
|
||||
msgstr "Supprimer ce préréglage"
|
||||
|
||||
#: pumpio.php:245
|
||||
#: pumpio.php:249
|
||||
msgid "Authenticate your pump.io connection"
|
||||
msgstr "Identifiez votre connexion à pump.io"
|
||||
|
||||
#: pumpio.php:252
|
||||
#: pumpio.php:256
|
||||
msgid "Pump.io servername (without \"http://\" or \"https://\" )"
|
||||
msgstr "Domaine du serveur Pump.io (sans \"http://\" ou \"https://\")"
|
||||
|
||||
#: pumpio.php:253
|
||||
#: pumpio.php:257
|
||||
msgid "Pump.io username (without the servername)"
|
||||
msgstr "Nom d'utilisateur Pump.io (sans le domaine de serveur)"
|
||||
|
||||
#: pumpio.php:254
|
||||
#: pumpio.php:258
|
||||
msgid "Import the remote timeline"
|
||||
msgstr "Importer la timeline distante"
|
||||
msgstr "Importer le flux distant"
|
||||
|
||||
#: pumpio.php:255
|
||||
#: pumpio.php:259
|
||||
msgid "Enable Pump.io Post Addon"
|
||||
msgstr "Activer l'extension Pump.io"
|
||||
|
||||
#: pumpio.php:256
|
||||
#: pumpio.php:260
|
||||
msgid "Post to Pump.io by default"
|
||||
msgstr "Publier sur Pump.io par défaut"
|
||||
|
||||
#: pumpio.php:257
|
||||
#: pumpio.php:261
|
||||
msgid "Should posts be public?"
|
||||
msgstr "Les messages devraient être publiques ?"
|
||||
|
||||
#: pumpio.php:258
|
||||
#: pumpio.php:262
|
||||
msgid "Mirror all public posts"
|
||||
msgstr "Refléter toutes les publications publiques"
|
||||
|
||||
#: pumpio.php:263
|
||||
#: pumpio.php:267
|
||||
msgid "Pump.io Import/Export/Mirror"
|
||||
msgstr "Import/Export/Miroir Pump.io"
|
||||
|
||||
#: pumpio.php:920
|
||||
#: pumpio.php:924
|
||||
msgid "status"
|
||||
msgstr "statut"
|
||||
|
||||
#: pumpio.php:924
|
||||
#: pumpio.php:928
|
||||
#, php-format
|
||||
msgid "%1$s likes %2$s's %3$s"
|
||||
msgstr "%1$s aime lea %3$s de %2$s"
|
||||
|
|
|
@ -15,7 +15,7 @@ $a->strings['Delete this preset'] = 'Supprimer ce préréglage';
|
|||
$a->strings['Authenticate your pump.io connection'] = 'Identifiez votre connexion à pump.io';
|
||||
$a->strings['Pump.io servername (without "http://" or "https://" )'] = 'Domaine du serveur Pump.io (sans "http://" ou "https://")';
|
||||
$a->strings['Pump.io username (without the servername)'] = 'Nom d\'utilisateur Pump.io (sans le domaine de serveur)';
|
||||
$a->strings['Import the remote timeline'] = 'Importer la timeline distante';
|
||||
$a->strings['Import the remote timeline'] = 'Importer le flux distant';
|
||||
$a->strings['Enable Pump.io Post Addon'] = 'Activer l\'extension Pump.io';
|
||||
$a->strings['Post to Pump.io by default'] = 'Publier sur Pump.io par défaut';
|
||||
$a->strings['Should posts be public?'] = 'Les messages devraient être publiques ?';
|
||||
|
|
|
@ -8,32 +8,35 @@
|
|||
"packages": [
|
||||
{
|
||||
"name": "akeeba/s3",
|
||||
"version": "2.0.0",
|
||||
"version": "2.3.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/akeeba/s3.git",
|
||||
"reference": "01520dae1f736555e08efda0ddc1044701bd340a"
|
||||
"reference": "7f5b3e929c93eb02ba24472560c0cbbef735aed9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/akeeba/s3/zipball/01520dae1f736555e08efda0ddc1044701bd340a",
|
||||
"reference": "01520dae1f736555e08efda0ddc1044701bd340a",
|
||||
"url": "https://api.github.com/repos/akeeba/s3/zipball/7f5b3e929c93eb02ba24472560c0cbbef735aed9",
|
||||
"reference": "7f5b3e929c93eb02ba24472560c0cbbef735aed9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-curl": "*",
|
||||
"ext-simplexml": "*",
|
||||
"php": ">=7.1.0 <8.1"
|
||||
"php": ">=7.1.0 <8.4"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/aliasing.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Akeeba\\Engine\\Postproc\\Connector\\S3v4\\": "src"
|
||||
"Akeeba\\S3\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"GPL-3.0+"
|
||||
"GPL-3.0-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
|
@ -48,7 +51,7 @@
|
|||
"keywords": [
|
||||
"s3"
|
||||
],
|
||||
"time": "2020-11-30T14:03:55+00:00"
|
||||
"time": "2023-09-26T11:40:10+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/.idea/
|
||||
/000/
|
||||
/minitest/config.php
|
||||
/minitest/config.*
|
||||
!/minitest/config.dist.php
|
||||
/minitest/tmp
|
||||
/vendor/
|
||||
|
|
|
@ -4,29 +4,53 @@ A compact, dependency-less Amazon S3 API client implementing the most commonly u
|
|||
|
||||
## Why reinvent the wheel
|
||||
|
||||
After having a lot of impossible to debug problems with Amazon's Guzzle-based AWS SDK we decided to roll our own connector for Amazon S3. This is by no means a complete implementation, just a small subset of S3's features which are required by our software. The design goals are simplicity, no external dependencies and low memory footprint.
|
||||
After having a lot of impossible to debug problems with Amazon's Guzzle-based AWS SDK we decided to roll our own connector for Amazon S3. This is by no means a complete implementation, just a small subset of S3's features which are required by our software. The design goals are simplicity, no external dependencies and a low memory footprint.
|
||||
|
||||
This code was originally based on [S3.php written by Donovan Schonknecht](http://undesigned.org.za/2007/10/22/amazon-s3-php-class) which is available under a BSD-like license. This repository no longer reflects the original author's work and should not be confused with it.
|
||||
|
||||
This software is distributed under the GNU General Public License version 3 or, at your option, any later version published by the Free Software Foundation (FSF). In short, it's "GPLv3+".
|
||||
This software is distributed under the GNU General Public License version 3 or, at your option, any later version published by the Free Software Foundation (FSF). In short, it's GPL-3.0-or-later, as noted in composer.json.
|
||||
|
||||
## Important note about version 2
|
||||
## Important notes about version 2
|
||||
|
||||
Akeeba Amazon S3 Connector version 2 has dropped support for PPH 5.3 to 7.0 inclusive. It is only compatible with PHP 7.1 or later, up to and including PHP 8.0.
|
||||
### PHP version support since 2.0
|
||||
|
||||
The most significant change in this version is that all methods use scalar type hints for parameters and return values. This _may_ break existing consumers which relied on implicit type conversion e.g. passing strings containing integer values instead of _actual_ integer values.
|
||||
Akeeba Amazon S3 Connector version 2 has dropped support for PHP 5.3 to 7.0 inclusive.
|
||||
|
||||
The most significant change in this version is that all methods use scalar type hints for parameters and return values. This _may_ break existing consumers which relied on implicit type conversion.
|
||||
|
||||
### Namespace change since 2.3
|
||||
|
||||
Up to and including version 2.2 of the library, the namespace was `\Akeeba\Engine\Postproc\Connector\S3v4`. From version 2.3 of the library the namespace has changed to `\Akeeba\S3`.
|
||||
|
||||
The library automatically registers aliases of the old classes to the new ones, thus ensuring updating the library will not introduce backwards incompatible changes. This is why it's not a major version update. Aliases will remain in place until at least version 3.0 of the library.
|
||||
|
||||
## Using the connector
|
||||
|
||||
You need to define a constant before using or referencing any class in the library:
|
||||
|
||||
```php
|
||||
defined('AKEEBAENGINE') or define('AKEEBAENGINE', 1);
|
||||
```
|
||||
|
||||
All library files have a line similar to
|
||||
|
||||
```php
|
||||
defined('AKEEBAENGINE') or die();
|
||||
```
|
||||
|
||||
to prevent direct access to the libraries files. This is intentional. The primary use case for this library is mass-distributed software which gets installed in a publicly accessible subdirectory of the web root. This line prevents any accidental path disclosure from PHP error messages if someone were to access these files directly on misconfigured servers.
|
||||
|
||||
If you are writing a Joomla extension, especially a plugin or module, please _always_ check if the constant has already been defined before defining it yourself. Thank you!
|
||||
|
||||
### Get a connector object
|
||||
|
||||
```php
|
||||
$configuration = new \Akeeba\Engine\Postproc\Connector\S3v4\Configuration(
|
||||
$configuration = new \Akeeba\S3\Configuration(
|
||||
'YourAmazonAccessKey',
|
||||
'YourAmazonSecretKey'
|
||||
);
|
||||
|
||||
$connector = new \Akeeba\Engine\Postproc\Connector\S3v4\Connector($configuration);
|
||||
$connector = new \Akeeba\S3\Connector($configuration);
|
||||
```
|
||||
|
||||
If you are running inside an Amazon EC2 instance you can fetch temporary credentials from the instance's metadata
|
||||
|
@ -37,7 +61,7 @@ IP hosting the instance's metadata cache service):
|
|||
$role = file_get_contents('http://169.254.169.254/latest/meta-data/iam/security-credentials/');
|
||||
$jsonCredentials = file_get_contents('http://169.254.169.254/latest/meta-data/iam/security-credentials/' . $role);
|
||||
$credentials = json_decode($jsonCredentials, true);
|
||||
$configuration = new \Akeeba\Engine\Postproc\Connector\S3v4\Configuration(
|
||||
$configuration = new \Akeeba\S3\Configuration(
|
||||
$credentials['AccessKeyId'],
|
||||
$credentials['SecretAccessKey'],
|
||||
'v4',
|
||||
|
@ -45,14 +69,14 @@ $configuration = new \Akeeba\Engine\Postproc\Connector\S3v4\Configuration(
|
|||
);
|
||||
$configuration->setToken($credentials['Token']);
|
||||
|
||||
$connector = new \Akeeba\Engine\Postproc\Connector\S3v4\Connector($configuration);
|
||||
$connector = new \Akeeba\S3\Connector($configuration);
|
||||
```
|
||||
|
||||
where `$yourRegion` is the AWS region of your bucket, e.g. `us-east-1`. Please note that we are passing the security
|
||||
token (`$credentials['Token']`) to the Configuration object. This is REQUIRED. The temporary credentials returned by
|
||||
the metadata service won't work without it.
|
||||
|
||||
Also worth noting is that the temporary credentials don't last forever. Check the `$credentials['Expiration']` to see
|
||||
Another point worth noting is that the temporary credentials don't last forever. Check the `$credentials['Expiration']` to see
|
||||
when they are about to expire. Amazon recommends that you retry fetching new credentials from the metadata service
|
||||
10 minutes before your cached credentials are set to expire. The metadata service is guaranteed to provision fresh
|
||||
temporary credentials by that time.
|
||||
|
@ -120,21 +144,21 @@ The last parameter (common prefixes) controls the listing of "subdirectories"
|
|||
From a file:
|
||||
|
||||
```php
|
||||
$input = \Akeeba\Engine\Postproc\Connector\S3v4\Input::createFromFile($sourceFile);
|
||||
$input = \Akeeba\S3\Input::createFromFile($sourceFile);
|
||||
$connector->putObject($input, 'mybucket', 'path/to/myfile.txt');
|
||||
```
|
||||
|
||||
From a string:
|
||||
|
||||
```php
|
||||
$input = \Akeeba\Engine\Postproc\Connector\S3v4\Input::createFromData($sourceString);
|
||||
$input = \Akeeba\S3\Input::createFromData($sourceString);
|
||||
$connector->putObject($input, 'mybucket', 'path/to/myfile.txt');
|
||||
```
|
||||
|
||||
From a stream resource:
|
||||
|
||||
```php
|
||||
$input = \Akeeba\Engine\Postproc\Connector\S3v4\Input::createFromResource($streamHandle, false);
|
||||
$input = \Akeeba\S3\Input::createFromResource($streamHandle, false);
|
||||
$connector->putObject($input, 'mybucket', 'path/to/myfile.txt');
|
||||
```
|
||||
|
||||
|
@ -145,7 +169,7 @@ In all cases the entirety of the file has to be loaded in memory.
|
|||
Files are uploaded in 5Mb chunks.
|
||||
|
||||
```php
|
||||
$input = \Akeeba\Engine\Postproc\Connector\S3v4\Input::createFromFile($sourceFile);
|
||||
$input = \Akeeba\S3\Input::createFromFile($sourceFile);
|
||||
$uploadId = $connector->startMultipart($input, 'mybucket', 'mypath/movie.mov');
|
||||
|
||||
$eTags = array();
|
||||
|
@ -155,7 +179,7 @@ $partNumber = 0;
|
|||
do
|
||||
{
|
||||
// IMPORTANT: You MUST create the input afresh before each uploadMultipart call
|
||||
$input = \Akeeba\Engine\Postproc\Connector\S3v4\Input::createFromFile($sourceFile);
|
||||
$input = \Akeeba\S3\Input::createFromFile($sourceFile);
|
||||
$input->setUploadID($uploadId);
|
||||
$input->setPartNumber(++$partNumber);
|
||||
|
||||
|
@ -169,7 +193,7 @@ do
|
|||
while (!is_null($eTag));
|
||||
|
||||
// IMPORTANT: You MUST create the input afresh before finalising the multipart upload
|
||||
$input = \Akeeba\Engine\Postproc\Connector\S3v4\Input::createFromFile($sourceFile);
|
||||
$input = \Akeeba\S3\Input::createFromFile($sourceFile);
|
||||
$input->setUploadID($uploadId);
|
||||
$input->setEtags($eTags);
|
||||
|
||||
|
@ -209,6 +233,23 @@ $content = $connector->getObject('mybucket', 'path/to/file.jpg', false);
|
|||
$connector->deleteObject('mybucket', 'path/to/file.jpg');
|
||||
```
|
||||
|
||||
### Test if an object exists
|
||||
|
||||
```php
|
||||
try
|
||||
{
|
||||
$headers = $connector->headObject('mybucket', 'path/to/file.jpg');
|
||||
$exists = true;
|
||||
}
|
||||
catch (\Akeeba\S3\Exception\CannotGetFile $e)
|
||||
{
|
||||
$headers = [];
|
||||
$exists = false;
|
||||
}
|
||||
```
|
||||
|
||||
The `$headers` variable contains an array with the S3 headers returned by the [HeadObject(https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html) API call. The header keys are always in lowercase. Please note that _not all_ of the headers Amazon describes in their documentation are returned in every request.
|
||||
|
||||
## Configuration options
|
||||
|
||||
The Configuration option has optional methods which can be used to enable some useful features in the connector.
|
||||
|
@ -216,7 +257,7 @@ The Configuration option has optional methods which can be used to enable some u
|
|||
You need to execute these methods against the Configuration object before passing it to the Connector's constructor. For example:
|
||||
|
||||
```php
|
||||
$configuration = new \Akeeba\Engine\Postproc\Connector\S3v4\Configuration(
|
||||
$configuration = new \Akeeba\S3\Configuration(
|
||||
'YourAmazonAccessKey',
|
||||
'YourAmazonSecretKey'
|
||||
);
|
||||
|
@ -225,7 +266,7 @@ $configuration = new \Akeeba\Engine\Postproc\Connector\S3v4\Configuration(
|
|||
$configuration->setSignatureMethod('v4');
|
||||
$configuration->setUseDualstackUrl(true);
|
||||
|
||||
$connector = new \Akeeba\Engine\Postproc\Connector\S3v4\Connector($configuration);
|
||||
$connector = new \Akeeba\S3\Connector($configuration);
|
||||
```
|
||||
|
||||
### HTTPS vs plain HTTP
|
||||
|
@ -245,7 +286,7 @@ Please note that if the S3-compatible APi uses v4 signatures you need to enter t
|
|||
```php
|
||||
// DigitalOcean Spaces using v4 signatures
|
||||
// The access credentials are those used in the example at https://developers.digitalocean.com/documentation/spaces/
|
||||
$configuration = new \Akeeba\Engine\Postproc\Connector\S3v4\Configuration(
|
||||
$configuration = new \Akeeba\S3\Configuration(
|
||||
'532SZONTQ6ALKBCU94OU',
|
||||
'zCkY83KVDXD8u83RouEYPKEm/dhPSPB45XsfnWj8fxQ',
|
||||
'v4',
|
||||
|
@ -253,7 +294,7 @@ $configuration = new \Akeeba\Engine\Postproc\Connector\S3v4\Configuration(
|
|||
);
|
||||
$configuration->setEndpoint('nyc3.digitaloceanspaces.com');
|
||||
|
||||
$connector = new \Akeeba\Engine\Postproc\Connector\S3v4\Connector($configuration);
|
||||
$connector = new \Akeeba\S3\Connector($configuration);
|
||||
```
|
||||
|
||||
If your S3-compatible API uses v2 signatures you do not need to specify a region.
|
||||
|
@ -261,14 +302,14 @@ If your S3-compatible API uses v2 signatures you do not need to specify a region
|
|||
```php
|
||||
// DigitalOcean Spaces using v2 signatures
|
||||
// The access credentials are those used in the example at https://developers.digitalocean.com/documentation/spaces/
|
||||
$configuration = new \Akeeba\Engine\Postproc\Connector\S3v4\Configuration(
|
||||
$configuration = new \Akeeba\S3\Configuration(
|
||||
'532SZONTQ6ALKBCU94OU',
|
||||
'zCkY83KVDXD8u83RouEYPKEm/dhPSPB45XsfnWj8fxQ',
|
||||
'v2'
|
||||
);
|
||||
$configuration->setEndpoint('nyc3.digitaloceanspaces.com');
|
||||
|
||||
$connector = new \Akeeba\Engine\Postproc\Connector\S3v4\Connector($configuration);
|
||||
$connector = new \Akeeba\S3\Connector($configuration);
|
||||
```
|
||||
|
||||
### Legacy path-style access
|
||||
|
@ -282,7 +323,7 @@ You need to do:
|
|||
$configuration->setUseLegacyPathStyle(true);
|
||||
```
|
||||
|
||||
Caveat: this will not work with v2 signatures if you are using Amazon AWS S3 proper. It will work with the v2 signatures if you are using a custom endpoint, though. In fact, most S3-compatible APIs implementing V2 signatures _expect_ you to use path-style access.
|
||||
Caveat: this will not work with v2 signatures if you are using Amazon AWS S3 proper. It will very likely work with the v2 signatures if you are using a custom endpoint, though.
|
||||
|
||||
### Dualstack (IPv4 and IPv6) support
|
||||
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
Need to check:
|
||||
|
||||
endpoint in [amazon, custom]
|
||||
signature in [v2, v4]
|
||||
path style in [true, false]
|
||||
upload
|
||||
download
|
||||
presigned URL generation
|
||||
presigned URL access
|
||||
|
||||
|
||||
USING VIRTUAL HOSTING, v4 SIGNATURES
|
||||
presigned URL must use s3.amazonaws.com i.e. path-style hosting (because who needs logic?)
|
|
@ -3,7 +3,7 @@
|
|||
"type": "library",
|
||||
"description": "A compact, dependency-less Amazon S3 API client implementing the most commonly used features",
|
||||
"require": {
|
||||
"php": ">=7.1.0 <8.1",
|
||||
"php": ">=7.1.0 <8.4",
|
||||
"ext-curl": "*",
|
||||
"ext-simplexml": "*"
|
||||
},
|
||||
|
@ -11,7 +11,7 @@
|
|||
"s3"
|
||||
],
|
||||
"homepage": "https://github.com/akeeba/s3",
|
||||
"license": "GPL-3.0+",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicholas K. Dionysopoulos",
|
||||
|
@ -22,7 +22,16 @@
|
|||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Akeeba\\Engine\\Postproc\\Connector\\S3v4\\": "src"
|
||||
}
|
||||
"Akeeba\\S3\\": "src"
|
||||
},
|
||||
"files": [
|
||||
"src/aliasing.php"
|
||||
]
|
||||
},
|
||||
"archive": {
|
||||
"exclude": [
|
||||
"minitest",
|
||||
"TODO.md"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "1070071b351d45a80934e854f0725d64",
|
||||
"packages": [],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": ">=5.3.4"
|
||||
},
|
||||
"platform-dev": []
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "27f387a657b2784510b177f73c436346",
|
||||
"packages": [],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": ">=7.1.0 <8.4",
|
||||
"ext-curl": "*",
|
||||
"ext-simplexml": "*"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.3.0"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
# Testing notes
|
||||
|
||||
## Against Amazon S3 proper
|
||||
|
||||
This is the _canonical_ method for testing this library since Amazon S3 proper is the canonical provider of the S3 API (and not all of its quirks are fully documented, we might add).
|
||||
|
||||
Copy `config.dist.php` to `config.php` and enter the connection information to your Amazon S3 or compatible service.
|
||||
|
||||
## Against [LocalStack](https://localstack.cloud)
|
||||
|
||||
This method is very useful for development.
|
||||
|
||||
Install LocalStack [as per their documentation](https://docs.localstack.cloud/getting-started/installation/).
|
||||
|
||||
You will also need to install [`awslocal`](https://github.com/localstack/awscli-local) like so:
|
||||
```php
|
||||
pip install awscli
|
||||
pip install awscli-local
|
||||
```
|
||||
|
||||
Start LocalStack e.g. `localstack start -d`
|
||||
|
||||
Create a new bucket called `test` i.e. `awslocal s3 mk s3://test`
|
||||
|
||||
Copy `config.dist.php` to `config.php` and make the following changes:
|
||||
```php
|
||||
define('DEFAULT_ENDPOINT', 'localhost.localstack.cloud:4566');
|
||||
define('DEFAULT_ACCESS_KEY', 'ANYRANDOMSTRINGWILLDO');
|
||||
define('DEFAULT_SECRET_KEY', 'ThisIsAlwaysIgnoredByLocalStack');
|
||||
define('DEFAULT_REGION', 'us-east-1');
|
||||
define('DEFAULT_BUCKET', 'test');
|
||||
define('DEFAULT_SIGNATURE', 'v4');
|
||||
define('DEFAULT_PATH_ACCESS', true);
|
||||
```
|
||||
|
||||
Note that single- and dualstack tests result in the same URLs for all S3-compatible services, including LocalStack. These tests are essentially duplicates in this use case.
|
||||
|
||||
## Against Wasabi
|
||||
|
||||
Wasabi nominally supports v4 signatures, but their implementation is actually _non-canonical_, as they only read the date from the optional `x-amz-date` header, without falling back to the standard HTTP `Date` header. We have added a workaround for this behaviour which necessitates testing with it.
|
||||
|
||||
Just like with Amazon S3 proper, copy `config.dist.php` to `config.php` and enter the connection information to your Wasabi storage. You will also need to set up the custom endpoint like so:
|
||||
```php
|
||||
define('DEFAULT_ENDPOINT', 's3.eu-central-2.wasabisys.com');
|
||||
```
|
||||
|
||||
**IMPORTANT!** The above endpoint will be different, depending on which region you've created your bucket in. The example above assumes the `eu-central-2` region. If you use the wrong region the tests _will_ fail!
|
||||
|
||||
## Against Synology C2
|
||||
|
||||
Synology C2 is an S3-“compatible” storage service. It is not very “compatible” though, since they implemented Amazon's documentation of the v4 signatures instead of how the v4 signatures work in the real world (yeah, there's a very big difference). While Amazon S3 _in reality_ expects all dates to be formatted as per RFC1123, they document that they expect them to be formatted as per “ISO 8601” and they give their _completely wrong_ interpretation of what the “ISO 8601” format is. Synology did not catch that discrepancy, and they only expect the wrongly formatted dates which is totally NOT what S3 itself expects. Luckily, most third party implementations expect either format because they've caught the discrepancy between documentation and reality, therefore making it possible for us to come up with a viable workaround.
|
||||
|
||||
And that's why we need to test with C2 as well, folks.
|
||||
|
||||
Copy `config.dist.php` to `config.php` and enter the connection information to your Synology S3 service.
|
||||
|
||||
It is very important to note two things:
|
||||
```php
|
||||
define('DEFAULT_ENDPOINT', 'eu-002.s3.synologyc2.net');
|
||||
define('DEFAULT_REGION', 'eu-002');
|
||||
```
|
||||
The endpoint URL is given in the Synology C2 Object Manager, next to each bucket. Note the part before `.s3.`. This is the **region** you need to use with v4 signatures. They do not document this anywhere.
|
|
@ -3,13 +3,13 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\MiniTest\Test;
|
||||
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
|
||||
use Akeeba\S3\Connector;
|
||||
use RuntimeException;
|
||||
|
||||
abstract class AbstractTest
|
||||
|
@ -58,24 +58,24 @@ abstract class AbstractTest
|
|||
*/
|
||||
protected static function createFile(int $size = AbstractTest::SIX_HUNDRED_KB, int $blockSize = self::BLOCK_SIZE, bool $reuseBlock = true)
|
||||
{
|
||||
$tempFilePath = tempnam(self::getTempFolder(), 'as3');
|
||||
$tempFilePath = tempnam(static::getTempFolder(), 'as3');
|
||||
|
||||
if ($tempFilePath === false)
|
||||
{
|
||||
throw new RuntimeException("Cannot create a temporary file.");
|
||||
}
|
||||
|
||||
$fp = @fopen($tempFilePath, 'wb', false);
|
||||
$fp = @fopen($tempFilePath, 'w', false);
|
||||
|
||||
if ($fp === false)
|
||||
{
|
||||
throw new RuntimeException("Cannot write to the temporary file.");
|
||||
}
|
||||
|
||||
$blockSize = self::BLOCK_SIZE;
|
||||
$blockSize = static::BLOCK_SIZE;
|
||||
$lastBlockSize = $size % $blockSize;
|
||||
$wholeBlocks = (int) (($size - $lastBlockSize) / $blockSize);
|
||||
$blockData = self::getRandomData();
|
||||
$blockData = static::getRandomData();
|
||||
|
||||
for ($i = 0; $i < $wholeBlocks; $i++)
|
||||
{
|
||||
|
@ -83,7 +83,7 @@ abstract class AbstractTest
|
|||
|
||||
if (!$reuseBlock)
|
||||
{
|
||||
$blockData = self::getRandomData($blockSize);
|
||||
$blockData = static::getRandomData($blockSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,7 +155,7 @@ abstract class AbstractTest
|
|||
return false;
|
||||
}
|
||||
|
||||
return hash_file(self::FILE_HASHING_ALGORITHM, $referenceFilePath) === hash_file(self::FILE_HASHING_ALGORITHM, $unknownFilePath);
|
||||
return hash_file(static::FILE_HASHING_ALGORITHM, $referenceFilePath) === hash_file(static::FILE_HASHING_ALGORITHM, $unknownFilePath);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,15 +3,15 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\MiniTest\Test;
|
||||
|
||||
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Input;
|
||||
use Akeeba\S3\Connector;
|
||||
use Akeeba\S3\Input;
|
||||
|
||||
/**
|
||||
* Upload, download and delete big files (over 1MB), without multipart uploads. Uses string or file sources.
|
||||
|
@ -51,7 +51,7 @@ class BigFiles extends AbstractTest
|
|||
/**
|
||||
* Number of uploaded chunks.
|
||||
*
|
||||
* This is set by self::upload(). Zero for single part uploads, non-zero for multipart uploads.
|
||||
* This is set by static::upload(). Zero for single part uploads, non-zero for multipart uploads.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
|
@ -59,42 +59,42 @@ class BigFiles extends AbstractTest
|
|||
|
||||
public static function upload5MBString(Connector $s3, array $options): bool
|
||||
{
|
||||
return self::upload($s3, $options, self::FIVE_MB, 'bigtest_5mb.dat');
|
||||
return static::upload($s3, $options, static::FIVE_MB, 'bigtest_5mb.dat');
|
||||
}
|
||||
|
||||
public static function upload6MBString(Connector $s3, array $options): bool
|
||||
{
|
||||
return self::upload($s3, $options, self::SIX_MB, 'bigtest_6mb.dat');
|
||||
return static::upload($s3, $options, static::SIX_MB, 'bigtest_6mb.dat');
|
||||
}
|
||||
|
||||
public static function upload10MBString(Connector $s3, array $options): bool
|
||||
{
|
||||
return self::upload($s3, $options, self::TEN_MB, 'bigtest_10mb.dat');
|
||||
return static::upload($s3, $options, static::TEN_MB, 'bigtest_10mb.dat');
|
||||
}
|
||||
|
||||
public static function upload11MBString(Connector $s3, array $options): bool
|
||||
{
|
||||
return self::upload($s3, $options, self::ELEVEN_MB, 'bigtest_11mb.dat');
|
||||
return static::upload($s3, $options, static::ELEVEN_MB, 'bigtest_11mb.dat');
|
||||
}
|
||||
|
||||
public static function upload5MBFile(Connector $s3, array $options): bool
|
||||
{
|
||||
return self::upload($s3, $options, self::FIVE_MB, 'bigtest_5mb.dat', false);
|
||||
return static::upload($s3, $options, static::FIVE_MB, 'bigtest_5mb.dat', false);
|
||||
}
|
||||
|
||||
public static function upload6MBFile(Connector $s3, array $options): bool
|
||||
{
|
||||
return self::upload($s3, $options, self::SIX_MB, 'bigtest_6mb.dat', false);
|
||||
return static::upload($s3, $options, static::SIX_MB, 'bigtest_6mb.dat', false);
|
||||
}
|
||||
|
||||
public static function upload10MBFile(Connector $s3, array $options): bool
|
||||
{
|
||||
return self::upload($s3, $options, self::TEN_MB, 'bigtest_10mb.dat', false);
|
||||
return static::upload($s3, $options, static::TEN_MB, 'bigtest_10mb.dat', false);
|
||||
}
|
||||
|
||||
public static function upload11MBFile(Connector $s3, array $options): bool
|
||||
{
|
||||
return self::upload($s3, $options, self::ELEVEN_MB, 'bigtest_11mb.dat', false);
|
||||
return static::upload($s3, $options, static::ELEVEN_MB, 'bigtest_11mb.dat', false);
|
||||
}
|
||||
|
||||
protected static function upload(Connector $s3, array $options, int $size, string $uri, bool $useString = true): bool
|
||||
|
@ -103,24 +103,24 @@ class BigFiles extends AbstractTest
|
|||
$dotPos = strrpos($uri, '.');
|
||||
$uri = substr($uri, 0, $dotPos) . '.' . md5(microtime(false)) . substr($uri, $dotPos);
|
||||
|
||||
self::$numberOfChunks = 0;
|
||||
static::$numberOfChunks = 0;
|
||||
|
||||
if ($useString)
|
||||
{
|
||||
$sourceData = self::getRandomData($size);
|
||||
$sourceData = static::getRandomData($size);
|
||||
$input = Input::createFromData($sourceData);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a file with random data
|
||||
$sourceFile = self::createFile($size);
|
||||
$sourceFile = static::createFile($size);
|
||||
$input = Input::createFromFile($sourceFile);
|
||||
}
|
||||
|
||||
// Upload the file. Throws exception if it fails.
|
||||
$bucket = $options['bucket'];
|
||||
|
||||
if (!self::$multipart)
|
||||
if (!static::$multipart)
|
||||
{
|
||||
$s3->putObject($input, $bucket, $uri);
|
||||
}
|
||||
|
@ -149,7 +149,7 @@ class BigFiles extends AbstractTest
|
|||
$input->setEtags($eTags);
|
||||
$input->setPartNumber($partNumber);
|
||||
|
||||
$etag = $s3->uploadMultipart($input, $bucket, $uri, [], self::$uploadChunkSize);
|
||||
$etag = $s3->uploadMultipart($input, $bucket, $uri, [], static::$uploadChunkSize);
|
||||
|
||||
// If the result was null we have no more file parts to process.
|
||||
if (is_null($etag))
|
||||
|
@ -166,7 +166,7 @@ class BigFiles extends AbstractTest
|
|||
$partNumber++;
|
||||
}
|
||||
|
||||
self::$numberOfChunks = count($eTags);
|
||||
static::$numberOfChunks = count($eTags);
|
||||
|
||||
// Finalize the multipart upload. Tells Amazon to construct the file from the uploaded parts.
|
||||
$s3->finalizeMultipart($input, $bucket, $uri);
|
||||
|
@ -176,7 +176,7 @@ class BigFiles extends AbstractTest
|
|||
$result = true;
|
||||
|
||||
// Should I download the file and compare its contents?
|
||||
if (self::$downloadAfter)
|
||||
if (static::$downloadAfter)
|
||||
{
|
||||
if ($useString)
|
||||
{
|
||||
|
@ -184,16 +184,16 @@ class BigFiles extends AbstractTest
|
|||
$downloadedData = $s3->getObject($bucket, $uri);
|
||||
|
||||
// Compare the file contents.
|
||||
$result = self::areStringsEqual($sourceData, $downloadedData);
|
||||
$result = static::areStringsEqual($sourceData, $downloadedData);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Download the data. Throws exception if it fails.
|
||||
$downloadedFile = tempnam(self::getTempFolder(), 'as3');
|
||||
$downloadedFile = tempnam(static::getTempFolder(), 'as3');
|
||||
$s3->getObject($bucket, $uri, $downloadedFile);
|
||||
|
||||
// Compare the file contents.
|
||||
$result = self::areFilesEqual($sourceFile, $downloadedFile);
|
||||
$result = static::areFilesEqual($sourceFile, $downloadedFile);
|
||||
|
||||
@unlink($downloadedFile);
|
||||
}
|
||||
|
@ -206,7 +206,7 @@ class BigFiles extends AbstractTest
|
|||
}
|
||||
|
||||
// Should I delete the remotely stored file?
|
||||
if (self::$deleteRemote)
|
||||
if (static::$deleteRemote)
|
||||
{
|
||||
// Delete the remote file. Throws exception if it fails.
|
||||
$s3->deleteObject($bucket, $uri);
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\MiniTest\Test;
|
||||
|
||||
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
|
||||
use Akeeba\S3\Connector;
|
||||
|
||||
class BucketLocation extends AbstractTest
|
||||
{
|
||||
|
@ -18,7 +18,7 @@ class BucketLocation extends AbstractTest
|
|||
{
|
||||
$location = $s3->getBucketLocation($options['bucket']);
|
||||
|
||||
self::assert($location === $options['region'], "Bucket ‘{$options['bucket']}′ reports being in region ‘{$location}′ instead of expected ‘{$options['region']}′");
|
||||
static::assert($location === $options['region'], "Bucket ‘{$options['bucket']}′ reports being in region ‘{$location}′ instead of expected ‘{$options['region']}′");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\MiniTest\Test;
|
||||
|
||||
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
|
||||
use Akeeba\S3\Connector;
|
||||
use RuntimeException;
|
||||
|
||||
class BucketsList extends AbstractTest
|
||||
|
@ -19,16 +19,16 @@ class BucketsList extends AbstractTest
|
|||
{
|
||||
$buckets = $s3->listBuckets(true);
|
||||
|
||||
self::assert(is_array($buckets), "Detailed buckets list is not an array");
|
||||
self::assert(isset($buckets['owner']), "Detailed buckets list does not list an owner");
|
||||
self::assert(isset($buckets['owner']['id']), "Detailed buckets list does not list an owner's id");
|
||||
self::assert(isset($buckets['owner']['name']), "Detailed buckets list does not list an owner's name");
|
||||
self::assert(isset($buckets['buckets']), "Detailed buckets list does not list any buckets");
|
||||
static::assert(is_array($buckets), "Detailed buckets list is not an array");
|
||||
static::assert(isset($buckets['owner']), "Detailed buckets list does not list an owner");
|
||||
static::assert(isset($buckets['owner']['id']), "Detailed buckets list does not list an owner's id");
|
||||
static::assert(isset($buckets['owner']['name']), "Detailed buckets list does not list an owner's name");
|
||||
static::assert(isset($buckets['buckets']), "Detailed buckets list does not list any buckets");
|
||||
|
||||
foreach ($buckets['buckets'] as $bucketInfo)
|
||||
{
|
||||
self::assert(isset($bucketInfo['name']), "Bucket information does not list a name");
|
||||
self::assert(isset($bucketInfo['time']), "Bucket information does not list a created times");
|
||||
static::assert(isset($bucketInfo['name']), "Bucket information does not list a name");
|
||||
static::assert(isset($bucketInfo['time']), "Bucket information does not list a created times");
|
||||
|
||||
if ($bucketInfo['name'] === $options['bucket'])
|
||||
{
|
||||
|
@ -43,8 +43,8 @@ class BucketsList extends AbstractTest
|
|||
{
|
||||
$buckets = $s3->listBuckets(false);
|
||||
|
||||
self::assert(is_array($buckets), "Simple buckets list is not an array");
|
||||
self::assert(in_array($options['bucket'], $buckets), "Simple buckets list does not include configured bucket ‘{$options['bucket']}′");
|
||||
static::assert(is_array($buckets), "Simple buckets list is not an array");
|
||||
static::assert(in_array($options['bucket'], $buckets), "Simple buckets list does not include configured bucket ‘{$options['bucket']}′");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
/**
|
||||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
|
||||
namespace Akeeba\MiniTest\Test;
|
||||
|
||||
|
||||
use Akeeba\S3\Connector;
|
||||
use Akeeba\S3\Exception\CannotDeleteFile;
|
||||
use Akeeba\S3\Exception\CannotGetFile;
|
||||
use Akeeba\S3\Input;
|
||||
|
||||
class HeadObject extends AbstractTest
|
||||
{
|
||||
public static function testExistingFile(Connector $s3, array $options): bool
|
||||
{
|
||||
$uri = 'head_test.dat';
|
||||
|
||||
// Randomize the name. Required for archive buckets where you cannot overwrite data.
|
||||
$dotPos = strrpos($uri, '.');
|
||||
$uri = substr($uri, 0, $dotPos) . '.' . md5(microtime(false)) . substr($uri, $dotPos);
|
||||
|
||||
// Create a file with random data
|
||||
$sourceFile = static::createFile(AbstractTest::TEN_KB);
|
||||
|
||||
// Upload the file. Throws exception if it fails.
|
||||
$bucket = $options['bucket'];
|
||||
$input = Input::createFromFile($sourceFile);
|
||||
|
||||
$s3->putObject($input, $bucket, $uri);
|
||||
|
||||
$headers = $s3->headObject($bucket, $uri);
|
||||
|
||||
static::assert(isset($headers['size']), 'The returned headers do not contain the object size');
|
||||
static::assert($headers['size'] == AbstractTest::TEN_KB, 'The returned size does not match');
|
||||
|
||||
// Remove the local files
|
||||
@unlink($sourceFile);
|
||||
|
||||
// Delete the remote file. Throws exception if it fails.
|
||||
$s3->deleteObject($bucket, $uri);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function testMissingFile(Connector $s3, array $options): bool
|
||||
{
|
||||
$bucket = $options['bucket'];
|
||||
|
||||
try
|
||||
{
|
||||
$headers = $s3->headObject($bucket, md5(microtime(false)) . '_does_not_exist');
|
||||
}
|
||||
catch (CannotGetFile $e)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -3,16 +3,16 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\MiniTest\Test;
|
||||
|
||||
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Exception\CannotPutFile;
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Input;
|
||||
use Akeeba\S3\Connector;
|
||||
use Akeeba\S3\Exception\CannotPutFile;
|
||||
use Akeeba\S3\Input;
|
||||
|
||||
class ListFiles extends AbstractTest
|
||||
{
|
||||
|
@ -34,9 +34,9 @@ class ListFiles extends AbstractTest
|
|||
|
||||
public static function setup(Connector $s3, array $options): void
|
||||
{
|
||||
$data = self::getRandomData(self::TEN_KB);
|
||||
$data = static::getRandomData(static::TEN_KB);
|
||||
|
||||
foreach (self::$paths as $uri)
|
||||
foreach (static::$paths as $uri)
|
||||
{
|
||||
$input = Input::createFromData($data);
|
||||
try
|
||||
|
@ -52,7 +52,7 @@ class ListFiles extends AbstractTest
|
|||
|
||||
public static function teardown(Connector $s3, array $options): void
|
||||
{
|
||||
foreach (self::$paths as $uri)
|
||||
foreach (static::$paths as $uri)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -69,29 +69,29 @@ class ListFiles extends AbstractTest
|
|||
{
|
||||
$listing = $s3->getBucket($options['bucket'], 'listtest_');
|
||||
|
||||
self::assert(is_array($listing), "The files listing must be an array");
|
||||
self::assert(count($listing) == 3, "I am expecting to see 3 files");
|
||||
static::assert(is_array($listing), "The files listing must be an array");
|
||||
static::assert(count($listing) == 3, "I am expecting to see 3 files");
|
||||
|
||||
// Make sure I have the expected files
|
||||
self::assert(array_key_exists('listtest_one.dat', $listing), "File listtest_one.dat not in listing");
|
||||
self::assert(array_key_exists('listtest_two.dat', $listing), "File listtest_two.dat not in listing");
|
||||
self::assert(array_key_exists('listtest_three.dat', $listing), "File listtest_three.dat not in listing");
|
||||
static::assert(array_key_exists('listtest_one.dat', $listing), "File listtest_one.dat not in listing");
|
||||
static::assert(array_key_exists('listtest_two.dat', $listing), "File listtest_two.dat not in listing");
|
||||
static::assert(array_key_exists('listtest_three.dat', $listing), "File listtest_three.dat not in listing");
|
||||
|
||||
// I must not see the files in subdirectories
|
||||
self::assert(!array_key_exists('listtest_four.dat', $listing), "File listtest_four.dat in listing");
|
||||
self::assert(!array_key_exists('listtest_five.dat', $listing), "File listtest_five.dat in listing");
|
||||
self::assert(!array_key_exists('listtest_six.dat', $listing), "File listtest_six.dat in listing");
|
||||
static::assert(!array_key_exists('listtest_four.dat', $listing), "File listtest_four.dat in listing");
|
||||
static::assert(!array_key_exists('listtest_five.dat', $listing), "File listtest_five.dat in listing");
|
||||
static::assert(!array_key_exists('listtest_six.dat', $listing), "File listtest_six.dat in listing");
|
||||
|
||||
// I must not see the files not matching the prefix I gave
|
||||
self::assert(!array_key_exists('spam.dat', $listing), "File spam.dat in listing");
|
||||
self::assert(!array_key_exists('ham.dat', $listing), "File ham.dat in listing");
|
||||
static::assert(!array_key_exists('spam.dat', $listing), "File spam.dat in listing");
|
||||
static::assert(!array_key_exists('ham.dat', $listing), "File ham.dat in listing");
|
||||
|
||||
foreach ($listing as $fileName => $info)
|
||||
{
|
||||
self::assert(isset($info['name']), "File entries must have a name");
|
||||
self::assert(isset($info['time']), "File entries must have a time");
|
||||
self::assert(isset($info['size']), "File entries must have a size");
|
||||
self::assert(isset($info['hash']), "File entries must have a hash");
|
||||
static::assert(isset($info['name']), "File entries must have a name");
|
||||
static::assert(isset($info['time']), "File entries must have a time");
|
||||
static::assert(isset($info['size']), "File entries must have a size");
|
||||
static::assert(isset($info['hash']), "File entries must have a hash");
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -101,37 +101,37 @@ class ListFiles extends AbstractTest
|
|||
{
|
||||
$listing = $s3->getBucket($options['bucket'], 'listtest_', null, 1);
|
||||
|
||||
self::assert(is_array($listing), "The files listing must be an array");
|
||||
self::assert(count($listing) == 1, sprintf("I am expecting to see 1 file, %s seen", count($listing)));
|
||||
static::assert(is_array($listing), "The files listing must be an array");
|
||||
static::assert(count($listing) == 1, sprintf("I am expecting to see 1 file, %s seen", count($listing)));
|
||||
|
||||
$files = array_keys($listing);
|
||||
$continued = $s3->getBucket($options['bucket'], 'listtest_', array_shift($files));
|
||||
|
||||
self::assert(is_array($continued), "The continued files listing must be an array");
|
||||
self::assert(count($continued) == 2, sprintf("I am expecting to see 2 files, %s seen", count($continued)));
|
||||
static::assert(is_array($continued), "The continued files listing must be an array");
|
||||
static::assert(count($continued) == 2, sprintf("I am expecting to see 2 files, %s seen", count($continued)));
|
||||
|
||||
$listing = array_merge($listing, $continued);
|
||||
|
||||
// Make sure I have the expected files
|
||||
self::assert(array_key_exists('listtest_one.dat', $listing), "File listtest_one.dat not in listing");
|
||||
self::assert(array_key_exists('listtest_two.dat', $listing), "File listtest_two.dat not in listing");
|
||||
self::assert(array_key_exists('listtest_three.dat', $listing), "File listtest_three.dat not in listing");
|
||||
static::assert(array_key_exists('listtest_one.dat', $listing), "File listtest_one.dat not in listing");
|
||||
static::assert(array_key_exists('listtest_two.dat', $listing), "File listtest_two.dat not in listing");
|
||||
static::assert(array_key_exists('listtest_three.dat', $listing), "File listtest_three.dat not in listing");
|
||||
|
||||
// I must not see the files in subdirectories
|
||||
self::assert(!array_key_exists('listtest_four.dat', $listing), "File listtest_four.dat in listing");
|
||||
self::assert(!array_key_exists('listtest_five.dat', $listing), "File listtest_five.dat in listing");
|
||||
self::assert(!array_key_exists('listtest_six.dat', $listing), "File listtest_six.dat in listing");
|
||||
static::assert(!array_key_exists('listtest_four.dat', $listing), "File listtest_four.dat in listing");
|
||||
static::assert(!array_key_exists('listtest_five.dat', $listing), "File listtest_five.dat in listing");
|
||||
static::assert(!array_key_exists('listtest_six.dat', $listing), "File listtest_six.dat in listing");
|
||||
|
||||
// I must not see the files not matching the prefix I gave
|
||||
self::assert(!array_key_exists('spam.dat', $listing), "File spam.dat in listing");
|
||||
self::assert(!array_key_exists('ham.dat', $listing), "File ham.dat in listing");
|
||||
static::assert(!array_key_exists('spam.dat', $listing), "File spam.dat in listing");
|
||||
static::assert(!array_key_exists('ham.dat', $listing), "File ham.dat in listing");
|
||||
|
||||
foreach ($listing as $fileName => $info)
|
||||
{
|
||||
self::assert(isset($info['name']), "File entries must have a name");
|
||||
self::assert(isset($info['time']), "File entries must have a time");
|
||||
self::assert(isset($info['size']), "File entries must have a size");
|
||||
self::assert(isset($info['hash']), "File entries must have a hash");
|
||||
static::assert(isset($info['name']), "File entries must have a name");
|
||||
static::assert(isset($info['time']), "File entries must have a time");
|
||||
static::assert(isset($info['size']), "File entries must have a size");
|
||||
static::assert(isset($info['hash']), "File entries must have a hash");
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -141,30 +141,30 @@ class ListFiles extends AbstractTest
|
|||
{
|
||||
$listing = $s3->getBucket($options['bucket'], 'list_deeper/test_');
|
||||
|
||||
self::assert(is_array($listing), "The files listing must be an array");
|
||||
self::assert(count($listing) == 3, "I am expecting to see 3 files");
|
||||
static::assert(is_array($listing), "The files listing must be an array");
|
||||
static::assert(count($listing) == 3, "I am expecting to see 3 files");
|
||||
|
||||
// Make sure I have the expected files
|
||||
self::assert(array_key_exists('list_deeper/test_one.dat', $listing), "File test_one.dat not in listing");
|
||||
self::assert(array_key_exists('list_deeper/test_two.dat', $listing), "File test_two.dat not in listing");
|
||||
self::assert(array_key_exists('list_deeper/test_three.dat', $listing), "File test_three.dat not in listing");
|
||||
static::assert(array_key_exists('list_deeper/test_one.dat', $listing), "File test_one.dat not in listing");
|
||||
static::assert(array_key_exists('list_deeper/test_two.dat', $listing), "File test_two.dat not in listing");
|
||||
static::assert(array_key_exists('list_deeper/test_three.dat', $listing), "File test_three.dat not in listing");
|
||||
|
||||
// I must not see the files with different prefix
|
||||
self::assert(!array_key_exists('list_deeper/listtest_four.dat', $listing), "File listtest_four.dat in listing");
|
||||
self::assert(!array_key_exists('list_deeper/listtest_five.dat', $listing), "File listtest_five.dat in listing");
|
||||
self::assert(!array_key_exists('list_deeper/listtest_six.dat', $listing), "File listtest_six.dat in listing");
|
||||
self::assert(!array_key_exists('list_deeper/spam.dat', $listing), "File spam.dat in listing");
|
||||
static::assert(!array_key_exists('list_deeper/listtest_four.dat', $listing), "File listtest_four.dat in listing");
|
||||
static::assert(!array_key_exists('list_deeper/listtest_five.dat', $listing), "File listtest_five.dat in listing");
|
||||
static::assert(!array_key_exists('list_deeper/listtest_six.dat', $listing), "File listtest_six.dat in listing");
|
||||
static::assert(!array_key_exists('list_deeper/spam.dat', $listing), "File spam.dat in listing");
|
||||
|
||||
// I must not see the files in subdirectories
|
||||
self::assert(!array_key_exists('list_deeper/listtest_deeper/seven.dat', $listing), "File spam.dat in listing");
|
||||
self::assert(!array_key_exists('list_deeper/listtest_deeper/eight.dat', $listing), "File spam.dat in listing");
|
||||
static::assert(!array_key_exists('list_deeper/listtest_deeper/seven.dat', $listing), "File spam.dat in listing");
|
||||
static::assert(!array_key_exists('list_deeper/listtest_deeper/eight.dat', $listing), "File spam.dat in listing");
|
||||
|
||||
foreach ($listing as $fileName => $info)
|
||||
{
|
||||
self::assert(isset($info['name']), "File entries must have a name");
|
||||
self::assert(isset($info['time']), "File entries must have a time");
|
||||
self::assert(isset($info['size']), "File entries must have a size");
|
||||
self::assert(isset($info['hash']), "File entries must have a hash");
|
||||
static::assert(isset($info['name']), "File entries must have a name");
|
||||
static::assert(isset($info['time']), "File entries must have a time");
|
||||
static::assert(isset($info['size']), "File entries must have a size");
|
||||
static::assert(isset($info['hash']), "File entries must have a hash");
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -174,41 +174,41 @@ class ListFiles extends AbstractTest
|
|||
{
|
||||
$listing = $s3->getBucket($options['bucket'], 'list_deeper/test_', null, 1);
|
||||
|
||||
self::assert(is_array($listing), "The files listing must be an array");
|
||||
self::assert(count($listing) == 1, sprintf("I am expecting to see 1 file, %s seen", count($listing)));
|
||||
static::assert(is_array($listing), "The files listing must be an array");
|
||||
static::assert(count($listing) == 1, sprintf("I am expecting to see 1 file, %s seen", count($listing)));
|
||||
|
||||
$files = array_keys($listing);
|
||||
$continued = $s3->getBucket($options['bucket'], 'list_deeper/test_', array_shift($files));
|
||||
|
||||
self::assert(is_array($continued), "The continued files listing must be an array");
|
||||
self::assert(count($continued) == 2, sprintf("I am expecting to see 2 files, %s seen", count($continued)));
|
||||
static::assert(is_array($continued), "The continued files listing must be an array");
|
||||
static::assert(count($continued) == 2, sprintf("I am expecting to see 2 files, %s seen", count($continued)));
|
||||
|
||||
$listing = array_merge($listing, $continued);
|
||||
|
||||
self::assert(is_array($listing), "The files listing must be an array");
|
||||
self::assert(count($listing) == 3, "I am expecting to see 3 files");
|
||||
static::assert(is_array($listing), "The files listing must be an array");
|
||||
static::assert(count($listing) == 3, "I am expecting to see 3 files");
|
||||
|
||||
// Make sure I have the expected files
|
||||
self::assert(array_key_exists('list_deeper/test_one.dat', $listing), "File test_one.dat not in listing");
|
||||
self::assert(array_key_exists('list_deeper/test_two.dat', $listing), "File test_two.dat not in listing");
|
||||
self::assert(array_key_exists('list_deeper/test_three.dat', $listing), "File test_three.dat not in listing");
|
||||
static::assert(array_key_exists('list_deeper/test_one.dat', $listing), "File test_one.dat not in listing");
|
||||
static::assert(array_key_exists('list_deeper/test_two.dat', $listing), "File test_two.dat not in listing");
|
||||
static::assert(array_key_exists('list_deeper/test_three.dat', $listing), "File test_three.dat not in listing");
|
||||
|
||||
// I must not see the files with different prefix
|
||||
self::assert(!array_key_exists('list_deeper/listtest_four.dat', $listing), "File listtest_four.dat in listing");
|
||||
self::assert(!array_key_exists('list_deeper/listtest_five.dat', $listing), "File listtest_five.dat in listing");
|
||||
self::assert(!array_key_exists('list_deeper/listtest_six.dat', $listing), "File listtest_six.dat in listing");
|
||||
self::assert(!array_key_exists('list_deeper/spam.dat', $listing), "File spam.dat in listing");
|
||||
static::assert(!array_key_exists('list_deeper/listtest_four.dat', $listing), "File listtest_four.dat in listing");
|
||||
static::assert(!array_key_exists('list_deeper/listtest_five.dat', $listing), "File listtest_five.dat in listing");
|
||||
static::assert(!array_key_exists('list_deeper/listtest_six.dat', $listing), "File listtest_six.dat in listing");
|
||||
static::assert(!array_key_exists('list_deeper/spam.dat', $listing), "File spam.dat in listing");
|
||||
|
||||
// I must not see the files in subdirectories
|
||||
self::assert(!array_key_exists('list_deeper/listtest_deeper/seven.dat', $listing), "File spam.dat in listing");
|
||||
self::assert(!array_key_exists('list_deeper/listtest_deeper/eight.dat', $listing), "File spam.dat in listing");
|
||||
static::assert(!array_key_exists('list_deeper/listtest_deeper/seven.dat', $listing), "File spam.dat in listing");
|
||||
static::assert(!array_key_exists('list_deeper/listtest_deeper/eight.dat', $listing), "File spam.dat in listing");
|
||||
|
||||
foreach ($listing as $fileName => $info)
|
||||
{
|
||||
self::assert(isset($info['name']), "File entries must have a name");
|
||||
self::assert(isset($info['time']), "File entries must have a time");
|
||||
self::assert(isset($info['size']), "File entries must have a size");
|
||||
self::assert(isset($info['hash']), "File entries must have a hash");
|
||||
static::assert(isset($info['name']), "File entries must have a name");
|
||||
static::assert(isset($info['time']), "File entries must have a time");
|
||||
static::assert(isset($info['size']), "File entries must have a size");
|
||||
static::assert(isset($info['hash']), "File entries must have a hash");
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -224,42 +224,42 @@ class ListFiles extends AbstractTest
|
|||
*/
|
||||
$listing = $s3->getBucket($options['bucket'], 'list_deeper/listtest_', null, 1);
|
||||
|
||||
self::assert(is_array($listing), "The files listing must be an array");
|
||||
self::assert(count($listing) == 1, sprintf("I am expecting to see 1 files, %s seen", count($listing)));
|
||||
static::assert(is_array($listing), "The files listing must be an array");
|
||||
static::assert(count($listing) == 1, sprintf("I am expecting to see 1 files, %s seen", count($listing)));
|
||||
|
||||
$files = array_keys($listing);
|
||||
$continued = $s3->getBucket($options['bucket'], 'list_deeper/listtest_', array_shift($files));
|
||||
|
||||
self::assert(is_array($continued), "The continued files listing must be an array");
|
||||
self::assert(count($continued) == 2, sprintf("I am expecting to see 2 files, %s seen", count($continued)));
|
||||
static::assert(is_array($continued), "The continued files listing must be an array");
|
||||
static::assert(count($continued) == 2, sprintf("I am expecting to see 2 files, %s seen", count($continued)));
|
||||
|
||||
$listing = array_merge($listing, $continued);
|
||||
|
||||
self::assert(is_array($listing), "The files listing must be an array");
|
||||
self::assert(count($listing) == 3, "I am expecting to see 3 files");
|
||||
static::assert(is_array($listing), "The files listing must be an array");
|
||||
static::assert(count($listing) == 3, "I am expecting to see 3 files");
|
||||
|
||||
// Make sure I have the expected files
|
||||
self::assert(array_key_exists('list_deeper/listtest_four.dat', $listing), "File listtest_four.dat not in listing");
|
||||
self::assert(array_key_exists('list_deeper/listtest_five.dat', $listing), "File listtest_five.dat not in listing");
|
||||
self::assert(array_key_exists('list_deeper/listtest_six.dat', $listing), "File listtest_six.dat not in listing");
|
||||
static::assert(array_key_exists('list_deeper/listtest_four.dat', $listing), "File listtest_four.dat not in listing");
|
||||
static::assert(array_key_exists('list_deeper/listtest_five.dat', $listing), "File listtest_five.dat not in listing");
|
||||
static::assert(array_key_exists('list_deeper/listtest_six.dat', $listing), "File listtest_six.dat not in listing");
|
||||
|
||||
|
||||
// I must not see the files with different prefix
|
||||
self::assert(!array_key_exists('list_deeper/test_one.dat', $listing), "File test_one.dat in listing");
|
||||
self::assert(!array_key_exists('list_deeper/test_two.dat', $listing), "File test_two.dat in listing");
|
||||
self::assert(!array_key_exists('list_deeper/test_three.dat', $listing), "File test_three.dat in listing");
|
||||
self::assert(!array_key_exists('list_deeper/spam.dat', $listing), "File spam.dat in listing");
|
||||
static::assert(!array_key_exists('list_deeper/test_one.dat', $listing), "File test_one.dat in listing");
|
||||
static::assert(!array_key_exists('list_deeper/test_two.dat', $listing), "File test_two.dat in listing");
|
||||
static::assert(!array_key_exists('list_deeper/test_three.dat', $listing), "File test_three.dat in listing");
|
||||
static::assert(!array_key_exists('list_deeper/spam.dat', $listing), "File spam.dat in listing");
|
||||
|
||||
// I must not see the files in subdirectories
|
||||
self::assert(!array_key_exists('list_deeper/listtest_deeper/seven.dat', $listing), "File spam.dat in listing");
|
||||
self::assert(!array_key_exists('list_deeper/listtest_deeper/eight.dat', $listing), "File spam.dat in listing");
|
||||
static::assert(!array_key_exists('list_deeper/listtest_deeper/seven.dat', $listing), "File spam.dat in listing");
|
||||
static::assert(!array_key_exists('list_deeper/listtest_deeper/eight.dat', $listing), "File spam.dat in listing");
|
||||
|
||||
foreach ($listing as $fileName => $info)
|
||||
{
|
||||
self::assert(isset($info['name']), "File entries must have a name");
|
||||
self::assert(isset($info['time']), "File entries must have a time");
|
||||
self::assert(isset($info['size']), "File entries must have a size");
|
||||
self::assert(isset($info['hash']), "File entries must have a hash");
|
||||
static::assert(isset($info['name']), "File entries must have a name");
|
||||
static::assert(isset($info['time']), "File entries must have a time");
|
||||
static::assert(isset($info['size']), "File entries must have a size");
|
||||
static::assert(isset($info['hash']), "File entries must have a hash");
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -269,37 +269,37 @@ class ListFiles extends AbstractTest
|
|||
{
|
||||
$listing = $s3->getBucket($options['bucket'], 'list_deeper/listtest_', null, null, '/', true);
|
||||
|
||||
self::assert(is_array($listing), "The files listing must be an array");
|
||||
self::assert(count($listing) == 4, sprintf("I am expecting to see 4 entries, %s entries seen.", count($listing)));
|
||||
static::assert(is_array($listing), "The files listing must be an array");
|
||||
static::assert(count($listing) == 4, sprintf("I am expecting to see 4 entries, %s entries seen.", count($listing)));
|
||||
|
||||
// Make sure I have the expected files
|
||||
self::assert(array_key_exists('list_deeper/listtest_four.dat', $listing), "File listtest_four.dat not in listing");
|
||||
self::assert(array_key_exists('list_deeper/listtest_five.dat', $listing), "File listtest_five.dat not in listing");
|
||||
self::assert(array_key_exists('list_deeper/listtest_six.dat', $listing), "File listtest_six.dat not in listing");
|
||||
self::assert(array_key_exists('list_deeper/listtest_deeper/', $listing), "Folder listtest_deeper not in listing");
|
||||
static::assert(array_key_exists('list_deeper/listtest_four.dat', $listing), "File listtest_four.dat not in listing");
|
||||
static::assert(array_key_exists('list_deeper/listtest_five.dat', $listing), "File listtest_five.dat not in listing");
|
||||
static::assert(array_key_exists('list_deeper/listtest_six.dat', $listing), "File listtest_six.dat not in listing");
|
||||
static::assert(array_key_exists('list_deeper/listtest_deeper/', $listing), "Folder listtest_deeper not in listing");
|
||||
|
||||
// I must not see the files in subdirectories
|
||||
self::assert(!array_key_exists('list_deeper/listtest_deeper/seven.dat', $listing), "File seven.dat in listing");
|
||||
self::assert(!array_key_exists('list_deeper/listtest_deeper/eight.dat', $listing), "File eight.dat in listing");
|
||||
static::assert(!array_key_exists('list_deeper/listtest_deeper/seven.dat', $listing), "File seven.dat in listing");
|
||||
static::assert(!array_key_exists('list_deeper/listtest_deeper/eight.dat', $listing), "File eight.dat in listing");
|
||||
|
||||
// I must not see the files with different prefix
|
||||
self::assert(!array_key_exists('list_deeper/spam.dat', $listing), "File spam.dat in listing");
|
||||
self::assert(!array_key_exists('list_deeper/test_one.dat', $listing), "File test_one.dat not in listing");
|
||||
self::assert(!array_key_exists('list_deeper/test_two.dat', $listing), "File test_two.dat not in listing");
|
||||
self::assert(!array_key_exists('list_deeper/test_three.dat', $listing), "File test_three.dat not in listing");
|
||||
static::assert(!array_key_exists('list_deeper/spam.dat', $listing), "File spam.dat in listing");
|
||||
static::assert(!array_key_exists('list_deeper/test_one.dat', $listing), "File test_one.dat not in listing");
|
||||
static::assert(!array_key_exists('list_deeper/test_two.dat', $listing), "File test_two.dat not in listing");
|
||||
static::assert(!array_key_exists('list_deeper/test_three.dat', $listing), "File test_three.dat not in listing");
|
||||
|
||||
foreach ($listing as $fileName => $info)
|
||||
{
|
||||
if (substr($fileName, -1) !== '/')
|
||||
{
|
||||
self::assert(isset($info['name']), "File entries must have a name");
|
||||
self::assert(isset($info['time']), "File entries must have a time");
|
||||
self::assert(isset($info['size']), "File entries must have a size");
|
||||
self::assert(isset($info['hash']), "File entries must have a hash");
|
||||
static::assert(isset($info['name']), "File entries must have a name");
|
||||
static::assert(isset($info['time']), "File entries must have a time");
|
||||
static::assert(isset($info['size']), "File entries must have a size");
|
||||
static::assert(isset($info['hash']), "File entries must have a hash");
|
||||
}
|
||||
else
|
||||
{
|
||||
self::assert(isset($info['prefix']), "Folder entries must return a prefix");
|
||||
static::assert(isset($info['prefix']), "Folder entries must return a prefix");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
/**
|
||||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\MiniTest\Test;
|
||||
|
||||
use Akeeba\S3\Connector;
|
||||
use Akeeba\S3\Input;
|
||||
|
||||
class ListThousandsOfFiles extends AbstractTest
|
||||
{
|
||||
private const PATH_PREFIX = 'massive/';
|
||||
|
||||
public static function setup(Connector $s3, array $options): void
|
||||
{
|
||||
if (defined('CREATE_2100_FILES') && CREATE_2100_FILES === false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$data = static::getRandomData(128);
|
||||
|
||||
echo "\nPopulating with 2100 files\n";
|
||||
|
||||
for ($i = 1; $i <= 2100; $i++)
|
||||
{
|
||||
if ($i % 10 === 0)
|
||||
{
|
||||
echo "Uploading from $i...\n";
|
||||
}
|
||||
|
||||
$uri = sprintf('%stest_%04u.dat', static::PATH_PREFIX, $i);
|
||||
$input = Input::createFromData($data);
|
||||
$s3->putObject($input, $options['bucket'], $uri);
|
||||
}
|
||||
}
|
||||
|
||||
public static function testGetAll(Connector $s3, array $options): bool
|
||||
{
|
||||
$listing = $s3->getBucket($options['bucket'], static::PATH_PREFIX);
|
||||
|
||||
static::assert(is_array($listing), "The files listing must be an array");
|
||||
static::assert(count($listing) === 2100, "I am expecting to see 2100 files");
|
||||
|
||||
for ($i = 1; $i <= 2100; $i++)
|
||||
{
|
||||
$key = sprintf('%stest_%04u.dat', static::PATH_PREFIX, $i);
|
||||
|
||||
static::assert(array_key_exists($key, $listing), sprintf('Results should list object %s', $key));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function testGetHundred(Connector $s3, array $options): bool
|
||||
{
|
||||
$listing = $s3->getBucket($options['bucket'], static::PATH_PREFIX, null, 100);
|
||||
|
||||
static::assert(is_array($listing), "The files listing must be an array");
|
||||
static::assert(count($listing) === 100, "I am expecting to see 100 files");
|
||||
|
||||
for ($i = 1; $i <= 100; $i++)
|
||||
{
|
||||
$key = sprintf('%stest_%04u.dat', static::PATH_PREFIX, $i);
|
||||
|
||||
static::assert(array_key_exists($key, $listing), sprintf('Results should list object %s', $key));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function testGetElevenHundred(Connector $s3, array $options): bool
|
||||
{
|
||||
$listing = $s3->getBucket($options['bucket'], static::PATH_PREFIX, null, 1100);
|
||||
|
||||
static::assert(is_array($listing), "The files listing must be an array");
|
||||
static::assert(count($listing) === 1100, "I am expecting to see 1100 files");
|
||||
|
||||
for ($i = 1; $i <= 1100; $i++)
|
||||
{
|
||||
$key = sprintf('%stest_%04u.dat', static::PATH_PREFIX, $i);
|
||||
|
||||
static::assert(array_key_exists($key, $listing), sprintf('Results should list object %s', $key));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function testGetLastHundred(Connector $s3, array $options): bool
|
||||
{
|
||||
$listing = $s3->getBucket($options['bucket'], static::PATH_PREFIX . 'test_20', null);
|
||||
|
||||
static::assert(is_array($listing), "The files listing must be an array");
|
||||
static::assert(count($listing) === 100, "I am expecting to see 100 files");
|
||||
|
||||
for ($i = 2000; $i <= 2099; $i++)
|
||||
{
|
||||
$key = sprintf('%stest_%04u.dat', static::PATH_PREFIX, $i);
|
||||
|
||||
static::assert(array_key_exists($key, $listing), sprintf('Results should list object %s', $key));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,16 +1,22 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\MiniTest\Test;
|
||||
|
||||
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
|
||||
use Akeeba\S3\Connector;
|
||||
|
||||
class Multipart extends BigFiles
|
||||
{
|
||||
public static function setup(Connector $s3, array $options): void
|
||||
{
|
||||
self::$multipart = true;
|
||||
static::$multipart = true;
|
||||
|
||||
parent::setup($s3, $options);
|
||||
}
|
||||
|
@ -20,7 +26,7 @@ class Multipart extends BigFiles
|
|||
$result = parent::upload5MBString($s3, $options);
|
||||
|
||||
$expectedChunks = 1;
|
||||
self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
|
||||
static::assert(static::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, static::$numberOfChunks));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
@ -30,7 +36,7 @@ class Multipart extends BigFiles
|
|||
$result = parent::upload6MBString($s3, $options);
|
||||
|
||||
$expectedChunks = 2;
|
||||
self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
|
||||
static::assert(static::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, static::$numberOfChunks));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
@ -40,7 +46,7 @@ class Multipart extends BigFiles
|
|||
$result = parent::upload10MBString($s3, $options);
|
||||
|
||||
$expectedChunks = 2;
|
||||
self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
|
||||
static::assert(static::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, static::$numberOfChunks));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
@ -50,7 +56,7 @@ class Multipart extends BigFiles
|
|||
$result = parent::upload11MBString($s3, $options);
|
||||
|
||||
$expectedChunks = 3;
|
||||
self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
|
||||
static::assert(static::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, static::$numberOfChunks));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
@ -60,7 +66,7 @@ class Multipart extends BigFiles
|
|||
$result = parent::upload5MBFile($s3, $options);
|
||||
|
||||
$expectedChunks = 1;
|
||||
self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
|
||||
static::assert(static::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, static::$numberOfChunks));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
@ -70,7 +76,7 @@ class Multipart extends BigFiles
|
|||
$result = parent::upload6MBFile($s3, $options);
|
||||
|
||||
$expectedChunks = 2;
|
||||
self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
|
||||
static::assert(static::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, static::$numberOfChunks));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
@ -80,7 +86,7 @@ class Multipart extends BigFiles
|
|||
$result = parent::upload10MBFile($s3, $options);
|
||||
|
||||
$expectedChunks = 2;
|
||||
self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
|
||||
static::assert(static::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, static::$numberOfChunks));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
@ -90,7 +96,7 @@ class Multipart extends BigFiles
|
|||
$result = parent::upload11MBFile($s3, $options);
|
||||
|
||||
$expectedChunks = 3;
|
||||
self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
|
||||
static::assert(static::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, static::$numberOfChunks));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
|
|
@ -3,33 +3,33 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\MiniTest\Test;
|
||||
|
||||
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Acl;
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Input;
|
||||
use Akeeba\S3\Acl;
|
||||
use Akeeba\S3\Connector;
|
||||
use Akeeba\S3\Input;
|
||||
use RuntimeException;
|
||||
|
||||
class SignedURLs extends AbstractTest
|
||||
{
|
||||
public static function signedURLPublicObject(Connector $s3, array $options): bool
|
||||
{
|
||||
return self::signedURL($s3, $options, Acl::ACL_PUBLIC_READ);
|
||||
return static::signedURL($s3, $options, Acl::ACL_PUBLIC_READ);
|
||||
}
|
||||
|
||||
public static function signedURLPrivateObject(Connector $s3, array $options): bool
|
||||
{
|
||||
return self::signedURL($s3, $options, Acl::ACL_PRIVATE);
|
||||
return static::signedURL($s3, $options, Acl::ACL_PRIVATE);
|
||||
}
|
||||
|
||||
private static function signedURL(Connector $s3, array $options, string $aclPrivilege): bool
|
||||
{
|
||||
$tempData = self::getRandomData(AbstractTest::TEN_KB);
|
||||
$tempData = static::getRandomData(AbstractTest::TEN_KB);
|
||||
$input = Input::createFromData($tempData);
|
||||
$uri = 'test.' . md5(microtime(false)) . '.dat';
|
||||
|
||||
|
@ -52,7 +52,7 @@ class SignedURLs extends AbstractTest
|
|||
throw new RuntimeException("Failed to download from signed URL ‘{$downloadURL}′");
|
||||
}
|
||||
|
||||
self::assert(self::areStringsEqual($tempData, $downloadedData), "Wrong data received from signed URL ‘{$downloadURL}′");
|
||||
static::assert(static::areStringsEqual($tempData, $downloadedData), "Wrong data received from signed URL ‘{$downloadURL}′");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
/**
|
||||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\MiniTest\Test;
|
||||
|
||||
use Akeeba\S3\Connector;
|
||||
use Akeeba\S3\Input;
|
||||
|
||||
/**
|
||||
* Upload, download and delete small files (under 1MB) using a string source
|
||||
*
|
||||
* @package Akeeba\MiniTest\Test
|
||||
*/
|
||||
class SingleSmallFile extends AbstractTest
|
||||
{
|
||||
public static function upload(Connector $s3, array $options): bool
|
||||
{
|
||||
$uri = 'test.txt';
|
||||
$sourceData = <<< TEXT
|
||||
This is a small text file.
|
||||
TEXT;
|
||||
|
||||
|
||||
// Upload the data. Throws exception if it fails.
|
||||
$bucket = $options['bucket'];
|
||||
$input = Input::createFromData($sourceData);
|
||||
|
||||
$s3->putObject($input, $bucket, $uri);
|
||||
|
||||
$downloadedData = $s3->getObject($bucket, $uri);
|
||||
$result = static::areStringsEqual($sourceData, $downloadedData);
|
||||
|
||||
$s3->deleteObject($bucket, $uri);
|
||||
|
||||
return $result ?? true;
|
||||
}
|
||||
}
|
|
@ -3,15 +3,15 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\MiniTest\Test;
|
||||
|
||||
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Input;
|
||||
use Akeeba\S3\Connector;
|
||||
use Akeeba\S3\Input;
|
||||
|
||||
/**
|
||||
* Upload, download and delete small files (under 1MB) using a file source
|
||||
|
@ -36,32 +36,32 @@ class SmallFiles extends AbstractTest
|
|||
|
||||
public static function upload10KbRoot(Connector $s3, array $options): bool
|
||||
{
|
||||
return self::upload($s3, $options, AbstractTest::TEN_KB, 'root_10kb.dat');
|
||||
return static::upload($s3, $options, AbstractTest::TEN_KB, 'root_10kb.dat');
|
||||
}
|
||||
|
||||
public static function upload10KbRootGreek(Connector $s3, array $options): bool
|
||||
{
|
||||
return self::upload($s3, $options, AbstractTest::TEN_KB, 'δοκιμή_10kb.dat');
|
||||
return static::upload($s3, $options, AbstractTest::TEN_KB, 'δοκιμή_10kb.dat');
|
||||
}
|
||||
|
||||
public static function upload10KbFolderGreek(Connector $s3, array $options): bool
|
||||
{
|
||||
return self::upload($s3, $options, AbstractTest::TEN_KB, 'ο_φάκελός_μου/δοκιμή_10kb.dat');
|
||||
return static::upload($s3, $options, AbstractTest::TEN_KB, 'ο_φάκελός_μου/δοκιμή_10kb.dat');
|
||||
}
|
||||
|
||||
public static function upload600KbRoot(Connector $s3, array $options): bool
|
||||
{
|
||||
return self::upload($s3, $options, AbstractTest::SIX_HUNDRED_KB, 'root_600kb.dat');
|
||||
return static::upload($s3, $options, AbstractTest::SIX_HUNDRED_KB, 'root_600kb.dat');
|
||||
}
|
||||
|
||||
public static function upload10KbFolder(Connector $s3, array $options): bool
|
||||
{
|
||||
return self::upload($s3, $options, AbstractTest::TEN_KB, 'my_folder/10kb.dat');
|
||||
return static::upload($s3, $options, AbstractTest::TEN_KB, 'my_folder/10kb.dat');
|
||||
}
|
||||
|
||||
public static function upload600KbFolder(Connector $s3, array $options): bool
|
||||
{
|
||||
return self::upload($s3, $options, AbstractTest::SIX_HUNDRED_KB, 'my_folder/600kb.dat');
|
||||
return static::upload($s3, $options, AbstractTest::SIX_HUNDRED_KB, 'my_folder/600kb.dat');
|
||||
}
|
||||
|
||||
protected static function upload(Connector $s3, array $options, int $size, string $uri): bool
|
||||
|
@ -71,7 +71,7 @@ class SmallFiles extends AbstractTest
|
|||
$uri = substr($uri, 0, $dotPos) . '.' . md5(microtime(false)) . substr($uri, $dotPos);
|
||||
|
||||
// Create a file with random data
|
||||
$sourceFile = self::createFile($size);
|
||||
$sourceFile = static::createFile($size);
|
||||
|
||||
// Upload the file. Throws exception if it fails.
|
||||
$bucket = $options['bucket'];
|
||||
|
@ -83,14 +83,14 @@ class SmallFiles extends AbstractTest
|
|||
$result = true;
|
||||
|
||||
// Should I download the file and compare its contents?
|
||||
if (self::$downloadAfter)
|
||||
if (static::$downloadAfter)
|
||||
{
|
||||
// Donwload the data. Throws exception if it fails.
|
||||
$downloadedFile = tempnam(self::getTempFolder(), 'as3');
|
||||
$downloadedFile = tempnam(static::getTempFolder(), 'as3');
|
||||
$s3->getObject($bucket, $uri, $downloadedFile);
|
||||
|
||||
// Compare the file contents.
|
||||
$result = self::areFilesEqual($sourceFile, $downloadedFile);
|
||||
$result = static::areFilesEqual($sourceFile, $downloadedFile);
|
||||
}
|
||||
|
||||
// Remove the local files
|
||||
|
@ -98,7 +98,7 @@ class SmallFiles extends AbstractTest
|
|||
@unlink($downloadedFile);
|
||||
|
||||
// Should I delete the remotely stored file?
|
||||
if (self::$deleteRemote)
|
||||
if (static::$deleteRemote)
|
||||
{
|
||||
// Delete the remote file. Throws exception if it fails.
|
||||
$s3->deleteObject($bucket, $uri);
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\MiniTest\Test;
|
||||
|
||||
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
|
||||
use Akeeba\S3\Connector;
|
||||
|
||||
/**
|
||||
* Upload and download small files (under 1MB) using a file source
|
||||
|
@ -21,7 +21,7 @@ class SmallFilesNoDelete extends SmallFiles
|
|||
{
|
||||
public static function setup(Connector $s3, array $options): void
|
||||
{
|
||||
self::$deleteRemote = false;
|
||||
static::$deleteRemote = false;
|
||||
|
||||
parent::setup($s3, $options);
|
||||
}
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\MiniTest\Test;
|
||||
|
||||
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
|
||||
use Akeeba\S3\Connector;
|
||||
|
||||
/**
|
||||
* Upload small files (under 1MB) using a file source
|
||||
|
@ -21,8 +21,8 @@ class SmallFilesOnlyUpload extends SmallFiles
|
|||
{
|
||||
public static function setup(Connector $s3, array $options): void
|
||||
{
|
||||
self::$deleteRemote = false;
|
||||
self::$downloadAfter = false;
|
||||
static::$deleteRemote = false;
|
||||
static::$downloadAfter = false;
|
||||
|
||||
parent::setup($s3, $options);
|
||||
}
|
||||
|
|
|
@ -3,15 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\MiniTest\Test;
|
||||
|
||||
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Input;
|
||||
use Akeeba\S3\Connector;
|
||||
use Akeeba\S3\Input;
|
||||
|
||||
/**
|
||||
* Upload, download and delete small files (under 1MB) using a string source
|
||||
|
@ -27,7 +26,7 @@ class SmallInlineFiles extends SmallFiles
|
|||
$uri = substr($uri, 0, $dotPos) . '.' . md5(microtime(false)) . substr($uri, $dotPos);
|
||||
|
||||
// Create some random data to upload
|
||||
$sourceData = self::getRandomData($size);
|
||||
$sourceData = static::getRandomData($size);
|
||||
|
||||
// Upload the data. Throws exception if it fails.
|
||||
$bucket = $options['bucket'];
|
||||
|
@ -39,15 +38,15 @@ class SmallInlineFiles extends SmallFiles
|
|||
$result = true;
|
||||
|
||||
// Should I download the file and compare its contents with my random data?
|
||||
if (self::$downloadAfter)
|
||||
if (static::$downloadAfter)
|
||||
{
|
||||
$downloadedData = $s3->getObject($bucket, $uri);
|
||||
|
||||
$result = self::areStringsEqual($sourceData, $downloadedData);
|
||||
$result = static::areStringsEqual($sourceData, $downloadedData);
|
||||
}
|
||||
|
||||
// Should I delete the remotely stored file?
|
||||
if (self::$deleteRemote)
|
||||
if (static::$deleteRemote)
|
||||
{
|
||||
// Delete the remote file. Throws exception if it fails.
|
||||
$s3->deleteObject($bucket, $uri);
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\MiniTest\Test;
|
||||
|
||||
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
|
||||
use Akeeba\S3\Connector;
|
||||
|
||||
/**
|
||||
* Upload and download small files (under 1MB) using a string source
|
||||
|
@ -21,7 +21,7 @@ class SmallInlineFilesNoDelete extends SmallInlineFiles
|
|||
{
|
||||
public static function setup(Connector $s3, array $options): void
|
||||
{
|
||||
self:: $deleteRemote = false;
|
||||
static:: $deleteRemote = false;
|
||||
|
||||
parent::setup($s3, $options);
|
||||
}
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\MiniTest\Test;
|
||||
|
||||
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
|
||||
use Akeeba\S3\Connector;
|
||||
|
||||
/**
|
||||
* Upload small files (under 1MB) using a string source
|
||||
|
@ -21,8 +21,8 @@ class SmallInlineFilesOnlyUpload extends SmallInlineFiles
|
|||
{
|
||||
public static function setup(Connector $s3, array $options): void
|
||||
{
|
||||
self::$deleteRemote = false;
|
||||
self::$downloadAfter = false;
|
||||
static::$deleteRemote = false;
|
||||
static::$downloadAfter = false;
|
||||
|
||||
parent::setup($s3, $options);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
/**
|
||||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\MiniTest\Test;
|
||||
|
||||
|
||||
use Akeeba\S3\Connector;
|
||||
use Akeeba\S3\Input;
|
||||
|
||||
/**
|
||||
* Upload, download and delete small XML files (under 1MB) using a string source
|
||||
*
|
||||
* @package Akeeba\MiniTest\Test
|
||||
*/
|
||||
class SmallInlineXMLFiles extends SmallFiles
|
||||
{
|
||||
public static function upload10KbRoot(Connector $s3, array $options): bool
|
||||
{
|
||||
return static::upload($s3, $options, AbstractTest::TEN_KB, 'root_10kb.xml');
|
||||
}
|
||||
|
||||
public static function upload10KbRootGreek(Connector $s3, array $options): bool
|
||||
{
|
||||
return static::upload($s3, $options, AbstractTest::TEN_KB, 'δοκιμή_10kb.xml');
|
||||
}
|
||||
|
||||
public static function upload10KbFolderGreek(Connector $s3, array $options): bool
|
||||
{
|
||||
return static::upload($s3, $options, AbstractTest::TEN_KB, 'ο_φάκελός_μου/δοκιμή_10kb.xml');
|
||||
}
|
||||
|
||||
public static function upload600KbRoot(Connector $s3, array $options): bool
|
||||
{
|
||||
return static::upload($s3, $options, AbstractTest::SIX_HUNDRED_KB, 'root_600kb.xml');
|
||||
}
|
||||
|
||||
public static function upload10KbFolder(Connector $s3, array $options): bool
|
||||
{
|
||||
return static::upload($s3, $options, AbstractTest::TEN_KB, 'my_folder/10kb.xml');
|
||||
}
|
||||
|
||||
public static function upload600KbFolder(Connector $s3, array $options): bool
|
||||
{
|
||||
return static::upload($s3, $options, AbstractTest::SIX_HUNDRED_KB, 'my_folder/600kb.xml');
|
||||
}
|
||||
|
||||
protected static function upload(Connector $s3, array $options, int $size, string $uri): bool
|
||||
{
|
||||
// Randomize the name. Required for archive buckets where you cannot overwrite data.
|
||||
$dotPos = strrpos($uri, '.');
|
||||
$uri = substr($uri, 0, $dotPos) . '.' . md5(microtime(false)) . substr($uri, $dotPos);
|
||||
|
||||
// Create some random data to upload
|
||||
$sourceData = static::createXMLFile($size);
|
||||
|
||||
// Upload the data. Throws exception if it fails.
|
||||
$bucket = $options['bucket'];
|
||||
$input = Input::createFromData($sourceData);
|
||||
|
||||
$s3->putObject($input, $bucket, $uri);
|
||||
|
||||
// Tentatively accept that this method succeeded.
|
||||
$result = true;
|
||||
|
||||
// Should I download the file and compare its contents with my random data?
|
||||
if (static::$downloadAfter)
|
||||
{
|
||||
$downloadedData = $s3->getObject($bucket, $uri);
|
||||
|
||||
$result = static::areStringsEqual($sourceData, $downloadedData);
|
||||
}
|
||||
|
||||
// Should I delete the remotely stored file?
|
||||
if (static::$deleteRemote)
|
||||
{
|
||||
// Delete the remote file. Throws exception if it fails.
|
||||
$s3->deleteObject($bucket, $uri);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private static function createXMLFile(int $size): string
|
||||
{
|
||||
$out = <<< XML
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<root>
|
||||
XML;
|
||||
|
||||
$chunks = floor(($size - 55) / 1024);
|
||||
|
||||
for ($i = 1; $i <= $chunks; $i++)
|
||||
{
|
||||
$randomBlock = static::genRandomData(1024 - 63);
|
||||
$out .= <<< XML
|
||||
<element>
|
||||
<id>$i</id>
|
||||
<data><![CDATA[$randomBlock]]></data>
|
||||
</element>
|
||||
XML;
|
||||
|
||||
}
|
||||
|
||||
|
||||
$out .= <<< XML
|
||||
</root>
|
||||
XML;
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
private static function genRandomData(int $length): string
|
||||
{
|
||||
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890';
|
||||
$maxLength = strlen($chars) - 1;
|
||||
$salt = '';
|
||||
|
||||
for ($i = 0; $i < $length; $i++)
|
||||
{
|
||||
$salt .= substr($chars, random_int(0, $maxLength), 1);
|
||||
}
|
||||
|
||||
return $salt;
|
||||
}
|
||||
}
|
|
@ -3,17 +3,17 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\MiniTest\Test;
|
||||
|
||||
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Acl;
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Input;
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\StorageClass;
|
||||
use Akeeba\S3\Acl;
|
||||
use Akeeba\S3\Connector;
|
||||
use Akeeba\S3\Input;
|
||||
use Akeeba\S3\StorageClass;
|
||||
|
||||
class StorageClasses extends AbstractTest
|
||||
{
|
||||
|
@ -23,12 +23,12 @@ class StorageClasses extends AbstractTest
|
|||
|
||||
public static function uploadRRS(Connector $s3, array $options): bool
|
||||
{
|
||||
return self::upload($s3, $options, self::TEN_KB, 'rrs_test_10kb.dat', StorageClass::REDUCED_REDUNDANCY);
|
||||
return static::upload($s3, $options, static::TEN_KB, 'rrs_test_10kb.dat', StorageClass::REDUCED_REDUNDANCY);
|
||||
}
|
||||
|
||||
public static function uploadIntelligentTiering(Connector $s3, array $options): bool
|
||||
{
|
||||
return self::upload($s3, $options, self::TEN_KB, 'rrs_test_10kb.dat', StorageClass::INTELLIGENT_TIERING);
|
||||
return static::upload($s3, $options, static::TEN_KB, 'rrs_test_10kb.dat', StorageClass::INTELLIGENT_TIERING);
|
||||
}
|
||||
|
||||
protected static function upload(Connector $s3, array $options, int $size, string $uri, string $storageClass = null)
|
||||
|
@ -38,7 +38,7 @@ class StorageClasses extends AbstractTest
|
|||
$uri = substr($uri, 0, $dotPos) . '.' . md5(microtime(false)) . substr($uri, $dotPos);
|
||||
|
||||
// Create some random data to upload
|
||||
$sourceData = self::getRandomData($size);
|
||||
$sourceData = static::getRandomData($size);
|
||||
|
||||
// Upload the data. Throws exception if it fails.
|
||||
$bucket = $options['bucket'];
|
||||
|
@ -54,15 +54,15 @@ class StorageClasses extends AbstractTest
|
|||
$result = true;
|
||||
|
||||
// Should I download the file and compare its contents with my random data?
|
||||
if (self::$downloadAfter)
|
||||
if (static::$downloadAfter)
|
||||
{
|
||||
$downloadedData = $s3->getObject($bucket, $uri);
|
||||
|
||||
$result = self::areStringsEqual($sourceData, $downloadedData);
|
||||
$result = static::areStringsEqual($sourceData, $downloadedData);
|
||||
}
|
||||
|
||||
// Should I delete the remotely stored file?
|
||||
if (self::$deleteRemote)
|
||||
if (static::$deleteRemote)
|
||||
{
|
||||
// Delete the remote file. Throws exception if it fails.
|
||||
$s3->deleteObject($bucket, $uri);
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
// Custom Endpoint. The example below is for using LocalStack, see https://localstack.cloud/
|
||||
// define('DEFAULT_ENDPOINT', 'localhost.localstack.cloud:4566');
|
||||
// Default Amazon S3 Access Key
|
||||
define('DEFAULT_ACCESS_KEY', 'your s3 access key');
|
||||
// Default Amazon S3 Secret Key
|
||||
|
@ -23,6 +25,8 @@ define('DEFAULT_DUALSTACK', false);
|
|||
define('DEFAULT_PATH_ACCESS', false);
|
||||
// Should I use SSL by default?
|
||||
define('DEFAULT_SSL', true);
|
||||
// Create the 2100 test files in the bucket?
|
||||
define('CREATE_2100_FILES', true);
|
||||
|
||||
/**
|
||||
* Tests for standard key pairs allowing us to read, write and delete
|
||||
|
@ -33,7 +37,9 @@ $standardTests = [
|
|||
'BucketsList',
|
||||
'BucketLocation',
|
||||
'SmallFiles',
|
||||
'HeadObject',
|
||||
'SmallInlineFiles',
|
||||
'SmallInlineXMLFiles',
|
||||
'SignedURLs',
|
||||
'StorageClasses',
|
||||
'ListFiles',
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Configuration;
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Input;
|
||||
use Akeeba\S3\Configuration;
|
||||
use Akeeba\S3\Connector;
|
||||
use Akeeba\S3\Input;
|
||||
|
||||
// Necessary for including the library
|
||||
define('AKEEBAENGINE', 1);
|
||||
|
@ -167,7 +167,7 @@ foreach ($testConfigurations as $description => $setup)
|
|||
'dualstack' => DEFAULT_DUALSTACK,
|
||||
'path_access' => DEFAULT_PATH_ACCESS,
|
||||
'ssl' => DEFAULT_SSL,
|
||||
'endpoint' => null,
|
||||
'endpoint' => defined('DEFAULT_ENDPOINT') ? constant('DEFAULT_ENDPOINT') : null,
|
||||
], $setup['configuration']);
|
||||
|
||||
// Extract the test classes/methods to run
|
||||
|
@ -185,15 +185,21 @@ foreach ($testConfigurations as $description => $setup)
|
|||
|
||||
// Create the S3 configuration object
|
||||
$s3Configuration = new Configuration($configOptions['access'], $configOptions['secret'], $configOptions['signature'], $configOptions['region']);
|
||||
$s3Configuration->setUseDualstackUrl($configOptions['dualstack']);
|
||||
$s3Configuration->setUseLegacyPathStyle($configOptions['path_access']);
|
||||
$s3Configuration->setSSL($configOptions['ssl']);
|
||||
$s3Configuration->setRegion($configOptions['region']);
|
||||
$s3Configuration->setSignatureMethod($configOptions['signature']);
|
||||
|
||||
if (!is_null($configOptions['endpoint']))
|
||||
{
|
||||
$s3Configuration->setEndpoint($configOptions['endpoint']);
|
||||
// We need to redo this because setting the endpoint may reset these options
|
||||
$s3Configuration->setRegion($configOptions['region']);
|
||||
$s3Configuration->setSignatureMethod($configOptions['signature']);
|
||||
}
|
||||
|
||||
$s3Configuration->setUseDualstackUrl($configOptions['dualstack']);
|
||||
$s3Configuration->setUseLegacyPathStyle($configOptions['path_access']);
|
||||
$s3Configuration->setSSL($configOptions['ssl']);
|
||||
|
||||
// Create the connector object
|
||||
$s3Connector = new Connector($s3Configuration);
|
||||
|
||||
|
|
|
@ -3,29 +3,29 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Postproc\Connector\S3v4;
|
||||
namespace Akeeba\S3;
|
||||
|
||||
// Protection against direct access
|
||||
defined('AKEEBAENGINE') or die();
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
/**
|
||||
* Shortcuts to often used access control privileges
|
||||
*/
|
||||
class Acl
|
||||
{
|
||||
const ACL_PRIVATE = 'private';
|
||||
public const ACL_PRIVATE = 'private';
|
||||
|
||||
const ACL_PUBLIC_READ = 'public-read';
|
||||
public const ACL_PUBLIC_READ = 'public-read';
|
||||
|
||||
const ACL_PUBLIC_READ_WRITE = 'public-read-write';
|
||||
public const ACL_PUBLIC_READ_WRITE = 'public-read-write';
|
||||
|
||||
const ACL_AUTHENTICATED_READ = 'authenticated-read';
|
||||
public const ACL_AUTHENTICATED_READ = 'authenticated-read';
|
||||
|
||||
const ACL_BUCKET_OWNER_READ = 'bucket-owner-read';
|
||||
public const ACL_BUCKET_OWNER_READ = 'bucket-owner-read';
|
||||
|
||||
const ACL_BUCKET_OWNER_FULL_CONTROL = 'bucket-owner-full-control';
|
||||
public const ACL_BUCKET_OWNER_FULL_CONTROL = 'bucket-owner-full-control';
|
||||
}
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Postproc\Connector\S3v4;
|
||||
namespace Akeeba\S3;
|
||||
|
||||
// Protection against direct access
|
||||
defined('AKEEBAENGINE') or die();
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
/**
|
||||
* Holds the Amazon S3 confiugration credentials
|
||||
|
@ -199,6 +199,8 @@ class Configuration
|
|||
throw new Exception\InvalidSignatureMethod;
|
||||
}
|
||||
|
||||
$this->signatureMethod = $signatureMethod;
|
||||
|
||||
// If you switch to v2 signatures we unset the region.
|
||||
if ($signatureMethod == 'v2')
|
||||
{
|
||||
|
@ -214,13 +216,7 @@ class Configuration
|
|||
$this->setUseLegacyPathStyle(false);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (empty($this->getRegion())) {
|
||||
$this->setRegion('us-east-1');
|
||||
}
|
||||
}
|
||||
|
||||
$this->signatureMethod = $signatureMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,22 +3,22 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Postproc\Connector\S3v4;
|
||||
namespace Akeeba\S3;
|
||||
|
||||
// Protection against direct access
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Exception\CannotDeleteFile;
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Exception\CannotGetBucket;
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Exception\CannotGetFile;
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Exception\CannotListBuckets;
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Exception\CannotOpenFileForWrite;
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Exception\CannotPutFile;
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Response\Error;
|
||||
use Akeeba\S3\Exception\CannotDeleteFile;
|
||||
use Akeeba\S3\Exception\CannotGetBucket;
|
||||
use Akeeba\S3\Exception\CannotGetFile;
|
||||
use Akeeba\S3\Exception\CannotListBuckets;
|
||||
use Akeeba\S3\Exception\CannotOpenFileForWrite;
|
||||
use Akeeba\S3\Exception\CannotPutFile;
|
||||
use Akeeba\S3\Response\Error;
|
||||
|
||||
defined('AKEEBAENGINE') or die();
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
class Connector
|
||||
{
|
||||
|
@ -81,7 +81,10 @@ class Connector
|
|||
|
||||
if (($input->getSize() <= 0) || (($input->getInputType() == Input::INPUT_DATA) && (!strlen($input->getDataReference()))))
|
||||
{
|
||||
throw new CannotPutFile('Missing input parameters', 0);
|
||||
if (substr($uri, -1) !== '/')
|
||||
{
|
||||
throw new CannotPutFile('Missing input parameters', 0);
|
||||
}
|
||||
}
|
||||
|
||||
// We need to post with Content-Length and Content-Type, MD5 is optional
|
||||
|
@ -169,7 +172,7 @@ class Connector
|
|||
|
||||
if (!is_resource($saveTo) && is_string($saveTo))
|
||||
{
|
||||
$fp = @fopen($saveTo, 'wb');
|
||||
$fp = @fopen($saveTo, 'w');
|
||||
|
||||
if ($fp === false)
|
||||
{
|
||||
|
@ -193,6 +196,53 @@ class Connector
|
|||
$request->setHeader('Range', "bytes=$from-$to");
|
||||
}
|
||||
|
||||
$response = $request->getResponse(true);
|
||||
|
||||
if (!$response->error->isError() && (($response->code !== 200) && ($response->code !== 206)))
|
||||
{
|
||||
$response->error = new Error(
|
||||
$response->code,
|
||||
"Unexpected HTTP status {$response->code}"
|
||||
);
|
||||
}
|
||||
|
||||
if ($response->error->isError())
|
||||
{
|
||||
throw new CannotGetFile(
|
||||
sprintf(
|
||||
__METHOD__ . "({%s}, {%s}): [%s] %s\n\nDebug info:\n%s",
|
||||
$bucket,
|
||||
$uri,
|
||||
$response->error->getCode(),
|
||||
$response->error->getMessage(),
|
||||
print_r($response->body, true)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!is_resource($fp))
|
||||
{
|
||||
return $response->body;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about an object.
|
||||
*
|
||||
* @param string $bucket Bucket name
|
||||
* @param string $uri Object URI
|
||||
*
|
||||
* @return array The headers returned by Amazon S3
|
||||
*
|
||||
* @throws CannotGetFile If the file does not exist
|
||||
* @see https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html
|
||||
*/
|
||||
public function headObject(string $bucket, string $uri): array
|
||||
{
|
||||
$request = new Request('HEAD', $bucket, $uri, $this->configuration);
|
||||
|
||||
$response = $request->getResponse();
|
||||
|
||||
if (!$response->error->isError() && (($response->code !== 200) && ($response->code !== 206)))
|
||||
|
@ -206,20 +256,21 @@ class Connector
|
|||
if ($response->error->isError())
|
||||
{
|
||||
throw new CannotGetFile(
|
||||
sprintf(__METHOD__ . "({$bucket}, {$uri}): [%s] %s\n\nDebug info:\n%s",
|
||||
$response->error->getCode(), $response->error->getMessage(), print_r($response->body, true)),
|
||||
$response->error->getCode()
|
||||
sprintf(
|
||||
__METHOD__ . "({%s}, {%s}): [%s] %s\n\nDebug info:\n%s",
|
||||
$bucket,
|
||||
$uri,
|
||||
$response->error->getCode(),
|
||||
$response->error->getMessage(),
|
||||
print_r($response->body, true)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!is_resource($fp))
|
||||
{
|
||||
return $response->body;
|
||||
}
|
||||
|
||||
return null;
|
||||
return $response->getHeaders();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete an object
|
||||
*
|
||||
|
@ -244,9 +295,13 @@ class Connector
|
|||
if ($response->error->isError())
|
||||
{
|
||||
throw new CannotDeleteFile(
|
||||
sprintf(__METHOD__ . "({$bucket}, {$uri}): [%s] %s",
|
||||
$response->error->getCode(), $response->error->getMessage()),
|
||||
$response->error->getCode()
|
||||
sprintf(
|
||||
__METHOD__ . "({%s}, {%s}): [%s] %s",
|
||||
$bucket,
|
||||
$uri,
|
||||
$response->error->getCode(),
|
||||
$response->error->getMessage()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -358,8 +413,7 @@ class Connector
|
|||
if ($response->error->isError())
|
||||
{
|
||||
throw new CannotGetBucket(
|
||||
sprintf(__METHOD__ . "(): [%s] %s", $response->error->getCode(), $response->error->getMessage()),
|
||||
$response->error->getCode()
|
||||
sprintf(__METHOD__ . "(): [%s] %s", $response->error->getCode(), $response->error->getMessage())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -403,168 +457,47 @@ class Connector
|
|||
*/
|
||||
public function getBucket(string $bucket, ?string $prefix = null, ?string $marker = null, ?int $maxKeys = null, string $delimiter = '/', bool $returnCommonPrefixes = false): array
|
||||
{
|
||||
$request = new Request('GET', $bucket, '', $this->configuration);
|
||||
$internalResult = $this->internalGetBucket($bucket, $prefix, $marker, $maxKeys, $delimiter, $returnCommonPrefixes);
|
||||
|
||||
if (!empty($prefix))
|
||||
{
|
||||
$request->setParameter('prefix', $prefix);
|
||||
}
|
||||
/**
|
||||
* @var array $objects
|
||||
* @var ?string $nextMarker
|
||||
*/
|
||||
extract($internalResult);
|
||||
unset($internalResult);
|
||||
|
||||
if (!empty($marker))
|
||||
{
|
||||
$request->setParameter('marker', $marker);
|
||||
}
|
||||
|
||||
if (!empty($maxKeys))
|
||||
{
|
||||
$request->setParameter('max-keys', $maxKeys);
|
||||
}
|
||||
|
||||
if (!empty($delimiter))
|
||||
{
|
||||
$request->setParameter('delimiter', $delimiter);
|
||||
}
|
||||
|
||||
$response = $request->getResponse();
|
||||
|
||||
if (!$response->error->isError() && $response->code !== 200)
|
||||
{
|
||||
$response->error = new Error(
|
||||
$response->code,
|
||||
"Unexpected HTTP status {$response->code}"
|
||||
);
|
||||
}
|
||||
|
||||
if ($response->error->isError())
|
||||
{
|
||||
throw new CannotGetBucket(
|
||||
sprintf(__METHOD__ . "(): [%s] %s", $response->error->getCode(), $response->error->getMessage()),
|
||||
$response->error->getCode()
|
||||
);
|
||||
}
|
||||
|
||||
$results = [];
|
||||
|
||||
$nextMarker = null;
|
||||
|
||||
if ($response->hasBody() && isset($response->body->Contents))
|
||||
{
|
||||
foreach ($response->body->Contents as $c)
|
||||
{
|
||||
$results[(string) $c->Key] = [
|
||||
'name' => (string) $c->Key,
|
||||
'time' => strtotime((string) $c->LastModified),
|
||||
'size' => (int) $c->Size,
|
||||
'hash' => substr((string) $c->ETag, 1, -1),
|
||||
];
|
||||
|
||||
$nextMarker = (string) $c->Key;
|
||||
}
|
||||
}
|
||||
|
||||
if ($returnCommonPrefixes && $response->hasBody() && isset($response->body->CommonPrefixes))
|
||||
{
|
||||
foreach ($response->body->CommonPrefixes as $c)
|
||||
{
|
||||
$results[(string) $c->Prefix] = ['prefix' => (string) $c->Prefix];
|
||||
}
|
||||
}
|
||||
|
||||
if ($response->hasBody() && isset($response->body->IsTruncated) &&
|
||||
((string) $response->body->IsTruncated == 'false')
|
||||
)
|
||||
{
|
||||
return $results;
|
||||
}
|
||||
|
||||
if ($response->hasBody() && isset($response->body->NextMarker))
|
||||
{
|
||||
$nextMarker = (string) $response->body->NextMarker;
|
||||
}
|
||||
|
||||
// Is it a truncated result?
|
||||
$isTruncated = ($nextMarker !== null) && ((string) $response->body->IsTruncated == 'true');
|
||||
// Is this a truncated result and no maxKeys specified?
|
||||
$isTruncatedAndNoMaxKeys = ($maxKeys == null) && $isTruncated;
|
||||
// Is this a truncated result with less keys than the specified maxKeys; and common prefixes found but not returned to the caller?
|
||||
$isTruncatedAndNeedsContinue = ($maxKeys != null) && $isTruncated && (count($results) < $maxKeys);
|
||||
|
||||
// Loop through truncated results if maxKeys isn't specified
|
||||
if ($isTruncatedAndNoMaxKeys || $isTruncatedAndNeedsContinue)
|
||||
// Loop through truncated results if maxKeys isn't specified or we don't have enough object records yet.
|
||||
if ($nextMarker !== null && ($maxKeys === null || count($objects) < $maxKeys))
|
||||
{
|
||||
do
|
||||
{
|
||||
$request = new Request('GET', $bucket, '', $this->configuration);
|
||||
$internalResult = $this->internalGetBucket($bucket, $prefix, $nextMarker, $maxKeys, $delimiter, $returnCommonPrefixes);
|
||||
|
||||
if (!empty($prefix))
|
||||
{
|
||||
$request->setParameter('prefix', $prefix);
|
||||
}
|
||||
$nextMarker = $internalResult['nextMarker'];
|
||||
$objects = array_merge($objects, $internalResult['objects']);
|
||||
|
||||
$request->setParameter('marker', $nextMarker);
|
||||
unset($internalResult);
|
||||
|
||||
if (!empty($delimiter))
|
||||
{
|
||||
$request->setParameter('delimiter', $delimiter);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$response = $request->getResponse();
|
||||
}
|
||||
catch (\Exception $e)
|
||||
// If the last call did not return a nextMarker I am done iterating.
|
||||
if ($nextMarker === null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if ($response->hasBody() && isset($response->body->Contents))
|
||||
// If we have maxKeys AND the number of objects is at least this many I am done iterating.
|
||||
if ($maxKeys !== null && count($objects) >= $maxKeys)
|
||||
{
|
||||
foreach ($response->body->Contents as $c)
|
||||
{
|
||||
$results[(string) $c->Key] = [
|
||||
'name' => (string) $c->Key,
|
||||
'time' => strtotime((string) $c->LastModified),
|
||||
'size' => (int) $c->Size,
|
||||
'hash' => substr((string) $c->ETag, 1, -1),
|
||||
];
|
||||
|
||||
$nextMarker = (string) $c->Key;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if ($returnCommonPrefixes && $response->hasBody() && isset($response->body->CommonPrefixes))
|
||||
{
|
||||
foreach ($response->body->CommonPrefixes as $c)
|
||||
{
|
||||
$results[(string) $c->Prefix] = ['prefix' => (string) $c->Prefix];
|
||||
}
|
||||
}
|
||||
|
||||
if ($response->hasBody() && isset($response->body->NextMarker))
|
||||
{
|
||||
$nextMarker = (string) $response->body->NextMarker;
|
||||
}
|
||||
|
||||
$continueCondition = false;
|
||||
|
||||
if ($isTruncatedAndNoMaxKeys)
|
||||
{
|
||||
$continueCondition = !$response->error->isError() && $isTruncated;
|
||||
}
|
||||
|
||||
if ($isTruncatedAndNeedsContinue)
|
||||
{
|
||||
$continueCondition = !$response->error->isError() && $isTruncated && (count($results) < $maxKeys);
|
||||
}
|
||||
} while ($continueCondition);
|
||||
} while (true);
|
||||
}
|
||||
|
||||
if (!is_null($maxKeys))
|
||||
if ($maxKeys !== null)
|
||||
{
|
||||
$results = array_splice($results, 0, $maxKeys);
|
||||
return array_splice($objects, 0, $maxKeys);
|
||||
}
|
||||
|
||||
return $results;
|
||||
return $objects;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -594,8 +527,7 @@ class Connector
|
|||
if ($response->error->isError())
|
||||
{
|
||||
throw new CannotListBuckets(
|
||||
sprintf(__METHOD__ . "(): [%s] %s", $response->error->getCode(), $response->error->getMessage()),
|
||||
$response->error->getCode()
|
||||
sprintf(__METHOD__ . "(): [%s] %s", $response->error->getCode(), $response->error->getMessage())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -691,7 +623,12 @@ class Connector
|
|||
if ($response->error->isError())
|
||||
{
|
||||
throw new CannotPutFile(
|
||||
sprintf(__METHOD__ . "(): [%s] %s\n\nDebug info:\n%s", $response->error->getCode(), $response->error->getMessage(), print_r($response->body, true))
|
||||
sprintf(
|
||||
__METHOD__ . "(): [%s] %s\n\nDebug info:\n%s",
|
||||
$response->error->getCode(),
|
||||
$response->error->getMessage(),
|
||||
print_r($response->body, true)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -958,4 +895,90 @@ class Connector
|
|||
{
|
||||
return $this->configuration;
|
||||
}
|
||||
|
||||
private function internalGetBucket(string $bucket, ?string $prefix = null, ?string $marker = null, ?int $maxKeys = null, string $delimiter = '/', bool $returnCommonPrefixes = false): array
|
||||
{
|
||||
$request = new Request('GET', $bucket, '', $this->configuration);
|
||||
|
||||
if (!empty($prefix))
|
||||
{
|
||||
$request->setParameter('prefix', $prefix);
|
||||
}
|
||||
|
||||
if (!empty($marker))
|
||||
{
|
||||
$request->setParameter('marker', $marker);
|
||||
}
|
||||
|
||||
if (!empty($maxKeys))
|
||||
{
|
||||
$request->setParameter('max-keys', $maxKeys);
|
||||
}
|
||||
|
||||
if (!empty($delimiter))
|
||||
{
|
||||
$request->setParameter('delimiter', $delimiter);
|
||||
}
|
||||
|
||||
$response = $request->getResponse();
|
||||
|
||||
if (!$response->error->isError() && $response->code !== 200)
|
||||
{
|
||||
$response->error = new Error(
|
||||
$response->code,
|
||||
"Unexpected HTTP status {$response->code}"
|
||||
);
|
||||
}
|
||||
|
||||
if ($response->error->isError())
|
||||
{
|
||||
throw new CannotGetBucket(
|
||||
sprintf(__METHOD__ . "(): [%s] %s", $response->error->getCode(), $response->error->getMessage())
|
||||
);
|
||||
}
|
||||
|
||||
$results = [
|
||||
'objects' => [],
|
||||
'nextMarker' => null,
|
||||
];
|
||||
|
||||
if ($response->hasBody() && isset($response->body->Contents))
|
||||
{
|
||||
foreach ($response->body->Contents as $c)
|
||||
{
|
||||
$results['objects'][(string) $c->Key] = [
|
||||
'name' => (string) $c->Key,
|
||||
'time' => strtotime((string) $c->LastModified),
|
||||
'size' => (int) $c->Size,
|
||||
'hash' => substr((string) $c->ETag, 1, -1),
|
||||
];
|
||||
|
||||
$results['nextMarker'] = (string) $c->Key;
|
||||
}
|
||||
}
|
||||
|
||||
if ($returnCommonPrefixes && $response->hasBody() && isset($response->body->CommonPrefixes))
|
||||
{
|
||||
foreach ($response->body->CommonPrefixes as $c)
|
||||
{
|
||||
$results['objects'][(string) $c->Prefix] = ['prefix' => (string) $c->Prefix];
|
||||
}
|
||||
}
|
||||
|
||||
if ($response->hasBody() && isset($response->body->IsTruncated) &&
|
||||
((string) $response->body->IsTruncated == 'false')
|
||||
)
|
||||
{
|
||||
$results['nextMarker'] = null;
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
if ($response->hasBody() && isset($response->body->NextMarker))
|
||||
{
|
||||
$results['nextMarker'] = (string) $response->body->NextMarker;
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
|
||||
namespace Akeeba\S3\Exception;
|
||||
|
||||
// Protection against direct access
|
||||
defined('AKEEBAENGINE') or die();
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
|
||||
namespace Akeeba\S3\Exception;
|
||||
|
||||
// Protection against direct access
|
||||
defined('AKEEBAENGINE') or die();
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
|
||||
namespace Akeeba\S3\Exception;
|
||||
|
||||
// Protection against direct access
|
||||
defined('AKEEBAENGINE') or die();
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
|
||||
namespace Akeeba\S3\Exception;
|
||||
|
||||
// Protection against direct access
|
||||
defined('AKEEBAENGINE') or die();
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
|
||||
namespace Akeeba\S3\Exception;
|
||||
|
||||
// Protection against direct access
|
||||
defined('AKEEBAENGINE') or die();
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
|
||||
namespace Akeeba\S3\Exception;
|
||||
|
||||
// Protection against direct access
|
||||
defined('AKEEBAENGINE') or die();
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
|
||||
namespace Akeeba\S3\Exception;
|
||||
|
||||
// Protection against direct access
|
||||
defined('AKEEBAENGINE') or die();
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
|
||||
namespace Akeeba\S3\Exception;
|
||||
|
||||
// Protection against direct access
|
||||
defined('AKEEBAENGINE') or die();
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
|
||||
namespace Akeeba\S3\Exception;
|
||||
|
||||
// Protection against direct access
|
||||
defined('AKEEBAENGINE') or die();
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
use Exception;
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
|
||||
namespace Akeeba\S3\Exception;
|
||||
|
||||
// Protection against direct access
|
||||
defined('AKEEBAENGINE') or die();
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
|
||||
namespace Akeeba\S3\Exception;
|
||||
|
||||
// Protection against direct access
|
||||
defined('AKEEBAENGINE') or die();
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
use Exception;
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
|
||||
namespace Akeeba\S3\Exception;
|
||||
|
||||
// Protection against direct access
|
||||
defined('AKEEBAENGINE') or die();
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
|
||||
namespace Akeeba\S3\Exception;
|
||||
|
||||
// Protection against direct access
|
||||
defined('AKEEBAENGINE') or die();
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
use Exception;
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
|
||||
namespace Akeeba\S3\Exception;
|
||||
|
||||
// Protection against direct access
|
||||
defined('AKEEBAENGINE') or die();
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
use Exception;
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
|
||||
namespace Akeeba\S3\Exception;
|
||||
|
||||
// Protection against direct access
|
||||
defined('AKEEBAENGINE') or die();
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
use Exception;
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Postproc\Connector\S3v4\Exception;
|
||||
namespace Akeeba\S3\Exception;
|
||||
|
||||
// Protection against direct access
|
||||
defined('AKEEBAENGINE') or die();
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
use LogicException;
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Postproc\Connector\S3v4;
|
||||
namespace Akeeba\S3;
|
||||
|
||||
// Protection against direct access
|
||||
defined('AKEEBAENGINE') or die();
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
/**
|
||||
* Defines an input source for PUT/POST requests to Amazon S3
|
||||
|
@ -20,17 +20,17 @@ class Input
|
|||
/**
|
||||
* Input type: resource
|
||||
*/
|
||||
const INPUT_RESOURCE = 1;
|
||||
public const INPUT_RESOURCE = 1;
|
||||
|
||||
/**
|
||||
* Input type: file
|
||||
*/
|
||||
const INPUT_FILE = 2;
|
||||
public const INPUT_FILE = 2;
|
||||
|
||||
/**
|
||||
* Input type: raw data
|
||||
*/
|
||||
const INPUT_DATA = 3;
|
||||
public const INPUT_DATA = 3;
|
||||
|
||||
/**
|
||||
* File pointer, in case we have a resource
|
||||
|
@ -177,7 +177,13 @@ class Input
|
|||
{
|
||||
if (is_resource($this->fp))
|
||||
{
|
||||
@fclose($this->fp);
|
||||
try
|
||||
{
|
||||
@fclose($this->fp);
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -258,10 +264,16 @@ class Input
|
|||
|
||||
if (is_resource($this->fp))
|
||||
{
|
||||
@fclose($this->fp);
|
||||
try
|
||||
{
|
||||
@fclose($this->fp);
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
$this->fp = @fopen($file, 'rb');
|
||||
$this->fp = @fopen($file, 'r');
|
||||
|
||||
if ($this->fp === false)
|
||||
{
|
||||
|
@ -295,7 +307,13 @@ class Input
|
|||
|
||||
if (is_resource($this->fp))
|
||||
{
|
||||
@fclose($this->fp);
|
||||
try
|
||||
{
|
||||
@fclose($this->fp);
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
$this->file = null;
|
||||
|
@ -329,7 +347,13 @@ class Input
|
|||
|
||||
if (is_resource($this->fp))
|
||||
{
|
||||
@fclose($this->fp);
|
||||
try
|
||||
{
|
||||
@fclose($this->fp);
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
$this->file = null;
|
||||
|
@ -450,7 +474,7 @@ class Input
|
|||
*/
|
||||
public function setSha256(?string $sha256): void
|
||||
{
|
||||
$this->sha256 = strtolower($sha256);
|
||||
$this->sha256 = is_null($sha256) ? null : strtolower($sha256);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -532,7 +556,7 @@ class Input
|
|||
switch ($this->getInputType())
|
||||
{
|
||||
case self::INPUT_DATA:
|
||||
return function_exists('mb_strlen') ? mb_strlen($this->data, '8bit') : strlen($this->data);
|
||||
return function_exists('mb_strlen') ? mb_strlen($this->data ?? '', '8bit') : strlen($this->data ?? '');
|
||||
break;
|
||||
|
||||
case self::INPUT_FILE:
|
||||
|
@ -635,7 +659,7 @@ class Input
|
|||
|
||||
$ext = strtolower(pathInfo($file, PATHINFO_EXTENSION));
|
||||
|
||||
return isset($exts[$ext]) ? $exts[$ext] : 'application/octet-stream';
|
||||
return $exts[$ext] ?? 'application/octet-stream';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,16 +3,16 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Postproc\Connector\S3v4;
|
||||
namespace Akeeba\S3;
|
||||
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Response\Error;
|
||||
use Akeeba\S3\Response\Error;
|
||||
|
||||
// Protection against direct access
|
||||
defined('AKEEBAENGINE') or die();
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
|
||||
class Request
|
||||
|
@ -142,6 +142,12 @@ class Request
|
|||
// The date must always be added as a header
|
||||
$this->headers['Date'] = gmdate('D, d M Y H:i:s O');
|
||||
|
||||
// S3-"compatible" services use a different date format. Because why not?
|
||||
if (strpos($this->headers['Host'], '.amazonaws.com') === false)
|
||||
{
|
||||
$this->headers['Date'] = gmdate('D, d M Y H:i:s T');
|
||||
}
|
||||
|
||||
// If there is a security token we need to set up the X-Amz-Security-Token header
|
||||
$token = $this->configuration->getToken();
|
||||
|
||||
|
@ -367,7 +373,7 @@ class Request
|
|||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getResponse(): Response
|
||||
public function getResponse(bool $rawResponse = false): Response
|
||||
{
|
||||
$this->processParametersIntoResource();
|
||||
|
||||
|
@ -417,8 +423,10 @@ class Request
|
|||
* Caveat: if your bucket contains dots in the name we have to turn off host verification due to the way the
|
||||
* S3 SSL certificates are set up.
|
||||
*/
|
||||
$isAmazonS3 = (substr($this->headers['Host'], -14) == '.amazonaws.com') ||
|
||||
substr($this->headers['Host'], -16) == 'amazonaws.com.cn';
|
||||
$isAmazonS3 = (substr($this->headers['Host'], -14) == '.amazonaws.com')
|
||||
|| substr(
|
||||
$this->headers['Host'], -16
|
||||
) == 'amazonaws.com.cn';
|
||||
$tooManyDots = substr_count($this->headers['Host'], '.') > 4;
|
||||
|
||||
$verifyHost = ($isAmazonS3 && $tooManyDots) ? 0 : 2;
|
||||
|
@ -429,6 +437,27 @@ class Request
|
|||
|
||||
curl_setopt($curl, CURLOPT_URL, $url);
|
||||
|
||||
/**
|
||||
* Set the optional x-amz-date header for third party services.
|
||||
*
|
||||
* Amazon S3 proper expects to get the date from the Date header. Third party services typically implement the
|
||||
* (wrongly) documented behaviour of using the x-amz-date header but, if it's missing, fall back to the Date
|
||||
* header. Wasabi does not fall back; it only uses the x-amz-date header which is why we have to set it here if
|
||||
* the request iss not made to Amazon S3 proper.
|
||||
*/
|
||||
$this->headers['x-amz-date'] = strpos($this->headers['Host'], '.amazonaws.com') !== false
|
||||
? ''
|
||||
: (new \DateTime($this->headers['Date']))->format('Ymd\THis\Z');
|
||||
|
||||
/**
|
||||
* Remove empty headers.
|
||||
*
|
||||
* While Amazon S3 proper and most third party implementations have no problem with that, there a few of them
|
||||
* (such as Synology C2) which choke on empty headers.
|
||||
*/
|
||||
$this->headers = array_filter($this->headers);
|
||||
|
||||
// Get the request signature
|
||||
$signer = Signature::getSignatureObject($this, $this->configuration->getSignatureMethod());
|
||||
$signer->preProcessHeaders($this->headers, $this->amzHeaders);
|
||||
|
||||
|
@ -482,7 +511,7 @@ class Request
|
|||
|
||||
$data = $this->input->getDataReference();
|
||||
|
||||
if (strlen($data))
|
||||
if (strlen($data ?? ''))
|
||||
{
|
||||
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
|
||||
}
|
||||
|
@ -538,12 +567,18 @@ class Request
|
|||
@curl_close($curl);
|
||||
|
||||
// Set the body data
|
||||
$this->response->finaliseBody();
|
||||
$this->response->finaliseBody($rawResponse);
|
||||
|
||||
// Clean up file resources
|
||||
if (!is_null($this->fp) && is_resource($this->fp))
|
||||
{
|
||||
fclose($this->fp);
|
||||
try
|
||||
{
|
||||
@fclose($this->fp);
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return $this->response;
|
||||
|
@ -560,7 +595,7 @@ class Request
|
|||
*/
|
||||
protected function __responseWriteCallback($curl, string $data): int
|
||||
{
|
||||
if (in_array($this->response->code, [200, 206]) && !is_null($this->fp) && is_resource($this->fp))
|
||||
if (in_array($this->response->code, [0, 200, 206]) && !is_null($this->fp) && is_resource($this->fp))
|
||||
{
|
||||
return fwrite($this->fp, $data);
|
||||
}
|
||||
|
@ -573,7 +608,7 @@ class Request
|
|||
/**
|
||||
* cURL header callback
|
||||
*
|
||||
* @param resource $curl cURL resource
|
||||
* @param resource $curl cURL resource
|
||||
* @param string &$data Data
|
||||
*
|
||||
* @return int Length in bytes
|
||||
|
@ -592,7 +627,15 @@ class Request
|
|||
return $strlen;
|
||||
}
|
||||
|
||||
[$header, $value] = explode(': ', trim($data), 2);
|
||||
// Ignore malformed headers without a value.
|
||||
if (strpos($data, ':') === false)
|
||||
{
|
||||
return $strlen;
|
||||
}
|
||||
|
||||
[$header, $value] = explode(':', trim($data), 2);
|
||||
$header = trim($header ?? '');
|
||||
$value = trim($value ?? '');
|
||||
|
||||
switch (strtolower($header))
|
||||
{
|
||||
|
@ -609,10 +652,12 @@ class Request
|
|||
break;
|
||||
|
||||
case 'etag':
|
||||
$this->response->setHeader('hash', $value[0] == '"' ? substr($value, 1, -1) : $value);
|
||||
$this->response->setHeader('hash', trim($value, '"'));
|
||||
break;
|
||||
|
||||
default:
|
||||
$this->response->setHeader(strtolower($header), is_numeric($value) ? (int) $value : $value);
|
||||
|
||||
if (preg_match('/^x-amz-meta-.*$/', $header))
|
||||
{
|
||||
$this->setHeader($header, is_numeric($value) ? (int) $value : $value);
|
||||
|
@ -652,13 +697,12 @@ class Request
|
|||
$query = substr($query, 0, -1);
|
||||
$this->uri .= $query;
|
||||
|
||||
if (array_key_exists('acl', $this->parameters) ||
|
||||
array_key_exists('location', $this->parameters) ||
|
||||
array_key_exists('torrent', $this->parameters) ||
|
||||
array_key_exists('logging', $this->parameters) ||
|
||||
array_key_exists('uploads', $this->parameters) ||
|
||||
array_key_exists('uploadId', $this->parameters) ||
|
||||
array_key_exists('partNumber', $this->parameters)
|
||||
if (array_key_exists('acl', $this->parameters) || array_key_exists('location', $this->parameters)
|
||||
|| array_key_exists('torrent', $this->parameters)
|
||||
|| array_key_exists('logging', $this->parameters)
|
||||
|| array_key_exists('uploads', $this->parameters)
|
||||
|| array_key_exists('uploadId', $this->parameters)
|
||||
|| array_key_exists('partNumber', $this->parameters)
|
||||
)
|
||||
{
|
||||
$this->resource .= $query;
|
||||
|
@ -720,6 +764,8 @@ class Request
|
|||
}
|
||||
|
||||
/**
|
||||
* Only applies to Amazon S3 proper.
|
||||
*
|
||||
* When using the Amazon S3 with the v4 signature API we have to use a different hostname per region. The
|
||||
* mapping can be found in https://docs.aws.amazon.com/general/latest/gr/s3.html#s3_region
|
||||
*
|
||||
|
@ -728,25 +774,27 @@ class Request
|
|||
*
|
||||
* v4 signing does NOT support non-Amazon endpoints.
|
||||
*/
|
||||
|
||||
// Most endpoints: s3-REGION.amazonaws.com
|
||||
$regionalEndpoint = $region . '.amazonaws.com';
|
||||
|
||||
// Exception: China
|
||||
if (substr($region, 0, 3) == 'cn-')
|
||||
if (in_array($endpoint, ['s3.amazonaws.com', 'amazonaws.com.cn']))
|
||||
{
|
||||
// Chinese endpoint, e.g.: s3.cn-north-1.amazonaws.com.cn
|
||||
$regionalEndpoint = $regionalEndpoint . '.cn';
|
||||
}
|
||||
// Most endpoints: s3-REGION.amazonaws.com
|
||||
$regionalEndpoint = $region . '.amazonaws.com';
|
||||
|
||||
// If dual-stack URLs are enabled then prepend the endpoint
|
||||
if ($configuration->getDualstackUrl())
|
||||
{
|
||||
$endpoint = 's3.dualstack.' . $regionalEndpoint;
|
||||
}
|
||||
else
|
||||
{
|
||||
$endpoint = 's3.' . $regionalEndpoint;
|
||||
// Exception: China
|
||||
if (substr($region, 0, 3) == 'cn-')
|
||||
{
|
||||
// Chinese endpoint, e.g.: s3.cn-north-1.amazonaws.com.cn
|
||||
$regionalEndpoint = $regionalEndpoint . '.cn';
|
||||
}
|
||||
|
||||
// If dual-stack URLs are enabled then prepend the endpoint
|
||||
if ($configuration->getDualstackUrl())
|
||||
{
|
||||
$endpoint = 's3.dualstack.' . $regionalEndpoint;
|
||||
}
|
||||
else
|
||||
{
|
||||
$endpoint = 's3.' . $regionalEndpoint;
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy path style access: return just the endpoint
|
||||
|
|
|
@ -3,18 +3,18 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Postproc\Connector\S3v4;
|
||||
namespace Akeeba\S3;
|
||||
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Exception\PropertyNotFound;
|
||||
use Akeeba\Engine\Postproc\Connector\S3v4\Response\Error;
|
||||
use Akeeba\S3\Exception\PropertyNotFound;
|
||||
use Akeeba\S3\Response\Error;
|
||||
use SimpleXMLElement;
|
||||
|
||||
// Protection against direct access
|
||||
defined('AKEEBAENGINE') or die();
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
/**
|
||||
* Amazon S3 API response object
|
||||
|
@ -124,7 +124,7 @@ class Response
|
|||
*
|
||||
* @param string|SimpleXMLElement|null $body
|
||||
*/
|
||||
public function setBody($body): void
|
||||
public function setBody($body, bool $rawResponse = false): void
|
||||
{
|
||||
$this->body = null;
|
||||
|
||||
|
@ -135,7 +135,7 @@ class Response
|
|||
|
||||
$this->body = $body;
|
||||
|
||||
$this->finaliseBody();
|
||||
$this->finaliseBody($rawResponse);
|
||||
}
|
||||
|
||||
public function resetBody(): void
|
||||
|
@ -153,7 +153,7 @@ class Response
|
|||
$this->body .= $data;
|
||||
}
|
||||
|
||||
public function finaliseBody(): void
|
||||
public function finaliseBody(bool $rawResponse = false): void
|
||||
{
|
||||
if (!$this->hasBody())
|
||||
{
|
||||
|
@ -165,8 +165,14 @@ class Response
|
|||
$this->headers['type'] = 'text/plain';
|
||||
}
|
||||
|
||||
if (is_string($this->body) &&
|
||||
(($this->headers['type'] == 'application/xml') || (substr($this->body, 0, 5) == '<?xml'))
|
||||
if (
|
||||
!$rawResponse
|
||||
&& is_string($this->body)
|
||||
&&
|
||||
(
|
||||
($this->headers['type'] == 'application/xml')
|
||||
|| (substr($this->body, 0, 5) == '<?xml')
|
||||
)
|
||||
)
|
||||
{
|
||||
$this->body = simplexml_load_string($this->body);
|
||||
|
@ -332,8 +338,8 @@ class Response
|
|||
)
|
||||
{
|
||||
$this->error = new Error(
|
||||
$this->code,
|
||||
(string) $this->body->Message
|
||||
500,
|
||||
(string) $this->body->Code . ':' . (string) $this->body->Message
|
||||
);
|
||||
|
||||
if (isset($this->body->Resource))
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Postproc\Connector\S3v4\Response;
|
||||
namespace Akeeba\S3\Response;
|
||||
|
||||
// Protection against direct access
|
||||
defined('AKEEBAENGINE') or die();
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
/**
|
||||
* S3 response error object
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue