├── assets ├── views │ ├── general │ │ ├── logout.html │ │ ├── about.html │ │ ├── welcome.html │ │ └── login.html │ ├── bookmark │ │ ├── layout.html │ │ ├── add.html │ │ ├── edit.html │ │ └── list.html │ ├── password │ │ ├── layout.html │ │ ├── auth.html │ │ └── list.html │ ├── alerts │ │ └── directive.html │ ├── mail │ │ ├── manage │ │ │ └── list.html │ │ ├── manage.html │ │ ├── auth.html │ │ ├── main │ │ │ ├── read.html │ │ │ └── list.html │ │ ├── main.html │ │ └── send.html │ ├── feed │ │ ├── main │ │ │ ├── item.html │ │ │ ├── queue.html │ │ │ └── list.html │ │ ├── manage │ │ │ ├── edit.html │ │ │ ├── list.html │ │ │ ├── add.html │ │ │ └── update-interval.html │ │ ├── main.html │ │ └── manage.html │ └── paginator │ │ └── directive.html ├── scripts │ ├── mail │ │ ├── controller │ │ │ ├── Mail.js │ │ │ ├── MailAuth.js │ │ │ ├── MailRead.js │ │ │ ├── MailSend.js │ │ │ ├── MailList.js │ │ │ ├── MailReply.js │ │ │ ├── MailBoxes.js │ │ │ └── MailManage.js │ │ ├── factory │ │ │ ├── ActionWrapper.js │ │ │ └── REST.js │ │ └── config.js │ ├── stubs │ │ ├── feed.js │ │ ├── mail.js │ │ ├── pass.js │ │ └── bookmark.js │ ├── feed │ │ ├── controller │ │ │ ├── Feed.js │ │ │ ├── ItemQueueMain.js │ │ │ ├── Items.js │ │ │ ├── Item.js │ │ │ ├── FeedList.js │ │ │ ├── ItemQueue.js │ │ │ ├── FeedManageAutoCleanup.js │ │ │ ├── FeedManageUpdateInterval.js │ │ │ └── FeedManage.js │ │ ├── filter │ │ │ └── SumByKey.js │ │ ├── factory │ │ │ └── REST.js │ │ └── config.js │ ├── general │ │ ├── filter │ │ │ ├── UnixTimeStamp.js │ │ │ └── Base64.js │ │ ├── factory │ │ │ └── REST.js │ │ ├── controller │ │ │ ├── Welcome.js │ │ │ └── Account.js │ │ ├── service │ │ │ ├── InstallWebApp.js │ │ │ ├── Credentials.js │ │ │ └── TemporaryStorage.js │ │ └── config.js │ ├── bookmark │ │ ├── factory │ │ │ └── REST.js │ │ ├── config.js │ │ └── controller │ │ │ └── BookMark.js │ ├── pass │ │ ├── factory │ │ │ ├── REST.js │ │ │ └── ActionWrapper.js │ │ └── config.js │ ├── alerts │ │ └── module.js │ ├── vendor-js │ │ └── angular │ │ │ └── angular-resource.min.js │ └── paginator │ │ └── module.js └── css │ └── app.css ├── database ├── .htaccess └── create.sql ├── public ├── install │ ├── views │ │ ├── footer.phtml │ │ ├── step-finish.phtml │ │ ├── step-database.phtml │ │ ├── header.phtml │ │ └── step-user.phtml │ └── .htaccess ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff ├── api │ └── 1 │ │ └── .htaccess ├── icon │ ├── .htaccess │ └── index.php ├── manifest.webapp └── .htaccess ├── src └── main │ └── Caco │ ├── Mail │ ├── IMAP │ │ ├── Mail.php │ │ ├── MailBoxStatus.php │ │ ├── MailHeader.php │ │ └── Account.php │ ├── MailException.php │ ├── Model │ │ └── MailAccount.php │ ├── SMTP │ │ └── Account.php │ ├── Account.php │ └── AccountMcrypt.php │ ├── MiniARException.php │ ├── CLI │ ├── ICLI.php │ └── AbstractCLI.php │ ├── Bookmark │ ├── Model │ │ └── Bookmark.php │ └── REST.php │ ├── Exports │ ├── Exporter │ │ ├── IXmlExporter.php │ │ ├── BookmarkHtml.php │ │ ├── Xbel.php │ │ ├── Opml.php │ │ └── Atom.php │ └── REST.php │ ├── Feed │ ├── CLI │ │ └── UpdateFeeds.php │ ├── IFeedReader.php │ ├── Model │ │ ├── ItemQueue.php │ │ └── Feed.php │ └── SimplePieFeedReader.php │ ├── Config │ ├── Model │ │ └── Config.php │ ├── REST.php │ └── CLI │ │ └── Manage.php │ ├── Slim │ ├── Auth │ │ ├── Basic.php │ │ ├── Model │ │ │ └── User.php │ │ └── UserManagement.php │ └── JsonView.php │ ├── Password │ ├── Model │ │ └── Container.php │ ├── CLI │ │ └── ChangeServerKey.php │ └── REST.php │ ├── SaltGenerator.php │ ├── Icon │ ├── REST.php │ ├── Model │ │ └── Icon.php │ └── FaviconDownloader.php │ ├── McryptContainer.php │ └── Upgrade │ └── CLI │ └── From2To3.php ├── spec └── api │ ├── auth_spec.js │ ├── config.js │ └── config_spec.js ├── package.json ├── composer.json ├── .gitignore ├── tools.sh ├── cli └── run_cli.php ├── create_archives.sh ├── LICENSE ├── run_tests.sh └── README.md /assets/views/general/logout.html: -------------------------------------------------------------------------------- 1 |

Logged out!

-------------------------------------------------------------------------------- /database/.htaccess: -------------------------------------------------------------------------------- 1 | order deny,allow 2 | deny from all -------------------------------------------------------------------------------- /public/install/views/footer.phtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/scripts/mail/controller/Mail.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.mail.crtl', ['caco.mail.REST']); -------------------------------------------------------------------------------- /assets/scripts/stubs/feed.js: -------------------------------------------------------------------------------- 1 | //A sub for modularising the frontend 2 | angular.module('caco.feed', []); -------------------------------------------------------------------------------- /assets/scripts/stubs/mail.js: -------------------------------------------------------------------------------- 1 | //A sub for modularising the frontend 2 | angular.module('caco.mail', []); -------------------------------------------------------------------------------- /assets/scripts/stubs/pass.js: -------------------------------------------------------------------------------- 1 | //A sub for modularising the frontend 2 | angular.module('caco.password', []); -------------------------------------------------------------------------------- /public/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cacodaimon/CacoCloud/HEAD/public/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cacodaimon/CacoCloud/HEAD/public/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cacodaimon/CacoCloud/HEAD/public/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cacodaimon/CacoCloud/HEAD/public/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public/api/1/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteBase /api/1/ 3 | RewriteCond %{REQUEST_FILENAME} !-f 4 | RewriteRule ^ index.php [QSA,L] -------------------------------------------------------------------------------- /public/icon/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteBase /icon/ 3 | RewriteCond %{REQUEST_FILENAME} !-f 4 | RewriteRule ^ index.php [QSA,L] -------------------------------------------------------------------------------- /public/install/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteBase /install/ 3 | RewriteCond %{REQUEST_FILENAME} !-f 4 | RewriteRule ^ index.php [QSA,L] -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cacodaimon/CacoCloud/HEAD/public/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cacodaimon/CacoCloud/HEAD/public/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cacodaimon/CacoCloud/HEAD/public/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /assets/scripts/feed/controller/Feed.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.feed.crtl', ['caco.feed.REST', 'caco.bookmark.REST', 'caco.general.REST', 'caco.feed.backend']); -------------------------------------------------------------------------------- /assets/scripts/stubs/bookmark.js: -------------------------------------------------------------------------------- 1 | //A sub for modularising the frontend 2 | angular.module('caco.bookmark', []); 3 | angular.module('caco.bookmark.REST', []) 4 | .service('BookMarkREST', function () {}); -------------------------------------------------------------------------------- /assets/scripts/general/filter/UnixTimeStamp.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.filter', []) 2 | .filter('unixTimeStamp', function() { 3 | return function(timeStamp) { 4 | return timeStamp * 1000; 5 | }; 6 | }); -------------------------------------------------------------------------------- /assets/views/bookmark/layout.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
7 |
8 |
9 |
-------------------------------------------------------------------------------- /assets/views/password/layout.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
7 |
8 |
9 |
-------------------------------------------------------------------------------- /assets/views/alerts/directive.html: -------------------------------------------------------------------------------- 1 |
4 | {{alert.title}} 5 | {{alert.message}} 6 |
-------------------------------------------------------------------------------- /assets/scripts/mail/controller/MailAuth.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.mail.crtl') 2 | .controller('MailAuthCrtl', function ($scope, $location, Credentials) { 3 | $scope.auth = function () { 4 | Credentials.key.email = $scope.emailKey; 5 | $location.path('/mail'); 6 | }; 7 | }) -------------------------------------------------------------------------------- /assets/scripts/general/filter/Base64.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.filter.base64', []) 2 | .filter('base64Encode', function() { 3 | return function(data) { 4 | return window.btoa(data); 5 | }; 6 | }) 7 | .filter('base64Decode', function() { 8 | return function(data) { 9 | return window.atob(data); 10 | }; 11 | }); -------------------------------------------------------------------------------- /public/manifest.webapp: -------------------------------------------------------------------------------- 1 | { 2 | "name": "caco-cloud", 3 | "description": "caco-cloud combines a feed reader, a bookmark manager, mail reader and a password manager in a fast and secure app.", 4 | "launch_path": "/", 5 | "developer": { 6 | "name": "Guido Krömer", 7 | "url": "http://www.cacodaemon.de" 8 | }, 9 | "default_locale": "en", 10 | "fullscreen": "true" 11 | } -------------------------------------------------------------------------------- /src/main/Caco/Mail/IMAP/Mail.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | class Mail extends MailHeader 10 | { 11 | /** 12 | * @var string 13 | */ 14 | public $bodyPlainText = ''; 15 | 16 | /** 17 | * @var string 18 | */ 19 | public $bodyHtml = ''; 20 | } -------------------------------------------------------------------------------- /spec/api/auth_spec.js: -------------------------------------------------------------------------------- 1 | var frisby = require('frisby'); 2 | var config = require('./config.js'); 3 | 4 | frisby.create('Test API Basic Auth') 5 | .get(config.apiUrlNoAuth + 'config') 6 | .expectStatus(401) 7 | .toss(); 8 | 9 | frisby.create('Test API Basic Auth') 10 | .get(config.apiUrl + 'config') 11 | .expectStatus(200) 12 | .expectHeaderContains('content-type', 'application/json') 13 | .toss(); -------------------------------------------------------------------------------- /src/main/Caco/Mail/MailException.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | class MailException extends \Exception 10 | { 11 | public function __construct($message = '', $code = 0, \Exception $previous = null) 12 | { 13 | parent::__construct($message, $code, $previous); 14 | } 15 | } -------------------------------------------------------------------------------- /assets/scripts/general/factory/REST.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.general.REST', ['ngResource']) 2 | .factory('ConfigREST', function ($resource) { 3 | return $resource('api/1/config/:key', {}, { 4 | one: {method: 'GET'}, 5 | all: {method: 'GET'}, 6 | remove: {method: 'DELETE'}, 7 | edit: {method: 'PUT'}, 8 | add: {method: 'POST'} 9 | }); 10 | }); -------------------------------------------------------------------------------- /src/main/Caco/MiniARException.php: -------------------------------------------------------------------------------- 1 | 7 | * @package Caco 8 | */ 9 | class MiniARException extends \Exception 10 | { 11 | public function __construct($message = '', $code = 0, \Exception $previous = null) 12 | { 13 | parent::__construct($message, is_int($code) ? $code : 0, $previous); 14 | } 15 | } -------------------------------------------------------------------------------- /assets/scripts/bookmark/factory/REST.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.bookmark.REST', ['ngResource']) 2 | .factory('BookMarkREST', function ($resource) { 3 | return $resource('api/1/bookmark/:id', {}, { 4 | one: {method: 'GET'}, 5 | all: {method: 'GET'}, 6 | remove: {method: 'DELETE'}, 7 | edit: {method: 'PUT'}, 8 | add: {method: 'POST'} 9 | }); 10 | }); -------------------------------------------------------------------------------- /src/main/Caco/CLI/ICLI.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | interface ICLI 10 | { 11 | /** 12 | * Sets the options array. 13 | * 14 | * @param array $options 15 | */ 16 | function init(array $options = null); 17 | 18 | /** 19 | * Runs the cli. 20 | * 21 | * @return int 22 | */ 23 | function run(); 24 | } -------------------------------------------------------------------------------- /assets/scripts/feed/controller/ItemQueueMain.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.feed.crtl') 2 | .controller('ItemQueueMainCrtl', function ($rootScope, $scope, $stateParams, $location, Items) { 3 | $rootScope.module = 'feed'; 4 | 5 | $scope.dequeue = function () { 6 | Items.dequeue(function (item, found) { 7 | found && $rootScope.$broadcast('ItemDequeued', item); 8 | $scope.notFound = !found; 9 | }); 10 | }; 11 | }); -------------------------------------------------------------------------------- /public/icon/index.php: -------------------------------------------------------------------------------- 1 | view(new \Caco\Slim\JsonView); 10 | 11 | $app->get('/feed/:id', '\Caco\Icon\REST:oneFeed') ->conditions(['id' => '\d+']); 12 | $app->get('/bookmark/:id', '\Caco\Icon\REST:oneBookmark')->conditions(['id' => '\d+']); 13 | 14 | $app->run(); -------------------------------------------------------------------------------- /assets/scripts/feed/filter/SumByKey.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.feed.filter', []) 2 | .filter('sumByKey', function() { 3 | return function(data, key) { 4 | if (typeof(data) === 'undefined' || typeof(key) === 'undefined') { 5 | return 0; 6 | } 7 | 8 | var sum = 0; 9 | for (var i = data.length - 1; i >= 0; i--) { 10 | sum += parseInt(data[i][key]); 11 | } 12 | 13 | return sum; 14 | }; 15 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "caco-Cloud", 3 | "version": "1.0.0", 4 | "author": "Guido Krömer ", 5 | "devDependencies": { 6 | "grunt": "~0.4.2", 7 | "grunt-contrib-uglify": "~0.2.7", 8 | "grunt-contrib-watch": "~0.5.3", 9 | "grunt-contrib-cssmin": "~0.7.0", 10 | "grunt-contrib-clean": "~0.5.0", 11 | "grunt-contrib-htmlmin": "~0.1.3", 12 | "grunt-contrib-copy": "~0.4.1", 13 | "frisby": "~0.7.2", 14 | "jasmine-node": "~1.12.0", 15 | "sleep": "~1.1.3" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /assets/scripts/feed/controller/Items.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.feed.crtl') 2 | .controller('ItemsCrtl', function ($rootScope, $scope, $stateParams, $location, $anchorScroll, Items, Paginator) { 3 | Items.all($stateParams.id, function (items) { 4 | $scope.items = items; 5 | Paginator.reset(); 6 | 7 | $location.hash('feed-items'); 8 | $anchorScroll(); 9 | }); 10 | 11 | $scope.enqueue = function (item) { 12 | Items.enqueue(item) 13 | }; 14 | }); -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "caco/Cloud", 3 | "require": { 4 | "php": ">=5.4.0", 5 | "slim/slim": "2.5.*", 6 | "simplepie/simplepie": "1.*", 7 | "phpmailer/phpmailer": "5.2.*", 8 | "guzzlehttp/guzzle": "~5.2", 9 | "arthurhoaro/favicon": "1.1.*" 10 | }, 11 | "autoload" : { 12 | "classmap" : ["src/main"] 13 | }, 14 | "authors": [ 15 | { 16 | "name": "Guido Krömer", 17 | "email": "mail@cacodaemon.de" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /public/install/views/step-finish.phtml: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Finishing installation

4 | 5 |
6 | 7 | Please delete the folder public/install before using CacoCloud! 8 | 9 |
10 | 11 |
12 | 13 | 14 | Proceed 15 | 16 | 17 | -------------------------------------------------------------------------------- /assets/scripts/pass/factory/REST.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.password.REST', ['ngResource', 'caco.password.ActionWrapper']) 2 | .factory('PasswordREST', function ($resource, PasswordActionWrapper) { 3 | var resource = $resource('api/1/password/:key/:id', {}, { 4 | one: {method: 'GET'}, 5 | all: {method: 'GET'}, 6 | remove: {method: 'DELETE'}, 7 | edit: {method: 'PUT'}, 8 | add: {method: 'POST'} 9 | }); 10 | 11 | return PasswordActionWrapper.wrap(resource, ['one', 'all', 'remove', 'edit', 'add']); 12 | }); -------------------------------------------------------------------------------- /assets/views/general/about.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

CacoCloud

4 |

CacoCloud is written with by Guido Krömer.

5 |

Code licensed under the MIT License and 6 | is released at .

7 |
8 |
-------------------------------------------------------------------------------- /assets/scripts/feed/controller/Item.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.feed.crtl') 2 | .controller('ItemCrtl', function ($rootScope, $scope, $stateParams, $location, $anchorScroll, Items, BookMarkREST) { 3 | Items.one($stateParams, function (item) { 4 | $scope.item = item; 5 | 6 | $location.hash('feed-item'); 7 | $anchorScroll(); 8 | }); 9 | 10 | $scope.addToBookmark = function (item) { 11 | BookMarkREST.add({}, {name: item.title, url: item.url}, function () { 12 | $location.path('/bookmark'); 13 | }); 14 | }; 15 | }); -------------------------------------------------------------------------------- /assets/scripts/mail/controller/MailRead.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.mail.crtl') 2 | .controller('MailReadCrtl', function ($rootScope, $scope, $stateParams, $location, MailREST) { 3 | $rootScope.id = $stateParams.id; 4 | $rootScope.mailBoxBase64 = $stateParams.mailBoxBase64; 5 | 6 | MailREST.one($stateParams, function (data) { 7 | var mail = data.response; 8 | 9 | if (!mail.seen) { 10 | $rootScope.$broadcast('MailChanged', {id: $stateParams.id, mailBox: $stateParams.mailBox}); 11 | } 12 | 13 | $scope.mail = mail; 14 | }); 15 | }); -------------------------------------------------------------------------------- /src/main/Caco/Bookmark/Model/Bookmark.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | class Bookmark extends \Caco\MiniAR 10 | { 11 | /** 12 | * @var string 13 | */ 14 | public $url; 15 | 16 | /** 17 | * @var string 18 | */ 19 | public $name; 20 | 21 | /** 22 | * @var int 23 | */ 24 | public $date; 25 | 26 | public function __construct(\PDO $pdo = null) 27 | { 28 | parent::__construct($pdo); 29 | 30 | $this->date = time(); 31 | } 32 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # NodeJS 2 | node_modules 3 | npm-debug.log 4 | 5 | # PHP Composer 6 | composer.phar 7 | composer.lock 8 | vendor/ 9 | vendor/* 10 | vendor/autoload/ 11 | vendor/autoload.php 12 | 13 | # SQLite 14 | *.sqlite* 15 | 16 | # Static Files 17 | *.ico 18 | 19 | # Grunt compiled files 20 | public/scripts/app.min.js 21 | public/scripts/vendor.min.js 22 | public/css/app.min.css 23 | public/index.html 24 | public/views/* 25 | 26 | # PHPStrom files 27 | .idea 28 | 29 | # frisbyjs files 30 | reports/ 31 | 32 | # OS files 33 | nohup.out 34 | 35 | # Other files 36 | public/install/finished 37 | CacoCloud.tar 38 | CacoCloud.tar.gz 39 | CacoCloud.tar.bz2 -------------------------------------------------------------------------------- /tools.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Shared functions for build.sh and run_tests.sh 5 | # 6 | 7 | function echo_blue () { 8 | echo -e "\e[94m$1\e[39m" 9 | } 10 | 11 | function echo_green () { 12 | echo -e "\e[92m$1\e[39m" 13 | } 14 | 15 | function echo_yellow () { 16 | echo -e "\e[93m$1\e[39m" 17 | } 18 | 19 | function echo_red () { 20 | echo -e "\e[91m$1\e[39m" 21 | } 22 | 23 | function npm_module_install () { 24 | MODULE_NAME="$1" 25 | 26 | if [ -d "node_modules/$MODULE_NAME" ]; then 27 | echo_yellow "Module $MODULE_NAME is already installed, skipping..." 28 | else 29 | npm install "$MODULE_NAME" --save-dev 30 | fi 31 | } -------------------------------------------------------------------------------- /assets/scripts/feed/controller/FeedList.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.feed.crtl') 2 | .controller('FeedCrtl', function ($rootScope, $scope, $stateParams, $location, Items, Feeds) { 3 | $rootScope.module = 'feed'; 4 | 5 | $scope.currentFeedId = 0; 6 | $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) { 7 | $scope.currentFeedId = toParams.id ? toParams.id : 0; 8 | }); 9 | 10 | $scope.$on('FeedsUpdated', function () { 11 | Feeds.get(function (feeds) { 12 | $scope.feeds = feeds; 13 | }); 14 | }); 15 | 16 | Feeds.get(function (feeds) { 17 | $scope.feeds = feeds; 18 | }); 19 | }); -------------------------------------------------------------------------------- /assets/scripts/feed/controller/ItemQueue.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.feed.crtl') 2 | .controller('ItemQueueCrtl', function ($rootScope, $scope, $stateParams, $location, Items, BookMarkREST) { 3 | $rootScope.module = 'feed'; 4 | 5 | Items.dequeue(function (item, found) { 6 | $scope.item = item; 7 | $scope.notFound = !found; 8 | }); 9 | 10 | $rootScope.$on('ItemDequeued', function (event, item) { 11 | $scope.item = item; 12 | }); 13 | 14 | 15 | $scope.addToBookmark = function (item) { 16 | BookMarkREST.add({}, {name: item.title, url: item.url}, function () { 17 | $location.path('/bookmark'); 18 | }); 19 | }; 20 | }); -------------------------------------------------------------------------------- /cli/run_cli.php: -------------------------------------------------------------------------------- 1 | exec('PRAGMA foreign_keys = ON'); 9 | 10 | $opts = getopt('c:', ['cli:']); 11 | 12 | $cliClassName = empty($opts['c']) ? $opts['cli'] : $opts['c']; 13 | /** @var Caco\CLI\ICLI $cliClass */ 14 | $cliClass = new $cliClassName; 15 | 16 | if (!($cliClass instanceof Caco\CLI\ICLI)) { 17 | die('Given class is not a cli!'); 18 | } 19 | 20 | $cliClass->init(); 21 | 22 | try { 23 | exit($cliClass->run()); 24 | } catch (InvalidArgumentException $e) { 25 | echo $e->getMessage() . PHP_EOL; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/Caco/Exports/Exporter/IXmlExporter.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | interface IXmlExporter 10 | { 11 | /** 12 | * @return string 13 | */ 14 | public function buildXml(); 15 | 16 | /** 17 | * Determines if the output should be downloadable in a browser. 18 | * 19 | * @return bool 20 | */ 21 | public function isFile(); 22 | 23 | /** 24 | * Gets the desired filename for downloading via HTTP id isFile() returns true. 25 | * IF isFile() returns false the output is a empty string. 26 | * 27 | * @return string 28 | */ 29 | public function getFileName(); 30 | } -------------------------------------------------------------------------------- /assets/scripts/general/controller/Welcome.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.general.crtl') 2 | .controller('WelcomeCrtl', function ($scope, $rootScope, WebApp) { 3 | if (WebApp.isWebAppDevice()) { 4 | WebApp.checkInstalled(function (installed) { 5 | !installed && $scope.$apply(function () { 6 | $scope.webAppInstallPossible = true; 7 | }); 8 | }); 9 | } else { 10 | $scope.webAppInstallPossible = false; 11 | } 12 | 13 | try { 14 | angular.module("caco.password"); 15 | $rootScope.passwd = true; 16 | } catch(err) { 17 | $rootScope.passwd = false; 18 | } 19 | 20 | $scope.install = function () { 21 | WebApp.install(); 22 | }; 23 | }); -------------------------------------------------------------------------------- /assets/scripts/bookmark/config.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.bookmark', ['ui.router', 'caco.bookmark.crtl']) 2 | .config(function($stateProvider) { 3 | $stateProvider 4 | .state('bookmark', { templateUrl: 'views/bookmark/layout.html' }) 5 | .state('bookmark.list', {url: '/bookmark', templateUrl: 'views/bookmark/list.html', controller: 'BookMarkCrtl'}) 6 | .state('bookmark.add', {url: '/bookmark/add', templateUrl: 'views/bookmark/add.html', controller: 'BookMarkCrtl'}) 7 | .state('bookmark.edit', {url: '/bookmark/edit/:id', templateUrl: 'views/bookmark/edit.html', controller: 'BookMarkCrtl'}); 8 | }) 9 | .run(function($rootScope) { 10 | $rootScope.moduleBookmark = true; 11 | }); -------------------------------------------------------------------------------- /assets/views/mail/manage/list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 21 | 22 | 23 |
6 | {{account.name}} 7 | 9 |
10 | 12 | 13 | 14 | 19 |
20 |
-------------------------------------------------------------------------------- /assets/scripts/general/controller/Account.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.general.crtl', ['caco.Credentials', 'caco.InstallWebApp']) 2 | .controller('AccountCrtl', function ($rootScope, $scope, $location, Credentials) { 3 | $scope.basicAuthPersist = 0; 4 | 5 | $scope.login = function () { 6 | Credentials.key.server = $scope.keyServer; 7 | 8 | Credentials.basicAuth = { 9 | user: $scope.basicAuthUser, 10 | pass: $scope.basicAuthPass 11 | }; 12 | 13 | if ($scope.basicAuthPersist > 0) { 14 | Credentials.persist($scope.basicAuthPersist); 15 | } 16 | 17 | $location.path('/welcome'); 18 | }; 19 | 20 | if ($location.path() === '/logout') { 21 | Credentials.logout(); 22 | $location.path('/login'); 23 | } 24 | }); -------------------------------------------------------------------------------- /assets/views/mail/manage.html: -------------------------------------------------------------------------------- 1 |
2 |
 
3 |
4 |
5 | 19 |
20 |
22 |
23 |
24 |
-------------------------------------------------------------------------------- /assets/views/feed/main/item.html: -------------------------------------------------------------------------------- 1 |
3 |
4 |
6 |
8 | 22 |
23 |
24 | -------------------------------------------------------------------------------- /create_archives.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | EXCLUDE_FILES="--exclude=CacoCloud.tar --exclude=.git --exclude=CacoCloud.tar.* --exclude=Gruntfile.js --exclude=package.json --exclude=composer.lock --exclude=composer.phar --exclude=composer.json --exclude=nohup.out --exclude=spec --exclude=reports --exclude=assets --exclude=node_modules --exclude=*.sh --exclude=public/install/finished --exclude=database/app.sqlite* --exclude=public/icons/bookmark/* --exclude=public/icons/feed/* --exclude=vendor/simplepie/simplepie/demo --exclude=vendor/simplepie/simplepie/tests --exclude=vendor/slim/slim/tests --exclude=vendor/phpmailer/phpmailer/examples --exclude=vendor/guzzlehttp/guzzle/tests --exclude=vendor/react/promise/tests --exclude=vendor/guzzlehttp/ringphp/tests --exclude=vendor/guzzlehttp/streams/tests --exclude=vendor/arthurhoaro/favicon/resources/tests" 4 | FILE_NAME="CacoCloud" 5 | GZIP="czpvf $FILE_NAME.tar.gz" 6 | 7 | tar -$GZIP * $EXCLUDE_FILES -------------------------------------------------------------------------------- /src/main/Caco/Feed/CLI/UpdateFeeds.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class UpdateFeeds extends AbstractCLI 16 | { 17 | /** 18 | * @var Manager 19 | */ 20 | protected $manager; 21 | 22 | public function __construct() 23 | { 24 | $this->manager = new Manager; 25 | $this->manager->setFeedReader(new SimplePieFeedReader); 26 | } 27 | 28 | /** 29 | * Runs the cli 30 | * 31 | * @return int 32 | */ 33 | public function run() 34 | { 35 | foreach ($this->manager->updateAllFeeds() as $id) { 36 | $this->printLine("Updated feed: $id"); 37 | } 38 | 39 | return 0; 40 | } 41 | } -------------------------------------------------------------------------------- /assets/scripts/pass/config.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.password', ['ui.router', 'caco.password.crtl']) 2 | .config(function($stateProvider) { 3 | $stateProvider 4 | .state('password', { templateUrl: 'views/password/layout.html' }) 5 | .state('password.list', {url: '/password', templateUrl: 'views/password/list.html', controller: 'PasswordCrtl'}) 6 | .state('password.add', {url: '/password/add', templateUrl: 'views/password/add.html', controller: 'PasswordCrtl'}) 7 | .state('password.edit', {url: '/password/edit/:id', templateUrl: 'views/password/edit.html', controller: 'PasswordCrtl'}) 8 | .state('password.auth', {url: '/password/auth', templateUrl: 'views/password/auth.html', controller: 'PasswordCrtl'}); 9 | }) 10 | .run(function($rootScope) { 11 | $rootScope.modulePassword = true; 12 | }); -------------------------------------------------------------------------------- /assets/scripts/feed/controller/FeedManageAutoCleanup.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.feed.crtl') 2 | .controller('FeedManageAutoCleanupCrtl', function ($rootScope, $scope, $stateParams, $location, ConfigREST) { 3 | $rootScope.module = 'feed'; 4 | $rootScope.modulePath = $location.path(); 5 | 6 | ConfigREST.one({key: 'auto-cleanup'}, function (data) { 7 | $scope.config = []; 8 | for (var i = data.response.length - 1; i >= 0; i--) { 9 | var row = data.response[i]; 10 | $scope.config[row.key] = row.value; 11 | } 12 | }); 13 | 14 | $scope.save = function () { 15 | for (var key in $scope.config) { 16 | if (!$scope.config.hasOwnProperty(key)) { 17 | continue; 18 | } 19 | 20 | ConfigREST.edit({key: key}, {key: key, value: $scope.config[key]}); 21 | } 22 | }; 23 | }); -------------------------------------------------------------------------------- /spec/api/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apiUrl: 'http://TEST_USER:TEST_PASSWORD@localhost:8000/api/1/', 3 | apiUrlNoAuth: 'http://localhost:8000/api/1/', 4 | mailtrap: { 5 | host: 'mailtrap.io', 6 | port: 25, 7 | user: 'inbox-d41d1bc2fd0e03a4', 8 | email: 'inbox-d41d1bc2fd0e03a4@mailtrap.io', 9 | password: '471e2131690eb7ed', 10 | apiToken: 'O3rSkidx6AqjyFuNLr9nnw', 11 | getApiUrlMessages: function () { 12 | return 'http://mailtrap.io/api/v1/inboxes/' + this.user + '/messages?page=1&token=' + this.apiToken; 13 | }, 14 | getApiUrlMessage: function (messageId) { 15 | return 'http://mailtrap.io/api/v1/inboxes/' + this.user + '/messages/' + messageId + '?token=' + this.apiToken; 16 | } 17 | }, 18 | popAccount: { 19 | host: 'my.inbox.com', 20 | port: 110, 21 | user: 'CacoCloudMailTest', 22 | password: 'pdGE9umSXqFSpgtX' 23 | } 24 | } -------------------------------------------------------------------------------- /assets/scripts/mail/controller/MailSend.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.mail.crtl') 2 | .controller('MailSendCrtl', function ($rootScope, $scope, $stateParams, $location, MailAccountREST, SendMailREST, Credentials, Alerts) { 3 | if (Credentials.emptyEmailKey()) { 4 | $location.path('/mail/auth'); 5 | } 6 | 7 | $rootScope.module = 'mail'; 8 | 9 | MailAccountREST.all({}, function (data) { 10 | $scope.accounts = data.response; 11 | }, function () { 12 | Alerts.addDanger('Could not find a account for sending the E-Mail!'); 13 | }); 14 | 15 | $scope.send = function () { 16 | SendMailREST.send({id: $scope.mail.fromId}, $scope.mail, function () { 17 | Alerts.addSuccess('The E-Mail has been send!'); 18 | $location.path('/mail'); 19 | }, function () { 20 | Alerts.addDanger('Could not send the mail!'); 21 | }); 22 | }; 23 | }); -------------------------------------------------------------------------------- /assets/scripts/mail/factory/ActionWrapper.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.mail.ActionWrapper', ['caco.Credentials']) 2 | .factory('EMailActionWrapper', function (Credentials) { 3 | this.wrap = function (resource, actions) { 4 | var wrappedResource = resource; 5 | for (var i = actions.length - 1; i >= 0; i--) { 6 | this.action(wrappedResource, actions[i]); 7 | } 8 | 9 | return wrappedResource; 10 | }; 11 | 12 | this.action = function (resource, action) { 13 | resource['_' + action] = resource[action]; 14 | 15 | resource[action] = function(data, success, error) { 16 | return resource['_' + action] ( 17 | angular.extend({}, data || {}, { 18 | key : Credentials.key.email 19 | }), 20 | success, 21 | error 22 | ); 23 | }; 24 | }; 25 | 26 | return this; 27 | }); -------------------------------------------------------------------------------- /assets/scripts/pass/factory/ActionWrapper.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.password.ActionWrapper', ['caco.Credentials']) 2 | .factory('PasswordActionWrapper', function (Credentials) { 3 | this.wrap = function (resource, actions) { 4 | var wrappedResource = resource; 5 | for (var i = actions.length - 1; i >= 0; i--) { 6 | this.action(wrappedResource, actions[i]); 7 | } 8 | 9 | return wrappedResource; 10 | }; 11 | 12 | this.action = function (resource, action) { 13 | resource['_' + action] = resource[action]; 14 | 15 | resource[action] = function(data, success, error) { 16 | return resource['_' + action] ( 17 | angular.extend({}, data || {}, { 18 | key : Credentials.key.server 19 | }), 20 | success, 21 | error 22 | ); 23 | }; 24 | }; 25 | 26 | return this; 27 | }); -------------------------------------------------------------------------------- /src/main/Caco/Feed/IFeedReader.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface IFeedReader 11 | { 12 | /** 13 | * Performs a feed url lookup by the given homepage URL. 14 | * 15 | * @param string $url 16 | * @return array 17 | */ 18 | public function lookupFeedURL($url); 19 | 20 | /** 21 | * Sets the current feed. 22 | * 23 | * @param string $url 24 | */ 25 | public function setFeed($url); 26 | 27 | /** 28 | * Gets the feed title; 29 | * 30 | * @return string 31 | */ 32 | public function getTitle(); 33 | 34 | /** 35 | * Gets the feed favicon image url; 36 | * 37 | * @return string 38 | */ 39 | public function getImageUrl(); 40 | 41 | /** 42 | * Gets all feed items as assoc array. 43 | * 44 | * @return array 45 | */ 46 | public function getItems(); 47 | } -------------------------------------------------------------------------------- /assets/views/password/auth.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
5 |
6 | Authorize 7 | 8 |
9 | 10 |
11 | 15 |
16 |
17 | 18 |
19 |
20 | 24 |
25 |
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /assets/views/feed/main/queue.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | 20 |
21 |
22 |
23 | The read queue is empty. 24 | 27 | Back to feeds 28 | 29 |
30 |
31 |
-------------------------------------------------------------------------------- /assets/scripts/general/service/InstallWebApp.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.InstallWebApp', []) 2 | .service('WebApp', function () { 3 | var loc = window.location; 4 | this.webappManifestURL = loc.protocol + '/' + loc.hostname + ':' + loc.port + loc.pathname + '/manifest.webapp'; 5 | 6 | this.isWebAppDevice = function () { 7 | return typeof(navigator.mozApps) !== 'undefined'; 8 | }; 9 | 10 | this.install = function () { 11 | if (!this.isWebAppDevice()) { 12 | return; 13 | } 14 | 15 | navigator.mozApps.install(this.webappManifestURL) 16 | }; 17 | 18 | this.checkInstalled = function (callBack) { 19 | if (!this.isWebAppDevice()) { 20 | return; 21 | } 22 | 23 | var request = navigator.mozApps.getSelf(); 24 | request.onsuccess = function() { 25 | if (request.result) { 26 | callBack(true); 27 | } else { 28 | callBack(false); 29 | } 30 | }; 31 | }; 32 | }); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Guido Krömer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/main/Caco/Config/Model/Config.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | class Config extends \Caco\MiniAR 10 | { 11 | const FALSE = 'false'; 12 | 13 | const TRUE = 'true'; 14 | 15 | /** 16 | * @var string 17 | */ 18 | public $key; 19 | 20 | /** 21 | * @var string 22 | */ 23 | public $value; 24 | 25 | /** 26 | * Read an config row by its key. 27 | * 28 | * @param string $key 29 | * @return bool 30 | */ 31 | public function readKey($key) 32 | { 33 | $query = sprintf('SELECT %s FROM `%s` WHERE `key` = ? LIMIT 1;', $this->getFieldList(), $this->getTableName()); 34 | 35 | return $this->readOne($query, [$key]); 36 | } 37 | 38 | /** 39 | * Returns an array of config records matching the given prefix. 40 | * 41 | * @param $prefix 42 | * @return Config[] 43 | */ 44 | public function readListByPrefix($prefix) 45 | { 46 | return $this->readList('key LIKE ?', [$prefix . '%']); 47 | } 48 | } -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Header unset ETag 3 | 4 | FileETag None 5 | 6 | 7 | AddOutputFilterByType DEFLATE text/html text/xml text/css text/plain 8 | AddOutputFilterByType DEFLATE text/javascript application/javascript application/x-javascript application/json 9 | AddOutputFilterByType DEFLATE image/svg+xml 10 | 11 | 12 | 13 | ExpiresActive On 14 | ExpiresByType text/html "access plus 1 week" 15 | ExpiresByType text/css "access plus 1 week" 16 | ExpiresByType application/javascript "access plus 1 week" 17 | ExpiresByType application/x-javascript "access plus 1 week" 18 | ExpiresByType image/gif "access plus 1 week" 19 | ExpiresByType image/jpeg "access plus 1 week" 20 | ExpiresByType image/png "access plus 1 week" 21 | ExpiresByType image/ico "access plus 1 week" 22 | ExpiresByType image/icon "access plus 1 week" 23 | ExpiresByType image/x-icon "access plus 1 week" 24 | ExpiresByType image/svg+xml "access plus 1 week" 25 | ExpiresByType application/x-font-ttf "access plus 1 week" 26 | ExpiresByType application/x-font-woff "access plus 1 week" 27 | -------------------------------------------------------------------------------- /assets/views/mail/auth.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
6 |
7 | Authorize 8 | 9 |
10 | 11 |
12 | 16 |
17 |
18 | 19 |
20 |
21 | 25 |
26 |
27 |
28 |
29 |
30 |
-------------------------------------------------------------------------------- /src/main/Caco/Slim/Auth/Basic.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class Basic extends Middleware 13 | { 14 | /** 15 | * @var string 16 | */ 17 | protected $realm = ''; 18 | 19 | /** 20 | * @param string $realm 21 | */ 22 | public function setRealm($realm) 23 | { 24 | $this->realm = $realm; 25 | } 26 | 27 | /** 28 | * @return string 29 | */ 30 | public function getRealm() 31 | { 32 | return $this->realm; 33 | } 34 | 35 | public function call() 36 | { 37 | $request = $this->app->request(); 38 | $response = $this->app->response(); 39 | $userName = $request->headers('PHP_AUTH_USER'); 40 | $password = $request->headers('PHP_AUTH_PW'); 41 | 42 | $user = new User; 43 | 44 | if ($user->read($userName) && $user->isValid($password)) { 45 | $this->next->call(); 46 | 47 | return; 48 | } 49 | 50 | $response->status(401); 51 | $response->header('WWW-Authenticate', "Basic realm=\"$this->realm\""); 52 | } 53 | } -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | . ./tools.sh 3 | 4 | # 5 | # Easy run frisby.js tests script. 6 | # 7 | 8 | function install_node_modules () { 9 | echo_blue "Installing modules for running frisby.js" 10 | npm_module_install jasmine-node 11 | npm_module_install frisby 12 | npm_module_install sleep 13 | } 14 | 15 | function create_test_user () { 16 | echo_blue "Creating test user" 17 | php cli/run_cli.php --cli=Caco\\Slim\\Auth\\UserManagement -a create -u TEST_USER -p TEST_PASSWORD 18 | } 19 | 20 | function delete_test_user () { 21 | echo_blue "Delete test user" 22 | php cli/run_cli.php --cli=Caco\\Slim\\Auth\\UserManagement -a delete -u TEST_USER 23 | } 24 | 25 | function start_php_build_in_http_server () { 26 | echo_blue "Starting PHP build in webserver" 27 | php -S 127.0.0.1:8000 -t public & 28 | PHP_SERVER_PID="$!" 29 | } 30 | 31 | function stop_php_build_in_http_server () { 32 | echo_blue "Stopping PHP build in webserver" 33 | kill "$PHP_SERVER_PID" 34 | } 35 | 36 | function run_tests () { 37 | echo_blue "Running the tests now..." 38 | node_modules/jasmine-node/bin/jasmine-node --junitreport spec/api/ 39 | } 40 | 41 | install_node_modules 42 | create_test_user 43 | start_php_build_in_http_server 44 | run_tests 45 | stop_php_build_in_http_server 46 | delete_test_user -------------------------------------------------------------------------------- /assets/css/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | overflow-y: scroll; 4 | } 5 | 6 | .scroll-x { 7 | overflow-x: auto; 8 | } 9 | 10 | .nav-stacked-deep ul { 11 | margin-left: 15px !important; 12 | } 13 | 14 | .anchor-fix { 15 | padding-top: 65px; 16 | margin-top: -65px; 17 | } 18 | 19 | .fade-in.ng-enter { 20 | -webkit-transition: 0.5s linear all; 21 | transition: 0.5s linear all; 22 | } 23 | 24 | .fade-in.ng-enter, 25 | .fade-in.ng-leave.ng-leave-active { 26 | opacity:0; 27 | } 28 | .fade-in.ng-leave, 29 | .fade-in.ng-enter.ng-enter-active { 30 | opacity:1; 31 | } 32 | 33 | .fade-out.ng-leave { 34 | -webkit-transition: 0.5s linear all; 35 | transition: 0.5s linear all; 36 | } 37 | 38 | .fade-out.ng-enter, 39 | .fade-out.ng-leave.ng-leave-active { 40 | opacity:0; 41 | } 42 | .fade-out.ng-leave, 43 | .fade-out.ng-enter.ng-enter-active { 44 | opacity:1; 45 | } 46 | 47 | .alert-slider.ng-enter, 48 | .alert-slider.ng-leave { 49 | -webkit-transition: 0.5s linear all; 50 | transition: 0.5s linear all; 51 | } 52 | 53 | .alert-slider.ng-enter, 54 | .alert-slider.ng-leave.ng-leave-active { 55 | max-height: 0px; 56 | } 57 | .alert-slider.ng-leave, 58 | .alert-slider.ng-enter.ng-enter-active { 59 | overflow-y: hidden; 60 | max-height: 250px; 61 | } -------------------------------------------------------------------------------- /assets/scripts/feed/controller/FeedManageUpdateInterval.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.feed.crtl') 2 | .controller('FeedManageUpdateIntervalCrtl', function ($rootScope, $scope, $stateParams, $location, ConfigREST, FeedCalculateUpdateIntervalsREST) { 3 | $rootScope.module = 'feed'; 4 | $rootScope.modulePath = $location.path(); 5 | 6 | ConfigREST.one({key: 'update-interval'}, function (data) { 7 | $scope.config = []; 8 | for (var i = data.response.length - 1; i >= 0; i--) { 9 | var row = data.response[i]; 10 | $scope.config[row.key] = row.value; 11 | } 12 | }); 13 | 14 | $scope.save = function () { 15 | for (var key in $scope.config) { 16 | if (!$scope.config.hasOwnProperty(key)) { 17 | continue; 18 | } 19 | 20 | ConfigREST.edit({key: key}, {key: key, value: $scope.config[key]}); 21 | } 22 | }; 23 | 24 | $scope.calculating = false; 25 | $scope.calculated = false; 26 | $scope.calculateUpdateInterval = function () { 27 | $scope.calculated = false; 28 | $scope.calculating = true; 29 | FeedCalculateUpdateIntervalsREST.perform({}, function () { 30 | $scope.calculating = false; 31 | $scope.calculated = true; 32 | }); 33 | }; 34 | }); -------------------------------------------------------------------------------- /assets/views/feed/manage/edit.html: -------------------------------------------------------------------------------- 1 |
3 |
4 | Edit a feed 5 | 6 |
7 | 9 |
10 | 15 |
16 |
17 | 18 |
19 | 21 |
22 | 27 |
28 |
29 | 30 |
31 |
32 | 36 |
37 |
38 |
39 |
-------------------------------------------------------------------------------- /src/main/Caco/Slim/JsonView.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | class JsonView extends \Slim\View 10 | { 11 | /** 12 | * Renders the template. 13 | * 14 | * @param string $template The HTTP status code. 15 | * @param null $data Not used. 16 | * @return string|void 17 | */ 18 | public function render($status, $data = null) 19 | { 20 | $app = \Slim\Slim::getInstance(); 21 | $app->contentType('application/json'); 22 | $app->expires(0); 23 | $app->response()->setStatus(intval($status)); 24 | $response = ['status' => $status]; 25 | 26 | $error = $this->data->get('error', false); 27 | switch ($status) { 28 | case 404: 29 | $error = $error ? $error : 'Resource not found'; 30 | break; 31 | case 500: 32 | $error = $error ? $error : 'Server Error'; 33 | break; 34 | } 35 | 36 | if ($error) { 37 | $response['error'] = $error; 38 | } 39 | 40 | $keys = $this->data->keys(); 41 | unset($keys[array_search('flash', $keys)]); 42 | 43 | foreach ($keys as $key) { 44 | $response[$key] = $this->data->get($key); 45 | } 46 | 47 | $app->response()->body(json_encode($response, JSON_NUMERIC_CHECK)); 48 | } 49 | } -------------------------------------------------------------------------------- /assets/views/bookmark/add.html: -------------------------------------------------------------------------------- 1 |
3 |
4 | Add a bookmark 5 | 6 |
7 | 9 |
10 | 14 |
15 |
16 | 17 |
18 | 20 |
21 | 26 | 27 | 28 | 29 |
30 |
31 | 32 |
33 |
34 | 38 |
39 |
40 |
41 |
-------------------------------------------------------------------------------- /assets/views/feed/main.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 35 |
-------------------------------------------------------------------------------- /assets/views/mail/main/read.html: -------------------------------------------------------------------------------- 1 |
3 |
4 |
5 | 6 | {{mail.subject}} 7 |
8 | 9 | {{mail.from}} 10 |
11 | 12 | {{mail.unixTimeStamp | unixTimeStamp | date:'yyyy-MM-dd HH:mm'}} 13 |
14 |
16 |
{{mail.bodyPlainText}}
17 |
18 |
21 |
22 | 38 |
39 |
-------------------------------------------------------------------------------- /src/main/Caco/Mail/Model/MailAccount.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class MailAccount extends \Caco\MiniAR 12 | { 13 | /** 14 | * @var string 15 | */ 16 | protected $data; 17 | 18 | /** 19 | * @var string 20 | */ 21 | protected $passwordSalt; 22 | 23 | /** 24 | * @var string 25 | */ 26 | protected $initializationVector; 27 | 28 | /** 29 | * @var string 30 | */ 31 | protected $cipher; 32 | 33 | /** 34 | * @param McryptContainer $container 35 | */ 36 | public function setContainer(McryptContainer $container) 37 | { 38 | $this->data = $container->getData(); 39 | $this->passwordSalt = $container->getPasswordSalt(); 40 | $this->initializationVector = $container->getInitializationVector(); 41 | $this->cipher = $container->getCipher(); 42 | } 43 | 44 | /** 45 | * @return McryptContainer 46 | */ 47 | public function getContainer() 48 | { 49 | $container = new McryptContainer; 50 | $container->setData($this->data); 51 | $container->setPasswordSalt($this->passwordSalt); 52 | $container->setInitializationVector($this->initializationVector); 53 | $container->setCipher($this->cipher); 54 | 55 | return $container; 56 | } 57 | } -------------------------------------------------------------------------------- /src/main/Caco/Password/Model/Container.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class Container extends \Caco\MiniAR 12 | { 13 | /** 14 | * @var string 15 | */ 16 | protected $data; 17 | 18 | /** 19 | * @var string 20 | */ 21 | protected $passwordSalt; 22 | 23 | /** 24 | * @var string 25 | */ 26 | protected $initializationVector; 27 | 28 | /** 29 | * @var string 30 | */ 31 | protected $cipher; 32 | 33 | /** 34 | * @param McryptContainer $container 35 | */ 36 | public function setContainer(McryptContainer $container) 37 | { 38 | $this->data = $container->getData(); 39 | $this->passwordSalt = $container->getPasswordSalt(); 40 | $this->initializationVector = $container->getInitializationVector(); 41 | $this->cipher = $container->getCipher(); 42 | } 43 | 44 | /** 45 | * @return McryptContainer 46 | */ 47 | public function getContainer() 48 | { 49 | $container = new McryptContainer; 50 | $container->setData($this->data); 51 | $container->setPasswordSalt($this->passwordSalt); 52 | $container->setInitializationVector($this->initializationVector); 53 | $container->setCipher($this->cipher); 54 | 55 | return $container; 56 | } 57 | } -------------------------------------------------------------------------------- /assets/views/feed/manage.html: -------------------------------------------------------------------------------- 1 |
2 |
 
3 |
4 |
5 | 31 |
32 |
34 |
35 |
36 |
-------------------------------------------------------------------------------- /assets/scripts/mail/controller/MailList.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.mail.crtl') 2 | .controller('MailListCrtl', function ($rootScope, $scope, $stateParams, $location, MailHeadersREST, MailREST) { 3 | $rootScope.id = $stateParams.id; 4 | $rootScope.mailBoxBase64 = $stateParams.mailBoxBase64; 5 | 6 | $scope.goToPage = function (page) { 7 | $stateParams.page = page; 8 | MailHeadersREST.all($stateParams, function (data) { 9 | $scope.headers = data.response; 10 | $scope.page = data.page; 11 | $scope.pages = Math.floor(data.messagesTotal / data.messagesPerPage); 12 | window.scrollTo(0, 0); 13 | }); 14 | }; 15 | $scope.goToPage(1); 16 | 17 | $scope.delete = function (id, mailBox, uniqueId) { 18 | if (!confirm('Confirm delete')) { 19 | return; 20 | } 21 | 22 | MailREST.remove({id: id, mailBoxBase64: mailBox, uniqueId: uniqueId}, function (data) { 23 | if (!data.response) { 24 | return; 25 | } 26 | 27 | for (var i = $scope.headers.length - 1; i >= 0; i--) { 28 | if ($scope.headers[i].uniqueId != uniqueId) { 29 | continue; 30 | } 31 | 32 | $scope.headers.splice(i, 1); 33 | } 34 | $rootScope.$broadcast('MailChanged', {id: id, mailBox: mailBox}); 35 | }); 36 | }; 37 | }); -------------------------------------------------------------------------------- /src/main/Caco/SaltGenerator.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | class SaltGenerator 10 | { 11 | /** 12 | * @var bool 13 | */ 14 | protected $openSLLExtensionLoaded = false; 15 | 16 | public function __construct() 17 | { 18 | $this->openSLLExtensionLoaded = extension_loaded('openssl'); 19 | } 20 | 21 | /** 22 | * Generates a random salt, uses openssl if available for random number generation. 23 | * 24 | * @param int $length The salt length. 25 | * @return string THe generated random salt. 26 | */ 27 | public function generate($length = 32) 28 | { 29 | $validChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 30 | $salt = ''; 31 | $count = strlen($validChars) - 1; 32 | 33 | while ($length--) { 34 | $salt .= $validChars[$this->randomNumber($count)]; 35 | } 36 | 37 | return $salt; 38 | } 39 | 40 | /** 41 | * Generates a random umber using OpenSSL or if not available mt_rand. 42 | * 43 | * @param int $max The maximum number. 44 | * @return int The generated random number. 45 | */ 46 | protected function randomNumber($max = 128) 47 | { 48 | if (!$this->openSLLExtensionLoaded) { 49 | return mt_rand(0, $max); 50 | } 51 | 52 | return hexdec(bin2hex(openssl_random_pseudo_bytes(4))) % $max; 53 | } 54 | } -------------------------------------------------------------------------------- /assets/scripts/general/service/Credentials.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.Credentials', ['caco.TemporaryStorage']) 2 | .service('Credentials', function (TempStorage) { 3 | this.init = function () { 4 | this.key = { 5 | server: null, 6 | email: null 7 | }; 8 | 9 | this.basicAuth = { 10 | user: null, 11 | pass: null 12 | }; 13 | }; 14 | 15 | this.persist = function (seconds) { 16 | TempStorage.setObject('caco.Credentials', this.basicAuth, seconds * 1000); 17 | }; 18 | 19 | this.load = function () { 20 | if (TempStorage.contains('caco.Credentials')) { 21 | this.basicAuth = TempStorage.getObject('caco.Credentials'); 22 | } 23 | } 24 | 25 | this.logout = function () { 26 | this.init(); 27 | TempStorage.clear(); 28 | }; 29 | 30 | this.empty = function () { 31 | return this.basicAuth.user == null || this.basicAuth.pass == null; 32 | }; 33 | 34 | this.emptyServerKey = function() { 35 | return this.key.server == null; 36 | }; 37 | 38 | this.emptyEmailKey = function() { 39 | return this.key.email == null; 40 | }; 41 | 42 | this.basicAuthHeader = function () { 43 | return 'Basic ' + window.btoa(this.basicAuth.user + ':' + this.basicAuth.pass); 44 | }; 45 | 46 | this.init(); 47 | this.load(); 48 | }); -------------------------------------------------------------------------------- /assets/views/bookmark/edit.html: -------------------------------------------------------------------------------- 1 |
3 |
4 | Edit a bookmark 5 | 6 |
7 | 9 |
10 | 15 |
16 |
17 | 18 |
19 | 21 |
22 | 27 | 28 | 29 | 30 |
31 |
32 | 33 |
34 |
35 | 39 |
40 |
41 |
42 |
-------------------------------------------------------------------------------- /src/main/Caco/Mail/IMAP/MailBoxStatus.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | class MailBoxStatus 10 | { 11 | /** 12 | * @var string 13 | */ 14 | public $name = ''; 15 | 16 | /** 17 | * @var int 18 | */ 19 | public $flags = 0; 20 | 21 | /** 22 | * @var int 23 | */ 24 | public $messages = 0; 25 | 26 | /** 27 | * @var int 28 | */ 29 | public $recent = 0; 30 | 31 | /** 32 | * @var int 33 | */ 34 | public $unseen = 0; 35 | 36 | /** 37 | * @var int 38 | */ 39 | public $uniqueIdNext = 0; 40 | 41 | /** 42 | * @var int 43 | */ 44 | public $uniqueIdValidity = 0; 45 | 46 | /** 47 | * @param string $name 48 | * @param int $flags 49 | * @param int $messages 50 | * @param int $recent 51 | * @param int $unseen 52 | * @param int $uniqueIdNext 53 | * @param int $uniqueIdValidity 54 | */ 55 | public function __construct($name = '', $flags = 0, $messages = 0, $recent = 0, $unseen = 0, $uniqueIdNext = 0, $uniqueIdValidity = 0) 56 | { 57 | $this->name = $name; 58 | $this->flags = $flags; 59 | $this->messages = $messages; 60 | $this->recent = $recent; 61 | $this->unseen = $unseen; 62 | $this->uniqueIdNext = $uniqueIdNext; 63 | $this->uniqueIdValidity = $uniqueIdValidity; 64 | } 65 | } -------------------------------------------------------------------------------- /assets/scripts/feed/factory/REST.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.feed.REST', ['ngResource']) 2 | .factory('FeedREST', function ($resource) { 3 | return $resource('api/1/feed/:id', {}, { 4 | one: {method: 'GET'}, 5 | all: {method: 'GET'}, 6 | remove: {method: 'DELETE'}, 7 | edit: {method: 'PUT'}, 8 | add: {method: 'POST'} 9 | }); 10 | }) 11 | .factory('FeedUpdateREST', function ($resource) { 12 | return $resource('api/1/feed/update/:id', {}, { 13 | perform: {method: 'GET'} 14 | }); 15 | }) 16 | .factory('ItemREST', function ($resource) { 17 | return $resource('api/1/feed/:id/item/:id_item', {}, { 18 | one: {method: 'GET'}, 19 | all: {method: 'GET'}, 20 | remove: {method: 'DELETE'} 21 | }); 22 | }) 23 | .factory('FeedCalculateUpdateIntervalsREST', function ($resource) { 24 | return $resource('api/1/feed/calculate-update-interval', {}, { 25 | perform: {method: 'GET'} 26 | }); 27 | }) 28 | .factory('ItemQueueREST', function ($resource) { 29 | return $resource('api/1/feed/item/queue/:id', {}, { 30 | enqueue: {method: 'POST'}, 31 | dequeue: {method: 'GET'} 32 | }); 33 | }) 34 | .factory('FeedUrlLookupREST', function ($resource) { 35 | return $resource('http://ajax.googleapis.com/ajax/services/feed/lookup', {},{ 36 | lookup: { method: 'JSONP', params: {v: '1.0', callback: 'JSON_CALLBACK'} } 37 | }); 38 | }); -------------------------------------------------------------------------------- /src/main/Caco/Icon/REST.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class REST 13 | { 14 | /** 15 | * @type int 16 | */ 17 | const ONE_YEAR_IN_SECONDS = 31536000; 18 | 19 | public function __construct() 20 | { 21 | $this->app = Slim::getInstance(); 22 | } 23 | 24 | /** 25 | * GET /icon/feed/:id 26 | * 27 | * @param int $id 28 | */ 29 | public function oneFeed($id) 30 | { 31 | $icon = new Icon; 32 | if ($icon->readOneFeed($id)) { 33 | $this->icon($icon); 34 | } else { 35 | $this->app->render(404); 36 | } 37 | } 38 | 39 | 40 | /** 41 | * GET /icon/bookmark/:id 42 | * 43 | * @param int $id 44 | */ 45 | public function oneBookmark($id) 46 | { 47 | $icon = new Icon; 48 | if ($icon->readOneBookmark($id)) { 49 | $this->icon($icon); 50 | } else { 51 | $this->app->render(404); 52 | } 53 | } 54 | 55 | /** 56 | * Adds the icon image data to the current response object. 57 | * 58 | * @param Icon $icon 59 | */ 60 | protected function icon(Icon $icon) { 61 | $this->app->expires(time() + self::ONE_YEAR_IN_SECONDS); 62 | $this->app->contentType('image/x-icon'); 63 | $response = $this->app->response(); 64 | $response->setStatus(200); 65 | $response->body($icon->data); 66 | } 67 | } -------------------------------------------------------------------------------- /src/main/Caco/Icon/Model/Icon.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | class Icon extends \Caco\MiniAR 10 | { 11 | /** 12 | * @var string 13 | */ 14 | public $id_bookmark; 15 | 16 | /** 17 | * @var int 18 | */ 19 | public $id_feed; 20 | 21 | /** 22 | * @var string 23 | */ 24 | public $data; 25 | 26 | /** 27 | * @var int 28 | */ 29 | public $inserted; 30 | 31 | public function __construct(\PDO $pdo = null) 32 | { 33 | parent::__construct($pdo); 34 | 35 | $this->inserted = time(); 36 | } 37 | 38 | /** 39 | * Reads a bookmark icon. 40 | * 41 | * @param int $id The bookmark id. 42 | * @return bool True if row was found. 43 | * @throws \Caco\MiniARException 44 | */ 45 | public function readOneBookmark($id) 46 | { 47 | $query = sprintf('SELECT %s FROM `%s` WHERE `id_bookmark` = ? LIMIT 1;', 48 | $this->getFieldList(), 49 | $this->getTableName() 50 | ); 51 | 52 | return $this->readOne($query, [$id]); 53 | } 54 | 55 | /** 56 | * Reads a feed icon. 57 | * 58 | * @param int $id The feed id. 59 | * @return bool True if row was found. 60 | * @throws \Caco\MiniARException 61 | */ 62 | public function readOneFeed($id) 63 | { 64 | $query = sprintf('SELECT %s FROM `%s` WHERE `id_feed` = ? LIMIT 1;', 65 | $this->getFieldList(), 66 | $this->getTableName() 67 | ); 68 | 69 | return $this->readOne($query, [$id]); 70 | } 71 | } -------------------------------------------------------------------------------- /assets/views/password/list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | 17 | 27 | 33 | 46 | 47 | 48 |
5 |
6 | 10 |
11 |
18 | 19 | {{password.name}} 20 | 21 |
22 | 24 | {{password.site}} 25 | 26 |
28 | 30 | {{password.date | unixTimeStamp | date:'yyyy-MM-dd HH:mm'}} 31 | 32 | 34 |
35 | 37 | 38 | 39 | 44 |
45 |
-------------------------------------------------------------------------------- /assets/views/paginator/directive.html: -------------------------------------------------------------------------------- 1 | 33 | 34 | -------------------------------------------------------------------------------- /assets/scripts/mail/controller/MailReply.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.mail.crtl') 2 | .controller('MailReplyCrtl', function ($rootScope, $scope, $stateParams, $location, MailAccountREST, MailREST, SendMailREST, Credentials, Alerts) { 3 | if (Credentials.emptyEmailKey()) { 4 | $location.path('/mail/auth'); 5 | } 6 | 7 | $rootScope.module = 'mail'; 8 | 9 | MailAccountREST.one({id: $stateParams.id}, function (data) { 10 | $scope.accounts = [data.response]; 11 | }, function () { 12 | Alerts.addDanger('Could not find a account for sending the E-Mail!'); 13 | }); 14 | 15 | MailREST.one($stateParams, function (data) { 16 | var mail = data.response; 17 | mail.subject = 'Re: ' + mail.subject; 18 | 19 | mail.to = mail.from; 20 | if (mail.to.indexOf('<') != -1) { 21 | mail.to = mail.to.substring(mail.to.indexOf('<') + 1, mail.to.length - 1); 22 | } 23 | 24 | mail.from = null; 25 | 26 | var body = ''; 27 | var lines = (mail.bodyPlainText ? mail.bodyPlainText : mail.bodyHtml).split('\n'); 28 | for (var i = 0; i < lines.length; i++) { 29 | body += '> ' + lines[i]; 30 | } 31 | 32 | mail.body = body; 33 | 34 | $scope.mail = mail; 35 | }); 36 | 37 | $scope.send = function () { 38 | SendMailREST.send({id: $scope.mail.fromId}, $scope.mail, function () { 39 | Alerts.addSuccess('The E-Mail has been send!'); 40 | $location.path('/mail'); 41 | }, function () { 42 | Alerts.addDanger('Could not send the mail!'); 43 | }); 44 | }; 45 | }); -------------------------------------------------------------------------------- /assets/scripts/alerts/module.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.Alerts', []) 2 | .service('Alerts', function () { 3 | this.alerts = []; 4 | 5 | this.id = 0; 6 | 7 | this.add = function (type, title, message) { 8 | this.alerts.push({ 9 | id: ++this.id, 10 | type: type, 11 | title: title, 12 | message: typeof(message) === 'undefined' ? null : message 13 | }) 14 | 15 | return this.id; 16 | }; 17 | 18 | this.remove = function (id) { 19 | for (var i = this.alerts.length - 1; i >= 0; i--) { 20 | if (this.alerts[i].id != id) { 21 | continue; 22 | } 23 | 24 | this.alerts.splice(i, 1); 25 | }; 26 | }; 27 | 28 | this.addSuccess = function (title, message) { 29 | return this.add('success', title, message); 30 | }; 31 | 32 | this.addInfo = function (title, message) { 33 | return this.add('info', title, message); 34 | }; 35 | 36 | this.addWarning = function (title, message) { 37 | return this.add('warning', title, message); 38 | }; 39 | 40 | this.addDanger = function (title, message) { 41 | return this.add('danger', title, message); 42 | }; 43 | }) 44 | .directive('alerts', function factory() { 45 | return { 46 | restrict: 'E', 47 | controller: function ($scope, Alerts) { 48 | $scope.alerts = Alerts.alerts; 49 | 50 | $scope.remove = function (id) { 51 | Alerts.remove(id); 52 | }; 53 | }, 54 | templateUrl: 'views/alerts/directive.html' 55 | }; 56 | }); -------------------------------------------------------------------------------- /assets/scripts/mail/controller/MailBoxes.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.mail.crtl') 2 | .controller('MailBoxesCrtl', function ($rootScope, $scope, $stateParams, $location, MailAccountREST, MailBoxesREST, Credentials) { 3 | if (Credentials.emptyEmailKey()) { 4 | $location.path('/mail/auth'); 5 | } 6 | 7 | $rootScope.module = 'mail'; 8 | 9 | MailAccountREST.all({}, function (data) { 10 | $scope.accounts = []; 11 | for (var i = data.response.length - 1; i >= 0; i--) { 12 | MailBoxesREST.all({id: data.response[i].id}, function (data1) { 13 | $scope.accounts.push(data1.response); 14 | }); 15 | } 16 | }); 17 | 18 | $scope.$on('MailChanged', function (event, message) { 19 | MailBoxesREST.all(message, function (data) { 20 | for (var i = $scope.accounts.length - 1; i >= 0; i--) { 21 | if ($scope.accounts[i].id == message.id) { 22 | $scope.accounts[i] = data.response; 23 | } 24 | } 25 | }); 26 | }); 27 | 28 | 29 | 30 | $scope.refresh = function (accountId) { 31 | for (var i = $scope.accounts.length - 1; i >= 0; i--) { 32 | if ($scope.accounts[i].id == accountId) { 33 | $scope.accounts[i].refresh = true; 34 | } 35 | } 36 | MailBoxesREST.all({id: accountId}, function (data1) { 37 | for (var i = $scope.accounts.length - 1; i >= 0; i--) { 38 | if ($scope.accounts[i].id == accountId) { 39 | $scope.accounts[i] = data1.response; 40 | } 41 | } 42 | }); 43 | }; 44 | }); -------------------------------------------------------------------------------- /src/main/Caco/Feed/Model/ItemQueue.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class ItemQueue extends \Caco\MiniAR 13 | { 14 | /** 15 | * @var int 16 | */ 17 | public $id_item; 18 | 19 | /** 20 | * @var int 21 | */ 22 | public $inserted; 23 | 24 | /** 25 | * Enqueue the given id, the current instance becomes that queue row. 26 | * 27 | * @param int $id 28 | * @return bool 29 | */ 30 | public function enqueue($id) 31 | { 32 | $this->clear(); 33 | $this->id_item = $id; 34 | $this->inserted = time(); 35 | 36 | return $this->save(); 37 | } 38 | 39 | /** 40 | * Dequeue a item, the current instance becomes that queue row. 41 | * 42 | * @return bool 43 | * @throws \Caco\MiniARException 44 | */ 45 | public function dequeue() 46 | { 47 | $this->clear(); 48 | $query = 'SELECT %s FROM `%s` WHERE 1 ORDER BY `inserted` ASC LIMIT 1;'; 49 | $query = sprintf($query, $this->getFieldList(), $this->getTableName()); 50 | 51 | $sth = $this->pdo->prepare($query); 52 | 53 | if ($this->pdo->errorCode() != '00000') { 54 | throw new MiniARException($this->pdo->errorInfo()[0], $this->pdo->errorCode()); 55 | } 56 | 57 | $sth->execute(); 58 | $result = $sth->fetchAll(\PDO::FETCH_ASSOC); 59 | 60 | if (empty($result)) { 61 | return false; 62 | } 63 | 64 | $this->setArray($result[0]); //-- delete from queue 65 | $this->delete(); 66 | $this->setArray($result[0]); 67 | 68 | return true; 69 | } 70 | } -------------------------------------------------------------------------------- /src/main/Caco/Mail/IMAP/MailHeader.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | class MailHeader 10 | { 11 | /** 12 | * @var string 13 | */ 14 | public $subject = ''; 15 | 16 | /** 17 | * @var string 18 | */ 19 | public $from = ''; 20 | 21 | /** 22 | * @var string 23 | */ 24 | public $to = ''; 25 | 26 | /** 27 | * @var string 28 | */ 29 | public $cc = ''; 30 | 31 | /** 32 | * @var string 33 | */ 34 | public $bcc = ''; 35 | 36 | /** 37 | * @var string 38 | */ 39 | public $date = ''; 40 | 41 | /** 42 | * @var int 43 | */ 44 | public $unixTimeStamp = 0; 45 | 46 | /** 47 | * @var string 48 | */ 49 | public $messageId = ''; 50 | 51 | /** 52 | * @var string 53 | */ 54 | public $inReplyToMessageId = ''; 55 | 56 | /** 57 | * @var int 58 | */ 59 | public $size = 0; 60 | 61 | /** 62 | * @var int 63 | */ 64 | public $uniqueId = 0; 65 | 66 | /** 67 | * @var int 68 | */ 69 | public $messageNumber = 0; 70 | 71 | /** 72 | * @var bool 73 | */ 74 | public $recent = false; 75 | 76 | /** 77 | * @var bool 78 | */ 79 | public $flagged = false; 80 | 81 | /** 82 | * @var bool 83 | */ 84 | public $answered = false; 85 | 86 | /** 87 | * @var bool 88 | */ 89 | public $deleted = false; 90 | 91 | /** 92 | * @var bool 93 | */ 94 | public $seen = false; 95 | 96 | /** 97 | * @var bool 98 | */ 99 | public $draft = false; 100 | } -------------------------------------------------------------------------------- /assets/views/mail/main.html: -------------------------------------------------------------------------------- 1 |
2 |
 
3 |
4 |
5 | 32 |
33 |
36 |
37 |
-------------------------------------------------------------------------------- /src/main/Caco/McryptContainer.php: -------------------------------------------------------------------------------- 1 | 7 | * @package Caco 8 | */ 9 | class McryptContainer 10 | { 11 | /** 12 | * @var string 13 | */ 14 | protected $data; 15 | 16 | /** 17 | * @var string 18 | */ 19 | protected $passwordSalt; 20 | 21 | /** 22 | * @var string 23 | */ 24 | protected $initializationVector; 25 | 26 | /** 27 | * @var string 28 | */ 29 | protected $cipher; 30 | 31 | /** 32 | * @param string $data 33 | */ 34 | public function setData($data) 35 | { 36 | $this->data = $data; 37 | } 38 | 39 | /** 40 | * @return string 41 | */ 42 | public function getData() 43 | { 44 | return $this->data; 45 | } 46 | 47 | /** 48 | * @param string $passwordSalt 49 | */ 50 | public function setPasswordSalt($passwordSalt) 51 | { 52 | $this->passwordSalt = $passwordSalt; 53 | } 54 | 55 | /** 56 | * @return string 57 | */ 58 | public function getPasswordSalt() 59 | { 60 | return $this->passwordSalt; 61 | } 62 | 63 | /** 64 | * @param string $initializationVector 65 | */ 66 | public function setInitializationVector($initializationVector) 67 | { 68 | $this->initializationVector = $initializationVector; 69 | } 70 | 71 | /** 72 | * @return string 73 | */ 74 | public function getInitializationVector() 75 | { 76 | return $this->initializationVector; 77 | } 78 | 79 | /** 80 | * @param string $cipher 81 | */ 82 | public function setCipher($cipher) 83 | { 84 | $this->cipher = $cipher; 85 | } 86 | 87 | /** 88 | * @return string 89 | */ 90 | public function getCipher() 91 | { 92 | return $this->cipher; 93 | } 94 | } -------------------------------------------------------------------------------- /assets/scripts/mail/factory/REST.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.mail.REST', ['ngResource', 'caco.mail.ActionWrapper']) 2 | .factory('MailAccountREST', function ($resource, EMailActionWrapper) { 3 | var resource = $resource('api/1/mail/:key/account/:id', {}, { 4 | one: {method: 'GET'}, 5 | all: {method: 'GET'}, 6 | remove: {method: 'DELETE'}, 7 | edit: {method: 'PUT'}, 8 | add: {method: 'POST'} 9 | }); 10 | 11 | return EMailActionWrapper.wrap(resource, ['one', 'all', 'remove', 'edit', 'add']); 12 | }) 13 | .factory('MailBoxesREST', function ($resource, EMailActionWrapper) { 14 | var resource = $resource('api/1/mail/:key/account/:id/mailbox', {}, { 15 | one: {method: 'GET'}, 16 | all: {method: 'GET'} 17 | }); 18 | 19 | return EMailActionWrapper.wrap(resource, ['one', 'all']); 20 | }) 21 | .factory('MailHeadersREST', function ($resource, EMailActionWrapper) { 22 | var resource = $resource('api/1/mail/:key/account/:id/mailbox/:mailBoxBase64', {}, { 23 | one: {method: 'GET'}, 24 | all: {method: 'GET'} 25 | }); 26 | 27 | return EMailActionWrapper.wrap(resource, ['one', 'all']); 28 | }) 29 | .factory('MailREST', function ($resource, EMailActionWrapper) { 30 | var resource = $resource('api/1/mail/:key/account/:id/mailbox/:mailBoxBase64/mail/:uniqueId', {}, { 31 | one: {method: 'GET'}, 32 | remove: {method: 'DELETE'} 33 | }); 34 | 35 | return EMailActionWrapper.wrap(resource, ['one', 'remove']); 36 | }) 37 | .factory('SendMailREST', function ($resource, EMailActionWrapper) { 38 | var resource = $resource('api/1/mail/:key/account/:id/send', {}, { 39 | send: {method: 'POST'} 40 | }); 41 | 42 | return EMailActionWrapper.wrap(resource, ['send']); 43 | }); -------------------------------------------------------------------------------- /src/main/Caco/Password/CLI/ChangeServerKey.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class ChangeServerKey extends AbstractCLI 17 | { 18 | protected $shortOptions = 'f:t:c::'; 19 | 20 | protected $longOptions = ['from:', 'to:', 'cipher::']; 21 | 22 | /** 23 | * @var \Caco\Mcrypt 24 | */ 25 | protected $crypto = null; 26 | 27 | public function __construct() 28 | { 29 | $this->crypto = new Mcrypt; 30 | } 31 | 32 | /** 33 | * Runs the cli 34 | * 35 | * @return int 36 | */ 37 | public function run() 38 | { 39 | $countChanged = 0; 40 | $from = $this->getArg('f', 'from', null, true); 41 | $to = $this->getArg('t', 'to', null, true); 42 | $cipher = $this->getArg('c', 'cipher', $this->crypto->getCipher()); 43 | $this->crypto->setCipher($cipher); 44 | 45 | $this->printLine("Using the cipher: $cipher"); 46 | $this->printLine("Changing the key $from to $to"); 47 | 48 | $container = new Container; 49 | $containerList = $container->readList(); 50 | foreach ($containerList as $container) { /** @var Container $container */ 51 | $data = $this->crypto->decrypt($container->getContainer(), $from); 52 | 53 | 54 | if ($data === false) { 55 | continue; 56 | } 57 | 58 | $container->setContainer($this->crypto->encrypt($data, $to)); 59 | $container->save(); 60 | $container->clear(); 61 | $countChanged++; 62 | } 63 | 64 | $this->printLine("Changed $countChanged passwords"); 65 | } 66 | } -------------------------------------------------------------------------------- /assets/views/feed/manage/list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 23 | 28 | 34 | 37 | 40 | 55 | 56 | 57 |
5 |
6 | 10 |
11 |
FeedUnreadTotal
24 | 27 | 29 | 31 | {{feed.title}} 32 | 33 | 35 | {{feed.unread}} 36 | 38 | {{feed.total}} 39 | 41 |
42 | 45 | 46 | 47 | 53 |
54 |
-------------------------------------------------------------------------------- /src/main/Caco/Slim/Auth/Model/User.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class User extends MiniAR 14 | { 15 | /** 16 | * @var string 17 | */ 18 | public $userName; 19 | 20 | /** 21 | * @var string 22 | */ 23 | protected $hash; 24 | 25 | /** 26 | * Sets a password for the current user. 27 | * 28 | * @param string $password 29 | */ 30 | public function setPassword($password) 31 | { 32 | $salt = (new SaltGenerator)->generate(32); 33 | 34 | $this->hash = crypt($password, sprintf('$2y$10$%s$', $salt)); 35 | } 36 | 37 | /** 38 | * Reads a user from the database. 39 | * 40 | * @param string $userName 41 | * @param string $select 42 | * @return bool 43 | */ 44 | public function read($userName, $select = null) 45 | { 46 | $query = sprintf('SELECT %s FROM `%s` WHERE `userName` = ? LIMIT 1;', $this->getFieldList(), $this->getTableName()); 47 | $sth = $this->pdo->prepare($query); 48 | 49 | $sth->execute([$userName]); 50 | $result = $sth->fetchAll(PDO::FETCH_ASSOC); 51 | 52 | if (empty($result)) { 53 | return false; 54 | } 55 | 56 | foreach ($result[0] as $key => $value) { 57 | $this->$key = $value; 58 | } 59 | 60 | return true; 61 | } 62 | 63 | /** 64 | * Checks if the given password is valid for the user. 65 | * 66 | * @param string $password 67 | * @return bool 68 | */ 69 | public function isValid($password) 70 | { 71 | return $this->hash == crypt($password, $this->hash); 72 | } 73 | 74 | /** 75 | * @return string 76 | */ 77 | public function __toString() 78 | { 79 | return empty($this->userName) || !is_string($this->userName) ? '' : $this->userName; 80 | } 81 | } -------------------------------------------------------------------------------- /assets/scripts/bookmark/controller/BookMark.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.bookmark.crtl', ['caco.bookmark.REST']) 2 | .controller('BookMarkCrtl', function ($rootScope, $scope, $stateParams, $location, BookMarkREST, Alerts, Paginator) { 3 | $rootScope.module = 'bookmark'; 4 | 5 | if ($stateParams.id) { 6 | BookMarkREST.one({id: $stateParams.id}, function (data) { 7 | $scope.bookmark = data.response; 8 | }); 9 | } 10 | if ($location.path() === '/bookmark') { 11 | BookMarkREST.all({}, function (data) { 12 | $scope.bookmarks = data.response; 13 | Paginator.reset(); 14 | }); 15 | } 16 | 17 | $scope.add = function () { 18 | BookMarkREST.add({}, $scope.newBookmark, function () { 19 | $location.path('/bookmark'); 20 | }, function () { 21 | Alerts.addDanger('Could not add the Bookmark!'); 22 | }); 23 | }; 24 | 25 | $scope.delete = function (id) { 26 | if (!confirm('Confirm delete')) { 27 | return; 28 | } 29 | 30 | BookMarkREST.remove({id: id}, {}, function(data) { 31 | if (data.status != 200) { 32 | return; 33 | } 34 | 35 | for (var i = $scope.bookmarks.length - 1; i >= 0; i--) { 36 | if ($scope.bookmarks[i].id != id) { 37 | continue; 38 | } 39 | 40 | $scope.bookmarks.splice(i, 1); 41 | } 42 | }, function () { 43 | Alerts.addDanger('Could not delete the Bookmark!'); 44 | }); 45 | }; 46 | 47 | $scope.edit = function () { 48 | BookMarkREST.edit({id: $scope.bookmark.id}, $scope.bookmark, function (data) { 49 | $location.path('/bookmark'); 50 | }, function () { 51 | Alerts.addDanger('Could not edit the bookmark!'); 52 | }); 53 | }; 54 | }); -------------------------------------------------------------------------------- /assets/views/bookmark/list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | 17 | 22 | 28 | 34 | 47 | 48 | 49 | 50 | 51 | 54 | 55 | 56 |
5 |
6 | 10 |
11 |
18 | 21 | 23 | 25 | {{bookmark.name}} 26 | 27 | 29 | 31 | {{bookmark.date | unixTimeStamp | date:'yyyy-MM-dd HH:mm'}} 32 | 33 | 35 |
36 | 38 | 39 | 40 | 45 |
46 |
52 | 53 |
-------------------------------------------------------------------------------- /src/main/Caco/Config/REST.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class REST 13 | { 14 | /** 15 | * @var \Slim\Slim 16 | */ 17 | protected $app; 18 | 19 | public function __construct() 20 | { 21 | $this->app = Slim::getInstance(); 22 | } 23 | 24 | public function one($key) 25 | { 26 | $result = (new Config)->readListByPrefix($key); 27 | 28 | if (empty($result)) { 29 | $this->app->render(404); 30 | } else { 31 | $this->app->render(200, ['response' => $result]); 32 | } 33 | 34 | } 35 | 36 | public function all() 37 | { 38 | $this->app->render(200, ['response' => (new Config)->readList()]); 39 | } 40 | 41 | public function add() 42 | { 43 | $config = new Config; 44 | $config->setArray(json_decode($this->app->request()->getBody(), true)); 45 | 46 | if ($config->save()) { 47 | $this->app->render(201, ['response' => $config->id]); 48 | } else { 49 | $this->app->render(500); 50 | } 51 | } 52 | 53 | public function edit($key) 54 | { 55 | $config = new Config; 56 | if (!$config->readKey($key)) { 57 | $this->app->render(404); 58 | 59 | return; 60 | } 61 | 62 | $config->setArray(json_decode($this->app->request()->getBody(), true)); 63 | 64 | if ($config->save()) { 65 | $this->app->render(200, ['response' => $config->id]); 66 | } else { 67 | $this->app->render(500); 68 | } 69 | } 70 | 71 | public function delete($key) 72 | { 73 | $config = new Config; 74 | if ($config->readKey($key)) { 75 | $id = $config->id; 76 | $this->app->render($config->delete() ? 200 : 500, ['response' => $id]); 77 | } else { 78 | $this->app->render(404); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /src/main/Caco/Icon/FaviconDownloader.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class FaviconDownloader 16 | { 17 | /** 18 | * Downloads a feed favicon. 19 | * 20 | * @param string $url The feed URL. 21 | * @param int $id The feed id. 22 | */ 23 | public function downloadFeed(Feed $feed, $url = null) 24 | { 25 | $icon = new Icon; 26 | $icon->id_feed = $feed->id; 27 | $icon->data = $this->downloadIcon(is_null($url) ? $this->guessIconURL($feed->url) : $url); 28 | $icon->save(); 29 | } 30 | 31 | /** 32 | * Downloads a bookmark favicon. 33 | * 34 | * @param string $url The bookmark URL. 35 | * @param int $id The feed id. 36 | */ 37 | public function downloadBookmark(Bookmark $bookmark) 38 | { 39 | $icon = new Icon; 40 | $icon->id_bookmark = $bookmark->id; 41 | $icon->data = $this->downloadIcon($this->guessIconURL($bookmark->url)); 42 | $icon->save(); 43 | } 44 | 45 | /** 46 | * Guesses the favicon URL. 47 | * 48 | * @param string $url The page URL. 49 | * @return string The favicon URL. 50 | */ 51 | private function guessIconURL($url) 52 | { 53 | $url = parse_url($url); 54 | $url = sprintf('%s://%s', 55 | isset($url['scheme']) ? $url['scheme'] : 'http', 56 | isset($url['host']) ? $url['host'] : strtolower($url['path'])); 57 | 58 | return (new Favicon)->get($url); 59 | } 60 | 61 | /** 62 | * Downloads a favicon. 63 | * 64 | * @param string $url The favicon URL. 65 | * @return string The favicon contents. 66 | */ 67 | private function downloadIcon($url) 68 | { 69 | $response = (new Client)->get($url); 70 | 71 | return $response->getBody()->getContents(); 72 | } 73 | } -------------------------------------------------------------------------------- /assets/scripts/feed/controller/FeedManage.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.feed.crtl') 2 | .controller('FeedManageCrtl', function ($rootScope, $scope, $stateParams, $location, Feeds, FeedREST, FeedUrlLookupREST, Alerts) { 3 | $rootScope.module = 'feed'; 4 | $rootScope.modulePath = $location.path(); 5 | 6 | if ($stateParams.id) { 7 | Feeds.getOne($stateParams.id, function (feed) { 8 | $scope.feed = feed; 9 | }) 10 | } else { 11 | Feeds.get(function (feeds) { 12 | $scope.feeds = feeds; 13 | }); 14 | } 15 | 16 | $scope.lookup = function () { 17 | FeedUrlLookupREST.lookup({q: $scope.lookupUrl}, {}, function (data) { 18 | if (data && data.responseStatus == 200) { 19 | $scope.feed = data.responseData; 20 | } 21 | }, function () { 22 | Alerts.addDanger('No feed found!'); 23 | }); 24 | }; 25 | 26 | 27 | $scope.add = function () { 28 | Feeds.add($scope.feed, function (feeds) { 29 | $scope.feeds = feeds; 30 | $location.path('/feed/manage'); 31 | }, function () { 32 | Alerts.addDanger('Feed has not been added!'); 33 | }); 34 | }; 35 | 36 | $scope.edit = function () { 37 | Feeds.edit($scope.feed, function (feeds) { 38 | $scope.feeds = feeds; 39 | $location.path('/feed/manage'); 40 | }, function () { 41 | Alerts.addDanger('Feed has not been edited!'); 42 | }); 43 | }; 44 | 45 | $scope.delete = function (id) { 46 | if (!confirm('Confirm delete')) { 47 | return; 48 | } 49 | 50 | Feeds.remove({id: id}, function (feeds) { 51 | $scope.feeds = feeds; 52 | $location.path('/feed/manage'); 53 | }, function () { 54 | Alerts.addDanger('Feed has not been deleted!'); 55 | }); 56 | }; 57 | }); -------------------------------------------------------------------------------- /public/install/views/step-database.phtml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 |

Creating the Database

12 | 13 | 14 |
15 | 16 | Created the database. 17 | 18 |

19 | The database was created successfully! 20 |

21 |

22 | The database version is: 23 |

24 |
25 | 26 | 27 | 28 |
29 | 30 | Found a exising database. 31 | 32 |

33 | Skipped creating the database! 34 |

35 |

36 | The database version is: 37 |

38 |
39 | 40 | 41 | 42 |
43 | 44 | Could not create the Database. 45 | 46 |

47 | 48 | 49 |

50 |
51 | 52 | 53 |
54 | 55 | 56 | 57 | Proceed 58 | 59 | 60 |
61 | 62 | Sorry, the Database could not be installed. 63 | 64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /assets/scripts/mail/controller/MailManage.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.mail.crtl') 2 | .controller('MailManageCrtl', function ($rootScope, $scope, $stateParams, $location, MailAccountREST, Credentials, Alerts) { 3 | if (Credentials.emptyEmailKey()) { 4 | $location.path('/mail/auth'); 5 | } 6 | 7 | $rootScope.module = 'mail'; 8 | $rootScope.modulePath = $location.path(); 9 | 10 | $scope.newAccount = {imap: {type: 1}, smtp: {authType: 'PLAIN', secure: 'SSL'}}; 11 | 12 | if ($stateParams.id) { 13 | MailAccountREST.one({id: $stateParams.id}, function (data) { 14 | $scope.account = data.response; 15 | }); 16 | } else { 17 | MailAccountREST.all({}, function (data) { 18 | $scope.accounts = data.response; 19 | }); 20 | } 21 | 22 | $scope.add = function () { 23 | MailAccountREST.add({}, $scope.newAccount, function () { 24 | $location.path('/mail/manage'); 25 | }, function () { 26 | Alerts.addDanger('Could not add the E-Mail account!'); 27 | }); 28 | }; 29 | 30 | $scope.edit = function () { 31 | MailAccountREST.edit({id: $scope.account.id}, $scope.account, function () { 32 | MailAccountREST.all({}, function (data) { 33 | $scope.accounts = data.response; 34 | }); 35 | $location.path('/mail/manage'); 36 | }, function () { 37 | Alerts.addDanger('Could not edit the E-Mail account!'); 38 | }); 39 | }; 40 | 41 | $scope.delete = function (id) { 42 | if (!confirm('Confirm delete')) { 43 | return; 44 | } 45 | 46 | MailAccountREST.remove({id: id}, {}, function(data) { 47 | MailAccountREST.all({}, function (data) { 48 | $scope.accounts = data.response; 49 | }); 50 | }, function () { 51 | Alerts.addDanger('Could not delete the E-Mail account!'); 52 | }); 53 | }; 54 | }); -------------------------------------------------------------------------------- /src/main/Caco/Mail/SMTP/Account.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | class Account implements \JsonSerializable 10 | { 11 | const SECURE_NONE = ''; 12 | 13 | const SECURE_SSL = 'ssl'; 14 | 15 | const SECURE_TLS = 'tls'; 16 | 17 | const AUTH_TYPE_LOGIN = 'LOGIN'; 18 | 19 | const AUTH_TYPE_PLAIN = 'PLAIN'; 20 | 21 | const AUTH_TYPE_NTLM = 'NTLM'; 22 | 23 | const AUTH_TYPE_CRAM_MD5 = 'CRAM-MD5'; 24 | 25 | /** 26 | * @var string 27 | */ 28 | public $host = ''; 29 | 30 | /** 31 | * @var int 32 | */ 33 | public $port = 0; 34 | 35 | /** 36 | * @var bool 37 | */ 38 | public $auth = true; 39 | 40 | /** 41 | * @var string 42 | */ 43 | public $authType = self::AUTH_TYPE_PLAIN; 44 | 45 | /** 46 | * @var string 47 | */ 48 | public $realName = ''; 49 | 50 | /** 51 | * @var string 52 | */ 53 | public $userName = ''; 54 | 55 | /** 56 | * @var string 57 | */ 58 | public $email = ''; 59 | 60 | /** 61 | * @var string 62 | */ 63 | public $password = ''; 64 | 65 | /** 66 | * @var string 67 | */ 68 | public $secure = self::SECURE_TLS; 69 | 70 | /** 71 | * Set multiple fields at once, by providing an assoc array. 72 | * 73 | * @param array $data 74 | */ 75 | public function setArray(array $data) 76 | { 77 | $className = get_class(); 78 | foreach ($data as $key => $value) { 79 | if (property_exists($className, $key)) { 80 | $this->$key = $value; 81 | } 82 | } 83 | } 84 | 85 | public function jsonSerialize() 86 | { 87 | return [ 88 | 'host' => $this->host, 89 | 'port' => $this->port, 90 | 'auth' => $this->auth, 91 | 'authType' => $this->authType, 92 | 'realName' => $this->realName, 93 | 'userName' => $this->userName, 94 | 'email' => $this->email, 95 | 'secure' => $this->secure, 96 | ]; 97 | } 98 | } -------------------------------------------------------------------------------- /src/main/Caco/Exports/Exporter/BookmarkHtml.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | 15 | 16 | class BookmarkHtml implements IXmlExporter 17 | { 18 | /** 19 | * @var Bookmark[] 20 | */ 21 | protected $bookmarks; 22 | 23 | public function __construct(array $bookmarks = null) 24 | { 25 | $this->bookmarks = $bookmarks; 26 | } 27 | 28 | /** 29 | * @param \Caco\Bookmark\Model\Bookmark[] $bookmarks 30 | */ 31 | public function setBookmarks(array $bookmarks) 32 | { 33 | $this->bookmarks = $bookmarks; 34 | } 35 | 36 | /** 37 | * @return \Caco\Bookmark\Model\Bookmark[] 38 | */ 39 | public function getBookmarks() 40 | { 41 | return $this->bookmarks; 42 | } 43 | 44 | /** 45 | * Builds and returns the bookmark export file. 46 | * 47 | * @return string 48 | */ 49 | public function buildXML() 50 | { 51 | $bookmarksString = ''; 52 | foreach ($this->bookmarks as $bookmark) { /** @var Bookmark $bookmark */ 53 | $bookmarksString .= "
url\" ADD_DATE=\"$bookmark->date\">$bookmark->name" . PHP_EOL; 54 | } 55 | 56 | return << 58 | 59 | 60 | CacoCloudBookmarks 61 |

CacoCloudBookmarks

62 |

63 |

CacoCloudBookmarks

64 | $bookmarksString 65 |

66 | EOT; 67 | } 68 | 69 | /** 70 | * Determines if the output should be downloadable in a browser. 71 | * 72 | * @return bool 73 | */ 74 | public function isFile() 75 | { 76 | return true; 77 | } 78 | 79 | /** 80 | * Gets the desired filename for downloading via HTTP. 81 | * 82 | * @return string 83 | */ 84 | public function getFileName() 85 | { 86 | return 'CacoCloudBookmarks.html'; 87 | } 88 | } -------------------------------------------------------------------------------- /src/main/Caco/Feed/SimplePieFeedReader.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class SimplePieFeedReader implements IFeedReader 12 | { 13 | /** 14 | * @var \SimplePie 15 | */ 16 | protected $simplePie; 17 | 18 | /** 19 | * @var string 20 | */ 21 | protected $url; 22 | 23 | public function __construct() 24 | { 25 | $this->simplePie = new \SimplePie(); 26 | $this->simplePie->enable_cache(false); 27 | $this->simplePie->set_cache_duration(600); 28 | } 29 | 30 | public function lookupFeedURL($url) 31 | { 32 | $response = json_decode(file_get_contents("http://ajax.googleapis.com/ajax/services/feed/lookup?v=1.0&q=$url")); 33 | 34 | return $response->responseStatus == 200 ? array('url' => $response->responseData->url) : null; 35 | } 36 | 37 | public function setFeed($url) 38 | { 39 | $this->simplePie->set_feed_url($this->url = $url); 40 | $this->simplePie->init(); 41 | } 42 | 43 | public function getTitle() 44 | { 45 | return $this->simplePie->get_title(); 46 | } 47 | 48 | public function getImageUrl() 49 | { 50 | $url = parse_url($this->simplePie->get_link()); 51 | $url = urlencode(sprintf('%s://%s', $url['scheme'], $url['host'])); 52 | 53 | return "http://g.etfv.co/$url"; 54 | } 55 | 56 | public function getItems() 57 | { 58 | $retVal = []; 59 | foreach ($this->simplePie->get_items() as $item) { /** @var \SimplePie_Item[] $item */ 60 | $retVal[] = [ 61 | 'uuid' => $item->get_id(), 62 | 'author' => $item->get_author() ? $item->get_author()->get_name() : null, 63 | 'title' => $item->get_title(), 64 | 'content' => $item->get_content(), 65 | 'url' => $item->get_link(), 66 | 'date' => ($timeStamp = strtotime($item->get_gmdate())) > 0 ? $timeStamp : time(), 67 | ]; 68 | } 69 | 70 | return $retVal; 71 | } 72 | } -------------------------------------------------------------------------------- /public/install/views/header.phtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | CacoCloud Installer 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

