mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-10-24 22:25:09 +03:00
Merge branch 'master' into patch-1
This commit is contained in:
commit
86dc155d1c
125 changed files with 2005 additions and 851 deletions
|
|
@ -232,6 +232,7 @@ export const
|
||||||
detectHiddenImages = true, // !!SettingsGet('try_to_detect_hidden_images'),
|
detectHiddenImages = true, // !!SettingsGet('try_to_detect_hidden_images'),
|
||||||
|
|
||||||
bqLevel = parseInt(SettingsUserStore.maxBlockquotesLevel()),
|
bqLevel = parseInt(SettingsUserStore.maxBlockquotesLevel()),
|
||||||
|
minFontSize = parseInt(SettingsUserStore.minFontSize()),
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
hasExternals: false,
|
hasExternals: false,
|
||||||
|
|
@ -625,12 +626,13 @@ export const
|
||||||
if (urls_broken.length) {
|
if (urls_broken.length) {
|
||||||
setAttribute('data-x-style-broken-urls', JSON.stringify(urls_broken));
|
setAttribute('data-x-style-broken-urls', JSON.stringify(urls_broken));
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
// https://github.com/the-djmaze/snappymail/issues/1082
|
// https://github.com/the-djmaze/snappymail/issues/1082
|
||||||
if (11 > pInt(oStyle.fontSize)) {
|
if (minFontSize > pInt(oStyle.fontSize)) {
|
||||||
oStyle.removeProperty('font-size');
|
oStyle.fontSize = minFontSize + "px";
|
||||||
|
// oStyle.removeProperty('font-size');
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
// Removes background and color
|
// Removes background and color
|
||||||
// Many e-mails incorrectly only define one, not both
|
// Many e-mails incorrectly only define one, not both
|
||||||
// And in dark theme mode this kills the readability
|
// And in dark theme mode this kills the readability
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ export class UserSettingsGeneral extends AbstractViewSettings {
|
||||||
['defaultSort', 'useThreads', 'threadAlgorithm',
|
['defaultSort', 'useThreads', 'threadAlgorithm',
|
||||||
// These use addSetting()
|
// These use addSetting()
|
||||||
'layout', 'messageReadDelay', 'messagesPerPage', 'checkMailInterval',
|
'layout', 'messageReadDelay', 'messagesPerPage', 'checkMailInterval',
|
||||||
'editorDefaultType', 'editorWysiwyg', 'msgDefaultAction', 'maxBlockquotesLevel',
|
'editorDefaultType', 'editorWysiwyg', 'msgDefaultAction', 'maxBlockquotesLevel', 'minFontSize',
|
||||||
// These are in addSettings()
|
// These are in addSettings()
|
||||||
'requestReadReceipt', 'requestDsn', 'requireTLS', 'pgpSign', 'pgpEncrypt',
|
'requestReadReceipt', 'requestDsn', 'requireTLS', 'pgpSign', 'pgpEncrypt',
|
||||||
'viewHTML', 'viewImages', 'viewImagesWhitelist', 'removeColors', 'allowStyles', 'allowDraftAutosave',
|
'viewHTML', 'viewImages', 'viewImagesWhitelist', 'removeColors', 'allowStyles', 'allowDraftAutosave',
|
||||||
|
|
@ -161,6 +161,7 @@ export class UserSettingsGeneral extends AbstractViewSettings {
|
||||||
this.addSetting('CheckMailInterval');
|
this.addSetting('CheckMailInterval');
|
||||||
this.addSetting('Layout');
|
this.addSetting('Layout');
|
||||||
this.addSetting('MaxBlockquotesLevel');
|
this.addSetting('MaxBlockquotesLevel');
|
||||||
|
this.addSetting('minFontSize');
|
||||||
this.addSetting('defaultSort');
|
this.addSetting('defaultSort');
|
||||||
|
|
||||||
this.addSettings([
|
this.addSettings([
|
||||||
|
|
|
||||||
|
|
@ -58,10 +58,9 @@ const
|
||||||
*/
|
*/
|
||||||
publicKeysItem = 'openpgp-public-keys',
|
publicKeysItem = 'openpgp-public-keys',
|
||||||
privateKeysItem = 'openpgp-private-keys',
|
privateKeysItem = 'openpgp-private-keys',
|
||||||
storage = window.localStorage,
|
|
||||||
loadOpenPgpKeys = async itemname => {
|
loadOpenPgpKeys = async itemname => {
|
||||||
let keys = [], key,
|
let keys = [], key,
|
||||||
armoredKeys = JSON.parse(storage.getItem(itemname)),
|
armoredKeys = JSON.parse(localStorage.getItem(itemname)),
|
||||||
i = arrayLength(armoredKeys);
|
i = arrayLength(armoredKeys);
|
||||||
while (i--) try {
|
while (i--) try {
|
||||||
key = await openpgp.readKey({armoredKey:armoredKeys[i]});
|
key = await openpgp.readKey({armoredKey:armoredKeys[i]});
|
||||||
|
|
@ -74,9 +73,9 @@ const
|
||||||
storeOpenPgpKeys = (keys, section) => {
|
storeOpenPgpKeys = (keys, section) => {
|
||||||
let armoredKeys = keys.map(item => item.armor);
|
let armoredKeys = keys.map(item => item.armor);
|
||||||
if (armoredKeys.length) {
|
if (armoredKeys.length) {
|
||||||
storage.setItem(section, JSON.stringify(armoredKeys));
|
localStorage.setItem(section, JSON.stringify(armoredKeys));
|
||||||
} else {
|
} else {
|
||||||
storage.removeItem(section);
|
localStorage.removeItem(section);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ export const SettingsUserStore = new class {
|
||||||
allowStyles: 0,
|
allowStyles: 0,
|
||||||
collapseBlockquotes: 1,
|
collapseBlockquotes: 1,
|
||||||
maxBlockquotesLevel: 0,
|
maxBlockquotesLevel: 0,
|
||||||
|
minFontSize: 0,
|
||||||
listInlineAttachments: 0,
|
listInlineAttachments: 0,
|
||||||
simpleAttachmentsList: 0,
|
simpleAttachmentsList: 0,
|
||||||
useCheckboxesInList: 1,
|
useCheckboxesInList: 1,
|
||||||
|
|
@ -98,6 +99,7 @@ export const SettingsUserStore = new class {
|
||||||
'AllowStyles',
|
'AllowStyles',
|
||||||
'CollapseBlockquotes',
|
'CollapseBlockquotes',
|
||||||
'MaxBlockquotesLevel',
|
'MaxBlockquotesLevel',
|
||||||
|
'minFontSize',
|
||||||
'ListInlineAttachments',
|
'ListInlineAttachments',
|
||||||
'simpleAttachmentsList',
|
'simpleAttachmentsList',
|
||||||
'UseCheckboxesInList',
|
'UseCheckboxesInList',
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#V-PopupsLanguages {
|
#V-PopupsLanguages {
|
||||||
|
|
||||||
max-width: 710px;
|
max-width: 720px;
|
||||||
|
|
||||||
label {
|
label {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
|
||||||
|
|
@ -591,6 +591,7 @@ class Actions
|
||||||
'ListInlineAttachments' => false,
|
'ListInlineAttachments' => false,
|
||||||
'CollapseBlockquotes' => $oConfig->Get('defaults', 'collapse_blockquotes', true),
|
'CollapseBlockquotes' => $oConfig->Get('defaults', 'collapse_blockquotes', true),
|
||||||
'MaxBlockquotesLevel' => 0,
|
'MaxBlockquotesLevel' => 0,
|
||||||
|
'minFontSize' => 0,
|
||||||
'simpleAttachmentsList' => false,
|
'simpleAttachmentsList' => false,
|
||||||
'listGrouped' => $oConfig->Get('defaults', 'mail_list_grouped', false),
|
'listGrouped' => $oConfig->Get('defaults', 'mail_list_grouped', false),
|
||||||
'MessagesPerPage' => \max(10, \intval($oConfig->Get('webmail', 'messages_per_page', 25)) ?: 25),
|
'MessagesPerPage' => \max(10, \intval($oConfig->Get('webmail', 'messages_per_page', 25)) ?: 25),
|
||||||
|
|
@ -687,6 +688,7 @@ class Actions
|
||||||
$aResult['ListInlineAttachments'] = (bool)$oSettings->GetConf('ListInlineAttachments', $aResult['ListInlineAttachments']);
|
$aResult['ListInlineAttachments'] = (bool)$oSettings->GetConf('ListInlineAttachments', $aResult['ListInlineAttachments']);
|
||||||
$aResult['CollapseBlockquotes'] = (bool)$oSettings->GetConf('CollapseBlockquotes', $aResult['CollapseBlockquotes']);
|
$aResult['CollapseBlockquotes'] = (bool)$oSettings->GetConf('CollapseBlockquotes', $aResult['CollapseBlockquotes']);
|
||||||
$aResult['MaxBlockquotesLevel'] = (int)$oSettings->GetConf('MaxBlockquotesLevel', $aResult['MaxBlockquotesLevel']);
|
$aResult['MaxBlockquotesLevel'] = (int)$oSettings->GetConf('MaxBlockquotesLevel', $aResult['MaxBlockquotesLevel']);
|
||||||
|
$aResult['minFontSize'] = (int)$oSettings->GetConf('minFontSize', $aResult['minFontSize']);
|
||||||
$aResult['simpleAttachmentsList'] = (bool)$oSettings->GetConf('simpleAttachmentsList', $aResult['simpleAttachmentsList']);
|
$aResult['simpleAttachmentsList'] = (bool)$oSettings->GetConf('simpleAttachmentsList', $aResult['simpleAttachmentsList']);
|
||||||
$aResult['listGrouped'] = (bool)$oSettings->GetConf('listGrouped', $aResult['listGrouped']);
|
$aResult['listGrouped'] = (bool)$oSettings->GetConf('listGrouped', $aResult['listGrouped']);
|
||||||
$aResult['ContactsAutosave'] = (bool)$oSettings->GetConf('ContactsAutosave', $aResult['ContactsAutosave']);
|
$aResult['ContactsAutosave'] = (bool)$oSettings->GetConf('ContactsAutosave', $aResult['ContactsAutosave']);
|
||||||
|
|
|
||||||
|
|
@ -183,6 +183,7 @@ trait User
|
||||||
$this->setSettingsFromParams($oSettings, 'ListInlineAttachments', 'bool');
|
$this->setSettingsFromParams($oSettings, 'ListInlineAttachments', 'bool');
|
||||||
$this->setSettingsFromParams($oSettings, 'CollapseBlockquotes', 'bool');
|
$this->setSettingsFromParams($oSettings, 'CollapseBlockquotes', 'bool');
|
||||||
$this->setSettingsFromParams($oSettings, 'MaxBlockquotesLevel', 'int');
|
$this->setSettingsFromParams($oSettings, 'MaxBlockquotesLevel', 'int');
|
||||||
|
$this->setSettingsFromParams($oSettings, 'minFontSize', 'int');
|
||||||
$this->setSettingsFromParams($oSettings, 'simpleAttachmentsList', 'bool');
|
$this->setSettingsFromParams($oSettings, 'simpleAttachmentsList', 'bool');
|
||||||
$this->setSettingsFromParams($oSettings, 'listGrouped', 'bool');
|
$this->setSettingsFromParams($oSettings, 'listGrouped', 'bool');
|
||||||
$this->setSettingsFromParams($oSettings, 'ContactsAutosave', 'bool');
|
$this->setSettingsFromParams($oSettings, 'ContactsAutosave', 'bool');
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ class BirthdayCalendarGenerator
|
||||||
// We've seen clients (ez-vcard) putting "BDAY:" properties
|
// We've seen clients (ez-vcard) putting "BDAY:" properties
|
||||||
// without a value into vCards. If we come across those, we'll
|
// without a value into vCards. If we come across those, we'll
|
||||||
// skip them.
|
// skip them.
|
||||||
if (empty($object->BDAY->getValue())) {
|
if ('' === $object->BDAY->getValue()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,7 +116,7 @@ class BirthdayCalendarGenerator
|
||||||
// Skip if we can't parse the BDAY value.
|
// Skip if we can't parse the BDAY value.
|
||||||
try {
|
try {
|
||||||
$dateParts = DateTimeParser::parseVCardDateTime($object->BDAY->getValue());
|
$dateParts = DateTimeParser::parseVCardDateTime($object->BDAY->getValue());
|
||||||
} catch (InvalidDataException $e) {
|
} catch (InvalidDataException) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ class Cli
|
||||||
// @codeCoverageIgnoreEnd
|
// @codeCoverageIgnoreEnd
|
||||||
|
|
||||||
try {
|
try {
|
||||||
list($options, $positional) = $this->parseArguments($argv);
|
[$options, $positional] = $this->parseArguments($argv);
|
||||||
|
|
||||||
if (isset($options['q'])) {
|
if (isset($options['q'])) {
|
||||||
$this->quiet = true;
|
$this->quiet = true;
|
||||||
|
|
@ -110,27 +110,10 @@ class Cli
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
case 'format':
|
case 'format':
|
||||||
switch ($value) {
|
$this->format = match ($value) {
|
||||||
// jcard/jcal documents
|
'jcard', 'jcal', 'vcard21', 'vcard30', 'vcard40', 'icalendar20', 'json', 'mimedir', 'icalendar', 'vcard' => $value,
|
||||||
case 'jcard':
|
default => throw new \InvalidArgumentException('Unknown format: '.$value),
|
||||||
case 'jcal':
|
};
|
||||||
// specific document versions
|
|
||||||
case 'vcard21':
|
|
||||||
case 'vcard30':
|
|
||||||
case 'vcard40':
|
|
||||||
case 'icalendar20':
|
|
||||||
// specific formats
|
|
||||||
case 'json':
|
|
||||||
case 'mimedir':
|
|
||||||
// icalendar/vcard
|
|
||||||
case 'icalendar':
|
|
||||||
case 'vcard':
|
|
||||||
$this->format = $value;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new \InvalidArgumentException('Unknown format: '.$value);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'pretty':
|
case 'pretty':
|
||||||
$this->pretty = true;
|
$this->pretty = true;
|
||||||
|
|
@ -139,28 +122,11 @@ class Cli
|
||||||
$this->forgiving = true;
|
$this->forgiving = true;
|
||||||
break;
|
break;
|
||||||
case 'inputformat':
|
case 'inputformat':
|
||||||
switch ($value) {
|
$this->inputFormat = match ($value) {
|
||||||
// json formats
|
'jcard', 'jcal', 'json' => 'json',
|
||||||
case 'jcard':
|
'mimedir', 'icalendar', 'vcard', 'vcard21', 'vcard30', 'vcard40', 'icalendar20' => 'mimedir',
|
||||||
case 'jcal':
|
default => throw new \InvalidArgumentException('Unknown format: '.$value),
|
||||||
case 'json':
|
};
|
||||||
$this->inputFormat = 'json';
|
|
||||||
break;
|
|
||||||
|
|
||||||
// mimedir formats
|
|
||||||
case 'mimedir':
|
|
||||||
case 'icalendar':
|
|
||||||
case 'vcard':
|
|
||||||
case 'vcard21':
|
|
||||||
case 'vcard30':
|
|
||||||
case 'vcard40':
|
|
||||||
case 'icalendar20':
|
|
||||||
$this->inputFormat = 'mimedir';
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new \InvalidArgumentException('Unknown format: '.$value);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new \InvalidArgumentException('Unknown option: '.$name);
|
throw new \InvalidArgumentException('Unknown option: '.$name);
|
||||||
|
|
@ -181,7 +147,7 @@ class Cli
|
||||||
throw new \InvalidArgumentException('Too many arguments');
|
throw new \InvalidArgumentException('Too many arguments');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!in_array($positional[0], ['validate', 'repair', 'convert', 'color'])) {
|
if (!in_array($positional[0], ['validate', 'repair', 'convert', 'color'], true)) {
|
||||||
throw new \InvalidArgumentException('Unknown command: '.$positional[0]);
|
throw new \InvalidArgumentException('Unknown command: '.$positional[0]);
|
||||||
}
|
}
|
||||||
} catch (\InvalidArgumentException $e) {
|
} catch (\InvalidArgumentException $e) {
|
||||||
|
|
@ -201,14 +167,14 @@ class Cli
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null === $this->inputFormat) {
|
if (null === $this->inputFormat) {
|
||||||
if ('.json' === substr($this->inputPath, -5)) {
|
if (str_ends_with($this->inputPath, '.json')) {
|
||||||
$this->inputFormat = 'json';
|
$this->inputFormat = 'json';
|
||||||
} else {
|
} else {
|
||||||
$this->inputFormat = 'mimedir';
|
$this->inputFormat = 'mimedir';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (null === $this->format) {
|
if (null === $this->format) {
|
||||||
if ('.json' === substr($this->outputPath, -5)) {
|
if (str_ends_with($this->outputPath, '.json')) {
|
||||||
$this->format = 'json';
|
$this->format = 'json';
|
||||||
} else {
|
} else {
|
||||||
$this->format = 'mimedir';
|
$this->format = 'mimedir';
|
||||||
|
|
@ -219,12 +185,13 @@ class Cli
|
||||||
|
|
||||||
try {
|
try {
|
||||||
while ($input = $this->readInput()) {
|
while ($input = $this->readInput()) {
|
||||||
|
// @phpstan-ignore method.dynamicName
|
||||||
$returnCode = $this->$command($input);
|
$returnCode = $this->$command($input);
|
||||||
if (0 !== $returnCode) {
|
if (0 !== $returnCode) {
|
||||||
$realCode = $returnCode;
|
$realCode = $returnCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (EofException $e) {
|
} catch (EofException) {
|
||||||
// end of file
|
// end of file
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->log('Error: '.$e->getMessage(), 'red');
|
$this->log('Error: '.$e->getMessage(), 'red');
|
||||||
|
|
@ -408,7 +375,7 @@ HELP
|
||||||
}
|
}
|
||||||
fwrite($this->stdout, json_encode($vObj->jsonSerialize(), $jsonOptions));
|
fwrite($this->stdout, json_encode($vObj->jsonSerialize(), $jsonOptions));
|
||||||
} else {
|
} else {
|
||||||
fwrite($this->stdout, $vObj->serialize());
|
fwrite($this->stdout, (string) $vObj->serialize());
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -538,7 +505,7 @@ HELP
|
||||||
$this->cWrite('red', ':');
|
$this->cWrite('red', ':');
|
||||||
|
|
||||||
if ($property instanceof Property\Binary) {
|
if ($property instanceof Property\Binary) {
|
||||||
$this->cWrite('default', 'embedded binary stripped. ('.strlen($property->getValue()).' bytes)');
|
$this->cWrite('default', 'embedded binary stripped. ('.strlen((string) $property->getValue()).' bytes)');
|
||||||
} else {
|
} else {
|
||||||
$parts = $property->getParts();
|
$parts = $property->getParts();
|
||||||
$first1 = true;
|
$first1 = true;
|
||||||
|
|
@ -593,17 +560,17 @@ HELP
|
||||||
|
|
||||||
$v = $argv[$ii];
|
$v = $argv[$ii];
|
||||||
|
|
||||||
if ('--' === substr($v, 0, 2)) {
|
if (str_starts_with((string) $v, '--')) {
|
||||||
// This is a long-form option.
|
// This is a long-form option.
|
||||||
$optionName = substr($v, 2);
|
$optionName = substr((string) $v, 2);
|
||||||
$optionValue = true;
|
$optionValue = true;
|
||||||
if (strpos($optionName, '=')) {
|
if (strpos($optionName, '=')) {
|
||||||
list($optionName, $optionValue) = explode('=', $optionName);
|
[$optionName, $optionValue] = explode('=', $optionName);
|
||||||
}
|
}
|
||||||
$options[$optionName] = $optionValue;
|
$options[$optionName] = $optionValue;
|
||||||
} elseif ('-' === substr($v, 0, 1) && strlen($v) > 1) {
|
} elseif (str_starts_with((string) $v, '-') && strlen((string) $v) > 1) {
|
||||||
// This is a short-form option.
|
// This is a short-form option.
|
||||||
foreach (str_split(substr($v, 1)) as $option) {
|
foreach (str_split(substr((string) $v, 1)) as $option) {
|
||||||
$options[$option] = true;
|
$options[$option] = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -628,6 +595,9 @@ HELP
|
||||||
if (!$this->parser) {
|
if (!$this->parser) {
|
||||||
if ('-' !== $this->inputPath) {
|
if ('-' !== $this->inputPath) {
|
||||||
$this->stdin = fopen($this->inputPath, 'r');
|
$this->stdin = fopen($this->inputPath, 'r');
|
||||||
|
if (false === $this->stdin) {
|
||||||
|
throw new InvalidDataException('Cannot open "'.$this->inputPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('mimedir' === $this->inputFormat) {
|
if ('mimedir' === $this->inputFormat) {
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ class Component extends Node
|
||||||
// If there's no dot in the name, it's an exact property name,
|
// If there's no dot in the name, it's an exact property name,
|
||||||
// we can just wipe out all those properties.
|
// we can just wipe out all those properties.
|
||||||
//
|
//
|
||||||
if (false === strpos($item, '.')) {
|
if (!str_contains($item, '.')) {
|
||||||
unset($this->children[strtoupper($item)]);
|
unset($this->children[strtoupper($item)]);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
@ -210,8 +210,8 @@ class Component extends Node
|
||||||
{
|
{
|
||||||
$group = null;
|
$group = null;
|
||||||
$name = strtoupper($name);
|
$name = strtoupper($name);
|
||||||
if (false !== strpos($name, '.')) {
|
if (str_contains($name, '.')) {
|
||||||
list($group, $name) = explode('.', $name, 2);
|
[$group, $name] = explode('.', $name, 2);
|
||||||
}
|
}
|
||||||
if ('' === $name) {
|
if ('' === $name) {
|
||||||
$name = null;
|
$name = null;
|
||||||
|
|
@ -222,16 +222,14 @@ class Component extends Node
|
||||||
|
|
||||||
if (is_null($group)) {
|
if (is_null($group)) {
|
||||||
return $result;
|
return $result;
|
||||||
} else {
|
|
||||||
// If we have a group filter as well, we need to narrow it down
|
|
||||||
// more.
|
|
||||||
return array_filter(
|
|
||||||
$result,
|
|
||||||
function ($child) use ($group) {
|
|
||||||
return $child instanceof Property && (null !== $child->group ? strtoupper($child->group) : '') === $group;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we have a group filter as well, we need to narrow it down
|
||||||
|
// more.
|
||||||
|
return array_filter(
|
||||||
|
$result,
|
||||||
|
fn ($child) => $child instanceof Property && (null !== $child->group ? strtoupper($child->group) : '') === $group
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we got to this point, it means there was no 'name' specified for
|
// If we got to this point, it means there was no 'name' specified for
|
||||||
|
|
@ -368,7 +366,7 @@ class Component extends Node
|
||||||
|
|
||||||
$writer->startElement(strtolower($this->name));
|
$writer->startElement(strtolower($this->name));
|
||||||
|
|
||||||
if (!empty($properties)) {
|
if ([] !== $properties) {
|
||||||
$writer->startElement('properties');
|
$writer->startElement('properties');
|
||||||
|
|
||||||
foreach ($properties as $property) {
|
foreach ($properties as $property) {
|
||||||
|
|
@ -378,7 +376,7 @@ class Component extends Node
|
||||||
$writer->endElement();
|
$writer->endElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($components)) {
|
if ([] !== $components) {
|
||||||
$writer->startElement('components');
|
$writer->startElement('components');
|
||||||
|
|
||||||
foreach ($components as $component) {
|
foreach ($components as $component) {
|
||||||
|
|
@ -422,13 +420,12 @@ class Component extends Node
|
||||||
$matches = $this->select($name);
|
$matches = $this->select($name);
|
||||||
if (0 === count($matches)) {
|
if (0 === count($matches)) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
|
||||||
$firstMatch = current($matches);
|
|
||||||
/* @var $firstMatch Property */
|
|
||||||
$firstMatch->setIterator(new ElementList(array_values($matches)));
|
|
||||||
|
|
||||||
return $firstMatch;
|
|
||||||
}
|
}
|
||||||
|
$firstMatch = current($matches);
|
||||||
|
/* @var $firstMatch Property */
|
||||||
|
$firstMatch->setIterator(new ElementList(array_values($matches)));
|
||||||
|
|
||||||
|
return $firstMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -542,7 +539,7 @@ class Component extends Node
|
||||||
$messages = [];
|
$messages = [];
|
||||||
|
|
||||||
foreach ($this->children() as $child) {
|
foreach ($this->children() as $child) {
|
||||||
$name = strtoupper($child->name);
|
$name = strtoupper((string) $child->name);
|
||||||
if (!isset($propertyCounters[$name])) {
|
if (!isset($propertyCounters[$name])) {
|
||||||
$propertyCounters[$name] = 1;
|
$propertyCounters[$name] = 1;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,9 @@ class VAlarm extends VObject\Component
|
||||||
public function getEffectiveTriggerTime(): \DateTimeImmutable
|
public function getEffectiveTriggerTime(): \DateTimeImmutable
|
||||||
{
|
{
|
||||||
$trigger = $this->TRIGGER;
|
$trigger = $this->TRIGGER;
|
||||||
if (!isset($trigger['VALUE']) || ($trigger['VALUE'] && 'DURATION' === strtoupper($trigger['VALUE']))) {
|
if (!isset($trigger['VALUE']) || ($trigger['VALUE'] && 'DURATION' === strtoupper((string) $trigger['VALUE']))) {
|
||||||
$triggerDuration = VObject\DateTimeParser::parseDuration($this->TRIGGER);
|
$triggerDuration = VObject\DateTimeParser::parseDuration($this->TRIGGER);
|
||||||
$related = (isset($trigger['RELATED']) && 'END' == strtoupper($trigger['RELATED'])) ? 'END' : 'START';
|
$related = (isset($trigger['RELATED']) && 'END' === strtoupper($trigger['RELATED'])) ? 'END' : 'START';
|
||||||
|
|
||||||
/** @var VEvent|VTodo $parentComponent */
|
/** @var VEvent|VTodo $parentComponent */
|
||||||
$parentComponent = $this->parent;
|
$parentComponent = $this->parent;
|
||||||
|
|
@ -96,9 +96,9 @@ class VAlarm extends VObject\Component
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
} else {
|
|
||||||
return $start <= $effectiveTrigger && $end > $effectiveTrigger;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $start <= $effectiveTrigger && $end > $effectiveTrigger;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ class VAvailability extends VObject\Component
|
||||||
*/
|
*/
|
||||||
public function isInTimeRange(\DateTimeInterface $start, \DateTimeInterface $end): bool
|
public function isInTimeRange(\DateTimeInterface $start, \DateTimeInterface $end): bool
|
||||||
{
|
{
|
||||||
list($effectiveStart, $effectiveEnd) = $this->getEffectiveStartEnd();
|
[$effectiveStart, $effectiveEnd] = $this->getEffectiveStartEnd();
|
||||||
|
|
||||||
return
|
return
|
||||||
(is_null($effectiveStart) || $start < $effectiveEnd)
|
(is_null($effectiveStart) || $start < $effectiveEnd)
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,10 @@ class VCalendar extends VObject\Document
|
||||||
|
|
||||||
// Additions from draft-daboo-calendar-availability-05
|
// Additions from draft-daboo-calendar-availability-05
|
||||||
'BUSYTYPE' => Property\Text::class,
|
'BUSYTYPE' => Property\Text::class,
|
||||||
|
|
||||||
|
// RFC 7986 properties
|
||||||
|
'NAME' => Property\FlatText::class,
|
||||||
|
'COLOR' => Property\FlatText::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -164,7 +168,7 @@ class VCalendar extends VObject\Document
|
||||||
*
|
*
|
||||||
* @param string|null $componentName filter by component name
|
* @param string|null $componentName filter by component name
|
||||||
*
|
*
|
||||||
* @return VObject\Component[]
|
* @return Component[]
|
||||||
*/
|
*/
|
||||||
public function getBaseComponents(?string $componentName = null): array
|
public function getBaseComponents(?string $componentName = null): array
|
||||||
{
|
{
|
||||||
|
|
@ -327,7 +331,7 @@ class VCalendar extends VObject\Document
|
||||||
foreach ($recurringEvents as $events) {
|
foreach ($recurringEvents as $events) {
|
||||||
try {
|
try {
|
||||||
$it = new EventIterator($events, null, $timeZone);
|
$it = new EventIterator($events, null, $timeZone);
|
||||||
} catch (NoInstancesException $e) {
|
} catch (NoInstancesException) {
|
||||||
// This event is recurring, but it doesn't have a single
|
// This event is recurring, but it doesn't have a single
|
||||||
// instance. We are skipping this event from the output
|
// instance. We are skipping this event from the output
|
||||||
// entirely.
|
// entirely.
|
||||||
|
|
@ -379,6 +383,18 @@ class VCalendar extends VObject\Document
|
||||||
|
|
||||||
'CALSCALE' => '?',
|
'CALSCALE' => '?',
|
||||||
'METHOD' => '?',
|
'METHOD' => '?',
|
||||||
|
|
||||||
|
// RFC 7986 calendar properties
|
||||||
|
'UID' => '?',
|
||||||
|
'LAST-MODIFIED' => '?',
|
||||||
|
'URL' => '?',
|
||||||
|
'REFRESH-INTERVAL' => '?',
|
||||||
|
'SOURCE' => '?',
|
||||||
|
'COLOR' => '?',
|
||||||
|
'NAME' => '*',
|
||||||
|
'DESCRIPTION' => '*',
|
||||||
|
'CATEGORIES' => '*',
|
||||||
|
'IMAGE' => '*',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -424,7 +440,7 @@ class VCalendar extends VObject\Document
|
||||||
if ($child instanceof Component) {
|
if ($child instanceof Component) {
|
||||||
++$componentsFound;
|
++$componentsFound;
|
||||||
|
|
||||||
if (!in_array($child->name, ['VEVENT', 'VTODO', 'VJOURNAL'])) {
|
if (!in_array($child->name, ['VEVENT', 'VTODO', 'VJOURNAL'], true)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$componentTypes[] = $child->name;
|
$componentTypes[] = $child->name;
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,13 @@ class VCard extends VObject\Document
|
||||||
'ORG-DIRECTORY' => VObject\Property\FlatText::class,
|
'ORG-DIRECTORY' => VObject\Property\FlatText::class,
|
||||||
|
|
||||||
'X-CRYPTO' => VObject\Property\XCrypto::class,
|
'X-CRYPTO' => VObject\Property\XCrypto::class,
|
||||||
|
|
||||||
|
// rfc9554 properties
|
||||||
|
'CREATED' => VObject\Property\VCard\TimeStamp::class,
|
||||||
|
'GRAMGENDER' => VObject\Property\FlatText::class,
|
||||||
|
'LANGUAGE' => VObject\Property\VCard\LanguageTag::class,
|
||||||
|
'PRONOUNS' => VObject\Property\FlatText::class,
|
||||||
|
'SOCIALPROFILE' => VObject\Property\Uri::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -326,6 +333,11 @@ class VCard extends VObject\Document
|
||||||
'CALURI' => '*',
|
'CALURI' => '*',
|
||||||
'CATEGORIES' => '*',
|
'CATEGORIES' => '*',
|
||||||
'CLIENTPIDMAP' => '*',
|
'CLIENTPIDMAP' => '*',
|
||||||
|
'CREATED' => '?',
|
||||||
|
'GRAMGENDER' => '*',
|
||||||
|
'LANGUAGE' => '?',
|
||||||
|
'PRONOUNS' => '*',
|
||||||
|
'SOCIALPROFILE' => '*',
|
||||||
'EMAIL' => '*',
|
'EMAIL' => '*',
|
||||||
'FBURL' => '*',
|
'FBURL' => '*',
|
||||||
'IMPP' => '*',
|
'IMPP' => '*',
|
||||||
|
|
@ -411,6 +423,34 @@ class VCard extends VObject\Document
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a property with a specific TYPE value (ADR, TEL, or EMAIL).
|
||||||
|
*
|
||||||
|
* This function will return null if the exact property list does not exist.
|
||||||
|
*
|
||||||
|
* For example to get the property of `TEL;TYPE=HOME,CELL`
|
||||||
|
* you would call `getByTypes('TEL', ['HOME', 'CELL'])`
|
||||||
|
*
|
||||||
|
* @param string[] $types
|
||||||
|
*
|
||||||
|
* @return \ArrayAccess|array|null
|
||||||
|
*/
|
||||||
|
public function getByTypes(string $propertyName, array $types)
|
||||||
|
{
|
||||||
|
$types = array_map(strtolower(...), $types);
|
||||||
|
foreach ($this->select($propertyName) as $field) {
|
||||||
|
if (isset($field['TYPE'])) {
|
||||||
|
$parts = array_map(strtolower(...), $field['TYPE']->getParts());
|
||||||
|
|
||||||
|
if (!array_diff($types, $parts) && !array_diff($parts, $types)) {
|
||||||
|
return $field;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method should return a list of default property values.
|
* This method should return a list of default property values.
|
||||||
*/
|
*/
|
||||||
|
|
@ -453,11 +493,9 @@ class VCard extends VObject\Document
|
||||||
$propertiesByGroup = [];
|
$propertiesByGroup = [];
|
||||||
|
|
||||||
foreach ($this->children() as $property) {
|
foreach ($this->children() as $property) {
|
||||||
$group = $property->group;
|
$group = $property->group ?? '';
|
||||||
|
|
||||||
if (!isset($propertiesByGroup[$group])) {
|
$propertiesByGroup[$group] ??= [];
|
||||||
$propertiesByGroup[$group] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$propertiesByGroup[$group][] = $property;
|
$propertiesByGroup[$group][] = $property;
|
||||||
}
|
}
|
||||||
|
|
@ -465,9 +503,9 @@ class VCard extends VObject\Document
|
||||||
$writer->startElement(strtolower($this->name));
|
$writer->startElement(strtolower($this->name));
|
||||||
|
|
||||||
foreach ($propertiesByGroup as $group => $properties) {
|
foreach ($propertiesByGroup as $group => $properties) {
|
||||||
if (!empty($group)) {
|
if ('' !== $group) {
|
||||||
$writer->startElement('group');
|
$writer->startElement('group');
|
||||||
$writer->writeAttribute('name', strtolower($group));
|
$writer->writeAttribute('name', strtolower((string) $group));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($properties as $property) {
|
foreach ($properties as $property) {
|
||||||
|
|
@ -487,7 +525,7 @@ class VCard extends VObject\Document
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($group)) {
|
if ('' !== $group) {
|
||||||
$writer->endElement();
|
$writer->endElement();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -503,7 +541,7 @@ class VCard extends VObject\Document
|
||||||
$className = parent::getClassNameForPropertyName($propertyName);
|
$className = parent::getClassNameForPropertyName($propertyName);
|
||||||
|
|
||||||
// In vCard 4, BINARY no longer exists, and we need URI instead.
|
// In vCard 4, BINARY no longer exists, and we need URI instead.
|
||||||
if (VObject\Property\Binary::class == $className && self::VCARD40 === $this->getDocumentType()) {
|
if (VObject\Property\Binary::class === $className && self::VCARD40 === $this->getDocumentType()) {
|
||||||
return VObject\Property\Uri::class;
|
return VObject\Property\Uri::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,10 +41,10 @@ class VEvent extends VObject\Component
|
||||||
*/
|
*/
|
||||||
public function isInTimeRange(\DateTimeInterface $start, \DateTimeInterface $end): bool
|
public function isInTimeRange(\DateTimeInterface $start, \DateTimeInterface $end): bool
|
||||||
{
|
{
|
||||||
if ($this->RRULE) {
|
if ($this->RRULE || $this->RDATE) {
|
||||||
try {
|
try {
|
||||||
$it = new EventIterator($this, null, $start->getTimezone());
|
$it = new EventIterator($this, null, $start->getTimezone());
|
||||||
} catch (NoInstancesException $e) {
|
} catch (NoInstancesException) {
|
||||||
// If we've caught this exception, there are no instances
|
// If we've caught this exception, there are no instances
|
||||||
// for the event that fall into the specified time-range.
|
// for the event that fall into the specified time-range.
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -117,6 +117,7 @@ class VEvent extends VObject\Component
|
||||||
'DTSTART' => $hasMethod ? '?' : '1',
|
'DTSTART' => $hasMethod ? '?' : '1',
|
||||||
'CLASS' => '?',
|
'CLASS' => '?',
|
||||||
'CREATED' => '?',
|
'CREATED' => '?',
|
||||||
|
'COLOR' => '?',
|
||||||
'DESCRIPTION' => '?',
|
'DESCRIPTION' => '?',
|
||||||
'GEO' => '?',
|
'GEO' => '?',
|
||||||
'LAST-MODIFIED' => '?',
|
'LAST-MODIFIED' => '?',
|
||||||
|
|
@ -137,7 +138,9 @@ class VEvent extends VObject\Component
|
||||||
'ATTENDEE' => '*',
|
'ATTENDEE' => '*',
|
||||||
'CATEGORIES' => '*',
|
'CATEGORIES' => '*',
|
||||||
'COMMENT' => '*',
|
'COMMENT' => '*',
|
||||||
|
'CONFERENCE' => '*',
|
||||||
'CONTACT' => '*',
|
'CONTACT' => '*',
|
||||||
|
'IMAGE' => '*',
|
||||||
'EXDATE' => '*',
|
'EXDATE' => '*',
|
||||||
'REQUEST-STATUS' => '*',
|
'REQUEST-STATUS' => '*',
|
||||||
'RELATED-TO' => '*',
|
'RELATED-TO' => '*',
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ class VFreeBusy extends VObject\Component
|
||||||
// Every period is formatted as [start]/[end]. The start is an
|
// Every period is formatted as [start]/[end]. The start is an
|
||||||
// absolute UTC time, the end may be an absolute UTC time, or
|
// absolute UTC time, the end may be an absolute UTC time, or
|
||||||
// duration (relative) value.
|
// duration (relative) value.
|
||||||
list($busyStart, $busyEnd) = explode('/', $period);
|
[$busyStart, $busyEnd] = explode('/', $period);
|
||||||
|
|
||||||
$busyStart = VObject\DateTimeParser::parse($busyStart);
|
$busyStart = VObject\DateTimeParser::parse($busyStart);
|
||||||
$busyEnd = VObject\DateTimeParser::parse($busyEnd);
|
$busyEnd = VObject\DateTimeParser::parse($busyEnd);
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ class VJournal extends VObject\Component
|
||||||
|
|
||||||
'CLASS' => '?',
|
'CLASS' => '?',
|
||||||
'CREATED' => '?',
|
'CREATED' => '?',
|
||||||
|
'COLOR' => '?',
|
||||||
'DTSTART' => '?',
|
'DTSTART' => '?',
|
||||||
'LAST-MODIFIED' => '?',
|
'LAST-MODIFIED' => '?',
|
||||||
'ORGANIZER' => '?',
|
'ORGANIZER' => '?',
|
||||||
|
|
@ -78,6 +79,7 @@ class VJournal extends VObject\Component
|
||||||
'CATEGORIES' => '*',
|
'CATEGORIES' => '*',
|
||||||
'COMMENT' => '*',
|
'COMMENT' => '*',
|
||||||
'CONTACT' => '*',
|
'CONTACT' => '*',
|
||||||
|
'IMAGE' => '*',
|
||||||
'DESCRIPTION' => '*',
|
'DESCRIPTION' => '*',
|
||||||
'EXDATE' => '*',
|
'EXDATE' => '*',
|
||||||
'RELATED-TO' => '*',
|
'RELATED-TO' => '*',
|
||||||
|
|
|
||||||
|
|
@ -54,9 +54,9 @@ class VTodo extends VObject\Component
|
||||||
return
|
return
|
||||||
($start < $due || $start <= $dtstart)
|
($start < $due || $start <= $dtstart)
|
||||||
&& ($end > $dtstart || $end >= $due);
|
&& ($end > $dtstart || $end >= $due);
|
||||||
} else {
|
|
||||||
return $start <= $dtstart && $end > $dtstart;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $start <= $dtstart && $end > $dtstart;
|
||||||
}
|
}
|
||||||
if ($due) {
|
if ($due) {
|
||||||
return $start < $due && $end >= $due;
|
return $start < $due && $end >= $due;
|
||||||
|
|
@ -98,6 +98,7 @@ class VTodo extends VObject\Component
|
||||||
'CLASS' => '?',
|
'CLASS' => '?',
|
||||||
'COMPLETED' => '?',
|
'COMPLETED' => '?',
|
||||||
'CREATED' => '?',
|
'CREATED' => '?',
|
||||||
|
'COLOR' => '?',
|
||||||
'DESCRIPTION' => '?',
|
'DESCRIPTION' => '?',
|
||||||
'DTSTART' => '?',
|
'DTSTART' => '?',
|
||||||
'GEO' => '?',
|
'GEO' => '?',
|
||||||
|
|
@ -120,7 +121,9 @@ class VTodo extends VObject\Component
|
||||||
'ATTENDEE' => '*',
|
'ATTENDEE' => '*',
|
||||||
'CATEGORIES' => '*',
|
'CATEGORIES' => '*',
|
||||||
'COMMENT' => '*',
|
'COMMENT' => '*',
|
||||||
|
'CONFERENCE' => '*',
|
||||||
'CONTACT' => '*',
|
'CONTACT' => '*',
|
||||||
|
'IMAGE' => '*',
|
||||||
'EXDATE' => '*',
|
'EXDATE' => '*',
|
||||||
'REQUEST-STATUS' => '*',
|
'REQUEST-STATUS' => '*',
|
||||||
'RELATED-TO' => '*',
|
'RELATED-TO' => '*',
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ class DateTimeParser
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$date = new \DateTimeImmutable($matches[1].'-'.$matches[2].'-'.$matches[3].' '.$matches[4].':'.$matches[5].':'.$matches[6], $tz);
|
$date = new \DateTimeImmutable($matches[1].'-'.$matches[2].'-'.$matches[3].' '.$matches[4].':'.$matches[5].':'.$matches[6], $tz);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception) {
|
||||||
throw new InvalidDataException('The supplied iCalendar datetime value is incorrect: '.$dt);
|
throw new InvalidDataException('The supplied iCalendar datetime value is incorrect: '.$dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,13 +63,11 @@ class DateTimeParser
|
||||||
throw new InvalidDataException('The supplied iCalendar date value is incorrect: '.$date);
|
throw new InvalidDataException('The supplied iCalendar date value is incorrect: '.$date);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_null($tz)) {
|
$tz ??= new \DateTimeZone('UTC');
|
||||||
$tz = new \DateTimeZone('UTC');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$date = new \DateTimeImmutable($matches[1].'-'.$matches[2].'-'.$matches[3], $tz);
|
$date = new \DateTimeImmutable($matches[1].'-'.$matches[2].'-'.$matches[3], $tz);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception) {
|
||||||
throw new InvalidDataException('The supplied iCalendar date value is incorrect: '.$date);
|
throw new InvalidDataException('The supplied iCalendar date value is incorrect: '.$date);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -205,9 +203,9 @@ class DateTimeParser
|
||||||
return self::parseDuration($date);
|
return self::parseDuration($date);
|
||||||
} elseif (8 === strlen($date)) {
|
} elseif (8 === strlen($date)) {
|
||||||
return self::parseDate($date, $referenceTz);
|
return self::parseDate($date, $referenceTz);
|
||||||
} else {
|
|
||||||
return self::parseDateTime($date, $referenceTz);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return self::parseDateTime($date, $referenceTz);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -329,7 +327,7 @@ class DateTimeParser
|
||||||
|
|
||||||
$result = [];
|
$result = [];
|
||||||
foreach ($parts as $part) {
|
foreach ($parts as $part) {
|
||||||
if (empty($matches[$part])) {
|
if (!array_key_exists($part, $matches) || '' === $matches[$part]) {
|
||||||
$result[$part] = null;
|
$result[$part] = null;
|
||||||
} elseif ('-' === $matches[$part] || '--' === $matches[$part]) {
|
} elseif ('-' === $matches[$part] || '--' === $matches[$part]) {
|
||||||
$result[$part] = null;
|
$result[$part] = null;
|
||||||
|
|
@ -424,7 +422,7 @@ class DateTimeParser
|
||||||
|
|
||||||
$result = [];
|
$result = [];
|
||||||
foreach ($parts as $part) {
|
foreach ($parts as $part) {
|
||||||
if (empty($matches[$part])) {
|
if (!array_key_exists($part, $matches) || '' === $matches[$part]) {
|
||||||
$result[$part] = null;
|
$result[$part] = null;
|
||||||
} elseif ('-' === $matches[$part]) {
|
} elseif ('-' === $matches[$part]) {
|
||||||
$result[$part] = null;
|
$result[$part] = null;
|
||||||
|
|
@ -541,7 +539,7 @@ class DateTimeParser
|
||||||
$parts['year0'] = &$parts['year'];
|
$parts['year0'] = &$parts['year'];
|
||||||
|
|
||||||
foreach ($parts as $part => &$value) {
|
foreach ($parts as $part => &$value) {
|
||||||
if (!empty($matches[$part])) {
|
if (array_key_exists($part, $matches) && '' !== $matches[$part]) {
|
||||||
$value = $matches[$part];
|
$value = $matches[$part];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,10 +118,10 @@ abstract class Document extends Component
|
||||||
public function create(string $name)
|
public function create(string $name)
|
||||||
{
|
{
|
||||||
if (isset(static::$componentMap[strtoupper($name)])) {
|
if (isset(static::$componentMap[strtoupper($name)])) {
|
||||||
return call_user_func_array([$this, 'createComponent'], func_get_args());
|
return $this->createComponent(...func_get_args());
|
||||||
} else {
|
|
||||||
return call_user_func_array([$this, 'createProperty'], func_get_args());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $this->createProperty(...func_get_args());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -146,9 +146,7 @@ abstract class Document extends Component
|
||||||
if (isset(static::$componentMap[$name])) {
|
if (isset(static::$componentMap[$name])) {
|
||||||
$class = static::$componentMap[$name];
|
$class = static::$componentMap[$name];
|
||||||
}
|
}
|
||||||
if (is_null($children)) {
|
$children ??= [];
|
||||||
$children = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return new $class($this, $name, $children, $defaults);
|
return new $class($this, $name, $children, $defaults);
|
||||||
}
|
}
|
||||||
|
|
@ -180,26 +178,29 @@ abstract class Document extends Component
|
||||||
|
|
||||||
$class = null;
|
$class = null;
|
||||||
|
|
||||||
|
// If a VALUE parameter is supplied, we have to use that
|
||||||
|
// According to https://datatracker.ietf.org/doc/html/rfc5545#section-3.2.20
|
||||||
|
// If the property's value is the default value type, then this
|
||||||
|
// parameter need not be specified. However, if the property's
|
||||||
|
// default value type is overridden by some other allowable value
|
||||||
|
// type, then this parameter MUST be specified.
|
||||||
|
if (!$valueType) {
|
||||||
|
$valueType = $parameters['VALUE'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
if ($valueType) {
|
if ($valueType) {
|
||||||
// The valueType argument comes first to figure out the correct
|
// The valueType argument comes first to figure out the correct
|
||||||
// class.
|
// class.
|
||||||
$class = $this->getClassNameForPropertyValue($valueType);
|
$class = $this->getClassNameForPropertyValue($valueType);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_null($class)) {
|
// If the value parameter is not set or set to something we do not recognize
|
||||||
// If a VALUE parameter is supplied, we should use that.
|
// we do not attempt to interpret or parse the datass value as specified in
|
||||||
if (isset($parameters['VALUE'])) {
|
// https://datatracker.ietf.org/doc/html/rfc5545#section-3.2.20
|
||||||
$class = $this->getClassNameForPropertyValue($parameters['VALUE']);
|
// So when we so far did not get a class-name, we use the default for the property
|
||||||
if (is_null($class)) {
|
$class ??= $this->getClassNameForPropertyName($name);
|
||||||
throw new InvalidDataException('Unsupported VALUE parameter for '.$name.' property. You supplied "'.$parameters['VALUE'].'"');
|
|
||||||
}
|
$parameters ??= [];
|
||||||
} else {
|
|
||||||
$class = $this->getClassNameForPropertyName($name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (is_null($parameters)) {
|
|
||||||
$parameters = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return new $class($this, $name, $value, $parameters, $group, $lineIndex, $lineString);
|
return new $class($this, $name, $value, $parameters, $group, $lineIndex, $lineString);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,25 +11,19 @@ namespace Sabre\VObject;
|
||||||
*/
|
*/
|
||||||
class FreeBusyData
|
class FreeBusyData
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Start timestamp.
|
|
||||||
*/
|
|
||||||
protected int $start;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* End timestamp.
|
|
||||||
*/
|
|
||||||
protected int $end;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of free-busy times.
|
* A list of free-busy times.
|
||||||
*/
|
*/
|
||||||
protected array $data;
|
protected array $data;
|
||||||
|
|
||||||
public function __construct(int $start, int $end)
|
public function __construct(/**
|
||||||
|
* Start timestamp.
|
||||||
|
*/
|
||||||
|
protected int $start, /**
|
||||||
|
* End timestamp.
|
||||||
|
*/
|
||||||
|
protected int $end)
|
||||||
{
|
{
|
||||||
$this->start = $start;
|
|
||||||
$this->end = $end;
|
|
||||||
$this->data = [];
|
$this->data = [];
|
||||||
|
|
||||||
$this->data[] = [
|
$this->data[] = [
|
||||||
|
|
|
||||||
|
|
@ -79,9 +79,7 @@ class FreeBusyGenerator
|
||||||
if ($objects) {
|
if ($objects) {
|
||||||
$this->setObjects($objects);
|
$this->setObjects($objects);
|
||||||
}
|
}
|
||||||
if (is_null($timeZone)) {
|
$timeZone ??= new \DateTimeZone('UTC');
|
||||||
$timeZone = new \DateTimeZone('UTC');
|
|
||||||
}
|
|
||||||
$this->setTimeZone($timeZone);
|
$this->setTimeZone($timeZone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -217,7 +215,7 @@ class FreeBusyGenerator
|
||||||
$new = [];
|
$new = [];
|
||||||
|
|
||||||
foreach ($old as $vavail) {
|
foreach ($old as $vavail) {
|
||||||
list($compStart, $compEnd) = $vavail->getEffectiveStartEnd();
|
[$compStart, $compEnd] = $vavail->getEffectiveStartEnd();
|
||||||
|
|
||||||
// We don't care about date-times that are earlier or later than the
|
// We don't care about date-times that are earlier or later than the
|
||||||
// start and end of the freebusy report, so this gets normalized
|
// start and end of the freebusy report, so this gets normalized
|
||||||
|
|
@ -237,7 +235,7 @@ class FreeBusyGenerator
|
||||||
// Going through our existing list of components to see if there's
|
// Going through our existing list of components to see if there's
|
||||||
// a higher priority component that already fully covers this one.
|
// a higher priority component that already fully covers this one.
|
||||||
foreach ($new as $higherVavail) {
|
foreach ($new as $higherVavail) {
|
||||||
list($higherStart, $higherEnd) = $higherVavail->getEffectiveStartEnd();
|
[$higherStart, $higherEnd] = $higherVavail->getEffectiveStartEnd();
|
||||||
if (
|
if (
|
||||||
(is_null($higherStart) || $higherStart < $compStart)
|
(is_null($higherStart) || $higherStart < $compStart)
|
||||||
&& (is_null($higherEnd) || $higherEnd > $compEnd)
|
&& (is_null($higherEnd) || $higherEnd > $compEnd)
|
||||||
|
|
@ -259,7 +257,7 @@ class FreeBusyGenerator
|
||||||
// priority components to override the lower ones.
|
// priority components to override the lower ones.
|
||||||
foreach (array_reverse($new) as $vavail) {
|
foreach (array_reverse($new) as $vavail) {
|
||||||
$busyType = isset($vavail->BUSYTYPE) ? strtoupper($vavail->BUSYTYPE) : 'BUSY-UNAVAILABLE';
|
$busyType = isset($vavail->BUSYTYPE) ? strtoupper($vavail->BUSYTYPE) : 'BUSY-UNAVAILABLE';
|
||||||
list($vavailStart, $vavailEnd) = $vavail->getEffectiveStartEnd();
|
[$vavailStart, $vavailEnd] = $vavail->getEffectiveStartEnd();
|
||||||
|
|
||||||
// Making the component size no larger than the requested free-busy
|
// Making the component size no larger than the requested free-busy
|
||||||
// report range.
|
// report range.
|
||||||
|
|
@ -281,7 +279,7 @@ class FreeBusyGenerator
|
||||||
// Looping over the AVAILABLE components.
|
// Looping over the AVAILABLE components.
|
||||||
if (isset($vavail->AVAILABLE)) {
|
if (isset($vavail->AVAILABLE)) {
|
||||||
foreach ($vavail->AVAILABLE as $available) {
|
foreach ($vavail->AVAILABLE as $available) {
|
||||||
list($availStart, $availEnd) = $available->getEffectiveStartEnd();
|
[$availStart, $availEnd] = $available->getEffectiveStartEnd();
|
||||||
$fbData->add(
|
$fbData->add(
|
||||||
$availStart->getTimeStamp(),
|
$availStart->getTimeStamp(),
|
||||||
$availEnd->getTimeStamp(),
|
$availEnd->getTimeStamp(),
|
||||||
|
|
@ -362,7 +360,7 @@ class FreeBusyGenerator
|
||||||
if ($component->RRULE) {
|
if ($component->RRULE) {
|
||||||
try {
|
try {
|
||||||
$iterator = new EventIterator($object, (string) $component->UID, $this->timeZone);
|
$iterator = new EventIterator($object, (string) $component->UID, $this->timeZone);
|
||||||
} catch (NoInstancesException $e) {
|
} catch (NoInstancesException) {
|
||||||
// This event is recurring, but it doesn't have a single
|
// This event is recurring, but it doesn't have a single
|
||||||
// instance. We are skipping this event from the output
|
// instance. We are skipping this event from the output
|
||||||
// entirely.
|
// entirely.
|
||||||
|
|
@ -437,12 +435,12 @@ class FreeBusyGenerator
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$values = explode(',', $freebusy);
|
$values = explode(',', (string) $freebusy);
|
||||||
foreach ($values as $value) {
|
foreach ($values as $value) {
|
||||||
list($startTime, $endTime) = explode('/', $value);
|
[$startTime, $endTime] = explode('/', $value);
|
||||||
$startTime = DateTimeParser::parseDateTime($startTime);
|
$startTime = DateTimeParser::parseDateTime($startTime);
|
||||||
|
|
||||||
if ('P' === substr($endTime, 0, 1) || '-P' === substr($endTime, 0, 2)) {
|
if (str_starts_with($endTime, 'P') || str_starts_with($endTime, '-P')) {
|
||||||
$duration = DateTimeParser::parseDuration($endTime);
|
$duration = DateTimeParser::parseDuration($endTime);
|
||||||
$endTime = clone $startTime;
|
$endTime = clone $startTime;
|
||||||
$endTime = $endTime->add($duration);
|
$endTime = $endTime->add($duration);
|
||||||
|
|
@ -507,7 +505,7 @@ class FreeBusyGenerator
|
||||||
$vfreebusy->add($dtstamp);
|
$vfreebusy->add($dtstamp);
|
||||||
|
|
||||||
foreach ($fbData->getData() as $busyTime) {
|
foreach ($fbData->getData() as $busyTime) {
|
||||||
$busyType = strtoupper($busyTime['type']);
|
$busyType = strtoupper((string) $busyTime['type']);
|
||||||
|
|
||||||
// Ignoring all the FREE parts, because those are already assumed.
|
// Ignoring all the FREE parts, because those are already assumed.
|
||||||
if ('FREE' === $busyType) {
|
if ('FREE' === $busyType) {
|
||||||
|
|
|
||||||
|
|
@ -228,7 +228,7 @@ class Broker
|
||||||
|
|
||||||
$eventInfo = $oldEventInfo;
|
$eventInfo = $oldEventInfo;
|
||||||
|
|
||||||
if (in_array($eventInfo['organizer'], $userHref)) {
|
if (in_array($eventInfo['organizer'], $userHref, true)) {
|
||||||
// This is an organizer deleting the event.
|
// This is an organizer deleting the event.
|
||||||
$eventInfo['attendees'] = [];
|
$eventInfo['attendees'] = [];
|
||||||
// Increasing the sequence, but only if the organizer deleted
|
// Increasing the sequence, but only if the organizer deleted
|
||||||
|
|
@ -237,7 +237,7 @@ class Broker
|
||||||
} else {
|
} else {
|
||||||
// This is an attendee deleting the event.
|
// This is an attendee deleting the event.
|
||||||
foreach ($eventInfo['attendees'] as $key => $attendee) {
|
foreach ($eventInfo['attendees'] as $key => $attendee) {
|
||||||
if (in_array($attendee['href'], $userHref)) {
|
if (in_array($attendee['href'], $userHref, true)) {
|
||||||
$eventInfo['attendees'][$key]['instances'] = ['master' => ['id' => 'master', 'partstat' => 'DECLINED'],
|
$eventInfo['attendees'][$key]['instances'] = ['master' => ['id' => 'master', 'partstat' => 'DECLINED'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
@ -246,16 +246,29 @@ class Broker
|
||||||
$baseCalendar = $oldCalendar;
|
$baseCalendar = $oldCalendar;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (in_array($eventInfo['organizer'], $userHref)) {
|
// Check if the user is the organizer
|
||||||
|
if (in_array($eventInfo['organizer'], $userHref, true)) {
|
||||||
return $this->parseEventForOrganizer($baseCalendar, $eventInfo, $oldEventInfo);
|
return $this->parseEventForOrganizer($baseCalendar, $eventInfo, $oldEventInfo);
|
||||||
} elseif ($oldCalendar) {
|
}
|
||||||
// We need to figure out if the user is an attendee, but we're only
|
|
||||||
// doing so if there's an oldCalendar, because we only want to
|
// Check if the user is an attendee
|
||||||
// process updates, not creation of new events.
|
foreach ($eventInfo['attendees'] as $attendee) {
|
||||||
foreach ($eventInfo['attendees'] as $attendee) {
|
if (in_array($attendee['href'], $userHref, true)) {
|
||||||
if (in_array($attendee['href'], $userHref)) {
|
// If this is a event update, we always generate a reply
|
||||||
|
if ($oldCalendar) {
|
||||||
return $this->parseEventForAttendee($baseCalendar, $eventInfo, $oldEventInfo, $attendee['href']);
|
return $this->parseEventForAttendee($baseCalendar, $eventInfo, $oldEventInfo, $attendee['href']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this is a new event, we only generate a reply if the participation status is set
|
||||||
|
foreach ($attendee['instances'] as $instance) {
|
||||||
|
if (isset($instance['partstat']) && 'NEEDS-ACTION' !== $instance['partstat']) {
|
||||||
|
// Attendee has responded (ACCEPTED/DECLINED/TENTATIVE) - generate REPLY
|
||||||
|
return $this->parseEventForAttendee($baseCalendar, $eventInfo, $oldEventInfo, $attendee['href']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// User is attendee but no response to process
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -341,7 +354,7 @@ class Broker
|
||||||
$instances[$recurId] = $attendee['PARTSTAT']->getValue();
|
$instances[$recurId] = $attendee['PARTSTAT']->getValue();
|
||||||
if (isset($vevent->{'REQUEST-STATUS'})) {
|
if (isset($vevent->{'REQUEST-STATUS'})) {
|
||||||
$requestStatus = $vevent->{'REQUEST-STATUS'}->getValue();
|
$requestStatus = $vevent->{'REQUEST-STATUS'}->getValue();
|
||||||
list($requestStatus) = explode(';', $requestStatus);
|
[$requestStatus] = explode(';', $requestStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -479,7 +492,6 @@ class Broker
|
||||||
}
|
}
|
||||||
|
|
||||||
$messages = [];
|
$messages = [];
|
||||||
|
|
||||||
foreach ($attendees as $attendee) {
|
foreach ($attendees as $attendee) {
|
||||||
// An organizer can also be an attendee. We should not generate any
|
// An organizer can also be an attendee. We should not generate any
|
||||||
// messages for those.
|
// messages for those.
|
||||||
|
|
@ -554,7 +566,7 @@ class Broker
|
||||||
|
|
||||||
$message->significantChange =
|
$message->significantChange =
|
||||||
'REQUEST' === $attendee['forceSend']
|
'REQUEST' === $attendee['forceSend']
|
||||||
|| count($oldAttendeeInstances) != count($newAttendeeInstances)
|
|| count($oldAttendeeInstances) !== count($newAttendeeInstances)
|
||||||
|| count(array_diff($oldAttendeeInstances, $newAttendeeInstances)) > 0
|
|| count(array_diff($oldAttendeeInstances, $newAttendeeInstances)) > 0
|
||||||
|| $oldEventInfo['significantChangeHash'] !== $eventInfo['significantChangeHash'];
|
|| $oldEventInfo['significantChangeHash'] !== $eventInfo['significantChangeHash'];
|
||||||
|
|
||||||
|
|
@ -564,9 +576,9 @@ class Broker
|
||||||
// We need to find a list of events that the attendee
|
// We need to find a list of events that the attendee
|
||||||
// is not a part of to add to the list of exceptions.
|
// is not a part of to add to the list of exceptions.
|
||||||
$exceptions = [];
|
$exceptions = [];
|
||||||
foreach ($eventInfo['instances'] as $instanceId => $vevent) {
|
foreach ($eventInfo['instances'] as $eventInstanceId => $vevent) {
|
||||||
if (!isset($attendee['newInstances'][$instanceId])) {
|
if (!isset($attendee['newInstances'][$eventInstanceId])) {
|
||||||
$exceptions[] = $instanceId;
|
$exceptions[] = $eventInstanceId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -580,6 +592,9 @@ class Broker
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
$currentEvent->EXDATE = $exceptions;
|
$currentEvent->EXDATE = $exceptions;
|
||||||
|
if ($currentEvent->DTSTART['TZID']) {
|
||||||
|
$currentEvent->EXDATE['TZID'] = clone $currentEvent->DTSTART['TZID'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -588,15 +603,13 @@ class Broker
|
||||||
unset($currentEvent->ORGANIZER['SCHEDULE-FORCE-SEND']);
|
unset($currentEvent->ORGANIZER['SCHEDULE-FORCE-SEND']);
|
||||||
unset($currentEvent->ORGANIZER['SCHEDULE-STATUS']);
|
unset($currentEvent->ORGANIZER['SCHEDULE-STATUS']);
|
||||||
|
|
||||||
foreach ($currentEvent->ATTENDEE as $attendee) {
|
foreach ($currentEvent->ATTENDEE as $currentEventAttendee) {
|
||||||
unset($attendee['SCHEDULE-FORCE-SEND']);
|
unset($currentEventAttendee['SCHEDULE-FORCE-SEND']);
|
||||||
unset($attendee['SCHEDULE-STATUS']);
|
unset($currentEventAttendee['SCHEDULE-STATUS']);
|
||||||
|
|
||||||
// We're adding PARTSTAT=NEEDS-ACTION to ensure that
|
// We're adding PARTSTAT=NEEDS-ACTION to ensure that
|
||||||
// iOS shows an "Inbox Item"
|
// iOS shows an "Inbox Item"
|
||||||
if (!isset($attendee['PARTSTAT'])) {
|
$currentEventAttendee['PARTSTAT'] ??= 'NEEDS-ACTION';
|
||||||
$attendee['PARTSTAT'] = 'NEEDS-ACTION';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -633,7 +646,12 @@ class Broker
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$oldInstances = !empty($oldEventInfo['attendees'][$attendee]['instances']) ?
|
$oldInstances =
|
||||||
|
is_array($oldEventInfo['attendees'])
|
||||||
|
&& array_key_exists($attendee, $oldEventInfo['attendees'])
|
||||||
|
&& is_array($oldEventInfo['attendees'][$attendee])
|
||||||
|
&& array_key_exists('instances', $oldEventInfo['attendees'][$attendee])
|
||||||
|
&& is_array($oldEventInfo['attendees'][$attendee]['instances']) ?
|
||||||
$oldEventInfo['attendees'][$attendee]['instances'] :
|
$oldEventInfo['attendees'][$attendee]['instances'] :
|
||||||
[];
|
[];
|
||||||
|
|
||||||
|
|
@ -664,7 +682,7 @@ class Broker
|
||||||
// We only need to do that though, if the master event is not declined.
|
// We only need to do that though, if the master event is not declined.
|
||||||
if (isset($instances['master']) && 'DECLINED' !== $instances['master']['newstatus']) {
|
if (isset($instances['master']) && 'DECLINED' !== $instances['master']['newstatus']) {
|
||||||
foreach ($eventInfo['exdate'] as $exDate) {
|
foreach ($eventInfo['exdate'] as $exDate) {
|
||||||
if (!in_array($exDate, $oldEventInfo['exdate'])) {
|
if (!in_array($exDate, $oldEventInfo['exdate'] ?? [], true)) {
|
||||||
if (isset($instances[$exDate])) {
|
if (isset($instances[$exDate])) {
|
||||||
$instances[$exDate]['newstatus'] = 'DECLINED';
|
$instances[$exDate]['newstatus'] = 'DECLINED';
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -707,7 +725,7 @@ class Broker
|
||||||
$hasReply = false;
|
$hasReply = false;
|
||||||
|
|
||||||
foreach ($instances as $instance) {
|
foreach ($instances as $instance) {
|
||||||
if ($instance['oldstatus'] == $instance['newstatus'] && 'REPLY' !== $eventInfo['organizerForceSend']) {
|
if ($instance['oldstatus'] === $instance['newstatus'] && 'REPLY' !== $eventInfo['organizerForceSend']) {
|
||||||
// Skip
|
// Skip
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -739,7 +757,7 @@ class Broker
|
||||||
// EXDATE
|
// EXDATE
|
||||||
$dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
|
$dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
|
||||||
// Treat is as a DATE field
|
// Treat is as a DATE field
|
||||||
if (strlen($instance['id']) <= 8) {
|
if (strlen((string) $instance['id']) <= 8) {
|
||||||
$event->add('DTSTART', $dt, ['VALUE' => 'DATE']);
|
$event->add('DTSTART', $dt, ['VALUE' => 'DATE']);
|
||||||
} else {
|
} else {
|
||||||
$event->add('DTSTART', $dt);
|
$event->add('DTSTART', $dt);
|
||||||
|
|
@ -751,7 +769,7 @@ class Broker
|
||||||
if ('master' !== $instance['id']) {
|
if ('master' !== $instance['id']) {
|
||||||
$dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
|
$dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
|
||||||
// Treat is as a DATE field
|
// Treat is as a DATE field
|
||||||
if (strlen($instance['id']) <= 8) {
|
if (strlen((string) $instance['id']) <= 8) {
|
||||||
$event->add('RECURRENCE-ID', $dt, ['VALUE' => 'DATE']);
|
$event->add('RECURRENCE-ID', $dt, ['VALUE' => 'DATE']);
|
||||||
} else {
|
} else {
|
||||||
$event->add('RECURRENCE-ID', $dt);
|
$event->add('RECURRENCE-ID', $dt);
|
||||||
|
|
@ -774,9 +792,9 @@ class Broker
|
||||||
$message->message = $icalMsg;
|
$message->message = $icalMsg;
|
||||||
|
|
||||||
return [$message];
|
return [$message];
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -843,7 +861,7 @@ class Broker
|
||||||
$organizer = $vevent->ORGANIZER->getNormalizedValue();
|
$organizer = $vevent->ORGANIZER->getNormalizedValue();
|
||||||
$organizerName = $vevent->ORGANIZER['CN'] ?? null;
|
$organizerName = $vevent->ORGANIZER['CN'] ?? null;
|
||||||
} else {
|
} else {
|
||||||
if (strtoupper($organizer) !== strtoupper($vevent->ORGANIZER->getNormalizedValue())) {
|
if (strtoupper($organizer) !== strtoupper((string) $vevent->ORGANIZER->getNormalizedValue())) {
|
||||||
throw new SameOrganizerForAllComponentsException('Every instance of the event must have the same organizer.');
|
throw new SameOrganizerForAllComponentsException('Every instance of the event must have the same organizer.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -869,7 +887,7 @@ class Broker
|
||||||
foreach ($vevent->select('RRULE') as $rr) {
|
foreach ($vevent->select('RRULE') as $rr) {
|
||||||
foreach ($rr->getParts() as $key => $val) {
|
foreach ($rr->getParts() as $key => $val) {
|
||||||
// ignore default values (https://github.com/sabre-io/vobject/issues/126)
|
// ignore default values (https://github.com/sabre-io/vobject/issues/126)
|
||||||
if ('INTERVAL' === $key && 1 == $val) {
|
if ('INTERVAL' === $key && '1' === (string) $val) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (is_array($val)) {
|
if (is_array($val)) {
|
||||||
|
|
@ -881,7 +899,7 @@ class Broker
|
||||||
sort($rrule);
|
sort($rrule);
|
||||||
}
|
}
|
||||||
if (isset($vevent->STATUS)) {
|
if (isset($vevent->STATUS)) {
|
||||||
$status = strtoupper($vevent->STATUS->getValue());
|
$status = strtoupper((string) $vevent->STATUS->getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
$recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
|
$recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
|
||||||
|
|
@ -892,11 +910,14 @@ class Broker
|
||||||
$timezone = $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimeZone();
|
$timezone = $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimeZone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$instances[$recurId] = $vevent;
|
||||||
|
|
||||||
if (isset($vevent->ATTENDEE)) {
|
if (isset($vevent->ATTENDEE)) {
|
||||||
foreach ($vevent->ATTENDEE as $attendee) {
|
foreach ($vevent->ATTENDEE as $attendee) {
|
||||||
if ($this->scheduleAgentServerRules
|
if ($this->scheduleAgentServerRules
|
||||||
&& isset($attendee['SCHEDULE-AGENT'])
|
&& isset($attendee['SCHEDULE-AGENT'])
|
||||||
&& 'CLIENT' === strtoupper($attendee['SCHEDULE-AGENT']->getValue())
|
&& 'CLIENT' === strtoupper((string) $attendee['SCHEDULE-AGENT']->getValue())
|
||||||
) {
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -930,10 +951,10 @@ class Broker
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$instances[$recurId] = $vevent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->significantChangeProperties as $prop) {
|
foreach ($this->significantChangeProperties as $prop) {
|
||||||
|
// @phpstan-ignore property.dynamicName
|
||||||
if (isset($vevent->$prop)) {
|
if (isset($vevent->$prop)) {
|
||||||
$propertyValues = $vevent->select($prop);
|
$propertyValues = $vevent->select($prop);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,12 @@ class Message
|
||||||
* Contains the ITip method, which is something like REQUEST, REPLY or
|
* Contains the ITip method, which is something like REQUEST, REPLY or
|
||||||
* CANCEL.
|
* CANCEL.
|
||||||
*/
|
*/
|
||||||
public ?string $method;
|
public ?string $method = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current sequence number for the event.
|
* The current sequence number for the event.
|
||||||
*/
|
*/
|
||||||
public ?int $sequence;
|
public ?int $sequence = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The senders' email address.
|
* The senders' email address.
|
||||||
|
|
@ -52,7 +52,7 @@ class Message
|
||||||
* The name of the sender. This is often populated from a CN parameter from
|
* The name of the sender. This is often populated from a CN parameter from
|
||||||
* either the ORGANIZER or ATTENDEE, depending on the message.
|
* either the ORGANIZER or ATTENDEE, depending on the message.
|
||||||
*/
|
*/
|
||||||
public ?string $senderName;
|
public ?string $senderName = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The recipient's email address.
|
* The recipient's email address.
|
||||||
|
|
@ -63,7 +63,7 @@ class Message
|
||||||
* The name of the recipient. This is usually populated with the CN
|
* The name of the recipient. This is usually populated with the CN
|
||||||
* parameter from the ATTENDEE or ORGANIZER property, if it's available.
|
* parameter from the ATTENDEE or ORGANIZER property, if it's available.
|
||||||
*/
|
*/
|
||||||
public ?string $recipientName;
|
public ?string $recipientName = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* After the message has been delivered, this should contain a string such
|
* After the message has been delivered, this should contain a string such
|
||||||
|
|
@ -107,10 +107,9 @@ class Message
|
||||||
{
|
{
|
||||||
if (!$this->scheduleStatus) {
|
if (!$this->scheduleStatus) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
|
||||||
list($scheduleStatus) = explode(';', $this->scheduleStatus);
|
|
||||||
|
|
||||||
return $scheduleStatus;
|
|
||||||
}
|
}
|
||||||
|
[$scheduleStatus] = explode(';', $this->scheduleStatus);
|
||||||
|
|
||||||
|
return $scheduleStatus;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable, \Js
|
||||||
/**
|
/**
|
||||||
* Reference to the parent object, if this is not the top object.
|
* Reference to the parent object, if this is not the top object.
|
||||||
*/
|
*/
|
||||||
public ?Node $parent;
|
public ?Node $parent = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterator override.
|
* Iterator override.
|
||||||
|
|
@ -52,7 +52,7 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable, \Js
|
||||||
/**
|
/**
|
||||||
* The root document.
|
* The root document.
|
||||||
*/
|
*/
|
||||||
protected ?Component $root;
|
protected ?Component $root = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serializes the node into a mimedir format.
|
* Serializes the node into a mimedir format.
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ trait PHPUnitAssertions
|
||||||
$input = Reader::read($input);
|
$input = Reader::read($input);
|
||||||
}
|
}
|
||||||
if (!$input instanceof Component) {
|
if (!$input instanceof Component) {
|
||||||
$this->fail('Input must be a string, stream or VObject component');
|
self::fail('Input must be a string, stream or VObject component');
|
||||||
}
|
}
|
||||||
unset($input->PRODID);
|
unset($input->PRODID);
|
||||||
if ($input instanceof Component\VCalendar && 'GREGORIAN' === (string) $input->CALSCALE) {
|
if ($input instanceof Component\VCalendar && 'GREGORIAN' === (string) $input->CALSCALE) {
|
||||||
|
|
@ -51,23 +51,28 @@ trait PHPUnitAssertions
|
||||||
return $input;
|
return $input;
|
||||||
};
|
};
|
||||||
|
|
||||||
$expected = $getObj($expected)->serialize();
|
/**
|
||||||
$actual = $getObj($actual)->serialize();
|
* @var string $expectedSerialized
|
||||||
|
*/
|
||||||
|
$expectedSerialized = $getObj($expected)->serialize();
|
||||||
|
$actualSerialized = $getObj($actual)->serialize();
|
||||||
|
|
||||||
// Finding wildcards in expected.
|
// Finding wildcards in expected.
|
||||||
preg_match_all('|^([A-Z]+):\\*\\*ANY\\*\\*\r$|m', $expected, $matches, PREG_SET_ORDER);
|
$result = preg_match_all('|^([A-Z]+):\\*\\*ANY\\*\\*\r$|m', $expectedSerialized, $matches, PREG_SET_ORDER);
|
||||||
|
|
||||||
|
self::assertNotFalse($result);
|
||||||
|
|
||||||
foreach ($matches as $match) {
|
foreach ($matches as $match) {
|
||||||
$actual = preg_replace(
|
$actualSerialized = preg_replace(
|
||||||
'|^'.preg_quote($match[1], '|').':(.*)\r$|m',
|
'|^'.preg_quote($match[1], '|').':(.*)\r$|m',
|
||||||
$match[1].':**ANY**'."\r",
|
$match[1].':**ANY**'."\r",
|
||||||
$actual
|
(string) $actualSerialized
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->assertEquals(
|
self::assertEquals(
|
||||||
$expected,
|
$expectedSerialized,
|
||||||
$actual,
|
$actualSerialized,
|
||||||
$message
|
$message
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ use Sabre\Xml;
|
||||||
* @author Evert Pot (http://evertpot.com/)
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
* @license http://sabre.io/license/ Modified BSD License
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
class Parameter extends Node
|
class Parameter extends Node implements \Stringable
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Parameter name.
|
* Parameter name.
|
||||||
|
|
@ -74,83 +74,12 @@ class Parameter extends Node
|
||||||
*/
|
*/
|
||||||
public static function guessParameterNameByValue(string $value): string
|
public static function guessParameterNameByValue(string $value): string
|
||||||
{
|
{
|
||||||
switch (strtoupper($value)) {
|
$name = match (strtoupper($value)) {
|
||||||
// Encodings
|
'7-BIT', 'QUOTED-PRINTABLE', 'BASE64' => 'ENCODING',
|
||||||
case '7-BIT':
|
'WORK', 'HOME', 'PREF', 'DOM', 'INTL', 'POSTAL', 'PARCEL', 'VOICE', 'FAX', 'MSG', 'CELL', 'PAGER', 'BBS', 'MODEM', 'CAR', 'ISDN', 'VIDEO', 'AOL', 'APPLELINK', 'ATTMAIL', 'CIS', 'EWORLD', 'INTERNET', 'IBMMAIL', 'MCIMAIL', 'POWERSHARE', 'PRODIGY', 'TLX', 'X400', 'GIF', 'CGM', 'WMF', 'BMP', 'DIB', 'PICT', 'TIFF', 'PDF', 'PS', 'JPEG', 'MPEG', 'MPEG2', 'AVI', 'QTIME', 'WAVE', 'PCM', 'AIFF', 'X509', 'PGP' => 'TYPE',
|
||||||
case 'QUOTED-PRINTABLE':
|
'INLINE', 'URL', 'CONTENT-ID', 'CID' => 'VALUE',
|
||||||
case 'BASE64':
|
default => '',
|
||||||
$name = 'ENCODING';
|
};
|
||||||
break;
|
|
||||||
|
|
||||||
// Common types
|
|
||||||
case 'WORK':
|
|
||||||
case 'HOME':
|
|
||||||
case 'PREF':
|
|
||||||
// Delivery Label Type
|
|
||||||
case 'DOM':
|
|
||||||
case 'INTL':
|
|
||||||
case 'POSTAL':
|
|
||||||
case 'PARCEL':
|
|
||||||
// Telephone types
|
|
||||||
case 'VOICE':
|
|
||||||
case 'FAX':
|
|
||||||
case 'MSG':
|
|
||||||
case 'CELL':
|
|
||||||
case 'PAGER':
|
|
||||||
case 'BBS':
|
|
||||||
case 'MODEM':
|
|
||||||
case 'CAR':
|
|
||||||
case 'ISDN':
|
|
||||||
case 'VIDEO':
|
|
||||||
// EMAIL types (lol)
|
|
||||||
case 'AOL':
|
|
||||||
case 'APPLELINK':
|
|
||||||
case 'ATTMAIL':
|
|
||||||
case 'CIS':
|
|
||||||
case 'EWORLD':
|
|
||||||
case 'INTERNET':
|
|
||||||
case 'IBMMAIL':
|
|
||||||
case 'MCIMAIL':
|
|
||||||
case 'POWERSHARE':
|
|
||||||
case 'PRODIGY':
|
|
||||||
case 'TLX':
|
|
||||||
case 'X400':
|
|
||||||
// Photo / Logo format types
|
|
||||||
case 'GIF':
|
|
||||||
case 'CGM':
|
|
||||||
case 'WMF':
|
|
||||||
case 'BMP':
|
|
||||||
case 'DIB':
|
|
||||||
case 'PICT':
|
|
||||||
case 'TIFF':
|
|
||||||
case 'PDF':
|
|
||||||
case 'PS':
|
|
||||||
case 'JPEG':
|
|
||||||
case 'MPEG':
|
|
||||||
case 'MPEG2':
|
|
||||||
case 'AVI':
|
|
||||||
case 'QTIME':
|
|
||||||
// Sound Digital Audio Type
|
|
||||||
case 'WAVE':
|
|
||||||
case 'PCM':
|
|
||||||
case 'AIFF':
|
|
||||||
// Key types
|
|
||||||
case 'X509':
|
|
||||||
case 'PGP':
|
|
||||||
$name = 'TYPE';
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Value types
|
|
||||||
case 'INLINE':
|
|
||||||
case 'URL':
|
|
||||||
case 'CONTENT-ID':
|
|
||||||
case 'CID':
|
|
||||||
$name = 'VALUE';
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
$name = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $name;
|
return $name;
|
||||||
}
|
}
|
||||||
|
|
@ -177,9 +106,9 @@ class Parameter extends Node
|
||||||
{
|
{
|
||||||
if (is_array($this->value)) {
|
if (is_array($this->value)) {
|
||||||
return implode(',', $this->value);
|
return implode(',', $this->value);
|
||||||
} else {
|
|
||||||
return $this->value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $this->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -201,9 +130,9 @@ class Parameter extends Node
|
||||||
return $this->value;
|
return $this->value;
|
||||||
} elseif (is_null($this->value)) {
|
} elseif (is_null($this->value)) {
|
||||||
return [];
|
return [];
|
||||||
} else {
|
|
||||||
return [$this->value];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return [$this->value];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -234,7 +163,8 @@ class Parameter extends Node
|
||||||
{
|
{
|
||||||
return in_array(
|
return in_array(
|
||||||
strtolower($value),
|
strtolower($value),
|
||||||
array_map('strtolower', (array) $this->value)
|
array_map(strtolower(...), (array) $this->value),
|
||||||
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -278,20 +208,19 @@ class Parameter extends Node
|
||||||
// added + as well.
|
// added + as well.
|
||||||
if (!preg_match('#(?: [\n":;\^,\+] )#x', $item)) {
|
if (!preg_match('#(?: [\n":;\^,\+] )#x', $item)) {
|
||||||
return $out.$item;
|
return $out.$item;
|
||||||
} else {
|
|
||||||
// Enclosing in double-quotes, and using RFC6868 for encoding any
|
|
||||||
// special characters
|
|
||||||
$out .= '"'.strtr(
|
|
||||||
$item,
|
|
||||||
[
|
|
||||||
'^' => '^^',
|
|
||||||
"\n" => '^n',
|
|
||||||
'"' => '^\'',
|
|
||||||
]
|
|
||||||
).'"';
|
|
||||||
|
|
||||||
return $out;
|
|
||||||
}
|
}
|
||||||
|
// Enclosing in double-quotes, and using RFC6868 for encoding any
|
||||||
|
// special characters
|
||||||
|
$out .= '"'.strtr(
|
||||||
|
$item,
|
||||||
|
[
|
||||||
|
'^' => '^^',
|
||||||
|
"\n" => '^n',
|
||||||
|
'"' => '^\'',
|
||||||
|
]
|
||||||
|
).'"';
|
||||||
|
|
||||||
|
return $out;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,12 @@ class Json extends Parser
|
||||||
/**
|
/**
|
||||||
* The input data.
|
* The input data.
|
||||||
*/
|
*/
|
||||||
protected ?array $input;
|
protected ?array $input = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Root component.
|
* Root component.
|
||||||
*/
|
*/
|
||||||
protected ?Document $root;
|
protected ?Document $root = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method starts the parsing process.
|
* This method starts the parsing process.
|
||||||
|
|
@ -61,16 +61,11 @@ class Json extends Parser
|
||||||
$this->options = $options;
|
$this->options = $options;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ($this->input[0]) {
|
$this->root = match ($this->input[0]) {
|
||||||
case 'vcalendar':
|
'vcalendar' => new VCalendar([], false),
|
||||||
$this->root = new VCalendar([], false);
|
'vcard' => new VCard([], false),
|
||||||
break;
|
default => throw new ParseException('The root component must either be a vcalendar, or a vcard'),
|
||||||
case 'vcard':
|
};
|
||||||
$this->root = new VCard([], false);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new ParseException('The root component must either be a vcalendar, or a vcard');
|
|
||||||
}
|
|
||||||
foreach ($this->input[1] as $prop) {
|
foreach ($this->input[1] as $prop) {
|
||||||
$this->root->add($this->parseProperty($prop));
|
$this->root->add($this->parseProperty($prop));
|
||||||
}
|
}
|
||||||
|
|
@ -97,17 +92,13 @@ class Json extends Parser
|
||||||
$self = $this;
|
$self = $this;
|
||||||
|
|
||||||
$properties = array_map(
|
$properties = array_map(
|
||||||
function ($jProp) use ($self) {
|
$self->parseProperty(...),
|
||||||
return $self->parseProperty($jProp);
|
|
||||||
},
|
|
||||||
$jComp[1]
|
$jComp[1]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isset($jComp[2])) {
|
if (isset($jComp[2])) {
|
||||||
$components = array_map(
|
$components = array_map(
|
||||||
function ($jComp) use ($self) {
|
$self->parseComponent(...),
|
||||||
return $self->parseComponent($jComp);
|
|
||||||
},
|
|
||||||
$jComp[2]
|
$jComp[2]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -134,7 +125,7 @@ class Json extends Parser
|
||||||
$valueType,
|
$valueType,
|
||||||
) = $jProp;
|
) = $jProp;
|
||||||
|
|
||||||
$propertyName = strtoupper($propertyName);
|
$propertyName = strtoupper((string) $propertyName);
|
||||||
|
|
||||||
// This is the default class we would be using if we didn't know the
|
// This is the default class we would be using if we didn't know the
|
||||||
// value type. We're using this value later in this function.
|
// value type. We're using this value later in this function.
|
||||||
|
|
@ -144,7 +135,7 @@ class Json extends Parser
|
||||||
|
|
||||||
$value = array_slice($jProp, 3);
|
$value = array_slice($jProp, 3);
|
||||||
|
|
||||||
$valueType = strtoupper($valueType);
|
$valueType = strtoupper((string) $valueType);
|
||||||
|
|
||||||
if (isset($parameters['group'])) {
|
if (isset($parameters['group'])) {
|
||||||
$propertyName = $parameters['group'].'.'.$propertyName;
|
$propertyName = $parameters['group'].'.'.$propertyName;
|
||||||
|
|
@ -169,7 +160,7 @@ class Json extends Parser
|
||||||
// If the value type we received (e.g.: TEXT) was not the default value
|
// If the value type we received (e.g.: TEXT) was not the default value
|
||||||
// type for the given property (e.g.: BDAY), we need to add a VALUE=
|
// type for the given property (e.g.: BDAY), we need to add a VALUE=
|
||||||
// parameter.
|
// parameter.
|
||||||
if ($defaultPropertyClass !== get_class($prop)) {
|
if ($defaultPropertyClass !== $prop::class) {
|
||||||
$prop['VALUE'] = $valueType;
|
$prop['VALUE'] = $valueType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,11 @@ use Sabre\VObject\Property;
|
||||||
*/
|
*/
|
||||||
class MimeDir extends Parser
|
class MimeDir extends Parser
|
||||||
{
|
{
|
||||||
|
public const TOKEN_PROPNAME = 1;
|
||||||
|
public const TOKEN_PROPVALUE = 2;
|
||||||
|
public const TOKEN_PARAMNAME = 3;
|
||||||
|
public const TOKEN_PARAMVALUE = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The input stream.
|
* The input stream.
|
||||||
*
|
*
|
||||||
|
|
@ -36,7 +41,7 @@ class MimeDir extends Parser
|
||||||
/**
|
/**
|
||||||
* Root component.
|
* Root component.
|
||||||
*/
|
*/
|
||||||
protected ?Document $root;
|
protected ?Document $root = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* By default, all input will be assumed to be UTF-8.
|
* By default, all input will be assumed to be UTF-8.
|
||||||
|
|
@ -106,7 +111,7 @@ class MimeDir extends Parser
|
||||||
*/
|
*/
|
||||||
public function setCharset(string $charset): void
|
public function setCharset(string $charset): void
|
||||||
{
|
{
|
||||||
if (!in_array($charset, self::$SUPPORTED_CHARSETS)) {
|
if (!in_array($charset, self::$SUPPORTED_CHARSETS, true)) {
|
||||||
throw new \InvalidArgumentException('Unsupported encoding. (Supported encodings: '.implode(', ', self::$SUPPORTED_CHARSETS).')');
|
throw new \InvalidArgumentException('Unsupported encoding. (Supported encodings: '.implode(', ', self::$SUPPORTED_CHARSETS).')');
|
||||||
}
|
}
|
||||||
$this->charset = $charset;
|
$this->charset = $charset;
|
||||||
|
|
@ -150,23 +155,18 @@ class MimeDir extends Parser
|
||||||
|
|
||||||
// BOM is ZERO WIDTH NO-BREAK SPACE (U+FEFF).
|
// BOM is ZERO WIDTH NO-BREAK SPACE (U+FEFF).
|
||||||
// It's 0xEF 0xBB 0xBF in UTF-8 hex.
|
// It's 0xEF 0xBB 0xBF in UTF-8 hex.
|
||||||
if (3 <= strlen($line)
|
if (3 <= strlen((string) $line)
|
||||||
&& 0xEF === ord($line[0])
|
&& 0xEF === ord($line[0])
|
||||||
&& 0xBB === ord($line[1])
|
&& 0xBB === ord($line[1])
|
||||||
&& 0xBF === ord($line[2])) {
|
&& 0xBF === ord($line[2])) {
|
||||||
$line = \substr($line, 3);
|
$line = \substr((string) $line, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (strtoupper($line)) {
|
$class = match (strtoupper((string) $line)) {
|
||||||
case 'BEGIN:VCALENDAR':
|
'BEGIN:VCALENDAR' => VCalendar::$componentMap['VCALENDAR'],
|
||||||
$class = VCalendar::$componentMap['VCALENDAR'];
|
'BEGIN:VCARD' => VCard::$componentMap['VCARD'],
|
||||||
break;
|
default => throw new ParseException('This parser only supports VCARD and VCALENDAR files'),
|
||||||
case 'BEGIN:VCARD':
|
};
|
||||||
$class = VCard::$componentMap['VCARD'];
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new ParseException('This parser only supports VCARD and VCALENDAR files');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->root = new $class([], false);
|
$this->root = new $class([], false);
|
||||||
|
|
||||||
|
|
@ -174,10 +174,10 @@ class MimeDir extends Parser
|
||||||
// Reading until we hit END:
|
// Reading until we hit END:
|
||||||
try {
|
try {
|
||||||
$line = $this->readLine();
|
$line = $this->readLine();
|
||||||
} catch (EofException $oEx) {
|
} catch (EofException) {
|
||||||
$line = 'END:'.$this->root->name;
|
$line = 'END:'.$this->root->name;
|
||||||
}
|
}
|
||||||
if ('END:' === strtoupper(\substr($line, 0, 4))) {
|
if ('END:' === strtoupper(\substr((string) $line, 0, 4))) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$result = $this->parseLine($line);
|
$result = $this->parseLine($line);
|
||||||
|
|
@ -186,7 +186,7 @@ class MimeDir extends Parser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$name = strtoupper(\substr($line, 4));
|
$name = strtoupper(\substr((string) $line, 4));
|
||||||
if ($name !== $this->root->name) {
|
if ($name !== $this->root->name) {
|
||||||
throw new ParseException('Invalid MimeDir file. expected: "END:'.$this->root->name.'" got: "END:'.$name.'"');
|
throw new ParseException('Invalid MimeDir file. expected: "END:'.$this->root->name.'" got: "END:'.$name.'"');
|
||||||
}
|
}
|
||||||
|
|
@ -215,7 +215,7 @@ class MimeDir extends Parser
|
||||||
while (true) {
|
while (true) {
|
||||||
// Reading until we hit END:
|
// Reading until we hit END:
|
||||||
$line = $this->readLine();
|
$line = $this->readLine();
|
||||||
if ('END:' === strtoupper(\substr($line, 0, 4))) {
|
if ('END:' === strtoupper(\substr((string) $line, 0, 4))) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$result = $this->parseLine($line);
|
$result = $this->parseLine($line);
|
||||||
|
|
@ -224,22 +224,21 @@ class MimeDir extends Parser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$name = strtoupper(\substr($line, 4));
|
$name = strtoupper(\substr((string) $line, 4));
|
||||||
if ($name !== $component->name) {
|
if ($name !== $component->name) {
|
||||||
throw new ParseException('Invalid MimeDir file. expected: "END:'.$component->name.'" got: "END:'.$name.'"');
|
throw new ParseException('Invalid MimeDir file. expected: "END:'.$component->name.'" got: "END:'.$name.'"');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $component;
|
return $component;
|
||||||
} else {
|
|
||||||
// Property reader
|
|
||||||
$property = $this->readProperty($line);
|
|
||||||
if (!$property) {
|
|
||||||
// Ignored line
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $property;
|
|
||||||
}
|
}
|
||||||
|
// Property reader
|
||||||
|
$property = $this->readProperty($line);
|
||||||
|
if (!$property) {
|
||||||
|
// Ignored line
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $property;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -360,6 +359,12 @@ class MimeDir extends Parser
|
||||||
'value' => null,
|
'value' => null,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Keep track on the last token we parsed in order to do
|
||||||
|
* better error checking
|
||||||
|
*/
|
||||||
|
$lastToken = null;
|
||||||
|
|
||||||
$lastParam = null;
|
$lastParam = null;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -385,10 +390,16 @@ class MimeDir extends Parser
|
||||||
// option is set to ignore invalid lines, we ignore this line
|
// option is set to ignore invalid lines, we ignore this line
|
||||||
// This can happen when servers provide faulty data as iCloud
|
// This can happen when servers provide faulty data as iCloud
|
||||||
// frequently does with X-APPLE-STRUCTURED-LOCATION
|
// frequently does with X-APPLE-STRUCTURED-LOCATION
|
||||||
|
$lastToken = self::TOKEN_PARAMVALUE;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw new ParseException('Invalid Mimedir file. Line starting at '.$this->startLine.' did not follow iCalendar/vCard conventions');
|
throw new ParseException('Invalid Mimedir file. Line starting at '.$this->startLine.' did not follow iCalendar/vCard conventions');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ('=' === $match[0][0] && self::TOKEN_PARAMNAME !== $lastToken) {
|
||||||
|
throw new ParseException('Invalid Mimedir file. Line starting at '.$this->startLine.': Missing parameter name for parameter value "'.$match['paramValue'].'"');
|
||||||
|
}
|
||||||
|
|
||||||
if (\is_null($property['parameters'][$lastParam])) {
|
if (\is_null($property['parameters'][$lastParam])) {
|
||||||
$property['parameters'][$lastParam] = $value;
|
$property['parameters'][$lastParam] = $value;
|
||||||
} elseif (is_array($property['parameters'][$lastParam])) {
|
} elseif (is_array($property['parameters'][$lastParam])) {
|
||||||
|
|
@ -402,21 +413,23 @@ class MimeDir extends Parser
|
||||||
$value,
|
$value,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
$lastToken = self::TOKEN_PARAMVALUE;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (isset($match['paramName'])) {
|
if (isset($match['paramName'])) {
|
||||||
$lastParam = strtoupper($match['paramName']);
|
$lastParam = strtoupper($match['paramName']);
|
||||||
if (!isset($property['parameters'][$lastParam])) {
|
$property['parameters'][$lastParam] ??= null;
|
||||||
$property['parameters'][$lastParam] = null;
|
$lastToken = self::TOKEN_PARAMNAME;
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (isset($match['propValue'])) {
|
if (isset($match['propValue'])) {
|
||||||
$property['value'] = $match['propValue'];
|
$property['value'] = $match['propValue'];
|
||||||
|
$lastToken = self::TOKEN_PROPVALUE;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (isset($match['name']) && $match['name']) {
|
if (isset($match['name']) && 0 < strlen($match['name'])) {
|
||||||
$property['name'] = strtoupper($match['name']);
|
$property['name'] = strtoupper($match['name']);
|
||||||
|
$lastToken = self::TOKEN_PROPNAME;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -425,10 +438,8 @@ class MimeDir extends Parser
|
||||||
// @codeCoverageIgnoreEnd
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\is_null($property['value'])) {
|
$property['value'] ??= '';
|
||||||
$property['value'] = '';
|
if (!isset($property['name']) || 0 === strlen($property['name'])) {
|
||||||
}
|
|
||||||
if (!$property['name']) {
|
|
||||||
if ($this->options & self::OPTION_IGNORE_INVALID_LINES) {
|
if ($this->options & self::OPTION_IGNORE_INVALID_LINES) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -671,7 +682,7 @@ class MimeDir extends Parser
|
||||||
// missing a whitespace. So if 'forgiving' is turned on, we will take
|
// missing a whitespace. So if 'forgiving' is turned on, we will take
|
||||||
// those as well.
|
// those as well.
|
||||||
if ($this->options & self::OPTION_FORGIVING) {
|
if ($this->options & self::OPTION_FORGIVING) {
|
||||||
while ('=' === \substr($value, -1) && $this->lineBuffer) {
|
while (str_ends_with($value, '=') && $this->lineBuffer) {
|
||||||
// Reading the line
|
// Reading the line
|
||||||
$this->readLine();
|
$this->readLine();
|
||||||
// Grabbing the raw form
|
// Grabbing the raw form
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,6 @@ abstract class Parser
|
||||||
*/
|
*/
|
||||||
public const OPTION_IGNORE_INVALID_LINES = 2;
|
public const OPTION_IGNORE_INVALID_LINES = 2;
|
||||||
|
|
||||||
/**
|
|
||||||
* Bitmask of parser options.
|
|
||||||
*/
|
|
||||||
protected int $options;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the parser.
|
* Creates the parser.
|
||||||
*
|
*
|
||||||
|
|
@ -42,12 +37,11 @@ abstract class Parser
|
||||||
*
|
*
|
||||||
* @param int $options any parser options (OPTION constants)
|
* @param int $options any parser options (OPTION constants)
|
||||||
*/
|
*/
|
||||||
public function __construct($input = null, int $options = 0)
|
public function __construct($input = null, protected int $options = 0)
|
||||||
{
|
{
|
||||||
if (!is_null($input)) {
|
if (!is_null($input)) {
|
||||||
$this->setInput($input);
|
$this->setInput($input);
|
||||||
}
|
}
|
||||||
$this->options = $options;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -28,17 +28,17 @@ class XML extends Parser
|
||||||
/**
|
/**
|
||||||
* The input data.
|
* The input data.
|
||||||
*/
|
*/
|
||||||
protected ?array $input;
|
protected ?array $input = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A pointer/reference to the input.
|
* A pointer/reference to the input.
|
||||||
*/
|
*/
|
||||||
private ?array $pointer;
|
private ?array $pointer = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Document, root component.
|
* Document, root component.
|
||||||
*/
|
*/
|
||||||
protected ?Document $root;
|
protected ?Document $root = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the parser.
|
* Creates the parser.
|
||||||
|
|
@ -112,7 +112,7 @@ class XML extends Parser
|
||||||
*/
|
*/
|
||||||
protected function parseVCalendarComponents(Component $parentComponent): void
|
protected function parseVCalendarComponents(Component $parentComponent): void
|
||||||
{
|
{
|
||||||
foreach ($this->pointer['value'] ?: [] as $children) {
|
foreach ($this->pointer['value'] ?? [] as $children) {
|
||||||
switch (static::getTagName($children['name'])) {
|
switch (static::getTagName($children['name'])) {
|
||||||
case 'properties':
|
case 'properties':
|
||||||
$this->pointer = &$children['value'];
|
$this->pointer = &$children['value'];
|
||||||
|
|
@ -145,8 +145,8 @@ class XML extends Parser
|
||||||
*/
|
*/
|
||||||
protected function parseProperties(Component $parentComponent, string $propertyNamePrefix = ''): void
|
protected function parseProperties(Component $parentComponent, string $propertyNamePrefix = ''): void
|
||||||
{
|
{
|
||||||
foreach ($this->pointer ?: [] as $xmlProperty) {
|
foreach ($this->pointer ?? [] as $xmlProperty) {
|
||||||
list($namespace, $tagName) = SabreXml\Service::parseClarkNotation($xmlProperty['name']);
|
[$namespace, $tagName] = SabreXml\Service::parseClarkNotation($xmlProperty['name']);
|
||||||
|
|
||||||
$propertyName = $tagName;
|
$propertyName = $tagName;
|
||||||
$propertyValue = [];
|
$propertyValue = [];
|
||||||
|
|
@ -322,7 +322,7 @@ class XML extends Parser
|
||||||
*/
|
*/
|
||||||
protected function parseComponent(Component $parentComponent): void
|
protected function parseComponent(Component $parentComponent): void
|
||||||
{
|
{
|
||||||
$components = $this->pointer['value'] ?: [];
|
$components = $this->pointer['value'] ?? [];
|
||||||
|
|
||||||
foreach ($components as $component) {
|
foreach ($components as $component) {
|
||||||
$componentName = static::getTagName($component['name']);
|
$componentName = static::getTagName($component['name']);
|
||||||
|
|
|
||||||
|
|
@ -14,27 +14,8 @@ use Sabre\Xml;
|
||||||
* @author Evert Pot (http://evertpot.com/)
|
* @author Evert Pot (http://evertpot.com/)
|
||||||
* @license http://sabre.io/license/ Modified BSD License
|
* @license http://sabre.io/license/ Modified BSD License
|
||||||
*/
|
*/
|
||||||
abstract class Property extends Node
|
abstract class Property extends Node implements \Stringable
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* The root document.
|
|
||||||
*/
|
|
||||||
public ?Component $root;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Property name.
|
|
||||||
*
|
|
||||||
* This will contain a string such as DTSTART, SUMMARY, FN.
|
|
||||||
*/
|
|
||||||
public ?string $name;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Property group.
|
|
||||||
*
|
|
||||||
* This is only used in vcards
|
|
||||||
*/
|
|
||||||
public ?string $group;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of parameters.
|
* List of parameters.
|
||||||
*/
|
*/
|
||||||
|
|
@ -75,13 +56,13 @@ abstract class Property extends Node
|
||||||
* @param array $parameters List of parameters
|
* @param array $parameters List of parameters
|
||||||
* @param string|null $group The vcard property group
|
* @param string|null $group The vcard property group
|
||||||
*/
|
*/
|
||||||
public function __construct(Component $root, ?string $name, $value = null, array $parameters = [], ?string $group = null, ?int $lineIndex = null, ?string $lineString = null)
|
public function __construct(public ?Component $root, /**
|
||||||
|
* Property name.
|
||||||
|
*
|
||||||
|
* This will contain a string such as DTSTART, SUMMARY, FN.
|
||||||
|
*/
|
||||||
|
public ?string $name, $value = null, array $parameters = [], public ?string $group = null, ?int $lineIndex = null, ?string $lineString = null)
|
||||||
{
|
{
|
||||||
$this->name = $name;
|
|
||||||
$this->group = $group;
|
|
||||||
|
|
||||||
$this->root = $root;
|
|
||||||
|
|
||||||
foreach ($parameters as $k => $v) {
|
foreach ($parameters as $k => $v) {
|
||||||
$this->add($k, $v);
|
$this->add($k, $v);
|
||||||
}
|
}
|
||||||
|
|
@ -123,7 +104,7 @@ abstract class Property extends Node
|
||||||
public function getValue()
|
public function getValue()
|
||||||
{
|
{
|
||||||
if (is_array($this->value)) {
|
if (is_array($this->value)) {
|
||||||
if (0 == count($this->value)) {
|
if (0 === count($this->value)) {
|
||||||
return null;
|
return null;
|
||||||
} elseif (1 === count($this->value)) {
|
} elseif (1 === count($this->value)) {
|
||||||
return $this->value[0];
|
return $this->value[0];
|
||||||
|
|
@ -155,9 +136,9 @@ abstract class Property extends Node
|
||||||
return [];
|
return [];
|
||||||
} elseif (is_array($this->value)) {
|
} elseif (is_array($this->value)) {
|
||||||
return $this->value;
|
return $this->value;
|
||||||
} else {
|
|
||||||
return [$this->value];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return [$this->value];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -242,7 +223,7 @@ abstract class Property extends Node
|
||||||
);
|
);
|
||||||
|
|
||||||
// remove single space after last CRLF
|
// remove single space after last CRLF
|
||||||
return \substr($str, 0, -1);
|
return \substr((string) $str, 0, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -282,7 +263,7 @@ abstract class Property extends Node
|
||||||
if ('VALUE' === $parameter->name) {
|
if ('VALUE' === $parameter->name) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$parameters[strtolower($parameter->name)] = $parameter->jsonSerialize();
|
$parameters[strtolower((string) $parameter->name)] = $parameter->jsonSerialize();
|
||||||
}
|
}
|
||||||
// In jCard, we need to encode the property-group as a separate 'group'
|
// In jCard, we need to encode the property-group as a separate 'group'
|
||||||
// parameter.
|
// parameter.
|
||||||
|
|
@ -292,7 +273,7 @@ abstract class Property extends Node
|
||||||
|
|
||||||
return array_merge(
|
return array_merge(
|
||||||
[
|
[
|
||||||
strtolower($this->name),
|
strtolower((string) $this->name),
|
||||||
(object) $parameters,
|
(object) $parameters,
|
||||||
strtolower($this->getValueType()),
|
strtolower($this->getValueType()),
|
||||||
],
|
],
|
||||||
|
|
@ -329,13 +310,13 @@ abstract class Property extends Node
|
||||||
$parameters[] = $parameter;
|
$parameters[] = $parameter;
|
||||||
}
|
}
|
||||||
|
|
||||||
$writer->startElement(strtolower($this->name));
|
$writer->startElement(strtolower((string) $this->name));
|
||||||
|
|
||||||
if (!empty($parameters)) {
|
if ([] !== $parameters) {
|
||||||
$writer->startElement('parameters');
|
$writer->startElement('parameters');
|
||||||
|
|
||||||
foreach ($parameters as $parameter) {
|
foreach ($parameters as $parameter) {
|
||||||
$writer->startElement(strtolower($parameter->name));
|
$writer->startElement(strtolower((string) $parameter->name));
|
||||||
$writer->write($parameter);
|
$writer->write($parameter);
|
||||||
$writer->endElement();
|
$writer->endElement();
|
||||||
}
|
}
|
||||||
|
|
@ -520,7 +501,7 @@ abstract class Property extends Node
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checking if the property name does not contain any invalid bytes.
|
// Checking if the property name does not contain any invalid bytes.
|
||||||
if (!preg_match('/^([A-Z0-9-]+)$/', $this->name)) {
|
if (!preg_match('/^([A-Z0-9-]+)$/', (string) $this->name)) {
|
||||||
$warnings[] = [
|
$warnings[] = [
|
||||||
'level' => $options & self::REPAIR ? 1 : 3,
|
'level' => $options & self::REPAIR ? 1 : 3,
|
||||||
'message' => 'The property name: '.$this->name.' contains invalid characters. Only A-Z, 0-9 and - are allowed',
|
'message' => 'The property name: '.$this->name.' contains invalid characters. Only A-Z, 0-9 and - are allowed',
|
||||||
|
|
@ -572,7 +553,7 @@ abstract class Property extends Node
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if ($allowedEncoding && !in_array(strtoupper($encoding), $allowedEncoding)) {
|
if ($allowedEncoding && !in_array(strtoupper($encoding), $allowedEncoding, true)) {
|
||||||
$warnings[] = [
|
$warnings[] = [
|
||||||
'level' => 3,
|
'level' => 3,
|
||||||
'message' => 'ENCODING='.strtoupper($encoding).' is not valid for this document type.',
|
'message' => 'ENCODING='.strtoupper($encoding).' is not valid for this document type.',
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ class Binary extends Property
|
||||||
*/
|
*/
|
||||||
public function setRawMimeDirValue(string $val): void
|
public function setRawMimeDirValue(string $val): void
|
||||||
{
|
{
|
||||||
$this->value = base64_decode($val);
|
$this->value = base64_decode($val, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -62,7 +62,7 @@ class Binary extends Property
|
||||||
*/
|
*/
|
||||||
public function getRawMimeDirValue(): string
|
public function getRawMimeDirValue(): string
|
||||||
{
|
{
|
||||||
return base64_encode($this->value);
|
return base64_encode((string) $this->value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -83,7 +83,7 @@ class Binary extends Property
|
||||||
*/
|
*/
|
||||||
public function getJsonValue(): array
|
public function getJsonValue(): array
|
||||||
{
|
{
|
||||||
return [base64_encode($this->getValue())];
|
return [base64_encode((string) $this->getValue())];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -93,7 +93,7 @@ class Binary extends Property
|
||||||
*/
|
*/
|
||||||
public function setJsonValue(array $value): void
|
public function setJsonValue(array $value): void
|
||||||
{
|
{
|
||||||
$value = array_map('base64_decode', $value);
|
$value = array_map(base64_decode(...), $value);
|
||||||
parent::setJsonValue($value);
|
parent::setJsonValue($value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,9 +56,7 @@ class Boolean extends Property
|
||||||
public function setXmlValue(array $value): void
|
public function setXmlValue(array $value): void
|
||||||
{
|
{
|
||||||
$value = array_map(
|
$value = array_map(
|
||||||
function ($value) {
|
fn ($value) => 'true' === $value,
|
||||||
return 'true' === $value;
|
|
||||||
},
|
|
||||||
$value
|
$value
|
||||||
);
|
);
|
||||||
parent::setXmlValue($value);
|
parent::setXmlValue($value);
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ class FloatValue extends Property
|
||||||
*/
|
*/
|
||||||
public function getJsonValue(): array
|
public function getJsonValue(): array
|
||||||
{
|
{
|
||||||
$val = array_map('floatval', $this->getParts());
|
$val = array_map(floatval(...), $this->getParts());
|
||||||
|
|
||||||
// Special-casing the GEO property.
|
// Special-casing the GEO property.
|
||||||
//
|
//
|
||||||
|
|
@ -86,7 +86,7 @@ class FloatValue extends Property
|
||||||
*/
|
*/
|
||||||
public function setXmlValue(array $value): void
|
public function setXmlValue(array $value): void
|
||||||
{
|
{
|
||||||
$value = array_map('floatval', $value);
|
$value = array_map(floatval(...), $value);
|
||||||
parent::setXmlValue($value);
|
parent::setXmlValue($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,7 +101,7 @@ class FloatValue extends Property
|
||||||
// See:
|
// See:
|
||||||
// http://tools.ietf.org/html/rfc6321#section-3.4.1.2
|
// http://tools.ietf.org/html/rfc6321#section-3.4.1.2
|
||||||
if ('GEO' === $this->name) {
|
if ('GEO' === $this->name) {
|
||||||
$value = array_map('floatval', $this->getParts());
|
$value = array_map(floatval(...), $this->getParts());
|
||||||
|
|
||||||
$writer->writeElement('latitude', $value[0]);
|
$writer->writeElement('latitude', $value[0]);
|
||||||
$writer->writeElement('longitude', $value[1]);
|
$writer->writeElement('longitude', $value[1]);
|
||||||
|
|
|
||||||
|
|
@ -43,10 +43,10 @@ class CalAddress extends Text
|
||||||
public function getNormalizedValue(): string
|
public function getNormalizedValue(): string
|
||||||
{
|
{
|
||||||
$input = $this->getValue();
|
$input = $this->getValue();
|
||||||
if (!strpos($input, ':')) {
|
if (!strpos((string) $input, ':')) {
|
||||||
return $input;
|
return $input;
|
||||||
}
|
}
|
||||||
list($schema, $everythingElse) = explode(':', $input, 2);
|
[$schema, $everythingElse] = explode(':', (string) $input, 2);
|
||||||
$schema = strtolower($schema);
|
$schema = strtolower($schema);
|
||||||
if ('mailto' === $schema) {
|
if ('mailto' === $schema) {
|
||||||
$everythingElse = strtolower($everythingElse);
|
$everythingElse = strtolower($everythingElse);
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ class DateTime extends Property
|
||||||
!$this->hasTime()
|
!$this->hasTime()
|
||||||
|| (
|
|| (
|
||||||
!isset($this['TZID'])
|
!isset($this['TZID'])
|
||||||
&& false === strpos($this->getValue(), 'Z')
|
&& !str_contains((string) $this->getValue(), 'Z')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -205,7 +205,7 @@ class DateTime extends Property
|
||||||
}
|
}
|
||||||
if (is_null($tz)) {
|
if (is_null($tz)) {
|
||||||
$tz = $d->getTimeZone();
|
$tz = $d->getTimeZone();
|
||||||
$isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z', '+00:00']);
|
$isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z', '+00:00'], true);
|
||||||
if (!$isUtc) {
|
if (!$isUtc) {
|
||||||
$this->offsetSet('TZID', $tz->getName());
|
$this->offsetSet('TZID', $tz->getName());
|
||||||
}
|
}
|
||||||
|
|
@ -257,15 +257,15 @@ class DateTime extends Property
|
||||||
$isFloating = $this->isFloating();
|
$isFloating = $this->isFloating();
|
||||||
|
|
||||||
$tz = $dts[0]->getTimeZone();
|
$tz = $dts[0]->getTimeZone();
|
||||||
$isUtc = !$isFloating && in_array($tz->getName(), ['UTC', 'GMT', 'Z']);
|
$isUtc = !$isFloating && in_array($tz->getName(), ['UTC', 'GMT', 'Z'], true);
|
||||||
|
|
||||||
return array_map(
|
return array_map(
|
||||||
function (\DateTimeInterface $dt) use ($hasTime, $isUtc) {
|
function (\DateTimeInterface $dt) use ($hasTime, $isUtc) {
|
||||||
if ($hasTime) {
|
if ($hasTime) {
|
||||||
return $dt->format('Y-m-d\\TH:i:s').($isUtc ? 'Z' : '');
|
return $dt->format('Y-m-d\\TH:i:s').($isUtc ? 'Z' : '');
|
||||||
} else {
|
|
||||||
return $dt->format('Y-m-d');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $dt->format('Y-m-d');
|
||||||
},
|
},
|
||||||
$dts
|
$dts
|
||||||
);
|
);
|
||||||
|
|
@ -286,9 +286,7 @@ class DateTime extends Property
|
||||||
// those.
|
// those.
|
||||||
$this->setValue(
|
$this->setValue(
|
||||||
array_map(
|
array_map(
|
||||||
function ($item) {
|
fn ($item) => strtr($item, [':' => '', '-' => '']),
|
||||||
return strtr($item, [':' => '', '-' => '']);
|
|
||||||
},
|
|
||||||
$value
|
$value
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
@ -306,7 +304,7 @@ class DateTime extends Property
|
||||||
public function offsetSet($offset, $value): void
|
public function offsetSet($offset, $value): void
|
||||||
{
|
{
|
||||||
parent::offsetSet($offset, $value);
|
parent::offsetSet($offset, $value);
|
||||||
if ('VALUE' !== strtoupper($offset)) {
|
if ('VALUE' !== strtoupper((string) $offset)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -347,7 +345,7 @@ class DateTime extends Property
|
||||||
DateTimeParser::parseDateTime($value);
|
DateTimeParser::parseDateTime($value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (InvalidDataException $e) {
|
} catch (InvalidDataException) {
|
||||||
$messages[] = [
|
$messages[] = [
|
||||||
'level' => 3,
|
'level' => 3,
|
||||||
'message' => 'The supplied value ('.$value.') is not a correct '.$valueType,
|
'message' => 'The supplied value ('.$value.') is not a correct '.$valueType,
|
||||||
|
|
|
||||||
|
|
@ -64,14 +64,17 @@ class Period extends Property
|
||||||
public function setJsonValue(array $value): void
|
public function setJsonValue(array $value): void
|
||||||
{
|
{
|
||||||
$value = array_map(
|
$value = array_map(
|
||||||
function ($item) {
|
fn ($item) => strtr(implode('/', $item), [':' => '', '-' => '']),
|
||||||
return strtr(implode('/', $item), [':' => '', '-' => '']);
|
|
||||||
},
|
|
||||||
$value
|
$value
|
||||||
);
|
);
|
||||||
parent::setJsonValue($value);
|
parent::setJsonValue($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function appendUtc(string $strDate)
|
||||||
|
{
|
||||||
|
return !str_ends_with($strDate, 'Z') ? '' : 'Z';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the value, in the format it should be encoded for json.
|
* Returns the value, in the format it should be encoded for json.
|
||||||
*
|
*
|
||||||
|
|
@ -83,21 +86,21 @@ class Period extends Property
|
||||||
{
|
{
|
||||||
$return = [];
|
$return = [];
|
||||||
foreach ($this->getParts() as $item) {
|
foreach ($this->getParts() as $item) {
|
||||||
list($start, $end) = explode('/', $item, 2);
|
[$start, $end] = explode('/', (string) $item, 2);
|
||||||
|
|
||||||
$start = DateTimeParser::parseDateTime($start);
|
$startDt = DateTimeParser::parseDateTime($start)->format('Y-m-d\\TH:i:s').$this->appendUtc($start);
|
||||||
|
|
||||||
// This is a duration value.
|
// This is a duration value.
|
||||||
if ('P' === $end[0]) {
|
if ('P' === $end[0]) {
|
||||||
$return[] = [
|
$return[] = [
|
||||||
$start->format('Y-m-d\\TH:i:s'),
|
$startDt,
|
||||||
$end,
|
$end,
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
$end = DateTimeParser::parseDateTime($end);
|
$endDt = DateTimeParser::parseDateTime($end)->format('Y-m-d\\TH:i:s').$this->appendUtc($end);
|
||||||
$return[] = [
|
$return[] = [
|
||||||
$start->format('Y-m-d\\TH:i:s'),
|
$startDt,
|
||||||
$end->format('Y-m-d\\TH:i:s'),
|
$endDt,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,14 +29,14 @@ class Recur extends Property
|
||||||
/**
|
/**
|
||||||
* Reference to the parent object, if this is not the top object.
|
* Reference to the parent object, if this is not the top object.
|
||||||
*/
|
*/
|
||||||
public ?Node $parent;
|
public ?Node $parent = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the current value.
|
* Updates the current value.
|
||||||
*
|
*
|
||||||
* This may be either a single, or multiple strings in an array.
|
* This may be either a single, or multiple strings in an array.
|
||||||
*
|
*
|
||||||
* @param string|array $value
|
* @param string|array|object $value
|
||||||
*/
|
*/
|
||||||
public function setValue($value): void
|
public function setValue($value): void
|
||||||
{
|
{
|
||||||
|
|
@ -52,17 +52,17 @@ class Recur extends Property
|
||||||
$v = strtoupper($v);
|
$v = strtoupper($v);
|
||||||
|
|
||||||
// The value had multiple sub-values
|
// The value had multiple sub-values
|
||||||
if (false !== strpos($v, ',')) {
|
if (str_contains($v, ',')) {
|
||||||
$v = explode(',', $v);
|
$v = explode(',', $v);
|
||||||
}
|
}
|
||||||
if (0 === strcmp($k, 'until')) {
|
if (0 === strcmp((string) $k, 'until')) {
|
||||||
$v = strtr($v, [':' => '', '-' => '']);
|
$v = strtr($v, [':' => '', '-' => '']);
|
||||||
}
|
}
|
||||||
} elseif (is_array($v)) {
|
} elseif (is_array($v)) {
|
||||||
$v = array_map('strtoupper', $v);
|
$v = array_map(strtoupper(...), $v);
|
||||||
}
|
}
|
||||||
|
|
||||||
$newVal[strtoupper($k)] = $v;
|
$newVal[strtoupper((string) $k)] = $v;
|
||||||
}
|
}
|
||||||
$this->value = $newVal;
|
$this->value = $newVal;
|
||||||
} elseif (is_string($value)) {
|
} elseif (is_string($value)) {
|
||||||
|
|
@ -151,13 +151,13 @@ class Recur extends Property
|
||||||
{
|
{
|
||||||
$values = [];
|
$values = [];
|
||||||
foreach ($this->getParts() as $k => $v) {
|
foreach ($this->getParts() as $k => $v) {
|
||||||
if (0 === strcmp($k, 'UNTIL')) {
|
if (0 === strcmp((string) $k, 'UNTIL')) {
|
||||||
$date = new DateTime($this->root, null, $v);
|
$date = new DateTime($this->root, null, $v);
|
||||||
$values[strtolower($k)] = $date->getJsonValue()[0];
|
$values[strtolower((string) $k)] = $date->getJsonValue()[0];
|
||||||
} elseif (0 === strcmp($k, 'COUNT')) {
|
} elseif (0 === strcmp((string) $k, 'COUNT')) {
|
||||||
$values[strtolower($k)] = intval($v);
|
$values[strtolower((string) $k)] = intval($v);
|
||||||
} else {
|
} else {
|
||||||
$values[strtolower($k)] = $v;
|
$values[strtolower((string) $k)] = $v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,7 +186,7 @@ class Recur extends Property
|
||||||
$newValue = [];
|
$newValue = [];
|
||||||
foreach (explode(';', $value) as $part) {
|
foreach (explode(';', $value) as $part) {
|
||||||
// Skipping empty parts.
|
// Skipping empty parts.
|
||||||
if (empty($part)) {
|
if ('' === $part) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -196,10 +196,10 @@ class Recur extends Property
|
||||||
throw new InvalidDataException('The supplied iCalendar RRULE part is incorrect: '.$part);
|
throw new InvalidDataException('The supplied iCalendar RRULE part is incorrect: '.$part);
|
||||||
}
|
}
|
||||||
|
|
||||||
list($partName, $partValue) = $parts;
|
[$partName, $partValue] = $parts;
|
||||||
|
|
||||||
// The value itself had multiple values..
|
// The value itself had multiple values..
|
||||||
if (false !== strpos($partValue, ',')) {
|
if (str_contains($partValue, ',')) {
|
||||||
$partValue = explode(',', $partValue);
|
$partValue = explode(',', $partValue);
|
||||||
}
|
}
|
||||||
$newValue[$partName] = $partValue;
|
$newValue[$partName] = $partValue;
|
||||||
|
|
@ -243,7 +243,7 @@ class Recur extends Property
|
||||||
if ($repair) {
|
if ($repair) {
|
||||||
unset($values[$key]);
|
unset($values[$key]);
|
||||||
}
|
}
|
||||||
} elseif ('BYMONTH' == $key) {
|
} elseif ('BYMONTH' === $key) {
|
||||||
$byMonth = (array) $value;
|
$byMonth = (array) $value;
|
||||||
foreach ($byMonth as $i => $v) {
|
foreach ($byMonth as $i => $v) {
|
||||||
if (!is_numeric($v) || (int) $v < 1 || (int) $v > 12) {
|
if (!is_numeric($v) || (int) $v < 1 || (int) $v > 12) {
|
||||||
|
|
@ -262,13 +262,13 @@ class Recur extends Property
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if there is no valid entry left, remove the whole value
|
// if there is no valid entry left, remove the whole value
|
||||||
if (is_array($value) && empty($values[$key])) {
|
if (is_array($value) && ([] === $values[$key])) {
|
||||||
unset($values[$key]);
|
unset($values[$key]);
|
||||||
}
|
}
|
||||||
} elseif ('BYWEEKNO' == $key) {
|
} elseif ('BYWEEKNO' === $key) {
|
||||||
$byWeekNo = (array) $value;
|
$byWeekNo = (array) $value;
|
||||||
foreach ($byWeekNo as $i => $v) {
|
foreach ($byWeekNo as $i => $v) {
|
||||||
if (!is_numeric($v) || (int) $v < -53 || 0 == (int) $v || (int) $v > 53) {
|
if (!is_numeric($v) || (int) $v < -53 || 0 === (int) $v || (int) $v > 53) {
|
||||||
$warnings[] = [
|
$warnings[] = [
|
||||||
'level' => $repair ? 1 : 3,
|
'level' => $repair ? 1 : 3,
|
||||||
'message' => 'BYWEEKNO in RRULE must have value(s) from -53 to -1, or 1 to 53!',
|
'message' => 'BYWEEKNO in RRULE must have value(s) from -53 to -1, or 1 to 53!',
|
||||||
|
|
@ -284,13 +284,13 @@ class Recur extends Property
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if there is no valid entry left, remove the whole value
|
// if there is no valid entry left, remove the whole value
|
||||||
if (is_array($value) && empty($values[$key])) {
|
if (is_array($value) && ([] === $values[$key])) {
|
||||||
unset($values[$key]);
|
unset($values[$key]);
|
||||||
}
|
}
|
||||||
} elseif ('BYYEARDAY' == $key) {
|
} elseif ('BYYEARDAY' === $key) {
|
||||||
$byYearDay = (array) $value;
|
$byYearDay = (array) $value;
|
||||||
foreach ($byYearDay as $i => $v) {
|
foreach ($byYearDay as $i => $v) {
|
||||||
if (!is_numeric($v) || (int) $v < -366 || 0 == (int) $v || (int) $v > 366) {
|
if (!is_numeric($v) || (int) $v < -366 || 0 === (int) $v || (int) $v > 366) {
|
||||||
$warnings[] = [
|
$warnings[] = [
|
||||||
'level' => $repair ? 1 : 3,
|
'level' => $repair ? 1 : 3,
|
||||||
'message' => 'BYYEARDAY in RRULE must have value(s) from -366 to -1, or 1 to 366!',
|
'message' => 'BYYEARDAY in RRULE must have value(s) from -366 to -1, or 1 to 366!',
|
||||||
|
|
@ -306,7 +306,7 @@ class Recur extends Property
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if there is no valid entry left, remove the whole value
|
// if there is no valid entry left, remove the whole value
|
||||||
if (is_array($value) && empty($values[$key])) {
|
if (is_array($value) && ([] === $values[$key])) {
|
||||||
unset($values[$key]);
|
unset($values[$key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ class IntegerValue extends Property
|
||||||
*/
|
*/
|
||||||
public function setXmlValue(array $value): void
|
public function setXmlValue(array $value): void
|
||||||
{
|
{
|
||||||
$value = array_map('intval', $value);
|
$value = array_map(intval(...), $value);
|
||||||
parent::setXmlValue($value);
|
parent::setXmlValue($value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ class Text extends Property
|
||||||
// 2. structured value properties
|
// 2. structured value properties
|
||||||
//
|
//
|
||||||
// The former is always separated by a comma, the latter by semicolon.
|
// The former is always separated by a comma, the latter by semicolon.
|
||||||
if (in_array($name, $this->structuredValues)) {
|
if (in_array($name, $this->structuredValues, true)) {
|
||||||
$this->delimiter = ';';
|
$this->delimiter = ';';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,7 +155,7 @@ class Text extends Property
|
||||||
// Structured text values should always be returned as a single
|
// Structured text values should always be returned as a single
|
||||||
// array-item. Multi-value text should be returned as multiple items in
|
// array-item. Multi-value text should be returned as multiple items in
|
||||||
// the top-array.
|
// the top-array.
|
||||||
if (in_array($this->name, $this->structuredValues)) {
|
if (in_array($this->name, $this->structuredValues, true)) {
|
||||||
return [$this->getParts()];
|
return [$this->getParts()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,7 +213,7 @@ class Text extends Property
|
||||||
|
|
||||||
// If the resulting value contains a \n, we must encode it as
|
// If the resulting value contains a \n, we must encode it as
|
||||||
// quoted-printable.
|
// quoted-printable.
|
||||||
if (false !== \strpos($val, "\n")) {
|
if (str_contains((string) $val, "\n")) {
|
||||||
$str .= ';ENCODING=QUOTED-PRINTABLE:';
|
$str .= ';ENCODING=QUOTED-PRINTABLE:';
|
||||||
$lastLine = $str;
|
$lastLine = $str;
|
||||||
$out = '';
|
$out = '';
|
||||||
|
|
@ -222,13 +222,13 @@ class Text extends Property
|
||||||
// encode newlines for us. Specifically, the \r\n sequence must in
|
// encode newlines for us. Specifically, the \r\n sequence must in
|
||||||
// vcards be encoded as =0D=OA and we must insert soft-newlines
|
// vcards be encoded as =0D=OA and we must insert soft-newlines
|
||||||
// every 75 bytes.
|
// every 75 bytes.
|
||||||
for ($ii = 0; $ii < \strlen($val); ++$ii) {
|
for ($ii = 0; $ii < \strlen((string) $val); ++$ii) {
|
||||||
$ord = \ord($val[$ii]);
|
$ord = \ord($val[$ii]);
|
||||||
// These characters are encoded as themselves.
|
// These characters are encoded as themselves.
|
||||||
if ($ord >= 32 && $ord <= 126) {
|
if ($ord >= 32 && $ord <= 126) {
|
||||||
$lastLine .= $val[$ii];
|
$lastLine .= $val[$ii];
|
||||||
} else {
|
} else {
|
||||||
$lastLine .= '='.\strtoupper(\bin2hex($val[$ii]));
|
$lastLine .= '='.\strtoupper(\bin2hex((string) $val[$ii]));
|
||||||
}
|
}
|
||||||
if (\strlen($lastLine) >= 75) {
|
if (\strlen($lastLine) >= 75) {
|
||||||
// Soft line break
|
// Soft line break
|
||||||
|
|
@ -241,22 +241,21 @@ class Text extends Property
|
||||||
}
|
}
|
||||||
|
|
||||||
return $out;
|
return $out;
|
||||||
} else {
|
}
|
||||||
$str .= ':'.$val;
|
$str .= ':'.$val;
|
||||||
|
|
||||||
$str = \preg_replace(
|
$str = \preg_replace(
|
||||||
'/(
|
'/(
|
||||||
(?:^.)? # 1 additional byte in first line because of missing single space (see next line)
|
(?:^.)? # 1 additional byte in first line because of missing single space (see next line)
|
||||||
.{1,74} # max 75 bytes per line (1 byte is used for a single space added after every CRLF)
|
.{1,74} # max 75 bytes per line (1 byte is used for a single space added after every CRLF)
|
||||||
(?![\x80-\xbf]) # prevent splitting multibyte characters
|
(?![\x80-\xbf]) # prevent splitting multibyte characters
|
||||||
)/x',
|
)/x',
|
||||||
"$1\r\n ",
|
"$1\r\n ",
|
||||||
$str
|
$str
|
||||||
);
|
);
|
||||||
|
|
||||||
// remove single space after last CRLF
|
// remove single space after last CRLF
|
||||||
return \substr($str, 0, -1);
|
return \substr((string) $str, 0, -1);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -271,7 +270,7 @@ class Text extends Property
|
||||||
foreach ($items as $i => $item) {
|
foreach ($items as $i => $item) {
|
||||||
$writer->writeElement(
|
$writer->writeElement(
|
||||||
$item,
|
$item,
|
||||||
!empty($values[$i]) ? $values[$i] : null
|
('' !== $values[$i] && [] !== $values[$i]) ? $values[$i] : null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ class Time extends Text
|
||||||
$timeStr .= 'Z';
|
$timeStr .= 'Z';
|
||||||
} else {
|
} else {
|
||||||
$timeStr .=
|
$timeStr .=
|
||||||
preg_replace('/([0-9]{2})([0-9]{2})$/', '$1:$2', $parts['timezone']);
|
preg_replace('/([0-9]{2})([0-9]{2})$/', '$1:$2', (string) $parts['timezone']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,9 +118,7 @@ class Time extends Text
|
||||||
public function setXmlValue(array $value): void
|
public function setXmlValue(array $value): void
|
||||||
{
|
{
|
||||||
$value = array_map(
|
$value = array_map(
|
||||||
function ($value) {
|
fn ($value) => str_replace(':', '', $value),
|
||||||
return str_replace(':', '', $value);
|
|
||||||
},
|
|
||||||
$value
|
$value
|
||||||
);
|
);
|
||||||
parent::setXmlValue($value);
|
parent::setXmlValue($value);
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ class Uri extends Text
|
||||||
public function parameters(): array
|
public function parameters(): array
|
||||||
{
|
{
|
||||||
$parameters = parent::parameters();
|
$parameters = parent::parameters();
|
||||||
if (!isset($parameters['VALUE']) && in_array($this->name, ['URL', 'PHOTO'])) {
|
if (!isset($parameters['VALUE']) && in_array($this->name, ['URL', 'PHOTO'], true)) {
|
||||||
// If we are encoding a URI value, and this URI value has no
|
// If we are encoding a URI value, and this URI value has no
|
||||||
// VALUE=URI parameter, we add it anyway.
|
// VALUE=URI parameter, we add it anyway.
|
||||||
//
|
//
|
||||||
|
|
@ -74,14 +74,10 @@ class Uri extends Text
|
||||||
$matches = preg_split($regex, $val, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
$matches = preg_split($regex, $val, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
||||||
$newVal = '';
|
$newVal = '';
|
||||||
foreach ($matches as $match) {
|
foreach ($matches as $match) {
|
||||||
switch ($match) {
|
match ($match) {
|
||||||
case '\:':
|
'\:' => $newVal .= ':',
|
||||||
$newVal .= ':';
|
default => $newVal .= $match,
|
||||||
break;
|
};
|
||||||
default:
|
|
||||||
$newVal .= $match;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$this->value = $newVal;
|
$this->value = $newVal;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -42,9 +42,7 @@ class UtcOffset extends Text
|
||||||
public function setJsonValue(array $value): void
|
public function setJsonValue(array $value): void
|
||||||
{
|
{
|
||||||
$value = array_map(
|
$value = array_map(
|
||||||
function ($value) {
|
fn ($value) => str_replace(':', '', $value),
|
||||||
return str_replace(':', '', $value);
|
|
||||||
},
|
|
||||||
$value
|
$value
|
||||||
);
|
);
|
||||||
parent::setJsonValue($value);
|
parent::setJsonValue($value);
|
||||||
|
|
@ -58,10 +56,8 @@ class UtcOffset extends Text
|
||||||
public function getJsonValue(): array
|
public function getJsonValue(): array
|
||||||
{
|
{
|
||||||
return array_map(
|
return array_map(
|
||||||
function ($value) {
|
fn ($value) => substr((string) $value, 0, -2).':'.
|
||||||
return substr($value, 0, -2).':'.
|
substr((string) $value, -2),
|
||||||
substr($value, -2);
|
|
||||||
},
|
|
||||||
parent::getJsonValue()
|
parent::getJsonValue()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ class DateAndOrTime extends Property
|
||||||
public function setDateTime(\DateTimeInterface $dt): void
|
public function setDateTime(\DateTimeInterface $dt): void
|
||||||
{
|
{
|
||||||
$tz = $dt->getTimeZone();
|
$tz = $dt->getTimeZone();
|
||||||
$isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z']);
|
$isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z'], true);
|
||||||
|
|
||||||
if ($isUtc) {
|
if ($isUtc) {
|
||||||
$value = $dt->format('Ymd\\THis\\Z');
|
$value = $dt->format('Ymd\\THis\\Z');
|
||||||
|
|
@ -232,14 +232,10 @@ class DateAndOrTime extends Property
|
||||||
$value = '';
|
$value = '';
|
||||||
|
|
||||||
// $d = defined
|
// $d = defined
|
||||||
$d = function ($part) use ($parts): bool {
|
$d = (fn ($part): bool => !is_null($parts[$part]));
|
||||||
return !is_null($parts[$part]);
|
|
||||||
};
|
|
||||||
|
|
||||||
// $r = read
|
// $r = read
|
||||||
$r = function ($part) use ($parts) {
|
$r = (fn ($part) => $parts[$part]);
|
||||||
return $parts[$part];
|
|
||||||
};
|
|
||||||
|
|
||||||
// From the Relax NG Schema.
|
// From the Relax NG Schema.
|
||||||
//
|
//
|
||||||
|
|
@ -341,7 +337,7 @@ class DateAndOrTime extends Property
|
||||||
|
|
||||||
try {
|
try {
|
||||||
DateTimeParser::parseVCardDateTime($value);
|
DateTimeParser::parseVCardDateTime($value);
|
||||||
} catch (InvalidDataException $e) {
|
} catch (InvalidDataException) {
|
||||||
$messages[] = [
|
$messages[] = [
|
||||||
'level' => 3,
|
'level' => 3,
|
||||||
'message' => 'The supplied value ('.$value.') is not a correct DATE-AND-OR-TIME property',
|
'message' => 'The supplied value ('.$value.') is not a correct DATE-AND-OR-TIME property',
|
||||||
|
|
|
||||||
|
|
@ -91,9 +91,7 @@ class EventIterator implements \Iterator
|
||||||
*/
|
*/
|
||||||
public function __construct($input, ?string $uid = null, ?\DateTimeZone $timeZone = null)
|
public function __construct($input, ?string $uid = null, ?\DateTimeZone $timeZone = null)
|
||||||
{
|
{
|
||||||
if (is_null($timeZone)) {
|
$timeZone ??= new \DateTimeZone('UTC');
|
||||||
$timeZone = new \DateTimeZone('UTC');
|
|
||||||
}
|
|
||||||
$this->timeZone = $timeZone;
|
$this->timeZone = $timeZone;
|
||||||
|
|
||||||
if (is_array($input)) {
|
if (is_array($input)) {
|
||||||
|
|
@ -164,8 +162,12 @@ class EventIterator implements \Iterator
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($this->masterEvent->RDATE)) {
|
if (isset($this->masterEvent->RDATE)) {
|
||||||
|
$rdateValues = [];
|
||||||
|
foreach ($this->masterEvent->RDATE as $rdate) {
|
||||||
|
$rdateValues = array_merge($rdateValues, $rdate->getParts());
|
||||||
|
}
|
||||||
$this->recurIterator = new RDateIterator(
|
$this->recurIterator = new RDateIterator(
|
||||||
$this->masterEvent->RDATE->getParts(),
|
$rdateValues,
|
||||||
$this->startDate
|
$this->startDate
|
||||||
);
|
);
|
||||||
} elseif (isset($this->masterEvent->RRULE)) {
|
} elseif (isset($this->masterEvent->RRULE)) {
|
||||||
|
|
@ -228,11 +230,10 @@ class EventIterator implements \Iterator
|
||||||
}
|
}
|
||||||
if ($this->currentOverriddenEvent && $this->currentOverriddenEvent->DTEND) {
|
if ($this->currentOverriddenEvent && $this->currentOverriddenEvent->DTEND) {
|
||||||
return $this->currentOverriddenEvent->DTEND->getDateTime($this->timeZone);
|
return $this->currentOverriddenEvent->DTEND->getDateTime($this->timeZone);
|
||||||
} else {
|
|
||||||
$end = clone $this->currentDate;
|
|
||||||
|
|
||||||
return $end->modify('+'.$this->eventDuration.' seconds');
|
|
||||||
}
|
}
|
||||||
|
$end = clone $this->currentDate;
|
||||||
|
|
||||||
|
return $end->modify('+'.$this->eventDuration.' seconds');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,13 @@ class RDateIterator implements \Iterator
|
||||||
*
|
*
|
||||||
* @param string|array $rrule
|
* @param string|array $rrule
|
||||||
*/
|
*/
|
||||||
public function __construct($rrule, \DateTimeInterface $start)
|
public function __construct($rrule, /**
|
||||||
|
* The reference start date/time for the rrule.
|
||||||
|
*
|
||||||
|
* All calculations are based on this initial date.
|
||||||
|
*/
|
||||||
|
protected \DateTimeInterface $startDate)
|
||||||
{
|
{
|
||||||
$this->startDate = $start;
|
|
||||||
$this->parseRDate($rrule);
|
$this->parseRDate($rrule);
|
||||||
$this->currentDate = clone $this->startDate;
|
$this->currentDate = clone $this->startDate;
|
||||||
}
|
}
|
||||||
|
|
@ -117,13 +121,6 @@ class RDateIterator implements \Iterator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The reference start date/time for the rrule.
|
|
||||||
*
|
|
||||||
* All calculations are based on this initial date.
|
|
||||||
*/
|
|
||||||
protected \DateTimeInterface $startDate;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The date of the current iteration. You can get this by calling
|
* The date of the current iteration. You can get this by calling
|
||||||
* ->current().
|
* ->current().
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,13 @@ class RRuleIterator implements \Iterator
|
||||||
*
|
*
|
||||||
* @throws InvalidDataException
|
* @throws InvalidDataException
|
||||||
*/
|
*/
|
||||||
public function __construct($rrule, \DateTimeInterface $start)
|
public function __construct($rrule, /**
|
||||||
|
* The reference start date/time for the rrule.
|
||||||
|
*
|
||||||
|
* All calculations are based on this initial date.
|
||||||
|
*/
|
||||||
|
protected \DateTimeInterface $startDate)
|
||||||
{
|
{
|
||||||
$this->startDate = $start;
|
|
||||||
$this->parseRRule($rrule);
|
$this->parseRRule($rrule);
|
||||||
$this->currentDate = clone $this->startDate;
|
$this->currentDate = clone $this->startDate;
|
||||||
}
|
}
|
||||||
|
|
@ -146,13 +150,6 @@ class RRuleIterator implements \Iterator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The reference start date/time for the rrule.
|
|
||||||
*
|
|
||||||
* All calculations are based on this initial date.
|
|
||||||
*/
|
|
||||||
protected \DateTimeInterface $startDate;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The date of the current iteration. You can get this by calling
|
* The date of the current iteration. You can get this by calling
|
||||||
* ->current().
|
* ->current().
|
||||||
|
|
@ -357,23 +354,23 @@ class RRuleIterator implements \Iterator
|
||||||
}
|
}
|
||||||
|
|
||||||
$recurrenceHours = [];
|
$recurrenceHours = [];
|
||||||
if (!empty($this->byHour)) {
|
if ($this->byHour) {
|
||||||
$recurrenceHours = $this->getHours();
|
$recurrenceHours = $this->getHours();
|
||||||
}
|
}
|
||||||
|
|
||||||
$recurrenceDays = [];
|
$recurrenceDays = [];
|
||||||
if (!empty($this->byDay)) {
|
if ($this->byDay) {
|
||||||
$recurrenceDays = $this->getDays();
|
$recurrenceDays = $this->getDays();
|
||||||
}
|
}
|
||||||
|
|
||||||
$recurrenceMonths = [];
|
$recurrenceMonths = [];
|
||||||
if (!empty($this->byMonth)) {
|
if ($this->byMonth) {
|
||||||
$recurrenceMonths = $this->getMonths();
|
$recurrenceMonths = $this->getMonths();
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if ($this->byHour) {
|
if ($this->byHour) {
|
||||||
if ('23' == $this->currentDate->format('G')) {
|
if ('23' === $this->currentDate->format('G')) {
|
||||||
// to obey the interval rule
|
// to obey the interval rule
|
||||||
$this->currentDate = $this->currentDate->modify('+'.($this->interval - 1).' days');
|
$this->currentDate = $this->currentDate->modify('+'.($this->interval - 1).' days');
|
||||||
}
|
}
|
||||||
|
|
@ -398,9 +395,9 @@ class RRuleIterator implements \Iterator
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} while (
|
} while (
|
||||||
($this->byDay && !in_array($currentDay, $recurrenceDays))
|
($this->byDay && !in_array((int) $currentDay, $recurrenceDays, true))
|
||||||
|| ($this->byHour && !in_array($currentHour, $recurrenceHours))
|
|| ($this->byHour && !in_array($currentHour, $recurrenceHours, true))
|
||||||
|| ($this->byMonth && !in_array($currentMonth, $recurrenceMonths))
|
|| ($this->byMonth && !in_array($currentMonth, $recurrenceMonths, true))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -442,18 +439,18 @@ class RRuleIterator implements \Iterator
|
||||||
$currentHour = (int) $this->currentDate->format('G');
|
$currentHour = (int) $this->currentDate->format('G');
|
||||||
|
|
||||||
// We need to roll over to the next week
|
// We need to roll over to the next week
|
||||||
if ($currentDay === $firstDay && (!$this->byHour || '0' == $currentHour)) {
|
if ($currentDay === $firstDay && (!$this->byHour || 0 === $currentHour)) {
|
||||||
$this->currentDate = $this->currentDate->modify('+'.($this->interval - 1).' weeks');
|
$this->currentDate = $this->currentDate->modify('+'.($this->interval - 1).' weeks');
|
||||||
|
|
||||||
// We need to go to the first day of this week, but only if we
|
// We need to go to the first day of this week, but only if we
|
||||||
// are not already on this first day of this week.
|
// are not already on this first day of this week.
|
||||||
if ($this->currentDate->format('w') != $firstDay) {
|
if ((int) $this->currentDate->format('w') !== $firstDay) {
|
||||||
$this->currentDate = $this->currentDate->modify('last '.$this->dayNames[$this->dayMap[$this->weekStart]]);
|
$this->currentDate = $this->currentDate->modify('last '.$this->dayNames[$this->dayMap[$this->weekStart]]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We have a match
|
// We have a match
|
||||||
} while (($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byHour && !in_array($currentHour, $recurrenceHours)));
|
} while (($this->byDay && !in_array($currentDay, $recurrenceDays, true)) || ($this->byHour && !in_array((string) $currentHour, $recurrenceHours, true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -476,14 +473,14 @@ class RRuleIterator implements \Iterator
|
||||||
++$increase;
|
++$increase;
|
||||||
$tempDate = clone $this->currentDate;
|
$tempDate = clone $this->currentDate;
|
||||||
$tempDate = $tempDate->modify('+ '.($this->interval * $increase).' months '.$this->startTime());
|
$tempDate = $tempDate->modify('+ '.($this->interval * $increase).' months '.$this->startTime());
|
||||||
} while ($tempDate->format('j') != $currentDayOfMonth);
|
} while ($tempDate->format('j') !== $currentDayOfMonth);
|
||||||
$this->currentDate = $tempDate;
|
$this->currentDate = $tempDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$occurrence = -1;
|
$selectedOccurrence = -1;
|
||||||
while (true) {
|
while (true) {
|
||||||
$occurrences = $this->getMonthlyOccurrences();
|
$occurrences = $this->getMonthlyOccurrences();
|
||||||
|
|
||||||
|
|
@ -491,6 +488,7 @@ class RRuleIterator implements \Iterator
|
||||||
// The first occurrence that's higher than the current
|
// The first occurrence that's higher than the current
|
||||||
// day of the month wins.
|
// day of the month wins.
|
||||||
if ($occurrence > $currentDayOfMonth) {
|
if ($occurrence > $currentDayOfMonth) {
|
||||||
|
$selectedOccurrence = $occurrence;
|
||||||
break 2;
|
break 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -533,7 +531,7 @@ class RRuleIterator implements \Iterator
|
||||||
$this->currentDate = $this->currentDate->setDate(
|
$this->currentDate = $this->currentDate->setDate(
|
||||||
(int) $this->currentDate->format('Y'),
|
(int) $this->currentDate->format('Y'),
|
||||||
(int) $this->currentDate->format('n'),
|
(int) $this->currentDate->format('n'),
|
||||||
(int) $occurrence
|
(int) $selectedOccurrence
|
||||||
)->modify($this->startTime());
|
)->modify($this->startTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -547,9 +545,9 @@ class RRuleIterator implements \Iterator
|
||||||
$currentDayOfMonth = $this->currentDate->format('j');
|
$currentDayOfMonth = $this->currentDate->format('j');
|
||||||
|
|
||||||
// No sub-rules, so we just advance by year
|
// No sub-rules, so we just advance by year
|
||||||
if (empty($this->byMonth)) {
|
if (!$this->byMonth) {
|
||||||
// Unless it was a leap day!
|
// Unless it was a leap day!
|
||||||
if (2 == $currentMonth && 29 == $currentDayOfMonth) {
|
if (2 === (int) $currentMonth && 29 === (int) $currentDayOfMonth) {
|
||||||
$counter = 0;
|
$counter = 0;
|
||||||
do {
|
do {
|
||||||
++$counter;
|
++$counter;
|
||||||
|
|
@ -563,7 +561,7 @@ class RRuleIterator implements \Iterator
|
||||||
// functions instead.
|
// functions instead.
|
||||||
$nextDate = clone $this->currentDate;
|
$nextDate = clone $this->currentDate;
|
||||||
$nextDate = $nextDate->modify('+ '.($this->interval * $counter).' years');
|
$nextDate = $nextDate->modify('+ '.($this->interval * $counter).' years');
|
||||||
} while (2 != $nextDate->format('n'));
|
} while (2 !== (int) $nextDate->format('n'));
|
||||||
|
|
||||||
$this->currentDate = $nextDate;
|
$this->currentDate = $nextDate;
|
||||||
|
|
||||||
|
|
@ -634,7 +632,7 @@ class RRuleIterator implements \Iterator
|
||||||
$date = $date->sub(new \DateInterval('P'.abs($byYearDay + 1).'D'));
|
$date = $date->sub(new \DateInterval('P'.abs($byYearDay + 1).'D'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($date > $this->currentDate && in_array($date->format('N'), $dayOffsets)) {
|
if ($date > $this->currentDate && in_array((int) $date->format('N'), $dayOffsets, true)) {
|
||||||
$checkDates[] = $date;
|
$checkDates[] = $date;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -665,7 +663,7 @@ class RRuleIterator implements \Iterator
|
||||||
// If we got a byDay or getMonthDay filter, we must first expand
|
// If we got a byDay or getMonthDay filter, we must first expand
|
||||||
// further.
|
// further.
|
||||||
if ($this->byDay || $this->byMonthDay) {
|
if ($this->byDay || $this->byMonthDay) {
|
||||||
$occurrence = -1;
|
$selectedOccurrence = -1;
|
||||||
while (true) {
|
while (true) {
|
||||||
$occurrences = $this->getMonthlyOccurrences();
|
$occurrences = $this->getMonthlyOccurrences();
|
||||||
|
|
||||||
|
|
@ -677,7 +675,8 @@ class RRuleIterator implements \Iterator
|
||||||
if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) {
|
if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) {
|
||||||
// only consider byMonth matches,
|
// only consider byMonth matches,
|
||||||
// otherwise, we don't follow RRule correctly
|
// otherwise, we don't follow RRule correctly
|
||||||
if (in_array($currentMonth, $this->byMonth)) {
|
if (in_array((string) $currentMonth, $this->byMonth, true)) {
|
||||||
|
$selectedOccurrence = $occurrence;
|
||||||
break 2;
|
break 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -693,12 +692,12 @@ class RRuleIterator implements \Iterator
|
||||||
$currentYear += $this->interval;
|
$currentYear += $this->interval;
|
||||||
$currentMonth = 1;
|
$currentMonth = 1;
|
||||||
}
|
}
|
||||||
} while (!in_array($currentMonth, $this->byMonth));
|
} while (!in_array((string) $currentMonth, $this->byMonth, true));
|
||||||
|
|
||||||
$this->currentDate = $this->currentDate->setDate(
|
$this->currentDate = $this->currentDate->setDate(
|
||||||
(int) $currentYear,
|
(int) $currentYear,
|
||||||
(int) $currentMonth,
|
$currentMonth,
|
||||||
(int) $currentDayOfMonth
|
$currentDayOfMonth
|
||||||
);
|
);
|
||||||
|
|
||||||
// To prevent running this forever (better: until we hit the max date of DateTimeImmutable) we simply
|
// To prevent running this forever (better: until we hit the max date of DateTimeImmutable) we simply
|
||||||
|
|
@ -714,28 +713,25 @@ class RRuleIterator implements \Iterator
|
||||||
$this->currentDate = $this->currentDate->setDate(
|
$this->currentDate = $this->currentDate->setDate(
|
||||||
(int) $currentYear,
|
(int) $currentYear,
|
||||||
(int) $currentMonth,
|
(int) $currentMonth,
|
||||||
(int) $occurrence
|
(int) $selectedOccurrence
|
||||||
)->modify($this->startTime());
|
|
||||||
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
// These are the 'byMonth' rules, if there are no byDay or
|
|
||||||
// byMonthDay sub-rules.
|
|
||||||
do {
|
|
||||||
++$currentMonth;
|
|
||||||
if ($currentMonth > 12) {
|
|
||||||
$currentYear += $this->interval;
|
|
||||||
$currentMonth = 1;
|
|
||||||
}
|
|
||||||
} while (!in_array($currentMonth, $this->byMonth));
|
|
||||||
$this->currentDate = $this->currentDate->setDate(
|
|
||||||
(int) $currentYear,
|
|
||||||
(int) $currentMonth,
|
|
||||||
(int) $currentDayOfMonth
|
|
||||||
)->modify($this->startTime());
|
)->modify($this->startTime());
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// These are the 'byMonth' rules, if there are no byDay or
|
||||||
|
// byMonthDay sub-rules.
|
||||||
|
do {
|
||||||
|
++$currentMonth;
|
||||||
|
if ($currentMonth > 12) {
|
||||||
|
$currentYear += $this->interval;
|
||||||
|
$currentMonth = 1;
|
||||||
|
}
|
||||||
|
} while (!in_array((string) $currentMonth, $this->byMonth, true));
|
||||||
|
$this->currentDate = $this->currentDate->setDate(
|
||||||
|
(int) $currentYear,
|
||||||
|
$currentMonth,
|
||||||
|
(int) $currentDayOfMonth
|
||||||
|
)->modify($this->startTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* }}} */
|
/* }}} */
|
||||||
|
|
@ -755,13 +751,14 @@ class RRuleIterator implements \Iterator
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($rrule as $key => $value) {
|
foreach ($rrule as $key => $value) {
|
||||||
$key = strtoupper($key);
|
$key = strtoupper((string) $key);
|
||||||
switch ($key) {
|
switch ($key) {
|
||||||
case 'FREQ':
|
case 'FREQ':
|
||||||
$value = strtolower($value);
|
$value = strtolower((string) $value);
|
||||||
if (!in_array(
|
if (!in_array(
|
||||||
$value,
|
$value,
|
||||||
['secondly', 'minutely', 'hourly', 'daily', 'weekly', 'monthly', 'yearly']
|
['secondly', 'minutely', 'hourly', 'daily', 'weekly', 'monthly', 'yearly'],
|
||||||
|
true
|
||||||
)) {
|
)) {
|
||||||
throw new InvalidDataException('Unknown value for FREQ='.strtoupper($value));
|
throw new InvalidDataException('Unknown value for FREQ='.strtoupper($value));
|
||||||
}
|
}
|
||||||
|
|
@ -809,7 +806,7 @@ class RRuleIterator implements \Iterator
|
||||||
case 'BYDAY':
|
case 'BYDAY':
|
||||||
$value = (array) $value;
|
$value = (array) $value;
|
||||||
foreach ($value as $part) {
|
foreach ($value as $part) {
|
||||||
if (!preg_match('#^ (-|\+)? ([1-5])? (MO|TU|WE|TH|FR|SA|SU) $# xi', $part)) {
|
if (!preg_match('#^ (-|\+)? ([1-5])? (MO|TU|WE|TH|FR|SA|SU) $#xi', (string) $part)) {
|
||||||
throw new InvalidDataException('Invalid part in BYDAY clause: '.$part);
|
throw new InvalidDataException('Invalid part in BYDAY clause: '.$part);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -823,7 +820,7 @@ class RRuleIterator implements \Iterator
|
||||||
case 'BYYEARDAY':
|
case 'BYYEARDAY':
|
||||||
$this->byYearDay = (array) $value;
|
$this->byYearDay = (array) $value;
|
||||||
foreach ($this->byYearDay as $byYearDay) {
|
foreach ($this->byYearDay as $byYearDay) {
|
||||||
if (!is_numeric($byYearDay) || (int) $byYearDay < -366 || 0 == (int) $byYearDay || (int) $byYearDay > 366) {
|
if (!is_numeric($byYearDay) || (int) $byYearDay < -366 || 0 === (int) $byYearDay || (int) $byYearDay > 366) {
|
||||||
throw new InvalidDataException('BYYEARDAY in RRULE must have value(s) from 1 to 366, or -366 to -1!');
|
throw new InvalidDataException('BYYEARDAY in RRULE must have value(s) from 1 to 366, or -366 to -1!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -832,7 +829,7 @@ class RRuleIterator implements \Iterator
|
||||||
case 'BYWEEKNO':
|
case 'BYWEEKNO':
|
||||||
$this->byWeekNo = (array) $value;
|
$this->byWeekNo = (array) $value;
|
||||||
foreach ($this->byWeekNo as $byWeekNo) {
|
foreach ($this->byWeekNo as $byWeekNo) {
|
||||||
if (!is_numeric($byWeekNo) || (int) $byWeekNo < -53 || 0 == (int) $byWeekNo || (int) $byWeekNo > 53) {
|
if (!is_numeric($byWeekNo) || (int) $byWeekNo < -53 || 0 === (int) $byWeekNo || (int) $byWeekNo > 53) {
|
||||||
throw new InvalidDataException('BYWEEKNO in RRULE must have value(s) from 1 to 53, or -53 to -1!');
|
throw new InvalidDataException('BYWEEKNO in RRULE must have value(s) from 1 to 53, or -53 to -1!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -852,7 +849,7 @@ class RRuleIterator implements \Iterator
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'WKST':
|
case 'WKST':
|
||||||
$this->weekStart = strtoupper($value);
|
$this->weekStart = strtoupper((string) $value);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -892,7 +889,7 @@ class RRuleIterator implements \Iterator
|
||||||
// that point and add it to the results.
|
// that point and add it to the results.
|
||||||
if ($this->byDay) {
|
if ($this->byDay) {
|
||||||
foreach ($this->byDay as $day) {
|
foreach ($this->byDay as $day) {
|
||||||
$dayName = $this->dayNames[$this->dayMap[substr($day, -2)]];
|
$dayName = $this->dayNames[$this->dayMap[substr((string) $day, -2)]];
|
||||||
|
|
||||||
// Dayname will be something like 'wednesday'. Now we need to find
|
// Dayname will be something like 'wednesday'. Now we need to find
|
||||||
// all wednesdays in this month.
|
// all wednesdays in this month.
|
||||||
|
|
@ -913,8 +910,8 @@ class RRuleIterator implements \Iterator
|
||||||
// So now we have 'all wednesdays' for month. It is however
|
// So now we have 'all wednesdays' for month. It is however
|
||||||
// possible that the user only really wanted the 1st, 2nd or last
|
// possible that the user only really wanted the 1st, 2nd or last
|
||||||
// wednesday.
|
// wednesday.
|
||||||
if (strlen($day) > 2) {
|
if (strlen((string) $day) > 2) {
|
||||||
$offset = (int) substr($day, 0, -2);
|
$offset = (int) substr((string) $day, 0, -2);
|
||||||
|
|
||||||
if ($offset > 0) {
|
if ($offset > 0) {
|
||||||
// It is possible that the day does not exist, such as a
|
// It is possible that the day does not exist, such as a
|
||||||
|
|
@ -949,7 +946,7 @@ class RRuleIterator implements \Iterator
|
||||||
$byMonthDayResults[] = $monthDay;
|
$byMonthDayResults[] = $monthDay;
|
||||||
} else {
|
} else {
|
||||||
// Negative values
|
// Negative values
|
||||||
$byMonthDayResults[] = $startDate->format('t') + 1 + $monthDay;
|
$byMonthDayResults[] = (int) $startDate->format('t') + 1 + $monthDay;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1018,7 +1015,7 @@ class RRuleIterator implements \Iterator
|
||||||
// The day may be preceded with a positive (+n) or
|
// The day may be preceded with a positive (+n) or
|
||||||
// negative (-n) integer. However, this does not make
|
// negative (-n) integer. However, this does not make
|
||||||
// sense in 'weekly' so we ignore it here.
|
// sense in 'weekly' so we ignore it here.
|
||||||
$recurrenceDays[] = $this->dayMap[substr($byDay, -2)];
|
$recurrenceDays[] = $this->dayMap[substr((string) $byDay, -2)];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $recurrenceDays;
|
return $recurrenceDays;
|
||||||
|
|
|
||||||
|
|
@ -96,8 +96,8 @@ class ICalendar implements SplitterInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
return $object;
|
return $object;
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,6 @@ use Sabre\VObject\Parser\MimeDir;
|
||||||
*/
|
*/
|
||||||
class VCard implements SplitterInterface
|
class VCard implements SplitterInterface
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* File handle.
|
|
||||||
*
|
|
||||||
* @var resource
|
|
||||||
*/
|
|
||||||
protected $input;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Persistent parser.
|
* Persistent parser.
|
||||||
*/
|
*/
|
||||||
|
|
@ -42,10 +35,12 @@ class VCard implements SplitterInterface
|
||||||
* @param resource $input
|
* @param resource $input
|
||||||
* @param int $options parser options, see the OPTIONS constants
|
* @param int $options parser options, see the OPTIONS constants
|
||||||
*/
|
*/
|
||||||
public function __construct($input, int $options = 0)
|
public function __construct(/**
|
||||||
|
* File handle.
|
||||||
|
*/
|
||||||
|
protected $input, int $options = 0)
|
||||||
{
|
{
|
||||||
$this->input = $input;
|
$this->parser = new MimeDir($this->input, $options);
|
||||||
$this->parser = new MimeDir($input, $options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -64,7 +59,7 @@ class VCard implements SplitterInterface
|
||||||
if (!$object instanceof Component\VCard) {
|
if (!$object instanceof Component\VCard) {
|
||||||
throw new VObject\ParseException('The supplied input contained non-VCARD data.');
|
throw new VObject\ParseException('The supplied input contained non-VCARD data.');
|
||||||
}
|
}
|
||||||
} catch (VObject\EofException $e) {
|
} catch (VObject\EofException) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,7 @@ class TimeZoneUtil
|
||||||
|
|
||||||
private static function getInstance(): self
|
private static function getInstance(): self
|
||||||
{
|
{
|
||||||
if (null === self::$instance) {
|
self::$instance ??= new self();
|
||||||
self::$instance = new self();
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::$instance;
|
return self::$instance;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,24 @@ class FindFromOffset implements TimezoneFinder
|
||||||
// @codeCoverageIgnoreEnd
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (0 === strncasecmp($tzid, 'UTC', 3)) {
|
||||||
|
$offset = substr($tzid, 3);
|
||||||
|
if (6 === strlen($offset) && ':' === $offset[3]) {
|
||||||
|
$offset = substr($offset, 0, 3).substr($offset, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (5 !== strlen($offset)
|
||||||
|
|| ('+' !== $offset[0] && '-' !== $offset[0])
|
||||||
|
|| 4 !== strspn(substr($offset, 1), '0123456789')
|
||||||
|
|| (int) substr($offset, 1, 2) > 23
|
||||||
|
|| (int) substr($offset, 3, 2) > 59
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new \DateTimeZone($offset);
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,13 +40,13 @@ class FindFromTimezoneIdentifier implements TimezoneFinder
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
in_array($tzid, $tzIdentifiers)
|
in_array($tzid, $tzIdentifiers, true)
|
||||||
|| preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)
|
|| preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)
|
||||||
|| in_array($tzid, $this->getIdentifiersBC())
|
|| in_array($tzid, $this->getIdentifiersBC(), true)
|
||||||
) {
|
) {
|
||||||
return new \DateTimeZone($tzid);
|
return new \DateTimeZone($tzid);
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,18 @@ class FindFromTimezoneMap implements TimezoneFinder
|
||||||
'/^\((UTC|GMT)(\+|\-)[\d]{2}\.[\d]{2}\) (.*)/',
|
'/^\((UTC|GMT)(\+|\-)[\d]{2}\.[\d]{2}\) (.*)/',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws void
|
||||||
|
*/
|
||||||
public function find(string $tzid, ?bool $failIfUncertain = false): ?\DateTimeZone
|
public function find(string $tzid, ?bool $failIfUncertain = false): ?\DateTimeZone
|
||||||
{
|
{
|
||||||
// Next, we check if the tzid is somewhere in our tzid map.
|
// Next, we check if the tzid is somewhere in our tzid map.
|
||||||
if ($this->hasTzInMap($tzid)) {
|
if ($this->hasTzInMap($tzid)) {
|
||||||
return new \DateTimeZone($this->getTzFromMap($tzid));
|
try {
|
||||||
|
return new \DateTimeZone($this->getTzFromMap($tzid));
|
||||||
|
} catch (\Exception) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some Microsoft products prefix the offset first, so let's strip that off
|
// Some Microsoft products prefix the offset first, so let's strip that off
|
||||||
|
|
@ -34,7 +41,11 @@ class FindFromTimezoneMap implements TimezoneFinder
|
||||||
}
|
}
|
||||||
$tzidAlternate = $matches[3];
|
$tzidAlternate = $matches[3];
|
||||||
if ($this->hasTzInMap($tzidAlternate)) {
|
if ($this->hasTzInMap($tzidAlternate)) {
|
||||||
return new \DateTimeZone($this->getTzFromMap($tzidAlternate));
|
try {
|
||||||
|
return new \DateTimeZone($this->getTzFromMap($tzidAlternate));
|
||||||
|
} catch (\Exception) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ class GuessFromLicEntry implements TimezoneGuesser
|
||||||
// Libical generators may specify strings like
|
// Libical generators may specify strings like
|
||||||
// "SystemV/EST5EDT". For those we must remove the
|
// "SystemV/EST5EDT". For those we must remove the
|
||||||
// SystemV part.
|
// SystemV part.
|
||||||
if ('SystemV/' === substr($lic, 0, 8)) {
|
if (str_starts_with($lic, 'SystemV/')) {
|
||||||
$lic = substr($lic, 8);
|
$lic = substr($lic, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ class GuessFromMsTzId implements TimezoneGuesser
|
||||||
$cdoId = (int) $vtimezone->{'X-MICROSOFT-CDO-TZID'}->getValue();
|
$cdoId = (int) $vtimezone->{'X-MICROSOFT-CDO-TZID'}->getValue();
|
||||||
|
|
||||||
// 2 can mean both Europe/Lisbon and Europe/Sarajevo.
|
// 2 can mean both Europe/Lisbon and Europe/Sarajevo.
|
||||||
if (2 === $cdoId && false !== strpos((string) $vtimezone->TZID, 'Sarajevo')) {
|
if (2 === $cdoId && str_contains((string) $vtimezone->TZID, 'Sarajevo')) {
|
||||||
return new \DateTimeZone('Europe/Sarajevo');
|
return new \DateTimeZone('Europe/Sarajevo');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,10 @@ class VCardConverter
|
||||||
return clone $input;
|
return clone $input;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!in_array($inputVersion, [Document::VCARD21, Document::VCARD30, Document::VCARD40])) {
|
if (!in_array($inputVersion, [Document::VCARD21, Document::VCARD30, Document::VCARD40], true)) {
|
||||||
throw new \InvalidArgumentException('Only vCard 2.1, 3.0 and 4.0 are supported for the input data');
|
throw new \InvalidArgumentException('Only vCard 2.1, 3.0 and 4.0 are supported for the input data');
|
||||||
}
|
}
|
||||||
if (!in_array($targetVersion, [Document::VCARD30, Document::VCARD40])) {
|
if (!in_array($targetVersion, [Document::VCARD30, Document::VCARD40], true)) {
|
||||||
throw new \InvalidArgumentException('You can only use vCard 3.0 or 4.0 for the target version');
|
throw new \InvalidArgumentException('You can only use vCard 3.0 or 4.0 for the target version');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,7 +69,7 @@ class VCardConverter
|
||||||
protected function convertProperty(Component\VCard $input, Component\VCard $output, Property $property, int $targetVersion): void
|
protected function convertProperty(Component\VCard $input, Component\VCard $output, Property $property, int $targetVersion): void
|
||||||
{
|
{
|
||||||
// Skipping these, those are automatically added.
|
// Skipping these, those are automatically added.
|
||||||
if (in_array($property->name, ['VERSION', 'PRODID'])) {
|
if (in_array($property->name, ['VERSION', 'PRODID'], true)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,7 +93,7 @@ class VCardConverter
|
||||||
);
|
);
|
||||||
|
|
||||||
if (Document::VCARD30 === $targetVersion) {
|
if (Document::VCARD30 === $targetVersion) {
|
||||||
if ($property instanceof Uri && in_array($property->name, ['PHOTO', 'LOGO', 'SOUND'])) {
|
if ($property instanceof Uri && in_array($property->name, ['PHOTO', 'LOGO', 'SOUND'], true)) {
|
||||||
/** @var Uri $newProperty */
|
/** @var Uri $newProperty */
|
||||||
$newProperty = $this->convertUriToBinary($output, $newProperty);
|
$newProperty = $this->convertUriToBinary($output, $newProperty);
|
||||||
} elseif ($property instanceof Property\VCard\DateAndOrTime) {
|
} elseif ($property instanceof Property\VCard\DateAndOrTime) {
|
||||||
|
|
@ -111,7 +111,7 @@ class VCardConverter
|
||||||
$newProperty['X-APPLE-OMIT-YEAR'] = '1604';
|
$newProperty['X-APPLE-OMIT-YEAR'] = '1604';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('ANNIVERSARY' == $newProperty->name) {
|
if ('ANNIVERSARY' === $newProperty->name) {
|
||||||
// Microsoft non-standard anniversary
|
// Microsoft non-standard anniversary
|
||||||
$newProperty->name = 'X-ANNIVERSARY';
|
$newProperty->name = 'X-ANNIVERSARY';
|
||||||
|
|
||||||
|
|
@ -127,7 +127,7 @@ class VCardConverter
|
||||||
$output->add('ITEM'.$x.'.X-ABLABEL', '_$!<Anniversary>!$_');
|
$output->add('ITEM'.$x.'.X-ABLABEL', '_$!<Anniversary>!$_');
|
||||||
}
|
}
|
||||||
} elseif ('KIND' === $property->name) {
|
} elseif ('KIND' === $property->name) {
|
||||||
switch (strtolower($property->getValue())) {
|
switch (strtolower((string) $property->getValue())) {
|
||||||
case 'org':
|
case 'org':
|
||||||
// vCard 3.0 does not have an equivalent to KIND:ORG,
|
// vCard 3.0 does not have an equivalent to KIND:ORG,
|
||||||
// but apple has an extension that means the same
|
// but apple has an extension that means the same
|
||||||
|
|
@ -149,7 +149,7 @@ class VCardConverter
|
||||||
}
|
}
|
||||||
} elseif (Document::VCARD40 === $targetVersion) {
|
} elseif (Document::VCARD40 === $targetVersion) {
|
||||||
// These properties were removed in vCard 4.0
|
// These properties were removed in vCard 4.0
|
||||||
if (in_array($property->name, ['NAME', 'MAILER', 'LABEL', 'CLASS'])) {
|
if (in_array($property->name, ['NAME', 'MAILER', 'LABEL', 'CLASS'], true)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,12 +171,12 @@ class VCardConverter
|
||||||
}
|
}
|
||||||
switch ($property->name) {
|
switch ($property->name) {
|
||||||
case 'X-ABSHOWAS':
|
case 'X-ABSHOWAS':
|
||||||
if ('COMPANY' === strtoupper($property->getValue())) {
|
if ('COMPANY' === strtoupper((string) $property->getValue())) {
|
||||||
$newProperty = $output->createProperty('KIND', 'ORG');
|
$newProperty = $output->createProperty('KIND', 'ORG');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'X-ADDRESSBOOKSERVER-KIND':
|
case 'X-ADDRESSBOOKSERVER-KIND':
|
||||||
if ('GROUP' === strtoupper($property->getValue())) {
|
if ('GROUP' === strtoupper((string) $property->getValue())) {
|
||||||
$newProperty = $output->createProperty('KIND', 'GROUP');
|
$newProperty = $output->createProperty('KIND', 'GROUP');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -274,10 +274,11 @@ class VCardConverter
|
||||||
$newTypes = [];
|
$newTypes = [];
|
||||||
foreach ($parameters['TYPE']->getParts() as $typePart) {
|
foreach ($parameters['TYPE']->getParts() as $typePart) {
|
||||||
if (in_array(
|
if (in_array(
|
||||||
strtoupper($typePart),
|
strtoupper((string) $typePart),
|
||||||
['JPEG', 'PNG', 'GIF']
|
['JPEG', 'PNG', 'GIF'],
|
||||||
|
true
|
||||||
)) {
|
)) {
|
||||||
$mimeType = 'image/'.strtolower($typePart);
|
$mimeType = 'image/'.strtolower((string) $typePart);
|
||||||
} else {
|
} else {
|
||||||
$newTypes[] = $typePart;
|
$newTypes[] = $typePart;
|
||||||
}
|
}
|
||||||
|
|
@ -292,7 +293,7 @@ class VCardConverter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$newProperty->setValue('data:'.$mimeType.';base64,'.base64_encode($value));
|
$newProperty->setValue('data:'.$mimeType.';base64,'.base64_encode((string) $value));
|
||||||
|
|
||||||
return $newProperty;
|
return $newProperty;
|
||||||
}
|
}
|
||||||
|
|
@ -313,7 +314,7 @@ class VCardConverter
|
||||||
$value = $newProperty->getValue();
|
$value = $newProperty->getValue();
|
||||||
|
|
||||||
// Only converting data: uris
|
// Only converting data: uris
|
||||||
if ('data:' !== substr($value, 0, 5)) {
|
if (!str_starts_with((string) $value, 'data:')) {
|
||||||
return $newProperty;
|
return $newProperty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -325,12 +326,12 @@ class VCardConverter
|
||||||
'BINARY'
|
'BINARY'
|
||||||
);
|
);
|
||||||
|
|
||||||
$mimeType = substr($value, 5, strpos($value, ',') - 5);
|
$mimeType = substr((string) $value, 5, strpos((string) $value, ',') - 5);
|
||||||
if (strpos($mimeType, ';')) {
|
if (strpos($mimeType, ';')) {
|
||||||
$mimeType = substr($mimeType, 0, strpos($mimeType, ';'));
|
$mimeType = substr($mimeType, 0, strpos($mimeType, ';'));
|
||||||
$newProperty->setValue(base64_decode(substr($value, strpos($value, ',') + 1)));
|
$newProperty->setValue(base64_decode(substr((string) $value, strpos((string) $value, ',') + 1), true));
|
||||||
} else {
|
} else {
|
||||||
$newProperty->setValue(substr($value, strpos($value, ',') + 1));
|
$newProperty->setValue(substr((string) $value, strpos((string) $value, ',') + 1));
|
||||||
}
|
}
|
||||||
unset($value);
|
unset($value);
|
||||||
|
|
||||||
|
|
@ -367,7 +368,7 @@ class VCardConverter
|
||||||
// that's now PREF=1.
|
// that's now PREF=1.
|
||||||
case 'TYPE':
|
case 'TYPE':
|
||||||
foreach ($param->getParts() as $paramPart) {
|
foreach ($param->getParts() as $paramPart) {
|
||||||
if ('PREF' === strtoupper($paramPart)) {
|
if ('PREF' === strtoupper((string) $paramPart)) {
|
||||||
$newProperty->add('PREF', '1');
|
$newProperty->add('PREF', '1');
|
||||||
} else {
|
} else {
|
||||||
$newProperty->add($param->name, $paramPart);
|
$newProperty->add($param->name, $paramPart);
|
||||||
|
|
@ -402,7 +403,7 @@ class VCardConverter
|
||||||
case 'ENCODING':
|
case 'ENCODING':
|
||||||
// This value only existed in vCard 2.1, and should be
|
// This value only existed in vCard 2.1, and should be
|
||||||
// removed for anything else.
|
// removed for anything else.
|
||||||
if ('QUOTED-PRINTABLE' !== strtoupper($param->getValue())) {
|
if ('QUOTED-PRINTABLE' !== strtoupper((string) $param->getValue())) {
|
||||||
$newProperty->add($param->name, $param->getParts());
|
$newProperty->add($param->name, $param->getParts());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -413,7 +414,7 @@ class VCardConverter
|
||||||
* Any other PREF numbers we'll drop.
|
* Any other PREF numbers we'll drop.
|
||||||
*/
|
*/
|
||||||
case 'PREF':
|
case 'PREF':
|
||||||
if ('1' == $param->getValue()) {
|
if ('1' === (string) $param->getValue()) {
|
||||||
$newProperty->add('TYPE', 'PREF');
|
$newProperty->add('TYPE', 'PREF');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -14,5 +14,5 @@ class Version
|
||||||
/**
|
/**
|
||||||
* Full version number.
|
* Full version number.
|
||||||
*/
|
*/
|
||||||
public const VERSION = '4.5.2';
|
public const VERSION = '5.0.0';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ return [
|
||||||
'Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London' => 'Europe/Lisbon',
|
'Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London' => 'Europe/Lisbon',
|
||||||
'Greenwich Mean Time; Dublin, Edinburgh, London' => 'Europe/London',
|
'Greenwich Mean Time; Dublin, Edinburgh, London' => 'Europe/London',
|
||||||
'Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna' => 'Europe/Berlin',
|
'Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna' => 'Europe/Berlin',
|
||||||
|
'Amsterdam, Berlin, Bern, Rom, Stockholm, Wien' => 'Europe/Berlin',
|
||||||
'Belgrade, Pozsony, Budapest, Ljubljana, Prague' => 'Europe/Prague',
|
'Belgrade, Pozsony, Budapest, Ljubljana, Prague' => 'Europe/Prague',
|
||||||
'Brussels, Copenhagen, Madrid, Paris' => 'Europe/Paris',
|
'Brussels, Copenhagen, Madrid, Paris' => 'Europe/Paris',
|
||||||
'Paris, Madrid, Brussels, Copenhagen' => 'Europe/Paris',
|
'Paris, Madrid, Brussels, Copenhagen' => 'Europe/Paris',
|
||||||
|
|
@ -39,12 +40,12 @@ return [
|
||||||
'Kabul' => 'Asia/Kabul',
|
'Kabul' => 'Asia/Kabul',
|
||||||
'Ekaterinburg' => 'Asia/Yekaterinburg',
|
'Ekaterinburg' => 'Asia/Yekaterinburg',
|
||||||
'Islamabad, Karachi, Tashkent' => 'Asia/Karachi',
|
'Islamabad, Karachi, Tashkent' => 'Asia/Karachi',
|
||||||
'Kolkata, Chennai, Mumbai, New Delhi, India Standard Time' => 'Asia/Calcutta',
|
'Kolkata, Chennai, Mumbai, New Delhi, India Standard Time' => 'Asia/Kolkata',
|
||||||
'Kathmandu, Nepal' => 'Asia/Kathmandu',
|
'Kathmandu, Nepal' => 'Asia/Kathmandu',
|
||||||
'Almaty, Novosibirsk, North Central Asia' => 'Asia/Almaty',
|
'Almaty, Novosibirsk, North Central Asia' => 'Asia/Almaty',
|
||||||
'Astana, Dhaka' => 'Asia/Dhaka',
|
'Astana, Dhaka' => 'Asia/Dhaka',
|
||||||
'Sri Jayawardenepura, Sri Lanka' => 'Asia/Colombo',
|
'Sri Jayawardenepura, Sri Lanka' => 'Asia/Colombo',
|
||||||
'Rangoon' => 'Asia/Rangoon',
|
'Rangoon' => 'Asia/Yangon',
|
||||||
'Bangkok, Hanoi, Jakarta' => 'Asia/Bangkok',
|
'Bangkok, Hanoi, Jakarta' => 'Asia/Bangkok',
|
||||||
'Krasnoyarsk' => 'Asia/Krasnoyarsk',
|
'Krasnoyarsk' => 'Asia/Krasnoyarsk',
|
||||||
'Beijing, Chongqing, Hong Kong SAR, Urumqi' => 'Asia/Shanghai',
|
'Beijing, Chongqing, Hong Kong SAR, Urumqi' => 'Asia/Shanghai',
|
||||||
|
|
@ -71,7 +72,7 @@ return [
|
||||||
'Mid-Atlantic' => 'America/Noronha',
|
'Mid-Atlantic' => 'America/Noronha',
|
||||||
'Brasilia' => 'America/Sao_Paulo', // Best guess
|
'Brasilia' => 'America/Sao_Paulo', // Best guess
|
||||||
'Buenos Aires' => 'America/Argentina/Buenos_Aires',
|
'Buenos Aires' => 'America/Argentina/Buenos_Aires',
|
||||||
'Greenland' => 'America/Godthab',
|
'Greenland' => 'America/Nuuk',
|
||||||
'Newfoundland' => 'America/St_Johns',
|
'Newfoundland' => 'America/St_Johns',
|
||||||
'Atlantic Time (Canada)' => 'America/Halifax',
|
'Atlantic Time (Canada)' => 'America/Halifax',
|
||||||
'Caracas, La Paz' => 'America/Caracas',
|
'Caracas, La Paz' => 'America/Caracas',
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ return [
|
||||||
'Newfoundland' => 'America/St_Johns',
|
'Newfoundland' => 'America/St_Johns',
|
||||||
'Argentina' => 'America/Argentina/Buenos_Aires',
|
'Argentina' => 'America/Argentina/Buenos_Aires',
|
||||||
'E. South America' => 'America/Belem',
|
'E. South America' => 'America/Belem',
|
||||||
'Greenland' => 'America/Godthab',
|
'Greenland' => 'America/Nuuk',
|
||||||
'Montevideo' => 'America/Montevideo',
|
'Montevideo' => 'America/Montevideo',
|
||||||
'SA Eastern' => 'America/Belem',
|
'SA Eastern' => 'America/Belem',
|
||||||
// 'Mid-Atlantic' => 'Etc/GMT-2', // conflict with windows timezones.
|
// 'Mid-Atlantic' => 'Etc/GMT-2', // conflict with windows timezones.
|
||||||
|
|
@ -71,12 +71,12 @@ return [
|
||||||
'Ekaterinburg' => 'Asia/Yekaterinburg',
|
'Ekaterinburg' => 'Asia/Yekaterinburg',
|
||||||
'Pakistan' => 'Asia/Karachi',
|
'Pakistan' => 'Asia/Karachi',
|
||||||
'West Asia' => 'Asia/Tashkent',
|
'West Asia' => 'Asia/Tashkent',
|
||||||
'India' => 'Asia/Calcutta',
|
'India' => 'Asia/Kolkata',
|
||||||
'Sri Lanka' => 'Asia/Colombo',
|
'Sri Lanka' => 'Asia/Colombo',
|
||||||
'Nepal' => 'Asia/Kathmandu',
|
'Nepal' => 'Asia/Kathmandu',
|
||||||
'Central Asia' => 'Asia/Dhaka',
|
'Central Asia' => 'Asia/Dhaka',
|
||||||
'N. Central Asia' => 'Asia/Almaty',
|
'N. Central Asia' => 'Asia/Almaty',
|
||||||
'Myanmar' => 'Asia/Rangoon',
|
'Myanmar' => 'Asia/Yangon',
|
||||||
'North Asia' => 'Asia/Krasnoyarsk',
|
'North Asia' => 'Asia/Krasnoyarsk',
|
||||||
'SE Asia' => 'Asia/Bangkok',
|
'SE Asia' => 'Asia/Bangkok',
|
||||||
'China' => 'Asia/Shanghai',
|
'China' => 'Asia/Shanghai',
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ return [
|
||||||
'Arab Standard Time' => 'Asia/Riyadh',
|
'Arab Standard Time' => 'Asia/Riyadh',
|
||||||
'Arabian Standard Time' => 'Asia/Dubai',
|
'Arabian Standard Time' => 'Asia/Dubai',
|
||||||
'Arabic Standard Time' => 'Asia/Baghdad',
|
'Arabic Standard Time' => 'Asia/Baghdad',
|
||||||
'Argentina Standard Time' => 'America/Buenos_Aires',
|
'Argentina Standard Time' => 'America/Argentina/Buenos_Aires',
|
||||||
'Astrakhan Standard Time' => 'Europe/Astrakhan',
|
'Astrakhan Standard Time' => 'Europe/Astrakhan',
|
||||||
'Atlantic Standard Time' => 'America/Halifax',
|
'Atlantic Standard Time' => 'America/Halifax',
|
||||||
'Aus Central W. Standard Time' => 'Australia/Eucla',
|
'Aus Central W. Standard Time' => 'Australia/Eucla',
|
||||||
|
|
@ -55,16 +55,16 @@ return [
|
||||||
'Eastern Standard Time (Mexico)' => 'America/Cancun',
|
'Eastern Standard Time (Mexico)' => 'America/Cancun',
|
||||||
'Egypt Standard Time' => 'Africa/Cairo',
|
'Egypt Standard Time' => 'Africa/Cairo',
|
||||||
'Ekaterinburg Standard Time' => 'Asia/Yekaterinburg',
|
'Ekaterinburg Standard Time' => 'Asia/Yekaterinburg',
|
||||||
'FLE Standard Time' => 'Europe/Kiev',
|
'FLE Standard Time' => 'Europe/Kyiv',
|
||||||
'Fiji Standard Time' => 'Pacific/Fiji',
|
'Fiji Standard Time' => 'Pacific/Fiji',
|
||||||
'GMT Standard Time' => 'Europe/London',
|
'GMT Standard Time' => 'Europe/London',
|
||||||
'GTB Standard Time' => 'Europe/Bucharest',
|
'GTB Standard Time' => 'Europe/Bucharest',
|
||||||
'Georgian Standard Time' => 'Asia/Tbilisi',
|
'Georgian Standard Time' => 'Asia/Tbilisi',
|
||||||
'Greenland Standard Time' => 'America/Godthab',
|
'Greenland Standard Time' => 'America/Nuuk',
|
||||||
'Greenwich Standard Time' => 'Atlantic/Reykjavik',
|
'Greenwich Standard Time' => 'Atlantic/Reykjavik',
|
||||||
'Haiti Standard Time' => 'America/Port-au-Prince',
|
'Haiti Standard Time' => 'America/Port-au-Prince',
|
||||||
'Hawaiian Standard Time' => 'Pacific/Honolulu',
|
'Hawaiian Standard Time' => 'Pacific/Honolulu',
|
||||||
'India Standard Time' => 'Asia/Calcutta',
|
'India Standard Time' => 'Asia/Kolkata',
|
||||||
'Iran Standard Time' => 'Asia/Tehran',
|
'Iran Standard Time' => 'Asia/Tehran',
|
||||||
'Israel Standard Time' => 'Asia/Jerusalem',
|
'Israel Standard Time' => 'Asia/Jerusalem',
|
||||||
'Jordan Standard Time' => 'Asia/Amman',
|
'Jordan Standard Time' => 'Asia/Amman',
|
||||||
|
|
@ -82,10 +82,10 @@ return [
|
||||||
'Morocco Standard Time' => 'Africa/Casablanca',
|
'Morocco Standard Time' => 'Africa/Casablanca',
|
||||||
'Mountain Standard Time' => 'America/Denver',
|
'Mountain Standard Time' => 'America/Denver',
|
||||||
'Mountain Standard Time (Mexico)' => 'America/Chihuahua',
|
'Mountain Standard Time (Mexico)' => 'America/Chihuahua',
|
||||||
'Myanmar Standard Time' => 'Asia/Rangoon',
|
'Myanmar Standard Time' => 'Asia/Yangon',
|
||||||
'N. Central Asia Standard Time' => 'Asia/Novosibirsk',
|
'N. Central Asia Standard Time' => 'Asia/Novosibirsk',
|
||||||
'Namibia Standard Time' => 'Africa/Windhoek',
|
'Namibia Standard Time' => 'Africa/Windhoek',
|
||||||
'Nepal Standard Time' => 'Asia/Katmandu',
|
'Nepal Standard Time' => 'Asia/Kathmandu',
|
||||||
'New Zealand Standard Time' => 'Pacific/Auckland',
|
'New Zealand Standard Time' => 'Pacific/Auckland',
|
||||||
'Newfoundland Standard Time' => 'America/St_Johns',
|
'Newfoundland Standard Time' => 'America/St_Johns',
|
||||||
'Norfolk Standard Time' => 'Pacific/Norfolk',
|
'Norfolk Standard Time' => 'Pacific/Norfolk',
|
||||||
|
|
@ -127,7 +127,7 @@ return [
|
||||||
'Transbaikal Standard Time' => 'Asia/Chita',
|
'Transbaikal Standard Time' => 'Asia/Chita',
|
||||||
'Turkey Standard Time' => 'Europe/Istanbul',
|
'Turkey Standard Time' => 'Europe/Istanbul',
|
||||||
'Turks And Caicos Standard Time' => 'America/Grand_Turk',
|
'Turks And Caicos Standard Time' => 'America/Grand_Turk',
|
||||||
'US Eastern Standard Time' => 'America/Indianapolis',
|
'US Eastern Standard Time' => 'America/Indiana/Indianapolis',
|
||||||
'US Mountain Standard Time' => 'America/Phoenix',
|
'US Mountain Standard Time' => 'America/Phoenix',
|
||||||
'UTC' => 'Etc/GMT',
|
'UTC' => 'Etc/GMT',
|
||||||
'UTC+12' => 'Etc/GMT-12',
|
'UTC+12' => 'Etc/GMT-12',
|
||||||
|
|
|
||||||
|
|
@ -212,11 +212,13 @@ function valueObject(Reader $reader, string $className, string $namespace): obje
|
||||||
|
|
||||||
$reader->read();
|
$reader->read();
|
||||||
do {
|
do {
|
||||||
if (Reader::ELEMENT === $reader->nodeType && $reader->namespaceURI == $namespace) {
|
if (Reader::ELEMENT === $reader->nodeType && $reader->namespaceURI === $namespace) {
|
||||||
if (property_exists($valueObject, $reader->localName)) {
|
if (property_exists($valueObject, $reader->localName)) {
|
||||||
if (is_array($defaultProperties[$reader->localName])) {
|
if (is_array($defaultProperties[$reader->localName])) {
|
||||||
|
// @phpstan-ignore property.dynamicName
|
||||||
$valueObject->{$reader->localName}[] = $reader->parseCurrentElement()['value'];
|
$valueObject->{$reader->localName}[] = $reader->parseCurrentElement()['value'];
|
||||||
} else {
|
} else {
|
||||||
|
// @phpstan-ignore property.dynamicName
|
||||||
$valueObject->{$reader->localName} = $reader->parseCurrentElement()['value'];
|
$valueObject->{$reader->localName} = $reader->parseCurrentElement()['value'];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -19,19 +19,13 @@ use Sabre\Xml;
|
||||||
*/
|
*/
|
||||||
class Base implements Xml\Element
|
class Base implements Xml\Element
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @var mixed PHP value to serialize
|
|
||||||
*/
|
|
||||||
protected $value;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param mixed $value PHP value to serialize
|
* @param mixed $value PHP value to serialize
|
||||||
*/
|
*/
|
||||||
public function __construct($value = null)
|
public function __construct(protected $value = null)
|
||||||
{
|
{
|
||||||
$this->value = $value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -21,17 +21,15 @@ use Sabre\Xml;
|
||||||
*/
|
*/
|
||||||
class Cdata implements Xml\XmlSerializable
|
class Cdata implements Xml\XmlSerializable
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* CDATA element value.
|
|
||||||
*/
|
|
||||||
protected string $value;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*/
|
*/
|
||||||
public function __construct(string $value)
|
public function __construct(
|
||||||
{
|
/**
|
||||||
$this->value = $value;
|
* CDATA element value.
|
||||||
|
*/
|
||||||
|
protected string $value,
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -52,6 +50,6 @@ class Cdata implements Xml\XmlSerializable
|
||||||
*/
|
*/
|
||||||
public function xmlSerialize(Xml\Writer $writer): void
|
public function xmlSerialize(Xml\Writer $writer): void
|
||||||
{
|
{
|
||||||
$writer->writeCData($this->value);
|
$writer->writeCdata($this->value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,21 +37,17 @@ use Sabre\Xml\Serializer;
|
||||||
*/
|
*/
|
||||||
class Elements implements Xml\Element
|
class Elements implements Xml\Element
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Value to serialize.
|
|
||||||
*
|
|
||||||
* @var array<int, mixed>
|
|
||||||
*/
|
|
||||||
protected array $value;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param array<int, mixed> $value
|
* @param array<int, mixed> $value
|
||||||
*/
|
*/
|
||||||
public function __construct(array $value = [])
|
public function __construct(
|
||||||
{
|
/**
|
||||||
$this->value = $value;
|
* Value to serialize.
|
||||||
|
*/
|
||||||
|
protected array $value = [],
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -37,21 +37,17 @@ use Sabre\Xml\Deserializer;
|
||||||
*/
|
*/
|
||||||
class KeyValue implements Xml\Element
|
class KeyValue implements Xml\Element
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Value to serialize.
|
|
||||||
*
|
|
||||||
* @var array<string, mixed>
|
|
||||||
*/
|
|
||||||
protected array $value;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param array<string, mixed> $value
|
* @param array<string, mixed> $value
|
||||||
*/
|
*/
|
||||||
public function __construct(array $value = [])
|
public function __construct(
|
||||||
{
|
/**
|
||||||
$this->value = $value;
|
* Value to serialize.
|
||||||
|
*/
|
||||||
|
protected array $value = [],
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -26,17 +26,15 @@ use function Sabre\Uri\resolve;
|
||||||
*/
|
*/
|
||||||
class Uri implements Xml\Element
|
class Uri implements Xml\Element
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Uri element value.
|
|
||||||
*/
|
|
||||||
protected string $value;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*/
|
*/
|
||||||
public function __construct(string $value)
|
public function __construct(
|
||||||
{
|
/**
|
||||||
$this->value = $value;
|
* Uri element value.
|
||||||
|
*/
|
||||||
|
protected string $value,
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -24,17 +24,15 @@ use Sabre\Xml\Writer;
|
||||||
*/
|
*/
|
||||||
class XmlFragment implements Element
|
class XmlFragment implements Element
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* The inner XML value.
|
|
||||||
*/
|
|
||||||
protected string $xml;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*/
|
*/
|
||||||
public function __construct(string $xml)
|
public function __construct(
|
||||||
{
|
/**
|
||||||
$this->xml = $xml;
|
* The inner XML value.
|
||||||
|
*/
|
||||||
|
protected string $xml,
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -72,7 +70,7 @@ class XmlFragment implements Element
|
||||||
<xml-fragment xmlns="http://sabre.io/ns">{$this->getXml()}</xml-fragment>
|
<xml-fragment xmlns="http://sabre.io/ns">{$this->getXml()}</xml-fragment>
|
||||||
XML;
|
XML;
|
||||||
|
|
||||||
$reader->xml($xml);
|
$reader->XML($xml);
|
||||||
|
|
||||||
while ($reader->read()) {
|
while ($reader->read()) {
|
||||||
if ($reader->depth < 1) {
|
if ($reader->depth < 1) {
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,6 @@ use LibXMLError;
|
||||||
*/
|
*/
|
||||||
class LibXMLException extends ParseException
|
class LibXMLException extends ParseException
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* The error list.
|
|
||||||
*
|
|
||||||
* @var \LibXMLError[]
|
|
||||||
*/
|
|
||||||
protected array $errors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the exception.
|
* Creates the exception.
|
||||||
*
|
*
|
||||||
|
|
@ -31,10 +24,12 @@ class LibXMLException extends ParseException
|
||||||
*
|
*
|
||||||
* @param \LibXMLError[] $errors
|
* @param \LibXMLError[] $errors
|
||||||
*/
|
*/
|
||||||
public function __construct(array $errors, int $code = 0, ?\Throwable $previousException = null)
|
public function __construct(/**
|
||||||
|
* The error list.
|
||||||
|
*/
|
||||||
|
protected array $errors, int $code = 0, ?\Throwable $previousException = null)
|
||||||
{
|
{
|
||||||
$this->errors = $errors;
|
parent::__construct($this->errors[0]->message.' on line '.$this->errors[0]->line.', column '.$this->errors[0]->column, $code, $previousException);
|
||||||
parent::__construct($errors[0]->message.' on line '.$errors[0]->line.', column '.$errors[0]->column, $code, $previousException);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ class Reader extends \XMLReader
|
||||||
*/
|
*/
|
||||||
public function getClark(): ?string
|
public function getClark(): ?string
|
||||||
{
|
{
|
||||||
if (!$this->localName) {
|
if ('' === $this->localName) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,7 +67,7 @@ class Reader extends \XMLReader
|
||||||
if (!$this->read()) {
|
if (!$this->read()) {
|
||||||
$errors = libxml_get_errors();
|
$errors = libxml_get_errors();
|
||||||
libxml_clear_errors();
|
libxml_clear_errors();
|
||||||
if ($errors) {
|
if (count($errors) > 0) {
|
||||||
throw new LibXMLException($errors);
|
throw new LibXMLException($errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -77,7 +77,7 @@ class Reader extends \XMLReader
|
||||||
// last line of defense in case errors did occur above
|
// last line of defense in case errors did occur above
|
||||||
$errors = libxml_get_errors();
|
$errors = libxml_get_errors();
|
||||||
libxml_clear_errors();
|
libxml_clear_errors();
|
||||||
if ($errors) {
|
if (count($errors) > 0) {
|
||||||
throw new LibXMLException($errors);
|
throw new LibXMLException($errors);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -153,7 +153,7 @@ class Reader extends \XMLReader
|
||||||
if (!$this->read()) {
|
if (!$this->read()) {
|
||||||
$errors = libxml_get_errors();
|
$errors = libxml_get_errors();
|
||||||
libxml_clear_errors();
|
libxml_clear_errors();
|
||||||
if ($errors) {
|
if (count($errors) > 0) {
|
||||||
throw new LibXMLException($errors);
|
throw new LibXMLException($errors);
|
||||||
}
|
}
|
||||||
throw new ParseException('This should never happen (famous last words)');
|
throw new ParseException('This should never happen (famous last words)');
|
||||||
|
|
@ -165,7 +165,7 @@ class Reader extends \XMLReader
|
||||||
if (!$this->isValid()) {
|
if (!$this->isValid()) {
|
||||||
$errors = libxml_get_errors();
|
$errors = libxml_get_errors();
|
||||||
|
|
||||||
if ($errors) {
|
if (count($errors) > 0) {
|
||||||
libxml_clear_errors();
|
libxml_clear_errors();
|
||||||
throw new LibXMLException($errors);
|
throw new LibXMLException($errors);
|
||||||
}
|
}
|
||||||
|
|
@ -199,7 +199,7 @@ class Reader extends \XMLReader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $elements ?: $text;
|
return count($elements) > 0 ? $elements : $text;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -210,8 +210,8 @@ class Reader extends \XMLReader
|
||||||
$result = '';
|
$result = '';
|
||||||
$previousDepth = $this->depth;
|
$previousDepth = $this->depth;
|
||||||
|
|
||||||
while ($this->read() && $this->depth != $previousDepth) {
|
while ($this->read() && $this->depth !== $previousDepth) {
|
||||||
if (in_array($this->nodeType, [\XMLReader::TEXT, \XMLReader::CDATA, \XMLReader::WHITESPACE])) {
|
if (in_array($this->nodeType, [\XMLReader::TEXT, \XMLReader::CDATA, \XMLReader::WHITESPACE], true)) {
|
||||||
$result .= $this->value;
|
$result .= $this->value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -266,7 +266,7 @@ class Reader extends \XMLReader
|
||||||
$attributes = [];
|
$attributes = [];
|
||||||
|
|
||||||
while ($this->moveToNextAttribute()) {
|
while ($this->moveToNextAttribute()) {
|
||||||
if ($this->namespaceURI) {
|
if ('' !== $this->namespaceURI) {
|
||||||
// Ignoring 'xmlns', it doesn't make any sense.
|
// Ignoring 'xmlns', it doesn't make any sense.
|
||||||
if ('http://www.w3.org/2000/xmlns/' === $this->namespaceURI) {
|
if ('http://www.w3.org/2000/xmlns/' === $this->namespaceURI) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -290,10 +290,10 @@ class Reader extends \XMLReader
|
||||||
public function getDeserializerForElementName(string $name): callable
|
public function getDeserializerForElementName(string $name): callable
|
||||||
{
|
{
|
||||||
if (!array_key_exists($name, $this->elementMap)) {
|
if (!array_key_exists($name, $this->elementMap)) {
|
||||||
if ('{}' == substr($name, 0, 2) && array_key_exists(substr($name, 2), $this->elementMap)) {
|
if (str_starts_with($name, '{}') && array_key_exists(substr($name, 2), $this->elementMap)) {
|
||||||
$name = substr($name, 2);
|
$name = substr($name, 2);
|
||||||
} else {
|
} else {
|
||||||
return ['Sabre\\Xml\\Element\\Base', 'xmlDeserialize'];
|
return Element\Base::xmlDeserialize(...);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -302,8 +302,8 @@ class Reader extends \XMLReader
|
||||||
return $deserializer;
|
return $deserializer;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_subclass_of($deserializer, 'Sabre\\Xml\\XmlDeserializable')) {
|
if (is_subclass_of($deserializer, XmlDeserializable::class)) {
|
||||||
return [$deserializer, 'xmlDeserialize'];
|
return fn (Reader $reader) => $deserializer::xmlDeserialize($reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new \LogicException('Could not use this type as a deserializer: '.get_debug_type($deserializer).' for element: '.$name);
|
throw new \LogicException('Could not use this type as a deserializer: '.get_debug_type($deserializer).' for element: '.$name);
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ function valueObject(Writer $writer, object $valueObject, string $namespace): vo
|
||||||
*
|
*
|
||||||
* You can even mix the two array syntaxes.
|
* You can even mix the two array syntaxes.
|
||||||
*
|
*
|
||||||
* @param string|int|float|bool|array<int|string, mixed>|object $value
|
* @param string|int|float|bool|array<int|string, mixed>|object|null $value
|
||||||
*/
|
*/
|
||||||
function standardSerializer(Writer $writer, $value): void
|
function standardSerializer(Writer $writer, $value): void
|
||||||
{
|
{
|
||||||
|
|
@ -133,9 +133,9 @@ function standardSerializer(Writer $writer, $value): void
|
||||||
} elseif ($value instanceof XmlSerializable) {
|
} elseif ($value instanceof XmlSerializable) {
|
||||||
// XmlSerializable classes or Element classes.
|
// XmlSerializable classes or Element classes.
|
||||||
$value->xmlSerialize($writer);
|
$value->xmlSerialize($writer);
|
||||||
} elseif (is_object($value) && isset($writer->classMap[get_class($value)])) {
|
} elseif (is_object($value) && isset($writer->classMap[$value::class])) {
|
||||||
// It's an object which class appears in the classmap.
|
// It's an object which class appears in the classmap.
|
||||||
$writer->classMap[get_class($value)]($writer, $value);
|
$writer->classMap[$value::class]($writer, $value);
|
||||||
} elseif (is_callable($value)) {
|
} elseif (is_callable($value)) {
|
||||||
// A callback
|
// A callback
|
||||||
$value($writer);
|
$value($writer);
|
||||||
|
|
@ -144,8 +144,8 @@ function standardSerializer(Writer $writer, $value): void
|
||||||
// describes a 'name' and optionally 'attributes' and 'value'.
|
// describes a 'name' and optionally 'attributes' and 'value'.
|
||||||
|
|
||||||
$name = $value['name'];
|
$name = $value['name'];
|
||||||
$attributes = isset($value['attributes']) ? $value['attributes'] : [];
|
$attributes = $value['attributes'] ?? [];
|
||||||
$value = isset($value['value']) ? $value['value'] : null;
|
$value = $value['value'] ?? null;
|
||||||
|
|
||||||
$writer->startElement($name);
|
$writer->startElement($name);
|
||||||
$writer->writeAttributes($attributes);
|
$writer->writeAttributes($attributes);
|
||||||
|
|
@ -157,7 +157,7 @@ function standardSerializer(Writer $writer, $value): void
|
||||||
// This item has a numeric index. We just loop through the
|
// This item has a numeric index. We just loop through the
|
||||||
// array and throw it back in the writer.
|
// array and throw it back in the writer.
|
||||||
standardSerializer($writer, $item);
|
standardSerializer($writer, $item);
|
||||||
} elseif (is_string($name) && is_array($item) && isset($item['attributes'])) {
|
} elseif (is_array($item) && isset($item['attributes'])) {
|
||||||
// The key is used for a name, but $item has 'attributes' and
|
// The key is used for a name, but $item has 'attributes' and
|
||||||
// possibly 'value'
|
// possibly 'value'
|
||||||
$writer->startElement($name);
|
$writer->startElement($name);
|
||||||
|
|
@ -176,7 +176,7 @@ function standardSerializer(Writer $writer, $value): void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} elseif (is_object($value)) {
|
} elseif (is_object($value)) {
|
||||||
throw new \InvalidArgumentException('The writer cannot serialize objects of class: '.get_class($value));
|
throw new \InvalidArgumentException('The writer cannot serialize objects of class: '.$value::class);
|
||||||
} elseif (!is_null($value)) {
|
} elseif (!is_null($value)) {
|
||||||
throw new \InvalidArgumentException('The writer cannot serialize values of type: '.get_debug_type($value));
|
throw new \InvalidArgumentException('The writer cannot serialize values of type: '.get_debug_type($value));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ class Service
|
||||||
}
|
}
|
||||||
|
|
||||||
// If input is empty, then it's safe to throw an exception
|
// If input is empty, then it's safe to throw an exception
|
||||||
if (empty($input)) {
|
if ('' === $input) {
|
||||||
throw new ParseException('The input element to parse is empty. Do not attempt to parse');
|
throw new ParseException('The input element to parse is empty. Do not attempt to parse');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,7 +174,7 @@ class Service
|
||||||
}
|
}
|
||||||
|
|
||||||
// If input is empty, then it's safe to throw an exception
|
// If input is empty, then it's safe to throw an exception
|
||||||
if (empty($input)) {
|
if ('' === $input) {
|
||||||
throw new ParseException('The input element to parse is empty. Do not attempt to parse');
|
throw new ParseException('The input element to parse is empty. Do not attempt to parse');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -254,14 +254,12 @@ class Service
|
||||||
*/
|
*/
|
||||||
public function mapValueObject(string $elementName, string $className): void
|
public function mapValueObject(string $elementName, string $className): void
|
||||||
{
|
{
|
||||||
list($namespace) = self::parseClarkNotation($elementName);
|
[$namespace] = self::parseClarkNotation($elementName);
|
||||||
|
|
||||||
require_once __DIR__ . '/Deserializer/functions.php';
|
require_once __DIR__ . '/Deserializer/functions.php';
|
||||||
$this->elementMap[$elementName] = function (Reader $reader) use ($className, $namespace) {
|
$this->elementMap[$elementName] = (fn (Reader $reader) => \Sabre\Xml\Deserializer\valueObject($reader, $className, $namespace));
|
||||||
return \Sabre\Xml\Deserializer\valueObject($reader, $className, $namespace);
|
|
||||||
};
|
|
||||||
require_once __DIR__ . '/Serializer/functions.php';
|
require_once __DIR__ . '/Serializer/functions.php';
|
||||||
$this->classMap[$className] = function (Writer $writer, $valueObject) use ($namespace) {
|
$this->classMap[$className] = function (Writer $writer, $valueObject) use ($namespace): void {
|
||||||
\Sabre\Xml\Serializer\valueObject($writer, $valueObject, $namespace);
|
\Sabre\Xml\Serializer\valueObject($writer, $valueObject, $namespace);
|
||||||
};
|
};
|
||||||
$this->valueObjectMap[$className] = $elementName;
|
$this->valueObjectMap[$className] = $elementName;
|
||||||
|
|
@ -280,12 +278,12 @@ class Service
|
||||||
*/
|
*/
|
||||||
public function writeValueObject(object $object, ?string $contextUri = null): string
|
public function writeValueObject(object $object, ?string $contextUri = null): string
|
||||||
{
|
{
|
||||||
if (!isset($this->valueObjectMap[get_class($object)])) {
|
if (!isset($this->valueObjectMap[$object::class])) {
|
||||||
throw new \InvalidArgumentException('"'.get_class($object).'" is not a registered value object class. Register your class with mapValueObject.');
|
throw new \InvalidArgumentException('"'.$object::class.'" is not a registered value object class. Register your class with mapValueObject.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->write(
|
return $this->write(
|
||||||
$this->valueObjectMap[get_class($object)],
|
$this->valueObjectMap[$object::class],
|
||||||
$object,
|
$object,
|
||||||
$contextUri
|
$contextUri
|
||||||
);
|
);
|
||||||
|
|
@ -306,7 +304,7 @@ class Service
|
||||||
static $cache = [];
|
static $cache = [];
|
||||||
|
|
||||||
if (!isset($cache[$str])) {
|
if (!isset($cache[$str])) {
|
||||||
if (!preg_match('/^{([^}]*)}(.*)$/', $str, $matches)) {
|
if (1 !== preg_match('/^{([^}]*)}(.*)$/', $str, $matches)) {
|
||||||
throw new \InvalidArgumentException('\''.$str.'\' is not a valid clark-notation formatted string');
|
throw new \InvalidArgumentException('\''.$str.'\' is not a valid clark-notation formatted string');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,5 +16,5 @@ class Version
|
||||||
/**
|
/**
|
||||||
* Full version number.
|
* Full version number.
|
||||||
*/
|
*/
|
||||||
public const VERSION = '4.0.6';
|
public const VERSION = '4.1.0';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -123,11 +123,11 @@ class Writer extends \XMLWriter
|
||||||
public function startElement($name): bool
|
public function startElement($name): bool
|
||||||
{
|
{
|
||||||
if ('{' === $name[0]) {
|
if ('{' === $name[0]) {
|
||||||
list($namespace, $localName) =
|
[$namespace, $localName] =
|
||||||
Service::parseClarkNotation($name);
|
Service::parseClarkNotation($name);
|
||||||
|
|
||||||
if (array_key_exists($namespace, $this->namespaceMap)) {
|
if (array_key_exists($namespace, $this->namespaceMap)) {
|
||||||
$result = $this->startElementNS(
|
$result = $this->startElementNs(
|
||||||
'' === $this->namespaceMap[$namespace] ? null : $this->namespaceMap[$namespace],
|
'' === $this->namespaceMap[$namespace] ? null : $this->namespaceMap[$namespace],
|
||||||
$localName,
|
$localName,
|
||||||
null
|
null
|
||||||
|
|
@ -139,10 +139,8 @@ class Writer extends \XMLWriter
|
||||||
$result = $this->startElement($localName);
|
$result = $this->startElement($localName);
|
||||||
$this->writeAttribute('xmlns', '');
|
$this->writeAttribute('xmlns', '');
|
||||||
} else {
|
} else {
|
||||||
if (!isset($this->adhocNamespaces[$namespace])) {
|
$this->adhocNamespaces[$namespace] ??= 'x'.(count($this->adhocNamespaces) + 1);
|
||||||
$this->adhocNamespaces[$namespace] = 'x'.(count($this->adhocNamespaces) + 1);
|
$result = $this->startElementNs($this->adhocNamespaces[$namespace], $localName, $namespace);
|
||||||
}
|
|
||||||
$result = $this->startElementNS($this->adhocNamespaces[$namespace], $localName, $namespace);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -151,7 +149,7 @@ class Writer extends \XMLWriter
|
||||||
|
|
||||||
if (!$this->namespacesWritten) {
|
if (!$this->namespacesWritten) {
|
||||||
foreach ($this->namespaceMap as $namespace => $prefix) {
|
foreach ($this->namespaceMap as $namespace => $prefix) {
|
||||||
$this->writeAttribute($prefix ? 'xmlns:'.$prefix : 'xmlns', $namespace);
|
$this->writeAttribute(is_string($prefix) && '' !== $prefix ? 'xmlns:'.$prefix : 'xmlns', $namespace);
|
||||||
}
|
}
|
||||||
$this->namespacesWritten = true;
|
$this->namespacesWritten = true;
|
||||||
}
|
}
|
||||||
|
|
@ -248,11 +246,9 @@ class Writer extends \XMLWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't know the namespace, we must add it in-line
|
// We don't know the namespace, we must add it in-line
|
||||||
if (!isset($this->adhocNamespaces[$namespace])) {
|
$this->adhocNamespaces[$namespace] ??= 'x'.(count($this->adhocNamespaces) + 1);
|
||||||
$this->adhocNamespaces[$namespace] = 'x'.(count($this->adhocNamespaces) + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->writeAttributeNS(
|
return $this->writeAttributeNs(
|
||||||
$this->adhocNamespaces[$namespace],
|
$this->adhocNamespaces[$namespace],
|
||||||
$localName,
|
$localName,
|
||||||
$namespace,
|
$namespace,
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,7 @@
|
||||||
"CHROME_NOTIFICATION_DESC_DENIED": "(تم إعتراضها من قبل المتصفح)",
|
"CHROME_NOTIFICATION_DESC_DENIED": "(تم إعتراضها من قبل المتصفح)",
|
||||||
"COLLAPSE_BLOCKQUOTES": "Collapse quoted text",
|
"COLLAPSE_BLOCKQUOTES": "Collapse quoted text",
|
||||||
"MAX_QUOTE_LEVEL": "Maximum visible quoted text levels",
|
"MAX_QUOTE_LEVEL": "Maximum visible quoted text levels",
|
||||||
|
"MIN_FONT_SIZE": "Minimum Font Size",
|
||||||
"LIST_INLINE_ATTACHMENTS": "Show inline attachments in list",
|
"LIST_INLINE_ATTACHMENTS": "Show inline attachments in list",
|
||||||
"LIST_SIMPLE_ATTACHMENTS": "Simple attachments list",
|
"LIST_SIMPLE_ATTACHMENTS": "Simple attachments list",
|
||||||
"LIST_MESSAGES_GROUPED": "List messages grouped (when sorting by date or from)",
|
"LIST_MESSAGES_GROUPED": "List messages grouped (when sorting by date or from)",
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,7 @@
|
||||||
"CHROME_NOTIFICATION_DESC_DENIED": "(Заблакавана браўзэрам)",
|
"CHROME_NOTIFICATION_DESC_DENIED": "(Заблакавана браўзэрам)",
|
||||||
"COLLAPSE_BLOCKQUOTES": "Згортваць блок с працытаваным тэкстам",
|
"COLLAPSE_BLOCKQUOTES": "Згортваць блок с працытаваным тэкстам",
|
||||||
"MAX_QUOTE_LEVEL": "Максімальны ўзровень адлюстравання працытаванага тэксту",
|
"MAX_QUOTE_LEVEL": "Максімальны ўзровень адлюстравання працытаванага тэксту",
|
||||||
|
"MIN_FONT_SIZE": "Minimum Font Size",
|
||||||
"LIST_INLINE_ATTACHMENTS": "Паказваць укладанні ў спісе",
|
"LIST_INLINE_ATTACHMENTS": "Паказваць укладанні ў спісе",
|
||||||
"LIST_SIMPLE_ATTACHMENTS": "Просты спіс укладанняў",
|
"LIST_SIMPLE_ATTACHMENTS": "Просты спіс укладанняў",
|
||||||
"LIST_MESSAGES_GROUPED": "Группаванне спісу паведамленняў (пры сартаванні по даце альбо адпраўніку)",
|
"LIST_MESSAGES_GROUPED": "Группаванне спісу паведамленняў (пры сартаванні по даце альбо адпраўніку)",
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,7 @@
|
||||||
"CHROME_NOTIFICATION_DESC_DENIED": "(Блокирано от браузъра)",
|
"CHROME_NOTIFICATION_DESC_DENIED": "(Блокирано от браузъра)",
|
||||||
"COLLAPSE_BLOCKQUOTES": "Collapse quoted text",
|
"COLLAPSE_BLOCKQUOTES": "Collapse quoted text",
|
||||||
"MAX_QUOTE_LEVEL": "Maximum visible quoted text levels",
|
"MAX_QUOTE_LEVEL": "Maximum visible quoted text levels",
|
||||||
|
"MIN_FONT_SIZE": "Minimum Font Size",
|
||||||
"LIST_INLINE_ATTACHMENTS": "Show inline attachments in list",
|
"LIST_INLINE_ATTACHMENTS": "Show inline attachments in list",
|
||||||
"LIST_SIMPLE_ATTACHMENTS": "Simple attachments list",
|
"LIST_SIMPLE_ATTACHMENTS": "Simple attachments list",
|
||||||
"LIST_MESSAGES_GROUPED": "List messages grouped (when sorting by date or from)",
|
"LIST_MESSAGES_GROUPED": "List messages grouped (when sorting by date or from)",
|
||||||
|
|
|
||||||
21
snappymail/v/0.0.0/app/localization/ca/LICENSE-RainLoop
Normal file
21
snappymail/v/0.0.0/app/localization/ca/LICENSE-RainLoop
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 RainLoopTeam
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
17
snappymail/v/0.0.0/app/localization/ca/README.md
Normal file
17
snappymail/v/0.0.0/app/localization/ca/README.md
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Catalan translation
|
||||||
|
|
||||||
|
Catalan (`ca`, Català) translation for SnappyMail. A complete linguistic pass
|
||||||
|
was applied on 2026-10-16; see [review notes](REVIEW.md). Further corrections
|
||||||
|
from Catalan-speaking users are welcome.
|
||||||
|
|
||||||
|
Existing translations were adapted from RainLoop's official Catalan resources:
|
||||||
|
https://github.com/RainLoop/rainloop-webmail/tree/9bf8fa5b63d7e6871ee533126a7e1b1c85ee45b5/rainloop/v/0.0.0/app/localization
|
||||||
|
|
||||||
|
SnappyMail-specific strings were translated for this contribution. Preserve the
|
||||||
|
original MIT copyright and permission notice in `LICENSE-RainLoop` when reusing
|
||||||
|
the adapted RainLoop texts. The translation includes user, admin and static
|
||||||
|
messages, plus the relative-time fallback resource.
|
||||||
|
|
||||||
|
Native language selector on a test login page (no authenticated session or mail):
|
||||||
|
|
||||||
|

|
||||||
43
snappymail/v/0.0.0/app/localization/ca/REVIEW.md
Normal file
43
snappymail/v/0.0.0/app/localization/ca/REVIEW.md
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
# Catalan linguistic review — 2026-10-16
|
||||||
|
|
||||||
|
Reviewed the full user, administration, static and relative-time resources
|
||||||
|
against their English originals: 567 user entries, 140 administration entries
|
||||||
|
and 7 static entries. This pass changes 210 entries (161 user,
|
||||||
|
46 administration, 3 static), including typography.
|
||||||
|
|
||||||
|
## Editorial criteria
|
||||||
|
|
||||||
|
- Use the Softcatalà distinction between short imperative commands in buttons
|
||||||
|
(for example, *Desa*, *Xifra*) and the *vós* form when addressing users in
|
||||||
|
explanations, questions or instructions (*Introduïu*, *Seleccioneu*, *Voleu*).
|
||||||
|
- Prefer consistent terms such as *desar*, *xifrar/desxifrar*, *conversa*,
|
||||||
|
*text sense format* and *emmagatzematge*. Preserve proper names such as Sieve.
|
||||||
|
- Correct semantic errors, not only spelling: `STOP_LABEL` must stop further
|
||||||
|
processing, `LABEL_UNREAD` marks messages as unread, and the domain allow-list
|
||||||
|
describes permitted **users**, not domains.
|
||||||
|
- Distinguish read/display receipts from delivery receipts. Preserve all message
|
||||||
|
variables, including `%READ-RECEIPT%`.
|
||||||
|
- Remove inherited RainLoop product references from SnappyMail interface text.
|
||||||
|
- Preserve JSON keys, technical flags, placeholders and required HTML structure.
|
||||||
|
Use consistent apostrophes and quotation marks in displayed Catalan text.
|
||||||
|
|
||||||
|
## Representative corrections
|
||||||
|
|
||||||
|
| Before | After | Reason |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| No aturis el processament de les regles | Atura el processament de les regles següents | The previous wording reversed the action. |
|
||||||
|
| No llegeixis els missatges seleccionats | Marca els missatges seleccionats com a no llegits | Describes changing the unread flag. |
|
||||||
|
| Net | Text sense format | Correct meaning of the plain-text editor option. |
|
||||||
|
| scripts de sedàs | scripts Sieve | Sieve is a protocol name. |
|
||||||
|
| Emmagatzeatge | Emmagatzematge | Spelling correction. |
|
||||||
|
| Llista de dominis… | Llista d’usuaris del domini… | The allow-list contains users. |
|
||||||
|
| El RainLoop està actualitzat. | SnappyMail està actualitzat. | Correct product name. |
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [Softcatalà linguistic style](https://www.softcatala.org/guia-estil-de-softcatala/aspectes-linguistics/)
|
||||||
|
- [TERMCAT criteria for software localization](https://arxiu.termcat.cat/criteris/localitzacio-productes-informatics.pdf)
|
||||||
|
|
||||||
|
This is a linguistic review of this contribution, not an endorsement by either
|
||||||
|
organization. Further review and corrections from Catalan-speaking maintainers
|
||||||
|
remain welcome.
|
||||||
182
snappymail/v/0.0.0/app/localization/ca/admin.json
Normal file
182
snappymail/v/0.0.0/app/localization/ca/admin.json
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
{
|
||||||
|
"GLOBAL": {
|
||||||
|
"ADD": "Afegeix",
|
||||||
|
"CLOSE": "Tanca",
|
||||||
|
"SAVE": "Desa",
|
||||||
|
"TEST": "Prova",
|
||||||
|
"UPDATE": "Actualitza",
|
||||||
|
"USERNAME": "Nom d’usuari",
|
||||||
|
"PASSWORD": "Contrasenya",
|
||||||
|
"CANCEL": "Cancel·la",
|
||||||
|
"SEARCH": "Cerca",
|
||||||
|
"YES": "Sí",
|
||||||
|
"NO": "No"
|
||||||
|
},
|
||||||
|
"LOGIN": {
|
||||||
|
"LABEL_LOGIN": "Nom d’usuari",
|
||||||
|
"LABEL_TOTP": "Codi TOTP",
|
||||||
|
"BUTTON_LOGIN": "Accedeix al tauler d’administració"
|
||||||
|
},
|
||||||
|
"TOP_PANEL": {
|
||||||
|
"LABEL_ADMIN_PANEL": "Tauler d’administració"
|
||||||
|
},
|
||||||
|
"SETTINGS_LABELS": {
|
||||||
|
"GENERAL": "General",
|
||||||
|
"LOGIN": "Inici de sessió",
|
||||||
|
"BRANDING": "Imatge de marca",
|
||||||
|
"CONTACTS": "Contactes",
|
||||||
|
"DOMAINS": "Dominis",
|
||||||
|
"SECURITY": "Seguretat",
|
||||||
|
"PACKAGES": "Extensions",
|
||||||
|
"CONFIG": "Configuració",
|
||||||
|
"ABOUT": "Quant a"
|
||||||
|
},
|
||||||
|
"TAB_GENERAL": {
|
||||||
|
"LEGEND_INTERFACE": "Interfície",
|
||||||
|
"LABEL_LANGUAGE": "Llengua",
|
||||||
|
"LABEL_LANGUAGE_ADMIN": "Llengua (administració)",
|
||||||
|
"LABEL_THEME": "Tema",
|
||||||
|
"LABEL_ALLOW_LANGUAGES_ON_SETTINGS": "Permet la selecció de llengua a la pantalla de configuració",
|
||||||
|
"LABEL_ALLOW_THEMES_ON_SETTINGS": "Permet la selecció del tema a la pantalla de configuració",
|
||||||
|
"LABEL_ALLOW_BACKGROUND_ON_SETTINGS": "Permet la selecció del fons a la pantalla de configuració",
|
||||||
|
"LABEL_SHOW_THUMBNAILS": "Mostra les miniatures (adjunts)",
|
||||||
|
"LEGEND_MAIN": "Principal",
|
||||||
|
"LABEL_ATTACHMENT_SIZE_LIMIT": "Límit de mida dels adjunts",
|
||||||
|
"LABEL_ALLOW_ADDITIONAL_ACCOUNTS": "Permet comptes addicionals",
|
||||||
|
"LABEL_ALLOW_IDENTITIES": "Permet identitats múltiples",
|
||||||
|
"ALERT_DATA_ACCESS": "La carpeta de dades és accessible des de l’exterior. Configureu el servidor web per impedir-hi l’accés extern. Més informació:",
|
||||||
|
"ALERT_WARNING": "Avís!",
|
||||||
|
"HTML_ALERT_WEAK_PASSWORD": "Esteu utilitzant la contrasenya d’administració predeterminada.\n<br>\nPer motius de seguretat,\n<strong><a href=\"#/security\">canvieu-la</a></strong>\nara per una altra.\n"
|
||||||
|
},
|
||||||
|
"TAB_LOGIN": {
|
||||||
|
"LEGEND_LOGIN_SCREEN": "Pantalla d’inici de sessió",
|
||||||
|
"LABEL_DEFAULT_DOMAIN": "Domini predeterminat",
|
||||||
|
"LABEL_DETERMINE_USER_DOMAIN": "Intenta determinar el domini de l’usuari",
|
||||||
|
"LABEL_ALLOW_LANGUAGES_ON_LOGIN": "Permet triar la llengua a la pantalla d’inici de sessió",
|
||||||
|
"LABEL_DETERMINE_USER_LANGUAGE": "Intenta determinar la llengua de l’usuari"
|
||||||
|
},
|
||||||
|
"TAB_BRANDING": {
|
||||||
|
"LEGEND_BRANDING": "Imatge de marca",
|
||||||
|
"LABEL_PAGE_TITLE": "Títol de la pàgina",
|
||||||
|
"LABEL_LOADING_DESCRIPTION": "Text durant la càrrega",
|
||||||
|
"LABEL_FAVICON_URL": "URL de la icona de favorits"
|
||||||
|
},
|
||||||
|
"TAB_CONTACTS": {
|
||||||
|
"LEGEND_CONTACTS": "Contactes",
|
||||||
|
"LEGEND_STORAGE": "Emmagatzematge (PDO)",
|
||||||
|
"LABEL_ENABLE_CONTACTS": "Activa els contactes",
|
||||||
|
"LABEL_ALLOW_SYNC": "Permet la sincronització dels contactes (amb el servidor extern CardDAV)",
|
||||||
|
"LABEL_STORAGE_TYPE": "Tipus",
|
||||||
|
"LABEL_STORAGE_DSN": "DSN (nom de la font de dades)",
|
||||||
|
"LABEL_STORAGE_USER": "Usuari",
|
||||||
|
"LABEL_STORAGE_PASSWORD": "Contrasenya",
|
||||||
|
"SQLITE_GLOBAL": "Utilitza una base de dades global en lloc d’una per compte",
|
||||||
|
"ALERT_NOTICE": "Atenció!",
|
||||||
|
"SUGGESTIONS_LIMIT": "Límit de suggeriments",
|
||||||
|
"HTML_ALERT_DO_NOT_USE_THIS_DATABASE": "No useu aquest tipus de base de dades amb un gran nombre d’usuaris actius.",
|
||||||
|
"HTML_ALERT_DOES_NOT_SUPPORTED": "El sistema no admet els contactes.\n<br>\nHeu d’instal·lar o activar l’extensió <strong>PDO (SQLite / MySQL / PostgreSQL)</strong> al servidor.\n"
|
||||||
|
},
|
||||||
|
"TAB_DOMAINS": {
|
||||||
|
"LEGEND_DOMAINS": "Dominis",
|
||||||
|
"BUTTON_ADD_DOMAIN": "Afegeix un domini",
|
||||||
|
"BUTTON_ADD_ALIAS": "Afegeix un àlies",
|
||||||
|
"DELETE_ARE_YOU_SURE": "Voleu continuar?",
|
||||||
|
"HTML_DOMAINS_HELPER": "Llista de dominis als quals el correu web pot accedir.\n<br>\nFeu clic al nom d’un domini per configurar-lo.\n"
|
||||||
|
},
|
||||||
|
"TAB_SECURITY": {
|
||||||
|
"LEGEND_SECURITY": "Seguretat",
|
||||||
|
"LABEL_USE_IMAGE_PROXY": "Utilitza un servidor intermediari local per a les imatges externes",
|
||||||
|
"LABEL_AUTO_VERIFY": "Verifica les signatures automàticament",
|
||||||
|
"LABEL_SHOW_PHP_INFO": "Mostra la informació de PHP",
|
||||||
|
"LEGEND_ADMIN_PANEL_ACCESS_CREDENTIALS": "Credencials d’accés al tauler d’administració",
|
||||||
|
"LABEL_CURRENT_PASSWORD": "Contrasenya actual",
|
||||||
|
"LABEL_NEW_PASSWORD": "Contrasenya nova",
|
||||||
|
"LABEL_REPEAT_PASSWORD": "Repetiu la contrasenya",
|
||||||
|
"LABEL_REQUIRE_VERIFICATION": "Exigeix la verificació del certificat SSL utilitzat (IMAP / SMTP)",
|
||||||
|
"CRYPTO": "Criptografia",
|
||||||
|
"LABEL_ALLOW_SELF_SIGNED": "Permet els certificats autosignats"
|
||||||
|
},
|
||||||
|
"TAB_PACKAGES": {
|
||||||
|
"LABEL_ENABLE_PLUGINS": "Activa les extensions",
|
||||||
|
"LEGEND_AVAILABLE_FOR_UPDATE": "Actualitzacions disponibles",
|
||||||
|
"LEGEND_AVAILABLE_FOR_INSTALLATION": "Extensions disponibles per instal·lar",
|
||||||
|
"LEGEND_INSTALLED_PACKAGES": "Paquets instal·lats",
|
||||||
|
"ALERT_CANNOT_ACCESS_REPOSITORY": "Ara no es pot accedir al repositori."
|
||||||
|
},
|
||||||
|
"TAB_ABOUT": {
|
||||||
|
"LEGEND_ABOUT": "Quant a",
|
||||||
|
"LABEL_TAG_HINT": "Client de correu electrònic senzill, modern i ràpid basat en la web",
|
||||||
|
"LABEL_ALL_RIGHTS_RESERVED": "Tots els drets reservats.",
|
||||||
|
"HINT_READ_CHANGE_LOG": "Llegiu el registre de canvis abans d’actualitzar.",
|
||||||
|
"HINT_IS_UP_TO_DATE": "SnappyMail està actualitzat.",
|
||||||
|
"HTML_NEW_VERSION": "Hi ha disponible una versió nova: <b>%VERSION%</b>.",
|
||||||
|
"LABEL_UPDATING": "S’està actualitzant…",
|
||||||
|
"LABEL_CHECKING": "S’està comprovant si hi ha actualitzacions…",
|
||||||
|
"BUTTON_RELEASES": "Versions publicades"
|
||||||
|
},
|
||||||
|
"POPUPS_DOMAIN_ALIAS": {
|
||||||
|
"TITLE_ADD_DOMAIN_ALIAS": "Afegeix un àlies",
|
||||||
|
"LABEL_ALIAS": "Àlies",
|
||||||
|
"LABEL_DOMAIN": "Domini"
|
||||||
|
},
|
||||||
|
"POPUPS_DOMAIN": {
|
||||||
|
"TITLE_ADD_DOMAIN": "Afegeix un domini",
|
||||||
|
"TITLE_ADD_DOMAIN_WITH_NAME": "Afegeix el domini «%NAME%»",
|
||||||
|
"TITLE_EDIT_DOMAIN": "Edita el domini «%NAME%»",
|
||||||
|
"LABEL_NAME": "Nom",
|
||||||
|
"NAME_HELPER": "S’admet el comodí *",
|
||||||
|
"LABEL_SERVER": "Servidor",
|
||||||
|
"LABEL_PORT": "Port",
|
||||||
|
"LABEL_SECURE": "Seguretat",
|
||||||
|
"TIMEOUT": "Temps d’espera",
|
||||||
|
"DISABLE_CAPABILITIES": "Desactiva capacitats",
|
||||||
|
"LIMITS": "Límits",
|
||||||
|
"FOLDERS": "Carpetes",
|
||||||
|
"MESSAGES": "Missatges",
|
||||||
|
"SECURE_OPTION_NONE": "Cap",
|
||||||
|
"LABEL_ALLOW_SIEVE_SCRIPTS": "Permet scripts Sieve",
|
||||||
|
"LABEL_USE_SHORT_LOGIN": "Utilitza el nom d’usuari sense el domini",
|
||||||
|
"LABEL_LOWERCASE_LOGIN": "Nom d’usuari en minúscules, sense distingir majúscules i minúscules",
|
||||||
|
"LABEL_USE_AUTH": "Utilitza l’autenticació",
|
||||||
|
"LABEL_SET_SENDER": "Utilitza el nom d’usuari com a remitent",
|
||||||
|
"LABEL_AUTH_PLAIN_LINE": "Força l’autenticació amb una sola ordre AUTH PLAIN",
|
||||||
|
"LABEL_USE_PHP_MAIL": "Utilitza la funció mail() de PHP",
|
||||||
|
"BUTTON_WHITE_LIST": "Usuaris permesos",
|
||||||
|
"NEW_DOMAIN_DESC": "Aquesta configuració de domini us permetrà treballar amb les adreces de correu de <strong>%NAME%</strong>.\n",
|
||||||
|
"WHITE_LIST_ALERT": "Llista d’usuaris del domini que poden accedir al correu web.\nSepareu-los amb espais.\n",
|
||||||
|
"VALID_SPAM_HEADERS": "Capçaleres vàlides de correu brossa",
|
||||||
|
"VALID_VIRUS_HEADERS": "Capçaleres vàlides de virus"
|
||||||
|
},
|
||||||
|
"POPUPS_PLUGIN": {
|
||||||
|
"TITLE_PLUGIN": "Extensió",
|
||||||
|
"DESC_NOTHING_TO_CONFIGURE": "No hi ha res per configurar."
|
||||||
|
},
|
||||||
|
"POPUPS_ASK": {
|
||||||
|
"DESC_WANT_CLOSE_THIS_WINDOW": "Segur que voleu tancar aquesta finestra?"
|
||||||
|
},
|
||||||
|
"POPUPS_LANGUAGES": {
|
||||||
|
"TITLE_LANGUAGES": "Trieu la llengua"
|
||||||
|
},
|
||||||
|
"HINTS": {
|
||||||
|
"BETA": "beta",
|
||||||
|
"WARNING": "Avís!"
|
||||||
|
},
|
||||||
|
"ERRORS": {
|
||||||
|
"DOMAIN_ALREADY_EXISTS": "El domini ja existeix.",
|
||||||
|
"UNKNOWN_ERROR": "Error desconegut"
|
||||||
|
},
|
||||||
|
"NOTIFICATIONS": {
|
||||||
|
"RequestError": "La sol·licitud ha fallat",
|
||||||
|
"RequestAborted": "S’ha cancel·lat la sol·licitud",
|
||||||
|
"RequestTimeout": "S’ha esgotat el temps d’espera de la sol·licitud",
|
||||||
|
"InvalidToken": "Testimoni no vàlid",
|
||||||
|
"AuthError": "Ha fallat l’autenticació",
|
||||||
|
"DomainAlreadyExists": "El domini ja existeix.",
|
||||||
|
"CantSavePluginSettings": "No es pot desar la configuració.",
|
||||||
|
"CantInstallPackage": "No s’ha pogut instal·lar l’extensió",
|
||||||
|
"CantDeletePackage": "No s’ha pogut eliminar l’extensió",
|
||||||
|
"InvalidPluginPackage": "Paquet d’extensió no vàlid",
|
||||||
|
"UnsupportedPluginPackage": "Paquet d’extensió no compatible",
|
||||||
|
"UnknownError": "Error desconegut"
|
||||||
|
}
|
||||||
|
}
|
||||||
75
snappymail/v/0.0.0/app/localization/ca/relativetimeformat.js
Normal file
75
snappymail/v/0.0.0/app/localization/ca/relativetimeformat.js
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
{
|
||||||
|
"long": {
|
||||||
|
"year": {
|
||||||
|
"past": {
|
||||||
|
"one": "fa {0} any",
|
||||||
|
"other": "fa {0} anys"
|
||||||
|
},
|
||||||
|
"future": {
|
||||||
|
"one": "d’aquí a {0} any",
|
||||||
|
"other": "d’aquí a {0} anys"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"month": {
|
||||||
|
"past": {
|
||||||
|
"one": "fa {0} mes",
|
||||||
|
"other": "fa {0} mesos"
|
||||||
|
},
|
||||||
|
"future": {
|
||||||
|
"one": "d’aquí a {0} mes",
|
||||||
|
"other": "d’aquí a {0} mesos"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"week": {
|
||||||
|
"past": {
|
||||||
|
"one": "fa {0} setmana",
|
||||||
|
"other": "fa {0} setmanes"
|
||||||
|
},
|
||||||
|
"future": {
|
||||||
|
"one": "d’aquí a {0} setmana",
|
||||||
|
"other": "d’aquí a {0} setmanes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"day": {
|
||||||
|
"past": {
|
||||||
|
"one": "fa {0} dia",
|
||||||
|
"other": "fa {0} dies"
|
||||||
|
},
|
||||||
|
"future": {
|
||||||
|
"one": "d’aquí a {0} dia",
|
||||||
|
"other": "d’aquí a {0} dies"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hour": {
|
||||||
|
"past": {
|
||||||
|
"one": "fa {0} hora",
|
||||||
|
"other": "fa {0} hores"
|
||||||
|
},
|
||||||
|
"future": {
|
||||||
|
"one": "d’aquí a {0} hora",
|
||||||
|
"other": "d’aquí a {0} hores"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minute": {
|
||||||
|
"past": {
|
||||||
|
"one": "fa {0} minut",
|
||||||
|
"other": "fa {0} minuts"
|
||||||
|
},
|
||||||
|
"future": {
|
||||||
|
"one": "d’aquí a {0} minut",
|
||||||
|
"other": "d’aquí a {0} minuts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"second": {
|
||||||
|
"past": {
|
||||||
|
"one": "fa {0} segon",
|
||||||
|
"other": "fa {0} segons"
|
||||||
|
},
|
||||||
|
"future": {
|
||||||
|
"one": "d’aquí a {0} segon",
|
||||||
|
"other": "d’aquí a {0} segons"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plural: n => n === 1 ? "one" : "other"
|
||||||
|
}
|
||||||
BIN
snappymail/v/0.0.0/app/localization/ca/selector.png
Normal file
BIN
snappymail/v/0.0.0/app/localization/ca/selector.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
9
snappymail/v/0.0.0/app/localization/ca/static.json
Normal file
9
snappymail/v/0.0.0/app/localization/ca/static.json
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"BACK_LINK": "Torna a carregar",
|
||||||
|
"NO_SCRIPT_TITLE": "Aquesta aplicació necessita JavaScript.",
|
||||||
|
"NO_SCRIPT_DESC": "El navegador no té activat JavaScript.\nActiveu-lo a la configuració del navegador i torneu-ho a provar.",
|
||||||
|
"NO_COOKIE_TITLE": "Aquesta aplicació necessita galetes.",
|
||||||
|
"NO_COOKIE_DESC": "El navegador no té activades les galetes.\nActiveu-les a la configuració del navegador i torneu-ho a provar.",
|
||||||
|
"BAD_BROWSER_TITLE": "El navegador està obsolet.",
|
||||||
|
"BAD_BROWSER_DESC": "Per utilitzar totes les funcions de l’aplicació,\nbaixeu i instal·leu un navegador actualitzat."
|
||||||
|
}
|
||||||
647
snappymail/v/0.0.0/app/localization/ca/user.json
Normal file
647
snappymail/v/0.0.0/app/localization/ca/user.json
Normal file
|
|
@ -0,0 +1,647 @@
|
||||||
|
{
|
||||||
|
"LANG_DIR": "ltr",
|
||||||
|
"GLOBAL": {
|
||||||
|
"TO_ARCHIVE": "Arxiva",
|
||||||
|
"ARE_YOU_SURE": "Voleu continuar?",
|
||||||
|
"BACK": "Enrere",
|
||||||
|
"BCC": "C/o",
|
||||||
|
"CANCEL": "Cancel·la",
|
||||||
|
"CC": "A/c",
|
||||||
|
"CLEAR": "Neteja",
|
||||||
|
"CLOSE": "Tanca",
|
||||||
|
"CONTACTS": "Contactes",
|
||||||
|
"DELETE": "Suprimeix",
|
||||||
|
"UNDELETE": "Restaura",
|
||||||
|
"DONE": "Fet",
|
||||||
|
"EMAIL": "Adreça de correu",
|
||||||
|
"FROM": "De",
|
||||||
|
"LOADING": "S’està carregant…",
|
||||||
|
"LOGOUT": "Tanca la sessió",
|
||||||
|
"MORE": "Més",
|
||||||
|
"MOVE_TO": "Mou a",
|
||||||
|
"COPY_TO": "Copia a",
|
||||||
|
"NAME": "Nom",
|
||||||
|
"NONE": "Cap",
|
||||||
|
"TO_SPAM": "Mou els missatges al correu brossa",
|
||||||
|
"NOT_SPAM": "No és correu brossa",
|
||||||
|
"USERNAME": "Nom d’usuari",
|
||||||
|
"PASSWORD": "Contrasenya",
|
||||||
|
"REMEMBER": "Recorda",
|
||||||
|
"REPLY_TO": "Respon a",
|
||||||
|
"SAVE": "Desa",
|
||||||
|
"SAVE_CHANGES": "Voleu desar els canvis?",
|
||||||
|
"SEARCH": "Cerca",
|
||||||
|
"SPAM": "Correu brossa",
|
||||||
|
"SUBJECT": "Assumpte",
|
||||||
|
"TEST": "Prova",
|
||||||
|
"TO": "Per a",
|
||||||
|
"SERVER_MESSAGE": "Missatge del servidor",
|
||||||
|
"YES": "Sí",
|
||||||
|
"NO": "No",
|
||||||
|
"INACTIVE": "Inactiu",
|
||||||
|
"DEFAULT": "predeterminat",
|
||||||
|
"HELP": "Ajuda",
|
||||||
|
"TEXT": "Text",
|
||||||
|
"ATTACHMENTS": "Adjunts"
|
||||||
|
},
|
||||||
|
"LOGIN": {
|
||||||
|
"LABEL_LOGIN": "Nom d’usuari",
|
||||||
|
"LABEL_SIGN_ME": "Recorda’m",
|
||||||
|
"BUTTON_SIGN_IN": "Inicia la sessió"
|
||||||
|
},
|
||||||
|
"TOP_TOOLBAR": {
|
||||||
|
"BUTTON_ADD_ACCOUNT": "Afegeix un compte"
|
||||||
|
},
|
||||||
|
"SEARCH": {
|
||||||
|
"TITLE_ADV": "Cerca avançada",
|
||||||
|
"TEXT": "Text",
|
||||||
|
"HAS_ATTACHMENT": "Té fitxers adjunts",
|
||||||
|
"FLAGGED": "Destacats",
|
||||||
|
"UNSEEN": "No llegits",
|
||||||
|
"DATE": "Data",
|
||||||
|
"DATE_ALL": "Qualsevol data",
|
||||||
|
"BEFORE_3_DAYS": "De fa més de 3 dies",
|
||||||
|
"BEFORE_7_DAYS": "De fa més d’una setmana",
|
||||||
|
"BEFORE_MONTH": "De fa més d’un mes",
|
||||||
|
"BEFORE_3_MONTHS": "De fa més de 3 mesos",
|
||||||
|
"BEFORE_6_MONTHS": "De fa més de 6 mesos",
|
||||||
|
"BEFORE_YEAR": "De fa més d’un any",
|
||||||
|
"SINCE_3_DAYS": "Antiguitat de fins a 3 dies",
|
||||||
|
"SINCE_7_DAYS": "Antiguitat de fins a 1 setmana",
|
||||||
|
"SINCE_MONTH": "Antiguitat de fins a 1 mes",
|
||||||
|
"SINCE_3_MONTHS": "Antiguitat de fins a 3 mesos",
|
||||||
|
"SINCE_6_MONTHS": "Antiguitat de fins a 6 mesos",
|
||||||
|
"SINCE_YEAR": "Antiguitat de fins a 1 any",
|
||||||
|
"SUBFOLDERS": "Subcarpetes",
|
||||||
|
"SUBFOLDERS_NONE": "Cap",
|
||||||
|
"SUBFOLDERS_SUBTREE": "Totes",
|
||||||
|
"SUBFOLDERS_SUBTREE_ONE": "Un nivell",
|
||||||
|
"REPLIED": "Respostos",
|
||||||
|
"KEYWORD": "Etiqueta"
|
||||||
|
},
|
||||||
|
"FOLDER_LIST": {
|
||||||
|
"BUTTON_COMPOSE": "Escriu",
|
||||||
|
"BUTTON_NEW_MESSAGE": "Missatge nou",
|
||||||
|
"INBOX_NAME": "Bústia d’entrada",
|
||||||
|
"SENT_NAME": "Enviats",
|
||||||
|
"DRAFTS_NAME": "Esborranys",
|
||||||
|
"TRASH_NAME": "Paperera",
|
||||||
|
"ARCHIVE_NAME": "Arxiu"
|
||||||
|
},
|
||||||
|
"IMAP": {
|
||||||
|
"ACL_RIGHTS": "Permisos",
|
||||||
|
"ACL_A": "Administra",
|
||||||
|
"ACL_E": "Elimina definitivament",
|
||||||
|
"ACL_I": "Insereix missatges",
|
||||||
|
"ACL_K": "Crea subcarpetes",
|
||||||
|
"ACL_L": "Mostra els missatges",
|
||||||
|
"ACL_P": "Envia correu",
|
||||||
|
"ACL_R": "Selecciona una carpeta",
|
||||||
|
"ACL_S": "Etiqueta \\SEEN",
|
||||||
|
"ACL_T": "Etiqueta \\DELETED",
|
||||||
|
"ACL_W": "Altres etiquetes",
|
||||||
|
"ACL_X": "Elimina o canvia el nom de la carpeta"
|
||||||
|
},
|
||||||
|
"MESSAGE_LIST": {
|
||||||
|
"BUTTON_RELOAD": "Torna a carregar la llista de missatges",
|
||||||
|
"BUTTON_EMPTY_FOLDER": "Buida la carpeta",
|
||||||
|
"BUTTON_MULTY_FORWARD": "Reenvia com a fitxers adjunts",
|
||||||
|
"BUTTON_DELETE_WITHOUT_MOVE": "Suprimeix definitivament",
|
||||||
|
"MENU_SET_SEEN": "Marca com a llegit",
|
||||||
|
"MENU_SET_ALL_SEEN": "Marca’ls tots com a llegits",
|
||||||
|
"MENU_UNSET_SEEN": "Marca com a no llegit",
|
||||||
|
"MENU_SET_FLAG": "Destaca",
|
||||||
|
"MENU_UNSET_FLAG": "Deixa de destacar",
|
||||||
|
"MENU_SELECT_ALL": "Selecciona-ho tot",
|
||||||
|
"MENU_SELECT_INVERT": "Inverteix la selecció",
|
||||||
|
"MENU_SELECT_UNSEEN": "No llegits",
|
||||||
|
"MENU_SELECT_SEEN": "Llegits",
|
||||||
|
"MENU_SELECT_FLAGGED": "Destacats",
|
||||||
|
"MENU_SELECT_UNFLAGGED": "Sense destacar",
|
||||||
|
"DOWNLOAD_ALL_ATTACHMENTS": "Baixa tots els fitxers adjunts",
|
||||||
|
"EMPTY_LIST": "No hi ha missatges.",
|
||||||
|
"EMPTY_SEARCH_LIST": "Cap missatge coincideix amb la cerca.",
|
||||||
|
"SEARCH_RESULT_FOR": "Resultats de la cerca de «%SEARCH%»",
|
||||||
|
"BACK_TO_MESSAGE_LIST": "Torna a la llista de missatges",
|
||||||
|
"PUT_MESSAGE_HERE": "Deixeu anar el missatge aquí per veure’l a la llista.",
|
||||||
|
"TODAY_AT": "avui a les %TIME%",
|
||||||
|
"YESTERDAY_AT": "ahir a les %TIME%",
|
||||||
|
"NEW_MESSAGE_NOTIFICATION": "Teniu %COUNT% missatges nous.",
|
||||||
|
"QUOTA_SIZE": "S’utilitzen <strong>%SIZE% (%PROC%%)</strong> de <strong>%LIMIT%</strong>",
|
||||||
|
"SORT": "Ordena",
|
||||||
|
"NO_SORT": "El servidor de correu no permet ordenar els missatges",
|
||||||
|
"SORT_ARRIVAL_ASC": "Data de recepció ascendent",
|
||||||
|
"SORT_ARRIVAL_DESC": "Data de recepció descendent",
|
||||||
|
"SORT_DATE_ASC": "Data ascendent",
|
||||||
|
"SORT_DATE_DESC": "Data descendent",
|
||||||
|
"SORT_SIZE_ASC": "Mida ascendent",
|
||||||
|
"SORT_SIZE_DESC": "Mida descendent",
|
||||||
|
"SORT_SUBJECT_ASC": "Assumpte A–Z",
|
||||||
|
"SORT_SUBJECT_DESC": "Assumpte Z–A",
|
||||||
|
"SORT_FROM_ASC": "Remitent A–Z",
|
||||||
|
"SORT_FROM_DESC": "Remitent Z–A"
|
||||||
|
},
|
||||||
|
"MESSAGE": {
|
||||||
|
"BUTTON_EDIT": "Edita",
|
||||||
|
"BUTTON_UNSUBSCRIBE": "Cancel·la la subscripció a la llista",
|
||||||
|
"BUTTON_REPLY": "Respon",
|
||||||
|
"BUTTON_REPLY_ALL": "Respon a tothom",
|
||||||
|
"BUTTON_FORWARD": "Reenvia",
|
||||||
|
"BUTTON_FORWARD_AS_ATTACHMENT": "Reenvia com a fitxer adjunt",
|
||||||
|
"BUTTON_EDIT_AS_NEW": "Edita com a missatge nou",
|
||||||
|
"BUTTON_SHOW_IMAGES": "Mostra les imatges externes",
|
||||||
|
"BUTTON_NOTIFY_READ_RECEIPT": "El remitent ha demanat rebre una notificació quan llegiu aquest missatge.",
|
||||||
|
"BUTTON_IN_NEW_WINDOW": "Mostra en una finestra separada",
|
||||||
|
"BUTTON_THREAD_LIST": "Llista de missatges de la conversa",
|
||||||
|
"BUTTON_THREAD_PREV": "Anterior",
|
||||||
|
"BUTTON_THREAD_NEXT": "Següent",
|
||||||
|
"BUTTON_THREAD_MORE": "Més missatges",
|
||||||
|
"MENU_HEADERS": "Mostra les capçaleres del missatge",
|
||||||
|
"MENU_VIEW_ORIGINAL": "Mostra el codi font",
|
||||||
|
"HTML_VIEW": "Mostra el missatge en HTML",
|
||||||
|
"PLAIN_VIEW": "Mostra el missatge en text sense format",
|
||||||
|
"SWAP_COLORS": "Intercanvia el color de fons predeterminat",
|
||||||
|
"MENU_DOWNLOAD_ORIGINAL": "Baixa com a fitxer .eml",
|
||||||
|
"MENU_FILTER_SIMILAR": "Filtra missatges semblants",
|
||||||
|
"MENU_PRINT": "Imprimeix",
|
||||||
|
"EMPTY_SUBJECT_TEXT": "(Sense assumpte)",
|
||||||
|
"LABEL_DATE": "Data",
|
||||||
|
"LABEL_FROM_SHORT": "de",
|
||||||
|
"LABEL_TO_SHORT": "per a",
|
||||||
|
"PRINT_LABEL_ATTACHMENTS": "Adjunts",
|
||||||
|
"MESSAGE_VIEW_DESC": "Seleccioneu qualsevol missatge de la llista per veure’l aquí.",
|
||||||
|
"MESSAGE_VIEW_MOVE_DESC": "Feu clic al nom de la carpeta del tauler de l’esquerra per seleccionar la destinació.",
|
||||||
|
"LINK_DOWNLOAD_AS_ZIP": "Baixa en format ZIP",
|
||||||
|
"SPAM_SCORE": "Puntuació de correu brossa",
|
||||||
|
"HAS_VIRUS_WARNING": "AVÍS: s’ha detectat un virus",
|
||||||
|
"TAGS": "Etiquetes",
|
||||||
|
"NEW_TAG": "Etiqueta nova",
|
||||||
|
"ENABLE_TRACKING_LINKS": "Activa els enllaços de seguiment",
|
||||||
|
"TRACKING_ENABLED": "Ara es farà el seguiment dels enllaços quan hi feu clic."
|
||||||
|
},
|
||||||
|
"MESSAGE_TAGS": {
|
||||||
|
"$important": "Important",
|
||||||
|
"$todo": "Per fer",
|
||||||
|
"$label1": "Important",
|
||||||
|
"$label2": "Feina",
|
||||||
|
"$label3": "Personal",
|
||||||
|
"$label4": "Per fer",
|
||||||
|
"$label5": "Més endavant"
|
||||||
|
},
|
||||||
|
"READ_RECEIPT": {
|
||||||
|
"SUBJECT": "Confirmació de lectura (missatge mostrat) - %SUBJECT%",
|
||||||
|
"BODY": "Aquesta és una confirmació de lectura del missatge que vau enviar a %READ-RECEIPT%.\n\nNota: aquesta confirmació només indica que el missatge s’ha mostrat a l’ordinador del destinatari.\nNo garanteix que el destinatari hagi llegit o entès el contingut del missatge.\n"
|
||||||
|
},
|
||||||
|
"SUGGESTIONS": {
|
||||||
|
"SEARCHING_DESC": "S’està cercant…"
|
||||||
|
},
|
||||||
|
"CONTACTS": {
|
||||||
|
"TAB_CONTACT": "Contacte",
|
||||||
|
"TAB_LOCATIONS": "Ubicacions",
|
||||||
|
"TAB_BUSINESS": "Feina",
|
||||||
|
"LABEL_ORGANIZATION": "Organització",
|
||||||
|
"LABEL_DEPARTMENT": "Departament",
|
||||||
|
"LABEL_TITLE": "Càrrec",
|
||||||
|
"TAB_CRYPTO": "Criptografia",
|
||||||
|
"ASK": "Pregunta",
|
||||||
|
"NEVER": "Mai",
|
||||||
|
"ALWAYS": "Sempre",
|
||||||
|
"ALWAYS_IF_POSSIBLE": "Sempre que sigui possible",
|
||||||
|
"BUTTON_ADD_CONTACT": "Afegeix un contacte",
|
||||||
|
"BUTTON_CREATE_CONTACT": "Crea",
|
||||||
|
"BUTTON_IMPORT": "Importa (csv, vcf, vCard)",
|
||||||
|
"BUTTON_EXPORT_VCARD": "Exporta (vcf, vCard)",
|
||||||
|
"BUTTON_EXPORT_CSV": "Exporta (csv)",
|
||||||
|
"ERROR_IMPORT_FILE": "Error d’importació (format de fitxer no vàlid)",
|
||||||
|
"EMPTY_LIST": "Aquí no hi ha cap contacte",
|
||||||
|
"EMPTY_SEARCH": "No s’ha trobat cap contacte",
|
||||||
|
"CLEAR_SEARCH": "Neteja la cerca",
|
||||||
|
"CONTACT_VIEW_DESC": "Seleccioneu un contacte de la llista per veure’l aquí.",
|
||||||
|
"LABEL_BIRTHDAY": "Data de naixement",
|
||||||
|
"DISPLAY_NAME": "Nom per mostrar",
|
||||||
|
"LAST_NAME": "Cognoms",
|
||||||
|
"FIRST_NAME": "Nom",
|
||||||
|
"MIDDLE_NAME": "Segon nom",
|
||||||
|
"NAME_PREFIX": "Prefix del nom",
|
||||||
|
"NAME_SUFFIX": "Sufix del nom",
|
||||||
|
"NICK_NAME": "Àlies",
|
||||||
|
"LABEL_READ_ONLY": "Només de lectura",
|
||||||
|
"ADD_MENU_LABEL": "Afegeix",
|
||||||
|
"ADD_MENU_NICKNAME": "Àlies",
|
||||||
|
"ADD_MENU_NOTE": "Nota",
|
||||||
|
"ADD_MENU_PHONE": "Telèfon",
|
||||||
|
"ADD_MENU_URL": "URL",
|
||||||
|
"ADD_MENU_ADDRESS": "Adreça",
|
||||||
|
"ADD_MENU_BIRTHDAY": "Data de naixement",
|
||||||
|
"ADD_MENU_TAGS": "Etiquetes",
|
||||||
|
"BUTTON_SHARE_ALL": "Tothom",
|
||||||
|
"BUTTON_SYNC": "Sincronització (CardDAV)",
|
||||||
|
"SEND_TO_ALL_CONTACT_EMAILS": "Envia a totes les adreces del contacte"
|
||||||
|
},
|
||||||
|
"COMPOSE": {
|
||||||
|
"LINK_SHOW_INPUTS": "Mostra tots els camps",
|
||||||
|
"BUTTON_SEND": "Envia",
|
||||||
|
"BUTTON_MINIMIZE": "Minimitza",
|
||||||
|
"SAVED_TIME": "Desat a les %TIME%",
|
||||||
|
"SAVED_ERROR_ON_SEND": "El missatge s’ha enviat, però no s’ha desat a la carpeta de missatges enviats.",
|
||||||
|
"DISCARD_UNSAVED_DATA": "Voleu descartar les dades no desades?",
|
||||||
|
"ATTACH_FILES": "Adjunta fitxers",
|
||||||
|
"ATTACH_DROP_FILES_DESC": "Deixeu anar fitxers aquí",
|
||||||
|
"REPLY_MESSAGE_TITLE": "%DATETIME%, %EMAIL% va escriure",
|
||||||
|
"FORWARD_MESSAGE_TOP_TITLE": "-------- Missatge reenviat -------",
|
||||||
|
"FORWARD_MESSAGE_TOP_SENT": "Enviat",
|
||||||
|
"EMPTY_TO_ERROR_DESC": "Indiqueu almenys un destinatari.",
|
||||||
|
"NO_ATTACHMENTS_HERE_DESC": "No hi ha cap fitxer adjunt.",
|
||||||
|
"ATTACHMENTS_ERROR_DESC": "No s’han pogut pujar tots els fitxers adjunts.",
|
||||||
|
"ATTACHMENTS_UPLOAD_ERROR_DESC": "Encara s’estan pujant fitxers adjunts.",
|
||||||
|
"ALLOW_SPELLCHECK": "Activa el corrector ortogràfic del navegador",
|
||||||
|
"ALLOW_DRAFT_AUTOSAVE": "Desa l’esborrany automàticament",
|
||||||
|
"BUTTON_REQUEST_READ_RECEIPT": "Demana una confirmació de lectura",
|
||||||
|
"BUTTON_MARK_AS_IMPORTANT": "Marca com a important",
|
||||||
|
"BUTTON_REQUEST_DSN": "Demana una confirmació de lliurament",
|
||||||
|
"BUTTON_REQUIRE_TLS": "Exigeix un lliurament segur",
|
||||||
|
"ERROR_EMPTY_BODY": "El cos del missatge és buit"
|
||||||
|
},
|
||||||
|
"POPUPS_ASK": {
|
||||||
|
"EXIT_ARE_YOU_SURE": "Segur que voleu sortir?",
|
||||||
|
"DESC_WANT_CLOSE_THIS_WINDOW": "Segur que voleu tancar aquesta finestra?",
|
||||||
|
"DESC_WANT_DELETE_MESSAGES": "Segur que voleu suprimir els missatges?"
|
||||||
|
},
|
||||||
|
"POPUPS_LANGUAGES": {
|
||||||
|
"TITLE_LANGUAGES": "Trieu la llengua"
|
||||||
|
},
|
||||||
|
"POPUPS_ADD_ACCOUNT": {
|
||||||
|
"TITLE_ADD_ACCOUNT": "Afegeix un compte",
|
||||||
|
"BUTTON_ADD_ACCOUNT": "Afegeix-lo",
|
||||||
|
"TITLE_UPDATE_ACCOUNT": "Actualitza el compte"
|
||||||
|
},
|
||||||
|
"POPUPS_IDENTITY": {
|
||||||
|
"TITLE_ADD_IDENTITY": "Afegeix una identitat",
|
||||||
|
"TITLE_UPDATE_IDENTITY": "Actualitza la identitat",
|
||||||
|
"LABEL": "Etiqueta",
|
||||||
|
"BUTTON_ADD_IDENTITY": "Afegeix-la",
|
||||||
|
"SIGNATURE": "Signatura",
|
||||||
|
"LABEL_SIGNATURE_INSERT_BEFORE": "Insereix aquesta signatura abans del text citat en les respostes."
|
||||||
|
},
|
||||||
|
"POPUPS_CREATE_FOLDER": {
|
||||||
|
"TITLE_CREATE_FOLDER": "Crea una carpeta",
|
||||||
|
"LABEL_PARENT": "Carpeta superior",
|
||||||
|
"BUTTON_CREATE": "Crea-la",
|
||||||
|
"TITLE_CREATING_PROCESS": "S’està creant la carpeta…"
|
||||||
|
},
|
||||||
|
"POPUPS_CLEAR_FOLDER": {
|
||||||
|
"TITLE_CLEAR_FOLDER": "Voleu suprimir tots els missatges de la carpeta?",
|
||||||
|
"DANGER_DESC_WARNING": "Avís",
|
||||||
|
"DANGER_DESC_HTML_1": "Aquesta acció suprimirà definitivament tots els missatges de la carpeta <strong>%FOLDER%</strong>.",
|
||||||
|
"DANGER_DESC_HTML_2": "Un cop iniciat, el procés no es pot aturar ni cancel·lar.",
|
||||||
|
"TITLE_CLEARING_PROCESS": "S’està buidant la carpeta…"
|
||||||
|
},
|
||||||
|
"CRYPTO": {
|
||||||
|
"SIGN": "Signa",
|
||||||
|
"ENCRYPT": "Xifra",
|
||||||
|
"DECRYPT": "Desxifra",
|
||||||
|
"VERIFY": "Verifica",
|
||||||
|
"GOOD_SIGNATURE": "Signatura vàlida de %USER%",
|
||||||
|
"ERROR": "Error de %TYPE%: %ERROR%",
|
||||||
|
"CREATE_SELF_SIGNED": "Crea un certificat autosignat",
|
||||||
|
"VALID_UNTIL": "Vàlid fins al",
|
||||||
|
"PRIVATE_KEY": "Clau privada",
|
||||||
|
"CHANGE_PASS": "Canvia la contrasenya",
|
||||||
|
"CURRENT_PASS": "Contrasenya actual",
|
||||||
|
"NEW_PASS": "Contrasenya nova",
|
||||||
|
"ASK_CRYPTKEY_PASS": "La contrasenya d’accés ha canviat. Introduïu la contrasenya anterior per desxifrar les dades."
|
||||||
|
},
|
||||||
|
"OPENPGP": {
|
||||||
|
"POPUP_IMPORT_TITLE": "Importa una clau OpenPGP",
|
||||||
|
"POPUP_IMPORT_BUTTON": "Importa-la",
|
||||||
|
"POPUP_VIEW_TITLE": "Mostra una clau OpenPGP",
|
||||||
|
"POPUP_VIEW_BUTTON": "Selecciona",
|
||||||
|
"POPUP_GENERATE_TITLE": "Genera un parell de claus OpenPGP",
|
||||||
|
"POPUP_GENERATE_BUTTON": "Genera",
|
||||||
|
"LABEL_KEY_TYPE": "Tipus",
|
||||||
|
"SIGNED_MESSAGE": "Missatge signat amb OpenPGP",
|
||||||
|
"ENCRYPTED_MESSAGE": "Missatge xifrat amb OpenPGP",
|
||||||
|
"STORE_IN_GNUPG": "Desa al servidor amb GnuPG",
|
||||||
|
"STORE_PUBLIC_KEY_IN_GNUPG": "Desa la clau pública al servidor amb GnuPG",
|
||||||
|
"STORE_PRIVATE_KEY_IN_GNUPG": "Desa la clau privada al servidor amb GnuPG",
|
||||||
|
"BACKUP_ON_SERVER": "Desa al servidor amb xifratge",
|
||||||
|
"BACKUP_PUBLIC_KEY_ON_SERVER": "Desa una còpia de la clau pública al servidor",
|
||||||
|
"BACKUP_PRIVATE_KEY_ON_SERVER": "Desa una còpia de la clau privada al servidor"
|
||||||
|
},
|
||||||
|
"SMIME": {
|
||||||
|
"POPUP_IMPORT_TITLE": "Importa un certificat S/MIME",
|
||||||
|
"CERTIFICATE": "Certificat",
|
||||||
|
"CERTIFICATES": "Certificats S/MIME",
|
||||||
|
"SIGNED_MESSAGE": "Missatge signat amb S/MIME",
|
||||||
|
"ENCRYPTED_MESSAGE": "Missatge xifrat amb S/MIME",
|
||||||
|
"PRIVATE_KEY_OF": "Clau privada S/MIME de %EMAIL%"
|
||||||
|
},
|
||||||
|
"POPUPS_FILTER": {
|
||||||
|
"TITLE_CREATE_FILTER": "Crea un filtre",
|
||||||
|
"TITLE_EDIT_FILTER": "Edita el filtre",
|
||||||
|
"LEGEND_CONDITIONS": "Condicions",
|
||||||
|
"LEGEND_ACTIONS": "Accions",
|
||||||
|
"BUTTON_ADD_CONDITION": "Afegeix-hi una condició",
|
||||||
|
"SELECT_ACTION_MOVE_TO": "Mou a",
|
||||||
|
"SELECT_ACTION_FORWARD_TO": "Reenvia a",
|
||||||
|
"SELECT_ACTION_REJECT": "Rebutja",
|
||||||
|
"SELECT_ACTION_VACATION_MESSAGE": "Missatge de vacances",
|
||||||
|
"SELECT_ACTION_DISCARD": "Descarta-ho",
|
||||||
|
"SELECT_FIELD_RECIPIENTS": "Destinataris (Per a o A/c)",
|
||||||
|
"SELECT_FIELD_HEADER": "Capçalera",
|
||||||
|
"SELECT_FIELD_BODY": "Cos",
|
||||||
|
"SELECT_FIELD_SIZE": "Mida",
|
||||||
|
"SELECT_TYPE_CONTAINS": "Conté",
|
||||||
|
"SELECT_TYPE_NOT_CONTAINS": "No conté",
|
||||||
|
"SELECT_TYPE_MATCHES": "Coincideix amb (s’admeten * i ?)",
|
||||||
|
"SELECT_TYPE_NOT_MATCHES": "No coincideix amb (s’admeten * i ?)",
|
||||||
|
"SELECT_TYPE_REGEXP": "Expressió regular",
|
||||||
|
"SELECT_TYPE_NOT_REGEXP": "No coincideix amb l’expressió regular",
|
||||||
|
"SELECT_TYPE_EQUAL_TO": "Igual a",
|
||||||
|
"SELECT_TYPE_NOT_EQUAL_TO": "No igual a",
|
||||||
|
"SELECT_TYPE_OVER": "Més gran que",
|
||||||
|
"SELECT_TYPE_UNDER": "Més petit que",
|
||||||
|
"SELECT_TYPE_TEXT": "Text",
|
||||||
|
"SELECT_TYPE_RAW": "Sense processar",
|
||||||
|
"SELECT_MATCH_ANY": "Coincideix amb QUALSEVOL de les regles següents",
|
||||||
|
"SELECT_MATCH_ALL": "Coincideix amb TOTES les regles següents",
|
||||||
|
"MARK_AS_READ_LABEL": "Marca com a llegit",
|
||||||
|
"REPLY_INTERVAL_LABEL": "Interval de resposta (dies)",
|
||||||
|
"KEEP_LABEL": "Mantén-ho",
|
||||||
|
"STOP_LABEL": "Atura el processament de les regles següents",
|
||||||
|
"VACATION_SUBJECT_LABEL": "Assumpte (opcional)",
|
||||||
|
"VACATION_MESSAGE_LABEL": "Missatge",
|
||||||
|
"VACATION_RECIPIENTS_LABEL": "Destinataris (separats per comes)",
|
||||||
|
"REJECT_MESSAGE_LABEL": "Missatge de rebuig",
|
||||||
|
"ALL_INCOMING_MESSAGES_DESC": "Tots els missatges d’entrada"
|
||||||
|
},
|
||||||
|
"POPUPS_SIEVE_SCRIPT": {
|
||||||
|
"TITLE_CREATE": "Crea un script",
|
||||||
|
"TITLE_EDIT": "Edita l’script",
|
||||||
|
"BUTTON_ADD_FILTER": "Afegeix-hi un filtre",
|
||||||
|
"BUTTON_RAW_SCRIPT": "Utilitza un script d’usuari personalitzat",
|
||||||
|
"CAPABILITY_LABEL": "Capacitats",
|
||||||
|
"CHANGES_NEED_TO_BE_SAVED_DESC": "Cal desar aquests canvis al servidor."
|
||||||
|
},
|
||||||
|
"POPUPS_SYSTEM_FOLDERS": {
|
||||||
|
"TITLE_SYSTEM_FOLDERS": "Seleccioneu carpetes del sistema",
|
||||||
|
"SELECT_CHOOSE_ONE": "Trieu-ne una",
|
||||||
|
"SELECT_UNUSE_NAME": "No l’utilitzis",
|
||||||
|
"NOTIFICATION_SENT": "No heu seleccionat la carpeta del sistema «Enviats», on es desen els missatges enviats.\nSi no els voleu desar, seleccioneu l’opció «No l’utilitzis».\n",
|
||||||
|
"NOTIFICATION_DRAFTS": "No heu seleccionat la carpeta del sistema «Esborranys», on es desen els missatges mentre els redacteu.",
|
||||||
|
"NOTIFICATION_SPAM": "No heu seleccionat la carpeta del sistema «Correu brossa», on es mouen els missatges marcats com a correu brossa.\nSi els voleu suprimir definitivament, seleccioneu l’opció «No l’utilitzis».\n",
|
||||||
|
"NOTIFICATION_TRASH": "No heu seleccionat la carpeta del sistema «Paperera», on es mouen els missatges suprimits.\nSi els voleu suprimir definitivament, seleccioneu l’opció «No l’utilitzis».\n",
|
||||||
|
"NOTIFICATION_ARCHIVE": "No heu seleccionat la carpeta del sistema «Arxiu», on es desen els missatges arxivats."
|
||||||
|
},
|
||||||
|
"TITLES": {
|
||||||
|
"MAILBOX": "Bústia de correu",
|
||||||
|
"SETTINGS": "Configuració"
|
||||||
|
},
|
||||||
|
"UPLOAD": {
|
||||||
|
"ERROR_FILE_IS_TOO_BIG": "El fitxer és massa gran",
|
||||||
|
"ERROR_FILE_PARTIALLY_UPLOADED": "El fitxer només s’ha carregat parcialment a causa d’un error desconegut",
|
||||||
|
"ERROR_NO_FILE_UPLOADED": "No s’ha carregat cap fitxer",
|
||||||
|
"ERROR_MISSING_TEMP_FOLDER": "Manca el fitxer temporal",
|
||||||
|
"ERROR_ON_SAVING_FILE": "S’ha produït un error en desar el fitxer.",
|
||||||
|
"ERROR_FILE_TYPE": "Tipus de fitxer no vàlid",
|
||||||
|
"ERROR_UNKNOWN": "S’ha produït un error desconegut en pujar el fitxer."
|
||||||
|
},
|
||||||
|
"EDITOR": {
|
||||||
|
"PLAIN": "HTML <-> TEXT",
|
||||||
|
"SOURCE": "HTML <-> CODI FONT",
|
||||||
|
"FONTFAMILY": "Tipus de lletra",
|
||||||
|
"FONTSIZE": "Mida de la lletra",
|
||||||
|
"DIR": "Direcció del text",
|
||||||
|
"DIR_LTR": "D’esquerra a dreta",
|
||||||
|
"DIR_RTL": "De dreta a esquerra",
|
||||||
|
"DIR_AUTO": "Automàtica",
|
||||||
|
"TEXTCOLOR": "Color del text",
|
||||||
|
"BACKGROUNDCOLOR": "Color de fons",
|
||||||
|
"BOLD": "Negreta",
|
||||||
|
"ITALIC": "Cursiva",
|
||||||
|
"UNDERLINE": "Subratllat",
|
||||||
|
"STRIKE": "Ratllat",
|
||||||
|
"SUB": "Subíndex",
|
||||||
|
"SUP": "Superíndex",
|
||||||
|
"OL": "Llista numerada",
|
||||||
|
"UL": "Llista amb pics",
|
||||||
|
"QUOTE": "Citació",
|
||||||
|
"INDENTDECREASE": "Disminueix el sagnat",
|
||||||
|
"INDENTINCREASE": "Augmenta el sagnat",
|
||||||
|
"LINK": "Enllaç",
|
||||||
|
"IMAGEURL": "URL de la imatge",
|
||||||
|
"IMAGEUPLOAD": "Selecciona una imatge",
|
||||||
|
"UNDO": "Desfés",
|
||||||
|
"REDO": "Refés",
|
||||||
|
"REMOVESTYLE": "Elimina el format"
|
||||||
|
},
|
||||||
|
"SETTINGS_LABELS": {
|
||||||
|
"GENERAL": "General",
|
||||||
|
"CONTACTS": "Contactes",
|
||||||
|
"FOLDERS": "Carpetes",
|
||||||
|
"ACCOUNTS": "Comptes",
|
||||||
|
"FILTERS": "Filtres",
|
||||||
|
"SECURITY": "Seguretat",
|
||||||
|
"THEMES": "Temes"
|
||||||
|
},
|
||||||
|
"SETTINGS_FILTERS": {
|
||||||
|
"SIMPLE": "Simple",
|
||||||
|
"ADVANCED": "Avançat",
|
||||||
|
"BUTTON_ADD_SCRIPT": "Afegeix un script",
|
||||||
|
"SUBNAME_MOVE_TO": "Mou a «%FOLDER%»",
|
||||||
|
"SUBNAME_FORWARD_TO": "Reenvia a «%EMAIL%»",
|
||||||
|
"SUBNAME_REJECT": "Rebutja",
|
||||||
|
"SUBNAME_VACATION_MESSAGE": "Missatge de vacances",
|
||||||
|
"SUBNAME_DISCARD": "Descarta-ho"
|
||||||
|
},
|
||||||
|
"SETTINGS_SECURITY": {
|
||||||
|
"LEGEND_SECURITY": "Seguretat",
|
||||||
|
"LABEL_AUTOLOGOUT": "Tancament automàtic de sessió",
|
||||||
|
"FORGET_KEY_PASS": "Oblida la contrasenya de la clau privada",
|
||||||
|
"NEVER": "Mai"
|
||||||
|
},
|
||||||
|
"SETTINGS_GENERAL": {
|
||||||
|
"LANGUAGE": "Llengua",
|
||||||
|
"HOUR_FORMAT": "Format de l’hora",
|
||||||
|
"IDENTITY": "Identitat",
|
||||||
|
"LAYOUT": "Disposició",
|
||||||
|
"LAYOUT_NO_SPLIT": "Sense divisió",
|
||||||
|
"LAYOUT_VERTICAL_SPLIT": "Divisió vertical",
|
||||||
|
"LAYOUT_HORIZONTAL_SPLIT": "Divisió horitzontal",
|
||||||
|
"SHOW_NEXT_MESSAGE": "Mostra el missatge següent en moure o eliminar el missatge actual",
|
||||||
|
"EDITOR": "Editor de text predeterminat",
|
||||||
|
"EDITOR_HTML": "HTML",
|
||||||
|
"EDITOR_PLAIN": "Text sense format",
|
||||||
|
"VIEW_OPTIONS": "Opcions de visualització",
|
||||||
|
"USE_PREVIEW_PANE": "Utilitza la subfinestra de previsualització",
|
||||||
|
"HIDE_DELETED_MESSAGES": "Amaga els missatges marcats per eliminar",
|
||||||
|
"USE_CHECKBOXES_IN_LIST": "Mostra les caselles a la llista",
|
||||||
|
"USE_THREADS": "Agrupa els missatges en converses",
|
||||||
|
"THREAD_ALGORITHM": "Algorisme d’agrupació de converses",
|
||||||
|
"REPLY_SAME_FOLDER": "Desa les respostes a la carpeta del missatge al qual es respon",
|
||||||
|
"PREFER_HTML": "Prefereix HTML al text sense format",
|
||||||
|
"PREFER_HTML_INFO": "De vegades els missatges inclouen tots dos formats. Aquesta opció permet triar si es mostra la part HTML o la part de text sense format.",
|
||||||
|
"REMOVE_COLORS": "Elimina els colors de fons i del text del cos del missatge",
|
||||||
|
"ALLOW_STYLES": "Permet CSS amb <style>",
|
||||||
|
"MESSAGE_PER_PAGE": "Missatges per pàgina",
|
||||||
|
"CHECK_MAIL_INTERVAL": "Interval de comprovació del correu",
|
||||||
|
"MARK_MESSAGE_READ_AFTER": "Marca el missatge com a llegit després de",
|
||||||
|
"MSG_DEFAULT_ACTION": "Acció predeterminada",
|
||||||
|
"SECONDS": "segons",
|
||||||
|
"MINUTES": "minuts",
|
||||||
|
"NOTIFICATIONS": "Notificacions",
|
||||||
|
"SOUND_NOTIFICATION": "Notificació acústica",
|
||||||
|
"CHROME_NOTIFICATION_DESC": "Mostra notificacions emergents per als missatges nous",
|
||||||
|
"CHROME_NOTIFICATION_DESC_DENIED": "(El navegador les ha bloquejades)",
|
||||||
|
"COLLAPSE_BLOCKQUOTES": "Replega el text citat",
|
||||||
|
"MAX_QUOTE_LEVEL": "Nombre màxim de nivells visibles de text citat",
|
||||||
|
"LIST_INLINE_ATTACHMENTS": "Mostra a la llista els fitxers adjunts inserits al missatge",
|
||||||
|
"LIST_SIMPLE_ATTACHMENTS": "Llista simple de fitxers adjunts",
|
||||||
|
"LIST_MESSAGES_GROUPED": "Agrupa els missatges de la llista en ordenar per data o remitent",
|
||||||
|
"EXTERNAL_IMAGES": "Imatges externes",
|
||||||
|
"IMAGES_OPTION_ASK": "Pregunta sempre",
|
||||||
|
"IMAGES_OPTION_WHITELIST": "Permet les adreces de confiança; pregunta en els altres casos",
|
||||||
|
"IMAGES_OPTION_ALWAYS": "Mostra sempre",
|
||||||
|
"IMAGES_WHITELIST": "Adreces de confiança per mostrar imatges",
|
||||||
|
"MESSAGE_POPUP_WINDOW": "Obre les finestres emergents en una finestra nova en lloc d’una pestanya",
|
||||||
|
"MAILTO": "Registra com a gestor d’enllaços «mailto:»",
|
||||||
|
"HTML_TO_MD": "Converteix l’HTML a Markdown en lloc de text sense format"
|
||||||
|
},
|
||||||
|
"SETTINGS_CONTACTS": {
|
||||||
|
"LABEL_CONTACTS_AUTOSAVE": "Afegeix els destinataris a la llibreta d’adreces automàticament",
|
||||||
|
"LEGEND_CONTACTS_SYNC": "Sincronització remota (CardDAV)",
|
||||||
|
"LABEL_CONTACTS_SYNC_ENABLE": "Activa la sincronització remota",
|
||||||
|
"LABEL_CONTACTS_SYNC_SERVER": "Servidor",
|
||||||
|
"LABEL_CONTACTS_SYNC_AB_URL": "URL de la llibreta d’adreces",
|
||||||
|
"LABEL_CONTACTS_SYNC_USER": "Usuari",
|
||||||
|
"SYNC_READ": "Només de lectura"
|
||||||
|
},
|
||||||
|
"SETTINGS_THEMES": {
|
||||||
|
"LEGEND_THEMES": "Temes",
|
||||||
|
"LABEL_BACKGROUND": "Fons",
|
||||||
|
"LABEL_FONTS": "Tipus de lletra",
|
||||||
|
"BUTTON_UPLOAD_BACKGROUND_IMAGE": "Puja una imatge de fons (JPG, PNG)",
|
||||||
|
"ERROR_FILE_IS_TOO_BIG": "El fitxer és massa gran",
|
||||||
|
"ERROR_FILE_TYPE_ERROR": "Tipus de fitxer no vàlid (només JPG i PNG)",
|
||||||
|
"ERROR_UNKNOWN": "S’ha produït un error desconegut en pujar el fitxer."
|
||||||
|
},
|
||||||
|
"SETTINGS_FOLDERS": {
|
||||||
|
"LEGEND_FOLDERS": "Llista de carpetes",
|
||||||
|
"BUTTON_CREATE": "Crea una carpeta",
|
||||||
|
"BUTTON_SYSTEM": "Carpetes del sistema",
|
||||||
|
"BUTTON_SUBSCRIBE": "Subscriu-te a la carpeta",
|
||||||
|
"BUTTON_UNSUBSCRIBE": "Cancel·la la subscripció a la carpeta",
|
||||||
|
"LOADING_PROCESS": "S’està actualitzant la llista de carpetes…",
|
||||||
|
"CREATING_PROCESS": "S’està creant la carpeta…",
|
||||||
|
"DELETING_PROCESS": "S’està suprimint la carpeta…",
|
||||||
|
"RENAMING_PROCESS": "S’està canviant el nom de la carpeta…",
|
||||||
|
"TO_MANY_FOLDERS_DESC_1": "Teniu massa carpetes.",
|
||||||
|
"TO_MANY_FOLDERS_DESC_2": "Només n’hem mostrat una part per evitar problemes de rendiment.",
|
||||||
|
"HELP_DELETE_FOLDER": "Suprimeix la carpeta",
|
||||||
|
"HELP_SHOW_HIDE_FOLDER": "Mostra o amaga la carpeta",
|
||||||
|
"HELP_CHECK_FOR_NEW_MESSAGES": "Activa o desactiva la comprovació de missatges nous",
|
||||||
|
"HIDE_UNSUBSCRIBED": "Amaga les carpetes sense subscripció",
|
||||||
|
"UNHIDE_KOLAB_FOLDERS": "Mostra les carpetes Kolab ocultes",
|
||||||
|
"TYPE_CALENDAR": "Calendari",
|
||||||
|
"TYPE_CONFIGURATION": "Configuració",
|
||||||
|
"TYPE_CONTACTS": "Contactes",
|
||||||
|
"TYPE_FILES": "Fitxers",
|
||||||
|
"TYPE_JOURNAL": "Diari",
|
||||||
|
"TYPE_NOTES": "Notes",
|
||||||
|
"TYPE_TASKS": "Tasques"
|
||||||
|
},
|
||||||
|
"SETTINGS_ACCOUNTS": {
|
||||||
|
"LEGEND_ACCOUNTS": "Comptes",
|
||||||
|
"LEGEND_IDENTITIES": "Identitats",
|
||||||
|
"LEGEND_ACCOUNTS_AND_IDENTITIES": "Comptes i identitats",
|
||||||
|
"BUTTON_ADD_ACCOUNT": "Afegeix un compte",
|
||||||
|
"BUTTON_ADD_IDENTITY": "Afegeix una identitat",
|
||||||
|
"LOADING_PROCESS": "S’està actualitzant…",
|
||||||
|
"SHOW_UNREAD": "Mostra el nombre de missatges no llegits"
|
||||||
|
},
|
||||||
|
"SETTINGS_OPENPGP": {
|
||||||
|
"BUTTON_IMPORT_KEY": "Importa una clau",
|
||||||
|
"BUTTON_GENERATE_KEY_PAIR": "Genera un parell de claus",
|
||||||
|
"IMPORT_FROM_SERVER": "Importa del servidor",
|
||||||
|
"TITLE_PRIVATE": "Privat",
|
||||||
|
"TITLE_PUBLIC": "Públic",
|
||||||
|
"GET_MAILVELOPE": "Obteniu l’extensió Mailvelope per al navegador."
|
||||||
|
},
|
||||||
|
"SHORTCUTS_HELP": {
|
||||||
|
"LEGEND_SHORTCUTS_HELP": "Ajuda per a les dreceres de teclat",
|
||||||
|
"TAB_MAILBOX": "Bústia de correu",
|
||||||
|
"TAB_MESSAGE_LIST": "Llista de missatges",
|
||||||
|
"TAB_MESSAGE_VIEW": "Vista de missatges",
|
||||||
|
"TAB_COMPOSE": "Escriu",
|
||||||
|
"LABEL_OPEN_USER_DROPDOWN": "Obre el desplegable d’usuaris",
|
||||||
|
"LABEL_REPLY": "Respon",
|
||||||
|
"LABEL_REPLY_ALL": "Respon a tothom",
|
||||||
|
"LABEL_FORWARD": "Reenvia",
|
||||||
|
"LABEL_FORWARD_MULTIPLY": "Reenvia com a fitxers adjunts",
|
||||||
|
"LABEL_CHECK_ALL": "Selecciona tots els missatges",
|
||||||
|
"LABEL_DELETE": "Suprimeix",
|
||||||
|
"LABEL_OPEN_THREAD": "Obre la conversa seleccionada",
|
||||||
|
"LABEL_MOVE": "Mou",
|
||||||
|
"LABEL_READ": "Marca els missatges seleccionats com a llegits",
|
||||||
|
"LABEL_UNREAD": "Marca els missatges seleccionats com a no llegits",
|
||||||
|
"LABEL_IMPORTANT": "Destaca els missatges seleccionats",
|
||||||
|
"LABEL_CANCEL_SEARCH": "Cancel·la la cerca",
|
||||||
|
"LABEL_FULLSCREEN_ENTER": "Pantalla completa (disposició de previsualització)",
|
||||||
|
"LABEL_VIEW_MESSAGE_ENTER": "Mostra el missatge (sense disposició de previsualització)",
|
||||||
|
"LABEL_SWITCH_TO_MESSAGE": "Canvia el focus al missatge seleccionat",
|
||||||
|
"LABEL_SWITCH_TO_FOLDER_LIST": "Canvia el focus a la llista de carpetes",
|
||||||
|
"LABEL_FULLSCREEN_TOGGLE": "Commuta el mode de pantalla completa",
|
||||||
|
"LABEL_BLOCKQUOTES_TOGGLE": "Mostra o amaga el text citat dels missatges",
|
||||||
|
"LABEL_THREAD_NEXT": "Missatge següent de la conversa",
|
||||||
|
"LABEL_THREAD_PREV": "Missatge anterior de la conversa",
|
||||||
|
"LABEL_EXIT_FULLSCREEN": "Surt del mode de pantalla completa",
|
||||||
|
"LABEL_CLOSE_MESSAGE": "Tanca el missatge (sense disposició de previsualització)",
|
||||||
|
"LABEL_SWITCH_TO_LIST": "Torna el focus a la llista de missatges",
|
||||||
|
"LABEL_OPEN_COMPOSE_POPUP": "Obre la finestra de redacció",
|
||||||
|
"LABEL_MINIMIZE_COMPOSE_POPUP": "Minimitza la finestra de redacció",
|
||||||
|
"LABEL_OPEN_IDENTITIES_DROPDOWN": "Obre el desplegable d’identitats",
|
||||||
|
"LABEL_SAVE_MESSAGE": "Desa el missatge",
|
||||||
|
"LABEL_SEND_MESSAGE": "Envia el missatge",
|
||||||
|
"LABEL_CLOSE_COMPOSE": "Tanca la finestra de redacció"
|
||||||
|
},
|
||||||
|
"NOTIFICATIONS": {
|
||||||
|
"RequestError": "La sol·licitud ha fallat",
|
||||||
|
"RequestAborted": "S’ha cancel·lat la sol·licitud",
|
||||||
|
"RequestTimeout": "S’ha esgotat el temps d’espera de la sol·licitud",
|
||||||
|
"InvalidToken": "Testimoni no vàlid",
|
||||||
|
"AuthError": "Ha fallat l’autenticació",
|
||||||
|
"ConnectionError": "No es pot connectar amb el servidor.",
|
||||||
|
"DomainAlreadyExists": "El domini ja existeix.",
|
||||||
|
"DomainNotAllowed": "El domini no es permet.",
|
||||||
|
"AccountNotAllowed": "El compte no es permet.",
|
||||||
|
"CryptKeyError": "La contrasenya d’accés ha canviat",
|
||||||
|
"ContactsSyncError": "Error de sincronització dels contactes",
|
||||||
|
"CantGetMessageList": "No es pot obtenir la llista de missatges.",
|
||||||
|
"CantGetMessage": "No es pot obtenir el missatge.",
|
||||||
|
"CantDeleteMessage": "No es pot suprimir el missatge.",
|
||||||
|
"CantCopyMessage": "No s’ha pogut copiar el missatge",
|
||||||
|
"CantMoveMessage": "No es pot moure el missatge.",
|
||||||
|
"CantSaveMessage": "No es pot desar el missatge.",
|
||||||
|
"CantSendMessage": "No es pot enviar el missatge.",
|
||||||
|
"InvalidRecipients": "Destinataris no vàlids",
|
||||||
|
"CantSaveFilters": "No es poden desar els filtres.",
|
||||||
|
"CantGetFilters": "No es poden obtenir els filtres.",
|
||||||
|
"CantActivateFiltersScript": "No s’ha pogut activar l’script de filtres",
|
||||||
|
"CantDeleteFiltersScript": "No s’ha pogut eliminar l’script de filtres",
|
||||||
|
"FiltersAreNotCorrect": "Els filtres no són correctes.",
|
||||||
|
"CantCreateFolder": "No es pot crear la carpeta.",
|
||||||
|
"CantRenameFolder": "No es pot canviar el nom de la carpeta.",
|
||||||
|
"CantDeleteFolder": "No es pot suprimir la carpeta.",
|
||||||
|
"CantDeleteNonEmptyFolder": "No es pot suprimir una carpeta que no és buida.",
|
||||||
|
"CantSubscribeFolder": "No es pot activar la subscripció a la carpeta.",
|
||||||
|
"CantUnsubscribeFolder": "No es pot cancel·lar la subscripció a la carpeta.",
|
||||||
|
"DemoSendMessageError": "Per raons de seguretat, aquest compte no està autoritzat a enviar missatges a adreces de correu electrònic externes.",
|
||||||
|
"DemoAccountError": "Per raons de seguretat, aquest compte no té permís per a aquesta acció.",
|
||||||
|
"AccountAlreadyExists": "El compte ja existeix.",
|
||||||
|
"AccountDoesNotExist": "El compte no existeix.",
|
||||||
|
"AccountSwitchFailed": "No s’ha pogut canviar al compte «%EMAIL%».",
|
||||||
|
"MailServerError": "Hi ha hagut un error en accedir al servidor de correu.",
|
||||||
|
"InvalidInputArgument": "Argument d’entrada no vàlid",
|
||||||
|
"UnknownError": "Error desconegut"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -100,7 +100,7 @@
|
||||||
"ACL_S": "\\SEEN značka",
|
"ACL_S": "\\SEEN značka",
|
||||||
"ACL_T": "\\DELETED značka",
|
"ACL_T": "\\DELETED značka",
|
||||||
"ACL_W": "Ostatní značky",
|
"ACL_W": "Ostatní značky",
|
||||||
"ACL_X": "Smazat/Přejmenovat složku"
|
"ACL_X": "Smazat\/Přejmenovat složku"
|
||||||
},
|
},
|
||||||
"MESSAGE_LIST": {
|
"MESSAGE_LIST": {
|
||||||
"BUTTON_RELOAD": "Obnovit seznam zpráv",
|
"BUTTON_RELOAD": "Obnovit seznam zpráv",
|
||||||
|
|
@ -130,6 +130,8 @@
|
||||||
"QUOTA_SIZE": "Používáte <strong>%SIZE% (%PROC%%)<\/strong> z vašich <strong>%LIMIT%<\/strong>",
|
"QUOTA_SIZE": "Používáte <strong>%SIZE% (%PROC%%)<\/strong> z vašich <strong>%LIMIT%<\/strong>",
|
||||||
"SORT": "Seřadit",
|
"SORT": "Seřadit",
|
||||||
"NO_SORT": "Server nepodporuje řazení",
|
"NO_SORT": "Server nepodporuje řazení",
|
||||||
|
"SORT_ARRIVAL_ASC": "Arrived ascending",
|
||||||
|
"SORT_ARRIVAL_DESC": "Arrived descending",
|
||||||
"SORT_DATE_ASC": "Datum vzestupně",
|
"SORT_DATE_ASC": "Datum vzestupně",
|
||||||
"SORT_DATE_DESC": "Datum sestupně",
|
"SORT_DATE_DESC": "Datum sestupně",
|
||||||
"SORT_SIZE_ASC": "Velikost vzestupně",
|
"SORT_SIZE_ASC": "Velikost vzestupně",
|
||||||
|
|
@ -466,7 +468,7 @@
|
||||||
"LAYOUT_NO_SPLIT": "Nerozdělovat",
|
"LAYOUT_NO_SPLIT": "Nerozdělovat",
|
||||||
"LAYOUT_VERTICAL_SPLIT": "Vertikální rozdělení",
|
"LAYOUT_VERTICAL_SPLIT": "Vertikální rozdělení",
|
||||||
"LAYOUT_HORIZONTAL_SPLIT": "Horizontální rozdělení",
|
"LAYOUT_HORIZONTAL_SPLIT": "Horizontální rozdělení",
|
||||||
"SHOW_NEXT_MESSAGE": "Zobrazit další zprávu při přesunutí/odstranění aktuální zprávy",
|
"SHOW_NEXT_MESSAGE": "Zobrazit další zprávu při přesunutí\/odstranění aktuální zprávy",
|
||||||
"EDITOR": "Přednastavený editor zpráv",
|
"EDITOR": "Přednastavený editor zpráv",
|
||||||
"EDITOR_HTML": "Html",
|
"EDITOR_HTML": "Html",
|
||||||
"EDITOR_PLAIN": "Text",
|
"EDITOR_PLAIN": "Text",
|
||||||
|
|
@ -493,6 +495,7 @@
|
||||||
"CHROME_NOTIFICATION_DESC_DENIED": "(Zablokované prohlížečem)",
|
"CHROME_NOTIFICATION_DESC_DENIED": "(Zablokované prohlížečem)",
|
||||||
"COLLAPSE_BLOCKQUOTES": "Sbalit citovaný text",
|
"COLLAPSE_BLOCKQUOTES": "Sbalit citovaný text",
|
||||||
"MAX_QUOTE_LEVEL": "Maximální počet úrovní zobrazených citací",
|
"MAX_QUOTE_LEVEL": "Maximální počet úrovní zobrazených citací",
|
||||||
|
"MIN_FONT_SIZE": "Minimum Font Size",
|
||||||
"LIST_INLINE_ATTACHMENTS": "Zobrazit vložené přílohy v seznamu",
|
"LIST_INLINE_ATTACHMENTS": "Zobrazit vložené přílohy v seznamu",
|
||||||
"LIST_SIMPLE_ATTACHMENTS": "Jednoduchý seznam příloh",
|
"LIST_SIMPLE_ATTACHMENTS": "Jednoduchý seznam příloh",
|
||||||
"LIST_MESSAGES_GROUPED": "Seskupit zprávy v seznamu (při řazení podle data nebo odesílatele)",
|
"LIST_MESSAGES_GROUPED": "Seskupit zprávy v seznamu (při řazení podle data nebo odesílatele)",
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,7 @@
|
||||||
"CHROME_NOTIFICATION_DESC_DENIED": "(Blokeret af browseren)",
|
"CHROME_NOTIFICATION_DESC_DENIED": "(Blokeret af browseren)",
|
||||||
"COLLAPSE_BLOCKQUOTES": "Collapse quoted text",
|
"COLLAPSE_BLOCKQUOTES": "Collapse quoted text",
|
||||||
"MAX_QUOTE_LEVEL": "Maximum visible quoted text levels",
|
"MAX_QUOTE_LEVEL": "Maximum visible quoted text levels",
|
||||||
|
"MIN_FONT_SIZE": "Minimum Font Size",
|
||||||
"LIST_INLINE_ATTACHMENTS": "Show inline attachments in list",
|
"LIST_INLINE_ATTACHMENTS": "Show inline attachments in list",
|
||||||
"LIST_SIMPLE_ATTACHMENTS": "Simple attachments list",
|
"LIST_SIMPLE_ATTACHMENTS": "Simple attachments list",
|
||||||
"LIST_MESSAGES_GROUPED": "List messages grouped (when sorting by date or from)",
|
"LIST_MESSAGES_GROUPED": "List messages grouped (when sorting by date or from)",
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,7 @@
|
||||||
"CHROME_NOTIFICATION_DESC_DENIED": "(Vom Browser blockiert)",
|
"CHROME_NOTIFICATION_DESC_DENIED": "(Vom Browser blockiert)",
|
||||||
"COLLAPSE_BLOCKQUOTES": "zitierten Text ausblenden",
|
"COLLAPSE_BLOCKQUOTES": "zitierten Text ausblenden",
|
||||||
"MAX_QUOTE_LEVEL": "Maximal sichtbare Ebenen von zitiertem Text",
|
"MAX_QUOTE_LEVEL": "Maximal sichtbare Ebenen von zitiertem Text",
|
||||||
|
"MIN_FONT_SIZE": "Minimum Font Size",
|
||||||
"LIST_INLINE_ATTACHMENTS": "Inline-Anhänge in der Liste anzeigen",
|
"LIST_INLINE_ATTACHMENTS": "Inline-Anhänge in der Liste anzeigen",
|
||||||
"LIST_SIMPLE_ATTACHMENTS": "Vereinfachte Liste der Anhänge",
|
"LIST_SIMPLE_ATTACHMENTS": "Vereinfachte Liste der Anhänge",
|
||||||
"LIST_MESSAGES_GROUPED": "Nachrichten bei Sortierung nach Datum oder Absender gruppieren",
|
"LIST_MESSAGES_GROUPED": "Nachrichten bei Sortierung nach Datum oder Absender gruppieren",
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,7 @@
|
||||||
"CHROME_NOTIFICATION_DESC_DENIED": "(Αποκλείστηκε από το φυλλομετρητή)",
|
"CHROME_NOTIFICATION_DESC_DENIED": "(Αποκλείστηκε από το φυλλομετρητή)",
|
||||||
"COLLAPSE_BLOCKQUOTES": "Collapse quoted text",
|
"COLLAPSE_BLOCKQUOTES": "Collapse quoted text",
|
||||||
"MAX_QUOTE_LEVEL": "Maximum visible quoted text levels",
|
"MAX_QUOTE_LEVEL": "Maximum visible quoted text levels",
|
||||||
|
"MIN_FONT_SIZE": "Minimum Font Size",
|
||||||
"LIST_INLINE_ATTACHMENTS": "Show inline attachments in list",
|
"LIST_INLINE_ATTACHMENTS": "Show inline attachments in list",
|
||||||
"LIST_SIMPLE_ATTACHMENTS": "Simple attachments list",
|
"LIST_SIMPLE_ATTACHMENTS": "Simple attachments list",
|
||||||
"LIST_MESSAGES_GROUPED": "List messages grouped (when sorting by date or from)",
|
"LIST_MESSAGES_GROUPED": "List messages grouped (when sorting by date or from)",
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,7 @@
|
||||||
"CHROME_NOTIFICATION_DESC_DENIED": "(Blocked by the browser)",
|
"CHROME_NOTIFICATION_DESC_DENIED": "(Blocked by the browser)",
|
||||||
"COLLAPSE_BLOCKQUOTES": "Collapse quoted text",
|
"COLLAPSE_BLOCKQUOTES": "Collapse quoted text",
|
||||||
"MAX_QUOTE_LEVEL": "Maximum visible quoted text levels",
|
"MAX_QUOTE_LEVEL": "Maximum visible quoted text levels",
|
||||||
|
"MIN_FONT_SIZE": "Minimum Font Size",
|
||||||
"LIST_INLINE_ATTACHMENTS": "Show inline attachments in list",
|
"LIST_INLINE_ATTACHMENTS": "Show inline attachments in list",
|
||||||
"LIST_SIMPLE_ATTACHMENTS": "Simple attachments list",
|
"LIST_SIMPLE_ATTACHMENTS": "Simple attachments list",
|
||||||
"LIST_MESSAGES_GROUPED": "List messages grouped (when sorting by date or from)",
|
"LIST_MESSAGES_GROUPED": "List messages grouped (when sorting by date or from)",
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,7 @@
|
||||||
"CHROME_NOTIFICATION_DESC_DENIED": "(Bloqueado por el navegador)",
|
"CHROME_NOTIFICATION_DESC_DENIED": "(Bloqueado por el navegador)",
|
||||||
"COLLAPSE_BLOCKQUOTES": "Colapsar texto citado",
|
"COLLAPSE_BLOCKQUOTES": "Colapsar texto citado",
|
||||||
"MAX_QUOTE_LEVEL": "Niveles máximos de texto citado visibles",
|
"MAX_QUOTE_LEVEL": "Niveles máximos de texto citado visibles",
|
||||||
|
"MIN_FONT_SIZE": "Minimum Font Size",
|
||||||
"LIST_INLINE_ATTACHMENTS": "Mostrar adjuntos en línea en la lista",
|
"LIST_INLINE_ATTACHMENTS": "Mostrar adjuntos en línea en la lista",
|
||||||
"LIST_SIMPLE_ATTACHMENTS": "Lista simple de adjuntos",
|
"LIST_SIMPLE_ATTACHMENTS": "Lista simple de adjuntos",
|
||||||
"LIST_MESSAGES_GROUPED": "Listar mensajes agrupados (al ordenar por fecha o remitente)",
|
"LIST_MESSAGES_GROUPED": "Listar mensajes agrupados (al ordenar por fecha o remitente)",
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,7 @@
|
||||||
"CHROME_NOTIFICATION_DESC_DENIED": "(Veebilehitseja poolt blokeeritud)",
|
"CHROME_NOTIFICATION_DESC_DENIED": "(Veebilehitseja poolt blokeeritud)",
|
||||||
"COLLAPSE_BLOCKQUOTES": "Collapse quoted text",
|
"COLLAPSE_BLOCKQUOTES": "Collapse quoted text",
|
||||||
"MAX_QUOTE_LEVEL": "Maximum visible quoted text levels",
|
"MAX_QUOTE_LEVEL": "Maximum visible quoted text levels",
|
||||||
|
"MIN_FONT_SIZE": "Minimum Font Size",
|
||||||
"LIST_INLINE_ATTACHMENTS": "Show inline attachments in list",
|
"LIST_INLINE_ATTACHMENTS": "Show inline attachments in list",
|
||||||
"LIST_SIMPLE_ATTACHMENTS": "Simple attachments list",
|
"LIST_SIMPLE_ATTACHMENTS": "Simple attachments list",
|
||||||
"LIST_MESSAGES_GROUPED": "List messages grouped (when sorting by date or from)",
|
"LIST_MESSAGES_GROUPED": "List messages grouped (when sorting by date or from)",
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,7 @@
|
||||||
"CHROME_NOTIFICATION_DESC_DENIED": "(Nabigatzaileak blokeatuta)",
|
"CHROME_NOTIFICATION_DESC_DENIED": "(Nabigatzaileak blokeatuta)",
|
||||||
"COLLAPSE_BLOCKQUOTES": "Bildu aipuko testua",
|
"COLLAPSE_BLOCKQUOTES": "Bildu aipuko testua",
|
||||||
"MAX_QUOTE_LEVEL": "Ageriko aipamen-maila maximoak",
|
"MAX_QUOTE_LEVEL": "Ageriko aipamen-maila maximoak",
|
||||||
|
"MIN_FONT_SIZE": "Minimum Font Size",
|
||||||
"LIST_INLINE_ATTACHMENTS": "Erakutsi sareko eranskinak zerrendan",
|
"LIST_INLINE_ATTACHMENTS": "Erakutsi sareko eranskinak zerrendan",
|
||||||
"LIST_SIMPLE_ATTACHMENTS": "Eranskinen zerrenda arrunta",
|
"LIST_SIMPLE_ATTACHMENTS": "Eranskinen zerrenda arrunta",
|
||||||
"LIST_MESSAGES_GROUPED": "Zerrendatu mezuak taldekatuak (ordena jatorria edo data denean)",
|
"LIST_MESSAGES_GROUPED": "Zerrendatu mezuak taldekatuak (ordena jatorria edo data denean)",
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,7 @@
|
||||||
"CHROME_NOTIFICATION_DESC_DENIED": "(مسدود شده توسط مرورگر)",
|
"CHROME_NOTIFICATION_DESC_DENIED": "(مسدود شده توسط مرورگر)",
|
||||||
"COLLAPSE_BLOCKQUOTES": "Collapse quoted text",
|
"COLLAPSE_BLOCKQUOTES": "Collapse quoted text",
|
||||||
"MAX_QUOTE_LEVEL": "Maximum visible quoted text levels",
|
"MAX_QUOTE_LEVEL": "Maximum visible quoted text levels",
|
||||||
|
"MIN_FONT_SIZE": "Minimum Font Size",
|
||||||
"LIST_INLINE_ATTACHMENTS": "Show inline attachments in list",
|
"LIST_INLINE_ATTACHMENTS": "Show inline attachments in list",
|
||||||
"LIST_SIMPLE_ATTACHMENTS": "Simple attachments list",
|
"LIST_SIMPLE_ATTACHMENTS": "Simple attachments list",
|
||||||
"LIST_MESSAGES_GROUPED": "List messages grouped (when sorting by date or from)",
|
"LIST_MESSAGES_GROUPED": "List messages grouped (when sorting by date or from)",
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,7 @@
|
||||||
"CHROME_NOTIFICATION_DESC_DENIED": "(Estetty selaimessa)",
|
"CHROME_NOTIFICATION_DESC_DENIED": "(Estetty selaimessa)",
|
||||||
"COLLAPSE_BLOCKQUOTES": "Collapse quoted text",
|
"COLLAPSE_BLOCKQUOTES": "Collapse quoted text",
|
||||||
"MAX_QUOTE_LEVEL": "Maximum visible quoted text levels",
|
"MAX_QUOTE_LEVEL": "Maximum visible quoted text levels",
|
||||||
|
"MIN_FONT_SIZE": "Minimum Font Size",
|
||||||
"LIST_INLINE_ATTACHMENTS": "Show inline attachments in list",
|
"LIST_INLINE_ATTACHMENTS": "Show inline attachments in list",
|
||||||
"LIST_SIMPLE_ATTACHMENTS": "Simple attachments list",
|
"LIST_SIMPLE_ATTACHMENTS": "Simple attachments list",
|
||||||
"LIST_MESSAGES_GROUPED": "List messages grouped (when sorting by date or from)",
|
"LIST_MESSAGES_GROUPED": "List messages grouped (when sorting by date or from)",
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,7 @@
|
||||||
"CHROME_NOTIFICATION_DESC_DENIED": "(Bloqué par le navigateur)",
|
"CHROME_NOTIFICATION_DESC_DENIED": "(Bloqué par le navigateur)",
|
||||||
"COLLAPSE_BLOCKQUOTES": "Réduire le texte cité",
|
"COLLAPSE_BLOCKQUOTES": "Réduire le texte cité",
|
||||||
"MAX_QUOTE_LEVEL": "Niveau maximum de texte cité visible",
|
"MAX_QUOTE_LEVEL": "Niveau maximum de texte cité visible",
|
||||||
|
"MIN_FONT_SIZE": "Minimum Font Size",
|
||||||
"LIST_INLINE_ATTACHMENTS": "Afficher les pièces jointes intégrées dans la liste",
|
"LIST_INLINE_ATTACHMENTS": "Afficher les pièces jointes intégrées dans la liste",
|
||||||
"LIST_SIMPLE_ATTACHMENTS": "Liste simple des pièces jointes",
|
"LIST_SIMPLE_ATTACHMENTS": "Liste simple des pièces jointes",
|
||||||
"LIST_MESSAGES_GROUPED": "Lister les messages groupés (lors du tri par Date ou Destinataire)",
|
"LIST_MESSAGES_GROUPED": "Lister les messages groupés (lors du tri par Date ou Destinataire)",
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,7 @@
|
||||||
"CHROME_NOTIFICATION_DESC_DENIED": "(a böngésző blokkolta)",
|
"CHROME_NOTIFICATION_DESC_DENIED": "(a böngésző blokkolta)",
|
||||||
"COLLAPSE_BLOCKQUOTES": "Collapse quoted text",
|
"COLLAPSE_BLOCKQUOTES": "Collapse quoted text",
|
||||||
"MAX_QUOTE_LEVEL": "Maximum visible quoted text levels",
|
"MAX_QUOTE_LEVEL": "Maximum visible quoted text levels",
|
||||||
|
"MIN_FONT_SIZE": "Minimum Font Size",
|
||||||
"LIST_INLINE_ATTACHMENTS": "Show inline attachments in list",
|
"LIST_INLINE_ATTACHMENTS": "Show inline attachments in list",
|
||||||
"LIST_SIMPLE_ATTACHMENTS": "Simple attachments list",
|
"LIST_SIMPLE_ATTACHMENTS": "Simple attachments list",
|
||||||
"LIST_MESSAGES_GROUPED": "List messages grouped (when sorting by date or from)",
|
"LIST_MESSAGES_GROUPED": "List messages grouped (when sorting by date or from)",
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,7 @@
|
||||||
"CHROME_NOTIFICATION_DESC_DENIED": "(diblokir oleh browser)",
|
"CHROME_NOTIFICATION_DESC_DENIED": "(diblokir oleh browser)",
|
||||||
"COLLAPSE_BLOCKQUOTES": "Collapse quoted text",
|
"COLLAPSE_BLOCKQUOTES": "Collapse quoted text",
|
||||||
"MAX_QUOTE_LEVEL": "Maximum visible quoted text levels",
|
"MAX_QUOTE_LEVEL": "Maximum visible quoted text levels",
|
||||||
|
"MIN_FONT_SIZE": "Minimum Font Size",
|
||||||
"LIST_INLINE_ATTACHMENTS": "Show inline attachments in list",
|
"LIST_INLINE_ATTACHMENTS": "Show inline attachments in list",
|
||||||
"LIST_SIMPLE_ATTACHMENTS": "Simple attachments list",
|
"LIST_SIMPLE_ATTACHMENTS": "Simple attachments list",
|
||||||
"LIST_MESSAGES_GROUPED": "List messages grouped (when sorting by date or from)",
|
"LIST_MESSAGES_GROUPED": "List messages grouped (when sorting by date or from)",
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,7 @@
|
||||||
"CHROME_NOTIFICATION_DESC_DENIED": "(Lokað af vafra)",
|
"CHROME_NOTIFICATION_DESC_DENIED": "(Lokað af vafra)",
|
||||||
"COLLAPSE_BLOCKQUOTES": "Collapse quoted text",
|
"COLLAPSE_BLOCKQUOTES": "Collapse quoted text",
|
||||||
"MAX_QUOTE_LEVEL": "Maximum visible quoted text levels",
|
"MAX_QUOTE_LEVEL": "Maximum visible quoted text levels",
|
||||||
|
"MIN_FONT_SIZE": "Minimum Font Size",
|
||||||
"LIST_INLINE_ATTACHMENTS": "Show inline attachments in list",
|
"LIST_INLINE_ATTACHMENTS": "Show inline attachments in list",
|
||||||
"LIST_SIMPLE_ATTACHMENTS": "Simple attachments list",
|
"LIST_SIMPLE_ATTACHMENTS": "Simple attachments list",
|
||||||
"LIST_MESSAGES_GROUPED": "List messages grouped (when sorting by date or from)",
|
"LIST_MESSAGES_GROUPED": "List messages grouped (when sorting by date or from)",
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,7 @@
|
||||||
"CHROME_NOTIFICATION_DESC_DENIED": "(Bloccate dal browser)",
|
"CHROME_NOTIFICATION_DESC_DENIED": "(Bloccate dal browser)",
|
||||||
"COLLAPSE_BLOCKQUOTES": "Nascondi testo quotato",
|
"COLLAPSE_BLOCKQUOTES": "Nascondi testo quotato",
|
||||||
"MAX_QUOTE_LEVEL": "Livelli massimi visibilità testo quotato",
|
"MAX_QUOTE_LEVEL": "Livelli massimi visibilità testo quotato",
|
||||||
|
"MIN_FONT_SIZE": "Minimum Font Size",
|
||||||
"LIST_INLINE_ATTACHMENTS": "Visualizza lista degli allegati in linea",
|
"LIST_INLINE_ATTACHMENTS": "Visualizza lista degli allegati in linea",
|
||||||
"LIST_SIMPLE_ATTACHMENTS": "Lista semplice allegati",
|
"LIST_SIMPLE_ATTACHMENTS": "Lista semplice allegati",
|
||||||
"LIST_MESSAGES_GROUPED": "Lista messaggi raggruppati (quando riordinati per data o mittente)",
|
"LIST_MESSAGES_GROUPED": "Lista messaggi raggruppati (quando riordinati per data o mittente)",
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue