This commit is contained in:
Nextgen-Networks 2026-02-20 13:27:32 +00:00 committed by GitHub
commit 61373faa97
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 714 additions and 549 deletions

View file

@ -31,7 +31,7 @@ If applicable, add screenshots to help explain your problem.
- Mode: [e.g. standalone, nextcloud, cyberpanel, docker] - Mode: [e.g. standalone, nextcloud, cyberpanel, docker]
**Debug/logging information** **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) - [ ] I've placed them here (few lines) or as attachments (many lines)
**Additional context** **Additional context**

View file

@ -21,7 +21,7 @@ jobs:
DOCKER_METADATA_PR_HEAD_SHA: 'true' DOCKER_METADATA_PR_HEAD_SHA: 'true'
with: with:
images: | images: |
djmaze/snappymail nextgennetworks/snappymail
ghcr.io/${{ github.repository }} ghcr.io/${{ github.repository }}
# type=ref,event=pr generates tag(s) on PRs only. E.g. 'pr-123', 'pr-123-abc0123' # type=ref,event=pr generates tag(s) on PRs only. E.g. 'pr-123', 'pr-123-abc0123'
tags: | tags: |

View file

@ -26,7 +26,7 @@ jobs:
DOCKER_METADATA_PR_HEAD_SHA: 'true' DOCKER_METADATA_PR_HEAD_SHA: 'true'
with: with:
images: | images: |
djmaze/snappymail nextgennetworks/snappymail
ghcr.io/${{ github.repository }} ghcr.io/${{ github.repository }}
# type=ref,event=branch generates tag(s) on branch only. E.g. 'master', 'master-abc0123' # 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' # type=ref,event=tag generates tag(s) on tags only. E.g. 'v0.0.0', 'v0.0.0-abc0123', and 'latest'

View file

