├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── composer.json ├── composer.lock ├── config.example.php ├── docker-utils ├── configure_smtp.sh ├── install_composer.sh └── test_mail.php ├── index.php └── lib ├── ConvertToMD.php ├── DeckClass.php └── MailClass.php /.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | ** 3 | 4 | # Allow files and directories 5 | !lib/ 6 | !docker-utils/ 7 | !composer* 8 | !config* 9 | !index.php 10 | 11 | # Ignore unnecessary files inside allowed directories 12 | # This should go after the allowed directories 13 | docker-utils/test_mail.php 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | config.php 2 | vendor/ 3 | .idea/ 4 | attachments/ 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:alpine3.15 AS builder 2 | 3 | # Install all dependencies required for API calls and mail handling 4 | RUN apk add --no-cache git php-curl php-mbstring php-imap php-xml ssmtp gawk 5 | 6 | # Install PHP ext-imap 7 | RUN set -eux; \ 8 | persistentDeps=" \ 9 | c-client \ 10 | "; \ 11 | buildDeps=" \ 12 | imap-dev \ 13 | krb5-dev \ 14 | openssl-dev \ 15 | "; \ 16 | apk add --no-cache --virtual .imap-persistent-deps ${persistentDeps}; \ 17 | apk add --no-cache --virtual .imap-build-deps ${buildDeps}; \ 18 | \ 19 | docker-php-ext-configure imap \ 20 | --with-imap-ssl \ 21 | --with-kerberos \ 22 | ; \ 23 | docker-php-ext-install -j$(nproc) imap; \ 24 | \ 25 | apk del --no-cache --no-network .imap-build-deps 26 | 27 | # Create a non-root user to own the files and run our server 28 | RUN adduser -D -g "Mail2deck" deckbot 29 | WORKDIR /home/deckbot/mail2deck 30 | 31 | # Copy scripts 32 | # Use the .dockerignore file to control what ends up inside the image! 33 | COPY . . 34 | 35 | # Install dependencies 36 | RUN docker-utils/install_composer.sh && \ 37 | ./composer.phar update && \ 38 | rm docker-utils/install_composer.sh composer.phar 39 | 40 | # Setup SMTP Server 41 | RUN docker-utils/configure_smtp.sh && \ 42 | rm docker-utils/configure_smtp.sh 43 | 44 | # Use our non-root user 45 | USER deckbot 46 | 47 | # Run script once 48 | CMD ["php", "index.php"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mail2deck 2 | Provides an "email in" solution for the Nextcloud Deck app 3 | # 🚀 A. For users 4 | Follow the above steps to add a new card from email. 5 | 6 | * Deck Bot is the user who will create the cards and it will be set up by your nextcloud admin. 7 | * In this tutorial email address for Deck Bot will be: bot@ncserver.com 8 | 9 | ## 1) Assign Deck Bot to the board. 10 | Deck Bot must be assigned and must have edit permission inside the board. 11 | 12 | ## 2) Mail subject & content 13 | Let's assume you want to add a card with title "Update website logo" on board "Website" and stack "To do". 14 | You can do this in two ways. 15 | 16 | ### 2.1: Set stack and board in the email subject 17 | Here's what the email subject should look like: 18 | Update website logo b-'website' s-'to do' 19 | 20 | * *You can use single or double quotes.* 21 | 22 | * *Case-insensitive for board and stack respectively.* 23 | 24 | ### 2.2: Set the board in the email address 25 | At the end of the email address prefix (before @) add "+website" 26 | 27 | Example: bot+website@ncserver.com 28 | 29 | * *If board has multiple words e.g. "some project", you'll have to send the email to bot+some+project@ncserver.com* 30 | 31 | In this case, if you don't specify the stack in the email subject, the card will be added in the first stack (if it exists). 32 | 33 | Note: 34 | * Email content will be card description 35 | * You can add attachments in the email and those will be integrated in the created card 36 | 37 | 38 | ### 2.3: Specify assignee 39 | 40 | Here's what the email subject should look like: 41 | 42 | `Update website logo b-'website' s-'to do' u-'bob'` 43 | 44 | * *You can use single or double quotes.* 45 | * *Case-insensitive for board, stack and user respectively.* 46 | 47 | ### 2.4: Specify due date 48 | You can use the optional parameter `d-` to add a due date to a card. 49 | Here's what the email subject should look like if you want to set a due date to the card: 50 | 51 | `Update website logo b-'website' s-'to do' u-'bob' d-'2022-08-22T19:29:30+00:00'` 52 | 53 | * *You can use single or double quotes.* 54 | 55 | # ⚙️ B. For NextCloud admins to setup 56 | ## Requirements 57 | This app requires php-curl, php-mbstring ,php-imap and some sort of imap server (e.g. Postfix with Courier). 58 | ## NC new user 59 | Create a new user from User Management on your NC server, which shall to function as a bot to post cards received as mail. We chose to call it *deckbot*, but you can call it whatever you want.
60 | __Note__: that you have to give *deckbot* permissions on each board you want to add new cards from email. 61 | ## Configure Email 62 | ### Option 1 - Set up Postfix for incoming email 63 | You can setup Posfix mail server folowing the instructions on [Posfix setup](https://docs.gitlab.com/ee/administration/reply_by_email_postfix_setup.html), and after that add "+" delimiter (which separates the user from the board name in the email address) using the command:
64 | ``` 65 | sudo postconf -e "recipient_delimiter = +" 66 | ``` 67 | ### Option 2 - Use an existing email server 68 | This could be any hosted email service. The only requirement is that you can connect to it via the IMAP protocol. 69 | *Please note this option may not be as flexible as a self-hosted server. For example your email service may not support the "+"delimiter for directing messages to a specific board.* 70 | ## Download and install 71 | ### Bare-metal installation 72 | If using a self-hosted Postfix server, clone this repository into the home directory of the *incoming* user. If not self-hosting, you may need to create a new user on your system and adjust the commands in future steps to match that username.
73 | ``` 74 | su - incoming 75 | git clone https://github.com/newroco/mail2deck.git mail2deck 76 | ``` 77 | Create config.php file and edit it for your needs: 78 | ``` 79 | cd /home/incoming/mail2deck 80 | cp config.example.php config.php 81 | sudo vim config.php 82 | ``` 83 | *You can refer to https://www.php.net/manual/en/function.imap-open.php for setting the value of MAIL_SERVER_FLAGS* 84 | #### Add a cronjob to run mail2deck. 85 | ``` 86 | sudo crontab -u incoming -e 87 | ``` 88 | Add the following line in the opened file (in this example, it runs every 5 minutes): 89 | */5 * * * * /usr/bin/php /home/incoming/mail2deck/index.php >/dev/null 2>&1 90 | 91 | ### Docker installation 92 | Clone and edit the config.example.php you find in this repository and move it as config.php 93 | ``` 94 | git clone https://github.com/newroco/mail2deck.git mail2deck 95 | cd mail2deck 96 | cp config.example.php config.php 97 | nano config.php 98 | ``` 99 | 100 | Build your image locally 101 | ``` 102 | docker build -t mail2deck:latest . 103 | ``` 104 | 105 | Run the docker image mapping the config.json as volume 106 | ``` 107 | docker run -d --name mail2deck mail2deck:latest 108 | ``` 109 | 110 | Edit your crontab 111 | ``` 112 | crontab -e 113 | ``` 114 | 115 | And add this line 116 | ``` 117 | */5 * * * * /usr/bin/docker start mail2deck 118 | ``` 119 | 120 | ## Finish 121 | Now __mail2deck__ will add new cards every five minutes if new emails are received. 122 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "league/html-to-markdown": "^5.1" 4 | }, 5 | "autoload": { 6 | "psr-4": { 7 | "Mail2Deck\\": "lib/" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "55d22588d74c4cd2af8b00fa004ab06f", 8 | "packages": [ 9 | { 10 | "name": "league/html-to-markdown", 11 | "version": "5.1.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/thephpleague/html-to-markdown.git", 15 | "reference": "e0fc8cf07bdabbcd3765341ecb50c34c271d64e1" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/e0fc8cf07bdabbcd3765341ecb50c34c271d64e1", 20 | "reference": "e0fc8cf07bdabbcd3765341ecb50c34c271d64e1", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "ext-dom": "*", 25 | "ext-xml": "*", 26 | "php": "^7.2.5 || ^8.0" 27 | }, 28 | "require-dev": { 29 | "mikehaertl/php-shellcommand": "^1.1.0", 30 | "phpstan/phpstan": "^0.12.99", 31 | "phpunit/phpunit": "^8.5 || ^9.2", 32 | "scrutinizer/ocular": "^1.6", 33 | "unleashedtech/php-coding-standard": "^2.7", 34 | "vimeo/psalm": "^4.22" 35 | }, 36 | "bin": [ 37 | "bin/html-to-markdown" 38 | ], 39 | "type": "library", 40 | "extra": { 41 | "branch-alias": { 42 | "dev-master": "5.2-dev" 43 | } 44 | }, 45 | "autoload": { 46 | "psr-4": { 47 | "League\\HTMLToMarkdown\\": "src/" 48 | } 49 | }, 50 | "notification-url": "https://packagist.org/downloads/", 51 | "license": [ 52 | "MIT" 53 | ], 54 | "authors": [ 55 | { 56 | "name": "Colin O'Dell", 57 | "email": "colinodell@gmail.com", 58 | "homepage": "https://www.colinodell.com", 59 | "role": "Lead Developer" 60 | }, 61 | { 62 | "name": "Nick Cernis", 63 | "email": "nick@cern.is", 64 | "homepage": "http://modernnerd.net", 65 | "role": "Original Author" 66 | } 67 | ], 68 | "description": "An HTML-to-markdown conversion helper for PHP", 69 | "homepage": "https://github.com/thephpleague/html-to-markdown", 70 | "keywords": [ 71 | "html", 72 | "markdown" 73 | ], 74 | "support": { 75 | "issues": "https://github.com/thephpleague/html-to-markdown/issues", 76 | "source": "https://github.com/thephpleague/html-to-markdown/tree/5.1.0" 77 | }, 78 | "funding": [ 79 | { 80 | "url": "https://www.colinodell.com/sponsor", 81 | "type": "custom" 82 | }, 83 | { 84 | "url": "https://www.paypal.me/colinpodell/10.00", 85 | "type": "custom" 86 | }, 87 | { 88 | "url": "https://github.com/colinodell", 89 | "type": "github" 90 | }, 91 | { 92 | "url": "https://tidelift.com/funding/github/packagist/league/html-to-markdown", 93 | "type": "tidelift" 94 | } 95 | ], 96 | "time": "2022-03-02T17:24:08+00:00" 97 | } 98 | ], 99 | "packages-dev": [], 100 | "aliases": [], 101 | "minimum-stability": "stable", 102 | "stability-flags": [], 103 | "prefer-stable": false, 104 | "prefer-lowest": false, 105 | "platform": [], 106 | "platform-dev": [], 107 | "plugin-api-version": "2.3.0" 108 | } 109 | -------------------------------------------------------------------------------- /config.example.php: -------------------------------------------------------------------------------- 1 | /etc/ssmtp/ssmtp.conf <&2 echo 'ERROR: Invalid installer checksum' 10 | rm composer-setup.php 11 | exit 1 12 | fi 13 | 14 | php composer-setup.php --quiet 15 | RESULT=$? 16 | rm composer-setup.php 17 | exit $RESULT -------------------------------------------------------------------------------- /docker-utils/test_mail.php: -------------------------------------------------------------------------------- 1 | php docker-utils/test_mail.php test@example.com 3 | 4 | $to = $argv[1]; 5 | $subject = 'An issue has been reported!'; 6 | $message = 'Mail2Deck response

