1
0
Fork 0
mirror of synced 2024-06-02 02:44:47 +12:00

Merge branch 'dev' of github.com:appwrite/appwrite into feat-465-add-arm-support

This commit is contained in:
Eldad Fux 2021-02-28 23:43:59 +02:00
commit c5dd0d1542
62 changed files with 822 additions and 866 deletions

View file

@ -4,10 +4,32 @@
# Version 0.7.1 (Not Released Yet)
- Updated missing storage env vars
- Added option to for Redis authentication
- Force adding a security email on setup
## Features
- Better error logs on appwrite cretificates worker
- Added option for Redis authentication
- Force adding a security email on setup
- SMTP is now disabled by default, no dummy SMTP is included in setup
- Added a new endpoint that returns the server and SDKs latest versions numbers #941
## Upgrades
- Upgraded redis extenstion lib to version 5.3.3
- Upgraded maxmind extenstion lib to version 1.10.0
- Upgraded utopia-php/cli lib to version 0.10.0
- Upgraded matomo/device-detector lib to version 4.1.0
- Upgraded dragonmantank/cron-expression lib to version 3.1.0
- Upgraded influxdb/influxdb-php lib to version 1.15.2
- Upgraded phpmailer/phpmailer lib to version 6.3.0
- Upgraded adhocore/jwt lib to version 1.1.2
## Bug Fixes
- Updated missing storage env vars
## Security
- Fixed an XSS vulnerability in the Appwrite console
# Version 0.7.0

View file

@ -143,7 +143,6 @@ Appwrite's current structure is a combination of both [Monolithic](https://en.wi
├── docs # Docs and tutorials
│ ├── examples
│ ├── references
│ ├── sdks
│ ├── services
│ ├── specs
│ └── tutorials
@ -157,13 +156,14 @@ Appwrite's current structure is a combination of both [Monolithic](https://en.wi
│ └── Appwrite
│ ├── Auth
│ ├── Database
│ ├── Detector
│ ├── Docker
│ ├── Event
│ ├── Extend
│ ├── Migration
│ ├── Network
│ ├── OpenSSL
│ ├── Resize
│ ├── Storage
│ ├── Specification
│ ├── Task
│ ├── Template
│ ├── URL

View file

@ -14,9 +14,9 @@ RUN composer update --ignore-platform-reqs --optimize-autoloader \
FROM php:7.4-cli-alpine as step1
ENV PHP_REDIS_VERSION=5.3.0 \
ENV PHP_REDIS_VERSION=5.3.3 \
PHP_SWOOLE_VERSION=v4.5.8 \
PHP_MAXMINDDB_VERSION=v1.8.0 \
PHP_MAXMINDDB_VERSION=v1.10.0 \
PHP_XDEBUG_VERSION=sdebug_2_9-beta
RUN \
@ -90,8 +90,11 @@ ENV _APP_SERVER=swoole \
_APP_INFLUXDB_PORT=8086 \
_APP_STATSD_HOST=telegraf \
_APP_STATSD_PORT=8125 \
_APP_SMTP_HOST=smtp \
_APP_SMTP_PORT=25 \
_APP_SMTP_HOST= \
_APP_SMTP_PORT= \
_APP_SMTP_SECURE= \
_APP_SMTP_USERNAME= \
_APP_SMTP_PASSWORD= \
_APP_FUNCTIONS_TIMEOUT=900 \
_APP_FUNCTIONS_CONTAINERS=10 \
_APP_FUNCTIONS_CPUS=1 \
@ -106,9 +109,6 @@ ENV _APP_SERVER=swoole \
# 1 Day = 86400 s
_APP_MAINTENANCE_RETENTION_ABUSE=86400 \
_APP_MAINTENANCE_INTERVAL=86400
#ENV _APP_SMTP_SECURE ''
#ENV _APP_SMTP_USERNAME ''
#ENV _APP_SMTP_PASSWORD ''
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

View file

@ -81,7 +81,7 @@ docker run -it --rm ,
Once the Docker installation completes, go to http://localhost to access the Appwrite console from your browser. Please note that on non-linux native hosts, the server might take a few minutes to start after installation completes.
For advanced production and custom installation, check out our Docker [environment variables](docs/tutorials/environment-variables.md) docs. You can also use our public [docker-compose.yml](https://appwrite.io/docker-compose.yml) file to manually set up and environment.
For advanced production and custom installation, check out our Docker [environment variables](docs/tutorials/environment-variables.md) docs. You can also use our public [docker-compose.yml](https://appwrite.io/docker-compose.yml) file to manually set up an environment.
### Upgrade from an Older Version
@ -133,13 +133,13 @@ Looking for more SDKs? - Help us by contributing a pull request to our [SDK Gene
## Contributing
All code contributions - including those of people having commit access - must go through a pull request and approved by a core developer before being merged. This is to ensure proper review of all the code.
All code contributions - including those of people having commit access - must go through a pull request and be approved by a core developer before being merged. This is to ensure a proper review of all the code.
We truly ❤️ pull requests! If you wish to help, you can learn more about how you can contribute to this project in the [contribution guide](CONTRIBUTING.md).
## Security
For security issues, kindly email us at [security@appwrite.io](mailto:security@appwrite.io) instead of posting a public issue in GitHub.
For security issues, kindly email us at [security@appwrite.io](mailto:security@appwrite.io) instead of posting a public issue on GitHub.
## Follow Us

View file

@ -105,10 +105,10 @@ return [
],
[
'name' => '_APP_SYSTEM_SECURITY_EMAIL_ADDRESS',
'description' => 'This is the email address used to issue SSL certificates for custom domains or the user agent in webhooks. The default value is \'security@localhost.test\'.',
'description' => 'This is the email address used to issue SSL certificates for custom domains or the user agent in your webhooks payload.',
'introduction' => '0.7.0',
'default' => 'security@localhost.test',
'required' => false,
'default' => '',
'required' => true,
'question' => '',
],
[
@ -141,6 +141,22 @@ return [
'required' => false,
'question' => '',
],
[
'name' => '_APP_REDIS_USER',
'description' => 'Redis server user.',
'introduction' => '0.7',
'default' => '',
'required' => false,
'question' => '',
],
[
'name' => '_APP_REDIS_PASS',
'description' => 'Redis server password.',
'introduction' => '0.7',
'default' => '',
'required' => false,
'question' => '',
],
],
],
[
@ -239,17 +255,17 @@ return [
'variables' => [
[
'name' => '_APP_SMTP_HOST',
'description' => 'SMTP server host name address. Default value is: \'smtp\'. Pass an empty string to disable all mail sending from the server.',
'description' => 'SMTP server host name address. Use an empty string to disable all mail sending from the server. The default value for this variable is an empty string',
'introduction' => '',
'default' => 'smtp',
'default' => '',
'required' => false,
'question' => '',
],
[
'name' => '_APP_SMTP_PORT',
'description' => 'SMTP server TCP port. Default value is: \'25\'.',
'description' => 'SMTP server TCP port. Empty by default.',
'introduction' => '',
'default' => '25',
'default' => '',
'required' => false,
'question' => '',
],

View file

@ -1,28 +1,28 @@
<?php
use Utopia\App;
use Utopia\Exception;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\Validator\Range;
use Utopia\Validator\URL;
use Utopia\Cache\Cache;
use Utopia\Cache\Adapter\Filesystem;
use Appwrite\Resize\Resize;
use Appwrite\URL\URL as URLParse;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Validator\HexColor;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
use Utopia\App;
use Utopia\Cache\Adapter\Filesystem;
use Utopia\Cache\Cache;
use Utopia\Config\Config;
use Utopia\Exception;
use Utopia\Image\Image;
use Utopia\Validator\Boolean;
use Utopia\Validator\HexColor;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Utopia\Validator\URL;
use Utopia\Validator\WhiteList;
$avatarCallback = function ($type, $code, $width, $height, $quality, $response) {
/** @var Appwrite\Utopia\Response $response */
$code = \strtolower($code);
$type = \strtolower($type);
$set = Config::getParam('avatar-'.$type, []);
$set = Config::getParam('avatar-' . $type, []);
if (empty($set)) {
throw new Exception('Avatar set not found', 404);
@ -37,17 +37,17 @@ $avatarCallback = function ($type, $code, $width, $height, $quality, $response)
}
$output = 'png';
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
$key = \md5('/v1/avatars/:type/:code-'.$code.$width.$height.$quality.$output);
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT'; // 45 days cache
$key = \md5('/v1/avatars/:type/:code-' . $code . $width . $height . $quality . $output);
$path = $set[$code];
$type = 'png';
if (!\is_readable($path)) {
throw new Exception('File not readable in '.$path, 500);
throw new Exception('File not readable in ' . $path, 500);
}
$cache = new Cache(new Filesystem(APP_STORAGE_CACHE.'/app-0')); // Limit file number or size
$data = $cache->load($key, 60 * 60 * 24 * 30 * 3 /* 3 months */);
$cache = new Cache(new Filesystem(APP_STORAGE_CACHE . '/app-0')); // Limit file number or size
$data = $cache->load($key, 60 * 60 * 24 * 30 * 3/* 3 months */);
if ($data) {
//$output = (empty($output)) ? $type : $output;
@ -60,24 +60,23 @@ $avatarCallback = function ($type, $code, $width, $height, $quality, $response)
;
}
$resize = new Resize(\file_get_contents($path));
$image = new Image(\file_get_contents($path));
$resize->crop((int) $width, (int) $height);
$image->crop((int) $width, (int) $height);
$output = (empty($output)) ? $type : $output;
$data = $resize->output($output, $quality);
$data = $image->output($output, $quality);
$cache->save($key, $data);
$response
->setContentType('image/png')
->addHeader('Expires', $date)
->addHeader('X-Appwrite-Cache', 'miss')
->send($data, null);
;
unset($resize);
unset($image);
};
App::get('/v1/avatars/credit-cards/:code')
@ -91,7 +90,7 @@ App::get('/v1/avatars/credit-cards/:code')
->label('sdk.description', '/docs/references/avatars/get-credit-card.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE_PNG)
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-credit-cards'))), 'Credit Card Code. Possible values: '.\implode(', ', \array_keys(Config::getParam('avatar-credit-cards'))).'.')
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-credit-cards'))), 'Credit Card Code. Possible values: ' . \implode(', ', \array_keys(Config::getParam('avatar-credit-cards'))) . '.')
->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('quality', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
@ -160,11 +159,11 @@ App::get('/v1/avatars/image')
$quality = 80;
$output = 'png';
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
$key = \md5('/v2/avatars/images-'.$url.'-'.$width.'/'.$height.'/'.$quality);
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT'; // 45 days cache
$key = \md5('/v2/avatars/images-' . $url . '-' . $width . '/' . $height . '/' . $quality);
$type = 'png';
$cache = new Cache(new Filesystem(APP_STORAGE_CACHE.'/app-0')); // Limit file number or size
$data = $cache->load($key, 60 * 60 * 24 * 7 /* 1 week */);
$cache = new Cache(new Filesystem(APP_STORAGE_CACHE . '/app-0')); // Limit file number or size
$data = $cache->load($key, 60 * 60 * 24 * 7/* 1 week */);
if ($data) {
return $response
@ -186,17 +185,17 @@ App::get('/v1/avatars/image')
}
try {
$resize = new Resize($fetch);
} catch (\Exception $exception) {
$image = new Image($fetch);
} catch (\Exception$exception) {
throw new Exception('Unable to parse image', 500);
}
$resize->crop((int) $width, (int) $height);
$image->crop((int) $width, (int) $height);
$output = (empty($output)) ? $type : $output;
$data = $resize->output($output, $quality);
$data = $image->output($output, $quality);
$cache->save($key, $data);
$response
@ -206,7 +205,7 @@ App::get('/v1/avatars/image')
->send($data);
;
unset($resize);
unset($image);
});
App::get('/v1/avatars/favicon')
@ -229,11 +228,11 @@ App::get('/v1/avatars/favicon')
$height = 56;
$quality = 80;
$output = 'png';
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
$key = \md5('/v2/avatars/favicon-'.$url);
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT'; // 45 days cache
$key = \md5('/v2/avatars/favicon-' . $url);
$type = 'png';
$cache = new Cache(new Filesystem(APP_STORAGE_CACHE.'/app-0')); // Limit file number or size
$data = $cache->load($key, 60 * 60 * 24 * 30 * 3 /* 3 months */);
$cache = new Cache(new Filesystem(APP_STORAGE_CACHE . '/app-0')); // Limit file number or size
$data = $cache->load($key, 60 * 60 * 24 * 30 * 3/* 3 months */);
if ($data) {
return $response
@ -316,7 +315,7 @@ App::get('/v1/avatars/favicon')
if (empty($outputHref) || empty($outputExt)) {
$default = \parse_url($url);
$outputHref = $default['scheme'].'://'.$default['host'].'/favicon.ico';
$outputHref = $default['scheme'] . '://' . $default['host'] . '/favicon.ico';
$outputExt = 'ico';
}
@ -343,13 +342,13 @@ App::get('/v1/avatars/favicon')
throw new Exception('Icon not found', 404);
}
$resize = new Resize($fetch);
$image = new Image($fetch);
$resize->crop((int) $width, (int) $height);
$image->crop((int) $width, (int) $height);
$output = (empty($output)) ? $type : $output;
$data = $resize->output($output, $quality);
$data = $image->output($output, $quality);
$cache->save($key, $data);
@ -359,7 +358,7 @@ App::get('/v1/avatars/favicon')
->addHeader('X-Appwrite-Cache', 'miss')
->send($data);
unset($resize);
unset($image);
});
App::get('/v1/avatars/qr')
@ -394,14 +393,14 @@ App::get('/v1/avatars/qr')
$response->addHeader('Content-Disposition', 'attachment; filename="qr.png"');
}
$resize = new Resize($qrcode->render($text));
$image = new Image($qrcode->render($text));
$resize->crop((int) $size, (int) $size);
$image->crop((int) $size, (int) $size);
$response
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
->setContentType('image/png')
->send($resize->output('png', 9))
->send($image->output('png', 9))
;
});
@ -437,10 +436,10 @@ App::get('/v1/avatars/initials')
['color' => '#610038', 'background' => '#f5d1e6'], // PINK
['color' => '#386100', 'background' => '#dcf1bd'], // LIME
['color' => '#615800', 'background' => '#f1ecba'], // YELLOW
['color' => '#610008', 'background' => '#f6d2d5'] // RED
['color' => '#610008', 'background' => '#f6d2d5'], // RED
];
$rand = \rand(0, \count($themes)-1);
$rand = \rand(0, \count($themes) - 1);
$name = (!empty($name)) ? $name : $user->getAttribute('name', $user->getAttribute('email', ''));
$words = \explode(' ', \strtoupper($name));
@ -457,23 +456,23 @@ App::get('/v1/avatars/initials')
}
$length = \count($words);
$rand = \substr($code,-1);
$background = (!empty($background)) ? '#'.$background : $themes[$rand]['background'];
$color = (!empty($color)) ? '#'.$color : $themes[$rand]['color'];
$rand = \substr($code, -1);
$background = (!empty($background)) ? '#' . $background : $themes[$rand]['background'];
$color = (!empty($color)) ? '#' . $color : $themes[$rand]['color'];
$image = new \Imagick();
$draw = new \ImagickDraw();
$fontSize = \min($width, $height) / 2;
$draw->setFont(__DIR__."/../../../public/fonts/poppins-v9-latin-500.ttf");
$image->setFont(__DIR__."/../../../public/fonts/poppins-v9-latin-500.ttf");
$draw->setFont(__DIR__ . "/../../../public/fonts/poppins-v9-latin-500.ttf");
$image->setFont(__DIR__ . "/../../../public/fonts/poppins-v9-latin-500.ttf");
$draw->setFillColor(new \ImagickPixel($color));
$draw->setFontSize($fontSize);
$draw->setTextAlignment(\Imagick::ALIGN_CENTER);
$draw->annotation($width / 1.97, ($height / 2) + ($fontSize / 3), $initials);
$image->newImage($width, $height, $background);
$image->setImageFormat("png");
$image->drawImage($draw);
@ -481,7 +480,7 @@ App::get('/v1/avatars/initials')
//$image->setImageCompressionQuality(9 - round(($quality / 100) * 9));
$response
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
->setContentType('image/png')
->send($image->getImageBlob())
;

View file

@ -294,7 +294,7 @@ App::put('/v1/functions/:functionId')
}
$original = $function->getAttribute('schedule', '');
$cron = (!empty($function->getAttribute('tag', null)) && !empty($schedule)) ? CronExpression::factory($schedule) : null;
$cron = (!empty($function->getAttribute('tag', null)) && !empty($schedule)) ? new CronExpression($schedule) : null;
$next = (!empty($function->getAttribute('tag', null)) && !empty($schedule)) ? $cron->getNextRunDate()->format('U') : null;
$function = $projectDB->updateDocument(array_merge($function->getArrayCopy(), [
@ -359,7 +359,7 @@ App::patch('/v1/functions/:functionId/tag')
}
$schedule = $function->getAttribute('schedule', '');
$cron = (empty($function->getAttribute('tag')) && !empty($schedule)) ? CronExpression::factory($schedule) : null;
$cron = (empty($function->getAttribute('tag')) && !empty($schedule)) ? new CronExpression($schedule) : null;
$next = (empty($function->getAttribute('tag')) && !empty($schedule)) ? $cron->getNextRunDate()->format('U') : null;
$function = $projectDB->updateDocument(array_merge($function->getArrayCopy(), [

View file

@ -943,7 +943,7 @@ App::post('/v1/projects/:projectId/tasks')
throw new Exception('Project not found', 404);
}
$cron = CronExpression::factory($schedule);
$cron = new CronExpression($schedule);
$next = ($status == 'play') ? $cron->getNextRunDate()->format('U') : null;
$security = ($security === '1' || $security === 'true' || $security === 1 || $security === true);
@ -1093,7 +1093,7 @@ App::put('/v1/projects/:projectId/tasks/:taskId')
throw new Exception('Task not found', 404);
}
$cron = CronExpression::factory($schedule);
$cron = new CronExpression($schedule);
$next = ($status == 'play') ? $cron->getNextRunDate()->format('U') : null;
$security = ($security === '1' || $security === 'true' || $security === 1 || $security === true);

View file

@ -18,7 +18,7 @@ use Utopia\Storage\Validator\File;
use Utopia\Storage\Validator\FileSize;
use Utopia\Storage\Validator\Upload;
use Utopia\Storage\Compression\Algorithms\GZIP;
use Appwrite\Resize\Resize;
use Utopia\Image\Image;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
@ -332,17 +332,17 @@ App::get('/v1/storage/files/:fileId/preview')
$source = $compressor->decompress($source);
}
$resize = new Resize($source);
$image = new Image($source);
$resize->crop((int) $width, (int) $height);
$image->crop((int) $width, (int) $height);
if (!empty($background)) {
$resize->setBackground('#'.$background);
$image->setBackground('#'.$background);
}
$output = (empty($output)) ? $type : $output;
$data = $resize->output($output, $quality);
$data = $image->output($output, $quality);
$cache->save($key, $data);
@ -353,7 +353,7 @@ App::get('/v1/storage/files/:fileId/preview')
->send($data)
;
unset($resize);
unset($image);
});
App::get('/v1/storage/files/:fileId/download')

View file

@ -11,13 +11,18 @@ App::init(function ($utopia, $request, $response, $layout) {
/* AJAX check */
if (!empty($request->getQuery('version', ''))) {
$layout->setPath(__DIR__.'/../../views/layouts/empty.phtml');
$layout->setPath(__DIR__ . '/../../views/layouts/empty.phtml');
}
$port = $request->getPort();
$protocol = $request->getProtocol();
$domain = $request->getHostname();
$layout
->setParam('title', APP_NAME)
->setParam('protocol', $request->getProtocol())
->setParam('domain', $request->getHostname())
->setParam('protocol', $protocol)
->setParam('domain', $domain)
->setParam('endpoint', $protocol . '://' . $domain . ($port != 80 && $port != 443 ? ':' . $port : ''))
->setParam('home', App::getEnv('_APP_HOME'))
->setParam('setup', App::getEnv('_APP_SETUP'))
->setParam('class', 'unknown')
@ -34,10 +39,10 @@ App::init(function ($utopia, $request, $response, $layout) {
$time = (60 * 60 * 24 * 45); // 45 days cache
$response
->addHeader('Cache-Control', 'public, max-age='.$time)
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time).' GMT') // 45 days cache
->addHeader('Cache-Control', 'public, max-age=' . $time)
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache
->addHeader('X-Frame-Options', 'SAMEORIGIN') // Avoid console and homepage from showing in iframes
->addHeader('X-XSS-Protection', '1; mode=block; report=/v1/xss?url='.\urlencode($request->getURI()))
->addHeader('X-XSS-Protection', '1; mode=block; report=/v1/xss?url=' . \urlencode($request->getURI()))
->addHeader('X-UA-Compatible', 'IE=Edge') // Deny IE browsers from going into quirks mode
;

View file

@ -144,6 +144,7 @@ App::get('/console/settings')
$page
->setParam('customDomainsEnabled', ($target->isKnown() && !$target->isTest()))
->setParam('customDomainsTarget', $target->get())
->setParam('smtpEnabled', (!empty(App::getEnv('_APP_SMTP_HOST'))))
;
$layout

View file

@ -375,4 +375,40 @@ App::get('/specs/:format')
$response
->json($specs->parse());
});
App::get('/versions')
->desc('Get Version')
->groups(['web', 'home'])
->label('scope', 'public')
->inject('response')
->action(function ($response) {
/** @var Appwrite\Utopia\Response $response */
$platforms = Config::getParam('platforms');
$versions = [
'server' => APP_VERSION_STABLE,
];
foreach($platforms as $platform) {
$languages = $platform['languages'] ?? [];
foreach ($languages as $key => $language) {
if(isset($language['dev']) && $language['dev']) {
continue;
}
if(isset($language['enabled']) && !$language['enabled']) {
continue;
}
$platformKey = $platform['key'] ?? '';
$languageKey = $language['key'] ?? '';
$version = $language['version'] ?? '';
$versions[$platformKey . '-' . $languageKey] = $version;
}
}
$response->json($versions);
});

View file