@ -72,7 +72,7 @@
### Fixed ### Fixed
- mXSS exploit found by SonarSource - 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 - Call to a member function Email() on null
[#1706](https://github.com/the-djmaze/snappymail/issues/1706) [#1706](https://github.com/the-djmaze/snappymail/issues/1706)
- IMAP capabilities via IMAP Proxy - IMAP capabilities via IMAP Proxy
@ -202,7 +202,7 @@
### Added ### Added
- Customize private key passphrase expiration interval - 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 - AdvancedSearch support for filtering mails before a given date
[#1606](https://github.com/the-djmaze/snappymail/pull/1606) by @codiflow [#1606](https://github.com/the-djmaze/snappymail/pull/1606) by @codiflow
- Control valid spam and virus headers - Control valid spam and virus headers
@ -411,9 +411,9 @@
[#1458](https://github.com/the-djmaze/snappymail/issues/1458) [#1458](https://github.com/the-djmaze/snappymail/issues/1458)
- Automatically verify PGP and S/MIME signed messages - Automatically verify PGP and S/MIME signed messages
- TNEFDecoder for - 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 - 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 - Polyfill for PHP ctype
[#1250](https://github.com/the-djmaze/snappymail/issues/1250) [#1250](https://github.com/the-djmaze/snappymail/issues/1250)

View file

@ -11,7 +11,7 @@
2. Install node.js - `https://nodejs.org/download/` 2. Install node.js - `https://nodejs.org/download/`
3. Install yarn - `https://yarnpkg.com/en/docs/install` 3. Install yarn - `https://yarnpkg.com/en/docs/install`
4. Install gulp - `npm install gulp -g` 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` 6. Clone snappymail - `git clone git@github.com:USERNAME/snappymail.git snappymail`
7. `cd snappymail` 7. `cd snappymail`
8. Install all dependencies - `yarn install` 8. Install all dependencies - `yarn install`

View file

@ -6,7 +6,7 @@ pkgrel=1
pkgdesc="modern PHP webmail client" pkgdesc="modern PHP webmail client"
arch=('any') arch=('any')
license=('AGPL3') license=('AGPL3')
url="https://github.com/the-djmaze/snappymail" url="https://github.com/nextgen-networks/snappymail/"
depends=('php-fpm') depends=('php-fpm')
makedepends=('php', 'nodejs' 'yarn' 'gulp', 'rollup') makedepends=('php', 'nodejs' 'yarn' 'gulp', 'rollup')
optdepends=('mariadb: storage backend for contacts' optdepends=('mariadb: storage backend for contacts'

View file

@ -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 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 Architecture: all
Homepage: https://snappymail.eu Homepage: https://snappymail.eu
Vcs-Browser: https://github.com/the-djmaze/snappymail Vcs-Browser: https://github.com/nextgen-networks/snappymail/
Vcs-Git: https://github.com/the-djmaze/snappymail.git 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. 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. It supports IMAP, SMTP and Sieve protocols, multiple accounts and identities, an admin panel for configuration.
Plugins can be installed to further extend functionality. Plugins can be installed to further extend functionality.

View file

@ -207,7 +207,7 @@ if ($options['aur']) {
pkgdesc = modern PHP webmail client pkgdesc = modern PHP webmail client
pkgver = '.$package->version.' pkgver = '.$package->version.'
pkgrel = 1 pkgrel = 1
url = https://github.com/the-djmaze/snappymail url = https://github.com/nextgen-networks/snappymail/
arch = any arch = any
license = AGPL3 license = AGPL3
makedepends = php makedepends = php
@ -218,7 +218,7 @@ if ($options['aur']) {
optdepends = mariadb: storage backend for contacts optdepends = mariadb: storage backend for contacts
optdepends = php-pgsql: storage backend for contacts optdepends = php-pgsql: storage backend for contacts
optdepends = php-sqlite: 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.sysusers
source = snappymail.tmpfiles source = snappymail.tmpfiles
b2sums = '.implode("\n b2sums = ", $b2sums).' b2sums = '.implode("\n b2sums = ", $b2sums).'
@ -244,7 +244,7 @@ else if ($options['docker']) {
$zip_filename = "snappymail-{$package->version}.zip"; $zip_filename = "snappymail-{$package->version}.zip";
copy($zip_destination, "./.docker/release/{$zip_filename}"); copy($zip_destination, "./.docker/release/{$zip_filename}");
if ($docker) { 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 { } else {
echo "Docker not installed!\n"; echo "Docker not installed!\n";
} }

View file

@ -10,7 +10,7 @@ Content-Type: text/plain; charset=us-ascii; format=flowed
Test message body with example TNEF part from Test message body with example TNEF part from
https://github.com/Yeraze/ytnef https://github.com/Yeraze/ytnef
https://github.com/the-djmaze/snappymail/discussions/1012 https://github.com/nextgen-networks/snappymail/discussions/1012
--=-2UbcDuksJBAwUNBNW7UD --=-2UbcDuksJBAwUNBNW7UD
Content-Type: application/vnd.ms-tnef Content-Type: application/vnd.ms-tnef

View file

@ -5,7 +5,7 @@ WORKDIR /app/code
# If you change the extraction below, be sure to test on scaleway # If you change the extraction below, be sure to test on scaleway
VERSION=2.38.2 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 && \ unzip /tmp/snappymail.zip -d /app/code && \
rm /tmp/snappymail.zip && \ rm /tmp/snappymail.zip && \
find /app/code/snappymail -type d -exec chmod 755 {} \; && \ find /app/code/snappymail -type d -exec chmod 755 {} \; && \

View file

@ -25,19 +25,19 @@ There, click on the link to go to the SnappyMail admin panel.
]]></description> ]]></description>
<namespace>SnappyMail</namespace> <namespace>SnappyMail</namespace>
<documentation> <documentation>
<user>https://github.com/the-djmaze/snappymail/blob/master/integrations/nextcloud/snappymail/README.md</user> <user>https://github.com/nextgen-networks/snappymail/blob/master/integrations/nextcloud/snappymail/README.md</user>
<admin>https://github.com/the-djmaze/snappymail/wiki/Admin-Manual</admin> <admin>https://github.com/nextgen-networks/snappymail/wiki/Admin-Manual</admin>
<developer>https://github.com/the-djmaze/snappymail/wiki/Developer-Documentation</developer> <developer>https://github.com/nextgen-networks/snappymail/wiki/Developer-Documentation</developer>
<website>https://github.com/the-djmaze/snappymail/wiki</website> <website>https://github.com/nextgen-networks/snappymail/wiki</website>
</documentation> </documentation>
<category>integration</category> <category>integration</category>
<category>office</category> <category>office</category>
<category>search</category> <category>search</category>
<category>social</category> <category>social</category>
<screenshot>https://raw.githubusercontent.com/the-djmaze/snappymail/master/integrations/nextcloud/screenshots/inbox.jpg</screenshot> <screenshot>https://raw.githubusercontent.com/nextgen-networks/snappymail/master/integrations/nextcloud/screenshots/inbox.jpg</screenshot>
<website>https://snappymail.eu</website> <website>https://snappymail.eu</website>
<discussion>https://github.com/the-djmaze/snappymail/discussions</discussion> <discussion>https://github.com/nextgen-networks/snappymail/discussions</discussion>
<repository>https://github.com/the-djmaze/snappymail/tree/master/integrations/nextcloud</repository> <repository>https://github.com/nextgen-networks/snappymail/tree/master/integrations/nextcloud</repository>
<bugs>https://github.com/the-djmaze/snappymail/issues</bugs> <bugs>https://github.com/the-djmaze/snappymail/issues</bugs>
<dependencies> <dependencies>
<php min-version="7.4" min-int-size="32" /> <php min-version="7.4" min-int-size="32" />
@ -64,7 +64,7 @@ There, click on the link to go to the SnappyMail admin panel.
<lib>xxtea</lib> <lib>xxtea</lib>
<lib>zip</lib> <lib>zip</lib>
--> -->
<nextcloud min-version="20" max-version="32" /> <nextcloud min-version="25" max-version="33" />
</dependencies> </dependencies>
<settings> <settings>
<admin>OCA\SnappyMail\Settings\AdminSettings</admin> <admin>OCA\SnappyMail\Settings\AdminSettings</admin>

View file

@ -5,7 +5,6 @@ namespace OCA\SnappyMail\AppInfo;
use OCA\SnappyMail\Util\SnappyMailHelper; use OCA\SnappyMail\Util\SnappyMailHelper;
use OCA\SnappyMail\Controller\FetchController; use OCA\SnappyMail\Controller\FetchController;
use OCA\SnappyMail\Controller\PageController; use OCA\SnappyMail\Controller\PageController;
use OCA\SnappyMail\Dashboard\UnreadMailWidget;
use OCA\SnappyMail\Search\Provider; use OCA\SnappyMail\Search\Provider;
use OCA\SnappyMail\Listeners\AccessTokenUpdatedListener; use OCA\SnappyMail\Listeners\AccessTokenUpdatedListener;
@ -18,6 +17,10 @@ use OCP\IUser;
use OCP\User\Events\PostLoginEvent; use OCP\User\Events\PostLoginEvent;
use OCP\User\Events\BeforeUserLoggedOutEvent; use OCP\User\Events\BeforeUserLoggedOutEvent;
use OCA\OIDCLogin\Events\AccessTokenUpdatedEvent; use OCA\OIDCLogin\Events\AccessTokenUpdatedEvent;
use OCP\IConfig;
use OCP\ISession;
use OCP\IUserSession;
use OCP\INavigationManager;
class Application extends App implements IBootstrap class Application extends App implements IBootstrap
{ {
@ -37,7 +40,9 @@ class Application extends App implements IBootstrap
'PageController', function($c) { 'PageController', function($c) {
return new PageController( return new PageController(
$c->query('AppName'), $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( return new FetchController(
$c->query('AppName'), $c->query('AppName'),
$c->query('Request'), $c->query('Request'),
$c->getServer()->getAppManager(), $c->query('ServerContainer')->getAppManager(),
$c->query('ServerContainer')->getConfig(), $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 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; return;
} }
$dispatcher = $context->getAppContainer()->query('OCP\EventDispatcher\IEventDispatcher'); $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 // Only store the user's password in the current session if they have
// enabled auto-login using Nextcloud username or email address. // enabled auto-login using Nextcloud username or email address.
if ($config->getAppValue('snappymail', 'snappymail-autologin', false) if ($config->getAppValue('snappymail', 'snappymail-autologin', false)
|| $config->getAppValue('snappymail', 'snappymail-autologin-with-email', false)) { || $config->getAppValue('snappymail', 'snappymail-autologin-with-email', false)) {
*/ */
$sUID = $Event->getUser()->getUID(); $sUID = $Event->getUser()->getUID();
\OC::$server->getSession()['snappymail-nc-uid'] = $sUID; $session = $context->getServerContainer()->getSession();
\OC::$server->getSession()['snappymail-passphrase'] = SnappyMailHelper::encodePassword($Event->getPassword(), $sUID); $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 // https://github.com/nextcloud/impersonate/pull/180
$class = 'OCA\Impersonate\Events\BeginImpersonateEvent'; $class = 'OCA\Impersonate\Events\BeginImpersonateEvent';
if (\class_exists($class)) { if (\class_exists($class)) {
$dispatcher->addListener($class, function ($Event) { $dispatcher->addListener($class, function ($Event) use ($context) {
\OC::$server->getSession()['snappymail-passphrase'] = ''; $context->getServerContainer()->getSession()['snappymail-passphrase'] = '';
SnappyMailHelper::loadApp(); SnappyMailHelper::loadApp();
\RainLoop\Api::Actions()->Logout(true); \RainLoop\Api::Actions()->Logout(true);
}); });
$dispatcher->addListener('OCA\Impersonate\Events\EndImpersonateEvent', function ($Event) { $dispatcher->addListener('OCA\Impersonate\Events\EndImpersonateEvent', function ($Event) use ($context) {
\OC::$server->getSession()['snappymail-passphrase'] = ''; $context->getServerContainer()->getSession()['snappymail-passphrase'] = '';
SnappyMailHelper::loadApp(); SnappyMailHelper::loadApp();
\RainLoop\Api::Actions()->Logout(true); \RainLoop\Api::Actions()->Logout(true);
}); });

View file

@ -3,14 +3,14 @@
namespace OCA\SnappyMail\Command; namespace OCA\SnappyMail\Command;
use OCA\SnappyMail\Util\SnappyMailHelper; use OCA\SnappyMail\Util\SnappyMailHelper;
use OC\Core\Command\Base;
use OCP\IConfig; use OCP\IConfig;
use OCP\IUserManager; use OCP\IUserManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
class Settings extends Base class Settings extends Command
{ {
protected IUserManager $userManager; protected IUserManager $userManager;
protected IConfig $config; protected IConfig $config;

View file

@ -2,6 +2,9 @@
namespace OCA\SnappyMail; namespace OCA\SnappyMail;
use OCP\Server;
use OCP\Security\IContentSecurityPolicyNonceManager;
class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy { class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy {
/** @var bool Whether inline JS snippets are allowed */ /** @var bool Whether inline JS snippets are allowed */
@ -37,7 +40,7 @@ class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy
public function getSnappyMailNonce() { public function getSnappyMailNonce() {
static $sNonce; static $sNonce;
if (!$sNonce) { if (!$sNonce) {
$cspManager = \OC::$server->getContentSecurityPolicyNonceManager(); $cspManager = Server::get(IContentSecurityPolicyNonceManager::class);
$sNonce = $cspManager->getNonce() ?: \SnappyMail\UUID::generate(); $sNonce = $cspManager->getNonce() ?: \SnappyMail\UUID::generate();
if (\method_exists($cspManager, 'browserSupportsCspV3') && !$cspManager->browserSupportsCspV3()) { if (\method_exists($cspManager, 'browserSupportsCspV3') && !$cspManager->browserSupportsCspV3()) {
$this->addAllowedScriptDomain("'nonce-{$sNonce}'"); $this->addAllowedScriptDomain("'nonce-{$sNonce}'");

View file

@ -10,17 +10,21 @@ use OCP\AppFramework\Http\JSONResponse;
use OCP\IConfig; use OCP\IConfig;
use OCP\IL10N; use OCP\IL10N;
use OCP\IRequest; use OCP\IRequest;
use OCP\IUserSession;
use Exception;
class FetchController extends Controller { class FetchController extends Controller {
private IConfig $config; private IConfig $config;
private IAppManager $appManager; private IAppManager $appManager;
private IL10N $l; 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); parent::__construct($appName, $request);
$this->config = $config; $this->config = $config;
$this->appManager = $appManager; $this->appManager = $appManager;
$this->l = $l; $this->l = $l;
$this->userSession = $userSession;
} }
public function upgrade(): JSONResponse { public function upgrade(): JSONResponse {
@ -104,8 +108,10 @@ class FetchController extends Controller {
try { try {
$sEmail = ''; $sEmail = '';
if (isset($_POST['appname'], $_POST['snappymail-password'], $_POST['snappymail-email']) && 'snappymail' === $_POST['appname']) { 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() : '';
if ($sUser) {
$sEmail = $_POST['snappymail-email']; $sEmail = $_POST['snappymail-email'];
$this->config->setUserValue($sUser, 'snappymail', 'snappymail-email', $sEmail); $this->config->setUserValue($sUser, 'snappymail', 'snappymail-email', $sEmail);
@ -114,6 +120,7 @@ class FetchController extends Controller {
$this->config->setUserValue($sUser, 'snappymail', 'passphrase', $this->config->setUserValue($sUser, 'snappymail', 'passphrase',
$sPass ? SnappyMailHelper::encodePassword($sPass, \md5($sEmail)) : ''); $sPass ? SnappyMailHelper::encodePassword($sPass, \md5($sEmail)) : '');
} }
}
} else { } else {
return new JSONResponse([ return new JSONResponse([
'status' => 'error', 'status' => 'error',
@ -144,4 +151,3 @@ class FetchController extends Controller {
} }
} }
} }

View file

@ -7,17 +7,27 @@ use OCA\SnappyMail\ContentSecurityPolicy;
use OCP\AppFramework\Controller; use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Http\TemplateResponse;
use OCP\INavigationManager;
use OCP\IConfig;
use OCP\IRequest;
class PageController extends Controller 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 * @NoAdminRequired
* @NoCSRFRequired * @NoCSRFRequired
*/ */
public function index() public function index()
{ {
$config = \OC::$server->getConfig();
$bAdmin = false; $bAdmin = false;
if (!empty($_SERVER['QUERY_STRING'])) { if (!empty($_SERVER['QUERY_STRING'])) {
SnappyMailHelper::loadApp(); SnappyMailHelper::loadApp();
@ -27,10 +37,8 @@ class PageController extends Controller
} }
} }
if (!$bAdmin && $config->getAppValue('snappymail', 'snappymail-no-embed')) { if (!$bAdmin && $this->config->getAppValue('snappymail', 'snappymail-no-embed')) {
\OC::$server->getNavigationManager()->setActiveEntry('snappymail'); $this->navigationManager->setActiveEntry('snappymail');
\OCP\Util::addScript('snappymail', 'snappymail');
\OCP\Util::addStyle('snappymail', 'style');
SnappyMailHelper::startApp(); SnappyMailHelper::startApp();
$response = new TemplateResponse('snappymail', 'index', [ $response = new TemplateResponse('snappymail', 'index', [
'snappymail-iframe-url' => SnappyMailHelper::normalizeUrl(SnappyMailHelper::getAppUrl()) 'snappymail-iframe-url' => SnappyMailHelper::normalizeUrl(SnappyMailHelper::getAppUrl())
@ -43,9 +51,7 @@ class PageController extends Controller
return $response; return $response;
} }
\OC::$server->getNavigationManager()->setActiveEntry('snappymail'); $this->navigationManager->setActiveEntry('snappymail');
\OCP\Util::addStyle('snappymail', 'embed');
SnappyMailHelper::startApp(); SnappyMailHelper::startApp();
$oConfig = \RainLoop\Api::Config(); $oConfig = \RainLoop\Api::Config();
@ -59,6 +65,8 @@ class PageController extends Controller
$csp = new ContentSecurityPolicy(); $csp = new ContentSecurityPolicy();
$sNonce = $csp->getSnappyMailNonce(); $sNonce = $csp->getSnappyMailNonce();
$cssLink = \RainLoop\Utils::WebStaticPath('css/'.($bAdmin?'admin':'app').$sAppCssMin.'.css');
$params = [ $params = [
'Admin' => $bAdmin ? 1 : 0, 'Admin' => $bAdmin ? 1 : 0,
'LoadingDescriptionEsc' => \htmlspecialchars($oConfig->Get('webmail', 'loading_description', 'SnappyMail'), ENT_QUOTES|ENT_IGNORE, 'UTF-8'), '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', '/\\s*([:;{},]+)\\s*/s',
'$1', '$1',
$oActions->compileCss($oActions->GetTheme($bAdmin), $bAdmin) $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 = new TemplateResponse('snappymail', 'index_embed', $params);
$response->setContentSecurityPolicy($csp); $response->setContentSecurityPolicy($csp);

View file

@ -6,7 +6,8 @@ use OCA\SnappyMail\AppInfo\Application;
use OCP\IConfig; use OCP\IConfig;
use OCP\Migration\IOutput; use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep; use OCP\Migration\IRepairStep;
use OCP\ILogger; use Psr\Log\LoggerInterface;
use OCP\App\IAppManager;
/** /**
* Note: this is not run at install, but when: * Note: this is not run at install, but when:
@ -15,6 +16,16 @@ use OCP\ILogger;
*/ */
class InstallStep implements IRepairStep 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() { public function getName() {
return 'Setup SnappyMail'; return 'Setup SnappyMail';
} }
@ -53,7 +64,7 @@ class InstallStep implements IRepairStep
if (!$oConfig->Get('webmail', 'app_path')) { if (!$oConfig->Get('webmail', 'app_path')) {
$output->info('Set config [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('webmail', 'allow_languages_on_settings', false);
$oConfig->Set('login', 'allow_languages_on_login', false); $oConfig->Set('login', 'allow_languages_on_login', false);
$bSave = true; $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" // ex: php occ config:app:set snappymail custom_config_file --value="/path/to/config.php"
// https://github.com/the-djmaze/snappymail/pull/1197 // https://github.com/the-djmaze/snappymail/pull/1197
try { try {
/** @var IConfig $ncConfig */ $customConfigFile = $this->config->getAppValue(Application::APP_ID, 'custom_config_file');
$ncConfig = \OC::$server->get(IConfig::class);
$customConfigFile = $ncConfig->getAppValue(Application::APP_ID, 'custom_config_file');
if ($customConfigFile) { if ($customConfigFile) {
$output->info("Load custom config: {$customConfigFile}"); $output->info("Load custom config: {$customConfigFile}");
if (!\str_contains($customConfigFile, ':') && \is_readable($customConfigFile)) { if (!\str_contains($customConfigFile, ':') && \is_readable($customConfigFile)) {
@ -129,9 +138,7 @@ class InstallStep implements IRepairStep
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
$output->warning("custom config error: " . $e->getMessage()); $output->warning("custom config error: " . $e->getMessage());
/** @var \Psr\Log\LoggerInterface $logger */ $this->logger->error("custom config error: " . $e->getMessage());
$logger = \OC::$server->get(\Psr\Log\LoggerInterface::class);
$logger->error("custom config error: " . $e->getMessage());
} }
} }
} }

View file

@ -5,14 +5,32 @@ use OCA\SnappyMail\Util\SnappyMailHelper;
use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig; use OCP\IConfig;
use OCP\Settings\ISettings; use OCP\Settings\ISettings;
use OCP\IUserSession;
use OCP\IGroupManager;
use OCP\IURLGenerator;
use OCP\App\IAppManager;
use OCP\Util;
class AdminSettings implements ISettings 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->config = $config;
$this->userSession = $userSession;
$this->groupManager = $groupManager;
$this->urlGenerator = $urlGenerator;
$this->appManager = $appManager;
} }
public function getForm() public function getForm()
@ -30,12 +48,15 @@ class AdminSettings implements ISettings
$v = $this->config->getAppValue('snappymail', $k); $v = $this->config->getAppValue('snappymail', $k);
$parameters[$k] = $v; $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'; // $parameters['snappymail-admin-panel-link'] = SnappyMailHelper::getAppUrl().'?admin';
SnappyMailHelper::loadApp(); SnappyMailHelper::loadApp();
$parameters['snappymail-admin-panel-link'] = $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'); . '?' . \RainLoop\Api::Config()->Get('security', 'admin_panel_key', 'admin');
} }
@ -49,7 +70,7 @@ class AdminSettings implements ISettings
$parameters['snappymail-admin-password'] = $sPassword; $parameters['snappymail-admin-password'] = $sPassword;
$parameters['can-import-rainloop'] = $sPassword && \is_dir( $parameters['can-import-rainloop'] = $sPassword && \is_dir(
\rtrim(\trim(\OC::$server->getSystemConfig()->getValue('datadirectory', '')), '\\/') \rtrim(\trim($this->config->getSystemValue('datadirectory', '')), '\\/')
. '/rainloop-storage' . '/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" // Prevent "Failed loading /nextcloud/snappymail/v/2.N.N/static/js/min/libs.min.js"
$app_path = $oConfig->Get('webmail', 'app_path'); $app_path = $oConfig->Get('webmail', 'app_path');
if (!$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', 'app_path', $app_path);
$oConfig->Set('webmail', 'theme', 'NextcloudV25+'); $oConfig->Set('webmail', 'theme', 'NextcloudV25+');
$oConfig->Save(); $oConfig->Save();
@ -73,7 +94,6 @@ class AdminSettings implements ISettings
$parameters['snappymail-app_path'] = $oConfig->Get('webmail', 'app_path', false); $parameters['snappymail-app_path'] = $oConfig->Get('webmail', 'app_path', false);
$parameters['snappymail-nc-lang'] = !$oConfig->Get('webmail', 'allow_languages_on_settings', true); $parameters['snappymail-nc-lang'] = !$oConfig->Get('webmail', 'allow_languages_on_settings', true);
\OCP\Util::addScript('snappymail', 'snappymail');
return new TemplateResponse('snappymail', 'admin-local', $parameters); return new TemplateResponse('snappymail', 'admin-local', $parameters);
} }

View file

@ -2,6 +2,14 @@
namespace OCA\SnappyMail\Util; namespace OCA\SnappyMail\Util;
use OCP\Server;
use OCP\IUserSession;
use OCP\IConfig;
use OCP\IURLGenerator;
use OCP\App\IAppManager;
use OCP\ISession;
use OCP\IGroupManager;
class SnappyMailResponse extends \OCP\AppFramework\Http\Response class SnappyMailResponse extends \OCP\AppFramework\Http\Response
{ {
public function render(): string public function render(): string
@ -56,8 +64,10 @@ class SnappyMailHelper
try { try {
$oActions = \RainLoop\Api::Actions(); $oActions = \RainLoop\Api::Actions();
if (isset($_GET[$oConfig->Get('security', 'admin_panel_key', 'admin')])) { if (isset($_GET[$oConfig->Get('security', 'admin_panel_key', 'admin')])) {
$user = Server::get(IUserSession::class)->getUser();
if ($oConfig->Get('security', 'allow_admin_panel', true) if ($oConfig->Get('security', 'allow_admin_panel', true)
&& \OC_User::isAdminUser(\OC::$server->getUserSession()->getUser()->getUID()) && $user
&& Server::get(IGroupManager::class)->isAdmin($user->getUID())
&& !$oActions->IsAdminLoggined(false) && !$oActions->IsAdminLoggined(false)
) { ) {
$sRand = \MailSo\Base\Utils::Sha1Rand(); $sRand = \MailSo\Base\Utils::Sha1Rand();
@ -79,7 +89,7 @@ class SnappyMailHelper
// file_get_contents(\OC::$SERVERROOT . 'config/config.php'); // file_get_contents(\OC::$SERVERROOT . 'config/config.php');
// $CONFIG['version'] // $CONFIG['version']
if (24 < $OC_Version[0]) { if (24 < $OC_Version[0]) {
$ocSession = \OC::$server->getSession(); $ocSession = Server::get(ISession::class);
$ocSession->reopen(); $ocSession->reopen();
if (!$doLogin && $ocSession['snappymail-uid'] && $ocSession['snappymail-uid'] != $aCredentials[0]) { if (!$doLogin && $ocSession['snappymail-uid'] && $ocSession['snappymail-uid'] != $aCredentials[0]) {
// UID changed, Impersonate plugin probably active // UID changed, Impersonate plugin probably active
@ -92,7 +102,7 @@ class SnappyMailHelper
if ($doLogin && $aCredentials[1] && $aCredentials[2]) { if ($doLogin && $aCredentials[1] && $aCredentials[2]) {
$isOIDC = \str_starts_with($aCredentials[2], 'oidc_login|'); $isOIDC = \str_starts_with($aCredentials[2], 'oidc_login|');
try { try {
$ocSession = \OC::$server->getSession(); $ocSession = Server::get(ISession::class);
$oAccount = $oActions->LoginProcess($aCredentials[1], $aCredentials[2]); $oAccount = $oActions->LoginProcess($aCredentials[1], $aCredentials[2]);
if (!$isOIDC && $oAccount if (!$isOIDC && $oAccount
&& $oConfig->Get('login', 'sign_me_auto', \RainLoop\Enumerations\SignMeType::DefaultOff) === \RainLoop\Enumerations\SignMeType::DefaultOn && $oConfig->Get('login', 'sign_me_auto', \RainLoop\Enumerations\SignMeType::DefaultOff) === \RainLoop\Enumerations\SignMeType::DefaultOn
@ -102,14 +112,18 @@ class SnappyMailHelper
} catch (\Throwable $e) { } catch (\Throwable $e) {
// Login failure, reset password to prevent more attempts // Login failure, reset password to prevent more attempts
if (!$isOIDC) { if (!$isOIDC) {
$sUID = \OC::$server->getUserSession()->getUser()->getUID(); $user = Server::get(IUserSession::class)->getUser();
\OC::$server->getSession()['snappymail-passphrase'] = ''; $sUID = $user ? $user->getUID() : null;
\OC::$server->getConfig()->setUserValue($sUID, 'snappymail', 'passphrase', ''); if ($sUID) {
$session = Server::get(ISession::class);
$session['snappymail-passphrase'] = '';
Server::get(IConfig::class)->setUserValue($sUID, 'snappymail', 'passphrase', '');
\SnappyMail\Log::error('Nextcloud', $e->getMessage()); \SnappyMail\Log::error('Nextcloud', $e->getMessage());
} }
} }
} }
} }
}
if ($handle) { if ($handle) {
\header_remove('Content-Security-Policy'); \header_remove('Content-Security-Policy');
@ -127,12 +141,12 @@ class SnappyMailHelper
// https://apps.nextcloud.com/apps/oidc_login // https://apps.nextcloud.com/apps/oidc_login
public static function isOIDCLogin() : bool public static function isOIDCLogin() : bool
{ {
$config = \OC::$server->getConfig(); $config = Server::get(IConfig::class);
if ($config->getAppValue('snappymail', 'snappymail-autologin-oidc', false)) { if ($config->getAppValue('snappymail', 'snappymail-autologin-oidc', false)) {
// Check if the OIDC Login app is enabled // Check if the OIDC Login app is enabled
if (\OC::$server->getAppManager()->isEnabledForUser('oidc_login')) { if (Server::get(IAppManager::class)->isEnabledForUser('oidc_login')) {
// Check if session is an OIDC Login // Check if session is an OIDC Login
$ocSession = \OC::$server->getSession(); $ocSession = Server::get(ISession::class);
if ($ocSession->get('is_oidc')) { if ($ocSession->get('is_oidc')) {
// IToken->getPassword() ??? // IToken->getPassword() ???
if ($ocSession->get('oidc_access_token')) { if ($ocSession->get('oidc_access_token')) {
@ -151,9 +165,14 @@ class SnappyMailHelper
private static function getLoginCredentials() : array private static function getLoginCredentials() : array
{ {
$sUID = \OC::$server->getUserSession()->getUser()->getUID(); $user = Server::get(IUserSession::class)->getUser();
$config = \OC::$server->getConfig(); $sUID = $user ? $user->getUID() : '';
$ocSession = \OC::$server->getSession(); $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, // If the user has set credentials for SnappyMail in their personal settings,
// this has the first priority. // this has the first priority.
@ -171,6 +190,7 @@ class SnappyMailHelper
// If the current user ID is identical to login ID (not valid when using account switching), // If the current user ID is identical to login ID (not valid when using account switching),
// this has the second priority. // this has the second priority.
// Note: $ocSession array access is supported but deprecated? ISession implements ArrayAccess.
if ($ocSession['snappymail-nc-uid'] == $sUID) { if ($ocSession['snappymail-nc-uid'] == $sUID) {
// If OpenID Connect (OIDC) is enabled and used for login, use this. // If OpenID Connect (OIDC) is enabled and used for login, use this.
@ -204,7 +224,7 @@ class SnappyMailHelper
public static function getAppUrl() : string public static function getAppUrl() : string
{ {
return \OC::$server->getURLGenerator()->linkToRoute('snappymail.page.appGet'); return Server::get(IURLGenerator::class)->linkToRoute('snappymail.page.appGet');
} }
public static function normalizeUrl(string $sUrl) : string public static function normalizeUrl(string $sUrl) : string

View file

@ -1,3 +1,4 @@
<?php script('snappymail', 'snappymail'); ?>
<div class="section"> <div class="section">
<form class="snappymail" action="admin.php" method="post"> <form class="snappymail" action="admin.php" method="post">
<input type="hidden" name="requesttoken" value="<?php echo $_['requesttoken'] ?>" id="requesttoken"> <input type="hidden" name="requesttoken" value="<?php echo $_['requesttoken'] ?>" id="requesttoken">

View file

@ -1,3 +1,5 @@
<iframe id="rliframe" style="border: none; width: 100%; min-height: 100%; position: relative;" tabindex="-1" frameborder="0" src="<?php echo $_['snappymail-iframe-url']; ?>"></iframe>
<?php <?php
// OCP\Util::addScript('snappymail', 'resize'); script('snappymail', 'snappymail');
style('snappymail', 'style');
?>
<iframe id="rliframe" style="border: none; width: 100%; min-height: 100%; position: relative;" tabindex="-1" frameborder="0" src="<?php echo $_['snappymail-iframe-url']; ?>"></iframe>

View file

@ -1,3 +1,5 @@
<?php style('snappymail', 'embed'); ?>
<link type="text/css" rel="stylesheet" href="<?php echo $_['CssLink']; ?>" />
<style id="app-boot-css"><?php echo $_['BaseAppBootCss']; ?></style> <style id="app-boot-css"><?php echo $_['BaseAppBootCss']; ?></style>
<style id="app-theme-style"><?php echo $_['BaseAppThemeCss']; ?></style> <style id="app-theme-style"><?php echo $_['BaseAppThemeCss']; ?></style>
<div id="rl-app" data-admin="<?php echo $_['Admin']; ?>" spellcheck="false"> <div id="rl-app" data-admin="<?php echo $_['Admin']; ?>" spellcheck="false">

View file

@ -22,17 +22,17 @@ There, click on the link to go to the SnappyMail admin panel.
]]></description> ]]></description>
<namespace>SnappyMail</namespace> <namespace>SnappyMail</namespace>
<documentation> <documentation>
<user>https://github.com/the-djmaze/snappymail/blob/master/integrations/owncloud/snappymail/README.md</user> <user>https://github.com/nextgen-networks/snappymail/blob/master/integrations/owncloud/snappymail/README.md</user>
<website>https://github.com/the-djmaze/snappymail/wiki</website> <website>https://github.com/nextgen-networks/snappymail/wiki</website>
</documentation> </documentation>
<category>integration</category> <category>integration</category>
<category>office</category> <category>office</category>
<category>search</category> <category>search</category>
<category>social</category> <category>social</category>
<screenshot>https://raw.githubusercontent.com/the-djmaze/snappymail/master/integrations/owncloud/screenshots/inbox.jpg</screenshot> <screenshot>https://raw.githubusercontent.com/nextgen-networks/snappymail/master/integrations/owncloud/screenshots/inbox.jpg</screenshot>
<website>https://snappymail.eu</website> <website>https://snappymail.eu</website>
<discussion>https://github.com/the-djmaze/snappymail/discussions</discussion> <discussion>https://github.com/nextgen-networks/snappymail/discussions</discussion>
<repository>https://github.com/the-djmaze/snappymail/tree/master/integrations/owncloud</repository> <repository>https://github.com/nextgen-networks/snappymail/tree/master/integrations/owncloud</repository>
<bugs>https://github.com/the-djmaze/snappymail/issues</bugs> <bugs>https://github.com/the-djmaze/snappymail/issues</bugs>
<dependencies> <dependencies>
<php min-version="7.4" min-int-size="64" /> <php min-version="7.4" min-int-size="64" />

View file

@ -153,7 +153,7 @@ sub script_snappymail_files
local ($d, $ver, $opts, $upgrade) = @_; local ($d, $ver, $opts, $upgrade) = @_;
local @files = ( { 'name' => "source", local @files = ( { 'name' => "source",
'file' => "snappymail-$ver.tar.gz", '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; return @files;
} }

View file

@ -12,7 +12,7 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/the-djmaze/snappymail.git" "url": "git://github.com/nextgen-networks/snappymail.git"
}, },
"scripts": { "scripts": {
"watch-css": "gulp watchCss" "watch-css": "gulp watchCss"

View file

@ -7,7 +7,7 @@
* be set to to 'example.com'. * be set to to 'example.com'.
* *
* Based on: * 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
* *
*/ */

View file

@ -148,7 +148,7 @@ class LoginGMailPlugin extends \RainLoop\Plugins\AbstractPlugin
->SetLabel('Client ID') ->SetLabel('Client ID')
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING) ->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING)
->SetAllowedInJs() ->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') \RainLoop\Plugins\Property::NewInstance('client_secret')
->SetLabel('Client Secret') ->SetLabel('Client Secret')
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING) ->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING)

View file

@ -166,7 +166,7 @@ class LoginO365Plugin extends \RainLoop\Plugins\AbstractPlugin
->SetLabel('Client ID') ->SetLabel('Client ID')
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING) ->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING)
->SetAllowedInJs() ->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') \RainLoop\Plugins\Property::NewInstance('client_secret')
->SetLabel('Client Secret') ->SetLabel('Client Secret')
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING) ->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING)

View file

@ -4,8 +4,9 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin
{ {
const const
NAME = 'Nextcloud', NAME = 'Nextcloud',
VERSION = '2.38.1', // Keep upstream metadata if you prefer; this is not functional.
RELEASE = '2024-10-08', VERSION = '2.38.2',
RELEASE = '2026-02-06',
CATEGORY = 'Integrations', CATEGORY = 'Integrations',
DESCRIPTION = 'Integrate with Nextcloud v20+', DESCRIPTION = 'Integrate with Nextcloud v20+',
REQUIRED = '2.38.0'; REQUIRED = '2.38.0';
@ -43,7 +44,6 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin
$this->addHook('sieve.before-login', 'beforeLogin'); $this->addHook('sieve.before-login', 'beforeLogin');
} else { } else {
\SnappyMail\Log::debug('Nextcloud', 'NOT integrated'); \SnappyMail\Log::debug('Nextcloud', 'NOT integrated');
// \OC::$server->getConfig()->getAppValue('snappymail', 'snappymail-no-embed');
$this->addHook('main.content-security-policy', 'ContentSecurityPolicy'); $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 public function loginCredentials(string &$sEmail, string &$sLogin, ?string &$sPassword = null) : void
{ {
/** // left intentionally as upstream (commented in upstream)
* 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;
} }
public function loginCredentials2(string &$sEmail, ?string &$sPassword = null) : void 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 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 if ($oAccount instanceof \RainLoop\Model\MainAccount
&& \OCA\SnappyMail\Util\SnappyMailHelper::isOIDCLogin() && \OCA\SnappyMail\Util\SnappyMailHelper::isOIDCLogin()
// && $oClient->supportsAuthType('OAUTHBEARER') // v2.28
&& \str_starts_with($oSettings->passphrase, 'oidc_login|') && \str_starts_with($oSettings->passphrase, 'oidc_login|')
) { ) {
// $oSettings->passphrase = \OC::$server->getSession()->get('snappymail-passphrase'); $oSettings->passphrase = (string) \OC::$server->getSession()->get('oidc_access_token');
$oSettings->passphrase = \OC::$server->getSession()->get('oidc_access_token');
\array_unshift($oSettings->SASLMechanisms, 'OAUTHBEARER'); \array_unshift($oSettings->SASLMechanisms, 'OAUTHBEARER');
} }
} }
/* /**
\OC::$server->getCalendarManager(); * Attach a Nextcloud file into SnappyMail temp storage (for composing).
\OC::$server->getLDAPProvider();
*/ */
public function NextcloudAttachFile() : array public function NextcloudAttachFile() : array
{ {
$aResult = [ $aResult = [
'success' => false, 'success' => false,
'tempName' => '' 'tempName' => ''
]; ];
$sFile = $this->jsonParam('file', '');
$oFiles = \OCP\Files::getStorage('files'); $sFile = (string) $this->jsonParam('file', '');
if ($oFiles && $oFiles->is_file($sFile) && $fp = $oFiles->fopen($sFile, 'rb')) {
try {
$oActions = \RainLoop\Api::Actions(); $oActions = \RainLoop\Api::Actions();
$oAccount = $oActions->getAccountFromToken(); $oAccount = $oActions->getAccountFromToken();
if ($oAccount) { if (!$oAccount) {
$sSavedName = 'nextcloud-file-' . \sha1($sFile . \microtime()); $aResult['error'] = 'no-account';
if (!$oActions->FilesProvider()->PutFile($oAccount, $sSavedName, $fp)) { 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'; $aResult['error'] = 'failed';
} else { } else {
$aResult['tempName'] = $sSavedName; $aResult['tempName'] = $sSavedName;
$aResult['success'] = true; $aResult['success'] = true;
} }
} catch (\Throwable $e) {
$aResult['error'] = 'exception';
$aResult['errorMessage'] = $e->getMessage();
} }
}
return $this->jsonResponse(__FUNCTION__, $aResult); return $this->jsonResponse(__FUNCTION__, $aResult);
} }
/**
* Save an .eml message from SnappyMail into Nextcloud Files.
*/
public function NextcloudSaveMsg() : array public function NextcloudSaveMsg() : array
{ {
$sSaveFolder = \ltrim($this->jsonParam('folder', ''), '/'); $sSaveFolder = \ltrim((string) $this->jsonParam('folder', ''), '/');
// $aValues = \RainLoop\Api::Actions()->decodeRawKey($this->jsonParam('msgHash', ''));
$msgHash = $this->jsonParam('msgHash', ''); $msgHash = (string) $this->jsonParam('msgHash', '');
$aValues = \json_decode(\MailSo\Base\Utils::UrlSafeBase64Decode($msgHash), true); $aValues = \json_decode(\MailSo\Base\Utils::UrlSafeBase64Decode($msgHash), true);
$aResult = [ $aResult = [
'folder' => '', 'folder' => '',
'filename' => '', 'filename' => '',
'success' => false 'success' => false,
]; ];
if ($sSaveFolder && !empty($aValues['folder']) && !empty($aValues['uid'])) {
if (!empty($aValues['folder']) && !empty($aValues['uid'])) {
$oActions = \RainLoop\Api::Actions(); $oActions = \RainLoop\Api::Actions();
$oMailClient = $oActions->MailClient(); $oMailClient = $oActions->MailClient();
if (!$oMailClient->IsLoggined()) { if (!$oMailClient->IsLoggined()) {
$oAccount = $oActions->getAccountFromToken(); $oAccount = $oActions->getAccountFromToken();
$oAccount->ImapConnectAndLogin($oActions->Plugins(), $oMailClient->ImapClient(), $oActions->Config()); $oAccount->ImapConnectAndLogin($oActions->Plugins(), $oMailClient->ImapClient(), $oActions->Config());
} }
// Default folder
$sSaveFolder = $sSaveFolder ?: 'Emails'; $sSaveFolder = $sSaveFolder ?: 'Emails';
$oFiles = \OCP\Files::getStorage('files'); $sSaveFolder = \trim($sSaveFolder, '/');
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';
// 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( $oMailClient->MessageMimeStream(
function ($rResource) use ($oFiles, $aResult) { function ($rResource) use (&$aResult, $folderNode, $filename) {
if (\is_resource($rResource)) { if (!\is_resource($rResource)) {
$aResult['success'] = $oFiles->file_put_contents("{$aResult['folder']}/{$aResult['filename']}", $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'], (string) $aValues['folder'],
@ -177,58 +233,96 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin
return $this->jsonResponse(__FUNCTION__, $aResult); 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) public function DoAttachmentsActions(\SnappyMail\AttachmentsAction $data)
{ {
if (static::isLoggedIn() && 'nextcloud' === $data->action) { if (!static::isLoggedIn() || 'nextcloud' !== $data->action) {
$oFiles = \OCP\Files::getStorage('files'); return;
if ($oFiles && \method_exists($oFiles, 'file_put_contents')) { }
$sSaveFolder = \ltrim($this->jsonParam('NcFolder', ''), '/');
try {
// Target folder in Nextcloud
$sSaveFolder = \ltrim((string) $this->jsonParam('NcFolder', ''), '/');
$sSaveFolder = $sSaveFolder ?: 'Attachments'; $sSaveFolder = $sSaveFolder ?: 'Attachments';
$oFiles->is_dir($sSaveFolder) || $oFiles->mkdir($sSaveFolder); $sSaveFolder = \trim($sSaveFolder, '/');
$userFolder = \OC::$server->getUserFolder();
$folderNode = $this->ensureFolderPath($userFolder, $sSaveFolder);
$data->result = true; $data->result = true;
foreach ($data->items as $aItem) { foreach ($data->items as $aItem) {
$sSavedFileName = empty($aItem['fileName']) ? 'file.dat' : $aItem['fileName']; $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'])) { if (!empty($aItem['data'])) {
$sSavedFileNameFull = static::SmartFileExists($sSaveFolder.'/'.$sSavedFileName, $oFiles); // Raw data string
if (!$oFiles->file_put_contents($sSavedFileNameFull, $aItem['data'])) { $bytes = \fwrite($out, (string) $aItem['data']);
$data->result = false; $wrote = ($bytes !== false);
}
} else if (!empty($aItem['fileHash'])) { } else if (!empty($aItem['fileHash'])) {
$fFile = $data->filesProvider->GetFile($data->account, $aItem['fileHash'], 'rb'); // Stream from SnappyMail temp storage
$fFile = $data->filesProvider->GetFile($data->account, (string) $aItem['fileHash'], 'rb');
if (\is_resource($fFile)) { if (\is_resource($fFile)) {
$sSavedFileNameFull = static::SmartFileExists($sSaveFolder.'/'.$sSavedFileName, $oFiles); \stream_copy_to_stream($fFile, $out);
if (!$oFiles->file_put_contents($sSavedFileNameFull, $fFile)) { \fclose($fFile);
$wrote = true;
}
}
\fclose($out);
if (!$wrote) {
// Clean up the empty file
try { $fileNode->delete(); } catch (\Throwable $e) {}
$data->result = false; $data->result = false;
} }
if (\is_resource($fFile)) {
\fclose($fFile);
}
}
}
}
} }
} catch (\Throwable $e) {
$data->result = false;
} }
} }
public function FilterAppData($bAdmin, &$aResult) : void public function FilterAppData($bAdmin, &$aResult) : void
{ {
if (!$bAdmin && \is_array($aResult)) { if ($bAdmin || !\is_array($aResult)) {
return;
}
$ocUser = \OC::$server->getUserSession()->getUser(); $ocUser = \OC::$server->getUserSession()->getUser();
if (!$ocUser) {
return;
}
$sUID = $ocUser->getUID(); $sUID = $ocUser->getUID();
$oUrlGen = \OC::$server->getURLGenerator(); $oUrlGen = \OC::$server->getURLGenerator();
$sWebDAV = $oUrlGen->getAbsoluteURL($oUrlGen->linkTo('', 'remote.php') . '/dav'); $sWebDAV = $oUrlGen->getAbsoluteURL($oUrlGen->linkTo('', 'remote.php') . '/dav');
// $sWebDAV = \OCP\Util::linkToRemote('dav');
$aResult['Nextcloud'] = [ $aResult['Nextcloud'] = [
'UID' => $sUID, 'UID' => $sUID,
'WebDAV' => $sWebDAV, 'WebDAV' => $sWebDAV,
'CalDAV' => $this->Config()->Get('plugin', 'calendar', false) 'CalDAV' => $this->Config()->Get('plugin', 'calendar', false)
// 'WebDAV_files' => $sWebDAV . '/files/' . $sUID
]; ];
if (empty($aResult['Auth'])) { if (empty($aResult['Auth'])) {
$config = \OC::$server->getConfig(); $config = \OC::$server->getConfig();
$sEmail = ''; $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)) { if ($config->getAppValue('snappymail', 'snappymail-autologin', false)) {
$sEmail = $sUID; $sEmail = $sUID;
} else if ($config->getAppValue('snappymail', 'snappymail-autologin-with-email', false)) { } else if ($config->getAppValue('snappymail', 'snappymail-autologin-with-email', false)) {
@ -236,31 +330,21 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin
} else { } else {
\SnappyMail\Log::debug('Nextcloud', 'snappymail-autologin is off'); \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. // User-set SnappyMail credentials override
$sCustomEmail = $config->getUserValue($sUID, 'snappymail', 'snappymail-email', ''); $sCustomEmail = $config->getUserValue($sUID, 'snappymail', 'snappymail-email', '');
if ($sCustomEmail) { if ($sCustomEmail) {
$sEmail = $sCustomEmail; $sEmail = $sCustomEmail;
} }
if (!$sEmail) { if (!$sEmail) {
$sEmail = $ocUser->getEMailAddress(); $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 ?: ''; $aResult['DevEmail'] = $sEmail ?: '';
} else if (!empty($aResult['ContactsSync'])) { } else if (!empty($aResult['ContactsSync'])) {
$bSave = false; $bSave = false;
if (empty($aResult['ContactsSync']['Url'])) { if (empty($aResult['ContactsSync']['Url'])) {
$aResult['ContactsSync']['Url'] = "{$sWebDAV}/addressbooks/users/{$sUID}/contacts/"; $aResult['ContactsSync']['Url'] = "{$sWebDAV}/addressbooks/users/{$sUID}/contacts/";
$bSave = true; $bSave = true;
@ -269,29 +353,31 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin
$aResult['ContactsSync']['User'] = $sUID; $aResult['ContactsSync']['User'] = $sUID;
$bSave = true; $bSave = true;
} }
$pass = \OC::$server->getSession()['snappymail-passphrase'];
if ($pass/* && empty($aResult['ContactsSync']['Password'])*/) { // 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); $pass = \SnappyMail\Crypt::DecryptUrlSafe($pass, $sUID);
if ($pass) { if ($pass) {
$aResult['ContactsSync']['Password'] = $pass; $aResult['ContactsSync']['Password'] = $pass;
$bSave = true; $bSave = true;
} }
} }
if ($bSave) { if ($bSave) {
$oActions = \RainLoop\Api::Actions(); $oActions = \RainLoop\Api::Actions();
$oActions->setContactsSyncData( $oActions->setContactsSyncData(
$oActions->getAccountFromToken(), $oActions->getAccountFromToken(),
array( [
'Mode' => $aResult['ContactsSync']['Mode'], 'Mode' => $aResult['ContactsSync']['Mode'],
'User' => $aResult['ContactsSync']['User'], 'User' => $aResult['ContactsSync']['User'],
'Password' => $aResult['ContactsSync']['Password'], 'Password' => $aResult['ContactsSync']['Password'],
'Url' => $aResult['ContactsSync']['Url'] 'Url' => $aResult['ContactsSync']['Url']
) ]
); );
} }
} }
} }
}
public function FilterLanguage(&$sLanguage, $bAdmin) : void public function FilterLanguage(&$sLanguage, $bAdmin) : void
{ {
@ -301,116 +387,124 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin
$userLang = \OC::$server->getConfig()->getUserValue($userId, 'core', 'lang', 'en'); $userLang = \OC::$server->getConfig()->getUserValue($userId, 'core', 'lang', 'en');
$userLang = \strtr($userLang, '_', '-'); $userLang = \strtr($userLang, '_', '-');
$sLanguage = $this->determineLocale($userLang, $aResultLang); $sLanguage = $this->determineLocale($userLang, $aResultLang);
// Check if $sLanguage is null
if (!$sLanguage) { 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 private function determineLocale(string $langCode, array $languagesArray) : ?string
{ {
// Direct check for the language code if (\in_array($langCode, $languagesArray, true)) {
if (\in_array($langCode, $languagesArray)) {
return $langCode; return $langCode;
} }
// Check without country code
if (\str_contains($langCode, '-')) { if (\str_contains($langCode, '-')) {
$langCode = \explode('-', $langCode)[0]; $langCode = \explode('-', $langCode)[0];
if (\in_array($langCode, $languagesArray)) { if (\in_array($langCode, $languagesArray, true)) {
return $langCode; return $langCode;
} }
} }
// Check with uppercase country code
$langCodeWithUpperCase = $langCode . '-' . \strtoupper($langCode); $langCodeWithUpperCase = $langCode . '-' . \strtoupper($langCode);
if (\in_array($langCodeWithUpperCase, $languagesArray)) { if (\in_array($langCodeWithUpperCase, $languagesArray, true)) {
return $langCodeWithUpperCase; return $langCodeWithUpperCase;
} }
// If no match is found
return null; return null;
} }
/**
* @param mixed $mResult
*/
public function MainFabrica(string $sName, &$mResult) public function MainFabrica(string $sName, &$mResult)
{ {
if (static::isLoggedIn()) { if (!static::isLoggedIn()) {
return;
}
if ('suggestions' === $sName && $this->Config()->Get('plugin', 'suggestions', true)) { if ('suggestions' === $sName && $this->Config()->Get('plugin', 'suggestions', true)) {
if (!\is_array($mResult)) { if (!\is_array($mResult)) {
$mResult = array(); $mResult = [];
} }
include_once __DIR__ . '/NextcloudContactsSuggestions.php'; include_once __DIR__ . '/NextcloudContactsSuggestions.php';
$mResult[] = new NextcloudContactsSuggestions( $mResult[] = new NextcloudContactsSuggestions(
$this->Config()->Get('plugin', 'ignoreSystemAddressbook', true) $this->Config()->Get('plugin', 'ignoreSystemAddressbook', true)
); );
} }
/*
if ($this->Config()->Get('plugin', 'storage', false) && ('storage' === $sName || 'storage-local' === $sName)) { // storage hook left as upstream (commented)
require_once __DIR__ . '/storage.php';
$oDriver = new \NextcloudStorage(APP_PRIVATE_DATA.'storage', $sName === 'storage-local');
}
*/
}
} }
protected function configMapping() : array protected function configMapping() : array
{ {
return array( return [
\RainLoop\Plugins\Property::NewInstance('suggestions')->SetLabel('Suggestions') \RainLoop\Plugins\Property::NewInstance('suggestions')->SetLabel('Suggestions')
->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL)
->SetDefaultValue(true), ->SetDefaultValue(true),
\RainLoop\Plugins\Property::NewInstance('ignoreSystemAddressbook')->SetLabel('Ignore system addressbook') \RainLoop\Plugins\Property::NewInstance('ignoreSystemAddressbook')->SetLabel('Ignore system addressbook')
->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL)
->SetDefaultValue(true), ->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"') \RainLoop\Plugins\Property::NewInstance('calendar')->SetLabel('Enable "Put ICS in calendar"')
->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL)
->SetDefaultValue(false) ->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)); $path = \trim($path, '/');
if ($path === '') {
if (!$oFiles->file_exists($sFilePath)) { return $baseFolder;
return $sFilePath;
} }
$aFileInfo = \pathinfo($sFilePath); $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);
}
}
$iIndex = 0; return $folderNode;
}
while (true) { /**
++$iIndex; * Return a unique filename inside a Nextcloud folder (keeps original extension).
$sFilePathNew = $aFileInfo['dirname'].'/'. */
\preg_replace('/\(\d{1,2}\)$/', '', $aFileInfo['filename']). private function uniqueNameInFolder(\OCP\Files\Folder $folderNode, string $fileName) : string
' ('.$iIndex.')'. {
(empty($aFileInfo['extension']) ? '' : '.'.$aFileInfo['extension']) $fileName = \trim($fileName);
; if ($fileName === '') {
if (!$oFiles->file_exists($sFilePathNew)) { $fileName = 'file.dat';
return $sFilePathNew;
} }
if (10 < $iIndex) {
break; 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;
} }
} }
return $sFilePath;
// fallback
return $fileName;
} }
} }

View file

@ -40,9 +40,9 @@
<br /> <br />
</span> </span>
<br /> <br />
<a class="btn" href="https://github.com/the-djmaze/snappymail/releases" target="_blank" data-i18n="TAB_ABOUT/BUTTON_RELEASES"></a> <a class="btn" href="https://github.com/nextgen-networks/snappymail/releases" target="_blank" data-i18n="TAB_ABOUT/BUTTON_RELEASES"></a>
<a class="btn" data-bind="visible: coreUpdatable, click: updateCoreData" data-i18n="GLOBAL/UPDATE"></a> <a class="btn" data-bind="visible: coreUpdatable, click: updateCoreData" data-i18n="GLOBAL/UPDATE"></a>
<a class="btn" href="https://github.com/the-djmaze/snappymail/releases/latest" data-bind="visible: !coreUpdatable()" data-i18n="GLOBAL/UPDATE"></a> <a class="btn" href="https://github.com/nextgen-networks/snappymail/releases/latest" data-bind="visible: !coreUpdatable()" data-i18n="GLOBAL/UPDATE"></a>
</div> </div>
<div data-bind="visible: 'up-to-date' === statusType()"> <div data-bind="visible: 'up-to-date' === statusType()">
<i style="color: green"></i> <i style="color: green"></i>

View file

@ -9,8 +9,8 @@
<div class="alert alert-error span8" style="margin-top: 10px;"> <div class="alert alert-error span8" style="margin-top: 10px;">
<h4 data-i18n="TAB_GENERAL/ALERT_WARNING"></h4> <h4 data-i18n="TAB_GENERAL/ALERT_WARNING"></h4>
<div data-i18n="TAB_GENERAL/ALERT_DATA_ACCESS"></div> <div data-i18n="TAB_GENERAL/ALERT_DATA_ACCESS"></div>
<a href="https://github.com/the-djmaze/snappymail/wiki/Installation-instructions#secure-data-folder" <a href="https://github.com/nextgen-networks/snappymail/wiki/Installation-instructions#secure-data-folder"
target="_blank"><strong>https://github.com/the-djmaze/snappymail/wiki/Installation-instructions</strong></a> target="_blank"><strong>https://github.com/nextgen-networks/snappymail/wiki/Installation-instructions</strong></a>
</div> </div>
</div> </div>
<div class="form-horizontal"> <div class="form-horizontal">

View file

@ -93,7 +93,7 @@
<h4> <h4>
<span data-i18n="POPUPS_DOMAIN/DISABLE_CAPABILITIES"></span> <span data-i18n="POPUPS_DOMAIN/DISABLE_CAPABILITIES"></span>
<a style="margin-left:0.5em" href="https://github.com/the-djmaze/snappymail/wiki/IMAP-capabilities" target="_blank"></a> <a style="margin-left:0.5em" href="https://github.com/nextgen-networks/snappymail/wiki/IMAP-capabilities" target="_blank"></a>
</h4> </h4>
<div data-bind="foreach: imapCapabilities" style="columns: 16em auto"> <div data-bind="foreach: imapCapabilities" style="columns: 16em auto">