16 |
17 |
18 |

CacoCloud Installer

19 |
20 |
21 | 22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | $msg): ?> 32 |
33 | 34 |
35 | 36 |
37 |
38 | 39 |
40 | 56 |
-------------------------------------------------------------------------------- /assets/scripts/mail/config.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.mail', ['ui.router', 'caco.mail.crtl', 'caco.filter.base64', 'caco.Credentials']) 2 | .config(function($stateProvider) { 3 | $stateProvider 4 | .state('mail', { templateUrl: 'views/mail/main.html', controller: 'MailBoxesCrtl' }) 5 | .state('mail.overview', {url: '/mail', templateUrl: 'views/mail/main/list.html' }) 6 | .state('mail.list', {url: '/mail/list/:id/:mailBoxBase64', templateUrl: 'views/mail/main/list.html', controller: 'MailListCrtl' }) 7 | .state('mail.read', {url: '/mail/read/:id/:mailBoxBase64/:uniqueId', templateUrl: 'views/mail/main/read.html', controller: 'MailReadCrtl' }) 8 | .state('mail-reply', {url: '/mail/reply/:id/:mailBoxBase64/:uniqueId',templateUrl: 'views/mail/send.html', controller: 'MailReplyCrtl' }) 9 | .state('mail-send', {url: '/mail/send', templateUrl: 'views/mail/send.html', controller: 'MailSendCrtl' }) 10 | .state('mail-auth', {url: '/mail/auth', templateUrl: 'views/mail/auth.html', controller: 'MailAuthCrtl' }) 11 | .state('mail-manage', { templateUrl: 'views/mail/manage.html' }) 12 | .state('mail-manage.auth', {url: '/mail/auth', templateUrl: 'views/mail/auth.html', controller: 'MailManageCrtl'}) 13 | .state('mail-manage.list', {url: '/mail/manage', templateUrl: 'views/mail/manage/list.html', controller: 'MailManageCrtl'}) 14 | .state('mail-manage.add', {url: '/mail/manage/add', templateUrl: 'views/mail/manage/add.html', controller: 'MailManageCrtl'}) 15 | .state('mail-manage.edit', {url: '/mail/manage/edit/:id', templateUrl: 'views/mail/manage/edit.html', controller: 'MailManageCrtl'}); 16 | }) 17 | .run(function($rootScope) { 18 | $rootScope.moduleMail = true; 19 | }); -------------------------------------------------------------------------------- /src/main/Caco/Exports/Exporter/Xbel.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class Xbel implements IXmlExporter 16 | { 17 | /** 18 | * @var Bookmark[] 19 | */ 20 | protected $bookmarks; 21 | 22 | public function __construct(array $bookmarks = null) 23 | { 24 | $this->bookmarks = $bookmarks; 25 | } 26 | 27 | /** 28 | * @param \Caco\Bookmark\Model\Bookmark[] $bookmarks 29 | */ 30 | public function setBookmarks(array $bookmarks) 31 | { 32 | $this->bookmarks = $bookmarks; 33 | } 34 | 35 | /** 36 | * @return \Caco\Bookmark\Model\Bookmark[] 37 | */ 38 | public function getBookmarks() 39 | { 40 | return $this->bookmarks; 41 | } 42 | 43 | /** 44 | * Builds and returns the XBEL xml. 45 | * 46 | * @return string 47 | */ 48 | public function buildXML() 49 | { 50 | $w = new XMLWriter; 51 | $w->openMemory(); 52 | 53 | $w->startDocument('1.0', 'utf-8'); 54 | 55 | $w->startElement('xbel'); 56 | $w->writeAttribute('version', '1.0'); 57 | 58 | $w->writeElement('title', 'CacoCloud bookmarks'); 59 | 60 | foreach ($this->bookmarks as $bookmark) { 61 | $w->startElement('bookmark'); 62 | $w->writeAttribute('href', $bookmark->url); 63 | $w->writeAttribute('added', date('Y-m-d', $bookmark->date)); 64 | $w->writeElement('title', htmlentities($bookmark->name)); 65 | $w->endElement(); //bookmark 66 | } 67 | 68 | $w->endElement(); //xbel 69 | $w->endDocument(); 70 | 71 | return $w->outputMemory(true); 72 | } 73 | 74 | /** 75 | * Determines if the output should be downloadable in a browser. 76 | * 77 | * @return bool 78 | */ 79 | public function isFile() 80 | { 81 | return true; 82 | } 83 | 84 | /** 85 | * Gets the desired filename for downloading via HTTP. 86 | * 87 | * @return string 88 | */ 89 | public function getFileName() 90 | { 91 | return 'CacoCloudBookmarks.xbel'; 92 | } 93 | } -------------------------------------------------------------------------------- /assets/views/general/welcome.html: -------------------------------------------------------------------------------- 1 |
2 |
 
3 |
4 |
5 |
6 |
7 |

8 | Welcome

9 |
10 | 46 |
47 |
48 |
49 |
-------------------------------------------------------------------------------- /assets/scripts/feed/config.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.feed', ['ui.router', 'caco.feed.crtl', 'caco.feed.filter']) 2 | .config(function($stateProvider) { 3 | $stateProvider 4 | .state('feed', { templateUrl: 'views/feed/main.html', controller: 'FeedCrtl' }) 5 | .state('feed.overview', {url: '/feed/show', templateUrl: 'views/feed/main/list.html', controller: 'ItemsCrtl' }) 6 | .state('feed.list', {url: '/feed/show/:id', templateUrl: 'views/feed/main/list.html', controller: 'ItemsCrtl' }) 7 | .state('feed.item', {url: '/feed/show/:id/item/:id_item', templateUrl: 'views/feed/main/item.html', controller: 'ItemCrtl' }) 8 | .state('queue', { templateUrl: 'views/feed/main/queue.html', controller: 'ItemQueueMainCrtl' }) 9 | .state('queue.item', {url: '/feed/show/item/queue', templateUrl: 'views/feed/main/item.html', controller: 'ItemQueueCrtl' }) 10 | .state('feed-manage', { templateUrl: 'views/feed/manage.html' }) 11 | .state('feed-manage.list', {url: '/feed/manage', templateUrl: 'views/feed/manage/list.html', controller: 'FeedManageCrtl' }) 12 | .state('feed-manage.add', {url: '/feed/manage/add', templateUrl: 'views/feed/manage/add.html', controller: 'FeedManageCrtl' }) 13 | .state('feed-manage.edit', {url: '/feed/manage/edit/:id', templateUrl: 'views/feed/manage/edit.html', controller: 'FeedManageCrtl' }) 14 | .state('feed-manage.update-interval', {url: '/feed/manage/update-interval', templateUrl: 'views/feed/manage/update-interval.html', controller: 'FeedManageUpdateIntervalCrtl'}) 15 | .state('feed-manage.auto-cleanup', {url: '/feed/manage/auto-cleanup', templateUrl: 'views/feed/manage/auto-cleanup.html', controller: 'FeedManageAutoCleanupCrtl'}); 16 | }) 17 | .run(function($rootScope) { 18 | $rootScope.moduleFeed = true; 19 | }); -------------------------------------------------------------------------------- /public/install/views/step-user.phtml: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Creating a new user

4 | 5 |
6 |
7 | New user 8 | 9 |
10 | 12 |
13 | 21 | 22 |
23 |
24 | 25 |
26 | 28 |
29 | 37 | 38 |
39 |
40 | 41 |
42 | 44 |
45 | 53 | 54 |
55 |
56 | 57 |
58 | 59 |
60 | 61 |
62 |
63 | 64 |
65 |
66 | 67 | -------------------------------------------------------------------------------- /assets/scripts/general/config.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.password', []); 2 | 3 | angular.module('caco', ['ngAnimate', 'ngSanitize', 'caco.Credentials', 'caco.Alerts', 'caco.filter', 'caco.general.crtl', 'caco.password', 'caco.bookmark', 'caco.feed', 'caco.mail', 'caco.ClientPaginate']) 4 | .config(function($stateProvider) { 5 | $stateProvider 6 | .state('general', {url: '', templateUrl: 'views/general/welcome.html', controller: 'WelcomeCrtl'}) 7 | .state('welcome', {url: '/welcome', templateUrl: 'views/general/welcome.html', controller: 'WelcomeCrtl'}) 8 | .state('login', {url: '/login', templateUrl: 'views/general/login.html', controller: 'AccountCrtl'}) 9 | .state('logout', {url: '/logout', templateUrl: 'views/general/logout.html', controller: 'AccountCrtl'}) 10 | .state('about', {url: '/about', templateUrl: 'views/general/about.html'}) 11 | }) 12 | .config(function ($httpProvider) { 13 | $httpProvider.interceptors.push(function ($q, $location, $rootScope, Credentials) { 14 | return { 15 | request: function(config) { 16 | config.headers.Authorization = Credentials.basicAuthHeader(); 17 | 18 | if ($rootScope.loading) { 19 | $rootScope.loading++ 20 | } else { 21 | $rootScope.loading = 1; 22 | } 23 | 24 | return config || $q.when(config); 25 | }, 26 | response: function (response) { 27 | $rootScope.loading && $rootScope.loading--; 28 | 29 | return response; 30 | }, 31 | responseError: function (rejection) { 32 | $rootScope.loading && $rootScope.loading--; 33 | 34 | if(rejection.status === 401) { 35 | Credentials.init(); 36 | $location.path('/login'); 37 | } 38 | 39 | return $q.reject(rejection); 40 | } 41 | }; 42 | }) 43 | }) 44 | .run(function($rootScope, $location, Credentials) { 45 | $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) { 46 | if (Credentials.empty() && toState.url != '/login') { 47 | $location.path('/login'); 48 | event.preventDefault(); 49 | } 50 | }); 51 | }); -------------------------------------------------------------------------------- /assets/views/mail/main/list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 23 | 30 | 35 | 49 | 50 | 51 | 52 | 53 | 67 | 68 | 69 |
SubjectFromDate
14 | 15 | 16 | {{header.subject}} 17 | 18 | 19 | {{header.subject}} 20 | 21 | 22 | 24 | 25 | 26 | {{header.from}} 27 | 28 | 29 | 31 | 32 | {{header.unixTimeStamp | unixTimeStamp | date:'yyyy-MM-dd HH:mm'}} 33 | 34 | 36 |
37 | 40 | 41 | 42 | 47 |
48 |
54 |
    55 | 60 | 65 |
