diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 998692964..9c16abc7e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -31,7 +31,7 @@ If applicable, add screenshots to help explain your problem. - Mode: [e.g. standalone, nextcloud, cyberpanel, docker] **Debug/logging information** -[Read here how to log](https://github.com/the-djmaze/snappymail/wiki/FAQ#how-do-i-enable-logging) +[Read here how to log](https://github.com/nextgen-networks/snappymail/wiki/FAQ#how-do-i-enable-logging) - [ ] I've placed them here (few lines) or as attachments (many lines) **Additional context** diff --git a/.github/workflows/docker-pr.yml b/.github/workflows/docker-pr.yml index af069cdf6..55a777acd 100644 --- a/.github/workflows/docker-pr.yml +++ b/.github/workflows/docker-pr.yml @@ -21,7 +21,7 @@ jobs: DOCKER_METADATA_PR_HEAD_SHA: 'true' with: images: | - djmaze/snappymail + nextgennetworks/snappymail ghcr.io/${{ github.repository }} # type=ref,event=pr generates tag(s) on PRs only. E.g. 'pr-123', 'pr-123-abc0123' tags: | diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index b957e2fc8..28fb6f31e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -26,7 +26,7 @@ jobs: DOCKER_METADATA_PR_HEAD_SHA: 'true' with: images: | - djmaze/snappymail + nextgennetworks/snappymail ghcr.io/${{ github.repository }} # type=ref,event=branch generates tag(s) on branch only. E.g. 'master', 'master-abc0123' # type=ref,event=tag generates tag(s) on tags only. E.g. 'v0.0.0', 'v0.0.0-abc0123', and 'latest' diff --git a/CHANGELOG.md b/CHANGELOG.md index d31a1b771..0809a39c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,7 +72,7 @@ ### Fixed - mXSS exploit found by SonarSource - [CVE-2024-45800](https://github.com/the-djmaze/snappymail/security/advisories/GHSA-2rq7-79vp-ffxm) + [CVE-2024-45800](https://github.com/nextgen-networks/snappymail/security/advisories/GHSA-2rq7-79vp-ffxm) - Call to a member function Email() on null [#1706](https://github.com/the-djmaze/snappymail/issues/1706) - IMAP capabilities via IMAP Proxy @@ -202,7 +202,7 @@ ### Added - Customize private key passphrase expiration interval - [#1545](https://github.com/the-djmaze/snappymail/discussions/1545) + [#1545](https://github.com/nextgen-networks/snappymail/discussions/1545) - AdvancedSearch support for filtering mails before a given date [#1606](https://github.com/the-djmaze/snappymail/pull/1606) by @codiflow - Control valid spam and virus headers @@ -411,9 +411,9 @@ [#1458](https://github.com/the-djmaze/snappymail/issues/1458) - Automatically verify PGP and S/MIME signed messages - TNEFDecoder for - [#1012](https://github.com/the-djmaze/snappymail/discussions/1012) + [#1012](https://github.com/nextgen-networks/snappymail/discussions/1012) - RTF to HTML converter for - [#1012](https://github.com/the-djmaze/snappymail/discussions/1012) + [#1012](https://github.com/nextgen-networks/snappymail/discussions/1012) - Polyfill for PHP ctype [#1250](https://github.com/the-djmaze/snappymail/issues/1250) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 95821f69d..9777f5e56 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ 2. Install node.js - `https://nodejs.org/download/` 3. Install yarn - `https://yarnpkg.com/en/docs/install` 4. Install gulp - `npm install gulp -g` -5. Fork snappymail from https://github.com/the-djmaze/snappymail +5. Fork snappymail from https://github.com/nextgen-networks/snappymail/ 6. Clone snappymail - `git clone git@github.com:USERNAME/snappymail.git snappymail` 7. `cd snappymail` 8. Install all dependencies - `yarn install` diff --git a/build/arch/PKGBUILD b/build/arch/PKGBUILD index 9390608cb..207d109ab 100644 --- a/build/arch/PKGBUILD +++ b/build/arch/PKGBUILD @@ -6,7 +6,7 @@ pkgrel=1 pkgdesc="modern PHP webmail client" arch=('any') license=('AGPL3') -url="https://github.com/the-djmaze/snappymail" +url="https://github.com/nextgen-networks/snappymail/" depends=('php-fpm') makedepends=('php', 'nodejs' 'yarn' 'gulp', 'rollup') optdepends=('mariadb: storage backend for contacts' diff --git a/build/deb/DEBIAN/control b/build/deb/DEBIAN/control index 884792246..65bb6b79a 100644 --- a/build/deb/DEBIAN/control +++ b/build/deb/DEBIAN/control @@ -6,8 +6,8 @@ Recommends: php-intl, php-sodium, php-uuid Suggests: php-sqlite3 | php-mysql | php-pgsql, php-curl, php-exif, php-gnupg, php-gd | php-gmagick | php-imagick, php-openssl, php-zip Architecture: all Homepage: https://snappymail.eu -Vcs-Browser: https://github.com/the-djmaze/snappymail -Vcs-Git: https://github.com/the-djmaze/snappymail.git +Vcs-Browser: https://github.com/nextgen-networks/snappymail/ +Vcs-Git: https://github.com/nextgen-networks/snappymail.git Description: SnappyMail is a PHP-based simple, modern, lightweight & fast web-based email client with no database requirements. It supports IMAP, SMTP and Sieve protocols, multiple accounts and identities, an admin panel for configuration. Plugins can be installed to further extend functionality. diff --git a/cli/release.php b/cli/release.php index aac915b02..5a0e12b07 100755 --- a/cli/release.php +++ b/cli/release.php @@ -207,7 +207,7 @@ if ($options['aur']) { pkgdesc = modern PHP webmail client pkgver = '.$package->version.' pkgrel = 1 - url = https://github.com/the-djmaze/snappymail + url = https://github.com/nextgen-networks/snappymail/ arch = any license = AGPL3 makedepends = php @@ -218,7 +218,7 @@ if ($options['aur']) { optdepends = mariadb: storage backend for contacts optdepends = php-pgsql: storage backend for contacts optdepends = php-sqlite: storage backend for contacts - source = snappymail-'.$package->version.'.tar.gz::https://github.com/the-djmaze/snappymail/archive/v'.$package->version.'.tar.gz + source = snappymail-'.$package->version.'.tar.gz::https://github.com/nextgen-networks/snappymail/archive/v'.$package->version.'.tar.gz source = snappymail.sysusers source = snappymail.tmpfiles b2sums = '.implode("\n b2sums = ", $b2sums).' @@ -244,7 +244,7 @@ else if ($options['docker']) { $zip_filename = "snappymail-{$package->version}.zip"; copy($zip_destination, "./.docker/release/{$zip_filename}"); if ($docker) { - passthru("{$docker} build --pull " . ROOT_DIR . "/.docker/release/ --build-arg FILES_ZIP={$zip_filename} -t snappymail:{$package->version}"); + passthru("{$docker} build --pull -f " . ROOT_DIR . '/.docker/release/Dockerfile ' . ROOT_DIR . " --build-arg FILES_ZIP={$zip_filename} -t snappymail:{$package->version}"); } else { echo "Docker not installed!\n"; } diff --git a/examples/eml/tnef.eml b/examples/eml/tnef.eml index 7709e9ada..9432fc4ce 100644 --- a/examples/eml/tnef.eml +++ b/examples/eml/tnef.eml @@ -10,7 +10,7 @@ Content-Type: text/plain; charset=us-ascii; format=flowed Test message body with example TNEF part from https://github.com/Yeraze/ytnef -https://github.com/the-djmaze/snappymail/discussions/1012 +https://github.com/nextgen-networks/snappymail/discussions/1012 --=-2UbcDuksJBAwUNBNW7UD Content-Type: application/vnd.ms-tnef diff --git a/integrations/cloudron/Dockerfile b/integrations/cloudron/Dockerfile index a3d442e96..94316c469 100644 --- a/integrations/cloudron/Dockerfile +++ b/integrations/cloudron/Dockerfile @@ -5,7 +5,7 @@ WORKDIR /app/code # If you change the extraction below, be sure to test on scaleway VERSION=2.38.2 -RUN wget https://github.com/the-djmaze/snappymail/releases/download/v${VERSION}/snappymail-${VERSION}.zip -O /tmp/snappymail.zip && \ +RUN wget https://github.com/nextgen-networks/snappymail/releases/download/v${VERSION}/snappymail-${VERSION}.zip -O /tmp/snappymail.zip && \ unzip /tmp/snappymail.zip -d /app/code && \ rm /tmp/snappymail.zip && \ find /app/code/snappymail -type d -exec chmod 755 {} \; && \ diff --git a/integrations/nextcloud/snappymail/appinfo/info.xml b/integrations/nextcloud/snappymail/appinfo/info.xml index d73ef28a0..44e402778 100644 --- a/integrations/nextcloud/snappymail/appinfo/info.xml +++ b/integrations/nextcloud/snappymail/appinfo/info.xml @@ -25,19 +25,19 @@ There, click on the link to go to the SnappyMail admin panel. ]]> SnappyMail - https://github.com/the-djmaze/snappymail/blob/master/integrations/nextcloud/snappymail/README.md - https://github.com/the-djmaze/snappymail/wiki/Admin-Manual - https://github.com/the-djmaze/snappymail/wiki/Developer-Documentation - https://github.com/the-djmaze/snappymail/wiki + https://github.com/nextgen-networks/snappymail/blob/master/integrations/nextcloud/snappymail/README.md + https://github.com/nextgen-networks/snappymail/wiki/Admin-Manual + https://github.com/nextgen-networks/snappymail/wiki/Developer-Documentation + https://github.com/nextgen-networks/snappymail/wiki integration office search social - https://raw.githubusercontent.com/the-djmaze/snappymail/master/integrations/nextcloud/screenshots/inbox.jpg + https://raw.githubusercontent.com/nextgen-networks/snappymail/master/integrations/nextcloud/screenshots/inbox.jpg https://snappymail.eu - https://github.com/the-djmaze/snappymail/discussions - https://github.com/the-djmaze/snappymail/tree/master/integrations/nextcloud + https://github.com/nextgen-networks/snappymail/discussions + https://github.com/nextgen-networks/snappymail/tree/master/integrations/nextcloud https://github.com/the-djmaze/snappymail/issues @@ -64,7 +64,7 @@ There, click on the link to go to the SnappyMail admin panel. xxtea zip --> - + OCA\SnappyMail\Settings\AdminSettings diff --git a/integrations/nextcloud/snappymail/lib/AppInfo/Application.php b/integrations/nextcloud/snappymail/lib/AppInfo/Application.php index d764520ca..d8e6b294b 100644 --- a/integrations/nextcloud/snappymail/lib/AppInfo/Application.php +++ b/integrations/nextcloud/snappymail/lib/AppInfo/Application.php @@ -5,7 +5,6 @@ namespace OCA\SnappyMail\AppInfo; use OCA\SnappyMail\Util\SnappyMailHelper; use OCA\SnappyMail\Controller\FetchController; use OCA\SnappyMail\Controller\PageController; -use OCA\SnappyMail\Dashboard\UnreadMailWidget; use OCA\SnappyMail\Search\Provider; use OCA\SnappyMail\Listeners\AccessTokenUpdatedListener; @@ -18,6 +17,10 @@ use OCP\IUser; use OCP\User\Events\PostLoginEvent; use OCP\User\Events\BeforeUserLoggedOutEvent; use OCA\OIDCLogin\Events\AccessTokenUpdatedEvent; +use OCP\IConfig; +use OCP\ISession; +use OCP\IUserSession; +use OCP\INavigationManager; class Application extends App implements IBootstrap { @@ -37,7 +40,9 @@ class Application extends App implements IBootstrap 'PageController', function($c) { return new PageController( $c->query('AppName'), - $c->query('Request') + $c->query('Request'), + $c->query(IConfig::class), + $c->query(INavigationManager::class) ); } ); @@ -47,9 +52,10 @@ class Application extends App implements IBootstrap return new FetchController( $c->query('AppName'), $c->query('Request'), - $c->getServer()->getAppManager(), + $c->query('ServerContainer')->getAppManager(), $c->query('ServerContainer')->getConfig(), - $c->query(IL10N::class) + $c->query(IL10N::class), + $c->query(IUserSession::class) ); } ); @@ -72,22 +78,24 @@ class Application extends App implements IBootstrap public function boot(IBootContext $context): void { - if (!\is_dir(\rtrim(\trim(\OC::$server->getSystemConfig()->getValue('datadirectory', '')), '\\/') . '/appdata_snappymail')) { + $config = $context->getServerContainer()->getConfig(); + if (!\is_dir(\rtrim(\trim($config->getSystemValue('datadirectory', '')), '\\/') . '/appdata_snappymail')) { return; } $dispatcher = $context->getAppContainer()->query('OCP\EventDispatcher\IEventDispatcher'); - $dispatcher->addListener(PostLoginEvent::class, function (PostLoginEvent $Event) { + $dispatcher->addListener(PostLoginEvent::class, function (PostLoginEvent $Event) use ($context) { /* - $config = \OC::$server->getConfig(); + $config = $context->getServerContainer()->getConfig(); // Only store the user's password in the current session if they have // enabled auto-login using Nextcloud username or email address. if ($config->getAppValue('snappymail', 'snappymail-autologin', false) || $config->getAppValue('snappymail', 'snappymail-autologin-with-email', false)) { */ $sUID = $Event->getUser()->getUID(); - \OC::$server->getSession()['snappymail-nc-uid'] = $sUID; - \OC::$server->getSession()['snappymail-passphrase'] = SnappyMailHelper::encodePassword($Event->getPassword(), $sUID); + $session = $context->getServerContainer()->getSession(); + $session['snappymail-nc-uid'] = $sUID; + $session['snappymail-passphrase'] = SnappyMailHelper::encodePassword($Event->getPassword(), $sUID); /* } */ @@ -105,13 +113,13 @@ class Application extends App implements IBootstrap // https://github.com/nextcloud/impersonate/pull/180 $class = 'OCA\Impersonate\Events\BeginImpersonateEvent'; if (\class_exists($class)) { - $dispatcher->addListener($class, function ($Event) { - \OC::$server->getSession()['snappymail-passphrase'] = ''; + $dispatcher->addListener($class, function ($Event) use ($context) { + $context->getServerContainer()->getSession()['snappymail-passphrase'] = ''; SnappyMailHelper::loadApp(); \RainLoop\Api::Actions()->Logout(true); }); - $dispatcher->addListener('OCA\Impersonate\Events\EndImpersonateEvent', function ($Event) { - \OC::$server->getSession()['snappymail-passphrase'] = ''; + $dispatcher->addListener('OCA\Impersonate\Events\EndImpersonateEvent', function ($Event) use ($context) { + $context->getServerContainer()->getSession()['snappymail-passphrase'] = ''; SnappyMailHelper::loadApp(); \RainLoop\Api::Actions()->Logout(true); }); diff --git a/integrations/nextcloud/snappymail/lib/Command/Settings.php b/integrations/nextcloud/snappymail/lib/Command/Settings.php index 1e6cae25d..2843d9f3b 100644 --- a/integrations/nextcloud/snappymail/lib/Command/Settings.php +++ b/integrations/nextcloud/snappymail/lib/Command/Settings.php @@ -3,14 +3,14 @@ namespace OCA\SnappyMail\Command; use OCA\SnappyMail\Util\SnappyMailHelper; -use OC\Core\Command\Base; use OCP\IConfig; use OCP\IUserManager; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -class Settings extends Base +class Settings extends Command { protected IUserManager $userManager; protected IConfig $config; diff --git a/integrations/nextcloud/snappymail/lib/ContentSecurityPolicy.php b/integrations/nextcloud/snappymail/lib/ContentSecurityPolicy.php index 27a9c44d1..fb8bea501 100644 --- a/integrations/nextcloud/snappymail/lib/ContentSecurityPolicy.php +++ b/integrations/nextcloud/snappymail/lib/ContentSecurityPolicy.php @@ -2,6 +2,9 @@ namespace OCA\SnappyMail; +use OCP\Server; +use OCP\Security\IContentSecurityPolicyNonceManager; + class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy { /** @var bool Whether inline JS snippets are allowed */ @@ -37,7 +40,7 @@ class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy public function getSnappyMailNonce() { static $sNonce; if (!$sNonce) { - $cspManager = \OC::$server->getContentSecurityPolicyNonceManager(); + $cspManager = Server::get(IContentSecurityPolicyNonceManager::class); $sNonce = $cspManager->getNonce() ?: \SnappyMail\UUID::generate(); if (\method_exists($cspManager, 'browserSupportsCspV3') && !$cspManager->browserSupportsCspV3()) { $this->addAllowedScriptDomain("'nonce-{$sNonce}'"); diff --git a/integrations/nextcloud/snappymail/lib/Controller/FetchController.php b/integrations/nextcloud/snappymail/lib/Controller/FetchController.php index 6ba8a7761..2091e6122 100644 --- a/integrations/nextcloud/snappymail/lib/Controller/FetchController.php +++ b/integrations/nextcloud/snappymail/lib/Controller/FetchController.php @@ -10,17 +10,21 @@ use OCP\AppFramework\Http\JSONResponse; use OCP\IConfig; use OCP\IL10N; use OCP\IRequest; +use OCP\IUserSession; +use Exception; class FetchController extends Controller { private IConfig $config; private IAppManager $appManager; private IL10N $l; + private IUserSession $userSession; - public function __construct(string $appName, IRequest $request, IAppManager $appManager, IConfig $config, IL10N $l) { + public function __construct(string $appName, IRequest $request, IAppManager $appManager, IConfig $config, IL10N $l, IUserSession $userSession) { parent::__construct($appName, $request); $this->config = $config; $this->appManager = $appManager; $this->l = $l; + $this->userSession = $userSession; } public function upgrade(): JSONResponse { @@ -104,15 +108,18 @@ class FetchController extends Controller { try { $sEmail = ''; if (isset($_POST['appname'], $_POST['snappymail-password'], $_POST['snappymail-email']) && 'snappymail' === $_POST['appname']) { - $sUser = \OC::$server->getUserSession()->getUser()->getUID(); + $user = $this->userSession->getUser(); + $sUser = $user ? $user->getUID() : ''; - $sEmail = $_POST['snappymail-email']; - $this->config->setUserValue($sUser, 'snappymail', 'snappymail-email', $sEmail); + if ($sUser) { + $sEmail = $_POST['snappymail-email']; + $this->config->setUserValue($sUser, 'snappymail', 'snappymail-email', $sEmail); - $sPass = $_POST['snappymail-password']; - if ('******' !== $sPass) { - $this->config->setUserValue($sUser, 'snappymail', 'passphrase', - $sPass ? SnappyMailHelper::encodePassword($sPass, \md5($sEmail)) : ''); + $sPass = $_POST['snappymail-password']; + if ('******' !== $sPass) { + $this->config->setUserValue($sUser, 'snappymail', 'passphrase', + $sPass ? SnappyMailHelper::encodePassword($sPass, \md5($sEmail)) : ''); + } } } else { return new JSONResponse([ @@ -144,4 +151,3 @@ class FetchController extends Controller { } } } - diff --git a/integrations/nextcloud/snappymail/lib/Controller/PageController.php b/integrations/nextcloud/snappymail/lib/Controller/PageController.php index 7744cb4fc..ee11d74e7 100644 --- a/integrations/nextcloud/snappymail/lib/Controller/PageController.php +++ b/integrations/nextcloud/snappymail/lib/Controller/PageController.php @@ -7,17 +7,27 @@ use OCA\SnappyMail\ContentSecurityPolicy; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\TemplateResponse; +use OCP\INavigationManager; +use OCP\IConfig; +use OCP\IRequest; class PageController extends Controller { + private IConfig $config; + private INavigationManager $navigationManager; + + public function __construct($appName, IRequest $request, IConfig $config, INavigationManager $navigationManager) { + parent::__construct($appName, $request); + $this->config = $config; + $this->navigationManager = $navigationManager; + } + /** * @NoAdminRequired * @NoCSRFRequired */ public function index() { - $config = \OC::$server->getConfig(); - $bAdmin = false; if (!empty($_SERVER['QUERY_STRING'])) { SnappyMailHelper::loadApp(); @@ -27,10 +37,8 @@ class PageController extends Controller } } - if (!$bAdmin && $config->getAppValue('snappymail', 'snappymail-no-embed')) { - \OC::$server->getNavigationManager()->setActiveEntry('snappymail'); - \OCP\Util::addScript('snappymail', 'snappymail'); - \OCP\Util::addStyle('snappymail', 'style'); + if (!$bAdmin && $this->config->getAppValue('snappymail', 'snappymail-no-embed')) { + $this->navigationManager->setActiveEntry('snappymail'); SnappyMailHelper::startApp(); $response = new TemplateResponse('snappymail', 'index', [ 'snappymail-iframe-url' => SnappyMailHelper::normalizeUrl(SnappyMailHelper::getAppUrl()) @@ -43,9 +51,7 @@ class PageController extends Controller return $response; } - \OC::$server->getNavigationManager()->setActiveEntry('snappymail'); - - \OCP\Util::addStyle('snappymail', 'embed'); + $this->navigationManager->setActiveEntry('snappymail'); SnappyMailHelper::startApp(); $oConfig = \RainLoop\Api::Config(); @@ -59,6 +65,8 @@ class PageController extends Controller $csp = new ContentSecurityPolicy(); $sNonce = $csp->getSnappyMailNonce(); + $cssLink = \RainLoop\Utils::WebStaticPath('css/'.($bAdmin?'admin':'app').$sAppCssMin.'.css'); + $params = [ 'Admin' => $bAdmin ? 1 : 0, 'LoadingDescriptionEsc' => \htmlspecialchars($oConfig->Get('webmail', 'loading_description', 'SnappyMail'), ENT_QUOTES|ENT_IGNORE, 'UTF-8'), @@ -71,16 +79,10 @@ class PageController extends Controller '/\\s*([:;{},]+)\\s*/s', '$1', $oActions->compileCss($oActions->GetTheme($bAdmin), $bAdmin) - ) + ), + 'CssLink' => $cssLink ]; -// \OCP\Util::addScript('snappymail', '../app/snappymail/v/'.APP_VERSION.'/static/js'.($sAppJsMin ? '/min' : '').'/boot'.$sAppJsMin); - - // Nextcloud html encodes, so addHeader('style') is not possible -// \OCP\Util::addHeader('style', ['id'=>'app-boot-css'], \file_get_contents(APP_VERSION_ROOT_PATH.'static/css/boot'.$sAppCssMin.'.css')); - \OCP\Util::addHeader('link', ['type'=>'text/css','rel'=>'stylesheet','href'=>\RainLoop\Utils::WebStaticPath('css/'.($bAdmin?'admin':'app').$sAppCssMin.'.css')], ''); -// \OCP\Util::addHeader('style', ['id'=>'app-theme-style','data-href'=>$params['BaseAppThemeCssLink']], $params['BaseAppThemeCss']); - $response = new TemplateResponse('snappymail', 'index_embed', $params); $response->setContentSecurityPolicy($csp); diff --git a/integrations/nextcloud/snappymail/lib/Migration/InstallStep.php b/integrations/nextcloud/snappymail/lib/Migration/InstallStep.php index 966ef2352..6f00cedfe 100644 --- a/integrations/nextcloud/snappymail/lib/Migration/InstallStep.php +++ b/integrations/nextcloud/snappymail/lib/Migration/InstallStep.php @@ -6,7 +6,8 @@ use OCA\SnappyMail\AppInfo\Application; use OCP\IConfig; use OCP\Migration\IOutput; use OCP\Migration\IRepairStep; -use OCP\ILogger; +use Psr\Log\LoggerInterface; +use OCP\App\IAppManager; /** * Note: this is not run at install, but when: @@ -15,6 +16,16 @@ use OCP\ILogger; */ class InstallStep implements IRepairStep { + private IAppManager $appManager; + private IConfig $config; + private LoggerInterface $logger; + + public function __construct(IAppManager $appManager, IConfig $config, LoggerInterface $logger) { + $this->appManager = $appManager; + $this->config = $config; + $this->logger = $logger; + } + public function getName() { return 'Setup SnappyMail'; } @@ -53,7 +64,7 @@ class InstallStep implements IRepairStep if (!$oConfig->Get('webmail', 'app_path')) { $output->info('Set config [webmail]app_path'); - $oConfig->Set('webmail', 'app_path', \OC::$server->getAppManager()->getAppWebPath('snappymail') . '/app/'); + $oConfig->Set('webmail', 'app_path', $this->appManager->getAppWebPath('snappymail') . '/app/'); $oConfig->Set('webmail', 'allow_languages_on_settings', false); $oConfig->Set('login', 'allow_languages_on_login', false); $bSave = true; @@ -116,9 +127,7 @@ class InstallStep implements IRepairStep // ex: php occ config:app:set snappymail custom_config_file --value="/path/to/config.php" // https://github.com/the-djmaze/snappymail/pull/1197 try { - /** @var IConfig $ncConfig */ - $ncConfig = \OC::$server->get(IConfig::class); - $customConfigFile = $ncConfig->getAppValue(Application::APP_ID, 'custom_config_file'); + $customConfigFile = $this->config->getAppValue(Application::APP_ID, 'custom_config_file'); if ($customConfigFile) { $output->info("Load custom config: {$customConfigFile}"); if (!\str_contains($customConfigFile, ':') && \is_readable($customConfigFile)) { @@ -129,9 +138,7 @@ class InstallStep implements IRepairStep } } catch (\Throwable $e) { $output->warning("custom config error: " . $e->getMessage()); - /** @var \Psr\Log\LoggerInterface $logger */ - $logger = \OC::$server->get(\Psr\Log\LoggerInterface::class); - $logger->error("custom config error: " . $e->getMessage()); + $this->logger->error("custom config error: " . $e->getMessage()); } } } diff --git a/integrations/nextcloud/snappymail/lib/Settings/AdminSettings.php b/integrations/nextcloud/snappymail/lib/Settings/AdminSettings.php index 7a69cb96f..4bf372eae 100644 --- a/integrations/nextcloud/snappymail/lib/Settings/AdminSettings.php +++ b/integrations/nextcloud/snappymail/lib/Settings/AdminSettings.php @@ -5,14 +5,32 @@ use OCA\SnappyMail\Util\SnappyMailHelper; use OCP\AppFramework\Http\TemplateResponse; use OCP\IConfig; use OCP\Settings\ISettings; +use OCP\IUserSession; +use OCP\IGroupManager; +use OCP\IURLGenerator; +use OCP\App\IAppManager; +use OCP\Util; class AdminSettings implements ISettings { - private $config; + private IConfig $config; + private IUserSession $userSession; + private IGroupManager $groupManager; + private IURLGenerator $urlGenerator; + private IAppManager $appManager; - public function __construct(IConfig $config) - { + public function __construct( + IConfig $config, + IUserSession $userSession, + IGroupManager $groupManager, + IURLGenerator $urlGenerator, + IAppManager $appManager + ) { $this->config = $config; + $this->userSession = $userSession; + $this->groupManager = $groupManager; + $this->urlGenerator = $urlGenerator; + $this->appManager = $appManager; } public function getForm() @@ -30,12 +48,15 @@ class AdminSettings implements ISettings $v = $this->config->getAppValue('snappymail', $k); $parameters[$k] = $v; } - $uid = \OC::$server->getUserSession()->getUser()->getUID(); - if (\OC_User::isAdminUser($uid)) { + + $user = $this->userSession->getUser(); + $uid = $user ? $user->getUID() : null; + + if ($uid && $this->groupManager->isAdmin($uid)) { // $parameters['snappymail-admin-panel-link'] = SnappyMailHelper::getAppUrl().'?admin'; SnappyMailHelper::loadApp(); $parameters['snappymail-admin-panel-link'] = - \OC::$server->getURLGenerator()->linkToRoute('snappymail.page.index') + $this->urlGenerator->linkToRoute('snappymail.page.index') . '?' . \RainLoop\Api::Config()->Get('security', 'admin_panel_key', 'admin'); } @@ -49,7 +70,7 @@ class AdminSettings implements ISettings $parameters['snappymail-admin-password'] = $sPassword; $parameters['can-import-rainloop'] = $sPassword && \is_dir( - \rtrim(\trim(\OC::$server->getSystemConfig()->getValue('datadirectory', '')), '\\/') + \rtrim(\trim($this->config->getSystemValue('datadirectory', '')), '\\/') . '/rainloop-storage' ); @@ -65,7 +86,7 @@ class AdminSettings implements ISettings // Prevent "Failed loading /nextcloud/snappymail/v/2.N.N/static/js/min/libs.min.js" $app_path = $oConfig->Get('webmail', 'app_path'); if (!$app_path) { - $app_path = \OC::$server->getAppManager()->getAppWebPath('snappymail') . '/app/'; + $app_path = $this->appManager->getAppWebPath('snappymail') . '/app/'; $oConfig->Set('webmail', 'app_path', $app_path); $oConfig->Set('webmail', 'theme', 'NextcloudV25+'); $oConfig->Save(); @@ -73,7 +94,6 @@ class AdminSettings implements ISettings $parameters['snappymail-app_path'] = $oConfig->Get('webmail', 'app_path', false); $parameters['snappymail-nc-lang'] = !$oConfig->Get('webmail', 'allow_languages_on_settings', true); - \OCP\Util::addScript('snappymail', 'snappymail'); return new TemplateResponse('snappymail', 'admin-local', $parameters); } diff --git a/integrations/nextcloud/snappymail/lib/Util/SnappyMailHelper.php b/integrations/nextcloud/snappymail/lib/Util/SnappyMailHelper.php index 80a93d061..528f5917d 100644 --- a/integrations/nextcloud/snappymail/lib/Util/SnappyMailHelper.php +++ b/integrations/nextcloud/snappymail/lib/Util/SnappyMailHelper.php @@ -1,232 +1,252 @@ -getSystemConfig()->getValue('datadirectory', '')), '\\/').'/appdata_snappymail/'); - - $app_dir = \dirname(\dirname(__DIR__)) . '/app'; - require_once $app_dir . '/index.php'; - } - - public static function startApp(bool $handle = false) - { - static::loadApp(); - - $oConfig = \RainLoop\Api::Config(); - - if (false !== \stripos(\php_sapi_name(), 'cli')) { - return; - } - - try { - $oActions = \RainLoop\Api::Actions(); - if (isset($_GET[$oConfig->Get('security', 'admin_panel_key', 'admin')])) { - if ($oConfig->Get('security', 'allow_admin_panel', true) - && \OC_User::isAdminUser(\OC::$server->getUserSession()->getUser()->getUID()) - && !$oActions->IsAdminLoggined(false) - ) { - $sRand = \MailSo\Base\Utils::Sha1Rand(); - if ($oActions->Cacher(null, true)->Set(\RainLoop\KeyPathHelper::SessionAdminKey($sRand), \time())) { - $sToken = \RainLoop\Utils::EncodeKeyValuesQ(array('token', $sRand)); -// $oActions->setAdminAuthToken($sToken); - \SnappyMail\Cookies::set('smadmin', $sToken); - } - } - } else { - $doLogin = !$oActions->getMainAccountFromToken(false); - $aCredentials = static::getLoginCredentials(); -/* - // NC25+ workaround for Impersonate plugin - // https://github.com/the-djmaze/snappymail/issues/561#issuecomment-1301317723 - // https://github.com/nextcloud/server/issues/34935#issuecomment-1302145157 - require \OC::$SERVERROOT . '/version.php'; -// \OC\SystemConfig -// file_get_contents(\OC::$SERVERROOT . 'config/config.php'); -// $CONFIG['version'] - if (24 < $OC_Version[0]) { - $ocSession = \OC::$server->getSession(); - $ocSession->reopen(); - if (!$doLogin && $ocSession['snappymail-uid'] && $ocSession['snappymail-uid'] != $aCredentials[0]) { - // UID changed, Impersonate plugin probably active - $oActions->Logout(true); - $doLogin = true; - } - $ocSession->set('snappymail-uid', $aCredentials[0]); - } -*/ - if ($doLogin && $aCredentials[1] && $aCredentials[2]) { - $isOIDC = \str_starts_with($aCredentials[2], 'oidc_login|'); - try { - $ocSession = \OC::$server->getSession(); - $oAccount = $oActions->LoginProcess($aCredentials[1], $aCredentials[2]); - if (!$isOIDC && $oAccount - && $oConfig->Get('login', 'sign_me_auto', \RainLoop\Enumerations\SignMeType::DefaultOff) === \RainLoop\Enumerations\SignMeType::DefaultOn - ) { - $oActions->SetSignMeToken($oAccount); - } - } catch (\Throwable $e) { - // Login failure, reset password to prevent more attempts - if (!$isOIDC) { - $sUID = \OC::$server->getUserSession()->getUser()->getUID(); - \OC::$server->getSession()['snappymail-passphrase'] = ''; - \OC::$server->getConfig()->setUserValue($sUID, 'snappymail', 'passphrase', ''); - \SnappyMail\Log::error('Nextcloud', $e->getMessage()); - } - } - } - } - - if ($handle) { - \header_remove('Content-Security-Policy'); - \RainLoop\Service::Handle(); - // https://github.com/the-djmaze/snappymail/issues/1069 - exit; -// return new SnappyMailResponse(); - } - } catch (\Throwable $e) { - // Ignore login failure - } - } - - // Check if OpenID Connect (OIDC) is enabled and used for login - // https://apps.nextcloud.com/apps/oidc_login - public static function isOIDCLogin() : bool - { - $config = \OC::$server->getConfig(); - if ($config->getAppValue('snappymail', 'snappymail-autologin-oidc', false)) { - // Check if the OIDC Login app is enabled - if (\OC::$server->getAppManager()->isEnabledForUser('oidc_login')) { - // Check if session is an OIDC Login - $ocSession = \OC::$server->getSession(); - if ($ocSession->get('is_oidc')) { - // IToken->getPassword() ??? - if ($ocSession->get('oidc_access_token')) { - return true; - } - \SnappyMail\Log::debug('Nextcloud', 'OIDC access_token missing'); - } else { - \SnappyMail\Log::debug('Nextcloud', 'No OIDC login'); - } - } else { - \SnappyMail\Log::debug('Nextcloud', 'OIDC login disabled'); - } - } - return false; - } - - private static function getLoginCredentials() : array - { - $sUID = \OC::$server->getUserSession()->getUser()->getUID(); - $config = \OC::$server->getConfig(); - $ocSession = \OC::$server->getSession(); - - // If the user has set credentials for SnappyMail in their personal settings, - // this has the first priority. - $sEmail = $config->getUserValue($sUID, 'snappymail', 'snappymail-email'); - $sPassword = $config->getUserValue($sUID, 'snappymail', 'passphrase') - ?: $config->getUserValue($sUID, 'snappymail', 'snappymail-password'); - if ($sEmail && $sPassword) { - $sPassword = static::decodePassword($sPassword, \md5($sEmail)); - if ($sPassword) { - return [$sUID, $sEmail, $sPassword]; - } else { - \SnappyMail\Log::debug('Nextcloud', 'decodePassword failed for getUserValue'); - } - } - - // If the current user ID is identical to login ID (not valid when using account switching), - // this has the second priority. - if ($ocSession['snappymail-nc-uid'] == $sUID) { - - // If OpenID Connect (OIDC) is enabled and used for login, use this. - if (static::isOIDCLogin()) { - $sEmail = $config->getUserValue($sUID, 'settings', 'email'); - return [$sUID, $sEmail, "oidc_login|{$sUID}"]; - } - - // Only use the user's password in the current session if they have - // enabled auto-login using Nextcloud username or email address. - $sEmail = ''; - $sPassword = ''; - if ($config->getAppValue('snappymail', 'snappymail-autologin', false)) { - $sEmail = $sUID; - $sPassword = $ocSession['snappymail-passphrase']; - } else if ($config->getAppValue('snappymail', 'snappymail-autologin-with-email', false)) { - $sEmail = $config->getUserValue($sUID, 'settings', 'email'); - $sPassword = $ocSession['snappymail-passphrase']; - } else { - \SnappyMail\Log::debug('Nextcloud', 'snappymail-autologin is off'); - } - if ($sPassword) { - return [$sUID, $sEmail, static::decodePassword($sPassword, $sUID)]; - } - } else { - \SnappyMail\Log::debug('Nextcloud', "snappymail-nc-uid mismatch '{$ocSession['snappymail-nc-uid']}' != '{$sUID}'"); - } - - return [$sUID, '', '']; - } - - public static function getAppUrl() : string - { - return \OC::$server->getURLGenerator()->linkToRoute('snappymail.page.appGet'); - } - - public static function normalizeUrl(string $sUrl) : string - { - $sUrl = \rtrim(\trim($sUrl), '/\\'); - if ('.php' !== \strtolower(\substr($sUrl, -4))) { - $sUrl .= '/'; - } - - return $sUrl; - } - - public static function encodePassword(string $sPassword, string $sSalt) : string - { - static::loadApp(); - return \SnappyMail\Crypt::EncryptUrlSafe($sPassword, $sSalt); - } - - public static function decodePassword(string $sPassword, string $sSalt) : ?\SnappyMail\SensitiveString - { - static::loadApp(); - $result = \SnappyMail\Crypt::DecryptUrlSafe($sPassword, $sSalt); - return $result ? new \SnappyMail\SensitiveString($result) : null; - } -} +getSystemConfig()->getValue('datadirectory', '')), '\\/').'/appdata_snappymail/'); + + $app_dir = \dirname(\dirname(__DIR__)) . '/app'; + require_once $app_dir . '/index.php'; + } + + public static function startApp(bool $handle = false) + { + static::loadApp(); + + $oConfig = \RainLoop\Api::Config(); + + if (false !== \stripos(\php_sapi_name(), 'cli')) { + return; + } + + try { + $oActions = \RainLoop\Api::Actions(); + if (isset($_GET[$oConfig->Get('security', 'admin_panel_key', 'admin')])) { + $user = Server::get(IUserSession::class)->getUser(); + if ($oConfig->Get('security', 'allow_admin_panel', true) + && $user + && Server::get(IGroupManager::class)->isAdmin($user->getUID()) + && !$oActions->IsAdminLoggined(false) + ) { + $sRand = \MailSo\Base\Utils::Sha1Rand(); + if ($oActions->Cacher(null, true)->Set(\RainLoop\KeyPathHelper::SessionAdminKey($sRand), \time())) { + $sToken = \RainLoop\Utils::EncodeKeyValuesQ(array('token', $sRand)); +// $oActions->setAdminAuthToken($sToken); + \SnappyMail\Cookies::set('smadmin', $sToken); + } + } + } else { + $doLogin = !$oActions->getMainAccountFromToken(false); + $aCredentials = static::getLoginCredentials(); +/* + // NC25+ workaround for Impersonate plugin + // https://github.com/the-djmaze/snappymail/issues/561#issuecomment-1301317723 + // https://github.com/nextcloud/server/issues/34935#issuecomment-1302145157 + require \OC::$SERVERROOT . '/version.php'; +// \OC\SystemConfig +// file_get_contents(\OC::$SERVERROOT . 'config/config.php'); +// $CONFIG['version'] + if (24 < $OC_Version[0]) { + $ocSession = Server::get(ISession::class); + $ocSession->reopen(); + if (!$doLogin && $ocSession['snappymail-uid'] && $ocSession['snappymail-uid'] != $aCredentials[0]) { + // UID changed, Impersonate plugin probably active + $oActions->Logout(true); + $doLogin = true; + } + $ocSession->set('snappymail-uid', $aCredentials[0]); + } +*/ + if ($doLogin && $aCredentials[1] && $aCredentials[2]) { + $isOIDC = \str_starts_with($aCredentials[2], 'oidc_login|'); + try { + $ocSession = Server::get(ISession::class); + $oAccount = $oActions->LoginProcess($aCredentials[1], $aCredentials[2]); + if (!$isOIDC && $oAccount + && $oConfig->Get('login', 'sign_me_auto', \RainLoop\Enumerations\SignMeType::DefaultOff) === \RainLoop\Enumerations\SignMeType::DefaultOn + ) { + $oActions->SetSignMeToken($oAccount); + } + } catch (\Throwable $e) { + // Login failure, reset password to prevent more attempts + if (!$isOIDC) { + $user = Server::get(IUserSession::class)->getUser(); + $sUID = $user ? $user->getUID() : null; + if ($sUID) { + $session = Server::get(ISession::class); + $session['snappymail-passphrase'] = ''; + Server::get(IConfig::class)->setUserValue($sUID, 'snappymail', 'passphrase', ''); + \SnappyMail\Log::error('Nextcloud', $e->getMessage()); + } + } + } + } + } + + if ($handle) { + \header_remove('Content-Security-Policy'); + \RainLoop\Service::Handle(); + // https://github.com/the-djmaze/snappymail/issues/1069 + exit; +// return new SnappyMailResponse(); + } + } catch (\Throwable $e) { + // Ignore login failure + } + } + + // Check if OpenID Connect (OIDC) is enabled and used for login + // https://apps.nextcloud.com/apps/oidc_login + public static function isOIDCLogin() : bool + { + $config = Server::get(IConfig::class); + if ($config->getAppValue('snappymail', 'snappymail-autologin-oidc', false)) { + // Check if the OIDC Login app is enabled + if (Server::get(IAppManager::class)->isEnabledForUser('oidc_login')) { + // Check if session is an OIDC Login + $ocSession = Server::get(ISession::class); + if ($ocSession->get('is_oidc')) { + // IToken->getPassword() ??? + if ($ocSession->get('oidc_access_token')) { + return true; + } + \SnappyMail\Log::debug('Nextcloud', 'OIDC access_token missing'); + } else { + \SnappyMail\Log::debug('Nextcloud', 'No OIDC login'); + } + } else { + \SnappyMail\Log::debug('Nextcloud', 'OIDC login disabled'); + } + } + return false; + } + + private static function getLoginCredentials() : array + { + $user = Server::get(IUserSession::class)->getUser(); + $sUID = $user ? $user->getUID() : ''; + $config = Server::get(IConfig::class); + $ocSession = Server::get(ISession::class); + + if (!$sUID) { + return ['', '', '']; + } + + // If the user has set credentials for SnappyMail in their personal settings, + // this has the first priority. + $sEmail = $config->getUserValue($sUID, 'snappymail', 'snappymail-email'); + $sPassword = $config->getUserValue($sUID, 'snappymail', 'passphrase') + ?: $config->getUserValue($sUID, 'snappymail', 'snappymail-password'); + if ($sEmail && $sPassword) { + $sPassword = static::decodePassword($sPassword, \md5($sEmail)); + if ($sPassword) { + return [$sUID, $sEmail, $sPassword]; + } else { + \SnappyMail\Log::debug('Nextcloud', 'decodePassword failed for getUserValue'); + } + } + + // If the current user ID is identical to login ID (not valid when using account switching), + // this has the second priority. + // Note: $ocSession array access is supported but deprecated? ISession implements ArrayAccess. + if ($ocSession['snappymail-nc-uid'] == $sUID) { + + // If OpenID Connect (OIDC) is enabled and used for login, use this. + if (static::isOIDCLogin()) { + $sEmail = $config->getUserValue($sUID, 'settings', 'email'); + return [$sUID, $sEmail, "oidc_login|{$sUID}"]; + } + + // Only use the user's password in the current session if they have + // enabled auto-login using Nextcloud username or email address. + $sEmail = ''; + $sPassword = ''; + if ($config->getAppValue('snappymail', 'snappymail-autologin', false)) { + $sEmail = $sUID; + $sPassword = $ocSession['snappymail-passphrase']; + } else if ($config->getAppValue('snappymail', 'snappymail-autologin-with-email', false)) { + $sEmail = $config->getUserValue($sUID, 'settings', 'email'); + $sPassword = $ocSession['snappymail-passphrase']; + } else { + \SnappyMail\Log::debug('Nextcloud', 'snappymail-autologin is off'); + } + if ($sPassword) { + return [$sUID, $sEmail, static::decodePassword($sPassword, $sUID)]; + } + } else { + \SnappyMail\Log::debug('Nextcloud', "snappymail-nc-uid mismatch '{$ocSession['snappymail-nc-uid']}' != '{$sUID}'"); + } + + return [$sUID, '', '']; + } + + public static function getAppUrl() : string + { + return Server::get(IURLGenerator::class)->linkToRoute('snappymail.page.appGet'); + } + + public static function normalizeUrl(string $sUrl) : string + { + $sUrl = \rtrim(\trim($sUrl), '/\\'); + if ('.php' !== \strtolower(\substr($sUrl, -4))) { + $sUrl .= '/'; + } + + return $sUrl; + } + + public static function encodePassword(string $sPassword, string $sSalt) : string + { + static::loadApp(); + return \SnappyMail\Crypt::EncryptUrlSafe($sPassword, $sSalt); + } + + public static function decodePassword(string $sPassword, string $sSalt) : ?\SnappyMail\SensitiveString + { + static::loadApp(); + $result = \SnappyMail\Crypt::DecryptUrlSafe($sPassword, $sSalt); + return $result ? new \SnappyMail\SensitiveString($result) : null; + } +} diff --git a/integrations/nextcloud/snappymail/templates/admin-local.php b/integrations/nextcloud/snappymail/templates/admin-local.php index 5270f3e50..dd5ddd6ee 100644 --- a/integrations/nextcloud/snappymail/templates/admin-local.php +++ b/integrations/nextcloud/snappymail/templates/admin-local.php @@ -1,3 +1,4 @@ +
diff --git a/integrations/nextcloud/snappymail/templates/index.php b/integrations/nextcloud/snappymail/templates/index.php index f304654fb..ba1b7eee0 100644 --- a/integrations/nextcloud/snappymail/templates/index.php +++ b/integrations/nextcloud/snappymail/templates/index.php @@ -1,3 +1,5 @@ - + diff --git a/integrations/nextcloud/snappymail/templates/index_embed.php b/integrations/nextcloud/snappymail/templates/index_embed.php index 4384bef5d..f853e9320 100644 --- a/integrations/nextcloud/snappymail/templates/index_embed.php +++ b/integrations/nextcloud/snappymail/templates/index_embed.php @@ -1,3 +1,5 @@ + +
diff --git a/integrations/owncloud/snappymail/appinfo/info.xml b/integrations/owncloud/snappymail/appinfo/info.xml index 84ce9cb7f..1b9092eb8 100644 --- a/integrations/owncloud/snappymail/appinfo/info.xml +++ b/integrations/owncloud/snappymail/appinfo/info.xml @@ -22,17 +22,17 @@ There, click on the link to go to the SnappyMail admin panel. ]]> SnappyMail - https://github.com/the-djmaze/snappymail/blob/master/integrations/owncloud/snappymail/README.md - https://github.com/the-djmaze/snappymail/wiki + https://github.com/nextgen-networks/snappymail/blob/master/integrations/owncloud/snappymail/README.md + https://github.com/nextgen-networks/snappymail/wiki integration office search social - https://raw.githubusercontent.com/the-djmaze/snappymail/master/integrations/owncloud/screenshots/inbox.jpg + https://raw.githubusercontent.com/nextgen-networks/snappymail/master/integrations/owncloud/screenshots/inbox.jpg https://snappymail.eu - https://github.com/the-djmaze/snappymail/discussions - https://github.com/the-djmaze/snappymail/tree/master/integrations/owncloud + https://github.com/nextgen-networks/snappymail/discussions + https://github.com/nextgen-networks/snappymail/tree/master/integrations/owncloud https://github.com/the-djmaze/snappymail/issues diff --git a/integrations/virtualmin/snappymail.pl b/integrations/virtualmin/snappymail.pl index c3fcc7ab5..61cba8f47 100644 --- a/integrations/virtualmin/snappymail.pl +++ b/integrations/virtualmin/snappymail.pl @@ -153,7 +153,7 @@ sub script_snappymail_files local ($d, $ver, $opts, $upgrade) = @_; local @files = ( { 'name' => "source", 'file' => "snappymail-$ver.tar.gz", - 'url' => "https://github.com/the-djmaze/snappymail/releases/download/v${ver}/snappymail-${ver}.tar.gz" }, + 'url' => "https://github.com/nextgen-networks/snappymail/releases/download/v${ver}/snappymail-${ver}.tar.gz" }, ); return @files; } diff --git a/package.json b/package.json index 3e3ff929d..889872ef9 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ }, "repository": { "type": "git", - "url": "git://github.com/the-djmaze/snappymail.git" + "url": "git://github.com/nextgen-networks/snappymail.git" }, "scripts": { "watch-css": "gulp watchCss" diff --git a/plugins/auto-domain-grab/index.php b/plugins/auto-domain-grab/index.php index ee2458948..5fea19cb2 100644 --- a/plugins/auto-domain-grab/index.php +++ b/plugins/auto-domain-grab/index.php @@ -7,7 +7,7 @@ * be set to to 'example.com'. * * Based on: - * https://github.com/the-djmaze/snappymail/blob/master/plugins/override-smtp-credentials/index.php + * https://github.com/nextgen-networks/snappymail/blob/master/plugins/override-smtp-credentials/index.php * */ diff --git a/plugins/login-gmail/index.php b/plugins/login-gmail/index.php index a6b8d3f9d..ca4f4e6b1 100644 --- a/plugins/login-gmail/index.php +++ b/plugins/login-gmail/index.php @@ -148,7 +148,7 @@ class LoginGMailPlugin extends \RainLoop\Plugins\AbstractPlugin ->SetLabel('Client ID') ->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING) ->SetAllowedInJs() - ->SetDescription('https://github.com/the-djmaze/snappymail/wiki/FAQ#gmail'), + ->SetDescription('https://github.com/nextgen-networks/snappymail/wiki/FAQ#gmail'), \RainLoop\Plugins\Property::NewInstance('client_secret') ->SetLabel('Client Secret') ->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING) diff --git a/plugins/login-o365/index.php b/plugins/login-o365/index.php index 62050f429..c372dd8ab 100644 --- a/plugins/login-o365/index.php +++ b/plugins/login-o365/index.php @@ -166,7 +166,7 @@ class LoginO365Plugin extends \RainLoop\Plugins\AbstractPlugin ->SetLabel('Client ID') ->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING) ->SetAllowedInJs() - ->SetDescription('https://github.com/the-djmaze/snappymail/wiki/FAQ#o365'), + ->SetDescription('https://github.com/nextgen-networks/snappymail/wiki/FAQ#o365'), \RainLoop\Plugins\Property::NewInstance('client_secret') ->SetLabel('Client Secret') ->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING) diff --git a/plugins/nextcloud/index.php b/plugins/nextcloud/index.php index c49524b33..bd9715e6d 100644 --- a/plugins/nextcloud/index.php +++ b/plugins/nextcloud/index.php @@ -4,8 +4,9 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin { const NAME = 'Nextcloud', - VERSION = '2.38.1', - RELEASE = '2024-10-08', + // Keep upstream metadata if you prefer; this is not functional. + VERSION = '2.38.2', + RELEASE = '2026-02-06', CATEGORY = 'Integrations', DESCRIPTION = 'Integrate with Nextcloud v20+', REQUIRED = '2.38.0'; @@ -36,14 +37,13 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin $this->addTemplate('templates/PopupsNextcloudFiles.html'); $this->addTemplate('templates/PopupsNextcloudCalendars.html'); -// $this->addHook('login.credentials.step-2', 'loginCredentials2'); -// $this->addHook('login.credentials', 'loginCredentials'); + // $this->addHook('login.credentials.step-2', 'loginCredentials2'); + // $this->addHook('login.credentials', 'loginCredentials'); $this->addHook('imap.before-login', 'beforeLogin'); $this->addHook('smtp.before-login', 'beforeLogin'); $this->addHook('sieve.before-login', 'beforeLogin'); } else { \SnappyMail\Log::debug('Nextcloud', 'NOT integrated'); - // \OC::$server->getConfig()->getAppValue('snappymail', 'snappymail-no-embed'); $this->addHook('main.content-security-policy', 'ContentSecurityPolicy'); } } @@ -72,13 +72,7 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin public function loginCredentials(string &$sEmail, string &$sLogin, ?string &$sPassword = null) : void { - /** - * This has an issue. - * When user changes email address, all settings are gone as the new - * _data_/_default_/storage/{domain}/{local-part} is used - */ -// $ocUser = \OC::$server->getUserSession()->getUser(); -// $sEmail = $ocUser->getEMailAddress() ?: $ocUser->getPrimaryEMailAddress() ?: $sEmail; + // left intentionally as upstream (commented in upstream) } public function loginCredentials2(string &$sEmail, ?string &$sPassword = null) : void @@ -89,83 +83,145 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin public function beforeLogin(\RainLoop\Model\Account $oAccount, \MailSo\Net\NetClient $oClient, \MailSo\Net\ConnectSettings $oSettings) : void { - // Only login with OIDC access token if - // it is enabled in config, the user is currently logged in with OIDC, - // the current snappymail account is the OIDC account and no account defined explicitly if ($oAccount instanceof \RainLoop\Model\MainAccount - && \OCA\SnappyMail\Util\SnappyMailHelper::isOIDCLogin() -// && $oClient->supportsAuthType('OAUTHBEARER') // v2.28 - && \str_starts_with($oSettings->passphrase, 'oidc_login|') + && \OCA\SnappyMail\Util\SnappyMailHelper::isOIDCLogin() + && \str_starts_with($oSettings->passphrase, 'oidc_login|') ) { -// $oSettings->passphrase = \OC::$server->getSession()->get('snappymail-passphrase'); - $oSettings->passphrase = \OC::$server->getSession()->get('oidc_access_token'); + $oSettings->passphrase = (string) \OC::$server->getSession()->get('oidc_access_token'); \array_unshift($oSettings->SASLMechanisms, 'OAUTHBEARER'); } } - /* - \OC::$server->getCalendarManager(); - \OC::$server->getLDAPProvider(); - */ - + /** + * Attach a Nextcloud file into SnappyMail temp storage (for composing). + */ public function NextcloudAttachFile() : array { $aResult = [ 'success' => false, 'tempName' => '' ]; - $sFile = $this->jsonParam('file', ''); - $oFiles = \OCP\Files::getStorage('files'); - if ($oFiles && $oFiles->is_file($sFile) && $fp = $oFiles->fopen($sFile, 'rb')) { + + $sFile = (string) $this->jsonParam('file', ''); + + try { $oActions = \RainLoop\Api::Actions(); $oAccount = $oActions->getAccountFromToken(); - if ($oAccount) { - $sSavedName = 'nextcloud-file-' . \sha1($sFile . \microtime()); - if (!$oActions->FilesProvider()->PutFile($oAccount, $sSavedName, $fp)) { - $aResult['error'] = 'failed'; - } else { - $aResult['tempName'] = $sSavedName; - $aResult['success'] = true; - } + if (!$oAccount) { + $aResult['error'] = 'no-account'; + return $this->jsonResponse(__FUNCTION__, $aResult); } + + $user = \OC::$server->getUserSession()->getUser(); + if (!$user) { + $aResult['error'] = 'no-user'; + return $this->jsonResponse(__FUNCTION__, $aResult); + } + + /** @var \OCP\Files\IRootFolder $root */ + $root = \OC::$server->get(\OCP\Files\IRootFolder::class); + $userFolder = $root->getUserFolder($user->getUID()); + + // SnappyMail sends "/Documents/app.svg" style paths; userFolder expects relative. + $relPath = \ltrim($sFile, '/'); + + if ($relPath === '') { + $aResult['error'] = 'empty-path'; + return $this->jsonResponse(__FUNCTION__, $aResult); + } + + $node = $userFolder->get($relPath); + if (!($node instanceof \OCP\Files\File)) { + $aResult['error'] = 'not-a-file'; + return $this->jsonResponse(__FUNCTION__, $aResult); + } + + $fp = $node->fopen('rb'); + if (!$fp) { + $aResult['error'] = 'open-failed'; + return $this->jsonResponse(__FUNCTION__, $aResult); + } + + $sSavedName = 'nextcloud-file-' . \sha1($sFile . \microtime(true)); + $ok = $oActions->FilesProvider()->PutFile($oAccount, $sSavedName, $fp); + @\fclose($fp); + + if (!$ok) { + $aResult['error'] = 'failed'; + } else { + $aResult['tempName'] = $sSavedName; + $aResult['success'] = true; + } + } catch (\Throwable $e) { + $aResult['error'] = 'exception'; + $aResult['errorMessage'] = $e->getMessage(); } + return $this->jsonResponse(__FUNCTION__, $aResult); } + /** + * Save an .eml message from SnappyMail into Nextcloud Files. + */ public function NextcloudSaveMsg() : array { - $sSaveFolder = \ltrim($this->jsonParam('folder', ''), '/'); -// $aValues = \RainLoop\Api::Actions()->decodeRawKey($this->jsonParam('msgHash', '')); - $msgHash = $this->jsonParam('msgHash', ''); + $sSaveFolder = \ltrim((string) $this->jsonParam('folder', ''), '/'); + + $msgHash = (string) $this->jsonParam('msgHash', ''); $aValues = \json_decode(\MailSo\Base\Utils::UrlSafeBase64Decode($msgHash), true); + $aResult = [ 'folder' => '', 'filename' => '', - 'success' => false + 'success' => false, ]; - if ($sSaveFolder && !empty($aValues['folder']) && !empty($aValues['uid'])) { + + if (!empty($aValues['folder']) && !empty($aValues['uid'])) { $oActions = \RainLoop\Api::Actions(); $oMailClient = $oActions->MailClient(); + if (!$oMailClient->IsLoggined()) { $oAccount = $oActions->getAccountFromToken(); $oAccount->ImapConnectAndLogin($oActions->Plugins(), $oMailClient->ImapClient(), $oActions->Config()); } + // Default folder $sSaveFolder = $sSaveFolder ?: 'Emails'; - $oFiles = \OCP\Files::getStorage('files'); - if ($oFiles) { - $oFiles->is_dir($sSaveFolder) || $oFiles->mkdir($sSaveFolder); - } - $aResult['folder'] = $sSaveFolder; - $aResult['filename'] = \MailSo\Base\Utils::SecureFileName( - \mb_substr($this->jsonParam('filename', '') ?: \date('YmdHis'), 0, 100) - ) . '.' . \md5($msgHash) . '.eml'; + $sSaveFolder = \trim($sSaveFolder, '/'); + // Use Nextcloud Files API (per-user) + $userFolder = \OC::$server->getUserFolder(); + + // Create folder path (supports nested folders) + $folderNode = $this->ensureFolderPath($userFolder, $sSaveFolder); + + $filenameBase = (string) ($this->jsonParam('filename', '') ?: \date('YmdHis')); + $safeBase = \MailSo\Base\Utils::SecureFileName(\mb_substr($filenameBase, 0, 100)); + $filename = $safeBase . '.' . \md5($msgHash) . '.eml'; + + $aResult['folder'] = $sSaveFolder; + $aResult['filename'] = $filename; $oMailClient->MessageMimeStream( - function ($rResource) use ($oFiles, $aResult) { - if (\is_resource($rResource)) { - $aResult['success'] = $oFiles->file_put_contents("{$aResult['folder']}/{$aResult['filename']}", $rResource); + function ($rResource) use (&$aResult, $folderNode, $filename) { + if (!\is_resource($rResource)) { + return; + } + + // If exists, overwrite by deleting and recreating (keeps behavior deterministic) + if ($folderNode->nodeExists($filename)) { + $existing = $folderNode->get($filename); + if ($existing instanceof \OCP\Files\File) { + $existing->delete(); + } + } + + $fileNode = $folderNode->newFile($filename); + $out = $fileNode->fopen('w'); + if (\is_resource($out)) { + \stream_copy_to_stream($rResource, $out); + \fclose($out); + $aResult['success'] = true; } }, (string) $aValues['folder'], @@ -177,118 +233,148 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin return $this->jsonResponse(__FUNCTION__, $aResult); } + /** + * Save message attachments from SnappyMail into Nextcloud Files. + * (Used by SnappyMail "save attachments to nextcloud" actions.) + */ public function DoAttachmentsActions(\SnappyMail\AttachmentsAction $data) { - if (static::isLoggedIn() && 'nextcloud' === $data->action) { - $oFiles = \OCP\Files::getStorage('files'); - if ($oFiles && \method_exists($oFiles, 'file_put_contents')) { - $sSaveFolder = \ltrim($this->jsonParam('NcFolder', ''), '/'); - $sSaveFolder = $sSaveFolder ?: 'Attachments'; - $oFiles->is_dir($sSaveFolder) || $oFiles->mkdir($sSaveFolder); - $data->result = true; - foreach ($data->items as $aItem) { - $sSavedFileName = empty($aItem['fileName']) ? 'file.dat' : $aItem['fileName']; - if (!empty($aItem['data'])) { - $sSavedFileNameFull = static::SmartFileExists($sSaveFolder.'/'.$sSavedFileName, $oFiles); - if (!$oFiles->file_put_contents($sSavedFileNameFull, $aItem['data'])) { - $data->result = false; - } - } else if (!empty($aItem['fileHash'])) { - $fFile = $data->filesProvider->GetFile($data->account, $aItem['fileHash'], 'rb'); - if (\is_resource($fFile)) { - $sSavedFileNameFull = static::SmartFileExists($sSaveFolder.'/'.$sSavedFileName, $oFiles); - if (!$oFiles->file_put_contents($sSavedFileNameFull, $fFile)) { - $data->result = false; - } - if (\is_resource($fFile)) { - \fclose($fFile); - } - } + if (!static::isLoggedIn() || 'nextcloud' !== $data->action) { + return; + } + + try { + // Target folder in Nextcloud + $sSaveFolder = \ltrim((string) $this->jsonParam('NcFolder', ''), '/'); + $sSaveFolder = $sSaveFolder ?: 'Attachments'; + $sSaveFolder = \trim($sSaveFolder, '/'); + + $userFolder = \OC::$server->getUserFolder(); + $folderNode = $this->ensureFolderPath($userFolder, $sSaveFolder); + + $data->result = true; + + foreach ($data->items as $aItem) { + $sSavedFileName = empty($aItem['fileName']) ? 'file.dat' : (string) $aItem['fileName']; + $sSavedFileName = \MailSo\Base\Utils::SecureFileName(\mb_substr($sSavedFileName, 0, 180)); + + $finalName = $this->uniqueNameInFolder($folderNode, $sSavedFileName); + + // Create file node + $fileNode = $folderNode->newFile($finalName); + + $out = $fileNode->fopen('w'); + if (!\is_resource($out)) { + $data->result = false; + continue; + } + + $wrote = false; + + if (!empty($aItem['data'])) { + // Raw data string + $bytes = \fwrite($out, (string) $aItem['data']); + $wrote = ($bytes !== false); + } else if (!empty($aItem['fileHash'])) { + // Stream from SnappyMail temp storage + $fFile = $data->filesProvider->GetFile($data->account, (string) $aItem['fileHash'], 'rb'); + if (\is_resource($fFile)) { + \stream_copy_to_stream($fFile, $out); + \fclose($fFile); + $wrote = true; } } + + \fclose($out); + + if (!$wrote) { + // Clean up the empty file + try { $fileNode->delete(); } catch (\Throwable $e) {} + $data->result = false; + } } + } catch (\Throwable $e) { + $data->result = false; } } public function FilterAppData($bAdmin, &$aResult) : void { - if (!$bAdmin && \is_array($aResult)) { - $ocUser = \OC::$server->getUserSession()->getUser(); - $sUID = $ocUser->getUID(); - $oUrlGen = \OC::$server->getURLGenerator(); - $sWebDAV = $oUrlGen->getAbsoluteURL($oUrlGen->linkTo('', 'remote.php') . '/dav'); -// $sWebDAV = \OCP\Util::linkToRemote('dav'); - $aResult['Nextcloud'] = [ - 'UID' => $sUID, - 'WebDAV' => $sWebDAV, - 'CalDAV' => $this->Config()->Get('plugin', 'calendar', false) -// 'WebDAV_files' => $sWebDAV . '/files/' . $sUID - ]; - if (empty($aResult['Auth'])) { - $config = \OC::$server->getConfig(); - $sEmail = ''; - // Only store the user's password in the current session if they have - // enabled auto-login using Nextcloud username or email address. - if ($config->getAppValue('snappymail', 'snappymail-autologin', false)) { - $sEmail = $sUID; - } else if ($config->getAppValue('snappymail', 'snappymail-autologin-with-email', false)) { - $sEmail = $config->getUserValue($sUID, 'settings', 'email', ''); - } else { - \SnappyMail\Log::debug('Nextcloud', 'snappymail-autologin is off'); - } - // If the user has set credentials for SnappyMail in their personal - // settings, override everything before and use those instead. - $sCustomEmail = $config->getUserValue($sUID, 'snappymail', 'snappymail-email', ''); - if ($sCustomEmail) { - $sEmail = $sCustomEmail; - } - if (!$sEmail) { - $sEmail = $ocUser->getEMailAddress(); -// ?: $ocUser->getPrimaryEMailAddress(); - } -/* - if ($config->getAppValue('snappymail', 'snappymail-autologin-oidc', false)) { - if (\OC::$server->getSession()->get('is_oidc')) { - $sEmail = "{$sUID}@nextcloud"; - $aResult['DevPassword'] = \OC::$server->getSession()->get('oidc_access_token'); - } else { - \SnappyMail\Log::debug('Nextcloud', 'Not an OIDC login'); - } - } else { - \SnappyMail\Log::debug('Nextcloud', 'OIDC is off'); - } -*/ - $aResult['DevEmail'] = $sEmail ?: ''; - } else if (!empty($aResult['ContactsSync'])) { - $bSave = false; - if (empty($aResult['ContactsSync']['Url'])) { - $aResult['ContactsSync']['Url'] = "{$sWebDAV}/addressbooks/users/{$sUID}/contacts/"; + if ($bAdmin || !\is_array($aResult)) { + return; + } + + $ocUser = \OC::$server->getUserSession()->getUser(); + if (!$ocUser) { + return; + } + + $sUID = $ocUser->getUID(); + $oUrlGen = \OC::$server->getURLGenerator(); + $sWebDAV = $oUrlGen->getAbsoluteURL($oUrlGen->linkTo('', 'remote.php') . '/dav'); + + $aResult['Nextcloud'] = [ + 'UID' => $sUID, + 'WebDAV' => $sWebDAV, + 'CalDAV' => $this->Config()->Get('plugin', 'calendar', false) + ]; + + if (empty($aResult['Auth'])) { + $config = \OC::$server->getConfig(); + $sEmail = ''; + + if ($config->getAppValue('snappymail', 'snappymail-autologin', false)) { + $sEmail = $sUID; + } else if ($config->getAppValue('snappymail', 'snappymail-autologin-with-email', false)) { + $sEmail = $config->getUserValue($sUID, 'settings', 'email', ''); + } else { + \SnappyMail\Log::debug('Nextcloud', 'snappymail-autologin is off'); + } + + // User-set SnappyMail credentials override + $sCustomEmail = $config->getUserValue($sUID, 'snappymail', 'snappymail-email', ''); + if ($sCustomEmail) { + $sEmail = $sCustomEmail; + } + + if (!$sEmail) { + $sEmail = $ocUser->getEMailAddress(); + } + + $aResult['DevEmail'] = $sEmail ?: ''; + } else if (!empty($aResult['ContactsSync'])) { + $bSave = false; + + if (empty($aResult['ContactsSync']['Url'])) { + $aResult['ContactsSync']['Url'] = "{$sWebDAV}/addressbooks/users/{$sUID}/contacts/"; + $bSave = true; + } + if (empty($aResult['ContactsSync']['User'])) { + $aResult['ContactsSync']['User'] = $sUID; + $bSave = true; + } + + // FIX: do not array-access the session; use ->get() + $pass = (string) \OC::$server->getSession()->get('snappymail-passphrase'); + if ($pass) { + $pass = \SnappyMail\Crypt::DecryptUrlSafe($pass, $sUID); + if ($pass) { + $aResult['ContactsSync']['Password'] = $pass; $bSave = true; } - if (empty($aResult['ContactsSync']['User'])) { - $aResult['ContactsSync']['User'] = $sUID; - $bSave = true; - } - $pass = \OC::$server->getSession()['snappymail-passphrase']; - if ($pass/* && empty($aResult['ContactsSync']['Password'])*/) { - $pass = \SnappyMail\Crypt::DecryptUrlSafe($pass, $sUID); - if ($pass) { - $aResult['ContactsSync']['Password'] = $pass; - $bSave = true; - } - } - if ($bSave) { - $oActions = \RainLoop\Api::Actions(); - $oActions->setContactsSyncData( - $oActions->getAccountFromToken(), - array( - 'Mode' => $aResult['ContactsSync']['Mode'], - 'User' => $aResult['ContactsSync']['User'], - 'Password' => $aResult['ContactsSync']['Password'], - 'Url' => $aResult['ContactsSync']['Url'] - ) - ); - } + } + + if ($bSave) { + $oActions = \RainLoop\Api::Actions(); + $oActions->setContactsSyncData( + $oActions->getAccountFromToken(), + [ + 'Mode' => $aResult['ContactsSync']['Mode'], + 'User' => $aResult['ContactsSync']['User'], + 'Password' => $aResult['ContactsSync']['Password'], + 'Url' => $aResult['ContactsSync']['Url'] + ] + ); } } } @@ -301,116 +387,124 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin $userLang = \OC::$server->getConfig()->getUserValue($userId, 'core', 'lang', 'en'); $userLang = \strtr($userLang, '_', '-'); $sLanguage = $this->determineLocale($userLang, $aResultLang); - // Check if $sLanguage is null if (!$sLanguage) { - $sLanguage = 'en'; // Assign 'en' if $sLanguage is null + $sLanguage = 'en'; } } } - /** - * Determine locale from user language. - * - * @param string $langCode The name of the input. - * @param array $languagesArray The value of the array. - * - * @return string return locale - */ private function determineLocale(string $langCode, array $languagesArray) : ?string { - // Direct check for the language code - if (\in_array($langCode, $languagesArray)) { + if (\in_array($langCode, $languagesArray, true)) { return $langCode; } - // Check without country code if (\str_contains($langCode, '-')) { $langCode = \explode('-', $langCode)[0]; - if (\in_array($langCode, $languagesArray)) { + if (\in_array($langCode, $languagesArray, true)) { return $langCode; } } - // Check with uppercase country code $langCodeWithUpperCase = $langCode . '-' . \strtoupper($langCode); - if (\in_array($langCodeWithUpperCase, $languagesArray)) { + if (\in_array($langCodeWithUpperCase, $languagesArray, true)) { return $langCodeWithUpperCase; } - // If no match is found return null; } - /** - * @param mixed $mResult - */ public function MainFabrica(string $sName, &$mResult) { - if (static::isLoggedIn()) { - if ('suggestions' === $sName && $this->Config()->Get('plugin', 'suggestions', true)) { - if (!\is_array($mResult)) { - $mResult = array(); - } - include_once __DIR__ . '/NextcloudContactsSuggestions.php'; - $mResult[] = new NextcloudContactsSuggestions( - $this->Config()->Get('plugin', 'ignoreSystemAddressbook', true) - ); - } -/* - if ($this->Config()->Get('plugin', 'storage', false) && ('storage' === $sName || 'storage-local' === $sName)) { - require_once __DIR__ . '/storage.php'; - $oDriver = new \NextcloudStorage(APP_PRIVATE_DATA.'storage', $sName === 'storage-local'); - } -*/ + if (!static::isLoggedIn()) { + return; } + + if ('suggestions' === $sName && $this->Config()->Get('plugin', 'suggestions', true)) { + if (!\is_array($mResult)) { + $mResult = []; + } + include_once __DIR__ . '/NextcloudContactsSuggestions.php'; + $mResult[] = new NextcloudContactsSuggestions( + $this->Config()->Get('plugin', 'ignoreSystemAddressbook', true) + ); + } + + // storage hook left as upstream (commented) } protected function configMapping() : array { - return array( + return [ \RainLoop\Plugins\Property::NewInstance('suggestions')->SetLabel('Suggestions') ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) ->SetDefaultValue(true), + \RainLoop\Plugins\Property::NewInstance('ignoreSystemAddressbook')->SetLabel('Ignore system addressbook') ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) ->SetDefaultValue(true), -/* - \RainLoop\Plugins\Property::NewInstance('storage')->SetLabel('Use Nextcloud user ID in config storage path') - ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) - ->SetDefaultValue(false) -*/ + \RainLoop\Plugins\Property::NewInstance('calendar')->SetLabel('Enable "Put ICS in calendar"') ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) ->SetDefaultValue(false) - ); + ]; } - private static function SmartFileExists(string $sFilePath, $oFiles) : string + /** + * Ensure a (possibly nested) folder path exists under the given base folder. + */ + private function ensureFolderPath(\OCP\Files\Folder $baseFolder, string $path) : \OCP\Files\Folder { - $sFilePath = \str_replace('\\', '/', \trim($sFilePath)); - - if (!$oFiles->file_exists($sFilePath)) { - return $sFilePath; + $path = \trim($path, '/'); + if ($path === '') { + return $baseFolder; } - $aFileInfo = \pathinfo($sFilePath); - - $iIndex = 0; - - while (true) { - ++$iIndex; - $sFilePathNew = $aFileInfo['dirname'].'/'. - \preg_replace('/\(\d{1,2}\)$/', '', $aFileInfo['filename']). - ' ('.$iIndex.')'. - (empty($aFileInfo['extension']) ? '' : '.'.$aFileInfo['extension']) - ; - if (!$oFiles->file_exists($sFilePathNew)) { - return $sFilePathNew; - } - if (10 < $iIndex) { - break; + $folderNode = $baseFolder; + foreach (\array_filter(\explode('/', $path), 'strlen') as $part) { + $part = (string) $part; + if ($folderNode->nodeExists($part)) { + $node = $folderNode->get($part); + if ($node instanceof \OCP\Files\Folder) { + $folderNode = $node; + } else { + // name collision: file exists where folder needed; create a suffixed folder + $folderNode = $folderNode->newFolder($part . '-folder'); + } + } else { + $folderNode = $folderNode->newFolder($part); } } - return $sFilePath; + + return $folderNode; + } + + /** + * Return a unique filename inside a Nextcloud folder (keeps original extension). + */ + private function uniqueNameInFolder(\OCP\Files\Folder $folderNode, string $fileName) : string + { + $fileName = \trim($fileName); + if ($fileName === '') { + $fileName = 'file.dat'; + } + + if (!$folderNode->nodeExists($fileName)) { + return $fileName; + } + + $info = \pathinfo($fileName); + $base = $info['filename'] ?? 'file'; + $ext = isset($info['extension']) && $info['extension'] !== '' ? ('.' . $info['extension']) : ''; + + for ($i = 1; $i <= 50; $i++) { + $try = $base . ' (' . $i . ')' . $ext; + if (!$folderNode->nodeExists($try)) { + return $try; + } + } + + // fallback + return $fileName; } } diff --git a/snappymail/v/0.0.0/app/templates/Views/Admin/AdminSettingsAbout.html b/snappymail/v/0.0.0/app/templates/Views/Admin/AdminSettingsAbout.html index b83ea9345..749bb035b 100644 --- a/snappymail/v/0.0.0/app/templates/Views/Admin/AdminSettingsAbout.html +++ b/snappymail/v/0.0.0/app/templates/Views/Admin/AdminSettingsAbout.html @@ -40,9 +40,9 @@

- + - +
diff --git a/snappymail/v/0.0.0/app/templates/Views/Admin/AdminSettingsGeneral.html b/snappymail/v/0.0.0/app/templates/Views/Admin/AdminSettingsGeneral.html index e31f81108..e5b68f2bd 100644 --- a/snappymail/v/0.0.0/app/templates/Views/Admin/AdminSettingsGeneral.html +++ b/snappymail/v/0.0.0/app/templates/Views/Admin/AdminSettingsGeneral.html @@ -9,8 +9,8 @@
diff --git a/snappymail/v/0.0.0/app/templates/Views/Admin/PopupsDomain.html b/snappymail/v/0.0.0/app/templates/Views/Admin/PopupsDomain.html index b78891e18..86780f5c7 100644 --- a/snappymail/v/0.0.0/app/templates/Views/Admin/PopupsDomain.html +++ b/snappymail/v/0.0.0/app/templates/Views/Admin/PopupsDomain.html @@ -93,7 +93,7 @@

- +