You created a new issue on board XXXXX.

Check out this link to see your newly created card.

'; 7 | $headers = array( 8 | 'From' => 'no-reply@example.com', 9 | 'MIME-Version' => '1.0', 10 | 'Content-Type' => 'text/html' 11 | ); 12 | 13 | mail($to, $subject, $message, $headers); 14 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | getNewMessages(); 12 | 13 | if(!$emails) { 14 | // delete all messages marked for deletion and return 15 | $inbox->expunge(); 16 | return; 17 | } 18 | 19 | for ($j = 0; $j < count($emails) && $j < 5; $j++) { 20 | $structure = $inbox->fetchMessageStructure($emails[$j]); 21 | $base64encode = false; 22 | if($structure->encoding == 3) { 23 | $base64encode = true; // BASE64 24 | } 25 | $attachments = array(); 26 | $attNames = array(); 27 | if (isset($structure->parts) && count($structure->parts)) { 28 | for ($i = 0; $i < count($structure->parts); $i++) { 29 | if ($structure->parts[$i]->ifdparameters) { 30 | foreach ($structure->parts[$i]->dparameters as $object) { 31 | if (strtolower($object->attribute) == 'filename') { 32 | $attachments[$i]['is_attachment'] = true; 33 | $attachments[$i]['filename'] = $object->value; 34 | } 35 | } 36 | } 37 | 38 | if ($structure->parts[$i]->ifparameters) { 39 | foreach ($structure->parts[$i]->parameters as $object) { 40 | if (strtolower($object->attribute) == 'name') { 41 | $attachments[$i]['is_attachment'] = true; 42 | $attachments[$i]['name'] = $object->value; 43 | } 44 | } 45 | } 46 | 47 | if ($attachments[$i]['is_attachment']) { 48 | $attachments[$i]['attachment'] = $inbox->fetchMessageBody($emails[$j], $i+1); 49 | if ($structure->parts[$i]->encoding == 3) { // 3 = BASE64 50 | $attachments[$i]['attachment'] = base64_decode($attachments[$i]['attachment']); 51 | } 52 | elseif ($structure->parts[$i]->encoding == 4) { // 4 = QUOTED-PRINTABLE 53 | $attachments[$i]['attachment'] = quoted_printable_decode($attachments[$i]['attachment']); 54 | } 55 | } 56 | } 57 | } 58 | for ($i = 1; $i <= count($attachments); $i++) { 59 | if(! file_exists(getcwd() . '/attachments')) { 60 | mkdir(getcwd() . '/attachments'); 61 | } 62 | if ($attachments[$i]['is_attachment'] == 1) { 63 | $filename = $attachments[$i]['name']; 64 | if (empty($filename)) $filename = $attachments[$i]['filename']; 65 | 66 | $fp = fopen(getcwd() . '/attachments/' . $filename, "w+"); 67 | fwrite($fp, $attachments[$i]['attachment']); 68 | fclose($fp); 69 | array_push($attNames, $attachments[$i]['filename']); 70 | } 71 | } 72 | 73 | $overview = $inbox->headerInfo($emails[$j]); 74 | $board = null; 75 | if(isset($overview->{'X-Original-To'}) && strstr($overview->{'X-Original-To'}, '+')) { 76 | $board = strstr(substr($overview->{'X-Original-To'}, strpos($overview->{'X-Original-To'}, '+') + 1), '@', true); 77 | } else { 78 | if(strstr($overview->to[0]->mailbox, '+')) { 79 | $board = substr($overview->to[0]->mailbox, strpos($overview->to[0]->mailbox, '+') + 1); 80 | } 81 | }; 82 | 83 | if(strstr($board, '+')) $board = str_replace('+', ' ', $board); 84 | 85 | $data = new stdClass(); 86 | $data->title = DECODE_SPECIAL_CHARACTERS ? mb_decode_mimeheader($overview->subject) : $overview->subject; 87 | $data->type = "plain"; 88 | $data->order = -time(); 89 | $body = $inbox->fetchMessageBody($emails[$j], 1.1); 90 | if ($body == "") { 91 | $body = $inbox->fetchMessageBody($emails[$j], 1); 92 | } 93 | if(count($attachments)) { 94 | $data->attachments = $attNames; 95 | $description = DECODE_SPECIAL_CHARACTERS ? quoted_printable_decode($body) : $body; 96 | } else { 97 | $description = DECODE_SPECIAL_CHARACTERS ? quoted_printable_decode($body) : $body; 98 | } 99 | if($base64encode) { 100 | $description = base64_decode($description); 101 | } 102 | if($description != strip_tags($description)) { 103 | $description = (new ConvertToMD($description))->execute(); 104 | } 105 | $data->description = $description; 106 | $mailSender = new stdClass(); 107 | $mailSender->userId = $overview->reply_to[0]->mailbox; 108 | 109 | $newcard = new DeckClass(); 110 | $response = $newcard->addCard($data, $mailSender, $board); 111 | $mailSender->origin .= "{$overview->reply_to[0]->mailbox}@{$overview->reply_to[0]->host}"; 112 | 113 | if(MAIL_NOTIFICATION) { 114 | if($response) { 115 | $inbox->reply($mailSender->origin, $response); 116 | } else { 117 | $inbox->reply($mailSender->origin); 118 | } 119 | } 120 | if(!$response) { 121 | foreach($attNames as $attachment) unlink(getcwd() . "/attachments/" . $attachment); 122 | } 123 | 124 | //remove email after processing 125 | if(DELETE_MAIL_AFTER_PROCESSING) { 126 | $inbox->delete($emails[$j]); 127 | } 128 | } 129 | ?> 130 | -------------------------------------------------------------------------------- /lib/ConvertToMD.php: -------------------------------------------------------------------------------- 1 | converter = new HtmlConverter([ 12 | 'strip_tags' => true, 13 | 'remove_nodes' => 'title' 14 | ]); 15 | $this->html = $html; 16 | } 17 | 18 | public function execute() 19 | { 20 | return $this->converter->convert($this->html); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/DeckClass.php: -------------------------------------------------------------------------------- 1 | $endpoint, 15 | CURLOPT_RETURNTRANSFER => true, 16 | CURLOPT_ENCODING => '', 17 | CURLOPT_MAXREDIRS => 10, 18 | CURLOPT_TIMEOUT => 0, 19 | CURLOPT_FOLLOWLOCATION => true, 20 | CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, 21 | CURLOPT_CUSTOMREQUEST => $request, 22 | CURLOPT_HTTPHEADER => array( 23 | 'Authorization: Basic ' . base64_encode(NC_USER . ':' . NC_PASSWORD), 24 | 'OCS-APIRequest: true', 25 | ), 26 | )); 27 | 28 | if($request === 'POST') curl_setopt($curl, CURLOPT_POSTFIELDS, (array) $data); 29 | 30 | $response = curl_exec($curl); 31 | $err = curl_error($curl); 32 | $this->responseCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); 33 | 34 | curl_close($curl); 35 | if($err) echo "cURL Error #:" . $err; 36 | 37 | return json_decode($response); 38 | } 39 | 40 | public function getParameters($params, $boardFromMail = null) {// get the board and the stack 41 | if(!$boardFromMail) // if board is not set within the email address, look for board into email subject 42 | if(preg_match('/b-"([^"]+)"/', $params, $m) || preg_match("/b-'([^']+)'/", $params, $m)) { 43 | $boardFromMail = $m[1]; 44 | $params = str_replace($m[0], '', $params); 45 | } 46 | if(preg_match('/s-"([^"]+)"/', $params, $m) || preg_match("/s-'([^']+)'/", $params, $m)) { 47 | $stackFromMail = $m[1]; 48 | $params = str_replace($m[0], '', $params); 49 | } 50 | if(preg_match('/u-"([^"]+)"/', $params, $m) || preg_match("/u-'([^']+)'/", $params, $m)) { 51 | $userFromMail = $m[1]; 52 | $params = str_replace($m[0], '', $params); 53 | } 54 | if(preg_match('/d-"([^"]+)"/', $params, $m) || preg_match("/d-'([^']+)'/", $params, $m)) { 55 | $duedateFromMail = $m[1]; 56 | $params = str_replace($m[0], '', $params); 57 | } 58 | 59 | $boards = $this->apiCall("GET", NC_SERVER . "/index.php/apps/deck/api/v1.0/boards"); 60 | $boardId = $boardName = null; 61 | foreach($boards as $board) { 62 | if(strtolower($board->title) == strtolower($boardFromMail)) { 63 | if(!$this->checkBotPermissions($board)) { 64 | return false; 65 | } 66 | $boardId = $board->id; 67 | $boardName = $board->title; 68 | break; 69 | } 70 | } 71 | 72 | if($boardId) { 73 | $stacks = $this->apiCall("GET", NC_SERVER . "/index.php/apps/deck/api/v1.0/boards/$boardId/stacks"); 74 | foreach($stacks as $key => $stack) 75 | if(strtolower($stack->title) == strtolower($stackFromMail)) { 76 | $stackId = $stack->id; 77 | break; 78 | } 79 | if($key == array_key_last($stacks) && !isset($stackId)) $stackId = $stacks[0]->id; 80 | } else { 81 | return false; 82 | } 83 | 84 | $boardStack = new \stdClass(); 85 | $boardStack->board = $boardId; 86 | $boardStack->stack = $stackId; 87 | $boardStack->newTitle = $params; 88 | $boardStack->boardTitle = $boardName; 89 | $boardStack->userId = strtolower($userFromMail); 90 | $boardStack->dueDate = $duedateFromMail; 91 | 92 | 93 | return $boardStack; 94 | } 95 | 96 | public function addCard($data, $user, $board = null) { 97 | $params = $this->getParameters($data->title, $board); 98 | 99 | if($params) { 100 | $data->title = $params->newTitle; 101 | $data->duedate = $params->dueDate; 102 | $card = $this->apiCall("POST", NC_SERVER . "/index.php/apps/deck/api/v1.0/boards/{$params->board}/stacks/{$params->stack}/cards", $data); 103 | $card->board = $params->board; 104 | $card->stack = $params->stack; 105 | 106 | if ($params->userId) $user->userId = $params->userId; 107 | 108 | if($this->responseCode == 200) { 109 | if(ASSIGN_SENDER || $params->userId) $this->assignUser($card, $user); 110 | if($data->attachments) $this->addAttachments($card, $data->attachments); 111 | $card->boardTitle = $params->boardTitle; 112 | } else { 113 | return false; 114 | } 115 | return $card; 116 | } 117 | return false; 118 | } 119 | 120 | private function addAttachments($card, $attachments) { 121 | $fullPath = getcwd() . "/attachments/"; //get full path to attachments directory 122 | for ($i = 0; $i < count($attachments); $i++) { 123 | $file = $fullPath . $attachments[$i]; 124 | $data = array( 125 | 'file' => new \CURLFile($file) 126 | ); 127 | $this->apiCall("POST", NC_SERVER . "/index.php/apps/deck/api/v1.0/boards/{$card->board}/stacks/{$card->stack}/cards/{$card->id}/attachments?type=file", $data, true); 128 | unlink($file); 129 | } 130 | } 131 | 132 | public function assignUser($card, $mailUser) 133 | { 134 | $board = $this->apiCall("GET", NC_SERVER . "/index.php/apps/deck/api/v1.0/boards/{$card->board}"); 135 | $boardUsers = array_map(function ($user) { return $user->uid; }, $board->users); 136 | 137 | foreach($boardUsers as $user) { 138 | if($user === $mailUser->userId) { 139 | $this->apiCall("PUT", NC_SERVER . "/index.php/apps/deck/api/v1.0/boards/{$card->board}/stacks/{$card->stack}/cards/{$card->id}/assignUser", $mailUser); 140 | break; 141 | } 142 | } 143 | } 144 | 145 | private function checkBotPermissions($board) { 146 | foreach($board->acl as $acl) 147 | if($acl->participant->uid == NC_USER && $acl->permissionEdit) 148 | return true; 149 | 150 | return false; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /lib/MailClass.php: -------------------------------------------------------------------------------- 1 | inbox = imap_open("{" . MAIL_SERVER . ":" . MAIL_SERVER_PORT . MAIL_SERVER_FLAGS . "}INBOX", MAIL_USER, MAIL_PASSWORD) 11 | or die("can't connect:" . imap_last_error()); 12 | } 13 | 14 | public function __destruct() 15 | { 16 | imap_close($this->inbox); 17 | } 18 | 19 | public function getNewMessages() { 20 | return imap_search($this->inbox, 'UNSEEN'); 21 | } 22 | 23 | public function fetchMessageStructure($email) { 24 | return imap_fetchstructure($this->inbox, $email); 25 | } 26 | 27 | public function fetchMessageBody($email, $section) { 28 | return imap_fetchbody($this->inbox, $email, $section); 29 | } 30 | 31 | public function headerInfo($email) { 32 | $headerInfo = imap_headerinfo($this->inbox, $email); 33 | $additionalHeaderInfo = imap_fetchheader($this->inbox, $email); 34 | $infos = explode("\n", $additionalHeaderInfo); 35 | 36 | foreach($infos as $info) { 37 | $data = explode(":", $info); 38 | if( count($data) == 2 && !isset($head[$data[0]])) { 39 | if(trim($data[0]) === 'X-Original-To') { 40 | $headerInfo->{'X-Original-To'} = trim($data[1]); 41 | break; 42 | } 43 | } 44 | } 45 | 46 | return $headerInfo; 47 | } 48 | 49 | public function reply($sender, $response = null) { 50 | $server = NC_SERVER; 51 | 52 | if(strstr($server, "https://")) { 53 | $server = str_replace('https://', '', $server); 54 | } else if(strstr($server, "http://")) { 55 | $server = str_replace('http://', '', $server); 56 | } 57 | 58 | $headers = array( 59 | 'From' => 'no-reply@' . $server, 60 | 'MIME-Version' => '1.0', 61 | 'Content-Type' => 'text/html' 62 | ); 63 | 64 | if($response) { 65 | $body = "

A new card has been created on board board}" . "\">{$response->boardTitle}.

66 |

Check out this board}/card/{$response->id}" . "\">link to see the newly created card.

67 |

Card ID is {$response->id}

"; 68 | $subject = 'A new card has been created!'; 69 | } else { 70 | $body = "

There was a problem creating a new card.

Make sure the board was setup correctly.

"; 71 | $subject = "A new card could not be created!"; 72 | } 73 | 74 | $message = ""; 75 | $message .= "mail2deck response"; 76 | $message .= "$body"; 77 | $message .= ""; 78 | 79 | mail($sender, $subject, $message, $headers); 80 | } 81 | 82 | /** 83 | * Mark emails for deletion 84 | * 85 | * @param $email email number that you want to delete 86 | * 87 | * @return void 88 | */ 89 | public function delete(int $email) 90 | { 91 | imap_delete($this->inbox, imap_uid($this->inbox, $email), FT_UID); 92 | } 93 | 94 | /** 95 | * Delete all messages marked for deletion 96 | */ 97 | public function expunge() 98 | { 99 | imap_expunge($this->inbox); 100 | } 101 | } 102 | --------------------------------------------------------------------------------