@ -34,7 +34,7 @@ use PDO as PDONative;
const APP_NAME = 'Appwrite';
const APP_DOMAIN = 'appwrite.io';
const APP_EMAIL_TEAM = 'team@localhost.test'; // Default email address
const APP_EMAIL_SECURITY = 'security@localhost.test'; // Default security email address
const APP_EMAIL_SECURITY = ''; // Default security email address
const APP_USERAGENT = APP_NAME.'-Server v%s. Please report abuse at %s';
const APP_MODE_DEFAULT = 'default';
const APP_MODE_ADMIN = 'admin';
@ -91,9 +91,13 @@ Config::load('storage-mimes', __DIR__.'/config/storage/mimes.php');
Config::load('storage-inputs', __DIR__.'/config/storage/inputs.php');
Config::load('storage-outputs', __DIR__.'/config/storage/outputs.php');
Resque::setBackend(App::getEnv('_APP_REDIS_HOST', '')
.':'.App::getEnv('_APP_REDIS_PORT', ''));
$user = App::getEnv('_APP_REDIS_USER','');
$pass = App::getEnv('_APP_REDIS_PASS','');
if(!empty($user) || !empty($pass)) {
Resque::setBackend('redis://'.$user.':'.$pass.'@'.App::getEnv('_APP_REDIS_HOST', '').':'.App::getEnv('_APP_REDIS_PORT', ''));
} else {
Resque::setBackend(App::getEnv('_APP_REDIS_HOST', '').':'.App::getEnv('_APP_REDIS_PORT', ''));
}
/**
* DB Filters
*/
@ -176,6 +180,18 @@ $register->set('statsd', function () { // Register DB connection
$register->set('cache', function () { // Register cache connection
$redis = new Redis();
$redis->pconnect(App::getEnv('_APP_REDIS_HOST', ''), App::getEnv('_APP_REDIS_PORT', ''));
$user = App::getEnv('_APP_REDIS_USER','');
$pass = App::getEnv('_APP_REDIS_PASS','');
$auth = [];
if(!empty($user)) {
$auth["user"] = $user;
}
if(!empty($pass)) {
$auth["pass"] = $pass;
}
if(!empty($auth)) {
$redis->auth($auth);
}
$redis->setOption(Redis::OPT_READ_TIMEOUT, -1);
return $redis;

View file

@ -139,7 +139,6 @@ $cli
Console::success('SMTP................connected 👍');
} catch (\Throwable $th) {
Console::error('SMTP.............disconnected 👎');
var_dump($th);
}
$host = App::getEnv('_APP_STATSD_HOST', 'telegraf');

View file

@ -114,7 +114,7 @@ $maxCells = 10;
<?php if(!$array): ?>
<?php switch($type):
case 'fileId': ?>
<img data-ls-if="{{node.<?php echo $this->escape($key); ?>}} != ''" src="" data-ls-attrs="src=//{{env.DOMAIN}}/v1/storage/files/{{node.<?php echo $this->escape($key); ?>}}/preview?width=65&height=65&project={{router.params.project}}&mode=admin" class="avatar" width="30" height="30" loading="lazy" />
<img data-ls-if="{{node.<?php echo $this->escape($key); ?>}} != ''" src="" data-ls-attrs="src={{env.ENDPOINT}}/v1/storage/files/{{node.<?php echo $this->escape($key); ?>}}/preview?width=65&height=65&project={{router.params.project}}&mode=admin" class="avatar" width="30" height="30" loading="lazy" />
<?php break; ?>
<?php case 'document': ?>
{...}

View file

@ -72,7 +72,7 @@ $rules = $collection->getAttribute('rules', []);
<?php if(!$array): ?>
<?php switch($type):
case 'fileId': ?>
<img data-ls-if="{{node.<?php echo $this->escape($key); ?>}} != ''" src="" data-ls-attrs="src=//{{env.DOMAIN}}/v1/storage/files/{{node.<?php echo $this->escape($key); ?>}}/preview?width=65&height=65&project={{router.params.project}}&mode=admin" class="avatar" width="30" height="30" loading="lazy" />
<img data-ls-if="{{node.<?php echo $this->escape($key); ?>}} != ''" src="" data-ls-attrs="src={{env.ENDPOINT}}/v1/storage/files/{{node.<?php echo $this->escape($key); ?>}}/preview?width=65&height=65&project={{router.params.project}}&mode=admin" class="avatar" width="30" height="30" loading="lazy" />
<?php break; ?>
<?php case 'document': ?>
{...}

View file

@ -54,7 +54,7 @@
<input type="radio" name="selected" data-ls-attrs="value={{file.$id}}" data-ls-bind="{{search.selected}}" />
</td>
<td data-title="x" class="">
<img src="" data-ls-attrs="src=//{{env.DOMAIN}}/v1/storage/files/{{file.$id}}/preview?width=65&height=65&project={{router.params.project}}&mode=admin" class="pull-start avatar" width="30" height="30" loading="lazy" />
<img src="" data-ls-attrs="src={{env.ENDPOINT}}/v1/storage/files/{{file.$id}}/preview?width=65&height=65&project={{router.params.project}}&mode=admin" class="pull-start avatar" width="30" height="30" loading="lazy" />
</td>
<td data-title="Name: " class="text-one-liner">
<span data-ls-bind="{{file.name}}" data-ls-attrs="title={{file.name}}" class="text-fade text-size-small"></span>
@ -64,6 +64,7 @@
</td>
<td data-title="Size: ">
<span class="text-fade text-size-small" data-ls-bind="{{file.sizeOriginal|humanFileSize}}"></span>
<span class="text-fade text-size-small" data-ls-bind="{{file.sizeOriginal|humanFileUnit}}"></span>
</td>
<td data-title="Created: ">
<span class="text-fade text-size-small" data-ls-bind="{{file.dateCreated|dateText}}"></span>

View file

@ -117,7 +117,7 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled',true);
<b data-ls-bind="{{tag.$id}}"></b> &nbsp;
<span class="text-fade" data-ls-bind="{{tag.command}}"></span>
<div class="text-size-small margin-top-small clear">
<span class="pull-start" data-ls-bind="Created {{tag.dateCreated|timeSince}} &nbsp; | &nbsp; {{tag.size|humanFileSize}}"></span>
<span class="pull-start" data-ls-bind="Created {{tag.dateCreated|timeSince}} &nbsp; | &nbsp; {{tag.size|humanFileSize}}{{tag.size|humanFileUnit}}"></span>
<form data-ls-if="{{tag.$id}} !== {{project-function.tag}}" name="functions.deleteTag" class="pull-start"
data-analytics
@ -596,9 +596,9 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled',true);
<p><b>PowerShell</b></p>
<div class="margin-bottom">
<textarea type="hidden" data-ls-bind="appwrite functions createTag ,
--functionId={{project-function.$id}} ,
--command='mycommand' ,
<textarea type="hidden" data-ls-bind="appwrite functions createTag `
--functionId={{project-function.$id}} `
--command='mycommand' `
--code='/myrepo/myfunction'" data-forms-code="powershell" data-lang="powershell" data-lang-label="PowerShell"></textarea>
</div>

View file

@ -97,7 +97,10 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled',true);
</div>
</div>
<div class="col span-3">
<div class="value margin-bottom-small"><span class="sum" data-ls-bind="{{usage.network.total|humanFileSize}}" data-default="0">0</span></div>
<div class="value margin-bottom-small">
<span class="sum" data-ls-bind="{{usage.network.total|humanFileSize}}" data-default="0">0</span>
<span data-ls-bind="{{usage.network.total|humanFileUnit}}" class="text-size-small unit"></span>
</div>
<div class="metric margin-bottom-small">Bandwidth</div>
<div class="margin-top-large value small">
@ -117,7 +120,10 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled',true);
<div class="margin-top-small"><b class="text-size-small unit">Documents</b></div>
</div>
<div class="col span-3">
<div class="value"><span class="sum" data-ls-bind="{{usage.storage.total|humanFileSize}}" data-default="0">0</span></div>
<div class="value">
<span class="sum" data-ls-bind="{{usage.storage.total|humanFileSize}}" data-default="0">0</span>
<span data-ls-bind="{{usage.storage.total|humanFileUnit}}" class="text-size-small unit"></span>
</div>
<div class="margin-top-small"><b class="text-size-small unit">Storage</b></div>
</div>
<div class="col span-3">

View file

@ -2,6 +2,7 @@
$customDomainsEnabled = $this->getParam('customDomainsEnabled', false);
$customDomainsTarget = $this->getParam('customDomainsTarget', false);
$smtpEnabled = $this->getParam('smtpEnabled', false);
?>
<div class="cover">
@ -135,7 +136,7 @@ $customDomainsTarget = $this->getParam('customDomainsTarget', false);
<label for="name">API Endpoint</label>
<div class="input-copy">
<input data-forms-copy type="text" disabled data-ls-bind="{{env.PROTOCOL}}://{{env.DOMAIN}}/v1" />
<input data-forms-copy type="text" disabled data-ls-bind="{{env.ENDPOINT}}/v1" />
</div>
<ul class="margin-bottom-large text-fade text-size-small">
@ -453,7 +454,7 @@ $customDomainsTarget = $this->getParam('customDomainsTarget', false);
data-failure-param-alert-classname="error">
<input name="teamId" type="hidden" data-ls-bind="{{member.teamId}}">
<input name="url" type="hidden" data-ls-bind="{{env.PROTOCOL}}://{{env.DOMAIN}}/auth/join?project={{router.params.project}}" />
<input name="url" type="hidden" data-ls-bind="{{env.ENDPOINT}}/auth/join?project={{router.params.project}}" />
<input name="email" type="hidden" data-ls-bind="{{member.email}}">
<input name="name" type="hidden" data-ls-bind="{{member.name}}">
<input name="roles" type="hidden" data-ls-bind="{{member.roles}}" data-cast-to="json">
@ -493,7 +494,7 @@ $customDomainsTarget = $this->getParam('customDomainsTarget', false);
data-failure-param-alert-classname="error">
<input name="teamId" id="team-teamId" type="hidden" data-ls-bind="{{console-project.teamId}}">
<input name="url" type="hidden" data-ls-bind="{{env.PROTOCOL}}://{{env.DOMAIN}}/auth/join?project={{router.params.project}}" />
<input name="url" type="hidden" data-ls-bind="{{env.ENDPOINT}}/auth/join?project={{router.params.project}}" />
<label for="email">Email</label>
<input name="email" id="email" type="email" autocomplete="email" required>
@ -506,10 +507,16 @@ $customDomainsTarget = $this->getParam('customDomainsTarget', false);
<option data-ls-attrs="value={{role.type}}" data-ls-bind="{{role.label}}"></option>
</select>
<?php if(!$smtpEnabled): ?>
<div class="box note padding-tiny warning margin-bottom text-align-center">
<i class="icon-warning"></i> SMTP connection is disabled. <a href="https://appwrite.io/docs/email-delivery" target="_blank" rel="noopener">Learn more <i class="icon-link-ext"></i></a>
</div>
<?php endif; ?>
<hr />
<div class="clear">
<button>Send Invite</button>
<button<?php if(!$smtpEnabled): ?> disabled<?php endif; ?>>Send Invite</button>
&nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</div>
</form>

View file

@ -113,7 +113,7 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
<tbody data-ls-loop="project-files.files" data-ls-as="file">
<tr>
<td class="hide">
<img src="" data-ls-attrs="src=//{{env.DOMAIN}}/v1/storage/files/{{file.$id}}/preview?width=65&height=65&project={{router.params.project}}&mode=admin" class="pull-start avatar" width="30" height="30" loading="lazy" />
<img src="" data-ls-attrs="src={{env.ENDPOINT}}/v1/storage/files/{{file.$id}}/preview?width=65&height=65&project={{router.params.project}}&mode=admin" class="pull-start avatar" width="30" height="30" loading="lazy" />
</td>
<td data-title="Name: " class="text-one-liner" data-ls-attrs="title={{file.name}}" >
<div data-ui-modal class="box modal sticky-footer width-large close" data-button-text="{{file.name}}" data-button-class="link" data-button-element="span">
@ -180,15 +180,15 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
<div class="margin-bottom-small">File Preview</div>
<div class="margin-bottom-small">
<img src="" class="file-preview" data-ls-attrs="src=//{{env.DOMAIN}}/v1/storage/files/{{file.$id}}/preview?width=350&height=250&project={{router.params.project}}&mode=admin" loading="lazy" width="225" height="160" />
<img src="" class="file-preview" data-ls-attrs="src={{env.ENDPOINT}}/v1/storage/files/{{file.$id}}/preview?width=350&height=250&project={{router.params.project}}&mode=admin" loading="lazy" width="225" height="160" />
</div>
<div class="margin-bottom-tiny">
<a href="" data-ls-attrs="href=//{{env.DOMAIN}}/v1/storage/files/{{file.$id}}/view?project={{router.params.project}}&mode=admin" target="_blank" rel="noopener"><i class="icon-angle-circled-right margin-start-negative-tiny margin-end-tiny"></i> New Window <i class="icon-link-ext"></i></a>
<a href="" data-ls-attrs="href={{env.ENDPOINT}}/v1/storage/files/{{file.$id}}/view?project={{router.params.project}}&mode=admin" target="_blank" rel="noopener"><i class="icon-angle-circled-right margin-start-negative-tiny margin-end-tiny"></i> New Window <i class="icon-link-ext"></i></a>
</div>
<div class="margin-bottom">
<a href="" data-ls-attrs="href=//{{env.DOMAIN}}/v1/storage/files/{{file.$id}}/download?project={{router.params.project}}&mode=admin" target="_blank" rel="noopener"><i class="icon-angle-circled-right margin-start-negative-tiny margin-end-tiny"></i> Download <i class="icon-link-ext"></i></a>
<a href="" data-ls-attrs="href={{env.ENDPOINT}}/v1/storage/files/{{file.$id}}/download?project={{router.params.project}}&mode=admin" target="_blank" rel="noopener"><i class="icon-angle-circled-right margin-start-negative-tiny margin-end-tiny"></i> Download <i class="icon-link-ext"></i></a>
</div>
</div>
</div>
@ -204,6 +204,7 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
</td>
<td data-title="Size: ">
<span class="text-fade text-size-small" data-ls-bind="{{file.sizeOriginal|humanFileSize}}"></span>
<span class="text-fade text-size-small" data-ls-bind="{{file.sizeOriginal|humanFileUnit}}"></span>
</td>
<td data-title="Created: ">
<span class="text-fade text-size-small" data-ls-bind="{{file.dateCreated|dateText}}"></span>

View file

@ -361,7 +361,7 @@ $providers = $this->getParam('providers', []);
<p>To complete set up, add this OAuth2 redirect URI to your <?php echo $this->escape(ucfirst($provider)); ?> app configuration.</p>
<div class="input-copy">
<input data-forms-copy type="text" disabled data-ls-bind="{{env.PROTOCOL}}://{{env.DOMAIN}}/v1/account/sessions/oauth2/callback/<?php echo $this->escape($provider); ?>/{{router.params.project}}" class="margin-bottom-no" />
<input data-forms-copy type="text" disabled data-ls-bind="{{env.ENDPOINT}}/v1/account/sessions/oauth2/callback/<?php echo $this->escape($provider); ?>/{{router.params.project}}" class="margin-bottom-no" />
</div>
</div>
</div>

View file

@ -145,7 +145,7 @@
data-failure-param-alert-classname="error">
<input name="teamId" id="team-teamId" type="hidden" data-ls-bind="{{team.$id}}">
<input name="url" type="hidden" data-ls-bind="{{env.PROTOCOL}}://{{env.DOMAIN}}" />
<input name="url" type="hidden" data-ls-bind="{{env.ENDPOINT}}" />
<label for="email">Email</label>
<input name="email" id="email" type="email" autocomplete="email" required>

View file

@ -23,7 +23,7 @@
<label>Email</label>
<input name="email" type="email" class="full-width" autocomplete="email" placeholder="me@example.com" required>
<input name="url" type="hidden" data-ls-bind="{{env.PROTOCOL}}://{{env.DOMAIN}}/auth/recovery/reset" />
<input name="url" type="hidden" data-ls-bind="{{env.ENDPOINT}}/auth/recovery/reset" />
<button type="submit" class="btn btn-primary"><i class="fa fa-sign-in"></i> Recover</button>
</form>

View file

@ -56,9 +56,12 @@ services:
- influxdb
environment:
- _APP_ENV
- _APP_CONSOLE_WHITELIST_EMAILS
- _APP_CONSOLE_WHITELIST_IPS
- _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_SYSTEM_RESPONSE_FORMAT
- _APP_OPTIONS_ABUSE
- _APP_OPTIONS_FORCE_HTTPS
- _APP_OPENSSL_KEY_V1
@ -66,6 +69,8 @@ services:
- _APP_DOMAIN_TARGET
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@ -104,6 +109,8 @@ services:
- _APP_ENV
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_STATSD_HOST
- _APP_STATSD_PORT
@ -121,6 +128,8 @@ services:
- _APP_ENV
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@ -142,6 +151,8 @@ services:
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@ -163,6 +174,8 @@ services:
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@ -187,6 +200,8 @@ services:
- _APP_ENV
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@ -211,6 +226,8 @@ services:
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DOMAIN_TARGET
- _APP_DB_HOST
- _APP_DB_PORT
@ -236,6 +253,8 @@ services:
- _APP_ENV
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@ -257,13 +276,14 @@ services:
- appwrite
depends_on:
- redis
- smtp
environment:
- _APP_ENV
- _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_SMTP_HOST
- _APP_SMTP_PORT
- _APP_SMTP_SECURE
@ -283,6 +303,8 @@ services:
- _APP_ENV
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_MAINTENANCE_INTERVAL
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_MAINTENANCE_RETENTION_ABUSE
@ -302,6 +324,8 @@ services:
- _APP_ENV
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
mariadb:
image: appwrite/mariadb:1.2.0 # fix issues when upgrading using: mysql_upgrade -u root -p
@ -318,16 +342,6 @@ services:
- MYSQL_PASSWORD=${_APP_DB_PASS}
command: 'mysqld --innodb-flush-method=fsync'
smtp:
image: appwrite/smtp:1.0.1
container_name: appwrite-smtp
restart: unless-stopped
networks:
- appwrite
environment:
- MAILNAME=appwrite
- RELAY_NETWORKS=:192.168.0.0/24:10.0.0.0/16
redis:
image: redis:6.0-alpine3.12
container_name: appwrite-redis

View file

@ -2,6 +2,7 @@
$protocol = $this->getParam('protocol', '');
$domain = $this->getParam('domain', '');
$endpoint = $this->getParam('endpoint', '');
$platforms = $this->getParam('platforms', []);
$version = $this->getParam('version', '0.0.0');
$isDev = $this->getParam('isDev', false);
@ -56,7 +57,7 @@ if(!empty($platforms)) {
<?php if (!empty($canonical)): ?>
<meta property="og:url" content="<?php echo $canonical; ?>" />
<?php endif; ?>
<meta property="og:image" content="<?php echo $protocol; ?>://<?php echo $domain; ?>/images/logo.png?v=<?php echo APP_CACHE_BUSTER; ?>" />
<meta property="og:image" content="<?php echo $endpoint; ?>/images/logo.png?v=<?php echo APP_CACHE_BUSTER; ?>" />
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
@ -71,6 +72,7 @@ if(!empty($platforms)) {
VERSION: '<?php echo $version; ?>',
CACHEBUSTER: '<?php echo $version; ?>/<?php echo APP_CACHE_BUSTER; ?>',
PROTOCOL: '<?php echo $protocol; ?>',
ENDPOINT: '<?php echo $endpoint; ?>',
DOMAIN: '<?php echo $domain; ?>',
HOME: '<?php echo $this->escape($this->getParam('home')); ?>',
SETUP: '<?php echo $this->escape($this->getParam('setup')); ?>',
@ -142,4 +144,4 @@ if(!empty($platforms)) {
<!-- Version <?php echo $version; ?> -->
</body>
</html>
</html>

View file

@ -110,14 +110,22 @@ class CertificatesV1
}
$staging = (App::isProduction()) ? '' : ' --dry-run';
$email = App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS');
$response = \shell_exec("certbot certonly --webroot --noninteractive --agree-tos{$staging}"
." --email ".App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', 'security@localhost.test')
if(empty($email)) {
throw new Exception('You must set a valid security email address (_APP_SYSTEM_SECURITY_EMAIL_ADDRESS) to issue an SSL certificate');
}
$stdout = '';
$stderr = '';
$exit = Console::execute("certbot certonly --webroot --noninteractive --agree-tos{$staging}"
." --email ".$email
." -w ".APP_STORAGE_CERTIFICATES
." -d {$domain->get()}");
." -d {$domain->get()}", '', $stdout, $stderr);
if(!$response) {
throw new Exception('Failed to issue a certificate');
if($stderr || $exit !== 0) {
throw new Exception('Failed to issue a certificate with message: '.$stderr);
}
$path = APP_STORAGE_CERTIFICATES.'/'.$domain->get();
@ -129,19 +137,19 @@ class CertificatesV1
}
if(!@\rename('/etc/letsencrypt/live/'.$domain->get().'/cert.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/cert.pem')) {
throw new Exception('Failed to rename certificate cert.pem: '.\json_encode($response));
throw new Exception('Failed to rename certificate cert.pem: '.\json_encode($stdout));
}
if(!@\rename('/etc/letsencrypt/live/'.$domain->get().'/chain.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/chain.pem')) {
throw new Exception('Failed to rename certificate chain.pem: '.\json_encode($response));
throw new Exception('Failed to rename certificate chain.pem: '.\json_encode($stdout));
}
if(!@\rename('/etc/letsencrypt/live/'.$domain->get().'/fullchain.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/fullchain.pem')) {
throw new Exception('Failed to rename certificate fullchain.pem: '.\json_encode($response));
throw new Exception('Failed to rename certificate fullchain.pem: '.\json_encode($stdout));
}
if(!@\rename('/etc/letsencrypt/live/'.$domain->get().'/privkey.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/privkey.pem')) {
throw new Exception('Failed to rename certificate privkey.pem: '.\json_encode($response));
throw new Exception('Failed to rename certificate privkey.pem: '.\json_encode($stdout));
}
$certificate = \array_merge($certificate, [
@ -154,7 +162,7 @@ class CertificatesV1
'issueDate' => \time(),
'renewDate' => $renew,
'attempts' => 0,
'log' => \json_encode($response),
'log' => \json_encode($stdout),
]);
$certificate = $consoleDB->createDocument($certificate);

View file

@ -227,7 +227,7 @@ class FunctionsV1
return;
}
$cron = CronExpression::factory($function->getAttribute('schedule'));
$cron = new CronExpression($function->getAttribute('schedule'));
$next = (int) $cron->getNextRunDate()->format('U');
$function

View file

@ -82,7 +82,7 @@ class TasksV1
// Reschedule
$cron = CronExpression::factory($task->getAttribute('schedule'));
$cron = new CronExpression($task->getAttribute('schedule'));
$next = (int) $cron->getNextRunDate()->format('U');
$headers = (\is_array($task->getAttribute('httpHeaders', []))) ? $task->getAttribute('httpHeaders', []) : [];

View file

@ -1,3 +1,10 @@
#!/bin/sh
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}" RESQUE_PHP='/usr/src/code/vendor/autoload.php' php /usr/src/code/vendor/bin/resque-scheduler
if [ -z "$_APP_REDIS_USER" ] || [ -z "$_APP_REDIS_PASS" ]
then
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
else
REDIS_BACKEND="redis://${_APP_REDIS_USER}:${_APP_REDIS_PASS}@${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
fi
REDIS_BACKEND=$REDIS_BACKEND RESQUE_PHP='/usr/src/code/vendor/autoload.php' php /usr/src/code/vendor/bin/resque-scheduler

View file

@ -1,3 +1,10 @@
#!/bin/sh
QUEUE='v1-audits' APP_INCLUDE='/usr/src/code/app/workers/audits.php' REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}" php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php
if [ -z "$_APP_REDIS_USER" ] || [ -z "$_APP_REDIS_PASS" ]
then
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
else
REDIS_BACKEND="redis://${_APP_REDIS_USER}:${_APP_REDIS_PASS}@${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
fi
QUEUE='v1-audits' APP_INCLUDE='/usr/src/code/app/workers/audits.php' php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php

9
bin/worker-certificates Normal file → Executable file
View file

@ -1,3 +1,10 @@
#!/bin/sh
QUEUE='v1-certificates' APP_INCLUDE='/usr/src/code/app/workers/certificates.php' REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}" php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php
if [ -z "$_APP_REDIS_USER" ] || [ -z "$_APP_REDIS_PASS" ]
then
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
else
REDIS_BACKEND="redis://${_APP_REDIS_USER}:${_APP_REDIS_PASS}@${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
fi
QUEUE='v1-certificates' APP_INCLUDE='/usr/src/code/app/workers/certificates.php' php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php

View file

@ -1,3 +1,10 @@
#!/bin/sh
QUEUE='v1-deletes' APP_INCLUDE='/usr/src/code/app/workers/deletes.php' REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}" php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php
if [ -z "$_APP_REDIS_USER" ] || [ -z "$_APP_REDIS_PASS" ]
then
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
else
REDIS_BACKEND="redis://${_APP_REDIS_USER}:${_APP_REDIS_PASS}@${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
fi
QUEUE='v1-deletes' APP_INCLUDE='/usr/src/code/app/workers/deletes.php' php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php

View file

@ -1,3 +1,10 @@
#!/bin/sh
QUEUE='v1-functions' APP_INCLUDE='/usr/src/code/app/workers/functions.php' REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}" php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php
if [ -z "$_APP_REDIS_USER" ] || [ -z "$_APP_REDIS_PASS" ]
then
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
else
REDIS_BACKEND="redis://${_APP_REDIS_USER}:${_APP_REDIS_PASS}@${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
fi
QUEUE='v1-functions' APP_INCLUDE='/usr/src/code/app/workers/functions.php' php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php

View file

@ -1,3 +1,10 @@
#!/bin/sh
QUEUE='v1-mails' APP_INCLUDE='/usr/src/code/app/workers/mails.php' REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}" php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php
if [ -z "$_APP_REDIS_USER" ] || [ -z "$_APP_REDIS_PASS" ]
then
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
else
REDIS_BACKEND="redis://${_APP_REDIS_USER}:${_APP_REDIS_PASS}@${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
fi
QUEUE='v1-mails' APP_INCLUDE='/usr/src/code/app/workers/mails.php' php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php

View file

@ -1,3 +1,10 @@
#!/bin/sh
QUEUE='v1-tasks' APP_INCLUDE='/usr/src/code/app/workers/tasks.php' REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}" php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php
if [ -z "$_APP_REDIS_USER" ] || [ -z "$_APP_REDIS_PASS" ]
then
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
else
REDIS_BACKEND="redis://${_APP_REDIS_USER}:${_APP_REDIS_PASS}@${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
fi
QUEUE='v1-tasks' APP_INCLUDE='/usr/src/code/app/workers/tasks.php' php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php

View file

@ -1,3 +1,10 @@
#!/bin/sh
QUEUE='v1-usage' APP_INCLUDE='/usr/src/code/app/workers/usage.php' REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}" php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php
if [ -z "$_APP_REDIS_USER" ] || [ -z "$_APP_REDIS_PASS" ]
then
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
else
REDIS_BACKEND="redis://${_APP_REDIS_USER}:${_APP_REDIS_PASS}@${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
fi
QUEUE='v1-usage' APP_INCLUDE='/usr/src/code/app/workers/usage.php' php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php

View file

@ -1,3 +1,10 @@
#!/bin/sh
QUEUE='v1-webhooks' APP_INCLUDE='/usr/src/code/app/workers/webhooks.php' REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}" php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php
if [ -z "$_APP_REDIS_USER" ] || [ -z "$_APP_REDIS_PASS" ]
then
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
else
REDIS_BACKEND="redis://${_APP_REDIS_USER}:${_APP_REDIS_PASS}@${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
fi
QUEUE='v1-webhooks' APP_INCLUDE='/usr/src/code/app/workers/webhooks.php' php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php

View file

@ -39,7 +39,7 @@
"utopia-php/analytics": "0.1.*",
"utopia-php/audit": "0.5.*",
"utopia-php/cache": "0.2.*",
"utopia-php/cli": "0.9.0",
"utopia-php/cli": "0.10.0",
"utopia-php/config": "0.2.*",
"utopia-php/locale": "0.3.*",
"utopia-php/registry": "0.2.*",
@ -47,16 +47,17 @@
"utopia-php/domains": "0.2.*",
"utopia-php/swoole": "0.2.*",
"utopia-php/system": "0.4.*",
"utopia-php/storage": "0.2.*",
"utopia-php/storage": "0.4.*",
"utopia-php/image": "0.1.*",
"resque/php-resque": "1.3.6",
"matomo/device-detector": "3.13.0",
"dragonmantank/cron-expression": "3.0.1",
"matomo/device-detector": "4.1.0",
"dragonmantank/cron-expression": "3.1.0",
"domnikl/statsd": "3.0.2",
"influxdb/influxdb-php": "1.15.1",
"phpmailer/phpmailer": "6.1.7",
"influxdb/influxdb-php": "1.15.2",
"phpmailer/phpmailer": "6.3.0",
"chillerlan/php-qrcode": "4.3.0",
"adhocore/jwt": "1.1.0"
"adhocore/jwt": "1.1.2"
},
"require-dev": {
"appwrite/sdk-generator": "0.5.5",

631
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -86,6 +86,8 @@ services:
- _APP_DOMAIN_TARGET
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@ -127,6 +129,8 @@ services:
- _APP_ENV
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_STATSD_HOST
- _APP_STATSD_PORT
@ -147,6 +151,8 @@ services:
- _APP_ENV
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@ -172,6 +178,8 @@ services:
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@ -196,6 +204,8 @@ services:
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@ -223,6 +233,8 @@ services:
- _APP_ENV
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@ -249,6 +261,8 @@ services:
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@ -276,6 +290,8 @@ services:
- _APP_ENV
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@ -310,6 +326,8 @@ services:
- _APP_SYSTEM_EMAIL_ADDRESS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_SMTP_HOST
- _APP_SMTP_PORT
- _APP_SMTP_SECURE
@ -332,6 +350,8 @@ services:
- _APP_ENV
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_MAINTENANCE_INTERVAL
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_MAINTENANCE_RETENTION_ABUSE
@ -353,6 +373,8 @@ services:
- _APP_ENV
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
mariadb:
image: appwrite/mariadb:1.2.0 # fix issues when upgrading using: mysql_upgrade -u root -p

View file

@ -2071,7 +2071,7 @@ container.path(paths[i],value);}});}
return;}
if(element.value!==value){element.value=value;element.dispatchEvent(new Event('change'));}
if(bind){element.addEventListener('input',sync);element.addEventListener('change',sync);}}
else{if(element.innerHTML!=value){element.innerHTML=value;}}};let sync=(()=>{return()=>{if(debug){console.info('debug-ls-bind','sync-path',paths);console.info('debug-ls-bind','sync-syntax',syntax);console.info('debug-ls-bind','sync-syntax-parsed',parsedSyntax);console.info('debug-ls-bind','sync-value',element.value);}
else{if(element.textContent!=value){element.textContent=value;}}};let sync=(()=>{return()=>{if(debug){console.info('debug-ls-bind','sync-path',paths);console.info('debug-ls-bind','sync-syntax',syntax);console.info('debug-ls-bind','sync-syntax-parsed',parsedSyntax);console.info('debug-ls-bind','sync-value',element.value);}
for(let i=0;i<paths.length;i++){if('{{'+paths[i]+'}}'!==parsedSyntax){if(debug){console.info('debug-ls-bind','sync-skipped-path',paths[i]);console.info('debug-ls-bind','sync-skipped-syntax',syntax);console.info('debug-ls-bind','sync-skipped-syntax-parsed',parsedSyntax);}
continue;}
if(debug){console.info('debug-ls-bind','sync-loop-path',paths[i]);console.info('debug-ls-bind','sync-loop-syntax',parsedSyntax);}
@ -2250,9 +2250,10 @@ return value+" "+unit+" "+direction;}).add("ms2hum",function($value){let temp=$v
(minutes?minutes+"m ":"")+
Number.parseFloat(seconds).toFixed(0)+"s");}
return"< 1s";}).add("seconds2hum",function($value){var seconds=($value).toFixed(3);var minutes=($value/(60)).toFixed(1);var hours=($value/(60*60)).toFixed(1);var days=($value/(60*60*24)).toFixed(1);if(seconds<60){return seconds+"s";}else if(minutes<60){return minutes+"m";}else if(hours<24){return hours+"h";}else{return days+"d"}}).add("markdown",function($value,markdown){return markdown.render($value);}).add("pageCurrent",function($value,env){return Math.ceil(parseInt($value||0)/env.PAGING_LIMIT)+1;}).add("pageTotal",function($value,env){let total=Math.ceil(parseInt($value||0)/env.PAGING_LIMIT);return total?total:1;}).add("humanFileSize",function($value){if(!$value){return 0;}
let thresh=1000;if(Math.abs($value)<thresh){return $value+" B";}
let units=["kB","MB","GB","TB","PB","EB","ZB","YB"];let u=-1;do{$value/=thresh;++u;}while(Math.abs($value)>=thresh&&u<units.length-1);return($value.toFixed(1)+'<span class="text-size-small unit">'+
units[u]+"</span>");}).add("statsTotal",function($value){if(!$value){return 0;}
let thresh=1000;if(Math.abs($value)<thresh){return $value;}
let units=["kB","MB","GB","TB","PB","EB","ZB","YB"];let u=-1;do{$value/=thresh;++u;}while(Math.abs($value)>=thresh&&u<units.length-1);return $value.toFixed(1);}).add("humanFileUnit",function($value){if(!$value){return'';}
let thresh=1000;if(Math.abs($value)<thresh){return'B';}
let units=["kB","MB","GB","TB","PB","EB","ZB","YB"];let u=-1;do{$value/=thresh;++u;}while(Math.abs($value)>=thresh&&u<units.length-1);return units[u];}).add("statsTotal",function($value){if(!$value){return 0;}
$value=abbreviate($value,0,false,false);return $value==="0"?"N/A":$value;}).add("isEmpty",function($value){return(!!$value);}).add("isEmptyObject",function($value){return((Object.keys($value).length===0&&$value.constructor===Object)||$value.length===0)}).add("activeDomainsCount",function($value){let result=[];if(Array.isArray($value)){result=$value.filter(function(node){return(node.verification&&node.certificateId);});}
return result.length;}).add("documentAction",function(container){let collection=container.get('project-collection');let document=container.get('project-document');if(collection&&document&&!document.$id){return'database.createDocument';}
return'database.updateDocument';}).add("documentSuccess",function(container){let document=container.get('project-document');if(document&&!document.$id){return',redirect';}
@ -2305,8 +2306,8 @@ parsedFailure[i].slice(1),{}));}
element.$lsSkip=false;view.render(element);});};let events=event.trim().split(",");for(let y=0;y<events.length;y++){if(""===events[y]){continue;}
switch(events[y].trim()){case"load":exec();break;case"none":break;case"click":case"change":case"keypress":case"keydown":case"keyup":case"input":case"submit":element.addEventListener(events[y],exec);break;default:document.addEventListener(events[y],exec);}}}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-analytics",controller:function(element){let action=element.getAttribute("data-analytics-event")||"click";let doNotTrack=window.navigator.doNotTrack;if(doNotTrack=='1'){return;}
element.addEventListener(action,function(){let category=element.getAttribute("data-analytics-category")||"undefined";let label=element.getAttribute("data-analytics-label")||"undefined";if(!ga){console.error("Google Analytics ga object is not available");}
ga("send",{hitType:"event",eventCategory:category,eventAction:action,eventLabel:label});});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-analytics-activity",controller:function(window,element,appwrite,account){let action=element.getAttribute("data-analytics-event")||"click";let activity=element.getAttribute("data-analytics-label")||"None";let doNotTrack=window.navigator.doNotTrack;if(doNotTrack=='1'){return;}
element.addEventListener(action,function(){let email=account?.email||element.elements['email'].value||'';appwrite.analytics.create(email,'console',activity,window.location.href)});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-analytics-pageview",controller:function(window,router,env){if(!ga){console.error("Google Analytics ga object is not available");}
ga("send",{hitType:"event",eventCategory:category,eventAction:action,eventLabel:label});});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-analytics-activity",controller:function(window,element,appwrite,container){let action=element.getAttribute("data-analytics-event")||"click";let activity=element.getAttribute("data-analytics-label")||"None";let doNotTrack=window.navigator.doNotTrack;if(doNotTrack=='1'){return;}
element.addEventListener(action,function(){let account=container.get('account');let email=account?.email||element?.elements['email']?.value||'';appwrite.analytics.create(email,'console',activity,window.location.href)});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-analytics-pageview",controller:function(window,router,env){if(!ga){console.error("Google Analytics ga object is not available");}
let doNotTrack=window.navigator.doNotTrack;if(doNotTrack=='1'){return;}
let project=router.params["project"]||'None';ga("set","page",window.location.pathname);ga("set","dimension1",project);ga('set','dimension2',env.VERSION);ga('set','dimension3',env.SETUP);ga("send","pageview");}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-clone",controller:function(element,document,view){var template=element.innerHTML.toString();var label=element.dataset["label"]||"Add";var icon=element.dataset["icon"]||null;var target=element.dataset["target"]||null;var first=parseInt(element.dataset["first"]||1);var button=document.createElement("button");button.type="button";button.innerText=" "+label+" ";button.classList.add("margin-end");button.classList.add("margin-bottom-small");button.classList.add("reverse");if(icon){var iconElement=document.createElement("i");iconElement.className=icon;button.insertBefore(iconElement,button.firstChild);}
if(target){target=document.getElementById(target);}

View file

@ -116,7 +116,7 @@ container.path(paths[i],value);}});}
return;}
if(element.value!==value){element.value=value;element.dispatchEvent(new Event('change'));}
if(bind){element.addEventListener('input',sync);element.addEventListener('change',sync);}}
else{if(element.innerHTML!=value){element.innerHTML=value;}}};let sync=(()=>{return()=>{if(debug){console.info('debug-ls-bind','sync-path',paths);console.info('debug-ls-bind','sync-syntax',syntax);console.info('debug-ls-bind','sync-syntax-parsed',parsedSyntax);console.info('debug-ls-bind','sync-value',element.value);}
else{if(element.textContent!=value){element.textContent=value;}}};let sync=(()=>{return()=>{if(debug){console.info('debug-ls-bind','sync-path',paths);console.info('debug-ls-bind','sync-syntax',syntax);console.info('debug-ls-bind','sync-syntax-parsed',parsedSyntax);console.info('debug-ls-bind','sync-value',element.value);}
for(let i=0;i<paths.length;i++){if('{{'+paths[i]+'}}'!==parsedSyntax){if(debug){console.info('debug-ls-bind','sync-skipped-path',paths[i]);console.info('debug-ls-bind','sync-skipped-syntax',syntax);console.info('debug-ls-bind','sync-skipped-syntax-parsed',parsedSyntax);}
continue;}
if(debug){console.info('debug-ls-bind','sync-loop-path',paths[i]);console.info('debug-ls-bind','sync-loop-syntax',parsedSyntax);}
@ -295,9 +295,10 @@ return value+" "+unit+" "+direction;}).add("ms2hum",function($value){let temp=$v
(minutes?minutes+"m ":"")+
Number.parseFloat(seconds).toFixed(0)+"s");}
return"< 1s";}).add("seconds2hum",function($value){var seconds=($value).toFixed(3);var minutes=($value/(60)).toFixed(1);var hours=($value/(60*60)).toFixed(1);var days=($value/(60*60*24)).toFixed(1);if(seconds<60){return seconds+"s";}else if(minutes<60){return minutes+"m";}else if(hours<24){return hours+"h";}else{return days+"d"}}).add("markdown",function($value,markdown){return markdown.render($value);}).add("pageCurrent",function($value,env){return Math.ceil(parseInt($value||0)/env.PAGING_LIMIT)+1;}).add("pageTotal",function($value,env){let total=Math.ceil(parseInt($value||0)/env.PAGING_LIMIT);return total?total:1;}).add("humanFileSize",function($value){if(!$value){return 0;}
let thresh=1000;if(Math.abs($value)<thresh){return $value+" B";}
let units=["kB","MB","GB","TB","PB","EB","ZB","YB"];let u=-1;do{$value/=thresh;++u;}while(Math.abs($value)>=thresh&&u<units.length-1);return($value.toFixed(1)+'<span class="text-size-small unit">'+
units[u]+"</span>");}).add("statsTotal",function($value){if(!$value){return 0;}
let thresh=1000;if(Math.abs($value)<thresh){return $value;}
let units=["kB","MB","GB","TB","PB","EB","ZB","YB"];let u=-1;do{$value/=thresh;++u;}while(Math.abs($value)>=thresh&&u<units.length-1);return $value.toFixed(1);}).add("humanFileUnit",function($value){if(!$value){return'';}
let thresh=1000;if(Math.abs($value)<thresh){return'B';}
let units=["kB","MB","GB","TB","PB","EB","ZB","YB"];let u=-1;do{$value/=thresh;++u;}while(Math.abs($value)>=thresh&&u<units.length-1);return units[u];}).add("statsTotal",function($value){if(!$value){return 0;}
$value=abbreviate($value,0,false,false);return $value==="0"?"N/A":$value;}).add("isEmpty",function($value){return(!!$value);}).add("isEmptyObject",function($value){return((Object.keys($value).length===0&&$value.constructor===Object)||$value.length===0)}).add("activeDomainsCount",function($value){let result=[];if(Array.isArray($value)){result=$value.filter(function(node){return(node.verification&&node.certificateId);});}
return result.length;}).add("documentAction",function(container){let collection=container.get('project-collection');let document=container.get('project-document');if(collection&&document&&!document.$id){return'database.createDocument';}
return'database.updateDocument';}).add("documentSuccess",function(container){let document=container.get('project-document');if(document&&!document.$id){return',redirect';}
@ -350,8 +351,8 @@ parsedFailure[i].slice(1),{}));}
element.$lsSkip=false;view.render(element);});};let events=event.trim().split(",");for(let y=0;y<events.length;y++){if(""===events[y]){continue;}
switch(events[y].trim()){case"load":exec();break;case"none":break;case"click":case"change":case"keypress":case"keydown":case"keyup":case"input":case"submit":element.addEventListener(events[y],exec);break;default:document.addEventListener(events[y],exec);}}}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-analytics",controller:function(element){let action=element.getAttribute("data-analytics-event")||"click";let doNotTrack=window.navigator.doNotTrack;if(doNotTrack=='1'){return;}
element.addEventListener(action,function(){let category=element.getAttribute("data-analytics-category")||"undefined";let label=element.getAttribute("data-analytics-label")||"undefined";if(!ga){console.error("Google Analytics ga object is not available");}
ga("send",{hitType:"event",eventCategory:category,eventAction:action,eventLabel:label});});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-analytics-activity",controller:function(window,element,appwrite,account){let action=element.getAttribute("data-analytics-event")||"click";let activity=element.getAttribute("data-analytics-label")||"None";let doNotTrack=window.navigator.doNotTrack;if(doNotTrack=='1'){return;}
element.addEventListener(action,function(){let email=account?.email||element.elements['email'].value||'';appwrite.analytics.create(email,'console',activity,window.location.href)});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-analytics-pageview",controller:function(window,router,env){if(!ga){console.error("Google Analytics ga object is not available");}
ga("send",{hitType:"event",eventCategory:category,eventAction:action,eventLabel:label});});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-analytics-activity",controller:function(window,element,appwrite,container){let action=element.getAttribute("data-analytics-event")||"click";let activity=element.getAttribute("data-analytics-label")||"None";let doNotTrack=window.navigator.doNotTrack;if(doNotTrack=='1'){return;}
element.addEventListener(action,function(){let account=container.get('account');let email=account?.email||element?.elements['email']?.value||'';appwrite.analytics.create(email,'console',activity,window.location.href)});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-analytics-pageview",controller:function(window,router,env){if(!ga){console.error("Google Analytics ga object is not available");}
let doNotTrack=window.navigator.doNotTrack;if(doNotTrack=='1'){return;}
let project=router.params["project"]||'None';ga("set","page",window.location.pathname);ga("set","dimension1",project);ga('set','dimension2',env.VERSION);ga('set','dimension3',env.SETUP);ga("send","pageview");}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-clone",controller:function(element,document,view){var template=element.innerHTML.toString();var label=element.dataset["label"]||"Add";var icon=element.dataset["icon"]||null;var target=element.dataset["target"]||null;var first=parseInt(element.dataset["first"]||1);var button=document.createElement("button");button.type="button";button.innerText=" "+label+" ";button.classList.add("margin-end");button.classList.add("margin-bottom-small");button.classList.add("reverse");if(icon){var iconElement=document.createElement("i");iconElement.className=icon;button.insertBefore(iconElement,button.firstChild);}
if(target){target=document.getElementById(target);}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -116,7 +116,7 @@ container.path(paths[i],value);}});}
return;}
if(element.value!==value){element.value=value;element.dispatchEvent(new Event('change'));}
if(bind){element.addEventListener('input',sync);element.addEventListener('change',sync);}}
else{if(element.innerHTML!=value){element.innerHTML=value;}}};let sync=(()=>{return()=>{if(debug){console.info('debug-ls-bind','sync-path',paths);console.info('debug-ls-bind','sync-syntax',syntax);console.info('debug-ls-bind','sync-syntax-parsed',parsedSyntax);console.info('debug-ls-bind','sync-value',element.value);}
else{if(element.textContent!=value){element.textContent=value;}}};let sync=(()=>{return()=>{if(debug){console.info('debug-ls-bind','sync-path',paths);console.info('debug-ls-bind','sync-syntax',syntax);console.info('debug-ls-bind','sync-syntax-parsed',parsedSyntax);console.info('debug-ls-bind','sync-value',element.value);}
for(let i=0;i<paths.length;i++){if('{{'+paths[i]+'}}'!==parsedSyntax){if(debug){console.info('debug-ls-bind','sync-skipped-path',paths[i]);console.info('debug-ls-bind','sync-skipped-syntax',syntax);console.info('debug-ls-bind','sync-skipped-syntax-parsed',parsedSyntax);}
continue;}
if(debug){console.info('debug-ls-bind','sync-loop-path',paths[i]);console.info('debug-ls-bind','sync-loop-syntax',parsedSyntax);}

View file

@ -133,7 +133,7 @@ window.ls.filter
let thresh = 1000;
if (Math.abs($value) < thresh) {
return $value + " B";
return $value;
}
let units = ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
@ -144,12 +144,28 @@ window.ls.filter
++u;
} while (Math.abs($value) >= thresh && u < units.length - 1);
return (
$value.toFixed(1) +
'<span class="text-size-small unit">' +
units[u] +
"</span>"
);
return $value.toFixed(1);
})
.add("humanFileUnit", function($value) {
if (!$value) {
return '';
}
let thresh = 1000;
if (Math.abs($value) < thresh) {
return 'B';
}
let units = ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
let u = -1;
do {
$value /= thresh;
++u;
} while (Math.abs($value) >= thresh && u < units.length - 1);
return units[u];
})
.add("statsTotal", function($value) {
if (!$value) {

View file

@ -3,7 +3,7 @@
window.ls.container.get("view").add({
selector: "data-analytics-activity",
controller: function(window, element, appwrite, account) {
controller: function(window, element, appwrite, container) {
let action = element.getAttribute("data-analytics-event") || "click";
let activity = element.getAttribute("data-analytics-label") || "None";
let doNotTrack = window.navigator.doNotTrack;
@ -13,8 +13,9 @@
}
element.addEventListener(action, function() {
let email = account?.email || element.elements['email'].value || '';
let account = container.get('account');
let email = account?.email || element?.elements['email']?.value || '';
appwrite.analytics.create(email, 'console', activity, window.location.href)
});
}

View file

@ -7,6 +7,14 @@
display: block;
border-bottom: none;
&.padding-tiny {
padding: 5px;
}
&.padding-xs {
padding: 10px;
}
&.padding-small {
padding: 15px;
}
@ -53,7 +61,7 @@
}
&.warning {
background: var(--config-color-success);
background: var(--config-color-warning);
color: #2d2d2d;
button,

View file

@ -53,6 +53,14 @@
}
}
&.padding-tiny {
padding: 5px;
}
&.padding-xs {
padding: 10px;
}
&.padding-small {
padding: 15px;
}

View file

@ -27,7 +27,7 @@
--config-color-fade-super: #f1f3f5;
--config-color-danger: #f53d3d;
--config-color-success: #1bbf61;
--config-color-warning: #ffed4d;
--config-color-warning: #fffbdd;
--config-color-info: #386fd2;
--config-border-color: #f3f3f3;
--config-border-fade: #e0e3e4;

View file

@ -1,220 +0,0 @@
<?php
namespace Appwrite\Resize;
use Exception;
use Imagick;
class Resize
{
private $image;
private $width;
private $height;
/**
* @param string $data
*
* @throws Exception
*/
public function __construct($data)
{
$this->image = new Imagick();
$this->image->readImageBlob($data);
$this->width = $this->image->getImageWidth();
$this->height = $this->image->getImageHeight();
}
/**
* @param int $width
* @param int $height
*
* @return Resize
*
* @throws \Throwable
*/
public function crop(int $width, int $height)
{
$originalAspect = $this->width / $this->height;
if (empty($width)) {
$width = $height * $originalAspect;
}
if (empty($height)) {
$height = $width / $originalAspect;
}
if (empty($height) && empty($width)) {
$height = $this->height;
$width = $this->width;
}
if ($this->image->getImageFormat() == 'GIF') {
$this->image = $this->image->coalesceImages();
foreach ($this->image as $frame) {
$frame->cropThumbnailImage($width, $height);
}
$this->image->deconstructImages();
} else {
$this->image->cropThumbnailImage($width, $height);
}
return $this;
}
/**
* @param $color
*
* @return Resize
*
* @throws \Throwable
*/
public function setBackground($color)
{
$this->image->setImageBackgroundColor($color);
$this->image = $this->image->mergeImageLayers(Imagick::LAYERMETHOD_FLATTEN);
return $this;
}
/**
* Output.
*
* Prints manipulated image.
*
* @param string $type
* @param int $quality
*
* @return string
*
* @throws Exception
*/
public function output(string $type, int $quality = 75)
{
return $this->save(null, $type, $quality);
}
/**
* @param string $path
* @param $type
* @param int $quality
*
* @return string
*
* @throws Exception
*/
public function save(string $path = null, string $type = '', int $quality = 75)
{
// Create directory with write permissions
if (null !== $path && !\file_exists(\dirname($path))) {
if (!@\mkdir(\dirname($path), 0755, true)) {
throw new Exception('Can\'t create directory '.\dirname($path));
}
}
switch ($type) {
case 'jpg':
case 'jpeg':
$this->image->setImageCompressionQuality($quality);
$this->image->setImageFormat('jpg');
break;
case 'gif':
$this->image->setImageFormat('gif');
break;
case 'webp':
try {
$this->image->setImageFormat('webp');
} catch (\Throwable $th) {
$signature = $this->image->getImageSignature();
$temp = '/tmp/temp-'.$signature.'.'.\strtolower($this->image->getImageFormat());
$output = '/tmp/output-'.$signature.'.webp';
// save temp
$this->image->writeImages($temp, true);
// convert temp
\exec("cwebp -quiet -metadata none -q $quality $temp -o $output");
$data = \file_get_contents($output);
//load webp
if (empty($path)) {
return $data;
} else {
\file_put_contents($path, $data, LOCK_EX);
}
$this->image->clear();
$this->image->destroy();
//delete webp
\unlink($output);
\unlink($temp);
return;
}
break;
case 'png':
/* Scale quality from 0-100 to 0-9 */
$scaleQuality = \round(($quality / 100) * 9);
/* Invert quality setting as 0 is best, not 9 */
$invertScaleQuality = 9 - $scaleQuality;
$this->image->setImageCompressionQuality($invertScaleQuality);
$this->image->setImageFormat('png');
break;
default:
throw new Exception('Invalid output type given');
break;
}
if (empty($path)) {
return $this->image->getImagesBlob();
} else {
$this->image->writeImages($path, true);
}
$this->image->clear();
$this->image->destroy();
}
/**
* @param int $newHeight
*
* @return int
*/
protected function getSizeByFixedHeight(int $newHeight):int
{
$ratio = $this->width / $this->height;
$newWidth = $newHeight * $ratio;
return $newWidth;
}
/**
* @param int $newWidth
*
* @return int
*/
protected function getSizeByFixedWidth(int $newWidth):int
{
$ratio = $this->height / $this->width;
$newHeight = $newWidth * $ratio;
return $newHeight;
}
}

View file

@ -186,4 +186,27 @@ class HTTPTest extends Scope
$this->assertEquals($body['continents']['AN'], 'Antarctica');
$this->assertEquals($body['continents']['AS'], 'Asia');
}
public function testVersions() {
/**
* Test without header
*/
$response = $this->client->call(Client::METHOD_GET, '/versions', array_merge([
'content-type' => 'application/json',
], $this->getHeaders()));
$body = $response['body'];
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertIsString($body['server']);
$this->assertIsString($body['client-web']);
$this->assertIsString($body['client-flutter']);
$this->assertIsString($body['console-web']);
$this->assertIsString($body['server-nodejs']);
$this->assertIsString($body['server-deno']);
$this->assertIsString($body['server-php']);
$this->assertIsString($body['server-python']);
$this->assertIsString($body['server-ruby']);
$this->assertIsString($body['server-cli']);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 596 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View file

@ -38,7 +38,7 @@ class DetectorTest extends TestCase
'clientName' => 'Firefox',
'clientVersion' => '47.0',
'clientEngine' => 'Gecko',
'clientEngineVersion' => '',
'clientEngineVersion' => '47.0',
]);
}

View file

@ -1,170 +0,0 @@
<?php
namespace Appwrite\Tests;
use Appwrite\Resize\Resize;
use PHPUnit\Framework\TestCase;
class ResizeTest extends TestCase
{
public function setUp(): void
{
}
public function tearDown(): void
{
}
public function testCrop100x100()
{
$resize = new Resize(\file_get_contents(__DIR__ . '/../../resources/disk-a/kitten-1.jpg'));
$target = __DIR__.'/100x100.jpg';
$resize->crop(100, 100);
$resize->save($target, 'jpg', 100);
$this->assertEquals(\is_readable($target), true);
$this->assertNotEmpty(\md5(\file_get_contents($target)));
$image = new \Imagick($target);
$this->assertEquals(100, $image->getImageWidth());
$this->assertEquals(100, $image->getImageHeight());
$this->assertEquals('JPEG', $image->getImageFormat());
\unlink($target);
}
public function testCrop100x400()
{
$resize = new Resize(\file_get_contents(__DIR__ . '/../../resources/disk-a/kitten-1.jpg'));
$target = __DIR__.'/100x400.jpg';
$resize->crop(100, 400);
$resize->save($target, 'jpg', 100);
$this->assertEquals(\is_readable($target), true);
$this->assertNotEmpty(\md5(\file_get_contents($target)));
$image = new \Imagick($target);
$this->assertEquals(100, $image->getImageWidth());
$this->assertEquals(400, $image->getImageHeight());
$this->assertEquals('JPEG', $image->getImageFormat());
\unlink($target);
}
public function testCrop400x100()
{
$resize = new Resize(\file_get_contents(__DIR__ . '/../../resources/disk-a/kitten-1.jpg'));
$target = __DIR__.'/400x100.jpg';
$resize->crop(400, 100);
$resize->save($target, 'jpg', 100);
$this->assertEquals(\is_readable($target), true);
$this->assertNotEmpty(\md5(\file_get_contents($target)));
$image = new \Imagick($target);
$this->assertEquals(400, $image->getImageWidth());
$this->assertEquals(100, $image->getImageHeight());
$this->assertEquals('JPEG', $image->getImageFormat());
\unlink($target);
}
public function testCrop100x100WEBP()
{
$resize = new Resize(\file_get_contents(__DIR__ . '/../../resources/disk-a/kitten-1.jpg'));
$target = __DIR__.'/100x100.webp';
$original = __DIR__.'/../../resources/resize/100x100.webp';
$resize->crop(100, 100);
$resize->save($target, 'webp', 100);
$this->assertEquals(\is_readable($target), true);
$this->assertNotEmpty(\md5(\file_get_contents($target)));
$image = new \Imagick($target);
$this->assertEquals(100, $image->getImageWidth());
$this->assertEquals(100, $image->getImageHeight());
$this->assertTrue(in_array($image->getImageFormat(), ['PAM', 'WEBP']));
\unlink($target);
}
public function testCrop100x100PNG()
{
$resize = new Resize(\file_get_contents(__DIR__ . '/../../resources/disk-a/kitten-1.jpg'));
$target = __DIR__.'/100x100.png';
$original = __DIR__.'/../../resources/resize/100x100.png';
$resize->crop(100, 100);
$resize->save($target, 'png', 100);
$this->assertEquals(\is_readable($target), true);
$this->assertGreaterThan(15000, \filesize($target));
$this->assertLessThan(30000, \filesize($target));
$this->assertEquals(\mime_content_type($target), \mime_content_type($original));
$this->assertNotEmpty(\md5(\file_get_contents($target)));
$image = new \Imagick($target);
$this->assertEquals(100, $image->getImageWidth());
$this->assertEquals(100, $image->getImageHeight());
$this->assertEquals('PNG', $image->getImageFormat());
\unlink($target);
}
public function testCrop100x100PNGQuality30()
{
$resize = new Resize(\file_get_contents(__DIR__ . '/../../resources/disk-a/kitten-1.jpg'));
$target = __DIR__.'/100x100-q30.jpg';
$original = __DIR__.'/../../resources/resize/100x100-q30.jpg';
$resize->crop(100, 100);
$resize->save($target, 'jpg', 10);
$this->assertEquals(\is_readable($target), true);
$this->assertGreaterThan(500, \filesize($target));
$this->assertLessThan(2000, \filesize($target));
$this->assertEquals(\mime_content_type($target), \mime_content_type($original));
$this->assertNotEmpty(\md5(\file_get_contents($target)));
$image = new \Imagick($target);
$this->assertEquals(100, $image->getImageWidth());
$this->assertEquals(100, $image->getImageHeight());
$this->assertEquals('JPEG', $image->getImageFormat());
\unlink($target);
}
public function testCrop100x100GIF()
{
$resize = new Resize(\file_get_contents(__DIR__ . '/../../resources/disk-a/kitten-3.gif'));
$target = __DIR__.'/100x100.gif';
$original = __DIR__.'/../../resources/resize/100x100.gif';
$resize->crop(100, 100);
$resize->save($target, 'gif', 100);
$this->assertEquals(\is_readable($target), true);
$this->assertGreaterThan(400000, \filesize($target));
$this->assertLessThan(800000, \filesize($target));
$this->assertEquals(\mime_content_type($target), \mime_content_type($original));
$this->assertNotEmpty(\md5(\file_get_contents($target)));
$image = new \Imagick($target);
$this->assertEquals(100, $image->getImageWidth());
$this->assertEquals(100, $image->getImageHeight());
$this->assertEquals('GIF', $image->getImageFormat());
\unlink($target);
}
}