Compare commits

...

7 commits

Author SHA1 Message Date
Maarten
5de44c77c2
Merge pull request #2084 from sasogu/add-catalan-localization
Add Catalan localization
2026-10-21 15:42:25 +02:00
the-djmaze
dfc9420385 Sabre XML v4.1.0 2026-10-21 15:18:43 +02:00
the-djmaze
e66f325aa3 Sabre VObject v5.0.0 2026-10-21 15:18:17 +02:00
Samuel Soriano
4e65e7ee84 Use the username label for login fields 2026-10-16 17:54:39 +02:00
Samuel Soriano
aca14765c9 Review Catalan terminology, grammar and action meanings 2026-10-16 17:53:07 +02:00
Samuel Soriano
be91e4eb80 Document the Catalan language selector preview 2026-10-16 16:20:42 +02:00
Samuel Soriano
24d7dee9b9 Add Catalan localization 2026-10-16 16:19:20 +02:00
76 changed files with 1672 additions and 752 deletions

View file

@ -95,7 +95,7 @@ class BirthdayCalendarGenerator
// We've seen clients (ez-vcard) putting "BDAY:" properties
// without a value into vCards. If we come across those, we'll
// skip them.
if (empty($object->BDAY->getValue())) {
if ('' === $object->BDAY->getValue()) {
continue;
}
@ -116,7 +116,7 @@ class BirthdayCalendarGenerator
// Skip if we can't parse the BDAY value.
try {
$dateParts = DateTimeParser::parseVCardDateTime($object->BDAY->getValue());
} catch (InvalidDataException $e) {
} catch (InvalidDataException) {
continue;
}

View file

@ -92,7 +92,7 @@ class Cli
// @codeCoverageIgnoreEnd
try {
list($options, $positional) = $this->parseArguments($argv);
[$options, $positional] = $this->parseArguments($argv);
if (isset($options['q'])) {
$this->quiet = true;
@ -110,27 +110,10 @@ class Cli
return 0;
case 'format':
switch ($value) {
// jcard/jcal documents
case 'jcard':
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);
}
$this->format = match ($value) {
'jcard', 'jcal', 'vcard21', 'vcard30', 'vcard40', 'icalendar20', 'json', 'mimedir', 'icalendar', 'vcard' => $value,
default => throw new \InvalidArgumentException('Unknown format: '.$value),
};
break;
case 'pretty':
$this->pretty = true;
@ -139,28 +122,11 @@ class Cli
$this->forgiving = true;
break;
case 'inputformat':
switch ($value) {
// json formats
case 'jcard':
case 'jcal':
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);
}
$this->inputFormat = match ($value) {
'jcard', 'jcal', 'json' => 'json',
'mimedir', 'icalendar', 'vcard', 'vcard21', 'vcard30', 'vcard40', 'icalendar20' => 'mimedir',
default => throw new \InvalidArgumentException('Unknown format: '.$value),
};
break;
default:
throw new \InvalidArgumentException('Unknown option: '.$name);
@ -181,7 +147,7 @@ class Cli
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]);
}
} catch (\InvalidArgumentException $e) {
@ -201,14 +167,14 @@ class Cli
}
if (null === $this->inputFormat) {
if ('.json' === substr($this->inputPath, -5)) {
if (str_ends_with($this->inputPath, '.json')) {
$this->inputFormat = 'json';
} else {
$this->inputFormat = 'mimedir';
}
}
if (null === $this->format) {
if ('.json' === substr($this->outputPath, -5)) {
if (str_ends_with($this->outputPath, '.json')) {
$this->format = 'json';
} else {
$this->format = 'mimedir';
@ -219,12 +185,13 @@ class Cli
try {
while ($input = $this->readInput()) {
// @phpstan-ignore method.dynamicName
$returnCode = $this->$command($input);
if (0 !== $returnCode) {
$realCode = $returnCode;
}
}
} catch (EofException $e) {
} catch (EofException) {
// end of file
} catch (\Exception $e) {
$this->log('Error: '.$e->getMessage(), 'red');
@ -408,7 +375,7 @@ HELP
}
fwrite($this->stdout, json_encode($vObj->jsonSerialize(), $jsonOptions));
} else {
fwrite($this->stdout, $vObj->serialize());
fwrite($this->stdout, (string) $vObj->serialize());
}
return 0;
@ -538,7 +505,7 @@ HELP
$this->cWrite('red', ':');
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 {
$parts = $property->getParts();
$first1 = true;
@ -593,17 +560,17 @@ HELP
$v = $argv[$ii];
if ('--' === substr($v, 0, 2)) {
if (str_starts_with((string) $v, '--')) {
// This is a long-form option.
$optionName = substr($v, 2);
$optionName = substr((string) $v, 2);
$optionValue = true;
if (strpos($optionName, '=')) {
list($optionName, $optionValue) = explode('=', $optionName);
[$optionName, $optionValue] = explode('=', $optionName);
}
$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.
foreach (str_split(substr($v, 1)) as $option) {
foreach (str_split(substr((string) $v, 1)) as $option) {
$options[$option] = true;
}
} else {
@ -628,6 +595,9 @@ HELP
if (!$this->parser) {
if ('-' !== $this->inputPath) {
$this->stdin = fopen($this->inputPath, 'r');
if (false === $this->stdin) {
throw new InvalidDataException('Cannot open "'.$this->inputPath);
}
}
if ('mimedir' === $this->inputFormat) {

View file

@ -140,7 +140,7 @@ class Component extends Node
// If there's no dot in the name, it's an exact property name,
// we can just wipe out all those properties.
//
if (false === strpos($item, '.')) {
if (!str_contains($item, '.')) {
unset($this->children[strtoupper($item)]);
return;
@ -210,8 +210,8 @@ class Component extends Node
{
$group = null;
$name = strtoupper($name);
if (false !== strpos($name, '.')) {
list($group, $name) = explode('.', $name, 2);
if (str_contains($name, '.')) {
[$group, $name] = explode('.', $name, 2);
}
if ('' === $name) {
$name = null;
@ -222,16 +222,14 @@ class Component extends Node
if (is_null($group)) {
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
@ -368,7 +366,7 @@ class Component extends Node
$writer->startElement(strtolower($this->name));
if (!empty($properties)) {
if ([] !== $properties) {
$writer->startElement('properties');
foreach ($properties as $property) {
@ -378,7 +376,7 @@ class Component extends Node
$writer->endElement();
}
if (!empty($components)) {
if ([] !== $components) {
$writer->startElement('components');
foreach ($components as $component) {
@ -422,13 +420,12 @@ class Component extends Node
$matches = $this->select($name);
if (0 === count($matches)) {
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 = [];
foreach ($this->children() as $child) {
$name = strtoupper($child->name);
$name = strtoupper((string) $child->name);
if (!isset($propertyCounters[$name])) {
$propertyCounters[$name] = 1;
} else {

View file

@ -32,9 +32,9 @@ class VAlarm extends VObject\Component
public function getEffectiveTriggerTime(): \DateTimeImmutable
{
$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);
$related = (isset($trigger['RELATED']) && 'END' == strtoupper($trigger['RELATED'])) ? 'END' : 'START';
$related = (isset($trigger['RELATED']) && 'END' === strtoupper($trigger['RELATED'])) ? 'END' : 'START';
/** @var VEvent|VTodo $parentComponent */
$parentComponent = $this->parent;
@ -96,9 +96,9 @@ class VAlarm extends VObject\Component
}
return false;
} else {
return $start <= $effectiveTrigger && $end > $effectiveTrigger;
}
return $start <= $effectiveTrigger && $end > $effectiveTrigger;
}
/**

View file

@ -33,7 +33,7 @@ class VAvailability extends VObject\Component
*/
public function isInTimeRange(\DateTimeInterface $start, \DateTimeInterface $end): bool
{
list($effectiveStart, $effectiveEnd) = $this->getEffectiveStartEnd();
[$effectiveStart, $effectiveEnd] = $this->getEffectiveStartEnd();
return
(is_null($effectiveStart) || $start < $effectiveEnd)

View file

@ -145,6 +145,10 @@ class VCalendar extends VObject\Document
// Additions from draft-daboo-calendar-availability-05
'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
*
* @return VObject\Component[]
* @return Component[]
*/
public function getBaseComponents(?string $componentName = null): array
{
@ -327,7 +331,7 @@ class VCalendar extends VObject\Document
foreach ($recurringEvents as $events) {
try {
$it = new EventIterator($events, null, $timeZone);
} catch (NoInstancesException $e) {
} catch (NoInstancesException) {
// This event is recurring, but it doesn't have a single
// instance. We are skipping this event from the output
// entirely.
@ -379,6 +383,18 @@ class VCalendar extends VObject\Document
'CALSCALE' => '?',
'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) {
++$componentsFound;
if (!in_array($child->name, ['VEVENT', 'VTODO', 'VJOURNAL'])) {
if (!in_array($child->name, ['VEVENT', 'VTODO', 'VJOURNAL'], true)) {
continue;
}
$componentTypes[] = $child->name;

View file

@ -132,6 +132,13 @@ class VCard extends VObject\Document
'ORG-DIRECTORY' => VObject\Property\FlatText::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' => '*',
'CATEGORIES' => '*',
'CLIENTPIDMAP' => '*',
'CREATED' => '?',
'GRAMGENDER' => '*',
'LANGUAGE' => '?',
'PRONOUNS' => '*',
'SOCIALPROFILE' => '*',
'EMAIL' => '*',
'FBURL' => '*',
'IMPP' => '*',
@ -411,6 +423,34 @@ class VCard extends VObject\Document
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.
*/
@ -453,11 +493,9 @@ class VCard extends VObject\Document
$propertiesByGroup = [];
foreach ($this->children() as $property) {
$group = $property->group;
$group = $property->group ?? '';
if (!isset($propertiesByGroup[$group])) {
$propertiesByGroup[$group] = [];
}
$propertiesByGroup[$group] ??= [];
$propertiesByGroup[$group][] = $property;
}
@ -465,9 +503,9 @@ class VCard extends VObject\Document
$writer->startElement(strtolower($this->name));
foreach ($propertiesByGroup as $group => $properties) {
if (!empty($group)) {
if ('' !== $group) {
$writer->startElement('group');
$writer->writeAttribute('name', strtolower($group));
$writer->writeAttribute('name', strtolower((string) $group));
}
foreach ($properties as $property) {
@ -487,7 +525,7 @@ class VCard extends VObject\Document
}
}
if (!empty($group)) {
if ('' !== $group) {
$writer->endElement();
}
}
@ -503,7 +541,7 @@ class VCard extends VObject\Document
$className = parent::getClassNameForPropertyName($propertyName);
// 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;
}

View file

@ -41,10 +41,10 @@ class VEvent extends VObject\Component
*/
public function isInTimeRange(\DateTimeInterface $start, \DateTimeInterface $end): bool
{
if ($this->RRULE) {
if ($this->RRULE || $this->RDATE) {
try {
$it = new EventIterator($this, null, $start->getTimezone());
} catch (NoInstancesException $e) {
} catch (NoInstancesException) {
// If we've caught this exception, there are no instances
// for the event that fall into the specified time-range.
return false;
@ -117,6 +117,7 @@ class VEvent extends VObject\Component
'DTSTART' => $hasMethod ? '?' : '1',
'CLASS' => '?',
'CREATED' => '?',
'COLOR' => '?',
'DESCRIPTION' => '?',
'GEO' => '?',
'LAST-MODIFIED' => '?',
@ -137,7 +138,9 @@ class VEvent extends VObject\Component
'ATTENDEE' => '*',
'CATEGORIES' => '*',
'COMMENT' => '*',
'CONFERENCE' => '*',
'CONTACT' => '*',
'IMAGE' => '*',
'EXDATE' => '*',
'REQUEST-STATUS' => '*',
'RELATED-TO' => '*',

View file

@ -41,7 +41,7 @@ class VFreeBusy extends VObject\Component
// Every period is formatted as [start]/[end]. The start is an
// absolute UTC time, the end may be an absolute UTC time, or
// duration (relative) value.
list($busyStart, $busyEnd) = explode('/', $period);
[$busyStart, $busyEnd] = explode('/', $period);
$busyStart = VObject\DateTimeParser::parse($busyStart);
$busyEnd = VObject\DateTimeParser::parse($busyEnd);

View file

@ -62,6 +62,7 @@ class VJournal extends VObject\Component
'CLASS' => '?',
'CREATED' => '?',
'COLOR' => '?',
'DTSTART' => '?',
'LAST-MODIFIED' => '?',
'ORGANIZER' => '?',
@ -78,6 +79,7 @@ class VJournal extends VObject\Component
'CATEGORIES' => '*',
'COMMENT' => '*',
'CONTACT' => '*',
'IMAGE' => '*',
'DESCRIPTION' => '*',
'EXDATE' => '*',
'RELATED-TO' => '*',

View file

@ -54,9 +54,9 @@ class VTodo extends VObject\Component
return
($start < $due || $start <= $dtstart)
&& ($end > $dtstart || $end >= $due);
} else {
return $start <= $dtstart && $end > $dtstart;
}
return $start <= $dtstart && $end > $dtstart;
}
if ($due) {
return $start < $due && $end >= $due;
@ -98,6 +98,7 @@ class VTodo extends VObject\Component
'CLASS' => '?',
'COMPLETED' => '?',
'CREATED' => '?',
'COLOR' => '?',
'DESCRIPTION' => '?',
'DTSTART' => '?',
'GEO' => '?',
@ -120,7 +121,9 @@ class VTodo extends VObject\Component
'ATTENDEE' => '*',
'CATEGORIES' => '*',
'COMMENT' => '*',
'CONFERENCE' => '*',
'CONTACT' => '*',
'IMAGE' => '*',
'EXDATE' => '*',
'REQUEST-STATUS' => '*',
'RELATED-TO' => '*',

View file

@ -42,7 +42,7 @@ class DateTimeParser
try {
$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);
}
@ -63,13 +63,11 @@ class DateTimeParser
throw new InvalidDataException('The supplied iCalendar date value is incorrect: '.$date);
}
if (is_null($tz)) {
$tz = new \DateTimeZone('UTC');
}
$tz ??= new \DateTimeZone('UTC');
try {
$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);
}
@ -205,9 +203,9 @@ class DateTimeParser
return self::parseDuration($date);
} elseif (8 === strlen($date)) {
return self::parseDate($date, $referenceTz);
} else {
return self::parseDateTime($date, $referenceTz);
}
return self::parseDateTime($date, $referenceTz);
}
/**
@ -329,7 +327,7 @@ class DateTimeParser
$result = [];
foreach ($parts as $part) {
if (empty($matches[$part])) {
if (!array_key_exists($part, $matches) || '' === $matches[$part]) {
$result[$part] = null;
} elseif ('-' === $matches[$part] || '--' === $matches[$part]) {
$result[$part] = null;
@ -424,7 +422,7 @@ class DateTimeParser
$result = [];
foreach ($parts as $part) {
if (empty($matches[$part])) {
if (!array_key_exists($part, $matches) || '' === $matches[$part]) {
$result[$part] = null;
} elseif ('-' === $matches[$part]) {
$result[$part] = null;
@ -541,7 +539,7 @@ class DateTimeParser
$parts['year0'] = &$parts['year'];
foreach ($parts as $part => &$value) {
if (!empty($matches[$part])) {
if (array_key_exists($part, $matches) && '' !== $matches[$part]) {
$value = $matches[$part];
}
}

View file

@ -118,10 +118,10 @@ abstract class Document extends Component
public function create(string $name)
{
if (isset(static::$componentMap[strtoupper($name)])) {
return call_user_func_array([$this, 'createComponent'], func_get_args());
} else {
return call_user_func_array([$this, 'createProperty'], func_get_args());
return $this->createComponent(...func_get_args());
}
return $this->createProperty(...func_get_args());
}
/**
@ -146,9 +146,7 @@ abstract class Document extends Component
if (isset(static::$componentMap[$name])) {
$class = static::$componentMap[$name];
}
if (is_null($children)) {
$children = [];
}
$children ??= [];
return new $class($this, $name, $children, $defaults);
}
@ -180,26 +178,29 @@ abstract class Document extends Component
$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) {
// The valueType argument comes first to figure out the correct
// class.
$class = $this->getClassNameForPropertyValue($valueType);
}
if (is_null($class)) {
// If a VALUE parameter is supplied, we should use that.
if (isset($parameters['VALUE'])) {
$class = $this->getClassNameForPropertyValue($parameters['VALUE']);
if (is_null($class)) {
throw new InvalidDataException('Unsupported VALUE parameter for '.$name.' property. You supplied "'.$parameters['VALUE'].'"');
}
} else {
$class = $this->getClassNameForPropertyName($name);
}
}
if (is_null($parameters)) {
$parameters = [];
}
// If the value parameter is not set or set to something we do not recognize
// we do not attempt to interpret or parse the datass value as specified in
// https://datatracker.ietf.org/doc/html/rfc5545#section-3.2.20
// So when we so far did not get a class-name, we use the default for the property
$class ??= $this->getClassNameForPropertyName($name);
$parameters ??= [];
return new $class($this, $name, $value, $parameters, $group, $lineIndex, $lineString);
}

View file

@ -11,25 +11,19 @@ namespace Sabre\VObject;
*/
class FreeBusyData
{
/**
* Start timestamp.
*/
protected int $start;
/**
* End timestamp.
*/
protected int $end;
/**
* A list of free-busy times.
*/
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[] = [

View file

@ -79,9 +79,7 @@ class FreeBusyGenerator
if ($objects) {
$this->setObjects($objects);
}
if (is_null($timeZone)) {
$timeZone = new \DateTimeZone('UTC');
}
$timeZone ??= new \DateTimeZone('UTC');
$this->setTimeZone($timeZone);
}
@ -217,7 +215,7 @@ class FreeBusyGenerator
$new = [];
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
// 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
// a higher priority component that already fully covers this one.
foreach ($new as $higherVavail) {
list($higherStart, $higherEnd) = $higherVavail->getEffectiveStartEnd();
[$higherStart, $higherEnd] = $higherVavail->getEffectiveStartEnd();
if (
(is_null($higherStart) || $higherStart < $compStart)
&& (is_null($higherEnd) || $higherEnd > $compEnd)
@ -259,7 +257,7 @@ class FreeBusyGenerator
// priority components to override the lower ones.
foreach (array_reverse($new) as $vavail) {
$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
// report range.
@ -281,7 +279,7 @@ class FreeBusyGenerator
// Looping over the AVAILABLE components.
if (isset($vavail->AVAILABLE)) {
foreach ($vavail->AVAILABLE as $available) {
list($availStart, $availEnd) = $available->getEffectiveStartEnd();
[$availStart, $availEnd] = $available->getEffectiveStartEnd();
$fbData->add(
$availStart->getTimeStamp(),
$availEnd->getTimeStamp(),
@ -362,7 +360,7 @@ class FreeBusyGenerator
if ($component->RRULE) {
try {
$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
// instance. We are skipping this event from the output
// entirely.
@ -437,12 +435,12 @@ class FreeBusyGenerator
continue;
}
$values = explode(',', $freebusy);
$values = explode(',', (string) $freebusy);
foreach ($values as $value) {
list($startTime, $endTime) = explode('/', $value);
[$startTime, $endTime] = explode('/', $value);
$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);
$endTime = clone $startTime;
$endTime = $endTime->add($duration);
@ -507,7 +505,7 @@ class FreeBusyGenerator
$vfreebusy->add($dtstamp);
foreach ($fbData->getData() as $busyTime) {
$busyType = strtoupper($busyTime['type']);
$busyType = strtoupper((string) $busyTime['type']);
// Ignoring all the FREE parts, because those are already assumed.
if ('FREE' === $busyType) {

View file

@ -228,7 +228,7 @@ class Broker
$eventInfo = $oldEventInfo;
if (in_array($eventInfo['organizer'], $userHref)) {
if (in_array($eventInfo['organizer'], $userHref, true)) {
// This is an organizer deleting the event.
$eventInfo['attendees'] = [];
// Increasing the sequence, but only if the organizer deleted
@ -237,7 +237,7 @@ class Broker
} else {
// This is an attendee deleting the event.
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'],
];
}
@ -246,16 +246,29 @@ class Broker
$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);
} 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
// process updates, not creation of new events.
foreach ($eventInfo['attendees'] as $attendee) {
if (in_array($attendee['href'], $userHref)) {
}
// Check if the user is an attendee
foreach ($eventInfo['attendees'] as $attendee) {
if (in_array($attendee['href'], $userHref, true)) {
// If this is a event update, we always generate a reply
if ($oldCalendar) {
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();
if (isset($vevent->{'REQUEST-STATUS'})) {
$requestStatus = $vevent->{'REQUEST-STATUS'}->getValue();
list($requestStatus) = explode(';', $requestStatus);
[$requestStatus] = explode(';', $requestStatus);
}
}
@ -479,7 +492,6 @@ class Broker
}
$messages = [];
foreach ($attendees as $attendee) {
// An organizer can also be an attendee. We should not generate any
// messages for those.
@ -554,7 +566,7 @@ class Broker
$message->significantChange =
'REQUEST' === $attendee['forceSend']
|| count($oldAttendeeInstances) != count($newAttendeeInstances)
|| count($oldAttendeeInstances) !== count($newAttendeeInstances)
|| count(array_diff($oldAttendeeInstances, $newAttendeeInstances)) > 0
|| $oldEventInfo['significantChangeHash'] !== $eventInfo['significantChangeHash'];
@ -564,9 +576,9 @@ class Broker
// We need to find a list of events that the attendee
// is not a part of to add to the list of exceptions.
$exceptions = [];
foreach ($eventInfo['instances'] as $instanceId => $vevent) {
if (!isset($attendee['newInstances'][$instanceId])) {
$exceptions[] = $instanceId;
foreach ($eventInfo['instances'] as $eventInstanceId => $vevent) {
if (!isset($attendee['newInstances'][$eventInstanceId])) {
$exceptions[] = $eventInstanceId;
}
}
@ -580,6 +592,9 @@ class Broker
));
} else {
$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-STATUS']);
foreach ($currentEvent->ATTENDEE as $attendee) {
unset($attendee['SCHEDULE-FORCE-SEND']);
unset($attendee['SCHEDULE-STATUS']);
foreach ($currentEvent->ATTENDEE as $currentEventAttendee) {
unset($currentEventAttendee['SCHEDULE-FORCE-SEND']);
unset($currentEventAttendee['SCHEDULE-STATUS']);
// We're adding PARTSTAT=NEEDS-ACTION to ensure that
// iOS shows an "Inbox Item"
if (!isset($attendee['PARTSTAT'])) {
$attendee['PARTSTAT'] = 'NEEDS-ACTION';
}
$currentEventAttendee['PARTSTAT'] ??= 'NEEDS-ACTION';
}
}
@ -633,7 +646,12 @@ class Broker
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'] :
[];
@ -664,7 +682,7 @@ class Broker
// We only need to do that though, if the master event is not declined.
if (isset($instances['master']) && 'DECLINED' !== $instances['master']['newstatus']) {
foreach ($eventInfo['exdate'] as $exDate) {
if (!in_array($exDate, $oldEventInfo['exdate'])) {
if (!in_array($exDate, $oldEventInfo['exdate'] ?? [], true)) {
if (isset($instances[$exDate])) {
$instances[$exDate]['newstatus'] = 'DECLINED';
} else {
@ -707,7 +725,7 @@ class Broker
$hasReply = false;
foreach ($instances as $instance) {
if ($instance['oldstatus'] == $instance['newstatus'] && 'REPLY' !== $eventInfo['organizerForceSend']) {
if ($instance['oldstatus'] === $instance['newstatus'] && 'REPLY' !== $eventInfo['organizerForceSend']) {
// Skip
continue;
}
@ -739,7 +757,7 @@ class Broker
// EXDATE
$dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
// Treat is as a DATE field
if (strlen($instance['id']) <= 8) {
if (strlen((string) $instance['id']) <= 8) {
$event->add('DTSTART', $dt, ['VALUE' => 'DATE']);
} else {
$event->add('DTSTART', $dt);
@ -751,7 +769,7 @@ class Broker
if ('master' !== $instance['id']) {
$dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
// Treat is as a DATE field
if (strlen($instance['id']) <= 8) {
if (strlen((string) $instance['id']) <= 8) {
$event->add('RECURRENCE-ID', $dt, ['VALUE' => 'DATE']);
} else {
$event->add('RECURRENCE-ID', $dt);
@ -774,9 +792,9 @@ class Broker
$message->message = $icalMsg;
return [$message];
} else {
return [];
}
return [];
}
/**
@ -843,7 +861,7 @@ class Broker
$organizer = $vevent->ORGANIZER->getNormalizedValue();
$organizerName = $vevent->ORGANIZER['CN'] ?? null;
} 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.');
}
}
@ -869,7 +887,7 @@ class Broker
foreach ($vevent->select('RRULE') as $rr) {
foreach ($rr->getParts() as $key => $val) {
// ignore default values (https://github.com/sabre-io/vobject/issues/126)
if ('INTERVAL' === $key && 1 == $val) {
if ('INTERVAL' === $key && '1' === (string) $val) {
continue;
}
if (is_array($val)) {
@ -881,7 +899,7 @@ class Broker
sort($rrule);
}
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';
@ -892,11 +910,14 @@ class Broker
$timezone = $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimeZone();
}
}
$instances[$recurId] = $vevent;
if (isset($vevent->ATTENDEE)) {
foreach ($vevent->ATTENDEE as $attendee) {
if ($this->scheduleAgentServerRules
&& isset($attendee['SCHEDULE-AGENT'])
&& 'CLIENT' === strtoupper($attendee['SCHEDULE-AGENT']->getValue())
&& 'CLIENT' === strtoupper((string) $attendee['SCHEDULE-AGENT']->getValue())
) {
continue;
}
@ -930,10 +951,10 @@ class Broker
];
}
}
$instances[$recurId] = $vevent;
}
foreach ($this->significantChangeProperties as $prop) {
// @phpstan-ignore property.dynamicName
if (isset($vevent->$prop)) {
$propertyValues = $vevent->select($prop);

View file

@ -32,12 +32,12 @@ class Message
* Contains the ITip method, which is something like REQUEST, REPLY or
* CANCEL.
*/
public ?string $method;
public ?string $method = null;
/**
* The current sequence number for the event.
*/
public ?int $sequence;
public ?int $sequence = null;
/**
* The senders' email address.
@ -52,7 +52,7 @@ class Message
* The name of the sender. This is often populated from a CN parameter from
* either the ORGANIZER or ATTENDEE, depending on the message.
*/
public ?string $senderName;
public ?string $senderName = null;
/**
* The recipient's email address.
@ -63,7 +63,7 @@ class Message
* The name of the recipient. This is usually populated with the CN
* 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
@ -107,10 +107,9 @@ class Message
{
if (!$this->scheduleStatus) {
return false;
} else {
list($scheduleStatus) = explode(';', $this->scheduleStatus);
return $scheduleStatus;
}
[$scheduleStatus] = explode(';', $this->scheduleStatus);
return $scheduleStatus;
}
}

View file

@ -42,7 +42,7 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable, \Js
/**
* Reference to the parent object, if this is not the top object.
*/
public ?Node $parent;
public ?Node $parent = null;
/**
* Iterator override.
@ -52,7 +52,7 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable, \Js
/**
* The root document.
*/
protected ?Component $root;
protected ?Component $root = null;
/**
* Serializes the node into a mimedir format.

View file

@ -41,7 +41,7 @@ trait PHPUnitAssertions
$input = Reader::read($input);
}
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);
if ($input instanceof Component\VCalendar && 'GREGORIAN' === (string) $input->CALSCALE) {
@ -51,23 +51,28 @@ trait PHPUnitAssertions
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.
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) {
$actual = preg_replace(
$actualSerialized = preg_replace(
'|^'.preg_quote($match[1], '|').':(.*)\r$|m',
$match[1].':**ANY**'."\r",
$actual
(string) $actualSerialized
);
}
$this->assertEquals(
$expected,
$actual,
self::assertEquals(
$expectedSerialized,
$actualSerialized,
$message
);
}

View file

@ -16,7 +16,7 @@ use Sabre\Xml;
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Parameter extends Node
class Parameter extends Node implements \Stringable
{
/**
* Parameter name.
@ -74,83 +74,12 @@ class Parameter extends Node
*/
public static function guessParameterNameByValue(string $value): string
{
switch (strtoupper($value)) {
// Encodings
case '7-BIT':
case 'QUOTED-PRINTABLE':
case 'BASE64':
$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 = '';
}
$name = match (strtoupper($value)) {
'7-BIT', 'QUOTED-PRINTABLE', 'BASE64' => 'ENCODING',
'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',
'INLINE', 'URL', 'CONTENT-ID', 'CID' => 'VALUE',
default => '',
};
return $name;
}
@ -177,9 +106,9 @@ class Parameter extends Node
{
if (is_array($this->value)) {
return implode(',', $this->value);
} else {
return $this->value;
}
return $this->value;
}
/**
@ -201,9 +130,9 @@ class Parameter extends Node
return $this->value;
} elseif (is_null($this->value)) {
return [];
} else {
return [$this->value];
}
return [$this->value];
}
/**
@ -234,7 +163,8 @@ class Parameter extends Node
{
return in_array(
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.
if (!preg_match('#(?: [\n":;\^,\+] )#x', $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;
}
);
}

View file

@ -27,12 +27,12 @@ class Json extends Parser
/**
* The input data.
*/
protected ?array $input;
protected ?array $input = null;
/**
* Root component.
*/
protected ?Document $root;
protected ?Document $root = null;
/**
* This method starts the parsing process.
@ -61,16 +61,11 @@ class Json extends Parser
$this->options = $options;
}
switch ($this->input[0]) {
case 'vcalendar':
$this->root = new VCalendar([], false);
break;
case 'vcard':
$this->root = new VCard([], false);
break;
default:
throw new ParseException('The root component must either be a vcalendar, or a vcard');
}
$this->root = match ($this->input[0]) {
'vcalendar' => new VCalendar([], false),
'vcard' => new VCard([], false),
default => throw new ParseException('The root component must either be a vcalendar, or a vcard'),
};
foreach ($this->input[1] as $prop) {
$this->root->add($this->parseProperty($prop));
}
@ -97,17 +92,13 @@ class Json extends Parser
$self = $this;
$properties = array_map(
function ($jProp) use ($self) {
return $self->parseProperty($jProp);
},
$self->parseProperty(...),
$jComp[1]
);
if (isset($jComp[2])) {
$components = array_map(
function ($jComp) use ($self) {
return $self->parseComponent($jComp);
},
$self->parseComponent(...),
$jComp[2]
);
} else {
@ -134,7 +125,7 @@ class Json extends Parser
$valueType,
) = $jProp;
$propertyName = strtoupper($propertyName);
$propertyName = strtoupper((string) $propertyName);
// 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.
@ -144,7 +135,7 @@ class Json extends Parser
$value = array_slice($jProp, 3);
$valueType = strtoupper($valueType);
$valueType = strtoupper((string) $valueType);
if (isset($parameters['group'])) {
$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
// type for the given property (e.g.: BDAY), we need to add a VALUE=
// parameter.
if ($defaultPropertyClass !== get_class($prop)) {
if ($defaultPropertyClass !== $prop::class) {
$prop['VALUE'] = $valueType;
}

View file

@ -26,6 +26,11 @@ use Sabre\VObject\Property;
*/
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.
*
@ -36,7 +41,7 @@ class MimeDir extends Parser
/**
* Root component.
*/
protected ?Document $root;
protected ?Document $root = null;
/**
* 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
{
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).')');
}
$this->charset = $charset;
@ -150,23 +155,18 @@ class MimeDir extends Parser
// BOM is ZERO WIDTH NO-BREAK SPACE (U+FEFF).
// It's 0xEF 0xBB 0xBF in UTF-8 hex.
if (3 <= strlen($line)
if (3 <= strlen((string) $line)
&& 0xEF === ord($line[0])
&& 0xBB === ord($line[1])
&& 0xBF === ord($line[2])) {
$line = \substr($line, 3);
$line = \substr((string) $line, 3);
}
switch (strtoupper($line)) {
case 'BEGIN:VCALENDAR':
$class = VCalendar::$componentMap['VCALENDAR'];
break;
case 'BEGIN:VCARD':
$class = VCard::$componentMap['VCARD'];
break;
default:
throw new ParseException('This parser only supports VCARD and VCALENDAR files');
}
$class = match (strtoupper((string) $line)) {
'BEGIN:VCALENDAR' => VCalendar::$componentMap['VCALENDAR'],
'BEGIN:VCARD' => VCard::$componentMap['VCARD'],
default => throw new ParseException('This parser only supports VCARD and VCALENDAR files'),
};
$this->root = new $class([], false);
@ -174,10 +174,10 @@ class MimeDir extends Parser
// Reading until we hit END:
try {
$line = $this->readLine();
} catch (EofException $oEx) {
} catch (EofException) {
$line = 'END:'.$this->root->name;
}
if ('END:' === strtoupper(\substr($line, 0, 4))) {
if ('END:' === strtoupper(\substr((string) $line, 0, 4))) {
break;
}
$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) {
throw new ParseException('Invalid MimeDir file. expected: "END:'.$this->root->name.'" got: "END:'.$name.'"');
}
@ -215,7 +215,7 @@ class MimeDir extends Parser
while (true) {
// Reading until we hit END:
$line = $this->readLine();
if ('END:' === strtoupper(\substr($line, 0, 4))) {
if ('END:' === strtoupper(\substr((string) $line, 0, 4))) {
break;
}
$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) {
throw new ParseException('Invalid MimeDir file. expected: "END:'.$component->name.'" got: "END:'.$name.'"');
}
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,
];
/*
* Keep track on the last token we parsed in order to do
* better error checking
*/
$lastToken = null;
$lastParam = null;
/*
@ -385,10 +390,16 @@ class MimeDir extends Parser
// option is set to ignore invalid lines, we ignore this line
// This can happen when servers provide faulty data as iCloud
// frequently does with X-APPLE-STRUCTURED-LOCATION
$lastToken = self::TOKEN_PARAMVALUE;
continue;
}
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])) {
$property['parameters'][$lastParam] = $value;
} elseif (is_array($property['parameters'][$lastParam])) {
@ -402,21 +413,23 @@ class MimeDir extends Parser
$value,
];
}
$lastToken = self::TOKEN_PARAMVALUE;
continue;
}
if (isset($match['paramName'])) {
$lastParam = strtoupper($match['paramName']);
if (!isset($property['parameters'][$lastParam])) {
$property['parameters'][$lastParam] = null;
}
$property['parameters'][$lastParam] ??= null;
$lastToken = self::TOKEN_PARAMNAME;
continue;
}
if (isset($match['propValue'])) {
$property['value'] = $match['propValue'];
$lastToken = self::TOKEN_PROPVALUE;
continue;
}
if (isset($match['name']) && $match['name']) {
if (isset($match['name']) && 0 < strlen($match['name'])) {
$property['name'] = strtoupper($match['name']);
$lastToken = self::TOKEN_PROPNAME;
continue;
}
@ -425,10 +438,8 @@ class MimeDir extends Parser
// @codeCoverageIgnoreEnd
}
if (\is_null($property['value'])) {
$property['value'] = '';
}
if (!$property['name']) {
$property['value'] ??= '';
if (!isset($property['name']) || 0 === strlen($property['name'])) {
if ($this->options & self::OPTION_IGNORE_INVALID_LINES) {
return false;
}
@ -671,7 +682,7 @@ class MimeDir extends Parser
// missing a whitespace. So if 'forgiving' is turned on, we will take
// those as well.
if ($this->options & self::OPTION_FORGIVING) {
while ('=' === \substr($value, -1) && $this->lineBuffer) {
while (str_ends_with($value, '=') && $this->lineBuffer) {
// Reading the line
$this->readLine();
// Grabbing the raw form

View file

@ -30,11 +30,6 @@ abstract class Parser
*/
public const OPTION_IGNORE_INVALID_LINES = 2;
/**
* Bitmask of parser options.
*/
protected int $options;
/**
* Creates the parser.
*
@ -42,12 +37,11 @@ abstract class Parser
*
* @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)) {
$this->setInput($input);
}
$this->options = $options;
}
/**

View file

@ -28,17 +28,17 @@ class XML extends Parser
/**
* The input data.
*/
protected ?array $input;
protected ?array $input = null;
/**
* A pointer/reference to the input.
*/
private ?array $pointer;
private ?array $pointer = null;
/**
* Document, root component.
*/
protected ?Document $root;
protected ?Document $root = null;
/**
* Creates the parser.
@ -112,7 +112,7 @@ class XML extends Parser
*/
protected function parseVCalendarComponents(Component $parentComponent): void
{
foreach ($this->pointer['value'] ?: [] as $children) {
foreach ($this->pointer['value'] ?? [] as $children) {
switch (static::getTagName($children['name'])) {
case 'properties':
$this->pointer = &$children['value'];
@ -145,8 +145,8 @@ class XML extends Parser
*/
protected function parseProperties(Component $parentComponent, string $propertyNamePrefix = ''): void
{
foreach ($this->pointer ?: [] as $xmlProperty) {
list($namespace, $tagName) = SabreXml\Service::parseClarkNotation($xmlProperty['name']);
foreach ($this->pointer ?? [] as $xmlProperty) {
[$namespace, $tagName] = SabreXml\Service::parseClarkNotation($xmlProperty['name']);
$propertyName = $tagName;
$propertyValue = [];
@ -322,7 +322,7 @@ class XML extends Parser
*/
protected function parseComponent(Component $parentComponent): void
{
$components = $this->pointer['value'] ?: [];
$components = $this->pointer['value'] ?? [];
foreach ($components as $component) {
$componentName = static::getTagName($component['name']);

View file

@ -14,27 +14,8 @@ use Sabre\Xml;
* @author Evert Pot (http://evertpot.com/)
* @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.
*/
@ -75,13 +56,13 @@ abstract class Property extends Node
* @param array $parameters List of parameters
* @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) {
$this->add($k, $v);
}
@ -123,7 +104,7 @@ abstract class Property extends Node
public function getValue()
{
if (is_array($this->value)) {
if (0 == count($this->value)) {
if (0 === count($this->value)) {
return null;
} elseif (1 === count($this->value)) {
return $this->value[0];
@ -155,9 +136,9 @@ abstract class Property extends Node
return [];
} elseif (is_array($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
return \substr($str, 0, -1);
return \substr((string) $str, 0, -1);
}
/**
@ -282,7 +263,7 @@ abstract class Property extends Node
if ('VALUE' === $parameter->name) {
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'
// parameter.
@ -292,7 +273,7 @@ abstract class Property extends Node
return array_merge(
[
strtolower($this->name),
strtolower((string) $this->name),
(object) $parameters,
strtolower($this->getValueType()),
],
@ -329,13 +310,13 @@ abstract class Property extends Node
$parameters[] = $parameter;
}
$writer->startElement(strtolower($this->name));
$writer->startElement(strtolower((string) $this->name));
if (!empty($parameters)) {
if ([] !== $parameters) {
$writer->startElement('parameters');
foreach ($parameters as $parameter) {
$writer->startElement(strtolower($parameter->name));
$writer->startElement(strtolower((string) $parameter->name));
$writer->write($parameter);
$writer->endElement();
}
@ -520,7 +501,7 @@ abstract class Property extends Node
}
// 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[] = [
'level' => $options & self::REPAIR ? 1 : 3,
'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;
}
if ($allowedEncoding && !in_array(strtoupper($encoding), $allowedEncoding)) {
if ($allowedEncoding && !in_array(strtoupper($encoding), $allowedEncoding, true)) {
$warnings[] = [
'level' => 3,
'message' => 'ENCODING='.strtoupper($encoding).' is not valid for this document type.',

View file

@ -54,7 +54,7 @@ class Binary extends Property
*/
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
{
return base64_encode($this->value);
return base64_encode((string) $this->value);
}
/**
@ -83,7 +83,7 @@ class Binary extends Property
*/
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
{
$value = array_map('base64_decode', $value);
$value = array_map(base64_decode(...), $value);
parent::setJsonValue($value);
}
}

View file

@ -56,9 +56,7 @@ class Boolean extends Property
public function setXmlValue(array $value): void
{
$value = array_map(
function ($value) {
return 'true' === $value;
},
fn ($value) => 'true' === $value,
$value
);
parent::setXmlValue($value);

View file

@ -67,7 +67,7 @@ class FloatValue extends Property
*/
public function getJsonValue(): array
{
$val = array_map('floatval', $this->getParts());
$val = array_map(floatval(...), $this->getParts());
// Special-casing the GEO property.
//
@ -86,7 +86,7 @@ class FloatValue extends Property
*/
public function setXmlValue(array $value): void
{
$value = array_map('floatval', $value);
$value = array_map(floatval(...), $value);
parent::setXmlValue($value);
}
@ -101,7 +101,7 @@ class FloatValue extends Property
// See:
// http://tools.ietf.org/html/rfc6321#section-3.4.1.2
if ('GEO' === $this->name) {
$value = array_map('floatval', $this->getParts());
$value = array_map(floatval(...), $this->getParts());
$writer->writeElement('latitude', $value[0]);
$writer->writeElement('longitude', $value[1]);

View file

@ -43,10 +43,10 @@ class CalAddress extends Text
public function getNormalizedValue(): string
{
$input = $this->getValue();
if (!strpos($input, ':')) {
if (!strpos((string) $input, ':')) {
return $input;
}
list($schema, $everythingElse) = explode(':', $input, 2);
[$schema, $everythingElse] = explode(':', (string) $input, 2);
$schema = strtolower($schema);
if ('mailto' === $schema) {
$everythingElse = strtolower($everythingElse);

View file

@ -111,7 +111,7 @@ class DateTime extends Property
!$this->hasTime()
|| (
!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)) {
$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) {
$this->offsetSet('TZID', $tz->getName());
}
@ -257,15 +257,15 @@ class DateTime extends Property
$isFloating = $this->isFloating();
$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(
function (\DateTimeInterface $dt) use ($hasTime, $isUtc) {
if ($hasTime) {
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
);
@ -286,9 +286,7 @@ class DateTime extends Property
// those.
$this->setValue(
array_map(
function ($item) {
return strtr($item, [':' => '', '-' => '']);
},
fn ($item) => strtr($item, [':' => '', '-' => '']),
$value
)
);
@ -306,7 +304,7 @@ class DateTime extends Property
public function offsetSet($offset, $value): void
{
parent::offsetSet($offset, $value);
if ('VALUE' !== strtoupper($offset)) {
if ('VALUE' !== strtoupper((string) $offset)) {
return;
}
@ -347,7 +345,7 @@ class DateTime extends Property
DateTimeParser::parseDateTime($value);
break;
}
} catch (InvalidDataException $e) {
} catch (InvalidDataException) {
$messages[] = [
'level' => 3,
'message' => 'The supplied value ('.$value.') is not a correct '.$valueType,

View file

@ -64,14 +64,17 @@ class Period extends Property
public function setJsonValue(array $value): void
{
$value = array_map(
function ($item) {
return strtr(implode('/', $item), [':' => '', '-' => '']);
},
fn ($item) => strtr(implode('/', $item), [':' => '', '-' => '']),
$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.
*
@ -83,21 +86,21 @@ class Period extends Property
{
$return = [];
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.
if ('P' === $end[0]) {
$return[] = [
$start->format('Y-m-d\\TH:i:s'),
$startDt,
$end,
];
} else {
$end = DateTimeParser::parseDateTime($end);
$endDt = DateTimeParser::parseDateTime($end)->format('Y-m-d\\TH:i:s').$this->appendUtc($end);
$return[] = [
$start->format('Y-m-d\\TH:i:s'),
$end->format('Y-m-d\\TH:i:s'),
$startDt,
$endDt,
];
}
}

View file

@ -29,14 +29,14 @@ class Recur extends Property
/**
* Reference to the parent object, if this is not the top object.
*/
public ?Node $parent;
public ?Node $parent = null;
/**
* Updates the current value.
*
* 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
{
@ -52,17 +52,17 @@ class Recur extends Property
$v = strtoupper($v);
// The value had multiple sub-values
if (false !== strpos($v, ',')) {
if (str_contains($v, ',')) {
$v = explode(',', $v);
}
if (0 === strcmp($k, 'until')) {
if (0 === strcmp((string) $k, 'until')) {
$v = strtr($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;
} elseif (is_string($value)) {
@ -151,13 +151,13 @@ class Recur extends Property
{
$values = [];
foreach ($this->getParts() as $k => $v) {
if (0 === strcmp($k, 'UNTIL')) {
if (0 === strcmp((string) $k, 'UNTIL')) {
$date = new DateTime($this->root, null, $v);
$values[strtolower($k)] = $date->getJsonValue()[0];
} elseif (0 === strcmp($k, 'COUNT')) {
$values[strtolower($k)] = intval($v);
$values[strtolower((string) $k)] = $date->getJsonValue()[0];
} elseif (0 === strcmp((string) $k, 'COUNT')) {
$values[strtolower((string) $k)] = intval($v);
} else {
$values[strtolower($k)] = $v;
$values[strtolower((string) $k)] = $v;
}
}
@ -186,7 +186,7 @@ class Recur extends Property
$newValue = [];
foreach (explode(';', $value) as $part) {
// Skipping empty parts.
if (empty($part)) {
if ('' === $part) {
continue;
}
@ -196,10 +196,10 @@ class Recur extends Property
throw new InvalidDataException('The supplied iCalendar RRULE part is incorrect: '.$part);
}
list($partName, $partValue) = $parts;
[$partName, $partValue] = $parts;
// The value itself had multiple values..
if (false !== strpos($partValue, ',')) {
if (str_contains($partValue, ',')) {
$partValue = explode(',', $partValue);
}
$newValue[$partName] = $partValue;
@ -243,7 +243,7 @@ class Recur extends Property
if ($repair) {
unset($values[$key]);
}
} elseif ('BYMONTH' == $key) {
} elseif ('BYMONTH' === $key) {
$byMonth = (array) $value;
foreach ($byMonth as $i => $v) {
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 (is_array($value) && empty($values[$key])) {
if (is_array($value) && ([] === $values[$key])) {
unset($values[$key]);
}
} elseif ('BYWEEKNO' == $key) {
} elseif ('BYWEEKNO' === $key) {
$byWeekNo = (array) $value;
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[] = [
'level' => $repair ? 1 : 3,
'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 (is_array($value) && empty($values[$key])) {
if (is_array($value) && ([] === $values[$key])) {
unset($values[$key]);
}
} elseif ('BYYEARDAY' == $key) {
} elseif ('BYYEARDAY' === $key) {
$byYearDay = (array) $value;
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[] = [
'level' => $repair ? 1 : 3,
'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 (is_array($value) && empty($values[$key])) {
if (is_array($value) && ([] === $values[$key])) {
unset($values[$key]);
}
}

View file

@ -62,7 +62,7 @@ class IntegerValue extends Property
*/
public function setXmlValue(array $value): void
{
$value = array_map('intval', $value);
$value = array_map(intval(...), $value);
parent::setXmlValue($value);
}
}

View file

@ -71,7 +71,7 @@ class Text extends Property
// 2. structured value properties
//
// 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 = ';';
}
@ -155,7 +155,7 @@ class Text extends Property
// Structured text values should always be returned as a single
// array-item. Multi-value text should be returned as multiple items in
// the top-array.
if (in_array($this->name, $this->structuredValues)) {
if (in_array($this->name, $this->structuredValues, true)) {
return [$this->getParts()];
}
@ -213,7 +213,7 @@ class Text extends Property
// If the resulting value contains a \n, we must encode it as
// quoted-printable.
if (false !== \strpos($val, "\n")) {
if (str_contains((string) $val, "\n")) {
$str .= ';ENCODING=QUOTED-PRINTABLE:';
$lastLine = $str;
$out = '';
@ -222,13 +222,13 @@ class Text extends Property
// encode newlines for us. Specifically, the \r\n sequence must in
// vcards be encoded as =0D=OA and we must insert soft-newlines
// every 75 bytes.
for ($ii = 0; $ii < \strlen($val); ++$ii) {
for ($ii = 0; $ii < \strlen((string) $val); ++$ii) {
$ord = \ord($val[$ii]);
// These characters are encoded as themselves.
if ($ord >= 32 && $ord <= 126) {
$lastLine .= $val[$ii];
} else {
$lastLine .= '='.\strtoupper(\bin2hex($val[$ii]));
$lastLine .= '='.\strtoupper(\bin2hex((string) $val[$ii]));
}
if (\strlen($lastLine) >= 75) {
// Soft line break
@ -241,22 +241,21 @@ class Text extends Property
}
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,74} # max 75 bytes per line (1 byte is used for a single space added after every CRLF)
(?![\x80-\xbf]) # prevent splitting multibyte characters
)/x',
"$1\r\n ",
$str
);
"$1\r\n ",
$str
);
// remove single space after last CRLF
return \substr($str, 0, -1);
}
// remove single space after last CRLF
return \substr((string) $str, 0, -1);
}
/**
@ -271,7 +270,7 @@ class Text extends Property
foreach ($items as $i => $item) {
$writer->writeElement(
$item,
!empty($values[$i]) ? $values[$i] : null
('' !== $values[$i] && [] !== $values[$i]) ? $values[$i] : null
);
}
};

View file

@ -104,7 +104,7 @@ class Time extends Text
$timeStr .= 'Z';
} else {
$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
{
$value = array_map(
function ($value) {
return str_replace(':', '', $value);
},
fn ($value) => str_replace(':', '', $value),
$value
);
parent::setXmlValue($value);

View file

@ -38,7 +38,7 @@ class Uri extends Text
public function parameters(): array
{
$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
// 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);
$newVal = '';
foreach ($matches as $match) {
switch ($match) {
case '\:':
$newVal .= ':';
break;
default:
$newVal .= $match;
break;
}
match ($match) {
'\:' => $newVal .= ':',
default => $newVal .= $match,
};
}
$this->value = $newVal;
} else {

View file

@ -42,9 +42,7 @@ class UtcOffset extends Text
public function setJsonValue(array $value): void
{
$value = array_map(
function ($value) {
return str_replace(':', '', $value);
},
fn ($value) => str_replace(':', '', $value),
$value
);
parent::setJsonValue($value);
@ -58,10 +56,8 @@ class UtcOffset extends Text
public function getJsonValue(): array
{
return array_map(
function ($value) {
return substr($value, 0, -2).':'.
substr($value, -2);
},
fn ($value) => substr((string) $value, 0, -2).':'.
substr((string) $value, -2),
parent::getJsonValue()
);
}

View file

@ -77,7 +77,7 @@ class DateAndOrTime extends Property
public function setDateTime(\DateTimeInterface $dt): void
{
$tz = $dt->getTimeZone();
$isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z']);
$isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z'], true);
if ($isUtc) {
$value = $dt->format('Ymd\\THis\\Z');
@ -232,14 +232,10 @@ class DateAndOrTime extends Property
$value = '';
// $d = defined
$d = function ($part) use ($parts): bool {
return !is_null($parts[$part]);
};
$d = (fn ($part): bool => !is_null($parts[$part]));
// $r = read
$r = function ($part) use ($parts) {
return $parts[$part];
};
$r = (fn ($part) => $parts[$part]);
// From the Relax NG Schema.
//
@ -341,7 +337,7 @@ class DateAndOrTime extends Property
try {
DateTimeParser::parseVCardDateTime($value);
} catch (InvalidDataException $e) {
} catch (InvalidDataException) {
$messages[] = [
'level' => 3,
'message' => 'The supplied value ('.$value.') is not a correct DATE-AND-OR-TIME property',

View file

@ -91,9 +91,7 @@ class EventIterator implements \Iterator
*/
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;
if (is_array($input)) {
@ -164,8 +162,12 @@ class EventIterator implements \Iterator
}
if (isset($this->masterEvent->RDATE)) {
$rdateValues = [];
foreach ($this->masterEvent->RDATE as $rdate) {
$rdateValues = array_merge($rdateValues, $rdate->getParts());
}
$this->recurIterator = new RDateIterator(
$this->masterEvent->RDATE->getParts(),
$rdateValues,
$this->startDate
);
} elseif (isset($this->masterEvent->RRULE)) {
@ -228,11 +230,10 @@ class EventIterator implements \Iterator
}
if ($this->currentOverriddenEvent && $this->currentOverriddenEvent->DTEND) {
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');
}
/**

View file

@ -26,9 +26,13 @@ class RDateIterator implements \Iterator
*
* @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->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
* ->current().

View file

@ -37,9 +37,13 @@ class RRuleIterator implements \Iterator
*
* @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->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
* ->current().
@ -357,23 +354,23 @@ class RRuleIterator implements \Iterator
}
$recurrenceHours = [];
if (!empty($this->byHour)) {
if ($this->byHour) {
$recurrenceHours = $this->getHours();
}
$recurrenceDays = [];
if (!empty($this->byDay)) {
if ($this->byDay) {
$recurrenceDays = $this->getDays();
}
$recurrenceMonths = [];
if (!empty($this->byMonth)) {
if ($this->byMonth) {
$recurrenceMonths = $this->getMonths();
}
do {
if ($this->byHour) {
if ('23' == $this->currentDate->format('G')) {
if ('23' === $this->currentDate->format('G')) {
// to obey the interval rule
$this->currentDate = $this->currentDate->modify('+'.($this->interval - 1).' days');
}
@ -398,9 +395,9 @@ class RRuleIterator implements \Iterator
return;
}
} while (
($this->byDay && !in_array($currentDay, $recurrenceDays))
|| ($this->byHour && !in_array($currentHour, $recurrenceHours))
|| ($this->byMonth && !in_array($currentMonth, $recurrenceMonths))
($this->byDay && !in_array((int) $currentDay, $recurrenceDays, true))
|| ($this->byHour && !in_array($currentHour, $recurrenceHours, true))
|| ($this->byMonth && !in_array($currentMonth, $recurrenceMonths, true))
);
}
@ -442,18 +439,18 @@ class RRuleIterator implements \Iterator
$currentHour = (int) $this->currentDate->format('G');
// 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');
// 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.
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]]);
}
}
// 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;
$tempDate = clone $this->currentDate;
$tempDate = $tempDate->modify('+ '.($this->interval * $increase).' months '.$this->startTime());
} while ($tempDate->format('j') != $currentDayOfMonth);
} while ($tempDate->format('j') !== $currentDayOfMonth);
$this->currentDate = $tempDate;
}
return;
}
$occurrence = -1;
$selectedOccurrence = -1;
while (true) {
$occurrences = $this->getMonthlyOccurrences();
@ -491,6 +488,7 @@ class RRuleIterator implements \Iterator
// The first occurrence that's higher than the current
// day of the month wins.
if ($occurrence > $currentDayOfMonth) {
$selectedOccurrence = $occurrence;
break 2;
}
}
@ -533,7 +531,7 @@ class RRuleIterator implements \Iterator
$this->currentDate = $this->currentDate->setDate(
(int) $this->currentDate->format('Y'),
(int) $this->currentDate->format('n'),
(int) $occurrence
(int) $selectedOccurrence
)->modify($this->startTime());
}
@ -547,9 +545,9 @@ class RRuleIterator implements \Iterator
$currentDayOfMonth = $this->currentDate->format('j');
// No sub-rules, so we just advance by year
if (empty($this->byMonth)) {
if (!$this->byMonth) {
// Unless it was a leap day!
if (2 == $currentMonth && 29 == $currentDayOfMonth) {
if (2 === (int) $currentMonth && 29 === (int) $currentDayOfMonth) {
$counter = 0;
do {
++$counter;
@ -563,7 +561,7 @@ class RRuleIterator implements \Iterator
// functions instead.
$nextDate = clone $this->currentDate;
$nextDate = $nextDate->modify('+ '.($this->interval * $counter).' years');
} while (2 != $nextDate->format('n'));
} while (2 !== (int) $nextDate->format('n'));
$this->currentDate = $nextDate;
@ -634,7 +632,7 @@ class RRuleIterator implements \Iterator
$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;
}
}
@ -665,7 +663,7 @@ class RRuleIterator implements \Iterator
// If we got a byDay or getMonthDay filter, we must first expand
// further.
if ($this->byDay || $this->byMonthDay) {
$occurrence = -1;
$selectedOccurrence = -1;
while (true) {
$occurrences = $this->getMonthlyOccurrences();
@ -677,7 +675,8 @@ class RRuleIterator implements \Iterator
if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) {
// only consider byMonth matches,
// 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;
}
}
@ -693,12 +692,12 @@ class RRuleIterator implements \Iterator
$currentYear += $this->interval;
$currentMonth = 1;
}
} while (!in_array($currentMonth, $this->byMonth));
} while (!in_array((string) $currentMonth, $this->byMonth, true));
$this->currentDate = $this->currentDate->setDate(
(int) $currentYear,
(int) $currentMonth,
(int) $currentDayOfMonth
$currentMonth,
$currentDayOfMonth
);
// 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(
(int) $currentYear,
(int) $currentMonth,
(int) $occurrence
)->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
(int) $selectedOccurrence
)->modify($this->startTime());
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) {
$key = strtoupper($key);
$key = strtoupper((string) $key);
switch ($key) {
case 'FREQ':
$value = strtolower($value);
$value = strtolower((string) $value);
if (!in_array(
$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));
}
@ -809,7 +806,7 @@ class RRuleIterator implements \Iterator
case 'BYDAY':
$value = (array) $value;
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);
}
}
@ -823,7 +820,7 @@ class RRuleIterator implements \Iterator
case 'BYYEARDAY':
$this->byYearDay = (array) $value;
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!');
}
}
@ -832,7 +829,7 @@ class RRuleIterator implements \Iterator
case 'BYWEEKNO':
$this->byWeekNo = (array) $value;
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!');
}
}
@ -852,7 +849,7 @@ class RRuleIterator implements \Iterator
break;
case 'WKST':
$this->weekStart = strtoupper($value);
$this->weekStart = strtoupper((string) $value);
break;
default:
@ -892,7 +889,7 @@ class RRuleIterator implements \Iterator
// that point and add it to the results.
if ($this->byDay) {
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
// all wednesdays in this month.
@ -913,8 +910,8 @@ class RRuleIterator implements \Iterator
// So now we have 'all wednesdays' for month. It is however
// possible that the user only really wanted the 1st, 2nd or last
// wednesday.
if (strlen($day) > 2) {
$offset = (int) substr($day, 0, -2);
if (strlen((string) $day) > 2) {
$offset = (int) substr((string) $day, 0, -2);
if ($offset > 0) {
// It is possible that the day does not exist, such as a
@ -949,7 +946,7 @@ class RRuleIterator implements \Iterator
$byMonthDayResults[] = $monthDay;
} else {
// 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
// negative (-n) integer. However, this does not make
// sense in 'weekly' so we ignore it here.
$recurrenceDays[] = $this->dayMap[substr($byDay, -2)];
$recurrenceDays[] = $this->dayMap[substr((string) $byDay, -2)];
}
return $recurrenceDays;

View file

@ -96,8 +96,8 @@ class ICalendar implements SplitterInterface
}
return $object;
} else {
return null;
}
return null;
}
}

View file

@ -22,13 +22,6 @@ use Sabre\VObject\Parser\MimeDir;
*/
class VCard implements SplitterInterface
{
/**
* File handle.
*
* @var resource
*/
protected $input;
/**
* Persistent parser.
*/
@ -42,10 +35,12 @@ class VCard implements SplitterInterface
* @param resource $input
* @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($input, $options);
$this->parser = new MimeDir($this->input, $options);
}
/**
@ -64,7 +59,7 @@ class VCard implements SplitterInterface
if (!$object instanceof Component\VCard) {
throw new VObject\ParseException('The supplied input contained non-VCARD data.');
}
} catch (VObject\EofException $e) {
} catch (VObject\EofException) {
return null;
}

View file

@ -41,9 +41,7 @@ class TimeZoneUtil
private static function getInstance(): self
{
if (null === self::$instance) {
self::$instance = new self();
}
self::$instance ??= new self();
return self::$instance;
}

View file

@ -24,6 +24,24 @@ class FindFromOffset implements TimezoneFinder
// @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;
}
}

View file

@ -40,13 +40,13 @@ class FindFromTimezoneIdentifier implements TimezoneFinder
try {
if (
in_array($tzid, $tzIdentifiers)
in_array($tzid, $tzIdentifiers, true)
|| preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)
|| in_array($tzid, $this->getIdentifiersBC())
|| in_array($tzid, $this->getIdentifiersBC(), true)
) {
return new \DateTimeZone($tzid);
}
} catch (\Exception $e) {
} catch (\Exception) {
}
return null;

View file

@ -18,11 +18,18 @@ class FindFromTimezoneMap implements TimezoneFinder
'/^\((UTC|GMT)(\+|\-)[\d]{2}\.[\d]{2}\) (.*)/',
];
/**
* @throws void
*/
public function find(string $tzid, ?bool $failIfUncertain = false): ?\DateTimeZone
{
// Next, we check if the tzid is somewhere in our tzid map.
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
@ -34,7 +41,11 @@ class FindFromTimezoneMap implements TimezoneFinder
}
$tzidAlternate = $matches[3];
if ($this->hasTzInMap($tzidAlternate)) {
return new \DateTimeZone($this->getTzFromMap($tzidAlternate));
try {
return new \DateTimeZone($this->getTzFromMap($tzidAlternate));
} catch (\Exception) {
return null;
}
}
}

View file

@ -23,7 +23,7 @@ class GuessFromLicEntry implements TimezoneGuesser
// Libical generators may specify strings like
// "SystemV/EST5EDT". For those we must remove the
// SystemV part.
if ('SystemV/' === substr($lic, 0, 8)) {
if (str_starts_with($lic, 'SystemV/')) {
$lic = substr($lic, 8);
}

View file

@ -105,7 +105,7 @@ class GuessFromMsTzId implements TimezoneGuesser
$cdoId = (int) $vtimezone->{'X-MICROSOFT-CDO-TZID'}->getValue();
// 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');
}

View file

@ -38,10 +38,10 @@ class VCardConverter
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');
}
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');
}
@ -69,7 +69,7 @@ class VCardConverter
protected function convertProperty(Component\VCard $input, Component\VCard $output, Property $property, int $targetVersion): void
{
// Skipping these, those are automatically added.
if (in_array($property->name, ['VERSION', 'PRODID'])) {
if (in_array($property->name, ['VERSION', 'PRODID'], true)) {
return;
}
@ -93,7 +93,7 @@ class VCardConverter
);
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 */
$newProperty = $this->convertUriToBinary($output, $newProperty);
} elseif ($property instanceof Property\VCard\DateAndOrTime) {
@ -111,7 +111,7 @@ class VCardConverter
$newProperty['X-APPLE-OMIT-YEAR'] = '1604';
}
if ('ANNIVERSARY' == $newProperty->name) {
if ('ANNIVERSARY' === $newProperty->name) {
// Microsoft non-standard anniversary
$newProperty->name = 'X-ANNIVERSARY';
@ -127,7 +127,7 @@ class VCardConverter
$output->add('ITEM'.$x.'.X-ABLABEL', '_$!<Anniversary>!$_');
}
} elseif ('KIND' === $property->name) {
switch (strtolower($property->getValue())) {
switch (strtolower((string) $property->getValue())) {
case 'org':
// vCard 3.0 does not have an equivalent to KIND:ORG,
// but apple has an extension that means the same
@ -149,7 +149,7 @@ class VCardConverter
}
} elseif (Document::VCARD40 === $targetVersion) {
// 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;
}
@ -171,12 +171,12 @@ class VCardConverter
}
switch ($property->name) {
case 'X-ABSHOWAS':
if ('COMPANY' === strtoupper($property->getValue())) {
if ('COMPANY' === strtoupper((string) $property->getValue())) {
$newProperty = $output->createProperty('KIND', 'ORG');
}
break;
case 'X-ADDRESSBOOKSERVER-KIND':
if ('GROUP' === strtoupper($property->getValue())) {
if ('GROUP' === strtoupper((string) $property->getValue())) {
$newProperty = $output->createProperty('KIND', 'GROUP');
}
break;
@ -274,10 +274,11 @@ class VCardConverter
$newTypes = [];
foreach ($parameters['TYPE']->getParts() as $typePart) {
if (in_array(
strtoupper($typePart),
['JPEG', 'PNG', 'GIF']
strtoupper((string) $typePart),
['JPEG', 'PNG', 'GIF'],
true
)) {
$mimeType = 'image/'.strtolower($typePart);
$mimeType = 'image/'.strtolower((string) $typePart);
} else {
$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;
}
@ -313,7 +314,7 @@ class VCardConverter
$value = $newProperty->getValue();
// Only converting data: uris
if ('data:' !== substr($value, 0, 5)) {
if (!str_starts_with((string) $value, 'data:')) {
return $newProperty;
}
@ -325,12 +326,12 @@ class VCardConverter
'BINARY'
);
$mimeType = substr($value, 5, strpos($value, ',') - 5);
$mimeType = substr((string) $value, 5, strpos((string) $value, ',') - 5);
if (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 {
$newProperty->setValue(substr($value, strpos($value, ',') + 1));
$newProperty->setValue(substr((string) $value, strpos((string) $value, ',') + 1));
}
unset($value);
@ -367,7 +368,7 @@ class VCardConverter
// that's now PREF=1.
case 'TYPE':
foreach ($param->getParts() as $paramPart) {
if ('PREF' === strtoupper($paramPart)) {
if ('PREF' === strtoupper((string) $paramPart)) {
$newProperty->add('PREF', '1');
} else {
$newProperty->add($param->name, $paramPart);
@ -402,7 +403,7 @@ class VCardConverter
case 'ENCODING':
// This value only existed in vCard 2.1, and should be
// removed for anything else.
if ('QUOTED-PRINTABLE' !== strtoupper($param->getValue())) {
if ('QUOTED-PRINTABLE' !== strtoupper((string) $param->getValue())) {
$newProperty->add($param->name, $param->getParts());
}
break;
@ -413,7 +414,7 @@ class VCardConverter
* Any other PREF numbers we'll drop.
*/
case 'PREF':
if ('1' == $param->getValue()) {
if ('1' === (string) $param->getValue()) {
$newProperty->add('TYPE', 'PREF');
}
break;

View file

@ -14,5 +14,5 @@ class Version
/**
* Full version number.
*/
public const VERSION = '4.5.2';
public const VERSION = '5.0.0';
}

View file

@ -17,6 +17,7 @@ return [
'Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London' => 'Europe/Lisbon',
'Greenwich Mean Time; Dublin, Edinburgh, London' => 'Europe/London',
'Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna' => 'Europe/Berlin',
'Amsterdam, Berlin, Bern, Rom, Stockholm, Wien' => 'Europe/Berlin',
'Belgrade, Pozsony, Budapest, Ljubljana, Prague' => 'Europe/Prague',
'Brussels, Copenhagen, Madrid, Paris' => 'Europe/Paris',
'Paris, Madrid, Brussels, Copenhagen' => 'Europe/Paris',
@ -39,12 +40,12 @@ return [
'Kabul' => 'Asia/Kabul',
'Ekaterinburg' => 'Asia/Yekaterinburg',
'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',
'Almaty, Novosibirsk, North Central Asia' => 'Asia/Almaty',
'Astana, Dhaka' => 'Asia/Dhaka',
'Sri Jayawardenepura, Sri Lanka' => 'Asia/Colombo',
'Rangoon' => 'Asia/Rangoon',
'Rangoon' => 'Asia/Yangon',
'Bangkok, Hanoi, Jakarta' => 'Asia/Bangkok',
'Krasnoyarsk' => 'Asia/Krasnoyarsk',
'Beijing, Chongqing, Hong Kong SAR, Urumqi' => 'Asia/Shanghai',
@ -71,7 +72,7 @@ return [
'Mid-Atlantic' => 'America/Noronha',
'Brasilia' => 'America/Sao_Paulo', // Best guess
'Buenos Aires' => 'America/Argentina/Buenos_Aires',
'Greenland' => 'America/Godthab',
'Greenland' => 'America/Nuuk',
'Newfoundland' => 'America/St_Johns',
'Atlantic Time (Canada)' => 'America/Halifax',
'Caracas, La Paz' => 'America/Caracas',

View file

@ -34,7 +34,7 @@ return [
'Newfoundland' => 'America/St_Johns',
'Argentina' => 'America/Argentina/Buenos_Aires',
'E. South America' => 'America/Belem',
'Greenland' => 'America/Godthab',
'Greenland' => 'America/Nuuk',
'Montevideo' => 'America/Montevideo',
'SA Eastern' => 'America/Belem',
// 'Mid-Atlantic' => 'Etc/GMT-2', // conflict with windows timezones.
@ -71,12 +71,12 @@ return [
'Ekaterinburg' => 'Asia/Yekaterinburg',
'Pakistan' => 'Asia/Karachi',
'West Asia' => 'Asia/Tashkent',
'India' => 'Asia/Calcutta',
'India' => 'Asia/Kolkata',
'Sri Lanka' => 'Asia/Colombo',
'Nepal' => 'Asia/Kathmandu',
'Central Asia' => 'Asia/Dhaka',
'N. Central Asia' => 'Asia/Almaty',
'Myanmar' => 'Asia/Rangoon',
'Myanmar' => 'Asia/Yangon',
'North Asia' => 'Asia/Krasnoyarsk',
'SE Asia' => 'Asia/Bangkok',
'China' => 'Asia/Shanghai',

View file

@ -20,7 +20,7 @@ return [
'Arab Standard Time' => 'Asia/Riyadh',
'Arabian Standard Time' => 'Asia/Dubai',
'Arabic Standard Time' => 'Asia/Baghdad',
'Argentina Standard Time' => 'America/Buenos_Aires',
'Argentina Standard Time' => 'America/Argentina/Buenos_Aires',
'Astrakhan Standard Time' => 'Europe/Astrakhan',
'Atlantic Standard Time' => 'America/Halifax',
'Aus Central W. Standard Time' => 'Australia/Eucla',
@ -55,16 +55,16 @@ return [
'Eastern Standard Time (Mexico)' => 'America/Cancun',
'Egypt Standard Time' => 'Africa/Cairo',
'Ekaterinburg Standard Time' => 'Asia/Yekaterinburg',
'FLE Standard Time' => 'Europe/Kiev',
'FLE Standard Time' => 'Europe/Kyiv',
'Fiji Standard Time' => 'Pacific/Fiji',
'GMT Standard Time' => 'Europe/London',
'GTB Standard Time' => 'Europe/Bucharest',
'Georgian Standard Time' => 'Asia/Tbilisi',
'Greenland Standard Time' => 'America/Godthab',
'Greenland Standard Time' => 'America/Nuuk',
'Greenwich Standard Time' => 'Atlantic/Reykjavik',
'Haiti Standard Time' => 'America/Port-au-Prince',
'Hawaiian Standard Time' => 'Pacific/Honolulu',
'India Standard Time' => 'Asia/Calcutta',
'India Standard Time' => 'Asia/Kolkata',
'Iran Standard Time' => 'Asia/Tehran',
'Israel Standard Time' => 'Asia/Jerusalem',
'Jordan Standard Time' => 'Asia/Amman',
@ -82,10 +82,10 @@ return [
'Morocco Standard Time' => 'Africa/Casablanca',
'Mountain Standard Time' => 'America/Denver',
'Mountain Standard Time (Mexico)' => 'America/Chihuahua',
'Myanmar Standard Time' => 'Asia/Rangoon',
'Myanmar Standard Time' => 'Asia/Yangon',
'N. Central Asia Standard Time' => 'Asia/Novosibirsk',
'Namibia Standard Time' => 'Africa/Windhoek',
'Nepal Standard Time' => 'Asia/Katmandu',
'Nepal Standard Time' => 'Asia/Kathmandu',
'New Zealand Standard Time' => 'Pacific/Auckland',
'Newfoundland Standard Time' => 'America/St_Johns',
'Norfolk Standard Time' => 'Pacific/Norfolk',
@ -127,7 +127,7 @@ return [
'Transbaikal Standard Time' => 'Asia/Chita',
'Turkey Standard Time' => 'Europe/Istanbul',
'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',
'UTC' => 'Etc/GMT',
'UTC+12' => 'Etc/GMT-12',

View file

@ -212,11 +212,13 @@ function valueObject(Reader $reader, string $className, string $namespace): obje
$reader->read();
do {
if (Reader::ELEMENT === $reader->nodeType && $reader->namespaceURI == $namespace) {
if (Reader::ELEMENT === $reader->nodeType && $reader->namespaceURI === $namespace) {
if (property_exists($valueObject, $reader->localName)) {
if (is_array($defaultProperties[$reader->localName])) {
// @phpstan-ignore property.dynamicName
$valueObject->{$reader->localName}[] = $reader->parseCurrentElement()['value'];
} else {
// @phpstan-ignore property.dynamicName
$valueObject->{$reader->localName} = $reader->parseCurrentElement()['value'];
}
} else {

View file

@ -19,19 +19,13 @@ use Sabre\Xml;
*/
class Base implements Xml\Element
{
/**
* @var mixed PHP value to serialize
*/
protected $value;
/**
* Constructor.
*
* @param mixed $value PHP value to serialize
*/
public function __construct($value = null)
public function __construct(protected $value = null)
{
$this->value = $value;
}
/**

View file

@ -21,17 +21,15 @@ use Sabre\Xml;
*/
class Cdata implements Xml\XmlSerializable
{
/**
* CDATA element value.
*/
protected string $value;
/**
* Constructor.
*/
public function __construct(string $value)
{
$this->value = $value;
public function __construct(
/**
* CDATA element value.
*/
protected string $value,
) {
}
/**
@ -52,6 +50,6 @@ class Cdata implements Xml\XmlSerializable
*/
public function xmlSerialize(Xml\Writer $writer): void
{
$writer->writeCData($this->value);
$writer->writeCdata($this->value);
}
}

View file

@ -37,21 +37,17 @@ use Sabre\Xml\Serializer;
*/
class Elements implements Xml\Element
{
/**
* Value to serialize.
*
* @var array<int, mixed>
*/
protected array $value;
/**
* Constructor.
*
* @param array<int, mixed> $value
*/
public function __construct(array $value = [])
{
$this->value = $value;
public function __construct(
/**
* Value to serialize.
*/
protected array $value = [],
) {
}
/**

View file

@ -37,21 +37,17 @@ use Sabre\Xml\Deserializer;
*/
class KeyValue implements Xml\Element
{
/**
* Value to serialize.
*
* @var array<string, mixed>
*/
protected array $value;
/**
* Constructor.
*
* @param array<string, mixed> $value
*/
public function __construct(array $value = [])
{
$this->value = $value;
public function __construct(
/**
* Value to serialize.
*/
protected array $value = [],
) {
}
/**

View file

@ -26,17 +26,15 @@ use function Sabre\Uri\resolve;
*/
class Uri implements Xml\Element
{
/**
* Uri element value.
*/
protected string $value;
/**
* Constructor.
*/
public function __construct(string $value)
{
$this->value = $value;
public function __construct(
/**
* Uri element value.
*/
protected string $value,
) {
}
/**

View file

@ -24,17 +24,15 @@ use Sabre\Xml\Writer;
*/
class XmlFragment implements Element
{
/**
* The inner XML value.
*/
protected string $xml;
/**
* Constructor.
*/
public function __construct(string $xml)
{
$this->xml = $xml;
public function __construct(
/**
* 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;
$reader->xml($xml);
$reader->XML($xml);
while ($reader->read()) {
if ($reader->depth < 1) {

View file

@ -17,13 +17,6 @@ use LibXMLError;
*/
class LibXMLException extends ParseException
{
/**
* The error list.
*
* @var \LibXMLError[]
*/
protected array $errors;
/**
* Creates the exception.
*
@ -31,10 +24,12 @@ class LibXMLException extends ParseException
*
* @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($errors[0]->message.' on line '.$errors[0]->line.', column '.$errors[0]->column, $code, $previousException);
parent::__construct($this->errors[0]->message.' on line '.$this->errors[0]->line.', column '.$this->errors[0]->column, $code, $previousException);
}
/**

View file

@ -33,7 +33,7 @@ class Reader extends \XMLReader
*/
public function getClark(): ?string
{
if (!$this->localName) {
if ('' === $this->localName) {
return null;
}
@ -67,7 +67,7 @@ class Reader extends \XMLReader
if (!$this->read()) {
$errors = libxml_get_errors();
libxml_clear_errors();
if ($errors) {
if (count($errors) > 0) {
throw new LibXMLException($errors);
}
}
@ -77,7 +77,7 @@ class Reader extends \XMLReader
// last line of defense in case errors did occur above
$errors = libxml_get_errors();
libxml_clear_errors();
if ($errors) {
if (count($errors) > 0) {
throw new LibXMLException($errors);
}
} finally {
@ -153,7 +153,7 @@ class Reader extends \XMLReader
if (!$this->read()) {
$errors = libxml_get_errors();
libxml_clear_errors();
if ($errors) {
if (count($errors) > 0) {
throw new LibXMLException($errors);
}
throw new ParseException('This should never happen (famous last words)');
@ -165,7 +165,7 @@ class Reader extends \XMLReader
if (!$this->isValid()) {
$errors = libxml_get_errors();
if ($errors) {
if (count($errors) > 0) {
libxml_clear_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 = '';
$previousDepth = $this->depth;
while ($this->read() && $this->depth != $previousDepth) {
if (in_array($this->nodeType, [\XMLReader::TEXT, \XMLReader::CDATA, \XMLReader::WHITESPACE])) {
while ($this->read() && $this->depth !== $previousDepth) {
if (in_array($this->nodeType, [\XMLReader::TEXT, \XMLReader::CDATA, \XMLReader::WHITESPACE], true)) {
$result .= $this->value;
}
}
@ -266,7 +266,7 @@ class Reader extends \XMLReader
$attributes = [];
while ($this->moveToNextAttribute()) {
if ($this->namespaceURI) {
if ('' !== $this->namespaceURI) {
// Ignoring 'xmlns', it doesn't make any sense.
if ('http://www.w3.org/2000/xmlns/' === $this->namespaceURI) {
continue;
@ -290,10 +290,10 @@ class Reader extends \XMLReader
public function getDeserializerForElementName(string $name): callable
{
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);
} else {
return ['Sabre\\Xml\\Element\\Base', 'xmlDeserialize'];
return Element\Base::xmlDeserialize(...);
}
}
@ -302,8 +302,8 @@ class Reader extends \XMLReader
return $deserializer;
}
if (is_subclass_of($deserializer, 'Sabre\\Xml\\XmlDeserializable')) {
return [$deserializer, 'xmlDeserialize'];
if (is_subclass_of($deserializer, XmlDeserializable::class)) {
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);

View file

@ -123,7 +123,7 @@ function valueObject(Writer $writer, object $valueObject, string $namespace): vo
*
* 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
{
@ -133,9 +133,9 @@ function standardSerializer(Writer $writer, $value): void
} elseif ($value instanceof XmlSerializable) {
// XmlSerializable classes or Element classes.
$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.
$writer->classMap[get_class($value)]($writer, $value);
$writer->classMap[$value::class]($writer, $value);
} elseif (is_callable($value)) {
// A callback
$value($writer);
@ -144,8 +144,8 @@ function standardSerializer(Writer $writer, $value): void
// describes a 'name' and optionally 'attributes' and 'value'.
$name = $value['name'];
$attributes = isset($value['attributes']) ? $value['attributes'] : [];
$value = isset($value['value']) ? $value['value'] : null;
$attributes = $value['attributes'] ?? [];
$value = $value['value'] ?? null;
$writer->startElement($name);
$writer->writeAttributes($attributes);
@ -157,7 +157,7 @@ function standardSerializer(Writer $writer, $value): void
// This item has a numeric index. We just loop through the
// array and throw it back in the writer.
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
// possibly 'value'
$writer->startElement($name);
@ -176,7 +176,7 @@ function standardSerializer(Writer $writer, $value): void
}
}
} 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)) {
throw new \InvalidArgumentException('The writer cannot serialize values of type: '.get_debug_type($value));
}

View file

@ -123,7 +123,7 @@ class Service
}
// 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');
}
@ -174,7 +174,7 @@ class Service
}
// 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');
}
@ -254,14 +254,12 @@ class Service
*/
public function mapValueObject(string $elementName, string $className): void
{
list($namespace) = self::parseClarkNotation($elementName);
[$namespace] = self::parseClarkNotation($elementName);
require_once __DIR__ . '/Deserializer/functions.php';
$this->elementMap[$elementName] = function (Reader $reader) use ($className, $namespace) {
return \Sabre\Xml\Deserializer\valueObject($reader, $className, $namespace);
};
$this->elementMap[$elementName] = (fn (Reader $reader) => \Sabre\Xml\Deserializer\valueObject($reader, $className, $namespace));
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);
};
$this->valueObjectMap[$className] = $elementName;
@ -280,12 +278,12 @@ class Service
*/
public function writeValueObject(object $object, ?string $contextUri = null): string
{
if (!isset($this->valueObjectMap[get_class($object)])) {
throw new \InvalidArgumentException('"'.get_class($object).'" is not a registered value object class. Register your class with mapValueObject.');
if (!isset($this->valueObjectMap[$object::class])) {
throw new \InvalidArgumentException('"'.$object::class.'" is not a registered value object class. Register your class with mapValueObject.');
}
return $this->write(
$this->valueObjectMap[get_class($object)],
$this->valueObjectMap[$object::class],
$object,
$contextUri
);
@ -306,7 +304,7 @@ class Service
static $cache = [];
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');
}

View file

@ -16,5 +16,5 @@ class Version
/**
* Full version number.
*/
public const VERSION = '4.0.6';
public const VERSION = '4.1.0';
}

View file

@ -123,11 +123,11 @@ class Writer extends \XMLWriter
public function startElement($name): bool
{
if ('{' === $name[0]) {
list($namespace, $localName) =
[$namespace, $localName] =
Service::parseClarkNotation($name);
if (array_key_exists($namespace, $this->namespaceMap)) {
$result = $this->startElementNS(
$result = $this->startElementNs(
'' === $this->namespaceMap[$namespace] ? null : $this->namespaceMap[$namespace],
$localName,
null
@ -139,10 +139,8 @@ class Writer extends \XMLWriter
$result = $this->startElement($localName);
$this->writeAttribute('xmlns', '');
} else {
if (!isset($this->adhocNamespaces[$namespace])) {
$this->adhocNamespaces[$namespace] = 'x'.(count($this->adhocNamespaces) + 1);
}
$result = $this->startElementNS($this->adhocNamespaces[$namespace], $localName, $namespace);
$this->adhocNamespaces[$namespace] ??= 'x'.(count($this->adhocNamespaces) + 1);
$result = $this->startElementNs($this->adhocNamespaces[$namespace], $localName, $namespace);
}
}
} else {
@ -151,7 +149,7 @@ class Writer extends \XMLWriter
if (!$this->namespacesWritten) {
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;
}
@ -248,11 +246,9 @@ class Writer extends \XMLWriter
}
// 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],
$localName,
$namespace,

View 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.

View 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):
![Català in the native selector](selector.png)

View 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.

View 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"
}
}

View 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"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View 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."
}

View 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"
}
}

View file

@ -1,6 +1,7 @@
{
"LANGS_NAMES": {
"id": "Bahasa Indonesia",
"ca": "Català",
"cs": "Čeština",
"da": "Dansk",
"de": "Deutsch",
@ -239,4 +240,4 @@
"za": "Zhuang",
"zu": "Zulu"
}
}
}