Automatic push 4.0.11

This commit is contained in:
chevereto
2024-02-21 18:55:20 +00:00
parent 6bc0fe6066
commit 0a1ab32eeb
27 changed files with 769 additions and 108 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

2
.gitignore vendored
View File

@@ -1,6 +1,8 @@
.DS_Store
/.env
/.idea
/app/.upgrading
/app/CHEVERETO_LICENSE_KEY
/app/vendor
/app/build
/app/.phpunit.cache

View File

@@ -1,17 +0,0 @@
Chevereto 4.0.10 (2024-02-07)
- ✅ Added version CLI command
- ✅ Improved error display on CLI install
- 🐞 Fixed bug in AWS open_basedir restriction
- 🐞 Fixed bug in banners settings page
- 🐞 Fixed bug in comments implementation
- 🐞 Fixed bug in file size handling
- 🐞 Fixed bug in notifications missing links
- 🐞 Fixed bug in NSFW display
- 🐞 Fixed bug in Spanish translation
- 🐞 Fixed bug in update database query
- 🐞 Fixed bug in upload file title
- 🐞 Fixed bug in user interface for iOS
- 💅 Disabled listing viewer by default
- 💅 Improved arrow style navigation
- 💅 Improved notifications display (default avatar)

8
.package/4.0.11.txt Normal file
View File

@@ -0,0 +1,8 @@
Chevereto 4.0.11 (2024-02-21)
- ✅ Added one-click upgrade system
- ✅ Added license key configuration
- ✅ Added /dashboard/?license
- ✅ Added /dashboard/?installed
- ✅ Added /dashboard/?upgrade
- 💅 Improved software version at dashboard

View File

@@ -1,12 +1,14 @@
# Chevereto: Ultimate image sharing software 🦄
# Chevereto: Ultimate image sharing software
> 🔔 [Subscribe](https://chv.to/newsletter) to don't miss any update regarding Chevereto.
<p align="center">
<img alt="Chevereto" src="chevereto.svg" width="50%">
<a href="https://chevereto.com"><img alt="Chevereto" src="chevereto.svg" width="80%"></a>
</p>
![CHUISS](.github/banner/chevereto-ultimate.png)
[![CHUISS](.github/banner/chevereto-ultimate-remix.png)](https://chevereto.com)
[![Community](https://img.shields.io/badge/chv.to-community-blue?style=flat-square)](https://chv.to/community)
[![Community](https://img.shields.io/badge/chv.to-community-blue?style=flat-square)](https://chv.to/community)
[![AGPL-3.0-only](https://img.shields.io/github/license/chevereto/chevereto?style=flat-square)](LICENSE)

10
app/composer.lock generated
View File

@@ -5378,16 +5378,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.10.57",
"version": "1.10.59",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "1627b1d03446904aaa77593f370c5201d2ecc34e"
"reference": "e607609388d3a6d418a50a49f7940e8086798281"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/1627b1d03446904aaa77593f370c5201d2ecc34e",
"reference": "1627b1d03446904aaa77593f370c5201d2ecc34e",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/e607609388d3a6d418a50a49f7940e8086798281",
"reference": "e607609388d3a6d418a50a49f7940e8086798281",
"shasum": ""
},
"require": {
@@ -5436,7 +5436,7 @@
"type": "tidelift"
}
],
"time": "2024-01-24T11:51:34+00:00"
"time": "2024-02-20T13:59:13+00:00"
},
{
"name": "phpunit/php-code-coverage",

View File

@@ -87,4 +87,5 @@ return [
'CHEVERETO_SERVICING' => 'server',
'CHEVERETO_SESSION_SAVE_HANDLER' => 'files',
'CHEVERETO_SESSION_SAVE_PATH' => '/tmp',
'CHEVERETO_EDITION' => 'pro',
];

View File

@@ -43,6 +43,7 @@ if ($opts === []) {
}
define('ACCESS', $access);
require_once __DIR__ . '/../load/php-boot.php';
require_once __DIR__ . '/../load/loader.php';
require_once loaderHandler(
$_COOKIE,
$_ENV,

View File

@@ -13,7 +13,17 @@ use function Chevereto\Legacy\loaderHandler;
define('ACCESS', 'web');
require_once __DIR__ . '/../load/php-boot.php';
$appDir = __DIR__ . '/../..';
$loadDir = __DIR__ . '/../load';
require_once $loadDir . '/php-boot.php';
$uri = $_SERVER['REQUEST_URI'] ?? '';
$parseUri = parse_url($uri);
if (in_array($parseUri['path'], ['/upgrading', '/upgrading/'])
&& file_exists($appDir . '/.upgrading/upgrading.lock')) {
require $appDir . '/upgrading.php';
exit;
}
require_once $loadDir . '/loader.php';
require_once loaderHandler(
$_COOKIE,
$_ENV,

View File

@@ -594,6 +594,7 @@ $settings_updates = [
'4.0.10' => [
'listing_viewer' => 0,
],
'4.0.11' => null,
];
$cheveretoFreeMap = [
'1.0.0' => '3.8.3',

View File

@@ -9,5 +9,5 @@
* file that was distributed with this source code.
*/
const APP_VERSION = '4.0.10';
const APP_VERSION = '4.0.11';
const APP_VERSION_AKA = 'macanudo';

View File

@@ -33,7 +33,6 @@ use function Chevereto\Legacy\G\is_url;
use function Chevereto\Legacy\G\json_error;
use function Chevereto\Legacy\G\json_output;
use function Chevereto\Legacy\G\random_string;
use function Chevereto\Legacy\G\timing_safe_compare;
use function Chevereto\Legacy\getSetting;
use function Chevereto\Vars\env;
use function Chevereto\Vars\files;
@@ -66,7 +65,7 @@ return function (Handler $handler) {
if ((getSetting('api_v1_key') ?? '') == '') {
throw new Exception("API V1 public key can't be null. Go to your dashboard and set the Guest API key.", 0);
}
if (!timing_safe_compare(getSetting('api_v1_key'), $key)) {
if (!hash_equals(getSetting('api_v1_key'), $key)) {
throw new Exception("Invalid guest API key.", 100);
}
} else {
@@ -124,7 +123,7 @@ return function (Handler $handler) {
throw new Exception('Upload using base64 source must be done using POST method.', 130);
}
$source = trim(preg_replace('/\s+/', '', $source));
if (!timing_safe_compare(base64_encode(base64_decode($source)), $source)) {
if (!hash_equals(base64_encode(base64_decode($source)), $source)) {
throw new Exception('Invalid base64 string.', 120);
}
$api_temp_file = tempnam(sys_get_temp_dir(), 'chvtemp');

View File

@@ -9,6 +9,7 @@
* file that was distributed with this source code.
*/
use function Chevere\String\randomString;
use Chevereto\Config\Config;
use function Chevereto\Legacy\badgePaid;
use Chevereto\Legacy\Classes\Akismet;
@@ -45,11 +46,11 @@ use function Chevereto\Legacy\G\is_valid_url;
use function Chevereto\Legacy\G\redirect;
use function Chevereto\Legacy\G\sanitize_relative_path;
use function Chevereto\Legacy\G\starts_with;
use function Chevereto\Legacy\G\timing_safe_compare;
use function Chevereto\Legacy\G\unlinkIfExists;
use function Chevereto\Legacy\get_available_languages;
use function Chevereto\Legacy\get_chv_default_setting;
use function Chevereto\Legacy\get_share_links;
use function Chevereto\Legacy\getLicenseKey;
use function Chevereto\Legacy\getSetting;
use function Chevereto\Legacy\getSettings;
use function Chevereto\Legacy\getSystemNotices;
@@ -73,6 +74,34 @@ return function (Handler $handler) {
return;
}
if (env()['CHEVERETO_CONTEXT'] !== 'saas'
&& ($handler->request()[0] ?? null) === 'upgrade'
) {
if (!$handler::checkAuthToken(request()['auth_token'] ?? '')) {
$handler->issueError(403);
return;
}
$upgradingDir = PATH_APP . '.upgrading/';
if (!is_dir($upgradingDir)) {
mkdir($upgradingDir);
}
$upgradingLock = $upgradingDir . 'upgrading.lock';
unlinkIfExists($upgradingLock);
$token = randomString(128);
touch($upgradingLock);
file_put_contents($upgradingLock, $token);
$params = [
'action' => 'download',
'token' => $token,
'return' => 'dashboard/?installed',
];
$query = http_build_query($params);
redirect(
get_base_url('upgrading/?' . $query),
302,
);
}
$doing = $handler->request()[0] ?? 'stats';
$logged_user = Login::getUser();
if ($logged_user === []) {
@@ -91,7 +120,7 @@ return function (Handler $handler) {
}
$route_prefix = 'dashboard';
$routes = [
'stats' => _s('Stats'),
'stats' => _s('Home'),
'images' => _s('Images'),
'albums' => _n('Album', 'Albums', 20),
'users' => _n('User', 'Users', 20),
@@ -118,7 +147,7 @@ return function (Handler $handler) {
}
}
$icons = [
'stats' => 'fas fa-chart-bar',
'stats' => 'fas fa-home',
'images' => 'fas fa-image',
'albums' => 'fas fa-images',
'users' => 'fas fa-users',
@@ -212,6 +241,7 @@ return function (Handler $handler) {
if ($doing == '') {
$doing = $default_route;
}
$route_menu = [];
foreach ($routes as $route => $label) {
$aux = str_replace('_', '-', $route);
$handler::setCond($route_prefix . '_' . $aux, $doing == $aux);
@@ -285,24 +315,34 @@ return function (Handler $handler) {
'files' => get_app_version(),
'db' => getSetting('chevereto_version_installed') ?? ''
];
$links = [];
$linksButtons = '';
$links = [
[
'label' => _s('Documentation'),
'icon' => 'fas fa-book',
'href' => $handler::var('docsBaseUrl')
],
[
'label' => _s('%s docs', _s('Admin')),
'icon' => 'fas fa-user-tie',
'href' => 'https://v4-admin.chevereto.com'
],
[
'label' => _s('%s docs', _n('User', 'Users', 1)),
'icon' => 'fas fa-user',
'href' => 'https://v4-user.chevereto.com'
],
];
$licenseKey = getLicenseKey();
$handler::setVar('licenseKey', $licenseKey);
if (env()['CHEVERETO_CONTEXT'] !== 'saas') {
$upgradeClass = 'hidden';
$upgradeLink = get_base_url('dashboard/upgrade/?auth_token=' . $handler::getAuthToken());
if ($licenseKey !== '' && env()['CHEVERETO_EDITION'] === 'free') {
$upgradeClass = '';
}
$upgradeTitle = '<i class=\"fa-solid fa-boxes-packing\"></i> ' . _s('Upgrade now');
$links = array_merge($links, [
[
'label' => _s('Upgrade now'),
'icon' => 'fas fa-download',
'class' => 'green ' . $upgradeClass,
'attr' => 'data-action="upgrade" data-options=\'{"title":"' . $upgradeTitle . '"}\' href="' . $upgradeLink . '" data-confirm="' . _s("The latest release will be downloaded and extracted in the filesystem.") . '"',
],
]);
$links = array_merge($links, [
[
'label' => _s("License key"),
'icon' => 'fas fa-key',
'class' => 'accent outline',
'attr' => 'data-action="license" data-modal="edit" data-target="modal-license-key"'
],
]);
}
if (env()['CHEVERETO_CONTEXT'] === 'saas') {
$links = array_merge($links, [
[
@@ -311,33 +351,12 @@ return function (Handler $handler) {
'href' => 'https://chevereto.cloud/support'
],
]);
} else {
$links = array_merge($links, [
[
'label' => _s("Releases"),
'icon' => 'fas fa-rocket',
'href' => 'https://releases.chevereto.com'
],
[
'label' => _s('Support'),
'icon' => 'fas fa-medkit',
'href' => 'https://chevereto.com/support'
],
[
'label' => _s('Community'),
'icon' => 'fas fa-users',
'href' => 'https://chevereto.com/community'
],
[
'label' => _s("License"),
'icon' => 'fas fa-key',
'href' => 'https://chevereto.com/panel/license'
]
]);
}
foreach ($links as $link) {
$linksButtons .= strtr('<a href="%href%" target="_blank" class="btn btn-small default margin-right-5 margin-top-5"><span class="btn-icon fa-btn-icon %icon%"></span> %label%</a>', [
'%href%' => $link['href'],
$attr = $link['attr'] ?? 'href="%href%" target="_blank"';
$class = $link['class'] ?? 'default';
$linksButtons .= strtr('<a ' . $attr . ' class="btn btn-small ' . $class . ' margin-right-5"><span class="btn-icon fa-btn-icon %icon%"></span><span class="btn-text">%label%</span></a>', [
'%href%' => $link['href'] ?? '',
'%icon%' => $link['icon'],
'%label%' => $link['label'],
]);
@@ -351,7 +370,7 @@ return function (Handler $handler) {
if (version_compare($chv_version['files'], $chv_version['db'], '>')) {
$install_update_button = $chv_version['db'] . ' DB <span class="fas fa-database"></span> <a href="' . get_base_url('update') . '">' . _s('install update') . '</a>';
}
$version_check .= '<a data-action="check-for-updates" class="btn btn-small accent margin-right-5 margin-top-5"><span class="fas fa-arrow-alt-circle-up"></span> ' . _s("Check updates") . '</a>';
$version_check .= '<a data-action="check-for-updates" class="btn btn-small accent margin-right-5 margin-top-5"><span class="fas fa-circle-up"></span> ' . _s("Check upgrades") . '</a>';
if (datetime_diff($cron_last_ran, null, 'm') > 5) {
$cronRemark .= ' — <span class="color-fail"><span class="fas fa-exclamation-triangle"></span> ' . _s('not running') . '</span>';
}
@@ -365,11 +384,11 @@ return function (Handler $handler) {
$chv_version_minor = $chv_versioning[0] . '.' . $chv_versioning[1];
$system_values = [
'chv_version' => [
'label' => '<div class="text-align-center"><a href="https://chevereto.com" target="_blank"><img src="' . absolute_to_url(PATH_PUBLIC_CONTENT_LEGACY_SYSTEM . 'chevereto-blue.svg') . '" alt="" width="50%"></a></div>',
'label' => '<div class="text-align-center"><a href="https://chevereto.com" target="_blank"><img src="' . absolute_to_url(PATH_PUBLIC_CONTENT_LEGACY_SYSTEM . 'chevereto-blue.svg') . '" alt="" width="80%"></a></div>',
'content' => '<div class="phone-text-align-center">'
. '<h3 class="margin-bottom-10"><a target="_blank" href="https://releases.chevereto.com/' . $chv_version_major . '/' . $chv_version_minor . '/' . $chv_version['files'] . '">'
. '<h3 class="margin-bottom-10 version-display"><a target="_blank" href="https://releases.chevereto.com/' . $chv_version_major . '/' . $chv_version_minor . '/' . $chv_version['files'] . '">'
. $chv_version['files']
. '<span class="btn-icon fas fas fa-code-branch"></span></a> <span class="software-version-name" title="' . APP_VERSION_AKA . '">' . APP_VERSION_AKA . '</span> </h3>'
. '<span class="btn-icon fas fas fa-code-branch margin-left-5"></span></a><span class="software-version-name margin-left-10" title="' . APP_VERSION_AKA . '">' . APP_VERSION_AKA . '</span> </h3>'
. $install_update_button
. '<div class="margin-bottom-20">' . $version_check . $linksButtons . '</div>
</div>'
@@ -390,11 +409,48 @@ return function (Handler $handler) {
'content' => '<i class="fas fa-network-wired"></i> ' . get_client_ip() . ' <a data-modal="simple" data-target="modal-connecting-ip"><i class="fas fa-question-circle margin-right-5"></i>' . _s('Not your IP?') . '</a>'
],
];
$cheveretoLinks = [
[
'label' => _s('Docs'),
'icon' => 'fas fa-book',
'href' => $handler::var('docsBaseUrl')
],
[
'label' => _s("Releases"),
'icon' => 'fas fa-rocket',
'href' => 'https://releases.chevereto.com'
],
[
'label' => _s('Support'),
'icon' => 'fas fa-medkit',
'href' => 'https://chevereto.com/support'
],
[
'label' => _s('Community'),
'icon' => 'fas fa-users',
'href' => 'https://chevereto.com/community'
],
];
$cheveretoLinksButtons = '';
foreach ($cheveretoLinks as $link) {
$attr = $link['attr'] ?? 'href="%href%" target="_blank"';
$cheveretoLinksButtons .= strtr('<a ' . $attr . ' class="btn default btn-small margin-right-5"><span class="btn-icon fa-btn-icon %icon%"></span><span class="btn-text">%label%</span></a>', [
'%href%' => $link['href'] ?? '',
'%icon%' => $link['icon'],
'%label%' => $link['label'],
]);
}
if (env()['CHEVERETO_CONTEXT'] !== 'saas') {
$mysqlVersion = $db->getAttr(PDO::ATTR_SERVER_VERSION);
$db->closeCursor();
$mysqlServerInfo = $db->getAttr(PDO::ATTR_SERVER_INFO);
$system_values_more = [
'links' => [
'label' => _s('Links'),
'content' => $cheveretoLinksButtons,
],
'cli' => [
'label' => 'CLI',
'content' => '<i class="fas fa-terminal"></i> <span data-click="select-all">' . PATH_PUBLIC . 'app/bin/legacy</span>',
@@ -1412,7 +1468,7 @@ return function (Handler $handler) {
if (isset($page['id']) && $page['id'] == $v['page_id']) {
continue; // Skip on same thing
}
if (timing_safe_compare($v[$kk], $POST[$kk])) {
if (hash_equals($v[$kk], $POST[$kk])) {
$input_errors[$kk] = sprintf($vv, $v['page_id']);
}
}
@@ -1427,7 +1483,7 @@ return function (Handler $handler) {
try {
Page::writePage(['file_path' => $POST['page_file_path'], 'code' => $page_write_code]);
if ($handler->request()[2] == 'edit' && isset($page['file_path']) && !timing_safe_compare($page['file_path'], $POST['page_file_path'])) {
if ($handler->request()[2] == 'edit' && isset($page['file_path']) && !hash_equals($page['file_path'], $POST['page_file_path'])) {
unlinkIfExists(Page::getPath($page['file_path']));
}
if (isset($page['id'])) {
@@ -1445,7 +1501,7 @@ return function (Handler $handler) {
foreach ($page_fields as $v) {
$postPage = $POST['page_' . $v];
if ($handler->request()[2] == 'edit') {
if (timing_safe_compare($page[$v] ?? '', $postPage ?? '')) {
if (hash_equals($page[$v] ?? '', $postPage ?? '')) {
continue;
} // Skip not updated values
}

View File

@@ -54,6 +54,7 @@ use function Chevereto\Legacy\isDebug;
use function Chevereto\Legacy\isShowEmbedContent;
use function Chevereto\Legacy\send_mail;
use function Chevereto\Legacy\time_elapsed_string;
use function Chevereto\Vars\env;
use function Chevereto\Vars\files;
use function Chevereto\Vars\post;
use function Chevereto\Vars\request;
@@ -1536,6 +1537,24 @@ return function (Handler $handler) {
User::update($user_id, ['status' => $doing == 'user_ban' ? 'banned' : 'valid']);
$json_array['status_code'] = 200;
break;
case 'set-license-key':
if (env()['CHEVERETO_CONTEXT'] === 'saas') {
throw new Exception('Not found', 404);
}
if (!Login::isAdmin()) {
throw new Exception(_s('Request denied'), 403);
}
$licenseKey = $POST['key'] ?? '';
$licenseFile = PATH_APP . 'CHEVERETO_LICENSE_KEY';
touch($licenseFile);
if (file_put_contents($licenseFile, $licenseKey)) {
$json_array['status_code'] = 200;
$json_array['success'] = ['message' => _s('License key updated'), 'code' => 200];
} else {
throw new Exception('Error updating license key', 500);
}
break;
case 'deny':
throw new Exception(_s('Request denied'), 403);

View File

@@ -31,7 +31,6 @@ use function Chevereto\Legacy\G\is_url_web;
use function Chevereto\Legacy\G\nullify_string;
use function Chevereto\Legacy\G\redirect;
use function Chevereto\Legacy\G\safe_html;
use function Chevereto\Legacy\G\timing_safe_compare;
use function Chevereto\Legacy\generate_hashed_token;
use function Chevereto\Legacy\get_available_languages;
use function Chevereto\Legacy\getIpButtonsArray;
@@ -251,11 +250,11 @@ return function (Handler $handler) {
continue;
}
}
if (timing_safe_compare($row['user_username'], $POST['username']) and $user['username'] !== $row['user_username']) {
if (hash_equals($row['user_username'], $POST['username']) and $user['username'] !== $row['user_username']) {
$input_errors['username'] = 'Username already being used';
}
if (
!empty($POST['email']) && timing_safe_compare($row['user_email'], $POST['email']) &&
!empty($POST['email']) && hash_equals($row['user_email'], $POST['email']) &&
$user['email'] !== $row['user_email']
) {
$input_errors['email'] = _s('Email already being used');
@@ -266,7 +265,7 @@ return function (Handler $handler) {
}
}
}
if (!$is_error && $is_email_required && !empty($POST['email']) && !timing_safe_compare($user['email'] ?? '', $POST['email'])) {
if (!$is_error && $is_email_required && !empty($POST['email']) && !hash_equals($user['email'] ?? '', $POST['email'])) {
Confirmation::delete(['type' => 'account-change-email', 'user_id' => $user['id']]);
$hashed_token = generate_hashed_token((int) $user['id']);
Confirmation::insert([

View File

@@ -23,7 +23,6 @@ use function Chevereto\Legacy\G\get_public_url;
use Chevereto\Legacy\G\Handler;
use function Chevereto\Legacy\G\redirect;
use function Chevereto\Legacy\G\safe_html;
use function Chevereto\Legacy\G\timing_safe_compare;
use function Chevereto\Legacy\generate_hashed_token;
use function Chevereto\Legacy\get_email_body_str;
use function Chevereto\Legacy\getSetting;
@@ -136,10 +135,10 @@ return function (Handler $handler) {
continue;
}
}
if (timing_safe_compare($row['user_username'], $POST['username'])) {
if (hash_equals($row['user_username'], $POST['username'])) {
$input_errors['username'] = 'Username already being used';
}
if (timing_safe_compare($row['user_email'], $POST['email'])) {
if (hash_equals($row['user_email'], $POST['email'])) {
$input_errors['email'] = _s('Email already being used');
}
if (!$show_resend_activation) {

View File

@@ -424,7 +424,7 @@ class Handler
return false;
}
return timing_safe_compare(session()['G_auth_token'], $token);
return hash_equals(session()['G_auth_token'], $token);
}
public static function setVar(string $var, mixed $value): void

View File

@@ -1032,6 +1032,7 @@ function loaderHandler(
'CHEVERETO_ENABLE_UPLOAD_WATERMARK' => '0',
'CHEVERETO_ENABLE_USERS' => '0',
'CHEVERETO_MAX_USERS' => '1',
'CHEVERETO_EDITION' => 'free',
));
new EnvVar($envVar);
new ServerVar(array_merge($envDefault, $env, $_server));
@@ -1314,3 +1315,14 @@ function adjustBrightness(string $hexCode, float $adjustPercent)
return '#' . implode($hexCode);
}
function getLicenseKey(): string
{
$licenseKey = env()['CHEVERETO_LICENSE_KEY'] ?? '';
$licenseFile = PATH_APP . 'CHEVERETO_LICENSE_KEY';
if ($licenseKey === '' && file_exists($licenseFile)) {
$licenseKey = file_get_contents($licenseFile);
}
return $licenseKey;
}

451
app/upgrading.php Normal file
View File

@@ -0,0 +1,451 @@
<?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.
*/
/*
Download (auto license):
php app/upgrading.php
Download (with license):
CHEVERETO_LICENSE_KEY=your_license_key php app/upgrading.php
* .upgrading/upgrading.lock
This setting affects non CLI (HTTP calls only).
It exists when the upgrade has been authorized at dashboard.
It contains the token for upgrade process, must be checked against request.
* .upgrading/downloading.lock
It exists when the upgrade is downloading the new version.
* .upgrading/extracting.lock
It exists when the upgrade is extracting the new version.
*/
namespace Chevereto;
use Exception;
use RuntimeException;
use stdClass;
use Throwable;
use ZipArchive;
require_once __DIR__ . '/legacy/load/php-boot.php';
const ZIP_BALL = 'https://chevereto.com/api/download/%tag%';
const LOGGER = __DIR__ . '/.upgrading/process.log';
if (!file_exists(LOGGER)) {
touch(LOGGER);
}
ob_start('ob_gzhandler');
ob_implicit_flush(true);
$rootDir = __DIR__ . '/..';
$workingDir = __DIR__ . '/.upgrading';
if (is_file($workingDir)) {
unlink($workingDir);
}
$runtimeTable = [
'log_errors' => ini_set('log_errors', true),
'display_errors' => ini_set('display_errors', true),
'error_log' => ini_set('error_log', $workingDir . '/error.log'),
'ignore_user_abort' => ignore_user_abort(true),
'time_limit' => @set_time_limit(0),
'ini_set' => ini_set('default_charset', 'utf-8'),
'setlocale' => setlocale(LC_ALL, 'en_US.UTF8'),
'output_buffering' => ini_set('output_buffering', 'off'),
'zlib.output_compression' => ini_set('zlib.output_compression', false),
];
$logProcess = $workingDir . '/process.log';
$lockUpgrading = $workingDir . '/upgrading.lock';
$lockDownloading = $workingDir . '/downloading.lock';
$lockExtracting = $workingDir . '/extracting.lock';
$upgradingKey = $rootDir . '/app/CHEVERETO_LICENSE_KEY';
if (PHP_SAPI !== 'cli') {
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
echo <<<HTML
<html><head><style>body {padding: 0.5em;}</style><script>
function goToUrl(url) {
window.location.href = url;
}
</script></head><body><pre>
HTML;
}
if (!is_dir($workingDir)) {
mkdir($workingDir, 0755, true);
}
if (!is_writable($workingDir)) {
abort('[!] Working dir is not writable', 500);
}
$envFile = __DIR__ . '/env.php';
$env = [];
if (file_exists($envFile)) {
$env = require $envFile;
}
$env = array_merge($_ENV, $_SERVER, $env);
if (!class_exists('ZipArchive')) {
abort('[!] ZipArchive is not available');
}
$licenseKey = $env['CHEVERETO_LICENSE_KEY'] ?? '';
if ($licenseKey === '' && file_exists($upgradingKey)) {
$licenseKey = file_get_contents($upgradingKey);
}
$return = $_GET['return'] ?? '';
$parseUri = parse_url($_SERVER['REQUEST_URI'] ?? '');
$query = $parseUri['query'] ?? '';
$pathUrl = $parseUri['path'] ?? '';
$rootUrl = rtrim(dirname($pathUrl), '/') . '/';
$actions = ['download', 'extract'];
$filePath = $workingDir . '/' . 'chevereto.zip';
if (PHP_SAPI === 'cli') {
echo <<<LOGO
__ __
____/ / ___ _ _____ _______ / /____
/ __/ _ \/ -_) |/ / -_) __/ -_) __/ _ \
\__/_//_/\__/|___/\__/_/ \__/\__/\___/
LOGO;
$singleStep = true;
$clear = getopt('c::') ?? null;
if ($clear) {
unlinkIfExists($lockUpgrading);
unlinkIfExists($lockDownloading);
unlinkIfExists($lockExtracting);
logger('Locks cleared');
die(0);
}
} else {
$singleStep = false;
$action = $_GET['action'] ?? '';
$token = $_GET['token'] ?? '';
if (!file_exists($lockUpgrading)) {
abort('[!] Upgrade is not expected', 403);
}
$upgradeToken = file_get_contents($lockUpgrading);
if (!hash_equals($upgradeToken, $token)) {
abort('[!] Invalid token', 403);
}
if (($env['CHEVERETO_CONTEXT'] ?? null) === 'saas') {
abort('[!] Upgrade is not needed on SaaS context', 403);
}
if (!in_array($action, $actions, true)) {
abort('[!] Provide action=download or action=extract', 400);
}
}
$upgradeToken ??= time();
if ($singleStep || $action === 'download') {
if (file_exists($lockDownloading)) {
abort('[!] Downloading is already in progress', 400);
}
logger('Lock downloading process');
file_put_contents($lockDownloading, $upgradeToken);
$params['tag'] = '4';
$params['license'] = $licenseKey;
if ($params['license'] === '') {
logger('Using free version [no CHEVERETO_LICENSE_KEY provided]');
} else {
logger('Using licensed version [CHEVERETO_LICENSE_KEY provided]');
}
logger(sprintf('About to download Chevereto %s', $params['tag']));
try {
$response = downloadAction($workingDir, $params);
} catch (Throwable $e) {
logger('Unlock downloading process');
unlink($lockDownloading);
abort($e->getMessage(), 400);
}
logger($response->message);
logger('Unlock downloading process');
unlink($lockDownloading);
$query = str_replace('action=download', 'action=extract', $query);
if (PHP_SAPI !== 'cli') {
$continueUri = $pathUrl . '?' . $query;
logger('Continue extraction in 3s at... ' . $continueUri);
sleep(3);
}
}
if ($singleStep || $action === 'extract') {
if (PHP_SAPI !== 'cli') {
echo file_get_contents(LOGGER);
}
if (file_exists($lockExtracting)) {
abort('[!] Extracting is already in progress', 400);
}
if (!file_exists($filePath)) {
abort('[!] Package not downloaded', 400);
}
logger('Lock extracting process');
file_put_contents($lockExtracting, $upgradeToken);
try {
$response = extractAction($rootDir, $filePath);
} catch (Throwable $e) {
logger('Unlock extracting process');
unlink($lockExtracting);
abort($e->getMessage(), $e->getCode());
}
logger($response->message);
unlink($filePath);
logger('Unlock extracting process');
unlink($lockExtracting);
logger('Chevereto filesystem upgraded');
unlinkIfExists($lockUpgrading);
$safeResult = false;
$command = $rootDir . '/app/bin/legacy -C update';
if (passthruEnabled()) {
logger('Command passthru');
$safeResult = passthru($command);
}
if ($safeResult === false) {
logger('Continue with database update');
}
if (PHP_SAPI !== 'cli') {
$continueUri = $rootUrl . $return;
logger('Redirecting in 3s...');
sleep(3);
}
unlink(LOGGER);
}
if (PHP_SAPI !== 'cli') {
echo '</pre></body>';
if (isset($continueUri)) {
echo <<<HTML
<script>goToUrl("{$continueUri}")</script>
HTML;
}
echo '</html>';
}
function logger(string $message): void
{
$hour = gmdate('H:i:s');
$message = $hour . ' * ' . $message . PHP_EOL;
fwrite(fopen('php://output', 'r+'), $message);
fwrite(fopen(LOGGER, 'a+'), $message);
ob_flush();
}
function curl(string $url, array $curlOpts = []): object
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_AUTOREFERER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_FAILONERROR, 0);
curl_setopt($ch, CURLOPT_VERBOSE, 0);
curl_setopt($ch, CURLOPT_USERAGENT, 'Chevereto Upgrade');
$fp = false;
foreach ($curlOpts as $k => $v) {
if (CURLOPT_FILE == $k) {
$fp = $v;
}
curl_setopt($ch, $k, $v);
}
$file_get_contents = curl_exec($ch);
$transfer = curl_getinfo($ch);
if (curl_errno($ch)) {
$curl_error = curl_error($ch);
curl_close($ch);
throw new Exception('Curl error ' . $curl_error, 500);
}
curl_close($ch);
$return = new stdClass();
if (is_resource($fp)) {
rewind($fp);
$return->raw = stream_get_contents($fp);
} else {
$return->raw = $file_get_contents;
}
if (false !== strpos($transfer['content_type'], 'application/json')) {
$return->json = json_decode($return->raw);
if (is_resource($fp)) {
$meta_data = stream_get_meta_data($fp);
unlink($meta_data['uri']);
}
}
$code = $transfer['http_code'];
if (200 != $code && !isset($return->json)) {
$return->json = new stdClass();
$return->json->error = new stdClass();
$return->json->error->message = 'Error performing HTTP request';
$return->json->error->code = $code;
}
$return->transfer = $transfer;
return $return;
}
function getFormatBytes($bytes, int $round = 1): string
{
if (!is_numeric($bytes)) {
return (string) $bytes;
}
if ($bytes < 1000) {
return "$bytes B";
}
$units = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
foreach ($units as $k => $v) {
$multiplier = pow(1000, $k + 1);
$threshold = $multiplier * 1000;
if ($bytes < $threshold) {
$size = round($bytes / $multiplier, $round);
return "$size $v";
}
}
}
function getBytesToMb($bytes, int $round = 2): float
{
$mb = $bytes / pow(10, 6);
if ($round) {
$mb = round($mb, $round);
}
return $mb;
}
function downloadFile(string $url, array $params, string $filePath, bool $post = true): object
{
$fp = fopen($filePath, 'wb+');
if (!$fp) {
throw new Exception("Can't open temp file " . $filePath . ' (wb+)');
}
$ops = [
CURLOPT_FILE => $fp,
];
if ($params !== []) {
$ops[CURLOPT_POSTFIELDS] = http_build_query($params);
}
if ($post) {
$ops[CURLOPT_POST] = true;
}
$curl = curl($url, $ops);
fclose($fp);
return $curl;
}
function downloadAction(string $workingDir, array $params): Response
{
$fileBasename = 'chevereto.zip';
$filePath = $workingDir . '/' . $fileBasename;
unlinkIfExists($filePath);
$isPost = false;
$zipBall = ZIP_BALL;
$tag = $params['tag'] ?? 'latest';
$zipBall = str_replace('%tag%', $tag, $zipBall);
$isPost = true;
$curl = downloadFile($zipBall, $params, $filePath, $isPost);
if (isset($curl->json->error)) {
throw new RuntimeException($curl->json->error->message, $curl->json->status_code);
}
if ($curl->transfer['http_code'] !== 200) {
$error = '[HTTP ' . $curl->transfer['http_code'] . '] ' . $zipBall;
throw new RuntimeException($error, $curl->transfer['http_code']);
}
$fileSize = filesize($filePath);
return new Response(
strtr('Downloaded %f (%w @%s)', [
'%f' => $fileBasename,
'%w' => getFormatBytes($fileSize),
'%s' => getBytesToMb($curl->transfer['speed_download']) . 'MB/s.',
]),
[
'fileBasename' => $fileBasename,
'filePath' => $filePath,
]
);
}
function extractAction(string $pathTo, string $filePath): Response
{
if (!file_exists($pathTo) && !mkdir($pathTo)) {
throw new Exception(sprintf("Working path %s doesn't exists and can't be created", $pathTo), 500);
}
if (!is_readable($pathTo)) {
throw new Exception(sprintf('Working path %s is not readable', $pathTo), 500);
}
if (!is_readable($filePath)) {
throw new Exception(sprintf("Can't read %s", basename($filePath)), 500);
}
$zip = new ZipArchive();
$timeStart = microtime(true);
$zipOpen = $zip->open($filePath);
if ($zipOpen !== true) {
throw new Exception(strtr("Can't extract %f - %m (ZipArchive #%z)", [
'%f' => $filePath,
'%m' => 'ZipArchive ' . $zipOpen . ' error',
'%z' => $zipOpen,
]), 500);
}
$numFiles = $zip->numFiles - 1;
$extraction = $zip->extractTo($pathTo);
if (!$extraction) {
throw new Exception("Unable to extract to");
}
$zip->close();
$timeTaken = round(microtime(true) - $timeStart, 2); //
clearstatcache(true, $pathTo);
return new Response(
strtr('Extraction completed for %n files in %ss', ['%n' => $numFiles, '%s' => $timeTaken]),
[
'numFiles' => $numFiles,
'timeTaken' => $timeTaken,
]
);
}
function abort(string $message)
{
logger($message);
die(255);
}
function passthruEnabled(): bool
{
if (!function_exists('passthru')) {
return false;
}
$disabled = explode(',', ini_get('disable_functions'));
return !in_array('passthru', $disabled);
}
function unlinkIfExists(string $file): void
{
if (!file_exists($file)) {
return;
}
unlink($file);
}
class Response
{
public string $message;
public array $data;
public function __construct(string $message, array $data = [])
{
$this->message = $message;
$this->data = $data;
}
}

View File

@@ -2161,20 +2161,27 @@ $(function () {
) == -1
) {
PF.fn.modal.simple({
title: '<i class="fas fa-arrow-alt-circle-up"></i> ' + PF.fn._s("Update available v%s", data.current_version),
title: '<i class="fas fa-arrow-alt-circle-up"></i> ' + PF.fn._s("Chevereto v%s available", data.current_version),
message: "<p>" +
PF.fn._s("There is an update available for this system.") +
"</p>" +
PF.fn._s("There is a new Chevereto version available with the following release notes.") +
' ' +
PF.fn._s("Check %s for a complete changelog since you last upgrade.", '<a href="https://releases.chevereto.com/4.X/4.0/' + CHV.obj.system_info.version + '" target="_blank">' + CHV.obj.system_info.version + '<span class="btn-icon fas fas fa-code-branch"></span></a>') +
'</p>' +
'<textarea class="r4 resize-vertical">' +
data.release_notes +
data.release_notes.trim() +
"</textarea>" +
'<p>' +
PF.fn._s("Check the %s for alternative update methods.", '<a href="https://chv.to/v4update" target="_blank">' + PF.fn._s('documentation') + '</a>') +
'</p>' +
'<div class="btn-container margin-bottom-0">' +
'<a href="https://chv.to/v4update" target="_blank" class="btn btn-input accent">' +
'<span class="btn-icon fas fa-external-link-alt user-select-none"></span>' +
'<a href="' + PF.obj.config.base_url + 'dashboard/upgrade/?auth_token=' + PF.obj.config.auth_token
+ '" class="btn btn-input accent">' +
'<span class="btn-icon fas fa-download user-select-none"></span>' +
'<span class="btn-text user-select-none">' +
PF.fn._s("Update instructions") +
PF.fn._s("Upgrade now") +
'</span>' +
'</a></div>',
'</a> ' +
'</div>',
html: true,
});
} else {
@@ -2189,7 +2196,30 @@ $(function () {
});
if (typeof PF.fn.get_url_var("checkUpdates") !== typeof undefined) {
$("[data-action=check-for-updates]").click();
$("[data-action=check-for-updates]").trigger("click");
}
if (typeof PF.fn.get_url_var("upgrade") !== typeof undefined) {
$("[data-action=upgrade]").trigger("click");
}
if (typeof PF.fn.get_url_var("license") !== typeof undefined) {
$("[data-action='license']").trigger("click");
}
if (typeof PF.fn.get_url_var("installed") !== typeof undefined) {
PF.fn.modal.simple({
title: '<i class="fas fa-code-branch"></i> ' + PF.fn._s("Chevereto v%s installed", CHV.obj.system_info.version),
message: "<p>" +
PF.fn._s('Usage of Chevereto Software must be in compliance with the software license terms known as "The Chevereto License".') +
'</p>' +
'<div class="btn-container margin-bottom-0">' +
'<a href="https://chevereto.com/license" target="_blank" class="btn btn-input accent">' +
'<span class="btn-icon fas fa-file-contract user-select-none"></span>' +
'<span class="btn-text user-select-none">' +
PF.fn._s("License agreement") +
'</span>' +
'</a> ' +
'</div>',
html: true,
});
}
$(document).on("click", "[data-action=system-update]", function (e) {
if (!$("input#system-update").prop("checked")) {
@@ -2832,7 +2862,31 @@ $(function () {
$icon.addClass(iconClass);
});
$(document).on("click", "[href^='https://chevereto.com/']", function(e) {
let hasBadge = $(this).find(".badge--paid").exists();
if(!hasBadge) {
return;
}
let href = $(this).attr("href");
let buyFrom = PF.fn._s('Get a license from %s to unlock all features and support.', '<a href="'+href+'" target="_blank">chevereto.com</a>');
let instructions = PF.fn._s('You can enter your license key in the dashboard panel.');
e.preventDefault();
e.stopPropagation();
PF.fn.modal.simple({
html: true,
title: '<i class="fa-solid fa-boxes-packing"></i> Upgrade Chevereto',
message: "<p>" + buyFrom +
" " + instructions + "</p>" +
'<div class="btn-container margin-bottom-0">' +
'<a href="' + PF.obj.config.base_url + 'dashboard/?license" class="btn btn-input accent">' +
'<span class="btn-icon fas fa-key user-select-none"></span>' +
'<span class="btn-text user-select-none">' +
PF.fn._s("Enter license") +
'</span>' +
'</a> ' +
'</div>',
});
})
});
if (typeof CHV == "undefined") {
@@ -3198,7 +3252,6 @@ CHV.fn.listingViewer = {
var object = this.getObject(true);
var template = this.getEl("template").html();
var matches = template.match(/%(\S+)%/g);
console.log(object)
if (matches) {
$.each(matches, function (i, v) {
var handle = v.slice(1, -1).split(".");
@@ -4715,7 +4768,6 @@ CHV.fn.resource_privacy_toggle = function (privacy) {
}
};
// Album stuff
CHV.fn.submit_create_album = function () {
var $modal = $(PF.obj.modal.selectors.root);
if ($("[name=form-album-name]", $modal).val() == "") {
@@ -6424,3 +6476,43 @@ CHV.fn.Palettes = {
}, 400);
}
}
CHV.fn.license = {
set: {
submit: function () {
var $modal = $(PF.obj.modal.selectors.root),
submit = true;
$.each($(":input", $modal), function (i, v) {
if ($(this).val() == "" && $(this).attr("required")) {
$(this).highlight();
submit = false;
}
});
if (!submit) {
PF.fn.growl.call(PF.fn._s("Please fill all the required fields."));
return false;
}
PF.obj.modal.form_data = {
action: "set-license-key",
key: $("[name=chevereto-license-key]", $modal).val(),
};
return true;
},
complete: {
success: function (XHR) {
let response = XHR.responseJSON;
let $trigger = $("[data-action=upgrade]");
if(CHV.obj.system_info.edition === 'free') {
$trigger.removeClass("hidden");
$trigger.trigger("click");
return;
}
PF.fn.growl.call(PF.fn._s(response.success.message));
},
error: function (XHR) {
var response = XHR.responseJSON;
PF.fn.growl.call(PF.fn._s(response.error.message));
},
},
},
};

File diff suppressed because one or more lines are too long

View File

@@ -454,7 +454,6 @@ $(function () {
deferred: window[$target.data("ajax-deferred")]
};
// Window functions failed? Maybe those are named fn...
if (typeof submit_function !== "function" && $target.data("submit-fn")) {
var submit_fn_split = $target.data("submit-fn").split(".");
submit_function = window;

View File

@@ -15,6 +15,7 @@ use function Chevereto\Legacy\get_captcha_invisible_html;
use function Chevereto\Legacy\get_translation_table;
use function Chevereto\Legacy\getSetting;
use function Chevereto\Legacy\getSettings;
use function Chevereto\Vars\env;
// @phpstan-ignore-next-line
if (!defined('ACCESS') || !ACCESS) {
@@ -116,7 +117,10 @@ if (Handler::cond('captcha_needed') && getSetting('captcha_api') == '3') {
CHV.obj.logged_user = <?php echo json_encode($logged_user_array); ?>;
<?php
if (Login::isAdmin()) { ?>
CHV.obj.system_info = <?php echo json_encode(['version' => get_app_version()]); ?>;
CHV.obj.system_info = <?php echo json_encode([
'version' => get_app_version(),
'edition' => env()['CHEVERETO_EDITION'],
]); ?>;
<?php
}
}

View File

@@ -2573,3 +2573,7 @@ body.full--wh {
.btn-cta:hover .btn-icon {
color: inherit;
}
.version-display {
font-size: 1.4em;
}

File diff suppressed because one or more lines are too long

View File

@@ -2,6 +2,7 @@
use Chevereto\Legacy\Classes\Stat;
use function Chevereto\Legacy\G\bytes_to_mb;
use function Chevereto\Legacy\G\get_base_url;
use function Chevereto\Legacy\G\get_client_ip;
use Chevereto\Legacy\G\Handler;
use function Chevereto\Legacy\get_static_url;
@@ -12,6 +13,24 @@ if (!defined('ACCESS') || !ACCESS) {
die('This file cannot be directly accessed.');
}
?>
<div data-modal="modal-license-key" class="hidden" data-submit-fn="CHV.fn.license.set.submit" data-ajax-deferred="CHV.fn.license.set.complete" data-ajax-url="<?php echo get_base_url('json'); ?>">
<span class="modal-box-title"><i class="fas fa-key"></i> <?php _se('License key'); ?></span>
<p><?php _se(
"Provide Chevereto license key by assigning the environment variable %env% or by creating the %file% containing the license key.",
[
'%env%' => '<code class="code font-weight-bold">CHEVERETO_LICENSE_KEY</code>',
'%file%' => '<code class="code font-weight-bold">' . PATH_APP . 'CHEVERETO_LICENSE_KEY</code>',
]
); ?></p>
<p><?php _se('You can also set the license in the textarea below.'); ?></p>
<div class="modal-form margin-top-20">
<div class="input-label overflow-auto">
<label for="chevereto-license-key"><?php _se('Chevereto license key'); ?></label>
<textarea placeholder="<?php _se('PASTE LICENSE KEY HERE'); ?>" id="chevereto-license-key" class="r3 resize-vertical" name="chevereto-license-key" data-focus="select-all"><?php echo Handler::var('licenseKey'); ?></textarea>
<div class="input-below font-size-small"><?php _se('Get a license from %s to unlock all features and support.', '<a href="https://chevereto.com/pricing" target="_blank">chevereto.com</a>'); ?></div>
</div>
</div>
</div>
<div data-modal="modal-connecting-ip" class="hidden">
<span class="modal-box-title"><i class="fas fa-question-circle"></i> <?php _se('Not your IP?'); ?></span>
<div class="connecting-ip"><?php echo get_client_ip(); ?></div>