initial
This commit is contained in:
commit
d4dcbc5a67
64 changed files with 3030 additions and 0 deletions
3
.babelrc
Normal file
3
.babelrc
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"presets": ["es2015"]
|
||||||
|
}
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
logs
|
||||||
|
node_modules/
|
||||||
|
.vagrant
|
||||||
|
.idea
|
||||||
|
target
|
52
.jshintrc
Normal file
52
.jshintrc
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"esnext": true,
|
||||||
|
"camelcase": true,
|
||||||
|
"devel": false,
|
||||||
|
"asi": false,
|
||||||
|
"boss": false,
|
||||||
|
"eqnull": false,
|
||||||
|
"es5": true,
|
||||||
|
"moz": false,
|
||||||
|
"evil": false,
|
||||||
|
"expr": true,
|
||||||
|
"funcscope": true,
|
||||||
|
"iterator": false,
|
||||||
|
"lastsemic": false,
|
||||||
|
"laxbreak": false,
|
||||||
|
"laxcomma": false,
|
||||||
|
"loopfunc": false,
|
||||||
|
"multistr": true,
|
||||||
|
"noyield": false,
|
||||||
|
"notypeof": false,
|
||||||
|
"proto": false,
|
||||||
|
"scripturl": false,
|
||||||
|
"shadow": false,
|
||||||
|
"sub": false,
|
||||||
|
"supernew": false,
|
||||||
|
"validthis": false,
|
||||||
|
"plusplus": false,
|
||||||
|
"jquery": true,
|
||||||
|
"mocha": false,
|
||||||
|
"node": true,
|
||||||
|
"latedef": true,
|
||||||
|
"quotmark": "single",
|
||||||
|
"maxparams": 6,
|
||||||
|
"maxdepth": 5,
|
||||||
|
"maxerr": 50,
|
||||||
|
"globalstrict": false,
|
||||||
|
"globals": {
|
||||||
|
"inject": true,
|
||||||
|
"angular": true,
|
||||||
|
"require": true,
|
||||||
|
"process": true,
|
||||||
|
"module": true,
|
||||||
|
"describe": true,
|
||||||
|
"beforeEach": true,
|
||||||
|
"afterEach": true,
|
||||||
|
"it": true,
|
||||||
|
"expect": true,
|
||||||
|
"__dirname": true,
|
||||||
|
"exports": true,
|
||||||
|
"spyOn": true
|
||||||
|
}
|
||||||
|
}
|
53
Vagrantfile
vendored
Normal file
53
Vagrantfile
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
# -*- mode: ruby -*-
|
||||||
|
# vi: set ft=ruby :
|
||||||
|
|
||||||
|
VAGRANTFILE_API_VERSION = "2"
|
||||||
|
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||||
|
|
||||||
|
config.vm.box = "avenuefactory/lamp"
|
||||||
|
config.vm.network "forwarded_port", guest: 80, host: 8080
|
||||||
|
config.vm.network "forwarded_port", guest: 993, host: 9993
|
||||||
|
|
||||||
|
config.vm.synced_folder "./", "/var/www/html", id: "vagrant-root",
|
||||||
|
owner: "vagrant",
|
||||||
|
group: "www-data",
|
||||||
|
mount_options: ["dmode=775,fmode=664"]
|
||||||
|
|
||||||
|
config.vm.provision "shell", inline: <<-SHELL
|
||||||
|
echo updating... && sudo apt-get -qq update
|
||||||
|
echo installing... && sudo apt-get -qq -y install php5-imap
|
||||||
|
sudo service apache2 restart
|
||||||
|
|
||||||
|
# Install and Configure Dovecot (http://blog.tedivm.com/open-source/2014/03/building-an-email-library-testing-with-vagrant-dovecot-and-travis-ci/)
|
||||||
|
#https://github.com/tedious/DovecotTesting/blob/master/resources/Scripts/Provision.sh
|
||||||
|
if which dovecot > /dev/null; then
|
||||||
|
echo 'Dovecot is already installed'
|
||||||
|
else
|
||||||
|
|
||||||
|
sudo mkdir /home/vagrant/Maildir
|
||||||
|
sudo chown -R vagrant:vagrant /home/vagrant/Maildir
|
||||||
|
sudo chmod a+rw /home/vagrant/Maildir
|
||||||
|
|
||||||
|
echo 'Installing Dovecot'
|
||||||
|
sudo apt-get -qq -y install dovecot-imapd
|
||||||
|
sudo touch /etc/dovecot/local.conf
|
||||||
|
sudo chmod go+rw /etc/dovecot/local.conf
|
||||||
|
echo 'mail_location = maildir:/home/vagrant/Maildir' >> /etc/dovecot/local.conf
|
||||||
|
echo 'disable_plaintext_auth = no' >> /etc/dovecot/local.conf
|
||||||
|
echo 'mail_max_userip_connections = 10000' >> /etc/dovecot/local.conf
|
||||||
|
sudo restart dovecot
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create user "test"
|
||||||
|
if getent passwd test > /dev/null; then
|
||||||
|
echo 'test already exists'
|
||||||
|
else
|
||||||
|
sudo useradd "test" -m -s /bin/bash
|
||||||
|
echo "test:test"|sudo chpasswd
|
||||||
|
echo 'User "test" created'
|
||||||
|
fi
|
||||||
|
|
||||||
|
SHELL
|
||||||
|
|
||||||
|
|
||||||
|
end
|
11
build.sh
Executable file
11
build.sh
Executable file
|
@ -0,0 +1,11 @@
|
||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
# install php dependencies
|
||||||
|
composer install
|
||||||
|
|
||||||
|
# copy backend
|
||||||
|
cp -rv src/{backend,config.sample}.php dist/
|
||||||
|
|
||||||
|
# build Javascript frontend
|
||||||
|
npm install
|
||||||
|
gulp build
|
8
composer.json
Normal file
8
composer.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"require": {
|
||||||
|
"php-imap/php-imap": "~2.0"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"vendor-dir": "dist/backend-libs"
|
||||||
|
}
|
||||||
|
}
|
62
composer.lock
generated
Normal file
62
composer.lock
generated
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
{
|
||||||
|
"_readme": [
|
||||||
|
"This file locks the dependencies of your project to a known state",
|
||||||
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||||
|
"This file is @generated automatically"
|
||||||
|
],
|
||||||
|
"hash": "d4d6c6077bbcd7958a06026e4117a70b",
|
||||||
|
"content-hash": "357059f60b3f57be6e5f15b1df393a72",
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "php-imap/php-imap",
|
||||||
|
"version": "2.0.3",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/barbushin/php-imap.git",
|
||||||
|
"reference": "cc1a49a3f68090db182183c59ffbc09055d59f5b"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/barbushin/php-imap/zipball/cc1a49a3f68090db182183c59ffbc09055d59f5b",
|
||||||
|
"reference": "cc1a49a3f68090db182183c59ffbc09055d59f5b",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-0": {
|
||||||
|
"PhpImap": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD 3-Clause"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sergey Barbushin",
|
||||||
|
"email": "barbushin@gmail.com",
|
||||||
|
"homepage": "http://linkedin.com/in/barbushin"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PHP class to access mailbox by POP3/IMAP/NNTP using IMAP extension",
|
||||||
|
"homepage": "https://github.com/barbushin/php-imap",
|
||||||
|
"keywords": [
|
||||||
|
"imap",
|
||||||
|
"mail",
|
||||||
|
"php"
|
||||||
|
],
|
||||||
|
"time": "2015-09-16 07:40:39"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packages-dev": [],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": [],
|
||||||
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
|
"platform": [],
|
||||||
|
"platform-dev": []
|
||||||
|
}
|
7
dist/backend-libs/autoload.php
vendored
Normal file
7
dist/backend-libs/autoload.php
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload.php @generated by Composer
|
||||||
|
|
||||||
|
require_once __DIR__ . '/composer' . '/autoload_real.php';
|
||||||
|
|
||||||
|
return ComposerAutoloaderInit38e9d02473bfdb3672d6b8a39de2fbc0::getLoader();
|
413
dist/backend-libs/composer/ClassLoader.php
vendored
Normal file
413
dist/backend-libs/composer/ClassLoader.php
vendored
Normal file
|
@ -0,0 +1,413 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer\Autoload;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
||||||
|
*
|
||||||
|
* $loader = new \Composer\Autoload\ClassLoader();
|
||||||
|
*
|
||||||
|
* // register classes with namespaces
|
||||||
|
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||||
|
* $loader->add('Symfony', __DIR__.'/framework');
|
||||||
|
*
|
||||||
|
* // activate the autoloader
|
||||||
|
* $loader->register();
|
||||||
|
*
|
||||||
|
* // to enable searching the include path (eg. for PEAR packages)
|
||||||
|
* $loader->setUseIncludePath(true);
|
||||||
|
*
|
||||||
|
* In this example, if you try to use a class in the Symfony\Component
|
||||||
|
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||||
|
* the autoloader will first look for the class under the component/
|
||||||
|
* directory, and it will then fallback to the framework/ directory if not
|
||||||
|
* found before giving up.
|
||||||
|
*
|
||||||
|
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
* @see http://www.php-fig.org/psr/psr-0/
|
||||||
|
* @see http://www.php-fig.org/psr/psr-4/
|
||||||
|
*/
|
||||||
|
class ClassLoader
|
||||||
|
{
|
||||||
|
// PSR-4
|
||||||
|
private $prefixLengthsPsr4 = array();
|
||||||
|
private $prefixDirsPsr4 = array();
|
||||||
|
private $fallbackDirsPsr4 = array();
|
||||||
|
|
||||||
|
// PSR-0
|
||||||
|
private $prefixesPsr0 = array();
|
||||||
|
private $fallbackDirsPsr0 = array();
|
||||||
|
|
||||||
|
private $useIncludePath = false;
|
||||||
|
private $classMap = array();
|
||||||
|
|
||||||
|
private $classMapAuthoritative = false;
|
||||||
|
|
||||||
|
public function getPrefixes()
|
||||||
|
{
|
||||||
|
if (!empty($this->prefixesPsr0)) {
|
||||||
|
return call_user_func_array('array_merge', $this->prefixesPsr0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPrefixesPsr4()
|
||||||
|
{
|
||||||
|
return $this->prefixDirsPsr4;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFallbackDirs()
|
||||||
|
{
|
||||||
|
return $this->fallbackDirsPsr0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFallbackDirsPsr4()
|
||||||
|
{
|
||||||
|
return $this->fallbackDirsPsr4;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClassMap()
|
||||||
|
{
|
||||||
|
return $this->classMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $classMap Class to filename map
|
||||||
|
*/
|
||||||
|
public function addClassMap(array $classMap)
|
||||||
|
{
|
||||||
|
if ($this->classMap) {
|
||||||
|
$this->classMap = array_merge($this->classMap, $classMap);
|
||||||
|
} else {
|
||||||
|
$this->classMap = $classMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a set of PSR-0 directories for a given prefix, either
|
||||||
|
* appending or prepending to the ones previously set for this prefix.
|
||||||
|
*
|
||||||
|
* @param string $prefix The prefix
|
||||||
|
* @param array|string $paths The PSR-0 root directories
|
||||||
|
* @param bool $prepend Whether to prepend the directories
|
||||||
|
*/
|
||||||
|
public function add($prefix, $paths, $prepend = false)
|
||||||
|
{
|
||||||
|
if (!$prefix) {
|
||||||
|
if ($prepend) {
|
||||||
|
$this->fallbackDirsPsr0 = array_merge(
|
||||||
|
(array) $paths,
|
||||||
|
$this->fallbackDirsPsr0
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$this->fallbackDirsPsr0 = array_merge(
|
||||||
|
$this->fallbackDirsPsr0,
|
||||||
|
(array) $paths
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$first = $prefix[0];
|
||||||
|
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||||
|
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($prepend) {
|
||||||
|
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||||
|
(array) $paths,
|
||||||
|
$this->prefixesPsr0[$first][$prefix]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||||
|
$this->prefixesPsr0[$first][$prefix],
|
||||||
|
(array) $paths
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a set of PSR-4 directories for a given namespace, either
|
||||||
|
* appending or prepending to the ones previously set for this namespace.
|
||||||
|
*
|
||||||
|
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||||
|
* @param array|string $paths The PSR-4 base directories
|
||||||
|
* @param bool $prepend Whether to prepend the directories
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function addPsr4($prefix, $paths, $prepend = false)
|
||||||
|
{
|
||||||
|
if (!$prefix) {
|
||||||
|
// Register directories for the root namespace.
|
||||||
|
if ($prepend) {
|
||||||
|
$this->fallbackDirsPsr4 = array_merge(
|
||||||
|
(array) $paths,
|
||||||
|
$this->fallbackDirsPsr4
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$this->fallbackDirsPsr4 = array_merge(
|
||||||
|
$this->fallbackDirsPsr4,
|
||||||
|
(array) $paths
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||||
|
// Register directories for a new namespace.
|
||||||
|
$length = strlen($prefix);
|
||||||
|
if ('\\' !== $prefix[$length - 1]) {
|
||||||
|
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||||
|
}
|
||||||
|
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||||
|
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||||
|
} elseif ($prepend) {
|
||||||
|
// Prepend directories for an already registered namespace.
|
||||||
|
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||||
|
(array) $paths,
|
||||||
|
$this->prefixDirsPsr4[$prefix]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Append directories for an already registered namespace.
|
||||||
|
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||||
|
$this->prefixDirsPsr4[$prefix],
|
||||||
|
(array) $paths
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a set of PSR-0 directories for a given prefix,
|
||||||
|
* replacing any others previously set for this prefix.
|
||||||
|
*
|
||||||
|
* @param string $prefix The prefix
|
||||||
|
* @param array|string $paths The PSR-0 base directories
|
||||||
|
*/
|
||||||
|
public function set($prefix, $paths)
|
||||||
|
{
|
||||||
|
if (!$prefix) {
|
||||||
|
$this->fallbackDirsPsr0 = (array) $paths;
|
||||||
|
} else {
|
||||||
|
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a set of PSR-4 directories for a given namespace,
|
||||||
|
* replacing any others previously set for this namespace.
|
||||||
|
*
|
||||||
|
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||||
|
* @param array|string $paths The PSR-4 base directories
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function setPsr4($prefix, $paths)
|
||||||
|
{
|
||||||
|
if (!$prefix) {
|
||||||
|
$this->fallbackDirsPsr4 = (array) $paths;
|
||||||
|
} else {
|
||||||
|
$length = strlen($prefix);
|
||||||
|
if ('\\' !== $prefix[$length - 1]) {
|
||||||
|
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||||
|
}
|
||||||
|
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||||
|
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns on searching the include path for class files.
|
||||||
|
*
|
||||||
|
* @param bool $useIncludePath
|
||||||
|
*/
|
||||||
|
public function setUseIncludePath($useIncludePath)
|
||||||
|
{
|
||||||
|
$this->useIncludePath = $useIncludePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be used to check if the autoloader uses the include path to check
|
||||||
|
* for classes.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getUseIncludePath()
|
||||||
|
{
|
||||||
|
return $this->useIncludePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns off searching the prefix and fallback directories for classes
|
||||||
|
* that have not been registered with the class map.
|
||||||
|
*
|
||||||
|
* @param bool $classMapAuthoritative
|
||||||
|
*/
|
||||||
|
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||||
|
{
|
||||||
|
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should class lookup fail if not found in the current class map?
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isClassMapAuthoritative()
|
||||||
|
{
|
||||||
|
return $this->classMapAuthoritative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers this instance as an autoloader.
|
||||||
|
*
|
||||||
|
* @param bool $prepend Whether to prepend the autoloader or not
|
||||||
|
*/
|
||||||
|
public function register($prepend = false)
|
||||||
|
{
|
||||||
|
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters this instance as an autoloader.
|
||||||
|
*/
|
||||||
|
public function unregister()
|
||||||
|
{
|
||||||
|
spl_autoload_unregister(array($this, 'loadClass'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the given class or interface.
|
||||||
|
*
|
||||||
|
* @param string $class The name of the class
|
||||||
|
* @return bool|null True if loaded, null otherwise
|
||||||
|
*/
|
||||||
|
public function loadClass($class)
|
||||||
|
{
|
||||||
|
if ($file = $this->findFile($class)) {
|
||||||
|
includeFile($file);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the path to the file where the class is defined.
|
||||||
|
*
|
||||||
|
* @param string $class The name of the class
|
||||||
|
*
|
||||||
|
* @return string|false The path if found, false otherwise
|
||||||
|
*/
|
||||||
|
public function findFile($class)
|
||||||
|
{
|
||||||
|
// work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
|
||||||
|
if ('\\' == $class[0]) {
|
||||||
|
$class = substr($class, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// class map lookup
|
||||||
|
if (isset($this->classMap[$class])) {
|
||||||
|
return $this->classMap[$class];
|
||||||
|
}
|
||||||
|
if ($this->classMapAuthoritative) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = $this->findFileWithExtension($class, '.php');
|
||||||
|
|
||||||
|
// Search for Hack files if we are running on HHVM
|
||||||
|
if ($file === null && defined('HHVM_VERSION')) {
|
||||||
|
$file = $this->findFileWithExtension($class, '.hh');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($file === null) {
|
||||||
|
// Remember that this class does not exist.
|
||||||
|
return $this->classMap[$class] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function findFileWithExtension($class, $ext)
|
||||||
|
{
|
||||||
|
// PSR-4 lookup
|
||||||
|
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||||
|
|
||||||
|
$first = $class[0];
|
||||||
|
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||||
|
foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
|
||||||
|
if (0 === strpos($class, $prefix)) {
|
||||||
|
foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
|
||||||
|
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PSR-4 fallback dirs
|
||||||
|
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||||
|
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PSR-0 lookup
|
||||||
|
if (false !== $pos = strrpos($class, '\\')) {
|
||||||
|
// namespaced class name
|
||||||
|
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||||
|
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||||
|
} else {
|
||||||
|
// PEAR-like class name
|
||||||
|
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->prefixesPsr0[$first])) {
|
||||||
|
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||||
|
if (0 === strpos($class, $prefix)) {
|
||||||
|
foreach ($dirs as $dir) {
|
||||||
|
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PSR-0 fallback dirs
|
||||||
|
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||||
|
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PSR-0 include paths.
|
||||||
|
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scope isolated include.
|
||||||
|
*
|
||||||
|
* Prevents access to $this/self from included files.
|
||||||
|
*/
|
||||||
|
function includeFile($file)
|
||||||
|
{
|
||||||
|
include $file;
|
||||||
|
}
|
21
dist/backend-libs/composer/LICENSE
vendored
Normal file
21
dist/backend-libs/composer/LICENSE
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
|
||||||
|
Copyright (c) 2016 Nils Adermann, Jordi Boggiano
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
9
dist/backend-libs/composer/autoload_classmap.php
vendored
Normal file
9
dist/backend-libs/composer/autoload_classmap.php
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload_classmap.php @generated by Composer
|
||||||
|
|
||||||
|
$vendorDir = dirname(dirname(__FILE__));
|
||||||
|
$baseDir = dirname(dirname($vendorDir));
|
||||||
|
|
||||||
|
return array(
|
||||||
|
);
|
10
dist/backend-libs/composer/autoload_namespaces.php
vendored
Normal file
10
dist/backend-libs/composer/autoload_namespaces.php
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload_namespaces.php @generated by Composer
|
||||||
|
|
||||||
|
$vendorDir = dirname(dirname(__FILE__));
|
||||||
|
$baseDir = dirname(dirname($vendorDir));
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'PhpImap' => array($vendorDir . '/php-imap/php-imap/src'),
|
||||||
|
);
|
9
dist/backend-libs/composer/autoload_psr4.php
vendored
Normal file
9
dist/backend-libs/composer/autoload_psr4.php
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload_psr4.php @generated by Composer
|
||||||
|
|
||||||
|
$vendorDir = dirname(dirname(__FILE__));
|
||||||
|
$baseDir = dirname(dirname($vendorDir));
|
||||||
|
|
||||||
|
return array(
|
||||||
|
);
|
52
dist/backend-libs/composer/autoload_real.php
vendored
Normal file
52
dist/backend-libs/composer/autoload_real.php
vendored
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload_real.php @generated by Composer
|
||||||
|
|
||||||
|
class ComposerAutoloaderInit38e9d02473bfdb3672d6b8a39de2fbc0
|
||||||
|
{
|
||||||
|
private static $loader;
|
||||||
|
|
||||||
|
public static function loadClassLoader($class)
|
||||||
|
{
|
||||||
|
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||||
|
require __DIR__ . '/ClassLoader.php';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getLoader()
|
||||||
|
{
|
||||||
|
if (null !== self::$loader) {
|
||||||
|
return self::$loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
spl_autoload_register(array('ComposerAutoloaderInit38e9d02473bfdb3672d6b8a39de2fbc0', 'loadClassLoader'), true, true);
|
||||||
|
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
|
||||||
|
spl_autoload_unregister(array('ComposerAutoloaderInit38e9d02473bfdb3672d6b8a39de2fbc0', 'loadClassLoader'));
|
||||||
|
|
||||||
|
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION');
|
||||||
|
if ($useStaticLoader) {
|
||||||
|
require_once __DIR__ . '/autoload_static.php';
|
||||||
|
|
||||||
|
call_user_func(\Composer\Autoload\ComposerStaticInit38e9d02473bfdb3672d6b8a39de2fbc0::getInitializer($loader));
|
||||||
|
} else {
|
||||||
|
$map = require __DIR__ . '/autoload_namespaces.php';
|
||||||
|
foreach ($map as $namespace => $path) {
|
||||||
|
$loader->set($namespace, $path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$map = require __DIR__ . '/autoload_psr4.php';
|
||||||
|
foreach ($map as $namespace => $path) {
|
||||||
|
$loader->setPsr4($namespace, $path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$classMap = require __DIR__ . '/autoload_classmap.php';
|
||||||
|
if ($classMap) {
|
||||||
|
$loader->addClassMap($classMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$loader->register(true);
|
||||||
|
|
||||||
|
return $loader;
|
||||||
|
}
|
||||||
|
}
|
26
dist/backend-libs/composer/autoload_static.php
vendored
Normal file
26
dist/backend-libs/composer/autoload_static.php
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload_static.php @generated by Composer
|
||||||
|
|
||||||
|
namespace Composer\Autoload;
|
||||||
|
|
||||||
|
class ComposerStaticInit38e9d02473bfdb3672d6b8a39de2fbc0
|
||||||
|
{
|
||||||
|
public static $prefixesPsr0 = array (
|
||||||
|
'P' =>
|
||||||
|
array (
|
||||||
|
'PhpImap' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/php-imap/php-imap/src',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
public static function getInitializer(ClassLoader $loader)
|
||||||
|
{
|
||||||
|
return \Closure::bind(function () use ($loader) {
|
||||||
|
$loader->prefixesPsr0 = ComposerStaticInit38e9d02473bfdb3672d6b8a39de2fbc0::$prefixesPsr0;
|
||||||
|
|
||||||
|
}, null, ClassLoader::class);
|
||||||
|
}
|
||||||
|
}
|
47
dist/backend-libs/composer/installed.json
vendored
Normal file
47
dist/backend-libs/composer/installed.json
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "php-imap/php-imap",
|
||||||
|
"version": "2.0.3",
|
||||||
|
"version_normalized": "2.0.3.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/barbushin/php-imap.git",
|
||||||
|
"reference": "cc1a49a3f68090db182183c59ffbc09055d59f5b"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/barbushin/php-imap/zipball/cc1a49a3f68090db182183c59ffbc09055d59f5b",
|
||||||
|
"reference": "cc1a49a3f68090db182183c59ffbc09055d59f5b",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.0"
|
||||||
|
},
|
||||||
|
"time": "2015-09-16 07:40:39",
|
||||||
|
"type": "library",
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-0": {
|
||||||
|
"PhpImap": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD 3-Clause"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sergey Barbushin",
|
||||||
|
"email": "barbushin@gmail.com",
|
||||||
|
"homepage": "http://linkedin.com/in/barbushin"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PHP class to access mailbox by POP3/IMAP/NNTP using IMAP extension",
|
||||||
|
"homepage": "https://github.com/barbushin/php-imap",
|
||||||
|
"keywords": [
|
||||||
|
"imap",
|
||||||
|
"mail",
|
||||||
|
"php"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
22
dist/backend-libs/php-imap/php-imap/.gitattributes
vendored
Normal file
22
dist/backend-libs/php-imap/php-imap/.gitattributes
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
* text=auto
|
||||||
|
|
||||||
|
# Custom for Visual Studio
|
||||||
|
*.cs diff=csharp
|
||||||
|
*.sln merge=union
|
||||||
|
*.csproj merge=union
|
||||||
|
*.vbproj merge=union
|
||||||
|
*.fsproj merge=union
|
||||||
|
*.dbproj merge=union
|
||||||
|
|
||||||
|
# Standard to msysgit
|
||||||
|
*.doc diff=astextplain
|
||||||
|
*.DOC diff=astextplain
|
||||||
|
*.docx diff=astextplain
|
||||||
|
*.DOCX diff=astextplain
|
||||||
|
*.dot diff=astextplain
|
||||||
|
*.DOT diff=astextplain
|
||||||
|
*.pdf diff=astextplain
|
||||||
|
*.PDF diff=astextplain
|
||||||
|
*.rtf diff=astextplain
|
||||||
|
*.RTF diff=astextplain
|
164
dist/backend-libs/php-imap/php-imap/.gitignore
vendored
Normal file
164
dist/backend-libs/php-imap/php-imap/.gitignore
vendored
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
#################
|
||||||
|
## Eclipse
|
||||||
|
#################
|
||||||
|
|
||||||
|
*.pydevproject
|
||||||
|
.project
|
||||||
|
.metadata
|
||||||
|
.idea
|
||||||
|
bin/
|
||||||
|
tmp/
|
||||||
|
*.tmp
|
||||||
|
*.bak
|
||||||
|
*.swp
|
||||||
|
*~.nib
|
||||||
|
local.properties
|
||||||
|
.classpath
|
||||||
|
.settings/
|
||||||
|
.loadpath
|
||||||
|
|
||||||
|
# External tool builders
|
||||||
|
.externalToolBuilders/
|
||||||
|
|
||||||
|
# Locally stored "Eclipse launch configurations"
|
||||||
|
*.launch
|
||||||
|
|
||||||
|
# CDT-specific
|
||||||
|
.cproject
|
||||||
|
|
||||||
|
# PDT-specific
|
||||||
|
.buildpath
|
||||||
|
|
||||||
|
|
||||||
|
#################
|
||||||
|
## Visual Studio
|
||||||
|
#################
|
||||||
|
|
||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Rr]elease/
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.vspscc
|
||||||
|
.builds
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
## TODO: If you have NuGet Package Restore enabled, uncomment this
|
||||||
|
#packages/
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish
|
||||||
|
|
||||||
|
# Others
|
||||||
|
[Bb]in
|
||||||
|
[Oo]bj
|
||||||
|
sql
|
||||||
|
TestResults
|
||||||
|
*.Cache
|
||||||
|
ClientBin
|
||||||
|
stylecop.*
|
||||||
|
~$*
|
||||||
|
*.dbmdl
|
||||||
|
Generated_Code #added for RIA/Silverlight projects
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file to a newer
|
||||||
|
# Visual Studio version. Backup files are not needed, because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
############
|
||||||
|
## Windows
|
||||||
|
############
|
||||||
|
|
||||||
|
# Windows image file caches
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
Desktop.ini
|
||||||
|
|
||||||
|
|
||||||
|
#############
|
||||||
|
## Python
|
||||||
|
#############
|
||||||
|
|
||||||
|
*.py[co]
|
||||||
|
|
||||||
|
# Packages
|
||||||
|
*.egg
|
||||||
|
*.egg-info
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
eggs
|
||||||
|
parts
|
||||||
|
bin
|
||||||
|
var
|
||||||
|
sdist
|
||||||
|
develop-eggs
|
||||||
|
.installed.cfg
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
.coverage
|
||||||
|
.tox
|
||||||
|
|
||||||
|
#Translations
|
||||||
|
*.mo
|
||||||
|
|
||||||
|
#Mr Developer
|
||||||
|
.mr.developer.cfg
|
||||||
|
|
||||||
|
# Mac crap
|
||||||
|
.DS_Store
|
32
dist/backend-libs/php-imap/php-imap/LICENSE
vendored
Normal file
32
dist/backend-libs/php-imap/php-imap/LICENSE
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
ImapMailbox
|
||||||
|
|
||||||
|
Copyright (c) 2012 by Barbushin Sergey <barbushin@gmail.com>.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
|
||||||
|
* The names of the contributors may not be used to endorse or
|
||||||
|
promote products derived from this software without specific
|
||||||
|
prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
53
dist/backend-libs/php-imap/php-imap/README.md
vendored
Normal file
53
dist/backend-libs/php-imap/php-imap/README.md
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
ImapMailbox is PHP class to access mailbox by POP3/IMAP/NNTP using IMAP extension
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Connect to mailbox by POP3/IMAP/NNTP (see [imap_open](http://php.net/imap_open))
|
||||||
|
* Get mailbox status (see [imap_check](http://php.net/imap_check))
|
||||||
|
* Receive emails (+attachments, +html body images)
|
||||||
|
* Search emails by custom criteria (see [imap_search](http://php.net/imap_search))
|
||||||
|
* Change email status (see [imap_setflag_full](http://php.net/imap_setflag_full))
|
||||||
|
* Delete email
|
||||||
|
|
||||||
|
### Installation by Composer
|
||||||
|
|
||||||
|
{
|
||||||
|
"require": {
|
||||||
|
"php-imap/php-imap": "~2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Or
|
||||||
|
|
||||||
|
$ composer require php-imap/php-imap ~2.0
|
||||||
|
|
||||||
|
### Migration from `v1.*` to `v2.*`
|
||||||
|
|
||||||
|
Just add following code in the head of your script:
|
||||||
|
|
||||||
|
use PhpImap\Mailbox as ImapMailbox;
|
||||||
|
use PhpImap\IncomingMail;
|
||||||
|
use PhpImap\IncomingMailAttachment;
|
||||||
|
|
||||||
|
### [Usage example](https://github.com/barbushin/php-imap/blob/master/example/index.php)
|
||||||
|
|
||||||
|
```php
|
||||||
|
$mailbox = new PhpImap\Mailbox('{imap.gmail.com:993/imap/ssl}INBOX', 'some@gmail.com', '*********', __DIR__);
|
||||||
|
$mails = array();
|
||||||
|
|
||||||
|
$mailsIds = $mailbox->searchMailBox('ALL');
|
||||||
|
if(!$mailsIds) {
|
||||||
|
die('Mailbox is empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
$mailId = reset($mailsIds);
|
||||||
|
$mail = $mailbox->getMail($mailId);
|
||||||
|
|
||||||
|
var_dump($mail);
|
||||||
|
var_dump($mail->getAttachments());
|
||||||
|
```
|
||||||
|
|
||||||
|
### Recommended
|
||||||
|
|
||||||
|
* Google Chrome extension [PHP Console](https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef)
|
||||||
|
* Google Chrome extension [JavaScript Errors Notifier](https://chrome.google.com/webstore/detail/javascript-errors-notifie/jafmfknfnkoekkdocjiaipcnmkklaajd)
|
28
dist/backend-libs/php-imap/php-imap/composer.json
vendored
Normal file
28
dist/backend-libs/php-imap/php-imap/composer.json
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"name": "php-imap/php-imap",
|
||||||
|
"description": "PHP class to access mailbox by POP3/IMAP/NNTP using IMAP extension",
|
||||||
|
"keywords": [
|
||||||
|
"PHP",
|
||||||
|
"IMAP",
|
||||||
|
"mail"
|
||||||
|
],
|
||||||
|
"homepage": "https://github.com/barbushin/php-imap",
|
||||||
|
"license": "BSD 3-Clause",
|
||||||
|
"type": "library",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Sergey Barbushin",
|
||||||
|
"homepage": "http://linkedin.com/in/barbushin",
|
||||||
|
"email": "barbushin@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.0"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-0": {
|
||||||
|
"PhpImap": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimum-stability": "stable"
|
||||||
|
}
|
65
dist/backend-libs/php-imap/php-imap/src/PhpImap/IncomingMail.php
vendored
Normal file
65
dist/backend-libs/php-imap/php-imap/src/PhpImap/IncomingMail.php
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<?php namespace PhpImap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see https://github.com/barbushin/php-imap
|
||||||
|
* @author Barbushin Sergey http://linkedin.com/in/barbushin
|
||||||
|
*/
|
||||||
|
class IncomingMail {
|
||||||
|
|
||||||
|
public $id;
|
||||||
|
public $date;
|
||||||
|
public $subject;
|
||||||
|
|
||||||
|
public $fromName;
|
||||||
|
public $fromAddress;
|
||||||
|
|
||||||
|
public $to = array();
|
||||||
|
public $toString;
|
||||||
|
public $cc = array();
|
||||||
|
public $replyTo = array();
|
||||||
|
|
||||||
|
public $messageId;
|
||||||
|
|
||||||
|
public $textPlain;
|
||||||
|
public $textHtml;
|
||||||
|
/** @var IncomingMailAttachment[] */
|
||||||
|
protected $attachments = array();
|
||||||
|
|
||||||
|
public function addAttachment(IncomingMailAttachment $attachment) {
|
||||||
|
$this->attachments[$attachment->id] = $attachment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return IncomingMailAttachment[]
|
||||||
|
*/
|
||||||
|
public function getAttachments() {
|
||||||
|
return $this->attachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get array of internal HTML links placeholders
|
||||||
|
* @return array attachmentId => link placeholder
|
||||||
|
*/
|
||||||
|
public function getInternalLinksPlaceholders() {
|
||||||
|
return preg_match_all('/=["\'](ci?d:([\w\.%*@-]+))["\']/i', $this->textHtml, $matches) ? array_combine($matches[2], $matches[1]) : array();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function replaceInternalLinks($baseUri) {
|
||||||
|
$baseUri = rtrim($baseUri, '\\/') . '/';
|
||||||
|
$fetchedHtml = $this->textHtml;
|
||||||
|
foreach($this->getInternalLinksPlaceholders() as $attachmentId => $placeholder) {
|
||||||
|
if(isset($this->attachments[$attachmentId])) {
|
||||||
|
$fetchedHtml = str_replace($placeholder, $baseUri . basename($this->attachments[$attachmentId]->filePath), $fetchedHtml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $fetchedHtml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IncomingMailAttachment {
|
||||||
|
|
||||||
|
public $id;
|
||||||
|
public $name;
|
||||||
|
public $filePath;
|
||||||
|
}
|
611
dist/backend-libs/php-imap/php-imap/src/PhpImap/Mailbox.php
vendored
Normal file
611
dist/backend-libs/php-imap/php-imap/src/PhpImap/Mailbox.php
vendored
Normal file
|
@ -0,0 +1,611 @@
|
||||||
|
<?php namespace PhpImap;
|
||||||
|
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see https://github.com/barbushin/php-imap
|
||||||
|
* @author Barbushin Sergey http://linkedin.com/in/barbushin
|
||||||
|
*/
|
||||||
|
class Mailbox {
|
||||||
|
|
||||||
|
protected $imapPath;
|
||||||
|
protected $imapLogin;
|
||||||
|
protected $imapPassword;
|
||||||
|
protected $imapOptions = 0;
|
||||||
|
protected $imapRetriesNum = 0;
|
||||||
|
protected $imapParams = array();
|
||||||
|
protected $serverEncoding;
|
||||||
|
protected $attachmentsDir;
|
||||||
|
|
||||||
|
public function __construct($imapPath, $login, $password, $attachmentsDir = null, $serverEncoding = 'UTF-8') {
|
||||||
|
$this->imapPath = $imapPath;
|
||||||
|
$this->imapLogin = $login;
|
||||||
|
$this->imapPassword = $password;
|
||||||
|
$this->serverEncoding = strtoupper($serverEncoding);
|
||||||
|
if($attachmentsDir) {
|
||||||
|
if(!is_dir($attachmentsDir)) {
|
||||||
|
throw new Exception('Directory "' . $attachmentsDir . '" not found');
|
||||||
|
}
|
||||||
|
$this->attachmentsDir = rtrim(realpath($attachmentsDir), '\\/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set custom connection arguments of imap_open method. See http://php.net/imap_open
|
||||||
|
* @param int $options
|
||||||
|
* @param int $retriesNum
|
||||||
|
* @param array $params
|
||||||
|
*/
|
||||||
|
public function setConnectionArgs($options = 0, $retriesNum = 0, array $params = null) {
|
||||||
|
$this->imapOptions = $options;
|
||||||
|
$this->imapRetriesNum = $retriesNum;
|
||||||
|
$this->imapParams = $params;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get IMAP mailbox connection stream
|
||||||
|
* @param bool $forceConnection Initialize connection if it's not initialized
|
||||||
|
* @return null|resource
|
||||||
|
*/
|
||||||
|
public function getImapStream($forceConnection = true) {
|
||||||
|
static $imapStream;
|
||||||
|
if($forceConnection) {
|
||||||
|
if($imapStream && (!is_resource($imapStream) || !imap_ping($imapStream))) {
|
||||||
|
$this->disconnect();
|
||||||
|
$imapStream = null;
|
||||||
|
}
|
||||||
|
if(!$imapStream) {
|
||||||
|
$imapStream = $this->initImapStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $imapStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function initImapStream() {
|
||||||
|
$imapStream = @imap_open($this->imapPath, $this->imapLogin, $this->imapPassword, $this->imapOptions, $this->imapRetriesNum, $this->imapParams);
|
||||||
|
if(!$imapStream) {
|
||||||
|
throw new Exception('Connection error: ' . imap_last_error());
|
||||||
|
}
|
||||||
|
return $imapStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function disconnect() {
|
||||||
|
$imapStream = $this->getImapStream(false);
|
||||||
|
if($imapStream && is_resource($imapStream)) {
|
||||||
|
imap_close($imapStream, CL_EXPUNGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get information about the current mailbox.
|
||||||
|
*
|
||||||
|
* Returns the information in an object with following properties:
|
||||||
|
* Date - current system time formatted according to RFC2822
|
||||||
|
* Driver - protocol used to access this mailbox: POP3, IMAP, NNTP
|
||||||
|
* Mailbox - the mailbox name
|
||||||
|
* Nmsgs - number of mails in the mailbox
|
||||||
|
* Recent - number of recent mails in the mailbox
|
||||||
|
*
|
||||||
|
* @return stdClass
|
||||||
|
*/
|
||||||
|
public function checkMailbox() {
|
||||||
|
return imap_check($this->getImapStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new mailbox specified by mailbox.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function createMailbox() {
|
||||||
|
return imap_createmailbox($this->getImapStream(), imap_utf7_encode($this->imapPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets status information about the given mailbox.
|
||||||
|
*
|
||||||
|
* This function returns an object containing status information.
|
||||||
|
* The object has the following properties: messages, recent, unseen, uidnext, and uidvalidity.
|
||||||
|
*
|
||||||
|
* @return stdClass if the box doesn't exist
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function statusMailbox() {
|
||||||
|
return imap_status($this->getImapStream(), $this->imapPath, SA_ALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets listing the folders
|
||||||
|
*
|
||||||
|
* This function returns an object containing listing the folders.
|
||||||
|
* The object has the following properties: messages, recent, unseen, uidnext, and uidvalidity.
|
||||||
|
*
|
||||||
|
* @return array listing the folders
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function getListingFolders() {
|
||||||
|
$folders = imap_list($this->getImapStream(), $this->imapPath, "*");
|
||||||
|
foreach ($folders as $key => $folder)
|
||||||
|
{
|
||||||
|
$folder = str_replace($this->imapPath, "", imap_utf7_decode($folder));
|
||||||
|
$folders[$key] = $folder;
|
||||||
|
}
|
||||||
|
return $folders;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function performs a search on the mailbox currently opened in the given IMAP stream.
|
||||||
|
* For example, to match all unanswered mails sent by Mom, you'd use: "UNANSWERED FROM mom".
|
||||||
|
* Searches appear to be case insensitive. This list of criteria is from a reading of the UW
|
||||||
|
* c-client source code and may be incomplete or inaccurate (see also RFC2060, section 6.4.4).
|
||||||
|
*
|
||||||
|
* @param string $criteria String, delimited by spaces, in which the following keywords are allowed. Any multi-word arguments (e.g. FROM "joey smith") must be quoted. Results will match all criteria entries.
|
||||||
|
* ALL - return all mails matching the rest of the criteria
|
||||||
|
* ANSWERED - match mails with the \\ANSWERED flag set
|
||||||
|
* BCC "string" - match mails with "string" in the Bcc: field
|
||||||
|
* BEFORE "date" - match mails with Date: before "date"
|
||||||
|
* BODY "string" - match mails with "string" in the body of the mail
|
||||||
|
* CC "string" - match mails with "string" in the Cc: field
|
||||||
|
* DELETED - match deleted mails
|
||||||
|
* FLAGGED - match mails with the \\FLAGGED (sometimes referred to as Important or Urgent) flag set
|
||||||
|
* FROM "string" - match mails with "string" in the From: field
|
||||||
|
* KEYWORD "string" - match mails with "string" as a keyword
|
||||||
|
* NEW - match new mails
|
||||||
|
* OLD - match old mails
|
||||||
|
* ON "date" - match mails with Date: matching "date"
|
||||||
|
* RECENT - match mails with the \\RECENT flag set
|
||||||
|
* SEEN - match mails that have been read (the \\SEEN flag is set)
|
||||||
|
* SINCE "date" - match mails with Date: after "date"
|
||||||
|
* SUBJECT "string" - match mails with "string" in the Subject:
|
||||||
|
* TEXT "string" - match mails with text "string"
|
||||||
|
* TO "string" - match mails with "string" in the To:
|
||||||
|
* UNANSWERED - match mails that have not been answered
|
||||||
|
* UNDELETED - match mails that are not deleted
|
||||||
|
* UNFLAGGED - match mails that are not flagged
|
||||||
|
* UNKEYWORD "string" - match mails that do not have the keyword "string"
|
||||||
|
* UNSEEN - match mails which have not been read yet
|
||||||
|
*
|
||||||
|
* @return array Mails ids
|
||||||
|
*/
|
||||||
|
public function searchMailbox($criteria = 'ALL') {
|
||||||
|
$mailsIds = imap_search($this->getImapStream(), $criteria, SE_UID, $this->serverEncoding);
|
||||||
|
return $mailsIds ? $mailsIds : array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save mail body.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function saveMail($mailId, $filename = 'email.eml') {
|
||||||
|
return imap_savebody($this->getImapStream(), $filename, $mailId, "", FT_UID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks mails listed in mailId for deletion.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function deleteMail($mailId) {
|
||||||
|
return imap_delete($this->getImapStream(), $mailId, FT_UID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function moveMail($mailId, $mailBox) {
|
||||||
|
return imap_mail_move($this->getImapStream(), $mailId, $mailBox, CP_UID) && $this->expungeDeletedMails();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes all the mails marked for deletion by imap_delete(), imap_mail_move(), or imap_setflag_full().
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function expungeDeletedMails() {
|
||||||
|
return imap_expunge($this->getImapStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the flag \Seen to a mail.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function markMailAsRead($mailId) {
|
||||||
|
return $this->setFlag(array($mailId), '\\Seen');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the flag \Seen from a mail.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function markMailAsUnread($mailId) {
|
||||||
|
return $this->clearFlag(array($mailId), '\\Seen');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the flag \Flagged to a mail.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function markMailAsImportant($mailId) {
|
||||||
|
return $this->setFlag(array($mailId), '\\Flagged');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the flag \Seen to a mails.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function markMailsAsRead(array $mailId) {
|
||||||
|
return $this->setFlag($mailId, '\\Seen');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the flag \Seen from some mails.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function markMailsAsUnread(array $mailId) {
|
||||||
|
return $this->clearFlag($mailId, '\\Seen');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the flag \Flagged to some mails.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function markMailsAsImportant(array $mailId) {
|
||||||
|
return $this->setFlag($mailId, '\\Flagged');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Causes a store to add the specified flag to the flags set for the mails in the specified sequence.
|
||||||
|
*
|
||||||
|
* @param array $mailsIds
|
||||||
|
* @param string $flag which you can set are \Seen, \Answered, \Flagged, \Deleted, and \Draft as defined by RFC2060.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function setFlag(array $mailsIds, $flag) {
|
||||||
|
return imap_setflag_full($this->getImapStream(), implode(',', $mailsIds), $flag, ST_UID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cause a store to delete the specified flag to the flags set for the mails in the specified sequence.
|
||||||
|
*
|
||||||
|
* @param array $mailsIds
|
||||||
|
* @param string $flag which you can set are \Seen, \Answered, \Flagged, \Deleted, and \Draft as defined by RFC2060.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function clearFlag(array $mailsIds, $flag) {
|
||||||
|
return imap_clearflag_full($this->getImapStream(), implode(',', $mailsIds), $flag, ST_UID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch mail headers for listed mails ids
|
||||||
|
*
|
||||||
|
* Returns an array of objects describing one mail header each. The object will only define a property if it exists. The possible properties are:
|
||||||
|
* subject - the mails subject
|
||||||
|
* from - who sent it
|
||||||
|
* to - recipient
|
||||||
|
* date - when was it sent
|
||||||
|
* message_id - Mail-ID
|
||||||
|
* references - is a reference to this mail id
|
||||||
|
* in_reply_to - is a reply to this mail id
|
||||||
|
* size - size in bytes
|
||||||
|
* uid - UID the mail has in the mailbox
|
||||||
|
* msgno - mail sequence number in the mailbox
|
||||||
|
* recent - this mail is flagged as recent
|
||||||
|
* flagged - this mail is flagged
|
||||||
|
* answered - this mail is flagged as answered
|
||||||
|
* deleted - this mail is flagged for deletion
|
||||||
|
* seen - this mail is flagged as already read
|
||||||
|
* draft - this mail is flagged as being a draft
|
||||||
|
*
|
||||||
|
* @param array $mailsIds
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getMailsInfo(array $mailsIds) {
|
||||||
|
$mails = imap_fetch_overview($this->getImapStream(), implode(',', $mailsIds), FT_UID);
|
||||||
|
if(is_array($mails) && count($mails))
|
||||||
|
{
|
||||||
|
foreach($mails as &$mail)
|
||||||
|
{
|
||||||
|
if(isset($mail->subject)) {
|
||||||
|
$mail->subject = $this->decodeMimeStr($mail->subject, $this->serverEncoding);
|
||||||
|
}
|
||||||
|
if(isset($mail->from)) {
|
||||||
|
$mail->from = $this->decodeMimeStr($mail->from, $this->serverEncoding);
|
||||||
|
}
|
||||||
|
if(isset($mail->to)) {
|
||||||
|
$mail->to = $this->decodeMimeStr($mail->to, $this->serverEncoding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $mails;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get information about the current mailbox.
|
||||||
|
*
|
||||||
|
* Returns an object with following properties:
|
||||||
|
* Date - last change (current datetime)
|
||||||
|
* Driver - driver
|
||||||
|
* Mailbox - name of the mailbox
|
||||||
|
* Nmsgs - number of messages
|
||||||
|
* Recent - number of recent messages
|
||||||
|
* Unread - number of unread messages
|
||||||
|
* Deleted - number of deleted messages
|
||||||
|
* Size - mailbox size
|
||||||
|
*
|
||||||
|
* @return object Object with info | FALSE on failure
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function getMailboxInfo() {
|
||||||
|
return imap_mailboxmsginfo($this->getImapStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets mails ids sorted by some criteria
|
||||||
|
*
|
||||||
|
* Criteria can be one (and only one) of the following constants:
|
||||||
|
* SORTDATE - mail Date
|
||||||
|
* SORTARRIVAL - arrival date (default)
|
||||||
|
* SORTFROM - mailbox in first From address
|
||||||
|
* SORTSUBJECT - mail subject
|
||||||
|
* SORTTO - mailbox in first To address
|
||||||
|
* SORTCC - mailbox in first cc address
|
||||||
|
* SORTSIZE - size of mail in octets
|
||||||
|
*
|
||||||
|
* @param int $criteria
|
||||||
|
* @param bool $reverse
|
||||||
|
* @return array Mails ids
|
||||||
|
*/
|
||||||
|
public function sortMails($criteria = SORTARRIVAL, $reverse = true) {
|
||||||
|
return imap_sort($this->getImapStream(), $criteria, $reverse, SE_UID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get mails count in mail box
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function countMails() {
|
||||||
|
return imap_num_msg($this->getImapStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the quota settings per user
|
||||||
|
* @return array - FALSE in the case of call failure
|
||||||
|
*/
|
||||||
|
protected function getQuota() {
|
||||||
|
return imap_get_quotaroot($this->getImapStream(), 'INBOX');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return quota limit in KB
|
||||||
|
* @return int - FALSE in the case of call failure
|
||||||
|
*/
|
||||||
|
public function getQuotaLimit() {
|
||||||
|
$quota = $this->getQuota();
|
||||||
|
if(is_array($quota)) {
|
||||||
|
$quota = $quota['STORAGE']['limit'];
|
||||||
|
}
|
||||||
|
return $quota;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return quota usage in KB
|
||||||
|
* @return int - FALSE in the case of call failure
|
||||||
|
*/
|
||||||
|
public function getQuotaUsage() {
|
||||||
|
$quota = $this->getQuota();
|
||||||
|
if(is_array($quota)) {
|
||||||
|
$quota = $quota['STORAGE']['usage'];
|
||||||
|
}
|
||||||
|
return $quota;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get mail data
|
||||||
|
*
|
||||||
|
* @param $mailId
|
||||||
|
* @param bool $markAsSeen
|
||||||
|
* @return IncomingMail
|
||||||
|
*/
|
||||||
|
public function getMail($mailId, $markAsSeen = true) {
|
||||||
|
$head = imap_rfc822_parse_headers(imap_fetchheader($this->getImapStream(), $mailId, FT_UID));
|
||||||
|
|
||||||
|
$mail = new IncomingMail();
|
||||||
|
$mail->id = $mailId;
|
||||||
|
$mail->date = date('Y-m-d H:i:s', isset($head->date) ? strtotime(preg_replace('/\(.*?\)/', '', $head->date)) : time());
|
||||||
|
$mail->subject = isset($head->subject) ? $this->decodeMimeStr($head->subject, $this->serverEncoding) : null;
|
||||||
|
$mail->fromName = isset($head->from[0]->personal) ? $this->decodeMimeStr($head->from[0]->personal, $this->serverEncoding) : null;
|
||||||
|
$mail->fromAddress = strtolower($head->from[0]->mailbox . '@' . $head->from[0]->host);
|
||||||
|
|
||||||
|
if(isset($head->to)) {
|
||||||
|
$toStrings = array();
|
||||||
|
foreach($head->to as $to) {
|
||||||
|
if(!empty($to->mailbox) && !empty($to->host)) {
|
||||||
|
$toEmail = strtolower($to->mailbox . '@' . $to->host);
|
||||||
|
$toName = isset($to->personal) ? $this->decodeMimeStr($to->personal, $this->serverEncoding) : null;
|
||||||
|
$toStrings[] = $toName ? "$toName <$toEmail>" : $toEmail;
|
||||||
|
$mail->to[$toEmail] = $toName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$mail->toString = implode(', ', $toStrings);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isset($head->cc)) {
|
||||||
|
foreach($head->cc as $cc) {
|
||||||
|
$mail->cc[strtolower($cc->mailbox . '@' . $cc->host)] = isset($cc->personal) ? $this->decodeMimeStr($cc->personal, $this->serverEncoding) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isset($head->reply_to)) {
|
||||||
|
foreach($head->reply_to as $replyTo) {
|
||||||
|
$mail->replyTo[strtolower($replyTo->mailbox . '@' . $replyTo->host)] = isset($replyTo->personal) ? $this->decodeMimeStr($replyTo->personal, $this->serverEncoding) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isset($head->message_id)) {
|
||||||
|
$mail->messageId = $head->message_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mailStructure = imap_fetchstructure($this->getImapStream(), $mailId, FT_UID);
|
||||||
|
|
||||||
|
if(empty($mailStructure->parts)) {
|
||||||
|
$this->initMailPart($mail, $mailStructure, 0, $markAsSeen);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
foreach($mailStructure->parts as $partNum => $partStructure) {
|
||||||
|
$this->initMailPart($mail, $partStructure, $partNum + 1, $markAsSeen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $mail;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function initMailPart(IncomingMail $mail, $partStructure, $partNum, $markAsSeen = true) {
|
||||||
|
$options = FT_UID;
|
||||||
|
if(!$markAsSeen) {
|
||||||
|
$options |= FT_PEEK;
|
||||||
|
}
|
||||||
|
$data = $partNum ? imap_fetchbody($this->getImapStream(), $mail->id, $partNum, $options) : imap_body($this->getImapStream(), $mail->id, $options);
|
||||||
|
|
||||||
|
if($partStructure->encoding == 1) {
|
||||||
|
$data = imap_utf8($data);
|
||||||
|
}
|
||||||
|
elseif($partStructure->encoding == 2) {
|
||||||
|
$data = imap_binary($data);
|
||||||
|
}
|
||||||
|
elseif($partStructure->encoding == 3) {
|
||||||
|
$data = preg_replace('~[^a-zA-Z0-9+=/]+~s', '', $data); // https://github.com/barbushin/php-imap/issues/88
|
||||||
|
$data = imap_base64($data);
|
||||||
|
}
|
||||||
|
elseif($partStructure->encoding == 4) {
|
||||||
|
$data = quoted_printable_decode($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$params = array();
|
||||||
|
if(!empty($partStructure->parameters)) {
|
||||||
|
foreach($partStructure->parameters as $param) {
|
||||||
|
$params[strtolower($param->attribute)] = $param->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!empty($partStructure->dparameters)) {
|
||||||
|
foreach($partStructure->dparameters as $param) {
|
||||||
|
$paramName = strtolower(preg_match('~^(.*?)\*~', $param->attribute, $matches) ? $matches[1] : $param->attribute);
|
||||||
|
if(isset($params[$paramName])) {
|
||||||
|
$params[$paramName] .= $param->value;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$params[$paramName] = $param->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// attachments
|
||||||
|
$attachmentId = $partStructure->ifid
|
||||||
|
? trim($partStructure->id, " <>")
|
||||||
|
: (isset($params['filename']) || isset($params['name']) ? mt_rand() . mt_rand() : null);
|
||||||
|
|
||||||
|
if($attachmentId) {
|
||||||
|
if(empty($params['filename']) && empty($params['name'])) {
|
||||||
|
$fileName = $attachmentId . '.' . strtolower($partStructure->subtype);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$fileName = !empty($params['filename']) ? $params['filename'] : $params['name'];
|
||||||
|
$fileName = $this->decodeMimeStr($fileName, $this->serverEncoding);
|
||||||
|
$fileName = $this->decodeRFC2231($fileName, $this->serverEncoding);
|
||||||
|
}
|
||||||
|
$attachment = new IncomingMailAttachment();
|
||||||
|
$attachment->id = $attachmentId;
|
||||||
|
$attachment->name = $fileName;
|
||||||
|
if($this->attachmentsDir) {
|
||||||
|
$replace = array(
|
||||||
|
'/\s/' => '_',
|
||||||
|
'/[^0-9a-zа-яіїє_\.]/iu' => '',
|
||||||
|
'/_+/' => '_',
|
||||||
|
'/(^_)|(_$)/' => '',
|
||||||
|
);
|
||||||
|
$fileSysName = preg_replace('~[\\\\/]~', '', $mail->id . '_' . $attachmentId . '_' . preg_replace(array_keys($replace), $replace, $fileName));
|
||||||
|
$attachment->filePath = $this->attachmentsDir . DIRECTORY_SEPARATOR . $fileSysName;
|
||||||
|
file_put_contents($attachment->filePath, $data);
|
||||||
|
}
|
||||||
|
$mail->addAttachment($attachment);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(!empty($params['charset'])) {
|
||||||
|
$data = $this->convertStringEncoding($data, $params['charset'], $this->serverEncoding);
|
||||||
|
}
|
||||||
|
if($partStructure->type == 0 && $data) {
|
||||||
|
if(strtolower($partStructure->subtype) == 'plain') {
|
||||||
|
$mail->textPlain .= $data;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$mail->textHtml .= $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif($partStructure->type == 2 && $data) {
|
||||||
|
$mail->textPlain .= trim($data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!empty($partStructure->parts)) {
|
||||||
|
foreach($partStructure->parts as $subPartNum => $subPartStructure) {
|
||||||
|
if($partStructure->type == 2 && $partStructure->subtype == 'RFC822') {
|
||||||
|
$this->initMailPart($mail, $subPartStructure, $partNum, $markAsSeen);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->initMailPart($mail, $subPartStructure, $partNum . '.' . ($subPartNum + 1), $markAsSeen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function decodeMimeStr($string, $charset = 'utf-8') {
|
||||||
|
$newString = '';
|
||||||
|
$elements = imap_mime_header_decode($string);
|
||||||
|
for($i = 0; $i < count($elements); $i++) {
|
||||||
|
if($elements[$i]->charset == 'default') {
|
||||||
|
$elements[$i]->charset = 'iso-8859-1';
|
||||||
|
}
|
||||||
|
$newString .= $this->convertStringEncoding($elements[$i]->text, $elements[$i]->charset, $charset);
|
||||||
|
}
|
||||||
|
return $newString;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUrlEncoded($string) {
|
||||||
|
$hasInvalidChars = preg_match( '#[^%a-zA-Z0-9\-_\.\+]#', $string );
|
||||||
|
$hasEscapedChars = preg_match( '#%[a-zA-Z0-9]{2}#', $string );
|
||||||
|
return !$hasInvalidChars && $hasEscapedChars;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function decodeRFC2231($string, $charset = 'utf-8') {
|
||||||
|
if(preg_match("/^(.*?)'.*?'(.*?)$/", $string, $matches)) {
|
||||||
|
$encoding = $matches[1];
|
||||||
|
$data = $matches[2];
|
||||||
|
if($this->isUrlEncoded($data)) {
|
||||||
|
$string = $this->convertStringEncoding(urldecode($data), $encoding, $charset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a string from one encoding to another.
|
||||||
|
* @param string $string
|
||||||
|
* @param string $fromEncoding
|
||||||
|
* @param string $toEncoding
|
||||||
|
* @return string Converted string if conversion was successful, or the original string if not
|
||||||
|
*/
|
||||||
|
protected function convertStringEncoding($string, $fromEncoding, $toEncoding) {
|
||||||
|
$convertedString = null;
|
||||||
|
if($string && $fromEncoding != $toEncoding) {
|
||||||
|
$convertedString = @iconv($fromEncoding, $toEncoding . '//IGNORE', $string);
|
||||||
|
if(!$convertedString && extension_loaded('mbstring')) {
|
||||||
|
$convertedString = @mb_convert_encoding($string, $toEncoding, $fromEncoding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $convertedString ?: $string;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct() {
|
||||||
|
$this->disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Exception extends \Exception {
|
||||||
|
|
||||||
|
}
|
8
dist/backend-libs/php-imap/php-imap/src/PhpImap/__autoload.php
vendored
Normal file
8
dist/backend-libs/php-imap/php-imap/src/PhpImap/__autoload.php
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?php namespace PhpImap;
|
||||||
|
|
||||||
|
spl_autoload_register(function ($class) {
|
||||||
|
if(strpos($class, __NAMESPACE__) === 0) {
|
||||||
|
/** @noinspection PhpIncludeInspection */
|
||||||
|
require_once(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php');
|
||||||
|
}
|
||||||
|
});
|
113
dist/backend.php
vendored
Normal file
113
dist/backend.php
vendored
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
<?php
|
||||||
|
require_once './config.php';
|
||||||
|
|
||||||
|
# load php dependencies:
|
||||||
|
require_once './backend-libs/autoload.php';
|
||||||
|
|
||||||
|
$imap_settings = $config['imap'];
|
||||||
|
$mailbox = new PhpImap\Mailbox($imap_settings['url'], $imap_settings['username'], $imap_settings['password']);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* print error and stop program.
|
||||||
|
* @param $status http status
|
||||||
|
* @param $text error text
|
||||||
|
*/
|
||||||
|
function error($status, $text) {
|
||||||
|
@http_response_code($status);
|
||||||
|
@print("{\"error\": \"$text\"}");
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* print all mails for the given $user as a json string.
|
||||||
|
* @param $username
|
||||||
|
*/
|
||||||
|
function print_inbox($username) {
|
||||||
|
global $mailbox, $config;
|
||||||
|
|
||||||
|
$name = clean_name($username);
|
||||||
|
if (strlen($name) === 0) {
|
||||||
|
error(400, 'invalid username');
|
||||||
|
}
|
||||||
|
$to = get_address($name, $config['mailHostname']);
|
||||||
|
$mail_ids = search_mails($to, $mailbox);
|
||||||
|
|
||||||
|
$emails = array();
|
||||||
|
foreach ($mail_ids as $id) {
|
||||||
|
$emails[] = $mailbox->getMail($id);
|
||||||
|
}
|
||||||
|
$address = get_address($name, $config['mailHostname']);
|
||||||
|
$data = array("mails" => $emails, 'username' => $name, 'address' => $address);
|
||||||
|
print(json_encode($data));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for mails with the recipient $to.
|
||||||
|
* @return array mail ids
|
||||||
|
*/
|
||||||
|
function search_mails($to, $mailbox) {
|
||||||
|
$filterTO = 'TO "' . $to . '"';
|
||||||
|
$filterCC = 'CC "' . $to . '"';
|
||||||
|
$mailsIdsTo = imap_sort($mailbox->getImapStream(), SORTARRIVAL, true, SE_UID, $filterTO);
|
||||||
|
$mailsIdsCc = imap_sort($mailbox->getImapStream(), SORTARRIVAL, true, SE_UID, $filterCC);
|
||||||
|
return array_merge($mailsIdsTo, $mailsIdsCc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove illegal characters from username and remove everything after the @-sign. You may extend it if your server supports them.
|
||||||
|
* @param $username
|
||||||
|
* @return clean username
|
||||||
|
*/
|
||||||
|
function clean_name($username) {
|
||||||
|
$username = preg_replace('/@.*$/', "", $username); // remove part after @
|
||||||
|
$username = preg_replace('/[^A-Za-z0-9_.+-]/', "", $username); // remove special characters
|
||||||
|
return $username;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates the full email address
|
||||||
|
* @param $username
|
||||||
|
* @param $domain
|
||||||
|
* @return $username@$domain
|
||||||
|
*/
|
||||||
|
function get_address($username, $domain) {
|
||||||
|
return $username . "@" . $domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* deletes messages older than X days.
|
||||||
|
*/
|
||||||
|
function delete_old_messages() {
|
||||||
|
global $mailbox;
|
||||||
|
|
||||||
|
$date = date('d-M-Y', strtotime('30 days ago'));
|
||||||
|
$ids = $mailbox->searchMailbox('BEFORE ' . $date);
|
||||||
|
foreach ($ids as $id) {
|
||||||
|
$mailbox->deleteMail($id);
|
||||||
|
}
|
||||||
|
$mailbox->expungeDeletedMails();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
header('Content-type: application/json');
|
||||||
|
|
||||||
|
// Never cache requests:
|
||||||
|
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
|
||||||
|
header("Cache-Control: post-check=0, pre-check=0", false);
|
||||||
|
header("Pragma: no-cache");
|
||||||
|
|
||||||
|
if (!isset($_GET['action'])) {
|
||||||
|
error(400, 'invalid parameter');
|
||||||
|
}
|
||||||
|
$action = $_GET['action'];
|
||||||
|
|
||||||
|
if ($action === "get" && isset($_GET['username'])) {
|
||||||
|
print_inbox($_GET['username']);
|
||||||
|
} else {
|
||||||
|
error(400, 'invalid action');
|
||||||
|
}
|
||||||
|
|
||||||
|
// run on every request
|
||||||
|
delete_old_messages();
|
15
dist/config.sample.php
vendored
Normal file
15
dist/config.sample.php
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
date_default_timezone_set('Europe/Paris');
|
||||||
|
error_reporting(0);
|
||||||
|
|
||||||
|
// configure this option if you want to allow requests from clients from other domains:
|
||||||
|
// see https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
|
||||||
|
// header("Access-Control-Allow-Origin: *");
|
||||||
|
|
||||||
|
// setup imap connection
|
||||||
|
$config['imap']['host'] = "localhost";
|
||||||
|
$config['imap']['url'] = '{' . $config['imap']['host'] . '/imap/ssl}INBOX';
|
||||||
|
$config['imap']['username'] = "test";
|
||||||
|
$config['imap']['password'] = "test";
|
||||||
|
$config['mailHostname'] = "example.com";
|
49
dist/index.html
vendored
Normal file
49
dist/index.html
vendored
Normal file
File diff suppressed because one or more lines are too long
80
gulpfile.babel.js
Normal file
80
gulpfile.babel.js
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import gulp from 'gulp';
|
||||||
|
import path from 'path';
|
||||||
|
import webpack from 'webpack';
|
||||||
|
import WebpackDevServer from 'webpack-dev-server';
|
||||||
|
import karma from 'karma';
|
||||||
|
import ip from 'ip';
|
||||||
|
import webpackConfig from './webpack.config';
|
||||||
|
import fileInline from 'gulp-file-inline';
|
||||||
|
|
||||||
|
let paths = {
|
||||||
|
build: path.join(__dirname, 'target/build')
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gulp-Task: Fuehrt webpack aus und startet den Development-Server
|
||||||
|
*/
|
||||||
|
gulp.task('dev-server', () => {
|
||||||
|
return new WebpackDevServer(webpack(webpackConfig.development), {
|
||||||
|
hot: true,
|
||||||
|
contentBase: './dist/',
|
||||||
|
watchOptions: {
|
||||||
|
aggregateTimeout: 100, poll: 300
|
||||||
|
}, stats: {
|
||||||
|
colors: true
|
||||||
|
}
|
||||||
|
}).listen(3000, 'localhost', function (err) {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gulp-Task: Fuehrt die Karma-Tests auf dem PhantomJS Browser aus
|
||||||
|
*/
|
||||||
|
gulp.task('test-phantomjs', (done) => {
|
||||||
|
|
||||||
|
let hostname = process.env.host || ip.address();
|
||||||
|
let externalport = process.env.externalport || 7777;
|
||||||
|
|
||||||
|
return new karma.Server({
|
||||||
|
configFile: __dirname + '/karma.conf.js',
|
||||||
|
hostname: hostname,
|
||||||
|
port: externalport,
|
||||||
|
browsers: ['PhantomJS']
|
||||||
|
}, done).start();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
gulp.task('webpack-prod', [], (done) => {
|
||||||
|
return webpack(webpackConfig.production, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
let inlineScript = function () {
|
||||||
|
return gulp
|
||||||
|
.src(path.join('target', 'build', 'index.html'))
|
||||||
|
.pipe(fileInline({
|
||||||
|
css: {
|
||||||
|
minify: false
|
||||||
|
},
|
||||||
|
js: {
|
||||||
|
minify: false
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.pipe(gulp.dest('dist'))
|
||||||
|
};
|
||||||
|
|
||||||
|
gulp.task('build', ['test-phantomjs', 'webpack-prod'], (done) => {
|
||||||
|
return inlineScript();
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('build-skipTests', ['webpack-prod'], (done) => {
|
||||||
|
return inlineScript();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
gulp.task('default', ['dev-server']);
|
112
karma.conf.js
Normal file
112
karma.conf.js
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
var ip = require('ip');
|
||||||
|
var webjsConfig = require('./shared.build.config');
|
||||||
|
|
||||||
|
module.exports = function (config) {
|
||||||
|
|
||||||
|
var seleniumWebgrid = {
|
||||||
|
hostname: 'webtestgrid.myhost.com',
|
||||||
|
port: 4444
|
||||||
|
};
|
||||||
|
|
||||||
|
config.set({
|
||||||
|
|
||||||
|
hostname: ip.address(),
|
||||||
|
|
||||||
|
basePath: __dirname,
|
||||||
|
|
||||||
|
// frameworks to use
|
||||||
|
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
||||||
|
frameworks: ['jasmine'],
|
||||||
|
|
||||||
|
/* list of files/patterns to load in the browser
|
||||||
|
Fuer den Phantomjs wird fuer die Methode 'bind' ein Polyfill geladen
|
||||||
|
da der Browser die Methode nicht kennt und diese von den Frameworks verwendet wird */
|
||||||
|
files: [
|
||||||
|
'./node_modules/phantomjs-polyfill/bind-polyfill.js', {
|
||||||
|
pattern: 'spec.bundle.js', watched: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
// files to exclude
|
||||||
|
exclude: [],
|
||||||
|
|
||||||
|
// preprocess matching files before serving them to the browser
|
||||||
|
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
||||||
|
preprocessors: {
|
||||||
|
'spec.bundle.js': ['webpack', 'sourcemap', 'coverage']
|
||||||
|
},
|
||||||
|
|
||||||
|
webpack: {
|
||||||
|
debug: true, devtool: 'inline-source-map', module: {
|
||||||
|
loaders: webjsConfig.webpackLoaders
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
webpackServer: {
|
||||||
|
// prevent console spamming when running in Karma!
|
||||||
|
noInfo: true
|
||||||
|
},
|
||||||
|
|
||||||
|
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
||||||
|
reporters: ['progress', 'junit', 'coverage'],
|
||||||
|
|
||||||
|
// enable colors in the output
|
||||||
|
colors: true,
|
||||||
|
|
||||||
|
// level of logging
|
||||||
|
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
||||||
|
logLevel: config.LOG_INFO,
|
||||||
|
|
||||||
|
// toggle whether to watch files and rerun tests upon incurring changes
|
||||||
|
autoWatch: false,
|
||||||
|
|
||||||
|
// Browser-Konfiguration auf dem Selenium Grid
|
||||||
|
customLaunchers: {
|
||||||
|
'SeleniumCH': {
|
||||||
|
base: 'WebDriver',
|
||||||
|
config: seleniumWebgrid,
|
||||||
|
browserName: 'chrome'
|
||||||
|
},
|
||||||
|
'SeleniumFF': {
|
||||||
|
base: 'WebDriver',
|
||||||
|
config: seleniumWebgrid,
|
||||||
|
browserName: 'firefox'
|
||||||
|
},
|
||||||
|
'SeleniumIE': {
|
||||||
|
base: 'WebDriver',
|
||||||
|
config: seleniumWebgrid,
|
||||||
|
browserName: 'internet explorer',
|
||||||
|
'x-ua-compatible': 'IE=edge'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// start these browsers
|
||||||
|
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
|
||||||
|
browsers: ['PhantomJS'], // Test auf dem PhantomJS
|
||||||
|
// browsers: ['SeleniumFF', 'SeleniumCH', 'SeleniumIE'], // Test auf dem Selenium-Webgrid
|
||||||
|
|
||||||
|
// if true, Karma runs tests once and exits
|
||||||
|
singleRun: true,
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
'karma-junit-reporter',
|
||||||
|
'karma-jasmine',
|
||||||
|
'karma-coverage',
|
||||||
|
'karma-phantomjs-launcher',
|
||||||
|
'karma-webpack',
|
||||||
|
'karma-sourcemap-loader',
|
||||||
|
'karma-webdriver-launcher'
|
||||||
|
],
|
||||||
|
|
||||||
|
// Coverage & JUnit Report fuer SonarQube
|
||||||
|
junitReporter: {
|
||||||
|
outputDir: 'target/surefire', suite: 'unit'
|
||||||
|
}, coverageReporter: {
|
||||||
|
reporters: [
|
||||||
|
{
|
||||||
|
type: 'lcov', dir: 'target/surefire', subdir: '.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
60
package.json
Normal file
60
package.json
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
{
|
||||||
|
"name": "disposable-mailbox",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "disposable-mailbox",
|
||||||
|
"homepage": "https://github.com/synox/disposable-mailbox",
|
||||||
|
"dependencies": {
|
||||||
|
"angular": "^1.5.2",
|
||||||
|
"angular-resource": "^1.5.2",
|
||||||
|
"angular-sanitize": "^1.5.6",
|
||||||
|
"angular-ui-router": "^1.0.0-beta.1",
|
||||||
|
"angular-ui-bootstrap": "^1.3.3",
|
||||||
|
"autolinker": "^0.27.0",
|
||||||
|
"babel-polyfill": "^6.9.1",
|
||||||
|
"bootstrap": "^3.3.6",
|
||||||
|
"chance": "^1.0.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"angular-mocks": "^1.5.2",
|
||||||
|
"babel-core": "^6.10.4",
|
||||||
|
"babel-loader": "^6.2.4",
|
||||||
|
"babel-preset-es2015": "^6.9.0",
|
||||||
|
"browser-sync": "^2.10.0",
|
||||||
|
"browser-sync-webpack-plugin": "^1.0.0",
|
||||||
|
"css-loader": "^0.23.1",
|
||||||
|
"file-loader": "^0.9.0",
|
||||||
|
"gulp": "^3.9.0",
|
||||||
|
"gulp-esdoc": "^0.2.0",
|
||||||
|
"gulp-file-inline": "^1.3.6",
|
||||||
|
"html-loader": "^0.4.3",
|
||||||
|
"html-webpack-plugin": "^2.15.0",
|
||||||
|
"ip": "^1.0.2",
|
||||||
|
"isparta-loader": "^2.0.0",
|
||||||
|
"jasmine-core": "^2.3.4",
|
||||||
|
"json-loader": "^0.5.3",
|
||||||
|
"karma": "^1.1.0",
|
||||||
|
"karma-coverage": "^1.0.0",
|
||||||
|
"karma-jasmine": "~1.0.2",
|
||||||
|
"karma-junit-reporter": "^1.1.0",
|
||||||
|
"karma-phantomjs-launcher": "^1.0.1",
|
||||||
|
"karma-sourcemap-loader": "^0.3.4",
|
||||||
|
"karma-webdriver-launcher": "^1.0.4",
|
||||||
|
"karma-webpack": "^1.5.1",
|
||||||
|
"ng-annotate-webpack-plugin": "^0.1.2",
|
||||||
|
"node-libs-browser": "^1.0.0",
|
||||||
|
"node.extend": "^1.1.5",
|
||||||
|
"phantomjs": "^2.1.7",
|
||||||
|
"phantomjs-polyfill": "0.0.2",
|
||||||
|
"proxy-middleware": "^0.15.0",
|
||||||
|
"raw-loader": "^0.5.1",
|
||||||
|
"style-loader": "^0.13.0",
|
||||||
|
"url-loader": "^0.5.6",
|
||||||
|
"webpack": "^1.9.5",
|
||||||
|
"webpack-dev-server": "^1.12.1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/synox/disposable-mailbox.git"
|
||||||
|
},
|
||||||
|
"license": "CC-BY-NC-SA-4.0"
|
||||||
|
}
|
65
readme.md
Normal file
65
readme.md
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
# self-hosted disposable email service
|
||||||
|
|
||||||
|
## Goals:
|
||||||
|
* easy to use: generate random name or use custom name, auto refresh
|
||||||
|
* easy to host: just php5 + imap extension
|
||||||
|
* easy to install: just copy files in `dist`
|
||||||
|
* minimal code base: minimal features and complexity
|
||||||
|
|
||||||
|
![screenshot](screenshot.png)
|
||||||
|
|
||||||
|
## Quality/Status:
|
||||||
|
This is **alpha-tested** software, do not use it in production yes, it may lose your mails and people may gain access to your mails. There are still unsolved problems. Contributions are welcome!
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a><br /><span xmlns:dct="http://purl.org/dc/terms/" property="dct:title">disposable-mailbox</span> by <a xmlns:cc="http://creativecommons.org/ns#" href="https://github.com/synox/disposable-mailbox" property="cc:attributionName" rel="cc:attributionURL">github:synox</a> is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License</a>.
|
||||||
|
|
||||||
|
## Webserver requirements
|
||||||
|
|
||||||
|
* php >=5.3.0
|
||||||
|
* [imap extension](http://php.net/manual/book.imap.php)
|
||||||
|
* apache 2 (but should work on any webserver)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. assure the [imap extension](http://php.net/manual/book.imap.php) is installed. The following command should not print any errors:
|
||||||
|
|
||||||
|
<?php print imap_base64("SU1BUCBleHRlbnNpb24gc2VlbXMgdG8gYmUgaW5zdGFsbGVkLiA="); ?>
|
||||||
|
|
||||||
|
2. clone or download this repository
|
||||||
|
3. copy the `dist` directory to your web server.
|
||||||
|
4. rename `config.sample.php` to `config.php` and apply the imap settings. Move `config.php` to a safe location outside the `public_html`.
|
||||||
|
5. open `backend.php` and set the new path to `config.php`.
|
||||||
|
|
||||||
|
|
||||||
|
## Build it yourself
|
||||||
|
Instead of using the files in the `dist` directory you can also build it yourself. You must have [npm](https://docs.npmjs.com/cli/install) and [composer](https://getcomposer.org/download/) installed.
|
||||||
|
|
||||||
|
Install php dependecies:
|
||||||
|
|
||||||
|
composer install
|
||||||
|
|
||||||
|
Install javascript dependencies:
|
||||||
|
|
||||||
|
npm install
|
||||||
|
|
||||||
|
Build frontend:
|
||||||
|
|
||||||
|
gulp build
|
||||||
|
|
||||||
|
The files are written to the `dist` directory.
|
||||||
|
|
||||||
|
There is a [Vagrantfile](Vagrantfile), in case you are familiar with [vagrant](https://www.vagrantup.com/).
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
1. reduce total dist size (<1 MB)
|
||||||
|
1. setup a link redirection provider (to keep the existence of your installation secret)
|
||||||
|
1. maybe make mails collapsible
|
||||||
|
|
||||||
|
## Credit
|
||||||
|
|
||||||
|
This could not be possible without...
|
||||||
|
|
||||||
|
* http://angularjs.org/
|
||||||
|
* https://github.com/SchweizerischeBundesbahnen/esta-webjs
|
||||||
|
* https://github.com/barbushin/php-imap
|
BIN
screenshot.png
Normal file
BIN
screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 59 KiB |
31
shared.build.config.js
Normal file
31
shared.build.config.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* @author u215942 (Stefan Zeller)
|
||||||
|
* @version: 1.0.1
|
||||||
|
* @since 04.04.2016
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.webpackLoaders = [
|
||||||
|
{
|
||||||
|
test: /\.js$/, exclude: [/node_modules/],
|
||||||
|
loader: 'babel',
|
||||||
|
query: {
|
||||||
|
// https://github.com/babel/babel-loader#options
|
||||||
|
cacheDirectory: true,
|
||||||
|
presets: ['es2015']
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
test: /\.json$/, loader: 'json'
|
||||||
|
}, {
|
||||||
|
test: /\.html$/, loader: 'html'
|
||||||
|
}, {
|
||||||
|
test: /\.css$/, loader: 'style!css'
|
||||||
|
}, {
|
||||||
|
test: /\.(jpe?g|png|gif|svg)$/i, loader: 'url'
|
||||||
|
}, {
|
||||||
|
test: /\.(woff|woff2)$/, loader: 'url?mimetype=application/font-woff'
|
||||||
|
}, {
|
||||||
|
test: /\.ttf$/, loader: 'url'
|
||||||
|
}, {
|
||||||
|
test: /\.eot$/, loader: 'url'
|
||||||
|
}
|
||||||
|
];
|
5
spec.bundle.js
Normal file
5
spec.bundle.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import 'angular';
|
||||||
|
import 'angular-mocks';
|
||||||
|
|
||||||
|
let context = require.context('./src/app', true, /\.spec\.js/);
|
||||||
|
context.keys().forEach(context);
|
11
src/app/app.component.js
Normal file
11
src/app/app.component.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import template from './app.html';
|
||||||
|
import './app.css';
|
||||||
|
|
||||||
|
let appComponent = () => {
|
||||||
|
return {
|
||||||
|
template,
|
||||||
|
restrict: 'E'
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default appComponent;
|
8
src/app/app.css
Normal file
8
src/app/app.css
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
body {
|
||||||
|
background: #eeeeee;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer p {
|
||||||
|
margin-top: 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
1
src/app/app.html
Normal file
1
src/app/app.html
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<div ui-view></div>
|
22
src/app/app.js
Normal file
22
src/app/app.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// Vendor-Imports
|
||||||
|
import angular from 'angular';
|
||||||
|
import uiRouter from 'angular-ui-router';
|
||||||
|
import ngResource from 'angular-resource';
|
||||||
|
import uiBootstrap from 'angular-ui-bootstrap';
|
||||||
|
import 'bootstrap/dist/css/bootstrap.css';
|
||||||
|
import 'babel-polyfill';
|
||||||
|
|
||||||
|
// Interne Modul-Imports
|
||||||
|
import Mailbox from './mailbox/mailbox';
|
||||||
|
import Navbar from './navbar/navbar';
|
||||||
|
|
||||||
|
import AppComponent from './app.component';
|
||||||
|
|
||||||
|
angular.module('app', [
|
||||||
|
uiRouter, ngResource, uiBootstrap, Mailbox.name, Navbar.name
|
||||||
|
])
|
||||||
|
.constant('config', {
|
||||||
|
'backend_url': './backend.php',
|
||||||
|
'reload_interval_ms': 10000
|
||||||
|
})
|
||||||
|
.directive('app', AppComponent);
|
8
src/app/mailbox/home/home.controller.js
Normal file
8
src/app/mailbox/home/home.controller.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
class HomeController {
|
||||||
|
/*@ngInject*/
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HomeController;
|
3
src/app/mailbox/home/home.css
Normal file
3
src/app/mailbox/home/home.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
div.home {
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
7
src/app/mailbox/home/home.html
Normal file
7
src/app/mailbox/home/home.html
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<navbar></navbar>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div class="container home">
|
||||||
|
Use the buttons above to create a new inbox, or open a specific mailbox.
|
||||||
|
</div>
|
||||||
|
</main>
|
9
src/app/mailbox/home/home.js
Normal file
9
src/app/mailbox/home/home.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import angular from 'angular';
|
||||||
|
import uiRouter from 'angular-ui-router';
|
||||||
|
|
||||||
|
import template from './home.html';
|
||||||
|
import controller from './home.controller';
|
||||||
|
import './home.css';
|
||||||
|
|
||||||
|
export default angular.module('mailbox.home', [uiRouter])
|
||||||
|
.component('home', {template, controller})
|
37
src/app/mailbox/inbox/inbox.controller.js
Normal file
37
src/app/mailbox/inbox/inbox.controller.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
class MailboxController {
|
||||||
|
/*@ngInject*/
|
||||||
|
constructor($log, $interval, config, mailboxService) {
|
||||||
|
this.$log = $log;
|
||||||
|
this.$interval = $interval;
|
||||||
|
this.config = config;
|
||||||
|
this.mailboxService = mailboxService;
|
||||||
|
this.loadingData = true;
|
||||||
|
this.mails = [];
|
||||||
|
this.address = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$onInit() {
|
||||||
|
this.username = this.mailboxService.getCurrentUsername();
|
||||||
|
this.intervalPromise = this.$interval(() => this.loadMails(), this.config.reload_interval_ms);
|
||||||
|
this.loadMails();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$onDestroy() {
|
||||||
|
this.$log.debug("destroying controller");
|
||||||
|
this.$interval.cancel(this.intervalPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
loadMails() {
|
||||||
|
this.mailboxService.loadEmails(this.username)
|
||||||
|
.then(data => {
|
||||||
|
this.mails = data.mails;
|
||||||
|
this.address = this.mailboxService.getCurrentAddress();
|
||||||
|
this.loadingData = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MailboxController;
|
12
src/app/mailbox/inbox/inbox.css
Normal file
12
src/app/mailbox/inbox/inbox.css
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
.email-table {
|
||||||
|
margin-top: 20px;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.waiting-screen {
|
||||||
|
padding: 40px 15px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
28
src/app/mailbox/inbox/inbox.html
Normal file
28
src/app/mailbox/inbox/inbox.html
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<navbar></navbar>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div class="container">
|
||||||
|
<div ng-show="$ctrl.loadingData" class="waiting-screen">
|
||||||
|
<h1> </h1>
|
||||||
|
<p class="lead">Loading Mails</p>
|
||||||
|
<p><br/>
|
||||||
|
<img src="spinner.gif">
|
||||||
|
<br/>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div ng-hide="$ctrl.loadingData">
|
||||||
|
<div ng-repeat="mail in $ctrl.mails | orderBy:'-date' track by $index" class="email-table">
|
||||||
|
<mail mail="mail"></mail>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="waiting-screen" ng-show="$ctrl.mails.length === 0">
|
||||||
|
<h1>{{$ctrl.address}}</h1>
|
||||||
|
<p class="lead">Inbox is empty.</p>
|
||||||
|
<p><br/>
|
||||||
|
<img src="spinner.gif">
|
||||||
|
<br/></p>
|
||||||
|
<p class="lead">Emails to {{address}} will be automatically displayed on this page. </p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
16
src/app/mailbox/inbox/inbox.js
Normal file
16
src/app/mailbox/inbox/inbox.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import angular from 'angular';
|
||||||
|
import uiRouter from 'angular-ui-router';
|
||||||
|
|
||||||
|
import template from './inbox.html';
|
||||||
|
import controller from './inbox.controller';
|
||||||
|
import './inbox.css';
|
||||||
|
import Mail from './mail/mail'
|
||||||
|
|
||||||
|
export default angular.module('mailbox.inbox', [uiRouter, Mail.name])
|
||||||
|
.component('inbox', {
|
||||||
|
template,
|
||||||
|
controller,
|
||||||
|
bindings: {
|
||||||
|
data: '<'
|
||||||
|
}
|
||||||
|
})
|
4
src/app/mailbox/inbox/inbox.spec.js
Normal file
4
src/app/mailbox/inbox/inbox.spec.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
describe('not tests', () => {
|
||||||
|
it('should run anyway', () => {
|
||||||
|
});
|
||||||
|
});
|
29
src/app/mailbox/inbox/mail/mail.controller.js
Normal file
29
src/app/mailbox/inbox/mail/mail.controller.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
class MailController {
|
||||||
|
/*@ngInject*/
|
||||||
|
constructor(mailboxService) {
|
||||||
|
this.mailboxService = mailboxService;
|
||||||
|
this.deleted = false;
|
||||||
|
this.displayMode = 'text'
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteMail(id) {
|
||||||
|
this.mailboxService.deleteMail(id)
|
||||||
|
.then(()=> {
|
||||||
|
this.deleted = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showTextButton() {
|
||||||
|
if ( this.mail.textPlain && !this.mail.textHtml){
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showHtmlButton() {
|
||||||
|
return !! this.mail.textHtml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MailController;
|
39
src/app/mailbox/inbox/mail/mail.css
Normal file
39
src/app/mailbox/inbox/mail/mail.css
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
|
||||||
|
|
||||||
|
.email-table .email-header {
|
||||||
|
font-weight: bold;
|
||||||
|
border-top: 5px solid #7C96AB;
|
||||||
|
border-bottom: 1px solid #7C96AB;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-table .email-info {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.email-table .email-body {
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-list {
|
||||||
|
margin: 0 0 21px;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
padding: 0 0 16px;
|
||||||
|
color: #999;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-list dt {
|
||||||
|
float: left;
|
||||||
|
color: #666;
|
||||||
|
margin: 0 3px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-list dd {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
39
src/app/mailbox/inbox/mail/mail.html
Normal file
39
src/app/mailbox/inbox/mail/mail.html
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<section ng-hide="$ctrl.deleted">
|
||||||
|
<div class="row email-header">
|
||||||
|
<div class="col-sm-4">{{$ctrl.mail.fromAddress}}</div>
|
||||||
|
<div class="col-sm-6">{{$ctrl.mail.subject}}</div>
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<!--<button class="btn btn-primary" ng-click="$ctrl.deleteMail($ctrl.mail.id)">Delete</button>-->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row email-info">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<dl class="info-list">
|
||||||
|
<dt>To:</dt>
|
||||||
|
<dd>{{$ctrl.mail.toString}}</dd>
|
||||||
|
<div ng-if="$ctrl.mail.cc" ng-repeat="(address,name) in $ctrl.mail.cc">
|
||||||
|
<dt>CC:</dt>
|
||||||
|
<dd>{{address}}</dd>
|
||||||
|
</div>
|
||||||
|
<dt>From:</dt>
|
||||||
|
<dd>{{$ctrl.mail.fromName}} <{{$ctrl.mail.fromAddress}}></dd>
|
||||||
|
<dt>Subject:</dt>
|
||||||
|
<dd>{{$ctrl.mail.subject}}</dd>
|
||||||
|
<dt>Date:</dt>
|
||||||
|
<dd>{{$ctrl.mail.date}}</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<label class="btn btn-primary" ng-show="$ctrl.showTextButton()" ng-model="$ctrl.displayMode"
|
||||||
|
uib-btn-radio="'text'">Text</label>
|
||||||
|
<label class="btn btn-primary" ng-show="$ctrl.showHtmlButton()" ng-model="$ctrl.displayMode"
|
||||||
|
uib-btn-radio="'html'">Html</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row email-body" ng-switch="$ctrl.displayMode">
|
||||||
|
<div class="col-sm-10" ng-switch-when="html" ng-bind-html="$ctrl.mail.textHtml"></div>
|
||||||
|
<div class="col-sm-10" ng-switch-when="text" ng-bind-html="$ctrl.mail.textPlain | nl2br | autolink "></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
33
src/app/mailbox/inbox/mail/mail.js
Normal file
33
src/app/mailbox/inbox/mail/mail.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import angular from 'angular';
|
||||||
|
import uiRouter from 'angular-ui-router';
|
||||||
|
import ngSanitize from 'angular-sanitize';
|
||||||
|
import Autolinker from 'autolinker';
|
||||||
|
|
||||||
|
import template from './mail.html';
|
||||||
|
import controller from './mail.controller';
|
||||||
|
import './mail.css';
|
||||||
|
|
||||||
|
|
||||||
|
export default angular.module('mailbox.inbox.mail', [uiRouter, ngSanitize])
|
||||||
|
.component('mail', {
|
||||||
|
template,
|
||||||
|
controller,
|
||||||
|
bindings: {
|
||||||
|
mail: '='
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// http://stackoverflow.com/a/20033625/79461
|
||||||
|
.filter("nl2br", function () {
|
||||||
|
return function (data) {
|
||||||
|
if (!data) return data;
|
||||||
|
return data.replace(/\r?\n/g, '<br/>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// http://stackoverflow.com/a/20033625/79461
|
||||||
|
.filter("autolink", function () {
|
||||||
|
return function (data) {
|
||||||
|
return Autolinker.link(data, {truncate: {length: 50, location: 'middle', newWindow: true}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
BIN
src/app/mailbox/inbox/spinner.gif
Normal file
BIN
src/app/mailbox/inbox/spinner.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
24
src/app/mailbox/mailbox.js
Normal file
24
src/app/mailbox/mailbox.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import angular from 'angular';
|
||||||
|
import uiRouter from 'angular-ui-router';
|
||||||
|
|
||||||
|
import Service from './service/service';
|
||||||
|
import Home from './home/home';
|
||||||
|
import Inbox from './inbox/inbox';
|
||||||
|
|
||||||
|
let module = angular.module('mailbox', [uiRouter, Inbox.name, Service.name, Home.name])
|
||||||
|
.config(/*@ngInject*/($stateProvider, $urlRouterProvider) => {
|
||||||
|
|
||||||
|
$urlRouterProvider.otherwise('/');
|
||||||
|
|
||||||
|
$stateProvider.state('home', {
|
||||||
|
url: "/",
|
||||||
|
component: 'home'
|
||||||
|
});
|
||||||
|
|
||||||
|
$stateProvider.state('inbox', {
|
||||||
|
url: '/:username',
|
||||||
|
component: 'inbox'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default module;
|
82
src/app/mailbox/service/mailbox.service.js
Normal file
82
src/app/mailbox/service/mailbox.service.js
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
import Chance from 'chance';
|
||||||
|
|
||||||
|
class MailboxService {
|
||||||
|
/*@ngInject*/
|
||||||
|
constructor($http, $log, $state, $stateParams, config) {
|
||||||
|
this.name = 'mailboxService';
|
||||||
|
this.$http = $http;
|
||||||
|
this.$log = $log;
|
||||||
|
this.$state = $state;
|
||||||
|
this.$stateParams = $stateParams;
|
||||||
|
this.config = config;
|
||||||
|
this.address = null;
|
||||||
|
this.chance = new Chance();
|
||||||
|
}
|
||||||
|
|
||||||
|
openMailbox(username) {
|
||||||
|
username = MailboxService.cleanUsername(username);
|
||||||
|
this.setCurrentAddress(username);
|
||||||
|
this.$state.go('inbox', {username: username});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteMail(id) {
|
||||||
|
this.$log.info('deleting mails with id ' + id);
|
||||||
|
return this.$http.post(this.config.backend_url, {
|
||||||
|
params: {
|
||||||
|
id: id,
|
||||||
|
username: this.getCurrentUsername(),
|
||||||
|
action: "delete"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadEmails(username) {
|
||||||
|
return this.$http.get(this.config.backend_url, {params: {username: username, action: "get"}})
|
||||||
|
.then(response=> {
|
||||||
|
this.setCurrentAddress(response.data.address);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static cleanUsername(username) {
|
||||||
|
return username.replace(/[@].*$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
createMailbox() {
|
||||||
|
let username = this.generateRandomUsername();
|
||||||
|
this.openMailbox(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
generateRandomUsername() {
|
||||||
|
let username = null;
|
||||||
|
if (this.chance.bool()) {
|
||||||
|
username = this.chance.word({syllables: 3});
|
||||||
|
} else {
|
||||||
|
username = this.chance.first();
|
||||||
|
}
|
||||||
|
if (this.chance.bool()) {
|
||||||
|
username += this.chance.integer({min: 50, max: 99});
|
||||||
|
}
|
||||||
|
if (this.chance.bool()) {
|
||||||
|
username += this.chance.tld();
|
||||||
|
}
|
||||||
|
username = username.toLowerCase();
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentUsername() {
|
||||||
|
return this.$stateParams.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentAddress(address) {
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentAddress() {
|
||||||
|
return this.address
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MailboxService;
|
6
src/app/mailbox/service/service.js
Normal file
6
src/app/mailbox/service/service.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import angular from 'angular';
|
||||||
|
import Service from './mailbox.service'
|
||||||
|
|
||||||
|
export default angular.module('mailbox.service', [])
|
||||||
|
.service('mailboxService', Service)
|
||||||
|
|
29
src/app/navbar/navbar.controller.js
Normal file
29
src/app/navbar/navbar.controller.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
class NavbarController {
|
||||||
|
/*@ngInject*/
|
||||||
|
constructor(mailboxService, $stateParams, $rootScope) {
|
||||||
|
this.$rootScope = $rootScope;
|
||||||
|
this.mailboxService = mailboxService;
|
||||||
|
this.$stateParams = $stateParams;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$onInit() {
|
||||||
|
this.$rootScope.$watch(
|
||||||
|
()=> this.mailboxService.getCurrentAddress(),
|
||||||
|
(newValue, oldValue)=> {
|
||||||
|
this.address = newValue;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.address = this.mailboxService.getCurrentAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
openMailbox(username) {
|
||||||
|
this.mailboxService.openMailbox(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
createMailbox() {
|
||||||
|
this.mailboxService.createMailbox();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NavbarController;
|
11
src/app/navbar/navbar.css
Normal file
11
src/app/navbar/navbar.css
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
background-color: #D9E2E9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.octicon-inbox {
|
||||||
|
display: inline-block;
|
||||||
|
width: 26px;
|
||||||
|
height: 23px;
|
||||||
|
background: url('octicon-inbox.png') no-repeat;
|
||||||
|
}
|
20
src/app/navbar/navbar.html
Normal file
20
src/app/navbar/navbar.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<div class="navbar" role="navigation">
|
||||||
|
<div class="container">
|
||||||
|
<div class="navbar-header">
|
||||||
|
<a class="navbar-brand"><span class="octicon-inbox"></span> Mailbox</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form class="navbar-form navbar-left">
|
||||||
|
<a class="btn btn-default" ng-click="$ctrl.createMailbox()" role="button">
|
||||||
|
<span class="glyphicon glyphicon-random" aria-hidden="true"></span>
|
||||||
|
create random
|
||||||
|
</a>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<form class="navbar-form navbar-left" role="search" ng-submit="$ctrl.openMailbox($ctrl.address)">
|
||||||
|
<input ng-model="$ctrl.address" type='text' class="form-control"/>
|
||||||
|
<button type="submit" class="btn btn-default">open</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
14
src/app/navbar/navbar.js
Normal file
14
src/app/navbar/navbar.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import angular from 'angular';
|
||||||
|
import uiRouter from 'angular-ui-router';
|
||||||
|
|
||||||
|
import template from './navbar.html';
|
||||||
|
import controller from './navbar.controller';
|
||||||
|
import './navbar.css'
|
||||||
|
|
||||||
|
let navbarModule = angular.module('navbar', [uiRouter])
|
||||||
|
.component('navbar', {
|
||||||
|
template,
|
||||||
|
controller
|
||||||
|
});
|
||||||
|
|
||||||
|
export default navbarModule;
|
BIN
src/app/navbar/octicon-inbox.png
Normal file
BIN
src/app/navbar/octicon-inbox.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7 KiB |
113
src/backend.php
Normal file
113
src/backend.php
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
<?php
|
||||||
|
require_once './config.php';
|
||||||
|
|
||||||
|
# load php dependencies:
|
||||||
|
require_once './backend-libs/autoload.php';
|
||||||
|
|
||||||
|
$imap_settings = $config['imap'];
|
||||||
|
$mailbox = new PhpImap\Mailbox($imap_settings['url'], $imap_settings['username'], $imap_settings['password']);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* print error and stop program.
|
||||||
|
* @param $status http status
|
||||||
|
* @param $text error text
|
||||||
|
*/
|
||||||
|
function error($status, $text) {
|
||||||
|
@http_response_code($status);
|
||||||
|
@print("{\"error\": \"$text\"}");
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* print all mails for the given $user as a json string.
|
||||||
|
* @param $username
|
||||||
|
*/
|
||||||
|
function print_inbox($username) {
|
||||||
|
global $mailbox, $config;
|
||||||
|
|
||||||
|
$name = clean_name($username);
|
||||||
|
if (strlen($name) === 0) {
|
||||||
|
error(400, 'invalid username');
|
||||||
|
}
|
||||||
|
$to = get_address($name, $config['mailHostname']);
|
||||||
|
$mail_ids = search_mails($to, $mailbox);
|
||||||
|
|
||||||
|
$emails = array();
|
||||||
|
foreach ($mail_ids as $id) {
|
||||||
|
$emails[] = $mailbox->getMail($id);
|
||||||
|
}
|
||||||
|
$address = get_address($name, $config['mailHostname']);
|
||||||
|
$data = array("mails" => $emails, 'username' => $name, 'address' => $address);
|
||||||
|
print(json_encode($data));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for mails with the recipient $to.
|
||||||
|
* @return array mail ids
|
||||||
|
*/
|
||||||
|
function search_mails($to, $mailbox) {
|
||||||
|
$filterTO = 'TO "' . $to . '"';
|
||||||
|
$filterCC = 'CC "' . $to . '"';
|
||||||
|
$mailsIdsTo = imap_sort($mailbox->getImapStream(), SORTARRIVAL, true, SE_UID, $filterTO);
|
||||||
|
$mailsIdsCc = imap_sort($mailbox->getImapStream(), SORTARRIVAL, true, SE_UID, $filterCC);
|
||||||
|
return array_merge($mailsIdsTo, $mailsIdsCc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove illegal characters from username and remove everything after the @-sign. You may extend it if your server supports them.
|
||||||
|
* @param $username
|
||||||
|
* @return clean username
|
||||||
|
*/
|
||||||
|
function clean_name($username) {
|
||||||
|
$username = preg_replace('/@.*$/', "", $username); // remove part after @
|
||||||
|
$username = preg_replace('/[^A-Za-z0-9_.+-]/', "", $username); // remove special characters
|
||||||
|
return $username;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates the full email address
|
||||||
|
* @param $username
|
||||||
|
* @param $domain
|
||||||
|
* @return $username@$domain
|
||||||
|
*/
|
||||||
|
function get_address($username, $domain) {
|
||||||
|
return $username . "@" . $domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* deletes messages older than X days.
|
||||||
|
*/
|
||||||
|
function delete_old_messages() {
|
||||||
|
global $mailbox;
|
||||||
|
|
||||||
|
$date = date('d-M-Y', strtotime('30 days ago'));
|
||||||
|
$ids = $mailbox->searchMailbox('BEFORE ' . $date);
|
||||||
|
foreach ($ids as $id) {
|
||||||
|
$mailbox->deleteMail($id);
|
||||||
|
}
|
||||||
|
$mailbox->expungeDeletedMails();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
header('Content-type: application/json');
|
||||||
|
|
||||||
|
// Never cache requests:
|
||||||
|
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
|
||||||
|
header("Cache-Control: post-check=0, pre-check=0", false);
|
||||||
|
header("Pragma: no-cache");
|
||||||
|
|
||||||
|
if (!isset($_GET['action'])) {
|
||||||
|
error(400, 'invalid parameter');
|
||||||
|
}
|
||||||
|
$action = $_GET['action'];
|
||||||
|
|
||||||
|
if ($action === "get" && isset($_GET['username'])) {
|
||||||
|
print_inbox($_GET['username']);
|
||||||
|
} else {
|
||||||
|
error(400, 'invalid action');
|
||||||
|
}
|
||||||
|
|
||||||
|
// run on every request
|
||||||
|
delete_old_messages();
|
15
src/config.sample.php
Normal file
15
src/config.sample.php
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
date_default_timezone_set('Europe/Paris');
|
||||||
|
error_reporting(0);
|
||||||
|
|
||||||
|
// configure this option if you want to allow requests from clients from other domains:
|
||||||
|
// see https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
|
||||||
|
// header("Access-Control-Allow-Origin: *");
|
||||||
|
|
||||||
|
// setup imap connection
|
||||||
|
$config['imap']['host'] = "localhost";
|
||||||
|
$config['imap']['url'] = '{' . $config['imap']['host'] . '/imap/ssl}INBOX';
|
||||||
|
$config['imap']['username'] = "test";
|
||||||
|
$config['imap']['password'] = "test";
|
||||||
|
$config['mailHostname'] = "example.com";
|
28
src/index.html
Normal file
28
src/index.html
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Mailbox</title>
|
||||||
|
<meta name="viewport" content="width=device-width initial-scale=1 maximum-scale=1 user-scalable=no">
|
||||||
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
|
<meta name="description" content="Mailbox">
|
||||||
|
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body ng-app="app" ng-cloak>
|
||||||
|
|
||||||
|
<app>
|
||||||
|
Loading application...
|
||||||
|
</app>
|
||||||
|
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>Powered by <a href="https://github.com/synox/disposable-mailbox"><strong>synox/disposable-mailbox</strong></a>
|
||||||
|
| <a href="https://github.com/synox/disposable-mailbox"><span class="octicon octicon-mark-github"></span> Fork me on github</a></p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
81
webpack.config.js
Normal file
81
webpack.config.js
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
var webpack = require('webpack');
|
||||||
|
var extend = require('node.extend');
|
||||||
|
var path = require('path');
|
||||||
|
var browserSyncPlugin = require('browser-sync-webpack-plugin');
|
||||||
|
var ngAnnotatePlugin = require('ng-annotate-webpack-plugin');
|
||||||
|
var HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
|
var webjsConfig = require('./shared.build.config');
|
||||||
|
var url = require('url');
|
||||||
|
var proxyMiddleware = require('proxy-middleware');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gemeinsame Konfigurationsdatei fuer Webpack (der Teil, der fuer alle Umgebungen gleich ist)
|
||||||
|
* @type {} Webpack Konfiguration
|
||||||
|
*/
|
||||||
|
var commonConfig = {
|
||||||
|
context: path.resolve(__dirname, 'src/app'),
|
||||||
|
// Einstiegspunkt fuer Webpack
|
||||||
|
entry: {
|
||||||
|
app: './app.js'
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: path.join(__dirname, 'dist'),
|
||||||
|
filename: '[name]bundle.js'
|
||||||
|
},
|
||||||
|
// Modulkonfiguration fuer alle Dateitypen, welcher Loader soll verwendet werden
|
||||||
|
module: {
|
||||||
|
loaders: webjsConfig.webpackLoaders
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
fallback: path.join(__dirname, 'node_modules')
|
||||||
|
},
|
||||||
|
resolveLoader: {fallback: path.join(__dirname, 'node_modules')}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Production Konfigurationsdatei fuer Webpack (der Teil, der nur fuer den produktiven Build ist)
|
||||||
|
* @type {} Webpack Konfiguration
|
||||||
|
*/
|
||||||
|
var production = extend({}, commonConfig, {
|
||||||
|
output: {
|
||||||
|
path: path.join(__dirname, 'target/build'),
|
||||||
|
filename: '[name].js'
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new ngAnnotatePlugin({add: true}),
|
||||||
|
new webpack.optimize.DedupePlugin(),
|
||||||
|
new webpack.optimize.UglifyJsPlugin({
|
||||||
|
minimize: true,
|
||||||
|
compress: {
|
||||||
|
warnings: true
|
||||||
|
},
|
||||||
|
sourceMap: false
|
||||||
|
}),
|
||||||
|
// automatisches Einfügen der Dateien app und vendor
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
template: '../index.html'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// development config
|
||||||
|
|
||||||
|
// forward requests (you may also have to change "backend_url" in app.js
|
||||||
|
var proxyOptions = url.parse('http://localhost:8080');
|
||||||
|
proxyOptions.route = '/backend.php';
|
||||||
|
|
||||||
|
var development = extend({}, commonConfig, {
|
||||||
|
plugins: [
|
||||||
|
new browserSyncPlugin({
|
||||||
|
proxy: 'localhost:3000',
|
||||||
|
middleware: proxyMiddleware(proxyOptions)
|
||||||
|
}),
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
template: '../index.html'
|
||||||
|
})
|
||||||
|
],
|
||||||
|
watch: true,
|
||||||
|
devtool: 'source-map'
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = {production: production, development: development};
|
Loading…
Reference in a new issue