mirror of
https://github.com/chevereto/chevereto.git
synced 2026-05-07 01:57:43 +02:00
Automatic push 4.5.0
This commit is contained in:
2
.github/test.yml
vendored
2
.github/test.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
operating-system: [ubuntu-22.04]
|
||||
php-versions: ["8.0", "8.1"]
|
||||
php-versions: ["8.2"]
|
||||
env:
|
||||
extensions: pcov, imagick
|
||||
tools: composer
|
||||
|
||||
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-22.04]
|
||||
php: ["8.1"]
|
||||
php: ["8.2"]
|
||||
env:
|
||||
tools: composer
|
||||
ini-values: default_charset='UTF-8'
|
||||
|
||||
2
.github/workflows/release-free.yml
vendored
2
.github/workflows/release-free.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
operating-system: [ubuntu-latest]
|
||||
php-versions: ["8.1"]
|
||||
php-versions: ["8.2"]
|
||||
env:
|
||||
tools: composer
|
||||
ini-values: default_charset='UTF-8'
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
Chevereto 4.4.2 (2026-01-06)
|
||||
|
||||
- Fixed bug with "Upgrade now" button display
|
||||
- Fixed bug in /settings/api route display
|
||||
- Fixed bug in delete URL not working
|
||||
- Fixed bug in Free edition missing Follow class
|
||||
- Fixed API documentation links
|
||||
28
.package/4.5.0.txt
Normal file
28
.package/4.5.0.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
Chevereto 4.5.0 (2026-04-08)
|
||||
|
||||
- Added /_/api/4/auth/verify route
|
||||
- Added /_/api/4/config/traefik internal HTTP provider route
|
||||
- Added /_/api/4/tenants/{id}/user-password-reset route
|
||||
- Added CHEVERETO_ENABLE_GUESTS env for controlling guest interactions
|
||||
- Added CHEVERETO_SERVICE_NAME env for specifying the service name
|
||||
- Added CHEVERETO_TRIAL_ENABLE_* keys support for controlling features enabled during trial
|
||||
- Added CHEVERETO_TRIAL_MAX_* keys support for controlling max limits during trial
|
||||
- Added CHEVERETO_TRIAL env for controlling trial mode
|
||||
- Added envTrialAware helper function for accessing trial-aware env variables
|
||||
- Added version-installed command
|
||||
- Added login_providers tenant stats
|
||||
- Added password parameter for password-reset command
|
||||
- Added port 8080 to the list of allowed ports
|
||||
- Added support for more email providers: AhaSend, Amazon SES, Azure, Brevo, Infobip, MailerSend, Mailgun, Mailjet, Mailomat, MailPace, Mailtrap, Mandrill, Microsoft Graph, Postal, Postmark, Resend, Scaleway, SendGrid and Sweego
|
||||
- Bumped minimum PHP version to 8.2
|
||||
- Fixed "Powered by" message
|
||||
- Fixed bug affecting homepage (free edition)
|
||||
- Fixed bug on /_/api/4/* routes missing error responses
|
||||
- Fixed bug on Tenants jobs:worker command when passing tenant id
|
||||
- Fixed bug on Tenants caching system
|
||||
- Fixed bug on tenants CLI database-migrate command
|
||||
- Fixed bug preventing Tenant installation
|
||||
- Fixed missing custom semantics parsing for image route description
|
||||
- Improved "Something went wrong" error page for both SaaS and self-hosted contexts
|
||||
- Improved album dropdown options on uploader
|
||||
- Renamed env variable CHEVERETO_JOBS_WORKER_INTERVAL to CHEVERETO_SCHEDULER_INTERVAL
|
||||
@@ -246,6 +246,7 @@ if($missingOptions ?? false) {
|
||||
$envDefault = require dirname(__DIR__, 1) . '/env-default.php';
|
||||
$envVar = array_merge($envDefault, ENV, getCheveretoEnv());
|
||||
$envVar['CHEVERETO_DB_TABLE_ROOT_PREFIX'] = $envVar['CHEVERETO_DB_TABLE_PREFIX'];
|
||||
$envVar['CHEVERETO_CACHE_KEY_ROOT_PREFIX'] = $envVar['CHEVERETO_CACHE_KEY_PREFIX'];
|
||||
$envVar['CHEVERETO_DB_TABLE_PREFIX'] .= '_'; // chv__
|
||||
$envVar['CHEVERETO_CACHE_KEY_PREFIX'] .= '_:'; // chv:_:
|
||||
new EnvVar($envVar);
|
||||
@@ -273,6 +274,7 @@ $redis->connect(env()['CHEVERETO_CACHE_HOST'], (int) env()['CHEVERETO_CACHE_PORT
|
||||
if (env()['CHEVERETO_CACHE_PASSWORD'] !== '') {
|
||||
$redis->auth(env()['CHEVERETO_CACHE_PASSWORD']);
|
||||
}
|
||||
$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
|
||||
new EncryptionInstance(
|
||||
new Encryption(
|
||||
new Key(env()['CHEVERETO_ENCRYPTION_KEY'])
|
||||
@@ -494,7 +496,7 @@ $databaseMigrate = function() use ($tenants, $opts, $logger): void {
|
||||
}
|
||||
foreach ($tenantsUpdate as $tenant) {
|
||||
runAppCommand(
|
||||
command: ['-C', 'database-update'],
|
||||
command: ['-C', 'database-migrate'],
|
||||
env: array_merge($_ENV, [
|
||||
'CHEVERETO_TENANT' => $tenant['id'],
|
||||
]),
|
||||
@@ -527,7 +529,7 @@ $jobsWorker = function() use ($tenants, $opts, $logger): void {
|
||||
while (true) {
|
||||
$now = time();
|
||||
if($opts['id'] ?? null) {
|
||||
$tenantsId = $opts['id'];
|
||||
$tenantsId = [$opts['id']];
|
||||
} else {
|
||||
$tenantsId = $tenants->getTenantsIds(
|
||||
['is_enabled' => true],
|
||||
@@ -539,7 +541,7 @@ $jobsWorker = function() use ($tenants, $opts, $logger): void {
|
||||
}
|
||||
foreach ($tenantsId as $tenantId) {
|
||||
$tenant = $tenants->getTenant($tenantId);
|
||||
$intervalSec = (int) ($tenant->env['CHEVERETO_JOBS_WORKER_INTERVAL'] ?? 300);
|
||||
$intervalSec = (int) ($tenant->env['CHEVERETO_SCHEDULER_INTERVAL'] ?? 300);
|
||||
$mustRun = $tenant->lastJobAt === null
|
||||
|| (strtotime($tenant->lastJobAt) + $intervalSec) <= $now;
|
||||
if ($mustRun) {
|
||||
@@ -547,11 +549,27 @@ $jobsWorker = function() use ($tenants, $opts, $logger): void {
|
||||
tenantId: $tenantId,
|
||||
lastJobAt: datetimegmt()
|
||||
);
|
||||
$commandEnv = array_merge($_ENV, [
|
||||
'CHEVERETO_TENANT' => $tenantId,
|
||||
]);
|
||||
$commandExit = runAppCommand(
|
||||
command: ['-C', 'version-installed'],
|
||||
env: $commandEnv,
|
||||
isVerbose: $isVerbose,
|
||||
logger: $logger
|
||||
);
|
||||
if($commandExit === 255) {
|
||||
$logger->write(
|
||||
<<<PLAIN
|
||||
Tenant {$tenantId} app is not installed, skipping
|
||||
|
||||
PLAIN
|
||||
);
|
||||
continue;
|
||||
}
|
||||
runAppCommand(
|
||||
command: ['-C', 'cron'],
|
||||
env: array_merge($_ENV, [
|
||||
'CHEVERETO_TENANT' => $tenantId,
|
||||
]),
|
||||
env: $commandEnv,
|
||||
isVerbose: $isVerbose,
|
||||
logger: $logger
|
||||
);
|
||||
|
||||
@@ -16,29 +16,27 @@
|
||||
"composer/package-versions-deprecated": true
|
||||
},
|
||||
"platform": {
|
||||
"php": "8.1.28"
|
||||
"php": "8.2"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"php": "^8.2",
|
||||
"intervention/image": "^2.6",
|
||||
"jeroendesloovere/xmp-metadata-extractor": "^2.0",
|
||||
"guzzlehttp/psr7": "^1.7||^2",
|
||||
"aws/aws-sdk-php": "^3.336.15",
|
||||
"phpmailer/phpmailer": "^6.5",
|
||||
"psr/cache": "^1",
|
||||
"psr/cache": "^3.0",
|
||||
"psr/log": "^1||^2||^3",
|
||||
"phpseclib/phpseclib": "^3.0.37",
|
||||
"mobiledetect/mobiledetectlib": "^2.8",
|
||||
"mlocati/ip-lib": "^1.17",
|
||||
"composer/ca-bundle": "^1.2",
|
||||
"chevere/throwable-handler": "^1.0.2",
|
||||
"pragmarx/google2fa": "^8.0",
|
||||
"pragmarx/google2fa-qrcode": "^3.0",
|
||||
"phpseclib/bcmath_compat": "^2.0",
|
||||
"chillerlan/php-qrcode": "^4.3",
|
||||
"firebase/php-jwt": "^6.3",
|
||||
"lychee-org/php-exif": "1.0.2",
|
||||
"firebase/php-jwt": "^7.0",
|
||||
"lychee-org/php-exif": "^1.0.2",
|
||||
"p3k/emoji-detector": "^1.0",
|
||||
"php-ffmpeg/php-ffmpeg": "^1.2",
|
||||
"psy/psysh": "^0.11.8",
|
||||
@@ -46,11 +44,32 @@
|
||||
"chevere/var-dump": "^2.0.7",
|
||||
"chevere/var-support": "^1.0",
|
||||
"matthiasmullie/scrapbook": "^1.0",
|
||||
"xrdebug/php": "^3.0",
|
||||
"donatj/phpuseragentparser": "^1.11",
|
||||
"chevere/router": "^0.9.1",
|
||||
"chevere/http": "^0.7.1",
|
||||
"laminas/laminas-httphandlerrunner": "^2.13"
|
||||
"laminas/laminas-httphandlerrunner": "^2.13",
|
||||
"chevere/throwable-handler": "^1.0",
|
||||
"symfony/mailer": "^7.4",
|
||||
"symfony/http-client": "^7.4",
|
||||
"symfony/amazon-mailer": "^7.4",
|
||||
"symfony/aha-send-mailer": "^7.4",
|
||||
"symfony/azure-mailer": "^7.4",
|
||||
"symfony/brevo-mailer": "^7.4",
|
||||
"symfony/infobip-mailer": "^7.4",
|
||||
"symfony/mailgun-mailer": "^7.4",
|
||||
"symfony/mailjet-mailer": "^7.4",
|
||||
"symfony/mailomat-mailer": "^7.4",
|
||||
"symfony/mail-pace-mailer": "^7.4",
|
||||
"symfony/mailer-send-mailer": "^7.4",
|
||||
"symfony/mailtrap-mailer": "^7.4",
|
||||
"symfony/mailchimp-mailer": "^7.4",
|
||||
"symfony/microsoft-graph-mailer": "^7.4",
|
||||
"symfony/postal-mailer": "^7.4",
|
||||
"symfony/postmark-mailer": "^7.4",
|
||||
"symfony/resend-mailer": "^7.4",
|
||||
"symfony/scaleway-mailer": "^7.4",
|
||||
"symfony/sendgrid-mailer": "^7.4",
|
||||
"symfony/sweego-mailer": "^7.4"
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
|
||||
3210
app/composer.lock
generated
3210
app/composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -90,7 +90,6 @@ return [
|
||||
'CHEVERETO_HTTPS' => '1',
|
||||
'CHEVERETO_IMAGE_FORMATS_AVAILABLE' => '["AVIF","JPEG","PNG","BMP","GIF","WEBP"]',
|
||||
'CHEVERETO_IMAGE_LIBRARY' => 'imagick',
|
||||
'CHEVERETO_JOBS_WORKER_INTERVAL' => '300',
|
||||
'CHEVERETO_MAX_ADMINS' => '0',
|
||||
'CHEVERETO_MAX_ALBUMS' => '0',
|
||||
'CHEVERETO_MAX_CACHE_TTL' => '86400',
|
||||
@@ -115,6 +114,8 @@ return [
|
||||
'CHEVERETO_MIN_STORAGES_ACTIVE' => '0',
|
||||
'CHEVERETO_PROVIDER_NAME' => 'Self-hosted Chevereto',
|
||||
'CHEVERETO_PROVIDER_URL' => '',
|
||||
'CHEVERETO_SCHEDULER_INTERVAL' => '300',
|
||||
'CHEVERETO_SERVICE_NAME' => 'app',
|
||||
'CHEVERETO_SERVICING' => 'server',
|
||||
'CHEVERETO_SESSION_SAVE_HANDLER' => 'files',
|
||||
'CHEVERETO_SESSION_SAVE_PATH' => '/tmp',
|
||||
@@ -123,6 +124,7 @@ return [
|
||||
'CHEVERETO_TENANTS_API_ALLOW_LIST' => '',
|
||||
'CHEVERETO_TENANTS_API_KEY_SECRET' => '',
|
||||
'CHEVERETO_TENANTS_API_REQUEST_SECRET' => '',
|
||||
'CHEVERETO_TRIAL' => '0',
|
||||
'CHEVERETO_XRDEBUG_HOST' => 'localhost',
|
||||
'CHEVERETO_XRDEBUG_HTTPS' => '0',
|
||||
'CHEVERETO_XRDEBUG_KEY' => '',
|
||||
|
||||
@@ -10,16 +10,22 @@
|
||||
*/
|
||||
|
||||
use Chevereto\Legacy\Classes\Login;
|
||||
use Chevereto\Legacy\Classes\Settings;
|
||||
use Chevereto\Legacy\Classes\User;
|
||||
use function Chevere\Standard\randomString;
|
||||
|
||||
$opts = getopt('C:u:') ?: [];
|
||||
$opts = getopt('C:u:x:') ?: [];
|
||||
$missing = [];
|
||||
if (! isset($opts['u'])) {
|
||||
echo '[Error] Missing username' . "\n";
|
||||
exit(255);
|
||||
}
|
||||
$password = randomString(24);
|
||||
$password = $opts['x'] ?? randomString(24);
|
||||
if (! preg_match('/' . Settings::USER_PASSWORD_PATTERN . '/', $password)) {
|
||||
echo '[Error] Invalid password' . "\n";
|
||||
exit(255);
|
||||
}
|
||||
|
||||
$user = User::getSingle($opts['u'], 'username');
|
||||
if ($user === []) {
|
||||
echo '[Error] User not found' . "\n";
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chevereto\Legacy\Classes;
|
||||
use function Chevereto\Legacy\cheveretoVersionInstalled;
|
||||
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
|
||||
class Mailer extends PHPMailer
|
||||
{
|
||||
public $XMailer = ' ';
|
||||
$version = cheveretoVersionInstalled();
|
||||
if ($version === '') {
|
||||
echo 'Chevereto is not installed' . PHP_EOL;
|
||||
exit(255);
|
||||
}
|
||||
echo $version . PHP_EOL;
|
||||
@@ -41,6 +41,7 @@ $options = [
|
||||
'setting-update',
|
||||
'database-migrate',
|
||||
'version',
|
||||
'version-installed',
|
||||
'stats',
|
||||
'stats-rebuild',
|
||||
];
|
||||
|
||||
@@ -59,6 +59,16 @@ if (cheveretoVersionInstalled() !== ''
|
||||
|
||||
throw new LogicException(message('Request denied. You must be an admin to be here.'), 403);
|
||||
}
|
||||
if ((env()['CHEVERETO_TENANT'] ?? '') !== '') {
|
||||
if (env()['CHEVERETO_DB_TABLE_PREFIX'] === env()['CHEVERETO_DB_TABLE_ROOT_PREFIX']
|
||||
|| env()['CHEVERETO_CACHE_KEY_PREFIX'] === env()['CHEVERETO_CACHE_KEY_ROOT_PREFIX']
|
||||
) {
|
||||
throw new LogicException(
|
||||
'Tenant namespace collision detected. Refusing install/update with root prefixes.',
|
||||
600
|
||||
);
|
||||
}
|
||||
}
|
||||
if (function_exists('opcache_reset')) {
|
||||
try {
|
||||
opcache_reset();
|
||||
@@ -121,7 +131,7 @@ $settings_updates = [
|
||||
// 'google' => 0, // Deprecated in 4.0.0-beta.11
|
||||
// 'google_client_id' => '',
|
||||
// 'google_client_secret' => '',
|
||||
'guest_uploads' => 1,
|
||||
'guest_uploads' => intval(env()['CHEVERETO_CONTEXT'] !== 'saas'),
|
||||
'listing_items_per_page' => '24',
|
||||
'maintenance' => 0,
|
||||
'captcha' => 0, //recaptcha
|
||||
@@ -660,6 +670,36 @@ $settings_updates = [
|
||||
'4.4.0' => null,
|
||||
'4.4.1' => null,
|
||||
'4.4.2' => null,
|
||||
'4.5.0' => [
|
||||
'email_ahasend_api_key' => '',
|
||||
'email_ses_access_key' => '',
|
||||
'email_ses_secret_key' => '',
|
||||
'email_azure_resource_name' => '',
|
||||
'email_azure_key' => '',
|
||||
'email_brevo_api_key' => '',
|
||||
'email_infobip_api_key' => '',
|
||||
'email_infobip_base_url' => '',
|
||||
'email_mailgun_api_key' => '',
|
||||
'email_mailgun_domain' => '',
|
||||
'email_mailjet_access_key' => '',
|
||||
'email_mailjet_secret_key' => '',
|
||||
'email_mailomat_api_key' => '',
|
||||
'email_mailpace_api_token' => '',
|
||||
'email_mailersend_api_key' => '',
|
||||
'email_mailtrap_api_token' => '',
|
||||
'email_mandrill_api_key' => '',
|
||||
'email_microsoftgraph_client_id' => '',
|
||||
'email_microsoftgraph_client_secret' => '',
|
||||
'email_microsoftgraph_tenant_id' => '',
|
||||
'email_postal_api_key' => '',
|
||||
'email_postal_base_url' => '',
|
||||
'email_postmark_api_token' => '',
|
||||
'email_resend_api_key' => '',
|
||||
'email_scaleway_project_id' => '',
|
||||
'email_scaleway_api_key' => '',
|
||||
'email_sendgrid_api_key' => '',
|
||||
'email_sweego_api_key' => '',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
const APP_VERSION = '4.4.2';
|
||||
const APP_VERSION_AKA = 'vivaracho';
|
||||
const APP_VERSION = '4.5.0';
|
||||
const APP_VERSION_AKA = 'elevado';
|
||||
|
||||
@@ -93,21 +93,49 @@ set_exception_handler(function (Throwable $throwable) {
|
||||
$internalHandler = $publicHandler->withIsDebug(true);
|
||||
$doDebug = in_array($debugLevel, [2, 3], true) || isDebug();
|
||||
if ($doDebug === false) {
|
||||
$publicHandler = $publicHandler
|
||||
->withIsDebug($doDebug)
|
||||
->withPutExtra(
|
||||
'Why am I seeing this?',
|
||||
$incidentId = $publicHandler->id();
|
||||
$providerName = getenv('CHEVERETO_PROVIDER_NAME') ?: '<provider name>';
|
||||
$providerUrl = getenv('CHEVERETO_PROVIDER_URL') ?: '#';
|
||||
if (getenv('CHEVERETO_CONTEXT') === 'saas') {
|
||||
$title = 'Service temporarily unavailable';
|
||||
$message = "We're already on it! Our team has been automatically notified and is working to resolve this issue.";
|
||||
$ownerGuide = strtr(
|
||||
<<<HTML
|
||||
For security reasons, detailed error information is not shown. This incident has been logged and will be reviewed by the system administrator.
|
||||
<p>Our team is already investigating this incident. For immediate assistance, contact %providerLink% support with incident ID %id%.</p>
|
||||
HTML,
|
||||
[
|
||||
'%providerLink%' => <<<HTML
|
||||
<a href="{$providerUrl}" target="_blank">{$providerName}</a>
|
||||
HTML,
|
||||
'%id%' => $incidentId,
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$title = 'Something went wrong';
|
||||
$message = 'Please try again later. If the error persists you may need to contact the website owner.';
|
||||
$ownerGuide = <<<HTML
|
||||
<p>This error has been logged with ID {$incidentId}. To diagnose the issue:</p>
|
||||
<ol>
|
||||
<li>Check your server error logs for this ID</li>
|
||||
<li>Review the <a href="https://v4-docs.chevereto.com/developer/how-to/debug" target="_blank">debugging guide</a> in the documentation</li>
|
||||
<li>Need help? Check our <a href="https://chevereto.com/support" target="_blank">support</a> alternatives</li>
|
||||
</ol>
|
||||
HTML;
|
||||
}
|
||||
$publicHandler = $publicHandler
|
||||
->withTitle($title)
|
||||
->withIsDebug($doDebug)
|
||||
->withMessage($message)
|
||||
->withPutExtra(
|
||||
'What happened?',
|
||||
<<<HTML
|
||||
<p>A technical error has occurred. The incident has been logged for investigation.</p>
|
||||
HTML
|
||||
)
|
||||
->withPutExtra(
|
||||
'Administrator guide',
|
||||
'Are you the owner of this website?',
|
||||
<<<HTML
|
||||
<ul>
|
||||
<li>Refer to the <a href="https://v4-docs.chevereto.com/developer/how-to/debug" target="_blank">Chevereto documentation</a> to understand how to debug this error.</li>
|
||||
<li>Need help? Visit <a href="https://chevereto.com/support" target="_blank">Chevereto support</a> to open a ticket.</li>
|
||||
</ul>
|
||||
{$ownerGuide}
|
||||
<style>.administrator-guide ul{margin:0;padding-left:1.5em}</style>
|
||||
HTML
|
||||
);
|
||||
|
||||
@@ -10,9 +10,11 @@
|
||||
*/
|
||||
|
||||
use Chevere\Http\Exceptions\ControllerException;
|
||||
use Chevere\Http\Exceptions\MethodNotAllowedException;
|
||||
use Chevere\Parameter\Interfaces\TypeInterface;
|
||||
use Chevere\Parameter\Type;
|
||||
use Chevere\Router\Container;
|
||||
use Chevere\Router\Exceptions\NotFoundException;
|
||||
use Chevere\Writer\NullWriter;
|
||||
use Chevereto\Config\Config;
|
||||
use Chevereto\Legacy\Classes\Cache;
|
||||
@@ -125,11 +127,32 @@ if ($isTenantsApiRouting) {
|
||||
foreach ($headers as $name => $value) {
|
||||
$serverRequest = $serverRequest->withHeader($name, $value);
|
||||
}
|
||||
$router = router(require dirname(__DIR__, 2) . '/routes/tenants-api-v4.php');
|
||||
$routerPath = dirname(__DIR__, 2) . '/routes/';
|
||||
$routes = [
|
||||
require $routerPath . 'tenants-api-v4.php',
|
||||
];
|
||||
if (in_array(server()['SERVER_NAME'], ['localhost', '127.0.0.1', '::1', 'app'])) {
|
||||
$routes[] = require $routerPath . 'tenants-internal-api-v4.php';
|
||||
}
|
||||
$router = router(...$routes);
|
||||
$container = $container->withAutoInject(
|
||||
$router->dependencies(),
|
||||
);
|
||||
$routed = $router->getRouted($serverRequest, $psr17Factory, container: $container);
|
||||
|
||||
try {
|
||||
$routed = $router->getRouted($serverRequest, $psr17Factory, container: $container);
|
||||
} catch (NotFoundException|MethodNotAllowedException $e) {
|
||||
http_response_code($e->getCode());
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
'error' => [
|
||||
'code' => $e->getCode(),
|
||||
'message' => $e->getMessage(),
|
||||
'list' => explode("\n", $e->getMessage()),
|
||||
],
|
||||
], JSON_PRETTY_PRINT);
|
||||
exit();
|
||||
}
|
||||
if ($routed->hasThrowable()
|
||||
&& ! ($routed->throwable() instanceof ControllerException)
|
||||
) {
|
||||
|
||||
@@ -21,7 +21,6 @@ use Chevereto\Legacy\Classes\Image;
|
||||
use Chevereto\Legacy\Classes\L10n;
|
||||
use Chevereto\Legacy\Classes\Listing;
|
||||
use Chevereto\Legacy\Classes\Login;
|
||||
use Chevereto\Legacy\Classes\Mailer;
|
||||
use Chevereto\Legacy\Classes\Page;
|
||||
use Chevereto\Legacy\Classes\ProjectArachnid;
|
||||
use Chevereto\Legacy\Classes\Settings;
|
||||
@@ -34,7 +33,8 @@ use Chevereto\Legacy\G\Handler;
|
||||
use FFMpeg\FFMpeg;
|
||||
use FFMpeg\FFProbe;
|
||||
use Intervention\Image\ImageManagerStatic;
|
||||
use PHPMailer\PHPMailer\SMTP;
|
||||
use Symfony\Component\Mailer\Transport;
|
||||
use Symfony\Component\Mailer\Transport\Smtp\SmtpTransport;
|
||||
use function Chevere\Standard\randomString;
|
||||
use function Chevereto\Encryption\hasEncryption;
|
||||
use function Chevereto\Legacy\badgePaid;
|
||||
@@ -73,9 +73,11 @@ use function Chevereto\Legacy\getSystemNotices;
|
||||
use function Chevereto\Legacy\getVariable;
|
||||
use function Chevereto\Legacy\headersNoCache;
|
||||
use function Chevereto\Legacy\strip_tags_content;
|
||||
use function Chevereto\Legacy\trialAwareLabel;
|
||||
use function Chevereto\Legacy\updateCheveretoNews;
|
||||
use function Chevereto\Legacy\upload_to_content_images;
|
||||
use function Chevereto\Vars\env;
|
||||
use function Chevereto\Vars\envTrialAware;
|
||||
use function Chevereto\Vars\files;
|
||||
use function Chevereto\Vars\get;
|
||||
use function Chevereto\Vars\post;
|
||||
@@ -916,13 +918,13 @@ return function (Handler $handler) {
|
||||
$settings_pages['title'] = _s('Add page');
|
||||
$settings_pages['doing'] = 'add';
|
||||
$pagesCount = Page::countAll();
|
||||
$maxPages = (int) env()['CHEVERETO_MAX_PAGES'];
|
||||
$maxPages = (int) envTrialAware()['CHEVERETO_MAX_PAGES'];
|
||||
if ($maxPages !== 0) {
|
||||
if ($pagesCount >= $maxPages) {
|
||||
$is_error = true;
|
||||
$error_title = _s('Quota limit reached');
|
||||
$error_message = _s(
|
||||
'Maximum number of pages allowed reached (limit %s).',
|
||||
'Maximum number of pages allowed reached (limit %s).' . trialAwareLabel(),
|
||||
$maxPages
|
||||
);
|
||||
$handler::setVar('error_title', $error_title);
|
||||
@@ -1168,7 +1170,28 @@ return function (Handler $handler) {
|
||||
// 'page_file_path_absolute' => $POST['page_file_path_absolute'],
|
||||
]);
|
||||
}
|
||||
$mailApis = ['smtp'];
|
||||
$mailApis = [
|
||||
'smtp',
|
||||
'ahasend',
|
||||
'ses',
|
||||
'azure',
|
||||
'brevo',
|
||||
'infobip',
|
||||
'mailgun',
|
||||
'mailjet',
|
||||
'mailomat',
|
||||
'mailpace',
|
||||
'mailersend',
|
||||
'mailtrap',
|
||||
'mandrill',
|
||||
'microsoftgraph',
|
||||
'postal',
|
||||
'postmark',
|
||||
'resend',
|
||||
'scaleway',
|
||||
'sendgrid',
|
||||
'sweego',
|
||||
];
|
||||
if (env()['CHEVERETO_SERVICING'] !== 'docker') {
|
||||
$mailApis[] = 'mail';
|
||||
}
|
||||
@@ -1262,14 +1285,6 @@ return function (Handler $handler) {
|
||||
'validate' => isset($POST['email_mode']) && in_array($POST['email_mode'], $mailApis, true),
|
||||
'error_msg' => _s('Invalid email mode'),
|
||||
],
|
||||
'email_smtp_server_port' => [
|
||||
'validate' => isset($POST['email_smtp_server_port']) && $POST['email_smtp_server_port'] > 0 && $POST['email_smtp_server_port'] < 65536,
|
||||
'error_msg' => _s('Invalid SMTP port'),
|
||||
],
|
||||
'email_smtp_server_security' => [
|
||||
'validate' => isset($POST['email_smtp_server_security']) && in_array($POST['email_smtp_server_security'], ['tls', 'ssl', 'unsecured'], true),
|
||||
'error_msg' => _s('Invalid SMTP security'),
|
||||
],
|
||||
'website_mode' => [
|
||||
'validate' => isset($POST['website_mode']) && in_array($POST['website_mode'], ['community', 'personal'], true),
|
||||
'error_msg' => _s('Invalid website mode'),
|
||||
@@ -1635,52 +1650,116 @@ return function (Handler $handler) {
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($POST['email_mode']) && $POST['email_mode'] === 'smtp') {
|
||||
$email_smtp_validate = [
|
||||
'email_smtp_server' => _s('Invalid SMTP server'),
|
||||
// 'email_smtp_server_username' => _s('Invalid SMTP username'),
|
||||
$emailMode = $POST['email_mode'] ?? '';
|
||||
$emailApiRequiredFields = [
|
||||
'smtp' => ['email_smtp_server', 'email_smtp_server_port', 'email_smtp_server_security'],
|
||||
'ahasend' => ['email_ahasend_api_key'],
|
||||
'ses' => ['email_ses_access_key', 'email_ses_secret_key'],
|
||||
'azure' => ['email_azure_resource_name', 'email_azure_key'],
|
||||
'brevo' => ['email_brevo_api_key'],
|
||||
'infobip' => ['email_infobip_api_key', 'email_infobip_base_url'],
|
||||
'mailersend' => ['email_mailersend_api_key'],
|
||||
'mailgun' => ['email_mailgun_api_key', 'email_mailgun_domain'],
|
||||
'mailjet' => ['email_mailjet_access_key', 'email_mailjet_secret_key'],
|
||||
'mailomat' => ['email_mailomat_api_key'],
|
||||
'mailpace' => ['email_mailpace_api_token'],
|
||||
'mailtrap' => ['email_mailtrap_api_token'],
|
||||
'mandrill' => ['email_mandrill_api_key'],
|
||||
'microsoftgraph' => ['email_microsoftgraph_client_id', 'email_microsoftgraph_client_secret', 'email_microsoftgraph_tenant_id'],
|
||||
'postal' => ['email_postal_api_key', 'email_postal_base_url'],
|
||||
'postmark' => ['email_postmark_api_token'],
|
||||
'resend' => ['email_resend_api_key'],
|
||||
'scaleway' => ['email_scaleway_project_id', 'email_scaleway_api_key'],
|
||||
'sendgrid' => ['email_sendgrid_api_key'],
|
||||
'sweego' => ['email_sweego_api_key'],
|
||||
];
|
||||
if ((env()['CHEVERETO_SERVICING'] !== 'server' && $emailMode === 'mail')
|
||||
|| (env()['CHEVERETO_CONTEXT'] === 'saas' && in_array($emailMode, ['smtp', 'mail'], true))
|
||||
) {
|
||||
$validations['email_mode'] = [
|
||||
'validate' => false,
|
||||
'error_msg' => _s('The %s API is not available in this context', $emailMode),
|
||||
];
|
||||
foreach ($email_smtp_validate as $k => $v) {
|
||||
$validations[$k] = [
|
||||
'validate' => (bool) $POST[$k],
|
||||
'error_msg' => $v,
|
||||
}
|
||||
if ($validations === [] && isset($emailApiRequiredFields[$emailMode])) {
|
||||
foreach ($emailApiRequiredFields[$emailMode] as $field) {
|
||||
$validations[$field] = [
|
||||
'validate' => ! empty($POST[$field]),
|
||||
'error_msg' => _s('Invalid value'),
|
||||
];
|
||||
}
|
||||
|
||||
$email_validate = [
|
||||
'email_smtp_server',
|
||||
'email_smtp_server_port',
|
||||
// 'email_smtp_server_username',
|
||||
// 'email_smtp_server_password',
|
||||
'email_smtp_server_security',
|
||||
];
|
||||
$email_error = false;
|
||||
foreach ($email_validate as $k) {
|
||||
if (! $validations[$k]['validate']) {
|
||||
$email_error = true;
|
||||
}
|
||||
$emailFieldsValid = array_reduce(
|
||||
$emailApiRequiredFields[$emailMode],
|
||||
fn (bool $carry, string $field) => $carry && ($validations[$field]['validate'] ?? false),
|
||||
true
|
||||
);
|
||||
if ($emailMode === 'smtp') {
|
||||
$validations['email_smtp_server'] = [
|
||||
'validate' => (bool) ($POST['email_smtp_server'] ?? ''),
|
||||
'error_msg' => _s('Invalid SMTP server'),
|
||||
];
|
||||
$validations['email_smtp_server_port'] = [
|
||||
'validate' => isset($POST['email_smtp_server_port']) && $POST['email_smtp_server_port'] > 0 && $POST['email_smtp_server_port'] < 65536,
|
||||
'error_msg' => _s('Invalid SMTP port'),
|
||||
];
|
||||
$validations['email_smtp_server_security'] = [
|
||||
'validate' => isset($POST['email_smtp_server_security']) && in_array($POST['email_smtp_server_security'], ['tls', 'ssl', 'unsecured'], true),
|
||||
'error_msg' => _s('Invalid SMTP security'),
|
||||
];
|
||||
$emailFieldsValid = $validations['email_smtp_server']['validate']
|
||||
&& $validations['email_smtp_server_port']['validate']
|
||||
&& $validations['email_smtp_server_security']['validate'];
|
||||
}
|
||||
if (! $email_error) {
|
||||
if ($emailFieldsValid) {
|
||||
try {
|
||||
$mail = new Mailer(true);
|
||||
$mail->Username = $POST['email_smtp_server_username'] ?? '';
|
||||
$mail->Password = $POST['email_smtp_server_password'] ?? '';
|
||||
$mail->SMTPAuth = $mail->Username !== '' || $mail->Password !== '';
|
||||
$mail->SMTPSecure = in_array($POST['email_smtp_server_security'], ['ssl', 'tls'])
|
||||
? $POST['email_smtp_server_security']
|
||||
: '';
|
||||
$mail->SMTPAutoTLS = in_array($POST['email_smtp_server_security'], ['ssl', 'tls']);
|
||||
$mail->Host = (string) $POST['email_smtp_server'];
|
||||
$mail->Port = (int) $POST['email_smtp_server_port'];
|
||||
$mail->SMTPDebug = SMTP::DEBUG_SERVER;
|
||||
$GLOBALS['SMTPDebug'] = '';
|
||||
$mail->Debugoutput = function ($str) {
|
||||
$GLOBALS['SMTPDebug'] .= "{$str}\n";
|
||||
$dsn = match ($emailMode) {
|
||||
'smtp' => (function () use ($POST): string {
|
||||
$username = urlencode($POST['email_smtp_server_username'] ?? '');
|
||||
$password = urlencode($POST['email_smtp_server_password'] ?? '');
|
||||
$host = (string) $POST['email_smtp_server'];
|
||||
$port = (int) $POST['email_smtp_server_port'];
|
||||
$security = $POST['email_smtp_server_security'];
|
||||
$scheme = $security === 'ssl' ? 'smtps' : 'smtp';
|
||||
$auth = ($username !== '' || $password !== '') ? "{$username}:{$password}@" : '';
|
||||
$dsn = "{$scheme}://{$auth}{$host}:{$port}";
|
||||
if ($security === 'tls') {
|
||||
$dsn .= '?encryption=tls';
|
||||
} elseif (! in_array($security, ['ssl', 'tls'], true)) {
|
||||
$dsn .= '?verify_peer=false';
|
||||
}
|
||||
|
||||
return $dsn;
|
||||
})(),
|
||||
'ahasend' => 'ahasend+api://' . urlencode($POST['email_ahasend_api_key']) . '@default',
|
||||
'ses' => 'ses+api://' . urlencode($POST['email_ses_access_key']) . ':' . urlencode($POST['email_ses_secret_key']) . '@default',
|
||||
'azure' => 'azure+api://' . urlencode($POST['email_azure_resource_name']) . ':' . urlencode($POST['email_azure_key']) . '@default',
|
||||
'brevo' => 'brevo+api://' . urlencode($POST['email_brevo_api_key']) . '@default',
|
||||
'infobip' => 'infobip+api://' . urlencode($POST['email_infobip_api_key']) . '@' . urlencode($POST['email_infobip_base_url']),
|
||||
'mailersend' => 'mailersend+api://' . urlencode($POST['email_mailersend_api_key']) . '@default',
|
||||
'mailgun' => 'mailgun+api://' . urlencode($POST['email_mailgun_api_key']) . ':' . urlencode($POST['email_mailgun_domain']) . '@default',
|
||||
'mailjet' => 'mailjet+api://' . urlencode($POST['email_mailjet_access_key']) . ':' . urlencode($POST['email_mailjet_secret_key']) . '@default',
|
||||
'mailomat' => 'mailomat+api://' . urlencode($POST['email_mailomat_api_key']) . '@default',
|
||||
'mailpace' => 'mailpace+api://' . urlencode($POST['email_mailpace_api_token']) . '@default',
|
||||
'mailtrap' => 'mailtrap+api://' . urlencode($POST['email_mailtrap_api_token']) . '@default',
|
||||
'mandrill' => 'mandrill+api://' . urlencode($POST['email_mandrill_api_key']) . '@default',
|
||||
'microsoftgraph' => 'microsoftgraph+api://' . urlencode($POST['email_microsoftgraph_client_id']) . ':' . urlencode($POST['email_microsoftgraph_client_secret']) . '@default?tenantId=' . urlencode($POST['email_microsoftgraph_tenant_id']),
|
||||
'postal' => 'postal+api://' . urlencode($POST['email_postal_api_key']) . '@' . urlencode($POST['email_postal_base_url']),
|
||||
'postmark' => 'postmark+api://' . urlencode($POST['email_postmark_api_token']) . '@default',
|
||||
'resend' => 'resend+api://' . urlencode($POST['email_resend_api_key']) . '@default',
|
||||
'scaleway' => 'scaleway+api://' . urlencode($POST['email_scaleway_project_id']) . ':' . urlencode($POST['email_scaleway_api_key']) . '@default',
|
||||
'sendgrid' => 'sendgrid+api://' . urlencode($POST['email_sendgrid_api_key']) . '@default',
|
||||
'sweego' => 'sweego+api://' . urlencode($POST['email_sweego_api_key']) . '@default',
|
||||
};
|
||||
$GLOBALS['SMTPDebug'] = "SMTP Debug>>\n" . $GLOBALS['SMTPDebug'];
|
||||
$mail->SmtpConnect();
|
||||
} catch (Exception $e) {
|
||||
$GLOBALS['SMTPDebug'] = "SMTP Exception>>\n" . ($mail->ErrorInfo ?: $e->getMessage());
|
||||
$transport = Transport::fromDsn($dsn);
|
||||
if ($transport instanceof SmtpTransport) {
|
||||
$GLOBALS['SMTPDebug'] = '';
|
||||
$transport->start();
|
||||
$GLOBALS['SMTPDebug'] = "SMTP Debug>>\nConnected successfully";
|
||||
} else {
|
||||
$GLOBALS['SMTPDebug'] = "Transport configured: {$emailMode}";
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$GLOBALS['SMTPDebug'] = "Error>>\n" . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,6 +292,7 @@ return function (Handler $handler) {
|
||||
$meta_description = $image['description'];
|
||||
} else {
|
||||
$image_tr = [
|
||||
'%s' => _s(mb_ucfirst($image['type'])),
|
||||
'%i' => $image[$image['title'] === null ? 'filename' : 'title'],
|
||||
'%a' => $image['album']['name'] ?? '',
|
||||
'%w' => getSetting('website_name'),
|
||||
@@ -301,11 +302,11 @@ return function (Handler $handler) {
|
||||
|| (
|
||||
! ((bool) ($image['user']['is_private'] ?? false)) && isset($image['album']['name'])
|
||||
)) {
|
||||
$meta_description = _s('Image %i in %a album', $image_tr);
|
||||
$meta_description = _s('%s %i in %a album', $image_tr);
|
||||
} elseif (isset($image['category']['id'])) {
|
||||
$meta_description = _s('Image %i in %c category', $image_tr);
|
||||
$meta_description = _s('%s %i in %c category', $image_tr);
|
||||
} else {
|
||||
$meta_description = _s('Image %i hosted in %w', $image_tr);
|
||||
$meta_description = _s('%s %i hosted in %w', $image_tr);
|
||||
}
|
||||
}
|
||||
$handler::setVar('meta_description', $meta_description ?? '');
|
||||
|
||||
@@ -16,10 +16,12 @@ use Chevereto\Http\Controllers\Api\V4\TenantPatch;
|
||||
use Chevereto\Http\Controllers\Api\V4\TenantPlanDelete;
|
||||
use Chevereto\Http\Controllers\Api\V4\TenantPlanGet;
|
||||
use Chevereto\Http\Controllers\Api\V4\TenantPlanPatch;
|
||||
use Chevereto\Http\Controllers\Api\V4\TenantsAuthVerifyPost;
|
||||
use Chevereto\Http\Controllers\Api\V4\TenantsGet;
|
||||
use Chevereto\Http\Controllers\Api\V4\TenantsPlansGet;
|
||||
use Chevereto\Http\Controllers\Api\V4\TenantsPlansPost;
|
||||
use Chevereto\Http\Controllers\Api\V4\TenantsPost;
|
||||
use Chevereto\Http\Controllers\Api\V4\TenantUserPasswordResetPatch;
|
||||
use Chevereto\Http\Middlewares\RestrictIpAccess;
|
||||
use Chevereto\Http\Middlewares\SignedRequest;
|
||||
use Chevereto\Http\Middlewares\TenantsApiKeyAuthorization;
|
||||
@@ -28,6 +30,10 @@ use function Chevere\Router\routes;
|
||||
use function Chevereto\Vars\env;
|
||||
|
||||
return routes(
|
||||
route(
|
||||
'/_/api/4/auth/verify',
|
||||
POST: TenantsAuthVerifyPost::class,
|
||||
),
|
||||
route(
|
||||
'/_/api/4/tenants',
|
||||
POST: TenantsPost::class,
|
||||
@@ -43,6 +49,10 @@ return routes(
|
||||
'/_/api/4/tenants/{id}/install',
|
||||
POST: TenantInstallPost::class,
|
||||
),
|
||||
route(
|
||||
'/_/api/4/tenants/{id}/user-password-reset',
|
||||
PATCH: TenantUserPasswordResetPatch::class,
|
||||
),
|
||||
route(
|
||||
'/_/api/4/tenants-plans',
|
||||
POST: TenantsPlansPost::class,
|
||||
|
||||
29
app/routes/tenants-internal-api-v4.php
Normal file
29
app/routes/tenants-internal-api-v4.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chevereto.
|
||||
*
|
||||
* (c) Rodolfo Berrios <rodolfo@chevereto.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Chevereto\Http\Controllers\Api\V4\TenantsConfigTraefikGet;
|
||||
use Chevereto\Http\Middlewares\RestrictIpAccess;
|
||||
use Chevereto\Http\Middlewares\TenantsApiKeyAuthorization;
|
||||
use function Chevere\Router\route;
|
||||
use function Chevere\Router\routes;
|
||||
|
||||
return routes(
|
||||
route(
|
||||
'/_/api/4/config/traefik',
|
||||
GET: TenantsConfigTraefikGet::class,
|
||||
),
|
||||
)
|
||||
->withAppendMiddleware(
|
||||
RestrictIpAccess::with(
|
||||
allowList: '127.0.0.1,::1,172.16.0.0/12,192.168.65.0/24',
|
||||
),
|
||||
TenantsApiKeyAuthorization::class,
|
||||
);
|
||||
@@ -17,6 +17,7 @@ CREATE TABLE `%table_root_prefix%tenants_stats` (
|
||||
`pages` INT UNSIGNED NOT NULL DEFAULT '0',
|
||||
`storages` INT UNSIGNED NOT NULL DEFAULT '0',
|
||||
`categories` INT UNSIGNED NOT NULL DEFAULT '0',
|
||||
`login_providers` INT UNSIGNED NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`tenant_id`),
|
||||
KEY `updated_at` (`updated_at`),
|
||||
FOREIGN KEY (tenant_id) REFERENCES `%table_root_prefix%tenants` (id) ON DELETE CASCADE
|
||||
|
||||
@@ -33,6 +33,7 @@ class TenantPlanPatch extends Controller
|
||||
public function __invoke(string $id): void
|
||||
{
|
||||
try {
|
||||
$this->tenants->getPlan($id);
|
||||
$this->tenants->editPlan(
|
||||
planId: $id,
|
||||
limits: $this->bodyParsed()->optional('limits')?->array(),
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chevereto.
|
||||
*
|
||||
* (c) Rodolfo Berrios <rodolfo@chevereto.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chevereto\Http\Controllers\Api\V4;
|
||||
|
||||
use Chevere\Http\Attributes\Response;
|
||||
use Chevere\Http\Controller;
|
||||
use Chevere\Http\Exceptions\ControllerException;
|
||||
use Chevere\Http\Status;
|
||||
use Chevere\Parameter\Interfaces\ParameterInterface;
|
||||
use Chevere\Writer\StreamWriter;
|
||||
use Chevereto\Exceptions\NotFoundException;
|
||||
use Chevereto\PCRE;
|
||||
use Chevereto\Tenants\Tenants;
|
||||
use function Chevere\Parameter\arrayp;
|
||||
use function Chevere\Parameter\string;
|
||||
use function Chevere\Standard\randomString;
|
||||
use function Chevere\Writer\streamTemp;
|
||||
use function Chevereto\Legacy\G\str_replace_last;
|
||||
use function Chevereto\Legacy\runAppCommand;
|
||||
use function Chevereto\Vars\env;
|
||||
|
||||
#[Response(
|
||||
new Status(201, fail: 404, conflict: 409),
|
||||
)]
|
||||
class TenantUserPasswordResetPatch extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private Tenants $tenants,
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(string $id): string
|
||||
{
|
||||
try {
|
||||
$tenant = $this->tenants->getTenant(tenantId: $id);
|
||||
} catch (NotFoundException) {
|
||||
throw new ControllerException(
|
||||
'Tenant not found',
|
||||
404
|
||||
);
|
||||
}
|
||||
$password = $this->bodyParsed()->optional('password')?->string()
|
||||
?? randomString(16);
|
||||
$logger = new StreamWriter(streamTemp());
|
||||
$exit = runAppCommand(
|
||||
command: [
|
||||
'-C', 'password-reset',
|
||||
'-u', $this->bodyParsed()->required('username')->string(),
|
||||
'-x', $password,
|
||||
],
|
||||
env: array_merge(env(), [
|
||||
'CHEVERETO_TENANT' => $tenant->id,
|
||||
'CHEVERETO_CACHE_KEY_PREFIX' => str_replace_last(
|
||||
'_:',
|
||||
'',
|
||||
env()['CHEVERETO_CACHE_KEY_PREFIX']
|
||||
),
|
||||
'CHEVERETO_DB_TABLE_PREFIX' => str_replace_last(
|
||||
'_',
|
||||
'',
|
||||
env()['CHEVERETO_DB_TABLE_PREFIX']
|
||||
),
|
||||
]),
|
||||
isVerbose: true,
|
||||
logger: $logger
|
||||
);
|
||||
xr($exit);
|
||||
if ($exit === 0) {
|
||||
return $password;
|
||||
}
|
||||
|
||||
throw new ControllerException(
|
||||
'Password reset failed',
|
||||
500
|
||||
);
|
||||
}
|
||||
|
||||
public static function acceptBody(): ParameterInterface
|
||||
{
|
||||
return arrayp(
|
||||
username: string(PCRE::USER_USERNAME->value),
|
||||
)->withOptional(
|
||||
password: string(PCRE::USER_PASSWORD->value)
|
||||
);
|
||||
}
|
||||
}
|
||||
28
app/src/Http/Controllers/Api/V4/TenantsAuthVerifyPost.php
Normal file
28
app/src/Http/Controllers/Api/V4/TenantsAuthVerifyPost.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chevereto.
|
||||
*
|
||||
* (c) Rodolfo Berrios <rodolfo@chevereto.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chevereto\Http\Controllers\Api\V4;
|
||||
|
||||
use Chevere\Http\Attributes\Response;
|
||||
use Chevere\Http\Controller;
|
||||
use Chevere\Http\Header;
|
||||
use Chevere\Http\Status;
|
||||
|
||||
#[Response(
|
||||
new Status(200),
|
||||
new Header('Content-Type', 'application/json'),
|
||||
)]
|
||||
class TenantsAuthVerifyPost extends Controller
|
||||
{
|
||||
public function __invoke(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
43
app/src/Http/Controllers/Api/V4/TenantsConfigTraefikGet.php
Normal file
43
app/src/Http/Controllers/Api/V4/TenantsConfigTraefikGet.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chevereto.
|
||||
*
|
||||
* (c) Rodolfo Berrios <rodolfo@chevereto.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chevereto\Http\Controllers\Api\V4;
|
||||
|
||||
use Chevere\Http\Attributes\Response;
|
||||
use Chevere\Http\Controller;
|
||||
use Chevere\Http\Header;
|
||||
use Chevere\Http\Status;
|
||||
use Chevereto\Tenants\Tenants;
|
||||
use Chevereto\Tenants\TenantsConfig;
|
||||
use function Chevereto\Vars\env;
|
||||
|
||||
#[Response(
|
||||
new Status(200),
|
||||
new Header('Content-Type', 'application/json'),
|
||||
)]
|
||||
class TenantsConfigTraefikGet extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private Tenants $tenants,
|
||||
private TenantsConfig $tenantsConfig,
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(): array
|
||||
{
|
||||
return $this->tenantsConfig->getConfig(
|
||||
tenants: $this->tenants,
|
||||
service: env()['CHEVERETO_SERVICE_NAME'],
|
||||
port: 80,
|
||||
middleware: ['cf-only']
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -118,7 +118,9 @@ class DB
|
||||
];
|
||||
$this->pdo_options = $this->pdo_default_attrs + $this->pdoAttrs;
|
||||
$this->pdo_options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION;
|
||||
$this->pdo_options[PDO::MYSQL_ATTR_INIT_COMMAND] = "SET time_zone = '+00:00', NAMES 'utf8mb4'";
|
||||
$attrInitOption = class_exists('Pdo\\Mysql') ? \Pdo\Mysql::ATTR_INIT_COMMAND : PDO::MYSQL_ATTR_INIT_COMMAND;
|
||||
$this->pdo_options[] = "SET time_zone = '+00:00', NAMES 'utf8mb4'";
|
||||
$this->pdo_options[$attrInitOption] = "SET time_zone = '+00:00', NAMES 'utf8mb4'";
|
||||
self::$dbh = new PDO($pdo_connect, $this->user, $this->pass, $this->pdo_options);
|
||||
self::$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
|
||||
self::$instance = $this;
|
||||
|
||||
@@ -32,6 +32,34 @@ class Settings
|
||||
'email_smtp_server_password',
|
||||
'email_smtp_server_port',
|
||||
'email_smtp_server_username',
|
||||
'email_ahasend_api_key',
|
||||
'email_ses_access_key',
|
||||
'email_ses_secret_key',
|
||||
'email_azure_resource_name',
|
||||
'email_azure_key',
|
||||
'email_brevo_api_key',
|
||||
'email_infobip_api_key',
|
||||
'email_infobip_base_url',
|
||||
'email_mailgun_api_key',
|
||||
'email_mailgun_domain',
|
||||
'email_mailjet_access_key',
|
||||
'email_mailjet_secret_key',
|
||||
'email_mailomat_api_key',
|
||||
'email_mailpace_api_token',
|
||||
'email_mailersend_api_key',
|
||||
'email_mailtrap_api_token',
|
||||
'email_mandrill_api_key',
|
||||
'email_microsoftgraph_client_id',
|
||||
'email_microsoftgraph_client_secret',
|
||||
'email_microsoftgraph_tenant_id',
|
||||
'email_postal_api_key',
|
||||
'email_postal_base_url',
|
||||
'email_postmark_api_token',
|
||||
'email_resend_api_key',
|
||||
'email_scaleway_project_id',
|
||||
'email_scaleway_api_key',
|
||||
'email_sendgrid_api_key',
|
||||
'email_sweego_api_key',
|
||||
'captcha_secret',
|
||||
'disqus_secret_key',
|
||||
'akismet_api_key',
|
||||
@@ -312,6 +340,13 @@ class Settings
|
||||
'upload_image_path' => 'images',
|
||||
],
|
||||
],
|
||||
'CHEVERETO_ENABLE_GUESTS' => ['0',
|
||||
[
|
||||
'enable_api_guest' => false,
|
||||
'guest_uploads' => false,
|
||||
'guest_albums' => false,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
public const STOCK = [
|
||||
@@ -421,6 +456,34 @@ class Settings
|
||||
'theme_palette_user_select' => true,
|
||||
'enable_api_user' => true,
|
||||
'enable_api_guest' => false,
|
||||
'email_ahasend_api_key' => '',
|
||||
'email_ses_access_key' => '',
|
||||
'email_ses_secret_key' => '',
|
||||
'email_azure_resource_name' => '',
|
||||
'email_azure_key' => '',
|
||||
'email_brevo_api_key' => '',
|
||||
'email_infobip_api_key' => '',
|
||||
'email_infobip_base_url' => '',
|
||||
'email_mailgun_api_key' => '',
|
||||
'email_mailgun_domain' => '',
|
||||
'email_mailjet_access_key' => '',
|
||||
'email_mailjet_secret_key' => '',
|
||||
'email_mailomat_api_key' => '',
|
||||
'email_mailpace_api_token' => '',
|
||||
'email_mailersend_api_key' => '',
|
||||
'email_mailtrap_api_token' => '',
|
||||
'email_mandrill_api_key' => '',
|
||||
'email_microsoftgraph_client_id' => '',
|
||||
'email_microsoftgraph_client_secret' => '',
|
||||
'email_microsoftgraph_tenant_id' => '',
|
||||
'email_postal_api_key' => '',
|
||||
'email_postal_base_url' => '',
|
||||
'email_postmark_api_token' => '',
|
||||
'email_resend_api_key' => '',
|
||||
'email_scaleway_project_id' => '',
|
||||
'email_scaleway_api_key' => '',
|
||||
'email_sendgrid_api_key' => '',
|
||||
'email_sweego_api_key' => '',
|
||||
];
|
||||
|
||||
public const USERNAME_MIN_LENGTH = 3;
|
||||
@@ -804,7 +867,6 @@ class Settings
|
||||
foreach ($binds as $bindK => $bindV) {
|
||||
$db->bind($bindK, $bindV);
|
||||
}
|
||||
|
||||
$return = $db->exec();
|
||||
if ($return) {
|
||||
self::cache();
|
||||
|
||||
@@ -17,7 +17,9 @@ use LogicException;
|
||||
use OverflowException;
|
||||
use function Chevere\Message\message;
|
||||
use function Chevereto\Legacy\G\datetimegmt;
|
||||
use function Chevereto\Legacy\trialAwareLabel;
|
||||
use function Chevereto\Vars\env;
|
||||
use function Chevereto\Vars\envTrialAware;
|
||||
|
||||
class Stat
|
||||
{
|
||||
@@ -127,7 +129,7 @@ class Stat
|
||||
600
|
||||
);
|
||||
}
|
||||
$maxLimit = (int) (env()[$env] ?? 0);
|
||||
$maxLimit = (int) (envTrialAware()[$env] ?? 0);
|
||||
if ($maxLimit === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -135,7 +137,7 @@ class Stat
|
||||
if (($count + $add) > $maxLimit) {
|
||||
throw new OverflowException(
|
||||
message(
|
||||
'Maximum %t% reached (limit %s%).',
|
||||
'Maximum %t% reached (limit %s%).' . trialAwareLabel(),
|
||||
t: $env,
|
||||
s: strval($maxLimit),
|
||||
),
|
||||
@@ -449,6 +451,7 @@ class Stat
|
||||
'file_likes' => 's.stat_image_likes',
|
||||
'album_likes' => 's.stat_album_likes',
|
||||
'storage_used' => 's.stat_disk_used',
|
||||
'login_providers' => 'u.login_providers',
|
||||
'admins' => 'u.admins',
|
||||
'managers' => 'u.managers',
|
||||
'pages' => 'u.pages',
|
||||
@@ -469,9 +472,7 @@ class Stat
|
||||
$selectColumns = implode(',', $pairs);
|
||||
$select = match ($asJsonColumn) {
|
||||
true => <<<SQL
|
||||
SELECT JSON_OBJECT(
|
||||
{$selectColumns}
|
||||
) AS stats
|
||||
SELECT JSON_OBJECT({$selectColumns}) AS stats
|
||||
SQL,
|
||||
false => <<<SQL
|
||||
SELECT {$selectColumns}
|
||||
@@ -487,7 +488,8 @@ class Stat
|
||||
SUM(CASE WHEN `user_is_manager` = 1 THEN 1 ELSE 0 END) AS managers,
|
||||
(SELECT COUNT(*) FROM `{$tablePrefix}pages`) AS pages,
|
||||
(SELECT COUNT(*) FROM `{$tablePrefix}storages` WHERE storage_deleted_at IS NULL) AS storages,
|
||||
(SELECT COUNT(*) FROM `{$tablePrefix}categories`) AS categories
|
||||
(SELECT COUNT(*) FROM `{$tablePrefix}categories`) AS categories,
|
||||
(SELECT COUNT(*) FROM `{$tablePrefix}login_providers` WHERE login_provider_is_enabled = 1) AS login_providers
|
||||
FROM `{$tablePrefix}users`
|
||||
) u
|
||||
WHERE s.stat_type = "total"
|
||||
|
||||
@@ -18,6 +18,7 @@ use function Chevereto\Legacy\assertNotStopWords;
|
||||
use function Chevereto\Legacy\G\get_base_url;
|
||||
use function Chevereto\Legacy\G\safe_html;
|
||||
use function Chevereto\Vars\env;
|
||||
use function Chevereto\Vars\envTrialAware;
|
||||
|
||||
/**
|
||||
* Tags on the database are "as is" without any encoding
|
||||
@@ -176,7 +177,7 @@ final class Tag
|
||||
if ($tag === []) {
|
||||
return;
|
||||
}
|
||||
$maxTags = (int) env()['CHEVERETO_MAX_TAGS'];
|
||||
$maxTags = (int) envTrialAware()['CHEVERETO_MAX_TAGS'];
|
||||
if ($maxTags > 0) {
|
||||
$currentTotalTags = Stat::getTotals()['tags'] ?? 0;
|
||||
if ($currentTotalTags >= $maxTags) {
|
||||
|
||||
@@ -37,7 +37,9 @@ use function Chevereto\Legacy\getSetting;
|
||||
use function Chevereto\Legacy\headersNoCache;
|
||||
use function Chevereto\Legacy\linkify_redirector;
|
||||
use function Chevereto\Legacy\system_notification_email;
|
||||
use function Chevereto\Legacy\trialAwareLabel;
|
||||
use function Chevereto\Vars\env;
|
||||
use function Chevereto\Vars\envTrialAware;
|
||||
|
||||
class User
|
||||
{
|
||||
@@ -134,37 +136,40 @@ class User
|
||||
} else {
|
||||
$userAlbums = [];
|
||||
$user_stream = self::getStreamAlbum($var);
|
||||
if ($user_stream === null || $user_stream['user_album_count'] === 0) {
|
||||
if ($user_stream === null) {
|
||||
return [];
|
||||
}
|
||||
$userAlbumsCount = $user_stream['user_album_count'];
|
||||
unset($user_stream['user_album_count']);
|
||||
$userAlbums['stream'] = $user_stream;
|
||||
$map = [];
|
||||
$children = [];
|
||||
$columns = [
|
||||
'album_id',
|
||||
'album_name',
|
||||
'album_privacy',
|
||||
'album_parent_id',
|
||||
'album_image_count',
|
||||
'album_cover_id',
|
||||
];
|
||||
$columnsString = implode(', ', $columns);
|
||||
$tableAlbums = DB::getTable('albums');
|
||||
$db = DB::getInstance();
|
||||
$db->query(
|
||||
<<<MySQL
|
||||
SELECT {$columnsString}
|
||||
FROM {$tableAlbums}
|
||||
WHERE album_user_id=:image_user_id
|
||||
ORDER BY album_parent_id ASC, album_name ASC LIMIT :limit
|
||||
MySQL
|
||||
);
|
||||
$db->bind(':limit', intval(env()['CHEVERETO_MAX_USER_ALBUMS_LIST']));
|
||||
$db->bind(':image_user_id', $id);
|
||||
$user_albums_db = $db->fetchAll();
|
||||
if ($user_albums_db) {
|
||||
$userAlbums += $user_albums_db;
|
||||
if ($userAlbumsCount > 0) {
|
||||
$columns = [
|
||||
'album_id',
|
||||
'album_name',
|
||||
'album_privacy',
|
||||
'album_parent_id',
|
||||
'album_image_count',
|
||||
'album_cover_id',
|
||||
];
|
||||
$columnsString = implode(', ', $columns);
|
||||
$tableAlbums = DB::getTable('albums');
|
||||
$db = DB::getInstance();
|
||||
$db->query(
|
||||
<<<MySQL
|
||||
SELECT {$columnsString}
|
||||
FROM {$tableAlbums}
|
||||
WHERE album_user_id=:image_user_id
|
||||
ORDER BY album_parent_id ASC, album_name ASC LIMIT :limit
|
||||
MySQL
|
||||
);
|
||||
$db->bind(':limit', intval(env()['CHEVERETO_MAX_USER_ALBUMS_LIST']));
|
||||
$db->bind(':image_user_id', $id);
|
||||
$user_albums_db = $db->fetchAll();
|
||||
if ($user_albums_db) {
|
||||
$userAlbums += $user_albums_db;
|
||||
}
|
||||
}
|
||||
foreach ($userAlbums as $k => &$v) {
|
||||
$album_id = isset($v['album_id'])
|
||||
@@ -343,7 +348,7 @@ class User
|
||||
if (! array_key_exists($role, $roles)) {
|
||||
throw new Exception('Invalid role', 600);
|
||||
}
|
||||
$maxLimit = (int) env()[$roleHandle] ?? 0;
|
||||
$maxLimit = (int) envTrialAware()[$roleHandle] ?? 0;
|
||||
if ($maxLimit === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -358,7 +363,7 @@ class User
|
||||
if (($count + 1) > $maxLimit) {
|
||||
throw new OverflowException(
|
||||
message(
|
||||
'Maximum %u% for role %r% reached (limit %c%)',
|
||||
'Maximum %u% for role %r% reached (limit %c%)' . trialAwareLabel(),
|
||||
u: _n('user', 'users', 20),
|
||||
c: strval($maxLimit),
|
||||
r: mb_strtolower($roleLabel),
|
||||
|
||||
@@ -31,7 +31,6 @@ use Chevereto\Legacy\Classes\KeyValue;
|
||||
use Chevereto\Legacy\Classes\KeyValueNull;
|
||||
use Chevereto\Legacy\Classes\L10n;
|
||||
use Chevereto\Legacy\Classes\Login;
|
||||
use Chevereto\Legacy\Classes\Mailer;
|
||||
use Chevereto\Legacy\Classes\Settings;
|
||||
use Chevereto\Legacy\Classes\StorageApis;
|
||||
use Chevereto\Legacy\Classes\Upload;
|
||||
@@ -56,9 +55,12 @@ use LogicException;
|
||||
use OutOfBoundsException;
|
||||
use OverflowException;
|
||||
use PDO;
|
||||
use PHPMailer\PHPMailer\SMTP;
|
||||
use Redis;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\Mailer\Mailer;
|
||||
use Symfony\Component\Mailer\Transport;
|
||||
use Symfony\Component\Mime\Address;
|
||||
use Symfony\Component\Mime\Email;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Throwable;
|
||||
use function Chevere\Filesystem\filePhpForPath;
|
||||
@@ -94,6 +96,7 @@ use function Chevereto\Legacy\G\starts_with;
|
||||
use function Chevereto\Legacy\G\unlinkIfExists;
|
||||
use function Chevereto\Vars\cookie;
|
||||
use function Chevereto\Vars\env;
|
||||
use function Chevereto\Vars\envTrialAware;
|
||||
use function Chevereto\Vars\post;
|
||||
use function Chevereto\Vars\server;
|
||||
use function Chevereto\Vars\session;
|
||||
@@ -195,55 +198,76 @@ function send_mail($to, $subject, $body): bool
|
||||
if (! filter_var($to, FILTER_VALIDATE_EMAIL)) {
|
||||
throw new Exception('Invalid to email', 100);
|
||||
}
|
||||
$writer = new StreamWriter(streamFor('php://temp', 'r+'));
|
||||
$body = trim($body);
|
||||
$mail = new Mailer();
|
||||
$mail->SMTPDebug = SMTP::DEBUG_SERVER;
|
||||
$mail->Debugoutput = function ($str, $level) use ($writer) {
|
||||
$writer->write("{$str} \n");
|
||||
};
|
||||
$alt_body = $mail->html2text($body);
|
||||
$mail->CharSet = 'UTF-8';
|
||||
if (getSetting('email_mode') === 'smtp') {
|
||||
$mail->isSMTP();
|
||||
$mail->Username = getSetting('email_smtp_server_username') ?? '';
|
||||
$mail->Password = getSetting('email_smtp_server_password') ?? '';
|
||||
$mail->SMTPAuth = $mail->Username !== '' || $mail->Password !== '';
|
||||
$mail->SMTPSecure = in_array(getSetting('email_smtp_server_security'), ['ssl', 'tls'], true)
|
||||
? getSetting('email_smtp_server_security')
|
||||
: '';
|
||||
$mail->SMTPAutoTLS = in_array(getSetting('email_smtp_server_security'), ['ssl', 'tls'], true);
|
||||
$mail->Port = getSetting('email_smtp_server_port');
|
||||
$mail->Host = getSetting('email_smtp_server');
|
||||
}
|
||||
$mail->Timeout = 30;
|
||||
$mail->Subject = $subject;
|
||||
if ($body !== $alt_body) {
|
||||
$mail->IsHTML(true);
|
||||
$mail->Body = $mail->normalizeBreaks($body);
|
||||
$mail->AltBody = $mail->normalizeBreaks($alt_body);
|
||||
} else {
|
||||
$mail->Body = $body;
|
||||
}
|
||||
$mail->addAddress($to);
|
||||
$alt_body = strip_tags($body);
|
||||
$email = new Email();
|
||||
$email->subject($subject);
|
||||
$email->to($to);
|
||||
$email->from(new Address($from[0], $from[1]));
|
||||
if ($reply_to && is_array($reply_to)) {
|
||||
foreach ($reply_to as $v) {
|
||||
$mail->addReplyTo($v);
|
||||
$email->addReplyTo($v);
|
||||
}
|
||||
}
|
||||
$mail->setFrom($from[0], $from[1]);
|
||||
if ($mail->Send()) {
|
||||
return true;
|
||||
if ($body !== $alt_body) {
|
||||
$email->html($body);
|
||||
$email->text($alt_body);
|
||||
} else {
|
||||
$email->text($body);
|
||||
}
|
||||
$mailerWrap = "\n----------- MAILER DEBUG -----------\n\n";
|
||||
$error = str_replace('-', '>', $mailerWrap)
|
||||
. $writer->__toString()
|
||||
. str_replace('-', '<', $mailerWrap);
|
||||
writers()->error()
|
||||
->write($error);
|
||||
xr(mailer: $error, to: $to, subject: $subject, body: $body);
|
||||
$emailMode = getSetting('email_mode') ?? 'mail';
|
||||
$dsn = match ($emailMode) {
|
||||
'smtp' => (function (): string {
|
||||
$username = urlencode(getSetting('email_smtp_server_username') ?? '');
|
||||
$password = urlencode(getSetting('email_smtp_server_password') ?? '');
|
||||
$host = getSetting('email_smtp_server') ?? 'localhost';
|
||||
$port = getSetting('email_smtp_server_port') ?? 25;
|
||||
$security = getSetting('email_smtp_server_security');
|
||||
$scheme = $security === 'ssl' ? 'smtps' : 'smtp';
|
||||
$auth = ($username !== '' || $password !== '') ? "{$username}:{$password}@" : '';
|
||||
$dsn = "{$scheme}://{$auth}{$host}:{$port}";
|
||||
if ($security === 'tls') {
|
||||
$dsn .= '?encryption=tls';
|
||||
} elseif (! in_array($security, ['ssl', 'tls'], true)) {
|
||||
$dsn .= '?verify_peer=false';
|
||||
}
|
||||
|
||||
throw new Exception($mail->ErrorInfo, 606);
|
||||
return $dsn;
|
||||
})(),
|
||||
'ahasend' => 'ahasend+api://' . urlencode(getSetting('email_ahasend_api_key') ?? '') . '@default',
|
||||
'ses' => 'ses+api://' . urlencode(getSetting('email_ses_access_key') ?? '') . ':' . urlencode(getSetting('email_ses_secret_key') ?? '') . '@default',
|
||||
'azure' => 'azure+api://' . urlencode(getSetting('email_azure_resource_name') ?? '') . ':' . urlencode(getSetting('email_azure_key') ?? '') . '@default',
|
||||
'brevo' => 'brevo+api://' . urlencode(getSetting('email_brevo_api_key') ?? '') . '@default',
|
||||
'infobip' => 'infobip+api://' . urlencode(getSetting('email_infobip_api_key') ?? '') . '@' . urlencode(getSetting('email_infobip_base_url') ?? 'default'),
|
||||
'mailgun' => 'mailgun+api://' . urlencode(getSetting('email_mailgun_api_key') ?? '') . ':' . urlencode(getSetting('email_mailgun_domain') ?? '') . '@default',
|
||||
'mailjet' => 'mailjet+api://' . urlencode(getSetting('email_mailjet_access_key') ?? '') . ':' . urlencode(getSetting('email_mailjet_secret_key') ?? '') . '@default',
|
||||
'mailomat' => 'mailomat+api://' . urlencode(getSetting('email_mailomat_api_key') ?? '') . '@default',
|
||||
'mailpace' => 'mailpace+api://' . urlencode(getSetting('email_mailpace_api_token') ?? '') . '@default',
|
||||
'mailersend' => 'mailersend+api://' . urlencode(getSetting('email_mailersend_api_key') ?? '') . '@default',
|
||||
'mailtrap' => 'mailtrap+api://' . urlencode(getSetting('email_mailtrap_api_token') ?? '') . '@default',
|
||||
'mandrill' => 'mandrill+api://' . urlencode(getSetting('email_mandrill_api_key') ?? '') . '@default',
|
||||
'microsoftgraph' => 'microsoftgraph+api://' . urlencode(getSetting('email_microsoftgraph_client_id') ?? '') . ':' . urlencode(getSetting('email_microsoftgraph_client_secret') ?? '') . '@default?tenantId=' . urlencode(getSetting('email_microsoftgraph_tenant_id') ?? ''),
|
||||
'postal' => 'postal+api://' . urlencode(getSetting('email_postal_api_key') ?? '') . '@' . urlencode(getSetting('email_postal_base_url') ?? 'default'),
|
||||
'postmark' => 'postmark+api://' . urlencode(getSetting('email_postmark_api_token') ?? '') . '@default',
|
||||
'resend' => 'resend+api://' . urlencode(getSetting('email_resend_api_key') ?? '') . '@default',
|
||||
'scaleway' => 'scaleway+api://' . urlencode(getSetting('email_scaleway_project_id') ?? '') . ':' . urlencode(getSetting('email_scaleway_api_key') ?? '') . '@default',
|
||||
'sendgrid' => 'sendgrid+api://' . urlencode(getSetting('email_sendgrid_api_key') ?? '') . '@default',
|
||||
'sweego' => 'sweego+api://' . urlencode(getSetting('email_sweego_api_key') ?? '') . '@default',
|
||||
default => 'sendmail://default',
|
||||
};
|
||||
|
||||
try {
|
||||
$transport = Transport::fromDsn($dsn);
|
||||
$mailer = new Mailer($transport);
|
||||
$mailer->send($email);
|
||||
} catch (\Throwable $e) {
|
||||
writers()->error()->write($e->getMessage());
|
||||
xr(mailer: $e->getMessage(), to: $to, subject: $subject, body: $body);
|
||||
|
||||
throw new Exception($e->getMessage(), 606);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function get_chevereto_version(bool $full = true): string
|
||||
@@ -1179,12 +1203,27 @@ function loaderHandler(
|
||||
?? '';
|
||||
$envVar['CHEVERETO_TENANT_HANDLE'] = '';
|
||||
$envVar['CHEVERETO_DB_TABLE_ROOT_PREFIX'] = $envVar['CHEVERETO_DB_TABLE_PREFIX'];
|
||||
if ($envVar['CHEVERETO_ENABLE_TENANTS'] === '1') {
|
||||
$envVar['CHEVERETO_CACHE_KEY_ROOT_PREFIX'] = $envVar['CHEVERETO_CACHE_KEY_PREFIX'];
|
||||
// try {
|
||||
// $xrArguments = [
|
||||
// 'isEnabled' => true,
|
||||
// 'isHttps' => false,
|
||||
// 'host' => 'host.docker.internal',
|
||||
// 'port' => 27420,
|
||||
// ];
|
||||
|
||||
// new XrInstance(new Xr(...$xrArguments));
|
||||
// } catch (Throwable) {
|
||||
// // Silent failover
|
||||
// }
|
||||
$isTenantsApi = false;
|
||||
if ($envVar['CHEVERETO_ENABLE_TENANTS'] === '1' || $envVar['CHEVERETO_TENANT'] !== '') {
|
||||
$redis = new Redis();
|
||||
$redis->connect($envVar['CHEVERETO_CACHE_HOST'], (int) $envVar['CHEVERETO_CACHE_PORT']);
|
||||
if ($envVar['CHEVERETO_CACHE_PASSWORD'] !== '') {
|
||||
$redis->auth($envVar['CHEVERETO_CACHE_PASSWORD']);
|
||||
}
|
||||
$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
|
||||
$lookupNamespace = $envVar['CHEVERETO_CACHE_KEY_PREFIX'] . '_:';
|
||||
if (PHP_SAPI === 'cli') {
|
||||
$websiteId = $envVar['CHEVERETO_TENANT'];
|
||||
@@ -1195,13 +1234,19 @@ function loaderHandler(
|
||||
PLAIN;
|
||||
exit(255);
|
||||
}
|
||||
$websiteOptions = $redis->get($lookupNamespace . 'tenant:' . $websiteId);
|
||||
$websiteOptions = unserialize($websiteOptions);
|
||||
$hostname = $websiteOptions['hostname'];
|
||||
/** @var array $websiteOptions */
|
||||
$websiteOptions = $redis->get($lookupNamespace . 'tenant:' . $websiteId) ?: [];
|
||||
$hostname = $websiteOptions['hostname'] ?? '';
|
||||
} else {
|
||||
$hostname = $_server['SERVER_NAME'];
|
||||
if (($envVar['CHEVERETO_SERVICING'] ?? '') === 'docker'
|
||||
&& $hostname === 'host.docker.internal'
|
||||
) {
|
||||
$hostname = $envVar['CHEVERETO_HOSTNAME'];
|
||||
}
|
||||
$isRootHostname = hash_equals($envVar['CHEVERETO_HOSTNAME'], $hostname);
|
||||
$isTenantsApi = $isRootHostname
|
||||
$isLocalhost = in_array($hostname, ['localhost', '127.0.0.1', '::1', $envVar['CHEVERETO_SERVICE_NAME']], true);
|
||||
$isTenantsApi = ($isRootHostname || $isLocalhost)
|
||||
&& str_starts_with(
|
||||
$_server['REQUEST_URI'],
|
||||
$envVar['CHEVERETO_HOSTNAME_PATH']
|
||||
@@ -1220,17 +1265,15 @@ function loaderHandler(
|
||||
} else {
|
||||
$websiteId = $redis->get($lookupNamespace . 'hostname:' . $hostname);
|
||||
if ($websiteId === false) {
|
||||
if ($isRootHostname) {
|
||||
redirect($envVar['CHEVERETO_PROVIDER'] ?? 'https://chevereto.com');
|
||||
}
|
||||
http_response_code(404);
|
||||
echo <<<PLAIN
|
||||
No website defined
|
||||
|
||||
PLAIN;
|
||||
exit(255);
|
||||
}
|
||||
$websiteOptions = $redis->get($lookupNamespace . 'tenant:' . $websiteId);
|
||||
$websiteOptions = unserialize($websiteOptions);
|
||||
/** @var array $websiteOptions */
|
||||
$websiteOptions = $redis->get($lookupNamespace . 'tenant:' . $websiteId) ?: [];
|
||||
}
|
||||
}
|
||||
if ($websiteOptions === []) {
|
||||
@@ -1268,8 +1311,8 @@ function loaderHandler(
|
||||
$envVar['CHEVERETO_TENANT_HANDLE'] = "{$websiteId}_";
|
||||
$envVar['CHEVERETO_HOSTNAME'] = $hostname;
|
||||
if ($websiteId !== '') {
|
||||
$envVar['CHEVERETO_CACHE_KEY_PREFIX'] .= "{$websiteId}:"; // chv:ABC:
|
||||
$envVar['CHEVERETO_DB_TABLE_PREFIX'] .= "{$websiteId}_"; // chv_ABC_
|
||||
$envVar['CHEVERETO_CACHE_KEY_PREFIX'] .= "{$websiteId}:"; // chv:ABC: (tenant)
|
||||
$envVar['CHEVERETO_DB_TABLE_PREFIX'] .= "{$websiteId}_"; // chv_ABC_ (tenant)
|
||||
} else {
|
||||
$envVar['CHEVERETO_CACHE_KEY_PREFIX'] .= '_:'; // chv:_: (global)
|
||||
$envVar['CHEVERETO_DB_TABLE_PREFIX'] .= '_'; // chv__ (global)
|
||||
@@ -1423,6 +1466,7 @@ function loaderHandler(
|
||||
if (env()['CHEVERETO_CACHE_PASSWORD'] !== '') {
|
||||
$redis->auth(env()['CHEVERETO_CACHE_PASSWORD']);
|
||||
}
|
||||
$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
|
||||
$keyValue = new KeyValue(
|
||||
$redis,
|
||||
env()['CHEVERETO_CACHE_KEY_PREFIX'],
|
||||
@@ -1435,7 +1479,13 @@ function loaderHandler(
|
||||
);
|
||||
}
|
||||
new Cache($keyValue);
|
||||
if ($_session === []
|
||||
$isAPI = str_starts_with(
|
||||
server()['REQUEST_URI'] ?? '',
|
||||
env()['CHEVERETO_HOSTNAME_PATH']
|
||||
. '/api/',
|
||||
);
|
||||
if (! ($isAPI || $isTenantsApi)
|
||||
&& $_session === []
|
||||
&& session_status() === PHP_SESSION_NONE
|
||||
&& ACCESS === 'web'
|
||||
) {
|
||||
@@ -1489,7 +1539,8 @@ function loaderHandler(
|
||||
);
|
||||
}
|
||||
define('HTTP_APP_PROTOCOL', Config::host()->isHttps() ? 'https' : 'http');
|
||||
$httpPort = ! in_array(server()['SERVER_PORT'] ?? '80', ['80', '443'], false)
|
||||
// TODO: Enable ENV to force using the port?
|
||||
$httpPort = ! in_array(server()['SERVER_PORT'] ?? '80', ['80', '8080', '443'], false)
|
||||
? ':' . server()['SERVER_PORT']
|
||||
: '';
|
||||
define('URL_APP_PUBLIC', HTTP_APP_PROTOCOL . '://' . Config::host()->hostname() . $httpPort . Config::host()->hostnamePath());
|
||||
@@ -1851,6 +1902,14 @@ function getCounts(string ...$table): array
|
||||
return DB::queryFetchSingle($query);
|
||||
}
|
||||
|
||||
function trialAwareLabel(): string
|
||||
{
|
||||
return match ('1') {
|
||||
env()['CHEVERETO_TRIAL'] => ' [trial]',
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
|
||||
function assertMaxCount(string $table): void
|
||||
{
|
||||
$tablesToEnv = [
|
||||
@@ -1867,7 +1926,7 @@ function assertMaxCount(string $table): void
|
||||
code: 400
|
||||
);
|
||||
}
|
||||
$maxLimit = (int) (env()[$tablesToEnv[$table]] ?? 0);
|
||||
$maxLimit = (int) (envTrialAware()[$tablesToEnv[$table]] ?? 0);
|
||||
if ($maxLimit === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -1875,7 +1934,7 @@ function assertMaxCount(string $table): void
|
||||
if (($count + 1) > $maxLimit) {
|
||||
throw new OverflowException(
|
||||
message(
|
||||
'Maximum number of %t% reached (limit %s%).',
|
||||
'Maximum number of %t% reached (limit %s%).' . trialAwareLabel(),
|
||||
t: $table,
|
||||
s: strval($maxLimit),
|
||||
),
|
||||
@@ -1959,20 +2018,41 @@ function hashString(string $string): string
|
||||
|
||||
function getPoweredByRemarks(): array
|
||||
{
|
||||
$responsible = match (env()['CHEVERETO_CONTEXT']) {
|
||||
'saas' => _s('operator'),
|
||||
default => _s('owner'),
|
||||
};
|
||||
$termsLink = '<a href="'
|
||||
. get_base_url(Handler::var('page_tos')['url'] ?? '')
|
||||
. '">Terms of Service</a>';
|
||||
$softwareLicenseLink = '<a href="https://chevereto.com/license">Chevereto License</a>';
|
||||
if (env()['CHEVERETO_EDITION'] === 'free') {
|
||||
$softwareLicenseLink = '<a href="https://www.gnu.org/licenses/agpl-3.0.en.html#license-text">AGPL-3.0 license</a>';
|
||||
$softwareLicenseLink = match (env()['CHEVERETO_EDITION']) {
|
||||
'free' => '<a href="https://www.gnu.org/licenses/agpl-3.0.en.html#license-text">AGPL-3.0 license</a>',
|
||||
default => '<a href="https://chevereto.com/license">Chevereto License</a>',
|
||||
};
|
||||
$websiteName = getSetting('website_name');
|
||||
if (strtolower($websiteName) === 'chevereto') {
|
||||
$websiteName = 'This website';
|
||||
}
|
||||
$providerLink = '<a href="' . env()['CHEVERETO_PROVIDER_URL'] . '">' . env()['CHEVERETO_PROVIDER_NAME'] . '</a>';
|
||||
$provider = match (env()['CHEVERETO_CONTEXT']) {
|
||||
'saas' => [
|
||||
'url' => env()['CHEVERETO_PROVIDER_URL'],
|
||||
'label' => env()['CHEVERETO_PROVIDER_NAME'],
|
||||
],
|
||||
default => [
|
||||
'url' => get_public_url(),
|
||||
'label' => $websiteName,
|
||||
],
|
||||
};
|
||||
$providerLink = message(
|
||||
'<a href="{{ url }}">{{ label }}</a>',
|
||||
...$provider
|
||||
);
|
||||
$about = _s('This service is based on Chevereto %edition edition software licensed under the %license.', [
|
||||
'%edition' => ucfirst(env()['CHEVERETO_EDITION']),
|
||||
'%license' => $softwareLicenseLink,
|
||||
]);
|
||||
$liability = _s("This website is hosted in a service layer not provided by Chevereto Software, which hereby declare to do not have any control nor access to the management layer of this website and it won't be responsible for this service neither the damages that this service may cause.");
|
||||
$content = _s('File uploads are stored and served from storage facilities provided by %s and managed by The Service Operator.', $providerLink);
|
||||
$content = _s('File uploads are stored and served from storage facilities provided and managed by the %s of this website.', $responsible);
|
||||
if (env()['CHEVERETO_CONTEXT'] === 'saas') {
|
||||
$about = _s('This service operates using Chevereto %edition edition software licensed under the %license.', [
|
||||
'%edition' => ucfirst(env()['CHEVERETO_EDITION']),
|
||||
@@ -1989,7 +2069,7 @@ function getPoweredByRemarks(): array
|
||||
$liability = _s('This website is hosted on a service layer provided by %s. Chevereto Software is not responsible for the operation of this service, nor for any damages that may result from its use.', $providerLink);
|
||||
}
|
||||
if (env()['CHEVERETO_ENABLE_LOCAL_STORAGE'] === '0') {
|
||||
$content = _s('File uploads are stored and served using external storage providers configured by The Service Operator.')
|
||||
$content = _s('File uploads are stored and served using external storage providers configured by the %s of this website.', $responsible)
|
||||
. ' '
|
||||
. _s('%s only hosts the database and application service layer.', $providerLink)
|
||||
. ' '
|
||||
@@ -2029,8 +2109,7 @@ function runAppCommand(
|
||||
}
|
||||
$logger->write(
|
||||
<<<PLAIN
|
||||
{$commandLine}
|
||||
{$exit}
|
||||
{$exit}: {$commandLine}
|
||||
|
||||
PLAIN
|
||||
);
|
||||
|
||||
@@ -17,6 +17,7 @@ use Chevereto\Encryption\Encryption;
|
||||
use Chevereto\Exceptions\NotFoundException;
|
||||
use Chevereto\Legacy\Classes\DB;
|
||||
use Chevereto\Legacy\Classes\Stat;
|
||||
use ErrorException;
|
||||
use PDO;
|
||||
use Redis;
|
||||
use function Chevereto\Legacy\G\datetimegmt;
|
||||
@@ -32,6 +33,8 @@ class Tenants
|
||||
'tenants_variables',
|
||||
];
|
||||
|
||||
private string $cacheRootPrefix;
|
||||
|
||||
private string $cachePrefix;
|
||||
|
||||
private string $tableRootPrefix;
|
||||
@@ -42,6 +45,7 @@ class Tenants
|
||||
private Encryption $encryption,
|
||||
private WriterInterface $logger = new NullWriter(),
|
||||
) {
|
||||
$this->cacheRootPrefix = env()['CHEVERETO_CACHE_KEY_ROOT_PREFIX']; // chv:
|
||||
$this->cachePrefix = env()['CHEVERETO_CACHE_KEY_PREFIX']; // chv:<websiteId>:
|
||||
$this->tableRootPrefix = env()['CHEVERETO_DB_TABLE_ROOT_PREFIX']; // chv_<websiteId>_
|
||||
}
|
||||
@@ -226,7 +230,7 @@ class Tenants
|
||||
$tenantKey = $this->getCacheKey('tenant', $tenantId);
|
||||
$tenantCache = $this->redis->get($tenantKey);
|
||||
if ($tenantCache && $tenant === null) {
|
||||
$tenant = unserialize($tenantCache);
|
||||
$tenant = $tenantCache;
|
||||
}
|
||||
$hostname = $tenant['hostname'] ?? null;
|
||||
if ($hostname !== null) {
|
||||
@@ -250,6 +254,24 @@ class Tenants
|
||||
PLAIN
|
||||
);
|
||||
}
|
||||
$tenantCachePattern = $this->cacheRootPrefix . $tenantId . ':*';
|
||||
$iterator = null;
|
||||
while ($iterator !== 0) {
|
||||
$scan = $this->redis->scan($iterator, "{$tenantCachePattern}");
|
||||
foreach ($scan as $key) {
|
||||
$result = (bool) $this->redis->del($key);
|
||||
$status = 'DELETE';
|
||||
if ($result === false && ! $this->redis->get($key)) {
|
||||
$status = ' 404';
|
||||
}
|
||||
$this->logger->write(
|
||||
<<<PLAIN
|
||||
* {$status} > {$key}
|
||||
|
||||
PLAIN
|
||||
);
|
||||
}
|
||||
}
|
||||
if ($dropTables) {
|
||||
$likePattern = "{$this->tableRootPrefix}{$tenantId}_%";
|
||||
$this->db->query(
|
||||
@@ -371,7 +393,8 @@ class Tenants
|
||||
'managers', s.managers,
|
||||
'pages', s.pages,
|
||||
'storages', s.storages,
|
||||
'categories', s.categories
|
||||
'categories', s.categories,
|
||||
'login_providers', s.login_providers
|
||||
) AS stats
|
||||
FROM `{$tableTenantsStats}` AS s
|
||||
WHERE s.tenant_id = t.id
|
||||
@@ -464,12 +487,14 @@ class Tenants
|
||||
$this->mergeTenantCacheable($tenant);
|
||||
$cached = $this->redis->get($tenantKey);
|
||||
if ($cached) {
|
||||
/** @var array $current */
|
||||
$current = unserialize($cached);
|
||||
$currentHostname = $current['hostname'];
|
||||
if ($currentHostname !== $tenant['hostname']) {
|
||||
$currentKey = $this->getCacheKey('hostname', $tenant['hostname']);
|
||||
$this->redis->del($currentKey);
|
||||
try {
|
||||
/** @var array $cached */
|
||||
$cachedHostname = $cached['hostname'];
|
||||
if ($cachedHostname !== $tenant['hostname']) {
|
||||
$cachedKey = $this->getCacheKey('hostname', $cachedHostname);
|
||||
$this->redis->del($cachedKey);
|
||||
}
|
||||
} catch (ErrorException) {
|
||||
}
|
||||
}
|
||||
$this->cacheTenantArray($tenant);
|
||||
@@ -700,8 +725,6 @@ class Tenants
|
||||
$tableTenants = $this->db::getTable('tenants');
|
||||
$tableTenantsPlans = $this->db::getTable('tenants_plans');
|
||||
$tableTenantsStats = $this->db::getTable('tenants_stats');
|
||||
$tablePrefixTenant = $this->tableRootPrefix . $tenantId . '_';
|
||||
$statQuery = Stat::getStatQuery($tablePrefixTenant);
|
||||
$this->db->query(
|
||||
<<<SQL
|
||||
SELECT
|
||||
@@ -737,7 +760,8 @@ class Tenants
|
||||
'managers', s.managers,
|
||||
'pages', s.pages,
|
||||
'storages', s.storages,
|
||||
'categories', s.categories
|
||||
'categories', s.categories,
|
||||
'login_providers', s.login_providers
|
||||
) AS stats
|
||||
FROM `{$tableTenantsStats}` AS s
|
||||
WHERE s.tenant_id = t.id
|
||||
@@ -767,7 +791,7 @@ class Tenants
|
||||
$this->redis->mset(
|
||||
[
|
||||
$hostnameKey => $tenant['id'],
|
||||
$tenantKey => serialize($tenant),
|
||||
$tenantKey => $tenant,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
146
app/src/Tenants/TenantsConfig.php
Normal file
146
app/src/Tenants/TenantsConfig.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Chevereto.
|
||||
*
|
||||
* (c) Rodolfo Berrios <rodolfo@chevereto.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chevereto\Tenants;
|
||||
|
||||
use Redis;
|
||||
use stdClass;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Generates Traefik dynamic configuration for all tenants.
|
||||
*/
|
||||
final class TenantsConfig
|
||||
{
|
||||
public const CF_RANGES = [
|
||||
'173.245.48.0/20',
|
||||
'103.21.244.0/22',
|
||||
'103.22.200.0/22',
|
||||
'103.31.4.0/22',
|
||||
'141.101.64.0/18',
|
||||
'108.162.192.0/18',
|
||||
'190.93.240.0/20',
|
||||
'188.114.96.0/20',
|
||||
'197.234.240.0/22',
|
||||
'198.41.128.0/17',
|
||||
'162.158.0.0/15',
|
||||
'104.16.0.0/13',
|
||||
'104.24.0.0/14',
|
||||
'172.64.0.0/13',
|
||||
'131.0.72.0/22',
|
||||
'2400:cb00::/32',
|
||||
'2606:4700::/32',
|
||||
'2803:f800::/32',
|
||||
'2405:b500::/32',
|
||||
'2405:8100::/32',
|
||||
'2a06:98c0::/29',
|
||||
'2c0f:f248::/32',
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
public readonly Redis $redis,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getConfig(
|
||||
Tenants $tenants,
|
||||
string $service,
|
||||
int $port,
|
||||
array $middleware
|
||||
): array {
|
||||
$middlewares = [];
|
||||
if (in_array('cf-only', $middleware)) {
|
||||
$middlewares['cf-only'] = [
|
||||
'ipAllowList' => [
|
||||
'sourceRange' => $this->getCFRanges(),
|
||||
],
|
||||
];
|
||||
}
|
||||
$rows = $tenants->getTenantsRows();
|
||||
$routers = [];
|
||||
foreach ($rows as $row) {
|
||||
$row = array_merge($row, [
|
||||
'limits' => [],
|
||||
'env' => [],
|
||||
]);
|
||||
$tenant = Tenant::fromRow($row);
|
||||
$routers[$tenant->id] = $this->getTenantRouter(
|
||||
$tenant,
|
||||
$service,
|
||||
array_keys($middlewares)
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
'http' => [
|
||||
'routers' => $routers,
|
||||
'services' => [
|
||||
$service => [
|
||||
'loadBalancer' => [
|
||||
'servers' => [
|
||||
[
|
||||
'url' => sprintf(
|
||||
'http://%s:%d',
|
||||
$service,
|
||||
$port
|
||||
),
|
||||
],
|
||||
],
|
||||
'passHostHeader' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
'middlewares' => $middlewares,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function getTenantRouter(Tenant $tenant, string $service, array $middlewares): array
|
||||
{
|
||||
return [
|
||||
'rule' => "Host(`{$tenant->hostname}`)",
|
||||
'service' => $service,
|
||||
'entryPoints' => ['websecure'],
|
||||
'tls' => new stdClass(),
|
||||
'middlewares' => $middlewares,
|
||||
];
|
||||
}
|
||||
|
||||
public function getCFRanges(): array
|
||||
{
|
||||
$cacheKey = 'cf:ip-ranges';
|
||||
$cached = $this->redis->get($cacheKey);
|
||||
if ($cached !== false) {
|
||||
return json_decode($cached, true);
|
||||
}
|
||||
|
||||
try {
|
||||
$v4 = file_get_contents('https://www.cloudflare.com/ips-v4');
|
||||
$v6 = file_get_contents('https://www.cloudflare.com/ips-v6');
|
||||
$ranges = array_values(
|
||||
array_filter(
|
||||
array_map(
|
||||
'trim',
|
||||
array_merge(
|
||||
explode("\n", $v4),
|
||||
explode("\n", $v6),
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
} catch (Throwable) {
|
||||
return self::CF_RANGES;
|
||||
}
|
||||
$this->redis->setex($cacheKey, 3600, json_encode($ranges));
|
||||
|
||||
return $ranges;
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,55 @@ function env(): array
|
||||
return $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ENV array but limited when CHEVERETO_TRIAL='1' by
|
||||
* * `CHEVERETO_TRIAL_MAX_*` (numeric limits) and
|
||||
* * `CHEVERETO_TRIAL_ENABLE_*` (boolean flags).
|
||||
*
|
||||
* The trial variables may only be used to *decrease* the value of the
|
||||
* corresponding default. In other words, if the trial value is more
|
||||
* permissive than the default, the default value will always be returned.
|
||||
* This keeps the SaaS trial from accidentally granting greater privileges
|
||||
* than the shipped product.
|
||||
*/
|
||||
function envTrialAware(): array
|
||||
{
|
||||
if (env()['CHEVERETO_TRIAL'] !== '1') {
|
||||
return env();
|
||||
}
|
||||
static $cache;
|
||||
if (! isset($cache)) {
|
||||
$cache = [];
|
||||
/** @var string $value */
|
||||
foreach (env() as $key => $value) {
|
||||
$defaultValue = $value;
|
||||
if (str_starts_with($key, 'CHEVERETO_MAX_')) {
|
||||
$trialMax = 'CHEVERETO_TRIAL_MAX_' . substr($key, strlen('CHEVERETO_MAX_'));
|
||||
/** @var string $trialValue */
|
||||
$trialValue = array_key_exists($trialMax, env())
|
||||
? env()[$trialMax]
|
||||
: '0';
|
||||
$cache[$key] = intval($trialValue) > intval($defaultValue)
|
||||
? $defaultValue
|
||||
: $trialValue;
|
||||
} elseif (str_starts_with($key, 'CHEVERETO_ENABLE_')) {
|
||||
$trialEnable = 'CHEVERETO_TRIAL_ENABLE_' . substr($key, strlen('CHEVERETO_ENABLE_'));
|
||||
/** @var string $trialValue */
|
||||
$trialValue = array_key_exists($trialEnable, env())
|
||||
? env()[$trialEnable]
|
||||
: '0';
|
||||
$cache[$key] = intval($trialValue) > intval($defaultValue)
|
||||
? $defaultValue
|
||||
: $trialValue;
|
||||
} else {
|
||||
$cache[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $cache;
|
||||
}
|
||||
|
||||
function request(): array
|
||||
{
|
||||
static $cache;
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 453 KiB After Width: | Height: | Size: 344 KiB |
@@ -3353,6 +3353,11 @@ body.landing .top-btn-text {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.panel-thumb-list a {
|
||||
display: block;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.panel-thumb-list img {
|
||||
display: block;
|
||||
width: 47px;
|
||||
@@ -4527,6 +4532,7 @@ a.top-user-avatar {
|
||||
.header-content .user-image img,
|
||||
.header .user-image img {
|
||||
border-radius: 50%;
|
||||
-o-object-fit: cover;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -19,13 +19,11 @@ if (!defined('ACCESS') || !ACCESS) {
|
||||
}
|
||||
if (Login::isLoggedUser()) {
|
||||
$user_albums = [];
|
||||
if (Login::getUser()['album_count'] > 0) {
|
||||
$user_albums = Handler::cond('owner')
|
||||
&& Handler::var('user_items_editor') !== null
|
||||
&& isset(Handler::var('user_items_editor')['user_albums'])
|
||||
? Handler::var('user_items_editor')['user_albums']
|
||||
: User::getAlbums(Login::getUser());
|
||||
}
|
||||
$user_albums = Handler::cond('owner')
|
||||
&& Handler::var('user_items_editor') !== null
|
||||
&& isset(Handler::var('user_items_editor')['user_albums'])
|
||||
? Handler::var('user_items_editor')['user_albums']
|
||||
: User::getAlbums(Login::getUser());
|
||||
}
|
||||
?>
|
||||
<div id="anywhere-upload" class="no-select upload-box upload-box--fixed upload-box--hidden queueEmpty" data-queue-size="0">
|
||||
@@ -164,16 +162,23 @@ if (Login::isLoggedUser()) {
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
if (Login::isLoggedUser() && Login::getUser()['album_count'] > 0) {
|
||||
if (Login::isLoggedUser()) {
|
||||
$isSelectedAlbum = Handler::var('album') !== [] && isset(Handler::var('album')['id_encoded']);
|
||||
?>
|
||||
<div class="input-label upload-input-col center-box text-align-left">
|
||||
<label for="upload-album-id"><?php _ne('Album', 'Albums', 1); ?></label>
|
||||
<select name="upload-album-id" id="upload-album-id" class="text-input">
|
||||
<option value="_" disabled<?php echo !$isSelectedAlbum ? ' selected' : ''; ?>><?php _se('Select %s', _s('album')); ?></option>
|
||||
<option value><?php _se('Create or move to %s after upload', _s('album')); ?></option>
|
||||
<?php
|
||||
|
||||
$user_album_options_html = [];
|
||||
foreach ($user_albums as $album) {
|
||||
if(!$album['id_encoded']) {
|
||||
continue;
|
||||
}
|
||||
$user_album_options_html[] = strtr('<option value="%id"%selected>%name</option>', [
|
||||
'%selected' => (Handler::var('album') !== [] && isset(Handler::var('album')['id_encoded']) && Handler::var('album')['id_encoded'] == $album['id_encoded']) ? ' selected' : '',
|
||||
'%selected' => ($isSelectedAlbum && isset(Handler::var('album')['id_encoded']) && Handler::var('album')['id_encoded'] == $album['id_encoded']) ? ' selected' : '',
|
||||
'%id' => $album['id_encoded'],
|
||||
'%name' => $album['indent_string'] . $album['name_with_privacy_readable_html']
|
||||
]);
|
||||
|
||||
@@ -38,31 +38,52 @@ echo read_the_docs_settings('email', _s('Email')); ?>
|
||||
<?php
|
||||
$mailOptions = [
|
||||
'smtp' => _s('SMTP'),
|
||||
'mail' => _s('PHP mail() func.'),
|
||||
'ahasend' => 'AhaSend',
|
||||
'ses' => 'Amazon SES',
|
||||
'azure' => 'Azure',
|
||||
'brevo' => 'Brevo',
|
||||
'infobip' => 'Infobip',
|
||||
'mailersend' => 'MailerSend',
|
||||
'mailgun' => 'Mailgun',
|
||||
'mailjet' => 'Mailjet',
|
||||
'mailomat' => 'Mailomat',
|
||||
'mailpace' => 'MailPace',
|
||||
'mailtrap' => 'Mailtrap',
|
||||
'mandrill' => 'Mandrill',
|
||||
'microsoftgraph' => 'Microsoft Graph',
|
||||
'postal' => 'Postal',
|
||||
'postmark' => 'Postmark',
|
||||
'resend' => 'Resend',
|
||||
'scaleway' => 'Scaleway',
|
||||
'sendgrid' => 'SendGrid',
|
||||
'sweego' => 'Sweego',
|
||||
];
|
||||
if(env()['CHEVERETO_SERVICING'] === 'server') {
|
||||
$mailOptions['mail'] = _s('PHP mail() func.');
|
||||
if (env()['CHEVERETO_SERVICING'] !== 'server') {
|
||||
unset($mailOptions['mail']);
|
||||
}
|
||||
$mailComboClass = '';
|
||||
if (count($mailOptions) == 2 && (Handler::var('safe_post')
|
||||
? Handler::var('safe_post')['email_mode']
|
||||
: Settings::get('email_mode')) !== 'smtp'
|
||||
) {
|
||||
$mailComboClass = ' soft-hidden';
|
||||
if (env()['CHEVERETO_CONTEXT'] === 'saas') {
|
||||
unset($mailOptions['smtp'], $mailOptions['mail']);
|
||||
}
|
||||
$currentEmailMode = Handler::var('safe_post') ? Handler::var('safe_post')['email_mode'] : Settings::get('email_mode');
|
||||
if(!array_key_exists($currentEmailMode, $mailOptions)) {
|
||||
$currentEmailMode = array_key_first($mailOptions);
|
||||
}
|
||||
?>
|
||||
<div class="input-label">
|
||||
<label for="email_mode"><?php _se('Email mode'); ?></label>
|
||||
<label for="email_mode"><?php _se('Email %s', 'API'); ?></label>
|
||||
<div class="c5 phablet-c1"><select type="text" name="email_mode" id="email_mode" class="text-input" data-combo="mail-combo">
|
||||
<?php echo get_select_options_html($mailOptions, Handler::var('safe_post') ? Handler::var('safe_post')['email_mode'] : Settings::get('email_mode')); ?>
|
||||
<?php echo get_select_options_html($mailOptions, $currentEmailMode); ?>
|
||||
</select></div>
|
||||
<div class="input-below input-warning red-warning clear-both"><?php echo Handler::var('input_errors')['email_mode'] ?? ''; ?></div>
|
||||
</div>
|
||||
<div id="mail-combo">
|
||||
<?php
|
||||
if (isset($GLOBALS['SMTPDebug'])) {
|
||||
echo '<p class="highlight padding-5 c9 phablet-c1">' . nl2br($GLOBALS['SMTPDebug']) . '</p>';
|
||||
} ?>
|
||||
<div data-combo-value="smtp" class="switch-combo c9 phablet-c1<?php echo $mailComboClass; ?>">
|
||||
<?php if (isset($GLOBALS['SMTPDebug'])) {
|
||||
echo '<p class="highlight padding-5 c9 phablet-c1 margin-bottom-10">' . nl2br($GLOBALS['SMTPDebug']) . '</p>';
|
||||
} ?>
|
||||
<div data-combo-value="smtp" class="switch-combo c9 phablet-c1<?php if ($currentEmailMode !== 'smtp') {
|
||||
echo ' soft-hidden';
|
||||
} ?>">
|
||||
<div class="input-label">
|
||||
<label for="email_smtp_server"><?php _se('SMTP server and port'); ?></label>
|
||||
<div class="overflow-auto">
|
||||
@@ -89,10 +110,225 @@ if (count($mailOptions) == 2 && (Handler::var('safe_post')
|
||||
<div class="input-label c5">
|
||||
<label for="email_smtp_server_security"><?php _se('SMTP security'); ?></label>
|
||||
<select type="text" name="email_smtp_server_security" id="email_smtp_server_security" class="text-input">
|
||||
<?php
|
||||
echo get_select_options_html(['tls' => 'TLS', 'ssl' => 'SSL', 'unsecured' => _s('Unsecured')], Handler::var('safe_post') ? Handler::var('safe_post')['email_smtp_server_security'] : Settings::get('email_smtp_server_security')); ?>
|
||||
<?php echo get_select_options_html(['tls' => 'TLS', 'ssl' => 'SSL', 'unsecured' => _s('Unsecured')], Handler::var('safe_post') ? Handler::var('safe_post')['email_smtp_server_security'] : Settings::get('email_smtp_server_security')); ?>
|
||||
</select>
|
||||
<div class="input-below input-warning red-warning clear-both"><?php echo Handler::var('input_errors')['email_smtp_server_security'] ?? ''; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-combo-value="ahasend" class="switch-combo c9 phablet-c1<?php if ($currentEmailMode !== 'ahasend') {
|
||||
echo ' soft-hidden';
|
||||
} ?>">
|
||||
<div class="input-label">
|
||||
<label for="email_ahasend_api_key"><?php _se('%s API key', 'AhaSend'); ?></label>
|
||||
<input type="text" name="email_ahasend_api_key" id="email_ahasend_api_key" class="text-input" value="<?php echo Handler::var('safe_post')['email_ahasend_api_key'] ?? Settings::get('email_ahasend_api_key'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_ahasend_api_key'] ?? ''; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-combo-value="ses" class="switch-combo c9 phablet-c1<?php if ($currentEmailMode !== 'ses') {
|
||||
echo ' soft-hidden';
|
||||
} ?>">
|
||||
<div class="input-label">
|
||||
<label for="email_ses_access_key"><?php _se('%s access key', 'Amazon SES'); ?></label>
|
||||
<input type="text" name="email_ses_access_key" id="email_ses_access_key" class="text-input" value="<?php echo Handler::var('safe_post')['email_ses_access_key'] ?? Settings::get('email_ses_access_key'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_ses_access_key'] ?? ''; ?></div>
|
||||
</div>
|
||||
<div class="input-label">
|
||||
<label for="email_ses_secret_key"><?php _se('%s secret key', 'Amazon SES'); ?></label>
|
||||
<input type="password" name="email_ses_secret_key" id="email_ses_secret_key" class="text-input" value="<?php echo Handler::var('safe_post')['email_ses_secret_key'] ?? Settings::get('email_ses_secret_key'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_ses_secret_key'] ?? ''; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-combo-value="azure" class="switch-combo c9 phablet-c1<?php if ($currentEmailMode !== 'azure') {
|
||||
echo ' soft-hidden';
|
||||
} ?>">
|
||||
<div class="input-label">
|
||||
<label for="email_azure_resource_name"><?php _se('%s resource name', 'Azure'); ?></label>
|
||||
<input type="text" name="email_azure_resource_name" id="email_azure_resource_name" class="text-input" value="<?php echo Handler::var('safe_post')['email_azure_resource_name'] ?? Settings::get('email_azure_resource_name'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_azure_resource_name'] ?? ''; ?></div>
|
||||
</div>
|
||||
<div class="input-label">
|
||||
<label for="email_azure_key"><?php _se('%s access key', 'Azure'); ?></label>
|
||||
<input type="password" name="email_azure_key" id="email_azure_key" class="text-input" value="<?php echo Handler::var('safe_post')['email_azure_key'] ?? Settings::get('email_azure_key'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_azure_key'] ?? ''; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-combo-value="brevo" class="switch-combo c9 phablet-c1<?php if ($currentEmailMode !== 'brevo') {
|
||||
echo ' soft-hidden';
|
||||
} ?>">
|
||||
<div class="input-label">
|
||||
<label for="email_brevo_api_key"><?php _se('%s API key', 'Brevo'); ?></label>
|
||||
<input type="text" name="email_brevo_api_key" id="email_brevo_api_key" class="text-input" value="<?php echo Handler::var('safe_post')['email_brevo_api_key'] ?? Settings::get('email_brevo_api_key'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_brevo_api_key'] ?? ''; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-combo-value="infobip" class="switch-combo c9 phablet-c1<?php if ($currentEmailMode !== 'infobip') {
|
||||
echo ' soft-hidden';
|
||||
} ?>">
|
||||
<div class="input-label">
|
||||
<label for="email_infobip_api_key"><?php _se('%s API key', 'Infobip'); ?></label>
|
||||
<input type="text" name="email_infobip_api_key" id="email_infobip_api_key" class="text-input" value="<?php echo Handler::var('safe_post')['email_infobip_api_key'] ?? Settings::get('email_infobip_api_key'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_infobip_api_key'] ?? ''; ?></div>
|
||||
</div>
|
||||
<div class="input-label">
|
||||
<label for="email_infobip_base_url"><?php _se('%s base URL', 'Infobip'); ?></label>
|
||||
<input type="text" name="email_infobip_base_url" id="email_infobip_base_url" class="text-input" value="<?php echo Handler::var('safe_post')['email_infobip_base_url'] ?? Settings::get('email_infobip_base_url'); ?>" placeholder="e.g. xxxxx.api.infobip.com">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_infobip_base_url'] ?? ''; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-combo-value="mailersend" class="switch-combo c9 phablet-c1<?php if ($currentEmailMode !== 'mailersend') {
|
||||
echo ' soft-hidden';
|
||||
} ?>">
|
||||
<div class="input-label">
|
||||
<label for="email_mailersend_api_key"><?php _se('%s API key', 'MailerSend'); ?></label>
|
||||
<input type="text" name="email_mailersend_api_key" id="email_mailersend_api_key" class="text-input" value="<?php echo Handler::var('safe_post')['email_mailersend_api_key'] ?? Settings::get('email_mailersend_api_key'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_mailersend_api_key'] ?? ''; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-combo-value="mailgun" class="switch-combo c9 phablet-c1<?php if ($currentEmailMode !== 'mailgun') {
|
||||
echo ' soft-hidden';
|
||||
} ?>">
|
||||
<div class="input-label">
|
||||
<label for="email_mailgun_api_key"><?php _se('%s API key', 'Mailgun'); ?></label>
|
||||
<input type="text" name="email_mailgun_api_key" id="email_mailgun_api_key" class="text-input" value="<?php echo Handler::var('safe_post')['email_mailgun_api_key'] ?? Settings::get('email_mailgun_api_key'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_mailgun_api_key'] ?? ''; ?></div>
|
||||
</div>
|
||||
<div class="input-label">
|
||||
<label for="email_mailgun_domain"><?php _se('%s domain', 'Mailgun'); ?></label>
|
||||
<input type="text" name="email_mailgun_domain" id="email_mailgun_domain" class="text-input" value="<?php echo Handler::var('safe_post')['email_mailgun_domain'] ?? Settings::get('email_mailgun_domain'); ?>" placeholder="e.g. mg.example.com">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_mailgun_domain'] ?? ''; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-combo-value="mailjet" class="switch-combo c9 phablet-c1<?php if ($currentEmailMode !== 'mailjet') {
|
||||
echo ' soft-hidden';
|
||||
} ?>">
|
||||
<div class="input-label">
|
||||
<label for="email_mailjet_access_key"><?php _se('%s API key', 'Mailjet'); ?></label>
|
||||
<input type="text" name="email_mailjet_access_key" id="email_mailjet_access_key" class="text-input" value="<?php echo Handler::var('safe_post')['email_mailjet_access_key'] ?? Settings::get('email_mailjet_access_key'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_mailjet_access_key'] ?? ''; ?></div>
|
||||
</div>
|
||||
<div class="input-label">
|
||||
<label for="email_mailjet_secret_key"><?php _se('%s secret key', 'Mailjet'); ?></label>
|
||||
<input type="password" name="email_mailjet_secret_key" id="email_mailjet_secret_key" class="text-input" value="<?php echo Handler::var('safe_post')['email_mailjet_secret_key'] ?? Settings::get('email_mailjet_secret_key'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_mailjet_secret_key'] ?? ''; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-combo-value="mailomat" class="switch-combo c9 phablet-c1<?php if ($currentEmailMode !== 'mailomat') {
|
||||
echo ' soft-hidden';
|
||||
} ?>">
|
||||
<div class="input-label">
|
||||
<label for="email_mailomat_api_key"><?php _se('%s API key', 'Mailomat'); ?></label>
|
||||
<input type="text" name="email_mailomat_api_key" id="email_mailomat_api_key" class="text-input" value="<?php echo Handler::var('safe_post')['email_mailomat_api_key'] ?? Settings::get('email_mailomat_api_key'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_mailomat_api_key'] ?? ''; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-combo-value="mailpace" class="switch-combo c9 phablet-c1<?php if ($currentEmailMode !== 'mailpace') {
|
||||
echo ' soft-hidden';
|
||||
} ?>">
|
||||
<div class="input-label">
|
||||
<label for="email_mailpace_api_token"><?php _se('%s API token', 'MailPace'); ?></label>
|
||||
<input type="text" name="email_mailpace_api_token" id="email_mailpace_api_token" class="text-input" value="<?php echo Handler::var('safe_post')['email_mailpace_api_token'] ?? Settings::get('email_mailpace_api_token'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_mailpace_api_token'] ?? ''; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-combo-value="mailtrap" class="switch-combo c9 phablet-c1<?php if ($currentEmailMode !== 'mailtrap') {
|
||||
echo ' soft-hidden';
|
||||
} ?>">
|
||||
<div class="input-label">
|
||||
<label for="email_mailtrap_api_token"><?php _se('%s API token', 'Mailtrap'); ?></label>
|
||||
<input type="text" name="email_mailtrap_api_token" id="email_mailtrap_api_token" class="text-input" value="<?php echo Handler::var('safe_post')['email_mailtrap_api_token'] ?? Settings::get('email_mailtrap_api_token'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_mailtrap_api_token'] ?? ''; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-combo-value="mandrill" class="switch-combo c9 phablet-c1<?php if ($currentEmailMode !== 'mandrill') {
|
||||
echo ' soft-hidden';
|
||||
} ?>">
|
||||
<div class="input-label">
|
||||
<label for="email_mandrill_api_key"><?php _se('%s API key', 'Mandrill'); ?></label>
|
||||
<input type="text" name="email_mandrill_api_key" id="email_mandrill_api_key" class="text-input" value="<?php echo Handler::var('safe_post')['email_mandrill_api_key'] ?? Settings::get('email_mandrill_api_key'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_mandrill_api_key'] ?? ''; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-combo-value="microsoftgraph" class="switch-combo c9 phablet-c1<?php if ($currentEmailMode !== 'microsoftgraph') {
|
||||
echo ' soft-hidden';
|
||||
} ?>">
|
||||
<div class="input-label">
|
||||
<label for="email_microsoftgraph_client_id"><?php _se('%s client app ID', 'Microsoft Graph'); ?></label>
|
||||
<input type="text" name="email_microsoftgraph_client_id" id="email_microsoftgraph_client_id" class="text-input" value="<?php echo Handler::var('safe_post')['email_microsoftgraph_client_id'] ?? Settings::get('email_microsoftgraph_client_id'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_microsoftgraph_client_id'] ?? ''; ?></div>
|
||||
</div>
|
||||
<div class="input-label">
|
||||
<label for="email_microsoftgraph_client_secret"><?php _se('%s client secret', 'Microsoft Graph'); ?></label>
|
||||
<input type="password" name="email_microsoftgraph_client_secret" id="email_microsoftgraph_client_secret" class="text-input" value="<?php echo Handler::var('safe_post')['email_microsoftgraph_client_secret'] ?? Settings::get('email_microsoftgraph_client_secret'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_microsoftgraph_client_secret'] ?? ''; ?></div>
|
||||
</div>
|
||||
<div class="input-label">
|
||||
<label for="email_microsoftgraph_tenant_id"><?php _se('%s tenant ID', 'Microsoft Graph'); ?></label>
|
||||
<input type="text" name="email_microsoftgraph_tenant_id" id="email_microsoftgraph_tenant_id" class="text-input" value="<?php echo Handler::var('safe_post')['email_microsoftgraph_tenant_id'] ?? Settings::get('email_microsoftgraph_tenant_id'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_microsoftgraph_tenant_id'] ?? ''; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-combo-value="postal" class="switch-combo c9 phablet-c1<?php if ($currentEmailMode !== 'postal') {
|
||||
echo ' soft-hidden';
|
||||
} ?>">
|
||||
<div class="input-label">
|
||||
<label for="email_postal_api_key"><?php _se('%s API key', 'Postal'); ?></label>
|
||||
<input type="text" name="email_postal_api_key" id="email_postal_api_key" class="text-input" value="<?php echo Handler::var('safe_post')['email_postal_api_key'] ?? Settings::get('email_postal_api_key'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_postal_api_key'] ?? ''; ?></div>
|
||||
</div>
|
||||
<div class="input-label">
|
||||
<label for="email_postal_base_url"><?php _se('%s server URL', 'Postal'); ?></label>
|
||||
<input type="text" name="email_postal_base_url" id="email_postal_base_url" class="text-input" value="<?php echo Handler::var('safe_post')['email_postal_base_url'] ?? Settings::get('email_postal_base_url'); ?>" placeholder="e.g. postal.example.com">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_postal_base_url'] ?? ''; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-combo-value="postmark" class="switch-combo c9 phablet-c1<?php if ($currentEmailMode !== 'postmark') {
|
||||
echo ' soft-hidden';
|
||||
} ?>">
|
||||
<div class="input-label">
|
||||
<label for="email_postmark_api_token"><?php _se('%s server API token', 'Postmark'); ?></label>
|
||||
<input type="text" name="email_postmark_api_token" id="email_postmark_api_token" class="text-input" value="<?php echo Handler::var('safe_post')['email_postmark_api_token'] ?? Settings::get('email_postmark_api_token'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_postmark_api_token'] ?? ''; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-combo-value="resend" class="switch-combo c9 phablet-c1<?php if ($currentEmailMode !== 'resend') {
|
||||
echo ' soft-hidden';
|
||||
} ?>">
|
||||
<div class="input-label">
|
||||
<label for="email_resend_api_key"><?php _se('%s API key', 'Resend'); ?></label>
|
||||
<input type="text" name="email_resend_api_key" id="email_resend_api_key" class="text-input" value="<?php echo Handler::var('safe_post')['email_resend_api_key'] ?? Settings::get('email_resend_api_key'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_resend_api_key'] ?? ''; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-combo-value="scaleway" class="switch-combo c9 phablet-c1<?php if ($currentEmailMode !== 'scaleway') {
|
||||
echo ' soft-hidden';
|
||||
} ?>">
|
||||
<div class="input-label">
|
||||
<label for="email_scaleway_project_id"><?php _se('%s project ID', 'Scaleway'); ?></label>
|
||||
<input type="text" name="email_scaleway_project_id" id="email_scaleway_project_id" class="text-input" value="<?php echo Handler::var('safe_post')['email_scaleway_project_id'] ?? Settings::get('email_scaleway_project_id'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_scaleway_project_id'] ?? ''; ?></div>
|
||||
</div>
|
||||
<div class="input-label">
|
||||
<label for="email_scaleway_api_key"><?php _se('%s API key', 'Scaleway'); ?></label>
|
||||
<input type="password" name="email_scaleway_api_key" id="email_scaleway_api_key" class="text-input" value="<?php echo Handler::var('safe_post')['email_scaleway_api_key'] ?? Settings::get('email_scaleway_api_key'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_scaleway_api_key'] ?? ''; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-combo-value="sendgrid" class="switch-combo c9 phablet-c1<?php if ($currentEmailMode !== 'sendgrid') {
|
||||
echo ' soft-hidden';
|
||||
} ?>">
|
||||
<div class="input-label">
|
||||
<label for="email_sendgrid_api_key"><?php _se('%s API key', 'SendGrid'); ?></label>
|
||||
<input type="text" name="email_sendgrid_api_key" id="email_sendgrid_api_key" class="text-input" value="<?php echo Handler::var('safe_post')['email_sendgrid_api_key'] ?? Settings::get('email_sendgrid_api_key'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_sendgrid_api_key'] ?? ''; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-combo-value="sweego" class="switch-combo c9 phablet-c1<?php if ($currentEmailMode !== 'sweego') {
|
||||
echo ' soft-hidden';
|
||||
} ?>">
|
||||
<div class="input-label">
|
||||
<label for="email_sweego_api_key"><?php _se('%s API key', 'Sweego'); ?></label>
|
||||
<input type="text" name="email_sweego_api_key" id="email_sweego_api_key" class="text-input" value="<?php echo Handler::var('safe_post')['email_sweego_api_key'] ?? Settings::get('email_sweego_api_key'); ?>">
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['email_sweego_api_key'] ?? ''; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
use Chevereto\Legacy\Classes\Login;
|
||||
use Chevereto\Legacy\Classes\Settings;
|
||||
use function Chevereto\Legacy\G\get_base_url;
|
||||
use Chevereto\Legacy\G\Handler;
|
||||
use function Chevereto\Legacy\G\safe_html;
|
||||
use function Chevereto\Legacy\get_select_options_html;
|
||||
use function Chevereto\Legacy\getSetting;
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
if (!defined('ACCESS') || !ACCESS) {
|
||||
die('This file cannot be directly accessed.');
|
||||
}
|
||||
if (getSetting('website_mode') === 'personal' && getSetting('website_mode_personal_routing') === '/') {
|
||||
echo '<div class="margin-bottom-10"><span class="icon fas fa-info-circle color-fail"></span> '
|
||||
. _s('These settings do not have any effect as "%s" is overriding / (root).', [
|
||||
'%s' => '<b>' . _s('%s routing', _s('Single profile')) . '</b>',
|
||||
])
|
||||
. '</div>';
|
||||
}
|
||||
echo read_the_docs_settings('homepage', _s('Homepage')); ?>
|
||||
<div class="input-label">
|
||||
<label for="homepage_style"><?php _se('Style'); ?></label>
|
||||
<div class="c5 phablet-c1"><select type="text" name="homepage_style" id="homepage_style" class="text-input" data-combo="home-style-combo">
|
||||
<?php
|
||||
echo get_select_options_html([
|
||||
'landing' => _s('Landing page'),
|
||||
'split' => _s('Split landing + images'),
|
||||
'route_explore' => _s('Route %s', _s('explore')),
|
||||
'route_upload' => _s('Route %s', _s('upload')),
|
||||
], Settings::get('homepage_style')); ?>
|
||||
</select></div>
|
||||
<div class="input-below input-warning red-warning"><?php echo Handler::var('input_errors')['homepage_style'] ?? ''; ?></div>
|
||||
<div class="input-below"><?php _se('Select the homepage style.'); ?></div>
|
||||
</div>
|
||||
<div id="home-style-combo">
|
||||
<div data-combo-value="landing split" class="switch-combo<?php if (!in_array((Handler::var('safe_post') ? Handler::var('safe_post')['homepage_style'] : Settings::get('homepage_style')), ['split', 'landing'])) {
|
||||
echo ' soft-hidden';
|
||||
} ?>">
|
||||
<?php
|
||||
foreach (Settings::get('homepage_cover_images') ?? [] as $k => $v) {
|
||||
$cover_index = $k + 1;
|
||||
$cover_label = 'homepage_cover_image_' . $k;
|
||||
$coverName = _s('Cover image') . ' (' . $cover_index . ')'; ?>
|
||||
<div class="input-label">
|
||||
<label for="<?php echo $cover_label; ?>"><?php echo $coverName; ?></label>
|
||||
<div class="transparent-canvas dark margin-bottom-10 col-12-max col-12"><a href="<?php echo $v['url']; ?>" target="_blank"><img class="display-block" width="100%" src="<?php echo $v['url']; ?>"></a></div>
|
||||
<?php if (count(Settings::get('homepage_cover_images')) > 1) {
|
||||
?>
|
||||
<div class="margin-top-10 margin-bottom-10">
|
||||
<a class="btn btn-small default" data-confirm="<?php _se("Do you really want to delete?"); ?> <?php _se("This can't be undone."); ?>" href="<?php echo get_base_url('dashboard/settings/homepage/?action=delete-cover&cover=' . $cover_index . '&auth_token=' . Handler::getAuthToken()); ?>"><i class="fas fa-trash-alt margin-right-5"></i><?php _se('Delete %s', $coverName); ?></a>
|
||||
</div>
|
||||
<?php
|
||||
} ?>
|
||||
<div class="c5 phablet-c1">
|
||||
<input id="<?php echo $cover_label; ?>" name="<?php echo $cover_label; ?>" type="file" accept="image/*">
|
||||
</div>
|
||||
<div class="input-below input-warning red-warning"><?php echo Handler::var('input_errors')['homepage_cover_image_' . $k] ?? ''; ?></div>
|
||||
</div>
|
||||
<?php
|
||||
} ?>
|
||||
<div class="input-label">
|
||||
<label for="homepage_cover_image_add"><?php _se('Add new cover image'); ?></label>
|
||||
<div class="c5 phablet-c1">
|
||||
<input id="homepage_cover_image_add" name="homepage_cover_image_add" type="file" accept="image/*">
|
||||
</div>
|
||||
<div class="input-below input-warning red-warning"><?php echo Handler::var('input_errors')['homepage_cover_image_add'] ?? ''; ?></div>
|
||||
</div>
|
||||
<hr class="line-separator">
|
||||
<div class="input-label">
|
||||
<label for="homepage_title_html"><?php _se('Title'); ?></label>
|
||||
<div class="c12 phablet-c1"><textarea type="text" name="homepage_title_html" id="homepage_title_html" class="text-input r2 resize-vertical" placeholder="<?php echo safe_html(_s('This will be added inside the homepage %s tag. Leave it blank to use the default contents.', '<h1>')); ?>"><?php echo Settings::get('homepage_title_html'); ?></textarea></div>
|
||||
</div>
|
||||
<div class="input-label">
|
||||
<label for="homepage_paragraph_html"><?php _se('Paragraph'); ?></label>
|
||||
<div class="c12 phablet-c1"><textarea type="text" name="homepage_paragraph_html" id="homepage_paragraph_html" class="text-input r2 resize-vertical" placeholder="<?php echo safe_html(_s('This will be added inside the homepage %s tag. Leave it blank to use the default contents.', '<p>')); ?>"><?php echo Settings::get('homepage_paragraph_html'); ?></textarea></div>
|
||||
</div>
|
||||
<hr class="line-separator">
|
||||
<div class="input-label">
|
||||
<label for="homepage_cta_color"><?php _se('Call to action button color'); ?></label>
|
||||
<div class="c5 phablet-c1"><select type="text" name="homepage_cta_color" id="homepage_cta_color" class="text-input">
|
||||
<?php
|
||||
echo get_select_options_html(
|
||||
[
|
||||
'accent' => 'Accent',
|
||||
'blue' => _s('Blue'),
|
||||
'green' => _s('Green'),
|
||||
'orange' => _s('Orange'),
|
||||
'red' => _s('Red'),
|
||||
'grey' => _s('Grey'),
|
||||
'black' => _s('Black'),
|
||||
'white' => _s('White'),
|
||||
'default' => _s('Default'),
|
||||
],
|
||||
Handler::var('safe_post')
|
||||
? Handler::var('safe_post')['homepage_cta_color']
|
||||
: Settings::get('homepage_cta_color')
|
||||
); ?>
|
||||
</select></div>
|
||||
<div class="input-below input-warning red-warning clear-both"><?php echo Handler::var('input_errors')['homepage_cta_color'] ?? ''; ?></div>
|
||||
<div class="input-below"><?php _se('Color of the homepage call to action button.'); ?></div>
|
||||
</div>
|
||||
<div class="input-label">
|
||||
<label for="homepage_cta_outline"><?php _se('Call to action outline style button'); ?></label>
|
||||
<div class="c5 phablet-c1"><select type="text" name="homepage_cta_outline" id="homepage_cta_outline" class="text-input">
|
||||
<?php
|
||||
echo get_select_options_html([1 => _s('Enabled'), 0 => _s('Disabled')], Settings::get('homepage_cta_outline')); ?>
|
||||
</select></div>
|
||||
<div class="input-below input-warning red-warning clear-both"><?php echo Handler::var('input_errors')['homepage_cta_outline'] ?? ''; ?></div>
|
||||
<div class="input-below"><?php _se('Enable this to use outline style for the homepage call to action button.'); ?></div>
|
||||
</div>
|
||||
<div class="input-label">
|
||||
<label for="homepage_cta_fn"><?php _se('Call to action functionality'); ?></label>
|
||||
<div class="c5 phablet-c1"><select type="text" name="homepage_cta_fn" id="homepage_cta_fn" class="text-input" data-combo="cta-fn-combo">
|
||||
<?php
|
||||
echo get_select_options_html([
|
||||
'cta-upload' => _s('Trigger uploader'),
|
||||
'cta-link' => _s('Open URL'),
|
||||
], Settings::get('homepage_cta_fn')); ?>
|
||||
</select></div>
|
||||
<div class="input-warning red-warning"><?php echo Handler::var('input_errors')['homepage_cta_fn'] ?? ''; ?></div>
|
||||
</div>
|
||||
<div id="cta-fn-combo">
|
||||
<div data-combo-value="cta-link" class="switch-combo<?php if ((Handler::var('safe_post') ? Handler::var('safe_post')['homepage_cta_fn'] : Settings::get('homepage_cta_fn')) !== 'cta-link') {
|
||||
echo ' soft-hidden';
|
||||
} ?>">
|
||||
<div class="input-label">
|
||||
<label for="homepage_cta_fn_extra"><?php _se('Call to action URL'); ?></label>
|
||||
<div class="c9 phablet-c1"><input type="text" name="homepage_cta_fn_extra" id="homepage_cta_fn_extra" class="text-input" value="<?php echo Settings::get('homepage_cta_fn_extra'); ?>" placeholder="<?php _se('Enter an absolute or relative URL'); ?>" <?php echo ((Handler::var('safe_post') ? Handler::var('safe_post')['homepage_cta_fn'] : Settings::get('homepage_cta_fn')) !== 'cta-link') ? 'data-required' : 'required'; ?>></div>
|
||||
<div class="input-below input-warning red-warning"><?php echo Handler::var('input_errors')['homepage_cta_fn_extra'] ?? ''; ?></div>
|
||||
<div class="input-below"><?php _se('A relative URL like %r will be mapped to %l', ['%r' => 'page/welcome', '%l' => get_base_url('page/welcome')]); ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-label">
|
||||
<label for="homepage_cta_html"><?php _se('Call to action HTML'); ?></label>
|
||||
<div class="c12 phablet-c1"><textarea type="text" name="homepage_cta_html" id="homepage_cta_html" class="text-input r2 resize-vertical" placeholder="<?php echo safe_html(_s('This will be added inside the call to action <a> tag. Leave it blank to use the default contents.')); ?>"><?php echo Settings::get('homepage_cta_html'); ?></textarea></div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-combo-value="split" class="switch-combo<?php if ((Handler::var('safe_post') ? Handler::var('safe_post')['homepage_style'] : Settings::get('homepage_style')) !== 'split') {
|
||||
echo ' soft-hidden';
|
||||
} ?>">
|
||||
<div class="input-label">
|
||||
<label for="homepage_uids"><?php _se('User IDs'); ?></label>
|
||||
<div class="c4"><input type="text" name="homepage_uids" id="homepage_uids" class="text-input" value="<?php echo Settings::get('homepage_uids'); ?>" placeholder="<?php _se('Empty'); ?>" rel="tooltip" title="<?php _se('Your user id is: %s', Login::getUser()['id']); ?>" data-tipTip="right"></div>
|
||||
<div class="input-below input-warning red-warning"><?php echo Handler::var('input_errors')['homepage_uids'] ?? ''; ?></div>
|
||||
<div class="input-below"><?php _se('Comma-separated list of target user IDs (integers) to show most recent images on homepage. Leave it empty to display trending images.'); ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user