66 |
-------------------------------------------------------------------------------- /assets/views/feed/manage/add.html: -------------------------------------------------------------------------------- 1 |
2 |
4 |
5 | Lookup a feed url 6 |
7 | 9 |
10 | 15 | 16 | 17 | 18 |
19 |
20 | 21 |
22 |
23 | 27 |
28 |
29 |
30 |
31 | 32 |
34 |
35 | Add a feed 36 | 37 |
38 | 40 |
41 | 46 | 47 | 48 | 49 |
50 |
51 | 52 |
53 |
54 | 58 |
59 |
60 |
61 |
62 |
-------------------------------------------------------------------------------- /assets/views/general/login.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
5 |
6 | Login 7 | 8 |
9 | 11 |
12 | 17 |
18 |
19 | 20 |
21 | 23 |
24 | 29 |
30 |
31 | 32 |
33 | 35 |
36 | 46 |
47 |
48 | 49 |
50 |
51 | 55 |
56 |
57 |
58 |
59 |
60 | -------------------------------------------------------------------------------- /assets/views/feed/main/list.html: -------------------------------------------------------------------------------- 1 |
3 | 4 | 5 | 6 | 14 | 15 | 16 | 17 | 19 | 26 | 42 | 51 | 52 | 53 | 54 | 55 | 58 | 59 | 60 |
7 |
8 | 12 |
13 |
20 | 21 | 24 | 25 | 27 | 30 | 31 | 33 | 34 | 35 |
36 | 37 | {{item.date | unixTimeStamp | date:'yyyy-MM-dd HH:mm'}} 38 | 39 | - {{item.author}} 40 |
41 |
43 | 50 |
56 | 57 |
61 |
-------------------------------------------------------------------------------- /src/main/Caco/Exports/REST.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | class REST 21 | { 22 | /** 23 | * @var \Slim\Slim 24 | */ 25 | protected $app; 26 | 27 | public function __construct() 28 | { 29 | $this->app = Slim::getInstance(); 30 | } 31 | 32 | /** 33 | * GET /export/feed/:id/atom 34 | * 35 | * @param int $id 36 | */ 37 | public function getFeedItemsAtom($id) 38 | { 39 | $feed = new Feed; 40 | if (!$feed->read($id)) { 41 | $this->app->render(404); 42 | 43 | return; 44 | } 45 | 46 | $items = (new Item)->readList('id_feed = ?', [$feed->id]); 47 | $this->xmlOutput(new Atom($feed, $items)); 48 | } 49 | 50 | /** 51 | * GET /export/feed/opml 52 | */ 53 | public function getAllFeedsOpml() 54 | { 55 | /** @var Config $apiUrl */ 56 | $apiUrl = (new Config)->readListByPrefix('api-url')[0]; 57 | $this->xmlOutput(new Opml($apiUrl->value, (new Feed)->all())); 58 | } 59 | 60 | /** 61 | * GET /export/bookmark/xbel 62 | */ 63 | public function getBookmarksXbel() 64 | { 65 | $bookmarks = (new Bookmark)->readList(); 66 | $this->xmlOutput(new Xbel($bookmarks)); 67 | } 68 | 69 | /** 70 | * GET /export/bookmark/html 71 | */ 72 | public function getBookmarksHtml() 73 | { 74 | $bookmarks = (new Bookmark)->readList(); 75 | $this->xmlOutput(new BookmarkHtml($bookmarks), 200, 'text/html'); 76 | } 77 | 78 | /** 79 | * @param IXmlExporter $exporter 80 | * @param int $status 81 | * @param string $contentType 82 | */ 83 | protected function xmlOutput(IXmlExporter $exporter, $status = 200, $contentType = 'application/xml') 84 | { 85 | $this->app->expires(0); 86 | $this->app->contentType($contentType); 87 | $response = $this->app->response(); 88 | $response->setStatus($status); 89 | $response->header('Content-Description', 'File Transfer'); 90 | if ($exporter->isFile()) { 91 | $response->header('Content-Disposition', 'attachment; filename=' . $exporter->getFileName()); 92 | } 93 | $response->body($exporter->buildXml()); 94 | } 95 | } -------------------------------------------------------------------------------- /assets/scripts/general/service/TemporaryStorage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A simple local storage module with cache expires time and offline mode. 3 | * 4 | * By Guido Krömer 5 | * */ 6 | angular.module('caco.TemporaryStorage', []) 7 | .service('TempStorage', function () { 8 | this.expires = 60000; //10 Min 9 | 10 | this.getObject = function (key) { 11 | return JSON.parse(this.get(key)) 12 | }; 13 | 14 | this.setObject = function (key, value, expires) { 15 | this.set(key, JSON.stringify(value), expires); 16 | }; 17 | 18 | this.get = function (key) { 19 | if (this.cacheValid(key)) { 20 | return localStorage.getItem(key); 21 | } 22 | 23 | return null; 24 | }; 25 | 26 | this.set = function (key, value, expires) { 27 | if (!expires) { 28 | expires = this.expires; 29 | } 30 | 31 | try { 32 | this.cacheAdd(key, expires); 33 | localStorage.setItem(key, value); 34 | } catch (e) { 35 | switch (e.name) { 36 | case 'QuotaExceededError': // Chrome and IE 37 | case 'QUOTA_EXCEEDED_ERR': // Chrome 38 | case 'NS_ERROR_DOM_QUOTA_REACHED': // Firefox 39 | localStorage.clear(); 40 | break; 41 | default: 42 | console.log('LocalStorage quota exceeded? ' + e.name); 43 | } 44 | } 45 | }; 46 | 47 | this.remove = function (key) { 48 | localStorage.removeItem(key); 49 | localStorage.removeItem('time-' + key); 50 | }; 51 | 52 | this.contains = function (key) { 53 | if (this.cacheValid(key)) { 54 | return localStorage.getItem(key) ? true : false; 55 | } 56 | 57 | return false; 58 | }; 59 | 60 | this.cacheValid = function (key) { 61 | var time = parseInt(localStorage.getItem('time-' + key)); 62 | 63 | if (!time) { 64 | return false; 65 | } 66 | 67 | if (!navigator.onLine) { 68 | return true; 69 | } 70 | 71 | if (time < new Date().getTime()) { 72 | this.remove(key); 73 | 74 | return false; 75 | } 76 | 77 | return true; 78 | }; 79 | 80 | this.cacheAdd = function (key, expires) { 81 | localStorage.setItem('time-' + key, parseInt(expires) + new Date().getTime()); 82 | }; 83 | 84 | this.clear = function () { 85 | localStorage.clear(); 86 | }; 87 | }); -------------------------------------------------------------------------------- /src/main/Caco/Mail/IMAP/Account.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | class Account implements \JsonSerializable 10 | { 11 | const TYPE_IMAP = 1; 12 | 13 | const TYPE_POP3 = 0; 14 | 15 | const PORT_IMAP = 143; 16 | 17 | const PORT_IMAPS = 993; 18 | 19 | const PORT_POP3 = 110; 20 | 21 | const PORT_POP3S = 995; 22 | 23 | /** 24 | * @var string 25 | */ 26 | public $host = ''; 27 | 28 | /** 29 | * @var int 30 | */ 31 | public $port = self::PORT_IMAP; 32 | 33 | /** 34 | * @var string 35 | */ 36 | public $userName = ''; 37 | 38 | /** 39 | * @var string 40 | */ 41 | public $password = ''; 42 | 43 | /** 44 | * @var int 45 | */ 46 | public $type = self::TYPE_IMAP; 47 | 48 | /** 49 | * @var bool 50 | */ 51 | public $ssl = false; 52 | 53 | /** 54 | * @var bool 55 | */ 56 | public $tls = false; 57 | 58 | /** 59 | * @var bool 60 | */ 61 | public $noTls = false; 62 | 63 | /** 64 | * @var bool 65 | */ 66 | public $secure = false; 67 | 68 | /** 69 | * @var bool 70 | */ 71 | public $validateCert = false; 72 | 73 | public function __toString() 74 | { 75 | $params = ''; 76 | $params .= $this->ssl ? '/ssl' : ''; 77 | $params .= !$this->validateCert ? '/novalidate-cert' : ''; 78 | $params .= $this->tls ? '/tls' : ''; 79 | $params .= $this->noTls ? '/notls' : ''; 80 | $params .= $this->secure ? '/secure' : ''; 81 | $type = $this->type == self::TYPE_POP3 ? 'pop3' : 'imap'; 82 | 83 | return sprintf('{%s:%d/%s%s}', $this->host, $this->port, $type, $params); 84 | } 85 | 86 | /** 87 | * Set multiple fields at once, by providing an assoc array. 88 | * 89 | * @param array $data 90 | */ 91 | public function setArray(array $data) 92 | { 93 | $className = get_class(); 94 | foreach ($data as $key => $value) { 95 | if (property_exists($className, $key)) { 96 | $this->$key = $value; 97 | } 98 | } 99 | } 100 | 101 | public function jsonSerialize() 102 | { 103 | return [ 104 | 'host' => $this->host, 105 | 'port' => $this->port, 106 | 'userName' => $this->userName, 107 | 'type' => $this->type, 108 | 'ssl' => $this->ssl, 109 | 'tls' => $this->tls, 110 | 'noTls' => $this->noTls, 111 | 'secure' => $this->secure, 112 | 'validateCert' => $this->validateCert, 113 | ]; 114 | } 115 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CacoCloud 2 | 3 | A simple, fast and secure PHP/AngularJS based single user feed and mail reader, password and bookmark manager. 4 | CacoCloud is divided into a [RESTful](https://github.com/Cacodaimon/CacoCloud/wiki/RESTful-API) PHP backend storing all data into a [SQLite](http://www.sqlite.org/) database and an SPA frontend based on [AngularJs](http://angularjs.org/). 5 | 6 | I have mainly written CacoCloud to fit my personal needs, but maybe it fits your needs, too. 7 | 8 | You can find some screenshots in the [wiki](https://github.com/Cacodaimon/CacoCloud/wiki/Screenshots). 9 | 10 | ## Installation 11 | 12 | For installing you can simply grap the archive file from the [homepage](http://cacodaimon.github.io/CacoCloud/) or build CacoCoud by your self. 13 | Watch the [video](https://www.youtube.com/watch?v=6nWa1qQuda4) how to install CacoCloud, from the archive [file](http://cacodaimon.github.io/CacoCloud/), in less than two minutes on a DigitalOcean VM with Debain Wheezy. 14 | 15 | If you wanna build it by your self, on a *nix machine you can run the `build.sh` script. 16 | This script will install the needed [node.js](http://nodejs.org/) modules for running grunt, installs [Composer](http://getcomposer.org/) and let the compser install all required PHP libs and creates a new and empty database. It also lets you create new user account. 17 | 18 | If you are planning to use the password manager component and using [Apache](http://httpd.apache.org/) you should modify log format, take the example virtual host file from `config/vhost.cfg`! And don't forget to use https instead of http! 19 | 20 | ## Used components 21 | 22 | CacoCloud is based on some awesome open source libraries and frameworks. 23 | 24 | The RESTful backend is based on [Slim PHP](http://www.slimframework.com/), rss/atom feeds gets parsed by [SimplePie](http://simplepie.org/) and E-Mail are send through SMTP by [PHPMailer](http://simplepie.org/). 25 | 26 | Since CacoCloud is an SPA, the frontend is build with love, [AngularJs](http://angularjs.org/) and [AngularUI Router](https://github.com/angular-ui/ui-router). Password stored into the password manager gets encrypted by [crypto-js](https://code.google.com/p/crypto-js/) on the client (no fear they are encrypted a second time by [Mcrypt](http://php.net/mcrypt) before they are stored into the database on the server). [zxcvbn](https://github.com/lowe/zxcvbn) estimates the strength of you password before storing it into the password manager. 27 | 28 | Thanks to [Bootstrap 3.0](http://getbootstrap.com/) the frontend is clean and responsible and a nice theme from [Bootswatch](http://bootswatch.com/) lets it not looks so Bootstrapped. The icons are by [Font Awesome](http://fontawesome.io/). 29 | 30 | At last all frontend code gets minified with the use of [Grunt](http://gruntjs.com/). 31 | 32 | [![Stories in Ready](https://badge.waffle.io/Cacodaimon/CacoCloud.png?label=ready)](http://waffle.io/Cacodaimon/CacoCloud) 33 | 34 | -------------------------------------------------------------------------------- /src/main/Caco/Exports/Exporter/Opml.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class Opml implements IXmlExporter 16 | { 17 | /** 18 | * @var Feed[] 19 | */ 20 | protected $feeds; 21 | 22 | /** 23 | * @var string 24 | */ 25 | protected $apiUrl; 26 | 27 | public function __construct($apiUrl = null, array $feeds = null) 28 | { 29 | $this->apiUrl = $apiUrl; 30 | $this->feeds = $feeds; 31 | } 32 | 33 | /** 34 | * @param \Caco\Feed\Model\Feed $feed 35 | */ 36 | public function setFeeds(array $feeds) 37 | { 38 | $this->feeds = $feeds; 39 | } 40 | 41 | /** 42 | * @return \Caco\Feed\Model\Feed 43 | */ 44 | public function getFeeds() 45 | { 46 | return $this->feeds; 47 | } 48 | 49 | /** 50 | * @param string $apiUrl 51 | */ 52 | public function setApiUrl($apiUrl) 53 | { 54 | $this->apiUrl = $apiUrl; 55 | } 56 | 57 | /** 58 | * @return string 59 | */ 60 | public function getApiUrl() 61 | { 62 | return $this->apiUrl; 63 | } 64 | 65 | /** 66 | * Builds and returns the OPML xml. 67 | * 68 | * @return string 69 | */ 70 | public function buildXML() 71 | { 72 | $w = new XMLWriter; 73 | $w->openMemory(); 74 | 75 | $w->startDocument('1.0', 'utf-8'); 76 | 77 | $w->startElement('opml'); 78 | $w->writeAttribute('version', '1.0'); 79 | 80 | $w->startElement('head'); 81 | $w->writeElement('title', 'CacoCloud feeds'); 82 | $w->endElement(); //head 83 | 84 | $w->startElement('body'); 85 | 86 | foreach ($this->feeds as $feed) { 87 | $w->startElement('outline'); 88 | 89 | $w->writeAttribute('text', $feed->title); 90 | $w->writeAttribute('title', $feed->title); 91 | $w->writeAttribute('type', 'atom'); 92 | $w->writeAttribute('xmlUrl', "$this->apiUrl/export/feed/$feed->id/atom"); 93 | 94 | $w->endElement(); 95 | } 96 | 97 | $w->endElement(); //body 98 | $w->endDocument(); 99 | 100 | return $w->outputMemory(true); 101 | } 102 | 103 | /** 104 | * Determines if the output should be downloadable in a browser. 105 | * 106 | * @return bool 107 | */ 108 | public function isFile() 109 | { 110 | return true; 111 | } 112 | 113 | /** 114 | * Gets the desired filename for downloading via HTTP. 115 | * 116 | * @return string 117 | */ 118 | public function getFileName() 119 | { 120 | return 'CacoCloudFeeds.opml'; 121 | } 122 | } -------------------------------------------------------------------------------- /src/main/Caco/Upgrade/CLI/From2To3.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class From2To3 extends AbstractCLI 17 | { 18 | private $types = ['bookmark', 'feed']; 19 | 20 | /** 21 | * Runs the cli 22 | * 23 | * @return int 24 | */ 25 | public function run() 26 | { 27 | $config = new Config; 28 | $config->readKey('database-version'); 29 | 30 | if ($config->value != 2) { 31 | throw new RuntimeException("Database Version is \"$config->value\" but should be \"2\"!"); 32 | } 33 | 34 | $this->upgradeDb(); 35 | $this->importIcons(); 36 | 37 | return 0; 38 | } 39 | 40 | /** 41 | * Upgrades the DB from Version 2 to 3. 42 | */ 43 | protected function upgradeDb() { 44 | $this->printLine('Upgrading the DB…'); 45 | $pdo = (new Icon)->getPdo(); 46 | 47 | $sql = 'CREATE TABLE IF NOT EXISTS icon ( 48 | id INTEGER PRIMARY KEY, 49 | inserted INTEGER NOT NULL, 50 | id_feed INTEGER UNIQUE, 51 | id_bookmark INTEGER UNIQUE, 52 | data BLOB NOT NULL, 53 | FOREIGN KEY (id_feed) REFERENCES feed(id) ON DELETE CASCADE 54 | FOREIGN KEY (id_bookmark) REFERENCES bookmark(id) ON DELETE CASCADE 55 | ); 56 | CREATE INDEX IF NOT EXISTS fk_icon_id_feed ON icon (id_feed); 57 | CREATE INDEX IF NOT EXISTS fk_icon_id_bookmark ON icon (id_bookmark);'; 58 | 59 | $pdo->exec($sql); 60 | 61 | $config = new Config; 62 | $config->readKey('database-version'); 63 | $config->value = 3; 64 | $config->save(); 65 | $this->printLine('DB upgraded to 3!'); 66 | } 67 | 68 | /** 69 | * Imports the favicons into the database and deletes them from filesystem. 70 | */ 71 | protected function importIcons() 72 | { 73 | $this->printLine('Importing icons…'); 74 | $iconsDir = sprintf('%s/public/icons', getcwd()); 75 | 76 | foreach ($this->types as $type) { 77 | $path = "$iconsDir/$type/*.ico"; 78 | foreach (glob($path) as $file) { 79 | $icon = new Icon; 80 | $icon->data = file_get_contents($file); 81 | if ($type === 'feed') { 82 | $icon->id_feed = str_replace('.ico', '', basename($file)); 83 | } else { 84 | $icon->id_bookmark = str_replace('.ico', '', basename($file)); 85 | } 86 | 87 | if ($icon->save()) { 88 | unlink($file); 89 | } 90 | } 91 | } 92 | $this->printLine('Icons imported!'); 93 | } 94 | } -------------------------------------------------------------------------------- /database/create.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS user ( 2 | id INTEGER PRIMARY KEY, 3 | userName TEXT UNIQUE, 4 | hash TEXT NOT NULL 5 | ); 6 | 7 | CREATE TABLE IF NOT EXISTS container ( 8 | id INTEGER PRIMARY KEY, 9 | data TEXT NOT NULL, 10 | passwordSalt TEXT NOT NULL, 11 | initializationVector TEXT NOT NULL, 12 | cipher TEXT NOT NULL 13 | ); 14 | 15 | CREATE TABLE IF NOT EXISTS bookmark ( 16 | id INTEGER PRIMARY KEY, 17 | name TEXT NOT NULL, 18 | url TEXT UNIQUE, 19 | date INTEGER 20 | ); 21 | 22 | INSERT INTO bookmark (name, url) VALUES ('Cacomania', 'http://cacodaemon.de/'); 23 | 24 | CREATE TABLE IF NOT EXISTS feed ( 25 | id INTEGER PRIMARY KEY, 26 | title TEXT NOT NULL, 27 | url TEXT UNIQUE, 28 | updated INTEGER, 29 | interval INTEGER 30 | ); 31 | 32 | INSERT INTO feed (title, url, updated, interval) VALUES ('Cacomania', 'http://cacodaemon.de/index.php?atom=1', 0, 86400); 33 | 34 | CREATE TABLE IF NOT EXISTS item ( 35 | id INTEGER PRIMARY KEY, 36 | id_feed INTEGER NOT NULL, 37 | uuid TEXT UNIQUE, 38 | title TEXT NOT NULL, 39 | author TEXT, 40 | content TEXT, 41 | url TEXT NOT NULL, 42 | date INTEGER NOT NULL, 43 | read INTEGER, 44 | FOREIGN KEY (id_feed) REFERENCES feed(id) ON DELETE CASCADE 45 | ); 46 | CREATE INDEX IF NOT EXISTS fk_item_id_feed ON item (id_feed); 47 | 48 | CREATE TABLE IF NOT EXISTS itemqueue ( 49 | id INTEGER PRIMARY KEY, 50 | id_item INTEGER UNIQUE, 51 | inserted INTEGER NOT NULL, 52 | FOREIGN KEY (id_item) REFERENCES item(id) ON DELETE CASCADE 53 | ); 54 | CREATE INDEX IF NOT EXISTS fk_itemqueue_id_item ON itemqueue (id_item); 55 | 56 | CREATE TABLE IF NOT EXISTS config ( 57 | id INTEGER PRIMARY KEY, 58 | key TEXT UNIQUE, 59 | value TEXT NOT NULL 60 | ); 61 | 62 | CREATE TABLE IF NOT EXISTS mailaccount ( 63 | id INTEGER PRIMARY KEY, 64 | data TEXT NOT NULL, 65 | passwordSalt TEXT NOT NULL, 66 | initializationVector TEXT NOT NULL, 67 | cipher TEXT NOT NULL 68 | ); 69 | 70 | CREATE TABLE IF NOT EXISTS icon ( 71 | id INTEGER PRIMARY KEY, 72 | inserted INTEGER NOT NULL, 73 | id_feed INTEGER UNIQUE, 74 | id_bookmark INTEGER UNIQUE, 75 | data BLOB NOT NULL, 76 | FOREIGN KEY (id_feed) REFERENCES feed(id) ON DELETE CASCADE 77 | FOREIGN KEY (id_bookmark) REFERENCES bookmark(id) ON DELETE CASCADE 78 | ); 79 | CREATE INDEX IF NOT EXISTS fk_icon_id_feed ON icon (id_feed); 80 | CREATE INDEX IF NOT EXISTS fk_icon_id_bookmark ON icon (id_bookmark); 81 | 82 | INSERT INTO config (key, value) VALUES ('auto-cleanup-enabled', 'true'); 83 | INSERT INTO config (key, value) VALUES ('auto-cleanup-days', 7); 84 | INSERT INTO config (key, value) VALUES ('auto-cleanup-min-item-count', 250); 85 | INSERT INTO config (key, value) VALUES ('auto-cleanup-only-read', 'false'); 86 | INSERT INTO config (key, value) VALUES ('auto-cleanup-max-item-count', 1000); 87 | INSERT INTO config (key, value) VALUES ('auto-cleanup-max-item-count-enabled', 'true'); 88 | 89 | INSERT INTO config (key, value) VALUES ('update-interval-min', 600); 90 | INSERT INTO config (key, value) VALUES ('update-interval-max', 604800); 91 | 92 | INSERT INTO config (key, value) VALUES ('database-version', 3); 93 | INSERT INTO config (key, value) VALUES ('api-url', 'http://localhost:8000/api'); -------------------------------------------------------------------------------- /assets/views/feed/manage/update-interval.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
5 |
6 | Update interval 7 | 8 |
9 | 12 |
13 | 18 | 19 | 20 | 21 |
22 |
23 | 24 |
25 | 28 |
29 | 34 | 35 | 36 | 37 |
38 |
39 | 40 |
41 |
42 | 46 |
47 |
48 |
49 |
50 |
51 |
52 |
54 |
55 | Calculate optimal feed update interval 56 | 57 |
58 | 59 | Calculating... 60 |
61 |
62 | Calculated! 63 |
64 | 65 |
66 |
67 | 71 |
72 |
73 |
74 |
75 |
76 |
-------------------------------------------------------------------------------- /spec/api/config_spec.js: -------------------------------------------------------------------------------- 1 | var frisby = require('frisby'); 2 | var config = require('./config.js'); 3 | var url = config.apiUrl + 'config'; 4 | 5 | frisby.create('API: List config') 6 | .get(url) 7 | .expectStatus(200) 8 | .expectHeaderContains('content-type', 'application/json') 9 | .expectJSONTypes({ 10 | status: Number, 11 | response: Array 12 | }) 13 | .toss(); 14 | 15 | frisby.create('API: Get config value: "database-version"') 16 | .get(url + '/database-version') 17 | .expectStatus(200) 18 | .expectHeaderContains('content-type', 'application/json') 19 | .expectJSONTypes({ 20 | status: Number, 21 | response: [{ 22 | id: Number, 23 | key: String, 24 | value: function(val) { expect(val).toBeType(); } 25 | }] 26 | }) 27 | .toss(); 28 | 29 | frisby.create('API: Add a new config value') 30 | .post(url, { 31 | key: 'TEST-KEY-1', 32 | value: 'TEST-VALUE-1' 33 | }, {json: true}) 34 | .expectHeaderContains('content-type', 'application/json') 35 | .expectStatus(201) 36 | .expectJSONTypes({ 37 | status: Number, 38 | response: Number 39 | }) 40 | .afterJSON(function(api) { 41 | var id = api.response; 42 | 43 | frisby.create('API: Get config value for key: "TEST-KEY-1"') 44 | .get(url + '/TEST-KEY-1') 45 | .expectStatus(200) 46 | .expectHeaderContains('content-type', 'application/json') 47 | .expectJSON({ 48 | status: 200, 49 | response: [ 50 | { 51 | id: id, 52 | key: 'TEST-KEY-1', 53 | value: 'TEST-VALUE-1' 54 | } 55 | ] 56 | }) 57 | .toss(); 58 | 59 | frisby.create('API: Edit config value for key: "TEST-KEY-1"') 60 | .put(url + '/TEST-KEY-1', { 61 | value: 'TEST-VALUE-ONE' 62 | }, {json: true}) 63 | .expectStatus(200) 64 | .expectHeaderContains('content-type', 'application/json') 65 | .expectJSONTypes({ 66 | status: Number, 67 | response: Number 68 | }) 69 | .toss(); 70 | 71 | frisby.create('API: Delete config value: "TEST-KEY-1"') 72 | .delete(url + '/TEST-KEY-1') 73 | .expectStatus(200) 74 | .expectHeaderContains('content-type', 'application/json') 75 | .expectJSONTypes({ 76 | status: Number, 77 | response: Number 78 | }) 79 | .toss(); 80 | }) 81 | .toss(); 82 | 83 | 84 | frisby.create('API: Delete a non existing key: "UNKNOWN-TEST-KEY"') 85 | .delete(url + '/UNKNOWN-TEST-KEY') 86 | .expectStatus(404) 87 | .expectHeaderContains('content-type', 'application/json') 88 | .expectJSONTypes({ 89 | status: Number 90 | }) 91 | .toss(); 92 | 93 | frisby.create('API: Edit a non existing key: "UNKNOWN-TEST-KEY"') 94 | .delete(url + '/UNKNOWN-TEST-KEY') 95 | .expectStatus(404) 96 | .expectHeaderContains('content-type', 'application/json') 97 | .expectJSONTypes({ 98 | status: Number 99 | }) 100 | .toss(); -------------------------------------------------------------------------------- /src/main/Caco/Mail/Account.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | class Account implements \JsonSerializable 10 | { 11 | /** 12 | * @var \Caco\Mail\SMTP\Account 13 | */ 14 | protected $smtp; 15 | 16 | /** 17 | * @var \Caco\Mail\IMAP\Account 18 | */ 19 | protected $imap; 20 | 21 | /** 22 | * @var string 23 | */ 24 | protected $name = ''; 25 | 26 | /** 27 | * @var int 28 | */ 29 | protected $id = 0; 30 | 31 | /** 32 | * @param string $name 33 | * @param \Caco\Mail\IMAP\Account $imap 34 | * @param \Caco\Mail\SMTP\Account $smtp 35 | */ 36 | public function __construct($name = '', \Caco\Mail\IMAP\Account $imap = null, \Caco\Mail\SMTP\Account $smtp = null) 37 | { 38 | $this->name = $name; 39 | $this->imap = $imap; 40 | $this->smtp = $smtp; 41 | } 42 | 43 | /** 44 | * @param \Caco\Mail\IMAP\Account $imap 45 | */ 46 | public function setImap(\Caco\Mail\IMAP\Account $imap) 47 | { 48 | $this->imap = $imap; 49 | } 50 | 51 | /** 52 | * @return \Caco\Mail\IMAP\Account 53 | */ 54 | public function getImap() 55 | { 56 | return $this->imap; 57 | } 58 | 59 | /** 60 | * @param \Caco\Mail\SMTP\Account $smtp 61 | */ 62 | public function setSmtp(\Caco\Mail\SMTP\Account $smtp) 63 | { 64 | $this->smtp = $smtp; 65 | } 66 | 67 | /** 68 | * @return \Caco\Mail\SMTP\Account 69 | */ 70 | public function getSmtp() 71 | { 72 | return $this->smtp; 73 | } 74 | 75 | /** 76 | * @param string $name 77 | */ 78 | public function setName($name) 79 | { 80 | $this->name = $name; 81 | } 82 | 83 | /** 84 | * @return string 85 | */ 86 | public function getName() 87 | { 88 | return $this->name; 89 | } 90 | 91 | /** 92 | * @param int $id 93 | */ 94 | public function setId($id) 95 | { 96 | $this->id = $id; 97 | } 98 | 99 | /** 100 | * @return int 101 | */ 102 | public function getId() 103 | { 104 | return $this->id; 105 | } 106 | 107 | /** 108 | * Set multiple fields at once, by providing an assoc array. 109 | * 110 | * @param array $data 111 | */ 112 | public function setArray(array $data) 113 | { 114 | if (isset($data['imap']) && !empty($data['imap'])) { 115 | if (is_null($this->imap)) { 116 | $this->imap = new \Caco\Mail\IMAP\Account; 117 | } 118 | $this->imap->setArray($data['imap']); 119 | } 120 | 121 | if (isset($data['smtp']) && !empty($data['smtp'])) { 122 | if (is_null($this->smtp)) { 123 | $this->smtp = new \Caco\Mail\SMTP\Account; 124 | } 125 | $this->smtp->setArray($data['smtp']); 126 | } 127 | 128 | if (isset($data['name']) && !empty($data['name']) && is_string($data['name'])) { 129 | $this->name = $data['name']; 130 | } 131 | } 132 | 133 | public function jsonSerialize() 134 | { 135 | return [ 136 | 'id' => $this->id, 137 | 'name' => $this->name, 138 | 'imap' => $this->imap, 139 | 'smtp' => $this->smtp, 140 | ]; 141 | } 142 | } -------------------------------------------------------------------------------- /src/main/Caco/Bookmark/REST.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class REST 15 | { 16 | public function __construct() 17 | { 18 | $this->app = Slim::getInstance(); 19 | } 20 | 21 | /** 22 | * GET /bookmark/:id 23 | * 24 | * @param int $id 25 | */ 26 | public function one($id) 27 | { 28 | $bookmark = new Bookmark; 29 | if ($bookmark->read($id)) { 30 | $this->app->render(200, ['response' => $bookmark]); 31 | } else { 32 | $this->app->render(404); 33 | } 34 | } 35 | 36 | /** 37 | * GET /bookmark 38 | */ 39 | public function all() 40 | { 41 | $this->app->render(200, ['response' => (new Bookmark)->readList()]); 42 | } 43 | 44 | /** 45 | * POST /bookmark 46 | */ 47 | public function add() 48 | { 49 | $data = json_decode($this->app->request()->getBody(), true); 50 | 51 | $bookmark = new Bookmark; 52 | $bookmark->name = $this->getTitleFromUrl($data['url'], isset($data['name']) ? $data['name'] : ''); 53 | $bookmark->url = $data['url']; 54 | $bookmark->date = isset($data['date']) && is_numeric($data['date']) ? intval($data['date']) : time(); 55 | 56 | if ($bookmark->save()) { 57 | (new FaviconDownloader)->downloadBookmark($bookmark); 58 | 59 | $this->app->render(201, ['response' => $bookmark->id]); 60 | } else { 61 | $this->app->render(500); 62 | } 63 | } 64 | 65 | /** 66 | * PUT /bookmark/:id 67 | * 68 | * @param int $id 69 | */ 70 | public function edit($id) 71 | { 72 | $bookmark = new Bookmark; 73 | if ($bookmark->read($id)) { 74 | $data = json_decode($this->app->request()->getBody(), true); 75 | $bookmark->name = $data['name']; 76 | $bookmark->url = $data['url']; 77 | 78 | $this->app->render($bookmark->save() ? 200 : 500, ['response' => $id]); 79 | } else { 80 | $this->app->render(404); 81 | } 82 | } 83 | 84 | /** 85 | * DELETE /bookmark/:id 86 | * 87 | * @param int $id 88 | */ 89 | public function delete($id) 90 | { 91 | $bookmark = new Bookmark; 92 | if ($bookmark->read($id)) { 93 | $this->app->render($bookmark->delete() ? 200 : 500, ['response' => $id]); 94 | } else { 95 | $this->app->render(404); 96 | } 97 | } 98 | 99 | /** 100 | * Gets the html title from the url. 101 | * 102 | * @param string $url 103 | * @param string $default 104 | * @return string 105 | */ 106 | protected function getTitleFromUrl($url, $default = '') 107 | { 108 | if (!empty($default)) { 109 | return $default; 110 | } 111 | 112 | $client = new Client(); 113 | $response = $client->get($url); 114 | 115 | preg_match('/(.+)<\/title>/', $response->getBody()->getContents(), $matches); 116 | 117 | if (empty($matches)) { 118 | return $url; 119 | } 120 | 121 | return mb_convert_encoding(html_entity_decode($matches[1]), 'UTF-8', 'UTF-8'); 122 | } 123 | } -------------------------------------------------------------------------------- /assets/scripts/vendor-js/angular/angular-resource.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.3.12 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(I,d,B){'use strict';function D(f,q){q=q||{};d.forEach(q,function(d,h){delete q[h]});for(var h in f)!f.hasOwnProperty(h)||"$"===h.charAt(0)&&"$"===h.charAt(1)||(q[h]=f[h]);return q}var w=d.$$minErr("$resource"),C=/^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/;d.module("ngResource",["ng"]).provider("$resource",function(){var f=this;this.defaults={stripTrailingSlashes:!0,actions:{get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}}}; 7 | this.$get=["$http","$q",function(q,h){function t(d,g){this.template=d;this.defaults=s({},f.defaults,g);this.urlParams={}}function v(x,g,l,m){function c(b,k){var c={};k=s({},g,k);r(k,function(a,k){u(a)&&(a=a());var d;if(a&&a.charAt&&"@"==a.charAt(0)){d=b;var e=a.substr(1);if(null==e||""===e||"hasOwnProperty"===e||!C.test("."+e))throw w("badmember",e);for(var e=e.split("."),n=0,g=e.length;n<g&&d!==B;n++){var h=e[n];d=null!==d?d[h]:B}}else d=a;c[k]=d});return c}function F(b){return b.resource}function e(b){D(b|| 8 | {},this)}var G=new t(x,m);l=s({},f.defaults.actions,l);e.prototype.toJSON=function(){var b=s({},this);delete b.$promise;delete b.$resolved;return b};r(l,function(b,k){var g=/^(POST|PUT|PATCH)$/i.test(b.method);e[k]=function(a,y,m,x){var n={},f,l,z;switch(arguments.length){case 4:z=x,l=m;case 3:case 2:if(u(y)){if(u(a)){l=a;z=y;break}l=y;z=m}else{n=a;f=y;l=m;break}case 1:u(a)?l=a:g?f=a:n=a;break;case 0:break;default:throw w("badargs",arguments.length);}var t=this instanceof e,p=t?f:b.isArray?[]:new e(f), 9 | A={},v=b.interceptor&&b.interceptor.response||F,C=b.interceptor&&b.interceptor.responseError||B;r(b,function(b,a){"params"!=a&&"isArray"!=a&&"interceptor"!=a&&(A[a]=H(b))});g&&(A.data=f);G.setUrlParams(A,s({},c(f,b.params||{}),n),b.url);n=q(A).then(function(a){var c=a.data,g=p.$promise;if(c){if(d.isArray(c)!==!!b.isArray)throw w("badcfg",k,b.isArray?"array":"object",d.isArray(c)?"array":"object");b.isArray?(p.length=0,r(c,function(a){"object"===typeof a?p.push(new e(a)):p.push(a)})):(D(c,p),p.$promise= 10 | g)}p.$resolved=!0;a.resource=p;return a},function(a){p.$resolved=!0;(z||E)(a);return h.reject(a)});n=n.then(function(a){var b=v(a);(l||E)(b,a.headers);return b},C);return t?n:(p.$promise=n,p.$resolved=!1,p)};e.prototype["$"+k]=function(a,b,c){u(a)&&(c=b,b=a,a={});a=e[k].call(this,a,this,b,c);return a.$promise||a}});e.bind=function(b){return v(x,s({},g,b),l)};return e}var E=d.noop,r=d.forEach,s=d.extend,H=d.copy,u=d.isFunction;t.prototype={setUrlParams:function(f,g,l){var m=this,c=l||m.template,h, 11 | e,q=m.urlParams={};r(c.split(/\W/),function(b){if("hasOwnProperty"===b)throw w("badname");!/^\d+$/.test(b)&&b&&(new RegExp("(^|[^\\\\]):"+b+"(\\W|$)")).test(c)&&(q[b]=!0)});c=c.replace(/\\:/g,":");g=g||{};r(m.urlParams,function(b,k){h=g.hasOwnProperty(k)?g[k]:m.defaults[k];d.isDefined(h)&&null!==h?(e=encodeURIComponent(h).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"%20").replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+"),c=c.replace(new RegExp(":"+ 12 | k+"(\\W|$)","g"),function(b,a){return e+a})):c=c.replace(new RegExp("(/?):"+k+"(\\W|$)","g"),function(b,a,c){return"/"==c.charAt(0)?c:a+c})});m.defaults.stripTrailingSlashes&&(c=c.replace(/\/+$/,"")||"/");c=c.replace(/\/\.(?=\w+($|\?))/,".");f.url=c.replace(/\/\\\./,"/.");r(g,function(b,c){m.urlParams[c]||(f.params=f.params||{},f.params[c]=b)})}};return v}]})})(window,window.angular); 13 | //# sourceMappingURL=angular-resource.min.js.map -------------------------------------------------------------------------------- /src/main/Caco/Config/CLI/Manage.php: -------------------------------------------------------------------------------- 1 | <?php 2 | namespace Caco\Config\CLI; 3 | 4 | use Caco\CLI\AbstractCLI; 5 | use Caco\Config\Model\Config; 6 | 7 | /** 8 | * Manage the configuration key value store. 9 | * 10 | * Class Manage 11 | * @package Caco\Config 12 | * @author Guido Krömer <mail 64 cacodaemon 46 de> 13 | */ 14 | class Manage extends AbstractCLI 15 | { 16 | protected $shortOptions = 'a:i:k:v:'; 17 | 18 | protected $longOptions = ['action:', 'id:', 'key:', 'value:']; 19 | 20 | /** 21 | * Runs the cli 22 | * 23 | * @return int 24 | */ 25 | public function run() 26 | { 27 | switch ($this->getArg('a', 'action', null, true)) { 28 | case 'create': 29 | $key = $this->getArg('k', 'key', null, true); 30 | $value = $this->getArg('v', 'value', null, true); 31 | 32 | return $this->createValue($key, $value); 33 | case 'update': 34 | $id = $this->getArg('i', 'id', null, true); 35 | $value = $this->getArg('v', 'value', null, true); 36 | 37 | return $this->updateValue($id, $value); 38 | case 'list': 39 | return $this->listValues(); 40 | case 'delete': 41 | $id = $this->getArg('i', 'id', null, true); 42 | 43 | return $this->deleteValue($id); 44 | default: 45 | $this->printLine('Invalid action (-a/--action) given, valid actions are create, update, delete or list!'); 46 | } 47 | } 48 | 49 | /** 50 | * @param string $key 51 | * @param string $value 52 | * @return int 53 | */ 54 | protected function createValue($key, $value) 55 | { 56 | $config = new Config; 57 | $config->key = $key; 58 | $config->value = $value; 59 | 60 | if ($config->save()) { 61 | $this->printLine("Key $key: $value has been created!"); 62 | 63 | return 0; 64 | } 65 | 66 | $this->printLine("Could not create the key $key: $value!"); 67 | 68 | return 255; 69 | } 70 | 71 | /** 72 | * @param int $id 73 | * @param string $value 74 | * @return int 75 | */ 76 | protected function updateValue($id, $value) 77 | { 78 | $config = new Config; 79 | $config->read($id); 80 | $config->value = $value; 81 | 82 | if ($config->save()) { 83 | $this->printLine("Key $config->key: $value has been updated!"); 84 | 85 | return 0; 86 | } 87 | 88 | $this->printLine("Could not update the key $config->key: $value!"); 89 | 90 | return 255; 91 | } 92 | 93 | /** 94 | * @return int 95 | */ 96 | protected function listValues() 97 | { 98 | $this->printLine("ID \t Key: Value"); 99 | foreach ((new Config())->readList() as $config) { /** @var Config $config */ 100 | $this->printLine("$config->id \t $config->key: $config->value"); 101 | } 102 | 103 | return 0; 104 | } 105 | 106 | /** 107 | * @param int $id 108 | * @return int 109 | */ 110 | protected function deleteValue($id) 111 | { 112 | $config = new Config; 113 | $config->read($id); 114 | 115 | if ($config->delete()) { 116 | $this->printLine("Key with the id: $id has been deleted!"); 117 | 118 | return 0; 119 | } 120 | 121 | $this->printLine("Could not delete the key with the id: $id!"); 122 | 123 | return 255; 124 | } 125 | } -------------------------------------------------------------------------------- /assets/scripts/paginator/module.js: -------------------------------------------------------------------------------- 1 | angular.module('caco.ClientPaginate', []) 2 | .filter('paginate', function(Paginator) { 3 | return function(input, rowsPerPage) { 4 | if (!input) { 5 | return input; 6 | } 7 | 8 | if (rowsPerPage) { 9 | Paginator.rowsPerPage = rowsPerPage; 10 | } 11 | 12 | Paginator.itemCount = input.length; 13 | 14 | return input.slice(parseInt(Paginator.page * Paginator.rowsPerPage), parseInt((Paginator.page + 1) * Paginator.rowsPerPage + 1) - 1); 15 | } 16 | }) 17 | .filter('forLoop', function() { 18 | return function(input, start, end) { 19 | input = new Array(end - start); 20 | for (var i = 0; start < end; start++, i++) { 21 | input[i] = start; 22 | } 23 | 24 | return input; 25 | } 26 | }) 27 | .service('Paginator', function ($rootScope) { 28 | this.page = 0; 29 | this.rowsPerPage = 50; 30 | this.itemCount = 0; 31 | this.limitPerPage = 10; 32 | 33 | this.setPage = function (page) { 34 | if (page > this.pageCount()) { 35 | return; 36 | } 37 | window.scrollTo(0, 0); 38 | 39 | this.page = page; 40 | }; 41 | 42 | this.nextPage = function () { 43 | if (this.isLastPage()) { 44 | return; 45 | } 46 | window.scrollTo(0, 0); 47 | 48 | this.page++; 49 | }; 50 | 51 | this.perviousPage = function () { 52 | if (this.isFirstPage()) { 53 | return; 54 | } 55 | window.scrollTo(0, 0); 56 | 57 | this.page--; 58 | }; 59 | 60 | this.firstPage = function () { 61 | this.page = 0; 62 | }; 63 | 64 | this.lastPage = function () { 65 | this.page = this.pageCount() - 1; 66 | }; 67 | 68 | this.isFirstPage = function () { 69 | return this.page == 0; 70 | }; 71 | 72 | this.isLastPage = function () { 73 | return this.page == this.pageCount() - 1; 74 | }; 75 | 76 | this.pageCount = function () { 77 | var count = Math.ceil(parseInt(this.itemCount, 10) / parseInt(this.rowsPerPage, 10)); 78 | if (count === 1) { 79 | this.page = 0; 80 | } 81 | 82 | return count; 83 | }; 84 | 85 | this.lowerLimit = function() { 86 | var pageCountLimitPerPageDiff = this.pageCount() - this.limitPerPage; 87 | 88 | if (pageCountLimitPerPageDiff < 0) { 89 | return 0; 90 | } 91 | 92 | if (this.page > pageCountLimitPerPageDiff + 1) { 93 | return pageCountLimitPerPageDiff; 94 | } 95 | 96 | var low = this.page - (Math.ceil(this.limitPerPage/2) - 1); 97 | 98 | return Math.max(low, 0); 99 | }; 100 | 101 | this.show = function () { 102 | return this.pageCount() > 1; 103 | }; 104 | 105 | this.reset = function () { 106 | this.page = 0; 107 | }; 108 | }) 109 | .directive('paginator', function factory() { 110 | return { 111 | restrict: 'E', 112 | controller: function ($scope, Paginator) { 113 | $scope.paginator = Paginator; 114 | }, 115 | templateUrl: 'views/paginator/directive.html' 116 | }; 117 | }); -------------------------------------------------------------------------------- /src/main/Caco/Feed/Model/Feed.php: -------------------------------------------------------------------------------- 1 | <?php 2 | namespace Caco\Feed\Model; 3 | 4 | use \Caco\MiniAR; 5 | use \Caco\MiniARException; 6 | 7 | /** 8 | * Class Feed 9 | * @package Caco\Feed 10 | * @author Guido Krömer <mail 64 cacodaemon 46 de> 11 | */ 12 | class Feed extends MiniAR 13 | { 14 | /** 15 | * @var string 16 | */ 17 | public $title; 18 | 19 | /** 20 | * @var string 21 | */ 22 | public $url; 23 | 24 | /** 25 | * @var int 26 | */ 27 | public $updated = 1; 28 | 29 | /** 30 | * @var int 31 | */ 32 | public $interval = 3600; 33 | 34 | /** 35 | * @var int 36 | */ 37 | public $total = 0; 38 | 39 | /** 40 | * @var int 41 | */ 42 | public $unread = 0; 43 | 44 | /** 45 | * @var int 46 | */ 47 | public $outdated = 0; 48 | 49 | /** 50 | * Read an active record from the database by it's id. 51 | * 52 | * @param int $id 53 | * @param string $select 54 | * @return bool 55 | */ 56 | public function read($id, $select = null) 57 | { 58 | $query = sprintf('SELECT 59 | f.`id`, 60 | f.`title`, 61 | f.`url`, 62 | f.`updated`, 63 | f.`interval`, 64 | COUNT(f.`id`) AS `total`, 65 | SUM(CASE WHEN i.`id` IS NULL OR i.`read` = 1 THEN 0 ELSE 1 END) AS `unread`, 66 | CASE WHEN `updated` < (? - `interval`) THEN 1 ELSE 0 END AS `outdated` 67 | FROM `%s` f 68 | LEFT JOIN `%s` i ON (i.`id_feed` = f.`id`) 69 | WHERE f.`id` = ? 70 | LIMIT 1;', $this->getTableName(), (new Item)->getTableName()); 71 | 72 | return $this->readOne($query, [time(), $id]); 73 | } 74 | 75 | /** 76 | * Returns all feeds. 77 | * 78 | * @return Feed[] 79 | */ 80 | public function all() 81 | { 82 | $query = sprintf('SELECT 83 | f.`id`, 84 | f.`title`, 85 | f.`url`, 86 | f.`updated`, 87 | f.`interval`, 88 | COUNT(f.id) AS `total`, 89 | SUM(CASE WHEN i.`id` IS NULL OR i.`read` = 1 THEN 0 ELSE 1 END) AS `unread`, 90 | CASE WHEN `updated` < (? - `interval`) THEN 1 ELSE 0 END AS `outdated` 91 | FROM `%s` f 92 | LEFT JOIN `item` i ON (i.`id_feed` = f.`id`) 93 | GROUP BY f.`id` 94 | ORDER BY `unread` DESC;', $this->getTableName()); 95 | 96 | return $this->readArray($query, [time()]); 97 | } 98 | 99 | /** 100 | * Gets a assoc array containing the default values of the data fields. 101 | * 102 | * @return array 103 | */ 104 | protected function getDefaultFields() 105 | { 106 | $fields = parent::getDefaultFields(); 107 | unset($fields['unread']); 108 | unset($fields['total']); 109 | unset($fields['outdated']); 110 | 111 | return $fields; 112 | } 113 | 114 | /** 115 | * Gets a list of all data fields. 116 | * 117 | * @return array 118 | */ 119 | protected function getFields() 120 | { 121 | $fields = parent::getFields(); 122 | unset($fields['unread']); 123 | unset($fields['total']); 124 | unset($fields['outdated']); 125 | 126 | return $fields; 127 | } 128 | } -------------------------------------------------------------------------------- /src/main/Caco/Exports/Exporter/Atom.php: -------------------------------------------------------------------------------- 1 | <?php 2 | namespace Caco\Exports\Exporter; 3 | 4 | use Caco\Feed\Model\Feed; 5 | use Caco\Feed\Model\Item; 6 | use XMLWriter; 7 | 8 | /** 9 | * Class Atom 10 | * 11 | * Exports the given feed items to an Atom 1.0 xml string. 12 | * 13 | * @package Caco\Exports\Exporter 14 | * @author Guido Krömer <mail 64 cacodaemon 46 de> 15 | */ 16 | class Atom implements IXmlExporter 17 | { 18 | /** 19 | * @var Feed 20 | */ 21 | protected $feed; 22 | 23 | /** 24 | * @var Item[] 25 | */ 26 | protected $items; 27 | 28 | /** 29 | * @param Feed $feed 30 | * @param Item[] $items 31 | */ 32 | public function __construct(Feed $feed = null, array $items = null) 33 | { 34 | $this->feed = $feed; 35 | $this->items = $items; 36 | } 37 | 38 | /** 39 | * @param \Caco\Feed\Model\Feed $feed 40 | */ 41 | public function setFeed($feed) 42 | { 43 | $this->feed = $feed; 44 | } 45 | 46 | /** 47 | * @return \Caco\Feed\Model\Feed 48 | */ 49 | public function getFeed() 50 | { 51 | return $this->feed; 52 | } 53 | 54 | /** 55 | * @param \Caco\Feed\Model\Item[] $items 56 | */ 57 | public function setItems(array $items) 58 | { 59 | $this->items = $items; 60 | } 61 | 62 | /** 63 | * @return \Caco\Feed\Model\Item[] 64 | */ 65 | public function getItems() 66 | { 67 | return $this->items; 68 | } 69 | 70 | /** 71 | * Builds and returns the atom feed xml. 72 | * 73 | * @return string 74 | */ 75 | public function buildXML() 76 | { 77 | $w = new XMLWriter; 78 | $w->openMemory(); 79 | 80 | $w->startDocument('1.0', 'utf-8'); 81 | 82 | $w->startElement('feed'); 83 | $w->writeAttribute('xmlns', 'http://www.w3.org/2005/Atom'); 84 | 85 | $w->writeElement('title', $this->feed->title); 86 | 87 | $w->startElement('link'); 88 | $w->writeAttribute('href', $this->feed->url); 89 | $w->endElement(); //link 90 | 91 | $w->writeElement('updated', date('Y-m-d\TH:i:s\Z', $this->feed->updated)); 92 | 93 | foreach ($this->items as $item) { 94 | $this->writeEntry($w, $item); 95 | } 96 | 97 | $w->endElement(); //feed 98 | $w->endDocument(); 99 | 100 | return $w->outputMemory(true); 101 | } 102 | 103 | /** 104 | * Adds an atom feed entry to the given xml. 105 | * 106 | * @param XMLWriter $w 107 | * @param Item $item 108 | */ 109 | protected function writeEntry(XMLWriter $w, Item $item) 110 | { 111 | $w->startElement('entry'); 112 | 113 | $w->writeElement('title', $item->title); 114 | 115 | $w->writeElement('published', date('Y-m-d\TH:i:s\Z', $item->date)); 116 | 117 | $w->startElement('link'); 118 | $w->writeAttribute('href', $item->url); 119 | $w->endElement(); //link 120 | 121 | $w->writeElement('id', $item->uuid); 122 | 123 | $w->startElement('content'); 124 | $w->writeAttribute('type', 'html'); 125 | 126 | $w->writeRaw(htmlentities($item->content)); 127 | 128 | $w->endElement(); //content 129 | 130 | $w->endElement(); //entry 131 | } 132 | 133 | /** 134 | * Determines if the output should be downloadable in a browser. 135 | * 136 | * @return bool 137 | */ 138 | public function isFile() 139 | { 140 | return false; 141 | } 142 | 143 | /** 144 | * Gets the desired filename for downloading via HTTP. 145 | * 146 | * @return string 147 | */ 148 | public function getFileName() 149 | { 150 | return ''; 151 | } 152 | } -------------------------------------------------------------------------------- /assets/views/mail/send.html: -------------------------------------------------------------------------------- 1 | <div class="container"> 2 | <div class="row"> </div> 3 | <div class="row"> 4 | <form ng-submit="send()" 5 | class="form-horizontal"> 6 | <fieldset> 7 | <legend>Send a mail</legend> 8 | 9 | <div class="form-group row"> 10 | <label for="mail-from" 11 | class="col-lg-1 control-label">From</label> 12 | <div class="col-lg-5 input-group"> 13 | <select id="mail-from" 14 | class="form-control" 15 | required="required" 16 | ng-model="mail.fromId" 17 | ng-options="account.id as account.name for account in accounts"> 18 | <option value="">Please select</option> 19 | 20 | </select> 21 | </div> 22 | 23 | <label for="mail-to" 24 | class="col-lg-1 control-label">To</label> 25 | <div class="col-lg-5 input-group"> 26 | <input type="text" 27 | class="form-control" 28 | id="mail-to" 29 | ng-model="mail.to"/> 30 | </div> 31 | </div> 32 | 33 | <div class="form-group row"> 34 | <label for="mail-cc" 35 | class="col-lg-1 control-label">CC</label> 36 | <div class="col-lg-5 input-group"> 37 | <input type="text" 38 | class="form-control" 39 | id="mail-cc" 40 | ng-model="mail.cc"/> 41 | </div> 42 | 43 | <label for="mail-bcc" 44 | class="col-lg-1 control-label">BCC</label> 45 | <div class="col-lg-5 input-group"> 46 | <input type="text" 47 | class="form-control" 48 | id="mail-bcc" 49 | ng-model="mail.bcc"/> 50 | </div> 51 | </div> 52 | 53 | <div class="form-group row"> 54 | <label for="mail-subject" 55 | class="col-lg-1 control-label">Subject</label> 56 | <div class="col-lg-11 input-group"> 57 | <input type="text" 58 | class="form-control" 59 | id="mail-subject" 60 | required="required" 61 | ng-model="mail.subject"/> 62 | </div> 63 | </div> 64 | 65 | <div class="form-group row"> 66 | <label for="mail-body" 67 | class="col-lg-1 control-label">Text</label> 68 | <div class="col-lg-11 input-group"> 69 | <textarea id="mail-body" 70 | class="form-control" 71 | rows="5" 72 | required="required" 73 | ng-model="mail.body"></textarea> 74 | </div> 75 | </div> 76 | 77 | <div class="form-group"> 78 | <div class="col-lg-offset-1 col-lg-11"> 79 | <button type="submit" 80 | class="btn btn-success"> 81 | <span class="fa fa-envelope"></span> Send 82 | </button> 83 | </div> 84 | </div> 85 | </fieldset> 86 | </form> 87 | </div> 88 | </div> -------------------------------------------------------------------------------- /src/main/Caco/CLI/AbstractCLI.php: -------------------------------------------------------------------------------- 1 | <?php 2 | namespace Caco\CLI; 3 | 4 | /** 5 | * Class AbstractCLI 6 | * @package Caco\CLI 7 | * @author Guido Krömer <mail 64 cacodaemon 46 de> 8 | */ 9 | abstract class AbstractCLI implements ICLI 10 | { 11 | /** 12 | * By getopt() parsed options. 13 | * 14 | * @var array 15 | */ 16 | protected $options; 17 | 18 | /** 19 | * getopt() compatible $options. 20 | * 21 | * @var string 22 | */ 23 | protected $shortOptions = ''; 24 | 25 | /** 26 | * getopt() compatible $longopts. 27 | * 28 | * @var array 29 | */ 30 | protected $longOptions = []; 31 | 32 | /** 33 | * Sets the options array. 34 | * 35 | * @param array $args 36 | */ 37 | public function init(array $options = null) 38 | { 39 | if (is_null($options)) { 40 | $this->setOptions(getopt($this->shortOptions, $this->longOptions)); 41 | } else { 42 | $this->setOptions($options); 43 | } 44 | } 45 | 46 | /** 47 | * Runs the cli 48 | * 49 | * @return int 50 | */ 51 | public abstract function run(); 52 | 53 | /** 54 | * @param array $options 55 | */ 56 | protected function setOptions($options) 57 | { 58 | $this->options = $options; 59 | } 60 | 61 | /** 62 | * Prints a message. 63 | * 64 | * @param string $message 65 | * @param string $format 66 | */ 67 | protected function printLine($message, $format = 'Y-m-d H:i:s') 68 | { 69 | printf('[%s] %s %s', date($format), $message, PHP_EOL); 70 | } 71 | 72 | /** 73 | * Gets the argument value or returns the default value. 74 | * If $throwError is true a InvalidArgumentException will be thrown instead of returning the default value . 75 | * 76 | * @param string $short 77 | * @param string $long 78 | * @param mixed null $default 79 | * @param bool $throwError 80 | * @throw \InvalidArgumentException 81 | */ 82 | protected function getArg($short, $long, $default = null, $throwError = false) 83 | { 84 | if (!$this->checkShortOptions($short)) { 85 | throw new \InvalidArgumentException("Given short option ($short) is not supported by the CLI!"); 86 | } 87 | 88 | if (!$this->checkLongOptions($long)) { 89 | throw new \InvalidArgumentException("Given long option ($long) is not supported by the CLI!"); 90 | } 91 | 92 | if (isset($this->options[$short])) { 93 | return $this->options[$short]; 94 | } 95 | 96 | if (isset($this->options[$long])) { 97 | return $this->options[$long]; 98 | } 99 | 100 | if (!$throwError) { 101 | return $default; 102 | } 103 | 104 | throw new \InvalidArgumentException("Argument -$short/--$long is not set!"); 105 | } 106 | 107 | /** 108 | * Checks if the given short option is supported by the CLI. 109 | * Returns false if the given key is not supported. 110 | * 111 | * @param string $short 112 | * @return bool 113 | */ 114 | private final function checkShortOptions($short) 115 | { 116 | return strpos($this->shortOptions, $short) !== false; 117 | } 118 | 119 | /** 120 | * Checks if the given long option is supported by the CLI. 121 | * Returns false if the given key is not supported. 122 | * 123 | * @param string $long 124 | * @return bool 125 | */ 126 | private final function checkLongOptions($long) 127 | { 128 | foreach ($this->longOptions as $longOption) { 129 | if (strpos($longOption, $long) !== false) { 130 | return true; 131 | } 132 | } 133 | 134 | return false; 135 | } 136 | } -------------------------------------------------------------------------------- /src/main/Caco/Mail/AccountMcrypt.php: -------------------------------------------------------------------------------- 1 | <?php 2 | namespace Caco\Mail; 3 | 4 | use Caco\Mcrypt; 5 | use Caco\Mail\Model\MailAccount; 6 | 7 | /** 8 | * Class AccountMcrypt 9 | * @package Caco\Mail 10 | * @author Guido Krömer <mail 64 cacodaemon 46 de> 11 | */ 12 | class AccountMcrypt 13 | { 14 | /** 15 | * @var \Caco\Mcrypt 16 | */ 17 | protected $crypto; 18 | 19 | public function __construct(Mcrypt $crypto = null) 20 | { 21 | if (is_null($crypto)) { 22 | $this->crypto = new Mcrypt; 23 | } else { 24 | $this->crypto = $crypto; 25 | } 26 | } 27 | 28 | /** 29 | * Get the account specified by its id and matching the given key. 30 | * 31 | * @param string $key 32 | * @param int $id 33 | * @return bool|Account 34 | */ 35 | public function one($key, $id) 36 | { 37 | $mailAccount = new MailAccount; 38 | 39 | if (!$mailAccount->read($id)) { 40 | return false; 41 | } 42 | 43 | $decrypted = $this->crypto->decrypt($mailAccount->getContainer(), $key); 44 | if ($decrypted === false) { 45 | return false; 46 | } 47 | 48 | /** @var Account $account */ 49 | $account = unserialize($decrypted); 50 | $account->setId($id); 51 | 52 | return $account; 53 | } 54 | 55 | /** 56 | * Get all mail account matching the key. 57 | * 58 | * @param string $key 59 | * @return Account[] 60 | */ 61 | public function all($key) 62 | { 63 | /** @var MailAccount[] $containerList */ 64 | $accountList = (new MailAccount)->readList(); 65 | 66 | /** @var Account[] $decryptedAccounts */ 67 | $decryptedAccounts = []; 68 | foreach ($accountList as $account) { 69 | $data = $this->crypto->decrypt($account->getContainer(), $key); 70 | 71 | if ($data === false) { 72 | continue; 73 | } 74 | 75 | /** @var Account $tempAccount */ 76 | $tempAccount = unserialize($data); 77 | $tempAccount->setId($account->id); 78 | $decryptedAccounts[] = $tempAccount; 79 | } 80 | 81 | return $decryptedAccounts; 82 | } 83 | 84 | /** 85 | * Creates a new mail account, returns the id on success or false if a error occurred. 86 | * 87 | * @param string $key 88 | * @param Account $account 89 | * @return int|bool 90 | */ 91 | public function add($key, Account $account) 92 | { 93 | $mailAccount = new MailAccount; 94 | $mailAccount->setContainer($this->crypto->encrypt(serialize($account), $key)); 95 | 96 | return $mailAccount->save() ? $mailAccount->id : false; 97 | } 98 | 99 | /** 100 | * Edit the mail account specified by its id and matches the given key. 101 | * 102 | * @param string $key 103 | * @param int $id 104 | * @param Account $account 105 | * @return bool 106 | */ 107 | public function edit($key, $id, Account $account) 108 | { 109 | if (!$this->one($key, $id)) { 110 | return false; 111 | } 112 | 113 | $mailAccount = new MailAccount; 114 | $mailAccount->read($id); 115 | $mailAccount->setContainer($this->crypto->encrypt(serialize($account), $key)); 116 | 117 | return $mailAccount->save(); 118 | } 119 | 120 | /** 121 | * Deletes the mail account specified by its id and matches the given key. 122 | * 123 | * @param string $key 124 | * @param int $id 125 | * @return bool 126 | */ 127 | public function delete($key, $id) 128 | { 129 | if (!$this->one($key, $id)) { 130 | return false; 131 | } 132 | 133 | $mailAccount = new MailAccount; 134 | $mailAccount->read($id); 135 | 136 | return $mailAccount->delete(); 137 | } 138 | } -------------------------------------------------------------------------------- /src/main/Caco/Slim/Auth/UserManagement.php: -------------------------------------------------------------------------------- 1 | <?php 2 | namespace Caco\Slim\Auth; 3 | 4 | use Caco\CLI\AbstractCLI; 5 | use Caco\Slim\Auth\Model\User; 6 | 7 | /** 8 | * Create a new user cli. 9 | * 10 | * @package Caco 11 | */ 12 | class UserManagement extends AbstractCLI 13 | { 14 | protected $shortOptions = 'a:u:p:i:'; 15 | 16 | protected $longOptions = ['action:', 'user:', 'password:', 'id:']; 17 | 18 | /** 19 | * Runs the cli 20 | * 21 | * @return int 22 | */ 23 | public function run() 24 | { 25 | switch ($this->getArg('a', 'action', null, true)) { 26 | case 'create': 27 | $name = $this->getArg('u', 'user', null, true); 28 | $password = $this->getArg('p', 'password', null, true); 29 | 30 | return $this->createUser($name, $password); 31 | case 'list': 32 | return $this->listUsers(); 33 | case 'delete': 34 | $id = $this->getArg('i', 'id', null); 35 | $name = $this->getArg('u', 'user', null); 36 | 37 | if (!is_null($id)) { 38 | return $this->deleteUser($id); 39 | } else if (!is_null($name)) { 40 | return $this->deleteUserByName($name); 41 | } else { 42 | throw new \InvalidArgumentException('Either an id (-i/--id) or a name (-u/--user) has to be specified!'); 43 | } 44 | 45 | default: 46 | $this->printLine('Invalid action (-a/--action) given, valid actions are create, delete or list!'); 47 | } 48 | } 49 | 50 | /** 51 | * Lists users. 52 | * 53 | * @return int 54 | */ 55 | protected function listUsers() 56 | { 57 | $this->printLine("ID \t Name"); 58 | foreach ((new User)->readList() as $user) { 59 | $this->printLine("$user->id \t $user"); 60 | } 61 | 62 | return 0; 63 | } 64 | 65 | /** 66 | * Deletes the given user specified by its name 67 | * 68 | * @param string $name 69 | * @return int 70 | */ 71 | protected function deleteUserByName($name) 72 | { 73 | /** @var User[] $users */ 74 | $users = (new User)->readList('`userName` = ?', [$name]); 75 | 76 | if (empty($users)) { 77 | $this->printLine("No user with the given name: $name found!"); 78 | 79 | return 255; 80 | } 81 | 82 | return $this->deleteUser($users[0]->id); 83 | } 84 | 85 | /** 86 | * Deletes the given user specified by its id. 87 | * 88 | * @param int $id 89 | * @return int 90 | */ 91 | protected function deleteUser($id) 92 | { 93 | /** @var User[] $users */ 94 | $users = (new User)->readList('`id` = ?', [$id]); 95 | 96 | if (empty($users)) { 97 | $this->printLine("No user with the given ID: $id found!"); 98 | 99 | return 255; 100 | } 101 | 102 | $userName = $users[0]->userName; 103 | if ($users[0]->delete()) { 104 | $this->printLine("User $userName has been deleted!"); 105 | 106 | return 0; 107 | } 108 | 109 | $this->printLine("Could not delete user $userName!"); 110 | 111 | return 255; 112 | } 113 | 114 | /** 115 | * Creates a new user. 116 | * 117 | * @param string $name 118 | * @param string $password 119 | * @return int 120 | */ 121 | protected function createUser($name, $password) 122 | { 123 | $user = new User; 124 | $user->userName = $name; 125 | $user->setPassword($password); 126 | 127 | if ($user->save()) { 128 | $this->printLine("User $user has been created!"); 129 | 130 | return 0; 131 | } 132 | 133 | $this->printLine("A error occurred, could not create $user!"); 134 | 135 | return 255; 136 | } 137 | } -------------------------------------------------------------------------------- /src/main/Caco/Password/REST.php: -------------------------------------------------------------------------------- 1 | <?php 2 | namespace Caco\Password; 3 | 4 | use \Slim\Slim; 5 | use \Caco\Mcrypt; 6 | use \Caco\Password\Model\Container; 7 | 8 | /** 9 | * Class REST 10 | * @package Caco\Password 11 | * @author Guido Krömer <mail 64 cacodaemon 46 de> 12 | */ 13 | class REST 14 | { 15 | /** 16 | * @var \Slim\Slim 17 | */ 18 | protected $app; 19 | 20 | /** 21 | * @var \Caco\Mcrypt 22 | */ 23 | protected $crypto; 24 | 25 | public function __construct() 26 | { 27 | $this->crypto = new Mcrypt; 28 | $this->group = 'password'; 29 | $this->app = Slim::getInstance(); 30 | } 31 | 32 | /** 33 | * GET /password/:key/:id 34 | * 35 | * @param string $key 36 | * @param int $id 37 | */ 38 | public function one($key, $id) 39 | { 40 | $container = new Container; 41 | if ($container->read($id)) { 42 | $response = json_decode($this->crypto->decrypt($container->getContainer(), $key)); 43 | $response->id = $id; 44 | $this->app->render(200, ['response' => $response]); 45 | } else { 46 | $this->app->render(404); 47 | } 48 | } 49 | 50 | /** 51 | * GET /password/:key 52 | * 53 | * @param string $key 54 | */ 55 | public function all($key) 56 | { 57 | /** @var Container[] $containerList */ 58 | $containerList = (new Container)->readList(); 59 | $response = []; 60 | foreach ($containerList as $container) { 61 | $data = $this->crypto->decrypt($container->getContainer(), $key); 62 | 63 | if ($data === false) { 64 | continue; 65 | } 66 | 67 | $data = json_decode($data); 68 | $data->id = $container->id; 69 | $response[] = $data; 70 | } 71 | 72 | $this->app->render(200, ['response' => $response]); 73 | } 74 | 75 | /** 76 | * POST /password/:key 77 | * 78 | * @param string $key 79 | */ 80 | public function add($key) 81 | { 82 | $rawData = $this->app->request()->getBody(); 83 | 84 | if (!$this->isValidJson($rawData)) { 85 | $this->app->render(500, ['error' => 'Invalid JSON encoded data.']); 86 | 87 | return; 88 | } 89 | 90 | $container = new Container; 91 | $container->setContainer($this->crypto->encrypt($rawData, $key)); 92 | $this->app->render($container->save() ? 201 : 500, ['response' => $container->id]); 93 | } 94 | 95 | /** 96 | * PUT /password/:key/:id 97 | * 98 | * @param string $key 99 | * @param int $id 100 | */ 101 | public function edit($key, $id) 102 | { 103 | $container = new Container; 104 | if ($container->read($id)) { 105 | $rawData = $this->app->request()->getBody(); 106 | $container->setContainer($this->crypto->encrypt($rawData, $key)); 107 | $this->app->render($container->save() ? 200 : 500, ['response' => $id]); 108 | } else { 109 | $this->app->render(404); 110 | } 111 | } 112 | 113 | /** 114 | * DELETE /password/:key/:id 115 | * 116 | * @param string $key 117 | * @param int $id 118 | */ 119 | public function delete($key, $id) 120 | { 121 | $container = new Container; 122 | if ($container->read($id) && $this->crypto->decrypt($container->getContainer(), $key) !== false) { 123 | $this->app->render($container->delete() ? 200 : 500, ['response' => $id]); 124 | } else { 125 | $this->app->render(404); 126 | } 127 | } 128 | 129 | /** 130 | * Checks if the given string is proper json encoded. 131 | * 132 | * @param string $jsonEncoded 133 | * @return bool 134 | */ 135 | protected function isValidJson($jsonEncoded) { 136 | json_decode($jsonEncoded); 137 | 138 | return json_last_error() === 0; 139 | } 140 | } --------------------------------------------------------------------------------