mirror of
https://git.friendi.ca/friendica/friendica.git
synced 2025-06-08 06:56:29 +02:00
Merge branch 'develop' into poc-addonmanager
This commit is contained in:
commit
6fe286e920
14 changed files with 219 additions and 42 deletions
|
@ -41,7 +41,7 @@ steps:
|
|||
volumes:
|
||||
- /tmp/drone-cache:/tmp/cache
|
||||
composer_install:
|
||||
image: friendicaci/php8.2:php8.2.16
|
||||
image: friendicaci/php8.2:php8.2.28
|
||||
commands:
|
||||
- mkdir addon # create empty addon folder to appease composer
|
||||
- export COMPOSER_HOME=.composer
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
matrix:
|
||||
include:
|
||||
- PHP_MAJOR_VERSION: 8.2
|
||||
PHP_VERSION: 8.2.16
|
||||
PHP_VERSION: 8.2.28
|
||||
|
||||
when:
|
||||
branch:
|
||||
|
|
|
@ -8,6 +8,10 @@ when:
|
|||
exclude: [ stable ]
|
||||
event: [ pull_request, push ]
|
||||
|
||||
# This forces PHP Unit executions at the "opensocial" labeled location (because of access issues with git.friendi.ca)
|
||||
labels:
|
||||
location: opensocial
|
||||
|
||||
steps:
|
||||
restore_cache:
|
||||
image: meltwater/drone-cache:dev
|
||||
|
@ -22,7 +26,7 @@ steps:
|
|||
- /tmp/drone-cache:/tmp/cache
|
||||
|
||||
composer_install:
|
||||
image: friendicaci/php8.3:php8.3.3
|
||||
image: friendicaci/php8.3:php8.3.17
|
||||
commands:
|
||||
- mkdir addon # create empty addon folder to appease composer
|
||||
- export COMPOSER_HOME=.composer
|
||||
|
@ -41,6 +45,6 @@ steps:
|
|||
- /tmp/drone-cache:/tmp/cache
|
||||
|
||||
phpmd:
|
||||
image: friendicaci/php8.3:php8.3.3
|
||||
image: friendicaci/php8.3:php8.3.17
|
||||
commands:
|
||||
- ./bin/composer.phar run phpmd
|
||||
|
|
|
@ -9,11 +9,13 @@ matrix:
|
|||
- PHP_MAJOR_VERSION: 8.0
|
||||
PHP_VERSION: 8.0.30
|
||||
- PHP_MAJOR_VERSION: 8.1
|
||||
PHP_VERSION: 8.1.27
|
||||
PHP_VERSION: 8.1.31
|
||||
- PHP_MAJOR_VERSION: 8.2
|
||||
PHP_VERSION: 8.2.16
|
||||
PHP_VERSION: 8.2.28
|
||||
- PHP_MAJOR_VERSION: 8.3
|
||||
PHP_VERSION: 8.3.3
|
||||
PHP_VERSION: 8.3.17
|
||||
- PHP_MAJOR_VERSION: 8.4
|
||||
PHP_VERSION: 8.4.5
|
||||
|
||||
# This forces PHP Unit executions at the "opensocial" labeled location (because of much more power...)
|
||||
labels:
|
||||
|
@ -24,7 +26,19 @@ when:
|
|||
exclude: [ stable ]
|
||||
event: [ pull_request, push ]
|
||||
|
||||
skip_clone: true
|
||||
|
||||
steps:
|
||||
clone:
|
||||
image: alpine/git
|
||||
commands:
|
||||
- git config --global user.email "no-reply@friendi.ca"
|
||||
- git config --global user.name "Friendica"
|
||||
- git config --global --add safe.directory $CI_WORKSPACE
|
||||
- git clone $CI_REPO_CLONE_URL .
|
||||
- git checkout $CI_COMMIT_BRANCH
|
||||
- git fetch origin $CI_COMMIT_REF
|
||||
- git merge $CI_COMMIT_SHA
|
||||
php-lint:
|
||||
image: php:${PHP_MAJOR_VERSION}
|
||||
commands:
|
||||
|
@ -94,7 +108,7 @@ steps:
|
|||
when:
|
||||
matrix:
|
||||
PHP_MAJOR_VERSION: 8.2
|
||||
PHP_VERSION: 8.2.16
|
||||
PHP_VERSION: 8.2.28
|
||||
repo:
|
||||
- friendica/friendica
|
||||
commands:
|
||||
|
|
|
@ -35,7 +35,7 @@ steps:
|
|||
volumes:
|
||||
- /tmp/drone-cache:/tmp/cache
|
||||
composer_install:
|
||||
image: friendicaci/php8.2:php8.2.16
|
||||
image: friendicaci/php8.2:php8.2.28
|
||||
commands:
|
||||
- mkdir addon # create empty addon folder to appease composer
|
||||
- export COMPOSER_HOME=.composer
|
||||
|
|
|
@ -208,7 +208,7 @@ class ErrorHandler
|
|||
*/
|
||||
private function handleException(Throwable $e): void
|
||||
{
|
||||
$level = LogLevel::ERROR;
|
||||
$level = LogLevel::CRITICAL;
|
||||
foreach ($this->uncaughtExceptionLevelMap as $class => $candidate) {
|
||||
if ($e instanceof $class) {
|
||||
$level = $candidate;
|
||||
|
|
|
@ -1090,9 +1090,9 @@ class Item
|
|||
}
|
||||
|
||||
if ($update_commented) {
|
||||
$fields = ['commented' => DateTimeFormat::utcNow(), 'changed' => DateTimeFormat::utcNow()];
|
||||
$fields = ['commented' => $posted_item['received'], 'changed' => $posted_item['received']];
|
||||
} else {
|
||||
$fields = ['changed' => DateTimeFormat::utcNow()];
|
||||
$fields = ['changed' => $posted_item['received']];
|
||||
}
|
||||
|
||||
Post::update($fields, ['uri-id' => $posted_item['parent-uri-id'], 'uid' => $posted_item['uid']]);
|
||||
|
|
|
@ -205,13 +205,25 @@ final class ItemHelper
|
|||
$item['file'] = trim($item['file'] ?? '');
|
||||
|
||||
// Items cannot be stored before they happen ...
|
||||
if ($item['created'] > DateTimeFormat::utcNow()) {
|
||||
$item['created'] = DateTimeFormat::utcNow();
|
||||
if ($item['received'] > DateTimeFormat::utcNow()) {
|
||||
$item['received'] = DateTimeFormat::utcNow();
|
||||
}
|
||||
|
||||
if ($item['created'] > $item['received']) {
|
||||
$item['created'] = $item['received'];
|
||||
}
|
||||
|
||||
// We haven't invented time travel by now.
|
||||
if ($item['edited'] > DateTimeFormat::utcNow()) {
|
||||
$item['edited'] = DateTimeFormat::utcNow();
|
||||
if ($item['edited'] > $item['received'] ) {
|
||||
$item['edited'] = $item['received'] ;
|
||||
}
|
||||
|
||||
if ($item['changed'] > $item['received'] ) {
|
||||
$item['changed'] = $item['received'] ;
|
||||
}
|
||||
|
||||
if ($item['commented'] > $item['received'] ) {
|
||||
$item['commented'] = $item['received'] ;
|
||||
}
|
||||
|
||||
$item['plink'] = ($item['plink'] ?? '') ?: $this->baseUrl . '/display/' . urlencode($item['guid']);
|
||||
|
|
|
@ -130,6 +130,7 @@ class Photo extends BaseApi
|
|||
|
||||
$photo = MPhoto::getPhoto($photoid, $scale, self::getCurrentUserID());
|
||||
if ($photo === false) {
|
||||
$this->logger->notice('Photo was not loaded', ['parameters' => $this->parameters, 'id' => $photoid]);
|
||||
throw new HTTPException\NotFoundException(DI::l10n()->t('The Photo with id %s is not available.', $photoid));
|
||||
}
|
||||
}
|
||||
|
@ -137,6 +138,7 @@ class Photo extends BaseApi
|
|||
$fetch = microtime(true) - $stamp;
|
||||
|
||||
if ($photo === false) {
|
||||
$this->logger->notice('Photo was not loaded', ['parameters' => $this->parameters]);
|
||||
throw new HTTPException\NotFoundException();
|
||||
}
|
||||
|
||||
|
@ -151,6 +153,7 @@ class Photo extends BaseApi
|
|||
$mimetype = $photo['type'];
|
||||
}
|
||||
if (empty($imgdata) && empty($photo['blurhash'])) {
|
||||
$this->logger->notice('Image data was not loaded', ['parameters' => $this->parameters, 'class' => $photo['backend-class'], 'ref' => $photo['backend-ref']]);
|
||||
throw new HTTPException\NotFoundException();
|
||||
}
|
||||
|
||||
|
@ -317,7 +320,11 @@ class Photo extends BaseApi
|
|||
$photo = MPhoto::selectFirst([], ['scale' => $scale, 'uid' => $contact['uid'], 'profile' => 1]);
|
||||
if (!empty($photo)) {
|
||||
return $photo;
|
||||
} else {
|
||||
$this->logger->notice('Profile photo was not loaded', ['scale' => $scale, 'uid' => $contact['uid']]);
|
||||
}
|
||||
} else {
|
||||
$this->logger->notice('Local Contact was not found', ['url' => $contact['nurl']]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -333,6 +340,7 @@ class Photo extends BaseApi
|
|||
if (!empty($photo)) {
|
||||
return $photo;
|
||||
} else {
|
||||
$this->logger->notice('Photo was not loaded', ['resource-id' => $resourceid]);
|
||||
$url = $contact['avatar'];
|
||||
}
|
||||
} else {
|
||||
|
@ -340,6 +348,8 @@ class Photo extends BaseApi
|
|||
}
|
||||
} elseif (!empty($contact['avatar'])) {
|
||||
$url = $contact['avatar'];
|
||||
} else {
|
||||
$url = '';
|
||||
}
|
||||
|
||||
// If it is a local link, we save resources by just redirecting to it.
|
||||
|
@ -381,8 +391,6 @@ class Photo extends BaseApi
|
|||
}
|
||||
}
|
||||
|
||||
$url = '';
|
||||
|
||||
if (empty($mimetext) && !empty($contact['blurhash'])) {
|
||||
$image = new Image('', image_type_to_mime_type(IMAGETYPE_WEBP));
|
||||
$image->getFromBlurHash($contact['blurhash'], $customsize, $customsize);
|
||||
|
|
|
@ -117,7 +117,7 @@ class DateTimeFormat
|
|||
$tz_to = 'UTC';
|
||||
}
|
||||
|
||||
if (($s === '') || (!is_string($s))) {
|
||||
if ($s === '') {
|
||||
$s = 'now';
|
||||
}
|
||||
|
||||
|
@ -135,7 +135,8 @@ class DateTimeFormat
|
|||
}
|
||||
|
||||
try {
|
||||
$d = new DateTime($s, $from_obj);
|
||||
$d = DateTime::createFromFormat('U', $s, $from_obj)
|
||||
?: new DateTime($s, $from_obj);
|
||||
} catch (Exception $e) {
|
||||
try {
|
||||
$d = new DateTime(self::fix($s), $from_obj);
|
||||
|
@ -176,6 +177,7 @@ class DateTimeFormat
|
|||
$pregPatterns = [
|
||||
['#(\w+), (\d+ \w+ \d+) (\d+:\d+:\d+) (.+)#', '$2 $3 $4'],
|
||||
['#(\d+:\d+) (\w+), (\w+) (\d+), (\d+)#', '$1 $2 $3 $4 $5'],
|
||||
['#\[[^\]]*\]#', ''], // 2025-03-07T08:54:14.341+01:00[Europe/Berlin]
|
||||
];
|
||||
|
||||
foreach ($pregPatterns as $pattern) {
|
||||
|
|
|
@ -121,6 +121,9 @@ class Cron
|
|||
|
||||
Worker::add(Worker::PRIORITY_LOW, 'UpdateAllSuggestions');
|
||||
|
||||
// add missing public contacts and account-user entries
|
||||
Worker::add(Worker::PRIORITY_LOW, 'FixContacts');
|
||||
|
||||
if (DI::config()->get('system', 'optimize_tables')) {
|
||||
Worker::add(Worker::PRIORITY_LOW, 'OptimizeTables');
|
||||
}
|
||||
|
|
51
src/Worker/FixContacts.php
Normal file
51
src/Worker/FixContacts.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
// Copyright (C) 2010-2024, the Friendica project
|
||||
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
namespace Friendica\Worker;
|
||||
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
|
||||
/**
|
||||
* add missing public contacts and account-user entries
|
||||
*/
|
||||
class FixContacts
|
||||
{
|
||||
public static function execute()
|
||||
{
|
||||
$added = 0;
|
||||
DI::logger()->info('Add missing public contacts');
|
||||
$contacts = DBA::p("SELECT `contact`.`id` FROM `contact` LEFT JOIN `contact` AS `pcontact` ON `contact`.`uri-id` = `pcontact`.`uri-id` WHERE `pcontact`.`id` IS NULL");
|
||||
while ($contact = DBA::fetch($contacts)) {
|
||||
Contact::selectAccountUserById($contact['id'], ['id']);
|
||||
$added++;
|
||||
}
|
||||
DBA::close($contacts);
|
||||
|
||||
if ($added == 0) {
|
||||
DI::logger()->info('No public contacts have been added');
|
||||
} else {
|
||||
DI::logger()->info('Missing public contacts have been added', ['added' => $added]);
|
||||
}
|
||||
|
||||
$added = 0;
|
||||
DI::logger()->info('Add missing account-user entries');
|
||||
$contacts = DBA::p("SELECT `contact`.`id`, `contact`.`uid`, `contact`.`uri-id`, `contact`.`url` FROM `contact` LEFT JOIN `account-user` ON `contact`.`id` = `account-user`.`id` WHERE `contact`.`id` > ? AND `account-user`.`id` IS NULL", 0);
|
||||
while ($contact = DBA::fetch($contacts)) {
|
||||
Contact::setAccountUser($contact['id'], $contact['uid'], $contact['uri-id'], $contact['url']);
|
||||
$added++;
|
||||
}
|
||||
DBA::close($contacts);
|
||||
|
||||
if ($added == 0) {
|
||||
DI::logger()->info('No account-user entries have been added');
|
||||
} else {
|
||||
DI::logger()->info('Missing account-user entries have been added', ['added' => $added]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ if (!file_exists(__DIR__ . '/../vendor/autoload.php')) {
|
|||
die('Vendor path not found. Please execute "bin/composer.phar install" on the command line in the web root.');
|
||||
}
|
||||
|
||||
error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED);
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
// Backward compatibility
|
||||
|
|
|
@ -16,39 +16,39 @@ class DateTimeFormatTest extends MockedTestCase
|
|||
{
|
||||
return [
|
||||
'validNormal' => [
|
||||
'input' => '1990-10',
|
||||
'input' => '1990-10',
|
||||
'assert' => true,
|
||||
],
|
||||
'validOneCharMonth' => [
|
||||
'input' => '1990-1',
|
||||
'input' => '1990-1',
|
||||
'assert' => true,
|
||||
],
|
||||
'validTwoCharMonth' => [
|
||||
'input' => '1990-01',
|
||||
'input' => '1990-01',
|
||||
'assert' => true,
|
||||
],
|
||||
'invalidFormat' => [
|
||||
'input' => '199-11',
|
||||
'input' => '199-11',
|
||||
'assert' => false,
|
||||
],
|
||||
'invalidFormat2' => [
|
||||
'input' => '1990-15',
|
||||
'input' => '1990-15',
|
||||
'assert' => false,
|
||||
],
|
||||
'invalidFormat3' => [
|
||||
'input' => '99-101',
|
||||
'input' => '99-101',
|
||||
'assert' => false,
|
||||
],
|
||||
'invalidFormat4' => [
|
||||
'input' => '11-1990',
|
||||
'input' => '11-1990',
|
||||
'assert' => false,
|
||||
],
|
||||
'invalidFuture' => [
|
||||
'input' => '3030-12',
|
||||
'input' => '3030-12',
|
||||
'assert' => false,
|
||||
],
|
||||
'invalidYear' => [
|
||||
'input' => '-100-10',
|
||||
'input' => '-100-10',
|
||||
'assert' => false,
|
||||
],
|
||||
];
|
||||
|
@ -79,51 +79,55 @@ class DateTimeFormatTest extends MockedTestCase
|
|||
return [
|
||||
'Mo, 19 Sep 2022 14:51:00 +0200' => [
|
||||
'expectedDate' => '2022-09-19T14:51:00+02:00',
|
||||
'dateString' => 'Mo, 19 Sep 2022 14:51:00 +0200',
|
||||
'dateString' => 'Mo, 19 Sep 2022 14:51:00 +0200',
|
||||
],
|
||||
'2020-11-21T12:00:13.745339ZZ' => [
|
||||
'expectedDate' => '2020-11-21T12:00:13+00:00',
|
||||
'dateString' => '2020-11-21T12:00:13.745339ZZ',
|
||||
'dateString' => '2020-11-21T12:00:13.745339ZZ',
|
||||
],
|
||||
'2016-09-09T13:32:00ZZ' => [
|
||||
'expectedDate' => '2016-09-09T13:32:00+00:00',
|
||||
'dateString' => '2016-09-09T13:32:00ZZ',
|
||||
'dateString' => '2016-09-09T13:32:00ZZ',
|
||||
],
|
||||
'Sun, 10/03/2021 - 12:41' => [
|
||||
'expectedDate' => '2021-10-03T12:41:00+00:00',
|
||||
'dateString' => 'Sun, 10/03/2021 - 12:41',
|
||||
'dateString' => 'Sun, 10/03/2021 - 12:41',
|
||||
],
|
||||
'4:30 PM, Sep 13, 2022' => [
|
||||
'expectedDate' => '2022-09-13T16:30:00+00:00',
|
||||
'dateString' => '4:30 PM, Sep 13, 2022',
|
||||
'dateString' => '4:30 PM, Sep 13, 2022',
|
||||
],
|
||||
'August 27, 2022 - 21:00' => [
|
||||
'expectedDate' => '2022-08-27T21:00:00+00:00',
|
||||
'dateString' => 'August 27, 2022 - 21:00',
|
||||
'dateString' => 'August 27, 2022 - 21:00',
|
||||
],
|
||||
'2021-09-19T14:06:03+00:00' => [
|
||||
'expectedDate' => '2021-09-19T14:06:03+00:00',
|
||||
'dateString' => '2021-09-19T14:06:03+00:00',
|
||||
'dateString' => '2021-09-19T14:06:03+00:00',
|
||||
],
|
||||
'Eastern Time timezone' => [
|
||||
'expectedDate' => '2022-09-30T00:00:00-05:00',
|
||||
'dateString' => 'September 30, 2022, 12:00 a.m. ET',
|
||||
'dateString' => 'September 30, 2022, 12:00 a.m. ET',
|
||||
],
|
||||
'German date time string' => [
|
||||
'expectedDate' => '2022-10-05T16:34:00+02:00',
|
||||
'dateString' => '05 Okt 2022 16:34:00 +0200',
|
||||
'dateString' => '05 Okt 2022 16:34:00 +0200',
|
||||
],
|
||||
'(Coordinated Universal Time)' => [
|
||||
'expectedDate' => '2022-12-30T14:29:10+00:00',
|
||||
'dateString' => 'Fri Dec 30 2022 14:29:10 GMT+0000 (Coordinated Universal Time)',
|
||||
'dateString' => 'Fri Dec 30 2022 14:29:10 GMT+0000 (Coordinated Universal Time)',
|
||||
],
|
||||
'Double HTML encode' => [
|
||||
'expectedDate' => '2015-05-22T08:48:00+12:00',
|
||||
'dateString' => '2015-05-22T08:48:00&#43;12:00'
|
||||
'dateString' => '2015-05-22T08:48:00&#43;12:00'
|
||||
],
|
||||
'2023-04-02\T17:22:42+05:30' => [
|
||||
'expectedDate' => '2023-04-02T17:22:42+05:30',
|
||||
'dateString' => '2023-04-02\T17:22:42+05:30'
|
||||
'dateString' => '2023-04-02\T17:22:42+05:30'
|
||||
],
|
||||
'2025-03-07T08:54:14.341+01:00[Europe/Berlin]' => [
|
||||
'expectedDate' => '2025-03-07T08:54:14+01:00',
|
||||
'dateString' => '2025-03-07T08:54:14.341+01:00[Europe/Berlin]'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
@ -151,9 +155,87 @@ class DateTimeFormatTest extends MockedTestCase
|
|||
*/
|
||||
public function testConvertRelative()
|
||||
{
|
||||
$now = DateTimeFormat::utcNow('U');
|
||||
$now = DateTimeFormat::utcNow('U');
|
||||
$date = DateTimeFormat::utc('now - 3 days', 'U');
|
||||
|
||||
$this->assertEquals(259200, $now - $date);
|
||||
}
|
||||
|
||||
public function dataConvert()
|
||||
{
|
||||
return [
|
||||
'unix timestamp' => [
|
||||
'expected' => '2025-03-12 16:18:27',
|
||||
's' => '1741796307',
|
||||
],
|
||||
'ATOM' => [
|
||||
'expected' => '2022-06-02 15:58:35',
|
||||
's' => '2022-06-02T16:58:35+01:00',
|
||||
],
|
||||
'COOKIE' => [
|
||||
'expected' => '2022-06-02 14:58:35',
|
||||
's' => 'Thursday, 02-Jun-2022 16:58:35 Africa/Cairo',
|
||||
],
|
||||
'ISO 8601/RFC 3339' => [
|
||||
'expected' => '2022-06-02 13:58:35',
|
||||
's' => '2022-06-02T16:58:35+0300',
|
||||
],
|
||||
'RFC 822/RFC 1036' => [
|
||||
'expected' => '2022-06-02 12:58:35',
|
||||
's' => 'Thu, 02 Jun 22 16:58:35 +0400',
|
||||
],
|
||||
'RFC 850' => [
|
||||
'expected' => '2022-06-02 11:58:35',
|
||||
's' => 'Thursday, 02-Jun-22 16:58:35 Indian/Kerguelen',
|
||||
],
|
||||
'RFC 1123/RFC 2822/RSS' => [
|
||||
'expected' => '2022-06-02 10:58:35',
|
||||
's' => 'Thu, 02 Jun 2022 16:58:35 +0600',
|
||||
],
|
||||
'RFC 3339/W3C' => [
|
||||
'expected' => '2025-03-07 01:54:14',
|
||||
's' => '2025-03-07T08:54:14+07:00',
|
||||
],
|
||||
'RFC 3339 extended' => [
|
||||
'expected' => '2025-03-07 00:54:14',
|
||||
's' => '2025-03-07T08:54:14.341+08:00',
|
||||
],
|
||||
'RFC 7231' => [
|
||||
'expected' => '2022-06-02 07:58:35',
|
||||
's' => 'Thu, 02 Jun 2022 16:58:35 Asia/Tokyo',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataConvert
|
||||
*/
|
||||
public function testConvert($expected, string $s = 'now', string $tz_to = 'UTC', string $tz_from = 'UTC', string $format = DateTimeFormat::MYSQL)
|
||||
{
|
||||
$this->assertSame($expected, DateTimeFormat::convert($s, $tz_to, $tz_from, $format));
|
||||
}
|
||||
|
||||
public function dataConvertNow()
|
||||
{
|
||||
return [
|
||||
'now missing' => [
|
||||
],
|
||||
'now empty' => [
|
||||
's' => '',
|
||||
],
|
||||
'now now' => [
|
||||
's' => 'now',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataConvertNow
|
||||
*/
|
||||
public function testConvertNow(string $s = 'now', string $tz_to = 'UTC', string $tz_from = 'UTC', string $format = DateTimeFormat::MYSQL)
|
||||
{
|
||||
$this->assertSame(date(DateTimeFormat::MYSQL), DateTimeFormat::convert($s, $tz_to, $tz_from, $format));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue