From d0d1cb2c45362470001b22cf6e5936ebed1711bd Mon Sep 17 00:00:00 2001 From: Matthew Ross Date: Wed, 20 May 2020 16:25:44 -0400 Subject: [PATCH] Finished email functionality --- README.md | 18 +- package-lock.json | 426 +++++++++++++-------- package.json | 8 +- src/api/controllers/BaseController.php | 10 +- src/api/controllers/Boards.php | 24 ++ src/api/controllers/Comments.php | 62 ++- src/api/controllers/Tasks.php | 64 +++- src/api/helpers/EmailData.php | 40 ++ src/api/helpers/Mailer.php | 94 +++-- src/app/board/column/column.component.html | 2 +- src/json/en_api.json | 14 +- src/json/es_api.json | 18 +- src/json/fr_api.json | 14 +- test/api/controllers/ActivityTest.php | 3 +- test/api/controllers/AttachmentsTest.php | 3 +- test/api/controllers/AuthTest.php | 3 +- test/api/controllers/AutoActionsTest.php | 3 +- test/api/controllers/BoardsTest.php | 66 ++-- test/api/controllers/ColumnsTest.php | 3 +- test/api/controllers/CommentsTest.php | 42 +- test/api/controllers/TasksTest.php | 41 +- test/api/controllers/UsersTest.php | 3 +- 22 files changed, 624 insertions(+), 337 deletions(-) create mode 100644 src/api/helpers/EmailData.php diff --git a/README.md b/README.md index d12ae84..0d11a87 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Installing TaskBoard is as easy as 1, 2, 3! 2. Extract it to your webserver 3. Verify the `api` directory is writable -Optionally, you will want to edit `api/helpers/mailer.php` if you intend to use email features. +If you intend to use email features, you will need to edit `api/helpers/mailer.php`. ### Server Config @@ -197,11 +197,11 @@ Because I like seeing the numbers. Language | Files | Blank | Comment | Code -----------|--------:|---------:|---------:|---------: -TypeScript | 66 | 957 | 126 | 4055 -PHP | 19 | 705 | 33 | 2132 -HTML | 21 | 257 | 0 | 1537 -SASS | 14 | 296 | 10 | 1338 -**SUM:** | **120** | **2215** | **169** | **9062** +TypeScript | 66 | 958 | 126 | 4057 +PHP | 20 | 752 | 40 | 2265 +HTML | 21 | 257 | 0 | 1540 +SASS | 14 | 298 | 10 | 1344 +**SUM:** | **121** | **2265** | **176** | **9206** Command: `cloc --exclude-dir=vendor --exclude-ext=json,svg,ini src/` @@ -209,8 +209,8 @@ Command: `cloc --exclude-dir=vendor --exclude-ext=json,svg,ini src/` Language | Files | Blank | Comment | Code -----------|-------:|---------:|---------:|---------: -TypeScript | 38 | 1009 | 8 | 3505 -PHP | 11 | 793 | 16 | 2338 -**SUM:** | **49** | **1802** | **24** | **5843** +TypeScript | 38 | 1009 | 8 | 3523 +PHP | 11 | 795 | 19 | 2300 +**SUM:** | **49** | **1804** | **27** | **5823** Command: `cloc --exclude-ext=xml test/` diff --git a/package-lock.json b/package-lock.json index aac9f6e..bac4e29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1753,9 +1753,9 @@ } }, "@types/highlight.js": { - "version": "9.12.3", - "resolved": "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-9.12.3.tgz", - "integrity": "sha512-pGF/zvYOACZ/gLGWdQH8zSwteQS1epp68yRcVLJMgUck/MjEn/FBYmPub9pXT8C1e4a8YZfHo1CKyV8q1vKUnQ==", + "version": "9.12.4", + "resolved": "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-9.12.4.tgz", + "integrity": "sha512-t2szdkwmg2JJyuCM20e8kR2X59WCE5Zkl4bzm1u1Oukjm79zpbiAv+QjnwLnuuV0WHEcX2NgUItu0pAMKuOPww==", "dev": true }, "@types/jasmine": { @@ -2686,9 +2686,9 @@ "dev": true }, "base64id": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", - "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", "dev": true }, "batch": { @@ -4458,9 +4458,9 @@ } }, "date-format": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", - "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz", + "integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==", "dev": true }, "debug": { @@ -4916,64 +4916,91 @@ } }, "engine.io": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz", - "integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.1.tgz", + "integrity": "sha512-8MfIfF1/IIfxuc2gv5K+XlFZczw/BpTvqBdl0E2fBLkYQp4miv4LuDTVtYt4yMyaIFLEr4vtaSgV4mjvll8Crw==", "dev": true, "requires": { "accepts": "~1.3.4", - "base64id": "1.0.0", + "base64id": "2.0.0", "cookie": "0.3.1", - "debug": "~3.1.0", - "engine.io-parser": "~2.1.0", - "ws": "~3.3.1" + "debug": "~4.1.0", + "engine.io-parser": "~2.2.0", + "ws": "^7.1.2" }, "dependencies": { "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, "engine.io-client": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", - "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.2.tgz", + "integrity": "sha512-AWjc1Xg06a6UPFOBAzJf48W1UR/qKYmv/ubgSCumo9GXgvL/xGIvo05dXoBL+2NTLMipDI7in8xK61C17L25xg==", "dev": true, "requires": { - "component-emitter": "1.2.1", + "component-emitter": "~1.3.0", "component-inherit": "0.0.3", - "debug": "~3.1.0", - "engine.io-parser": "~2.1.1", + "debug": "~4.1.0", + "engine.io-parser": "~2.2.0", "has-cors": "1.1.0", "indexof": "0.0.1", "parseqs": "0.0.5", "parseuri": "0.0.5", - "ws": "~3.3.1", + "ws": "~6.1.0", "xmlhttprequest-ssl": "~1.5.4", "yeast": "0.1.2" }, "dependencies": { + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "ws": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", + "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" } } } }, "engine.io-parser": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", - "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.0.tgz", + "integrity": "sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==", "dev": true, "requires": { "after": "0.8.2", @@ -5124,9 +5151,9 @@ "dev": true }, "eventemitter3": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz", - "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", + "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==", "dev": true }, "events": { @@ -5632,9 +5659,9 @@ } }, "follow-redirects": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.9.0.tgz", - "integrity": "sha512-CRcPzsSIbXyVDl0QI01muNDu69S8trU4jArW9LpOt2WtC6LyUJetcIrmfHsRBx7/Jb6GHJUiuqyYxPooFfNt6A==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz", + "integrity": "sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==", "dev": true, "requires": { "debug": "^3.0.0" @@ -6273,9 +6300,9 @@ "dev": true }, "http-proxy": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz", - "integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", "dev": true, "requires": { "eventemitter3": "^4.0.0", @@ -7514,33 +7541,33 @@ } }, "karma": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/karma/-/karma-5.0.5.tgz", - "integrity": "sha512-Q4Su7kNwkTgqS+KbSCYgH0p4a/0JIxVLyp7qKNV7vgPNhIF4kIoh0GlUfMKpw67BrR3hgPQSJoxgF7xnzUtPpg==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/karma/-/karma-5.0.8.tgz", + "integrity": "sha512-n0iQ66to2YivGTw202ReC5I33F7/BaiQRBEP6MNRex//3ckblNcEDV5T5CL+2W/wdjPc479IxDkMtBoOZ/4PnA==", "dev": true, "requires": { - "body-parser": "^1.16.1", + "body-parser": "^1.19.0", "braces": "^3.0.2", "chokidar": "^3.0.0", - "colors": "^1.1.0", - "connect": "^3.6.0", + "colors": "^1.4.0", + "connect": "^3.7.0", "di": "^0.0.1", - "dom-serialize": "^2.2.0", - "flatted": "^2.0.0", - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "http-proxy": "^1.13.0", - "isbinaryfile": "^4.0.2", - "lodash": "^4.17.14", - "log4js": "^4.0.0", - "mime": "^2.3.1", - "minimatch": "^3.0.2", - "qjobs": "^1.1.4", - "range-parser": "^1.2.0", - "rimraf": "^2.6.0", - "socket.io": "2.1.1", + "dom-serialize": "^2.2.1", + "flatted": "^2.0.2", + "glob": "^7.1.6", + "graceful-fs": "^4.2.4", + "http-proxy": "^1.18.0", + "isbinaryfile": "^4.0.6", + "lodash": "^4.17.15", + "log4js": "^6.2.1", + "mime": "^2.4.5", + "minimatch": "^3.0.4", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^2.3.0", "source-map": "^0.6.1", - "tmp": "0.0.33", + "tmp": "0.2.1", "ua-parser-js": "0.7.21", "yargs": "^15.3.1" }, @@ -7603,6 +7630,26 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -7618,6 +7665,12 @@ "p-locate": "^4.1.0" } }, + "mime": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.5.tgz", + "integrity": "sha512-3hQhEUF027BuxZjQA3s7rIv/7VCQPa27hN9u9g87sEkWaKwQPuXOkVKtOeiyUrnWqTDiOs8Ed2rwg733mB0R5w==", + "dev": true + }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -7654,6 +7707,15 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", @@ -7674,6 +7736,15 @@ "ansi-regex": "^5.0.0" } }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -8011,16 +8082,16 @@ } }, "log4js": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.5.1.tgz", - "integrity": "sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.2.1.tgz", + "integrity": "sha512-7n+Oqxxz7VcQJhIlqhcYZBTpbcQ7XsR0MUIfJkx/n3VUjkAS4iUr+4UJlhxf28RvP9PMGQXbgTUhLApnu0XXgA==", "dev": true, "requires": { - "date-format": "^2.0.0", + "date-format": "^3.0.0", "debug": "^4.1.1", - "flatted": "^2.0.0", + "flatted": "^2.0.1", "rfdc": "^1.1.4", - "streamroller": "^1.0.6" + "streamroller": "^2.2.4" }, "dependencies": { "debug": { @@ -8222,9 +8293,9 @@ } }, "marked": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-1.0.0.tgz", - "integrity": "sha512-Wo+L1pWTVibfrSr+TTtMuiMfNzmZWiOPeO7rZsQUY5bgsxpHesBEcIWJloWVTFnrMXnf/TL30eTFSGJddmQAng==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.1.0.tgz", + "integrity": "sha512-EkE7RW6KcXfMHy2PA7Jg0YJE1l8UPEZE8k45tylzmZM30/r1M1MUXWQfJlrSbsTeh7m/XTwHbWUENvAJZpp1YA==" }, "md5.js": { "version": "1.3.5", @@ -12218,9 +12289,9 @@ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, "puppeteer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-3.0.4.tgz", - "integrity": "sha512-1QEb4tJXXbNId7WSHlcDkS3B4GklTIebKn8Y9D6B7tAdUjQncb+8QlTjbQsAgGX5dhRG32Qycuk5XKzJgLs0sg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-3.1.0.tgz", + "integrity": "sha512-jLa9sqdVx0tPnr2FcwAq+8DSjGhSM4YpkwOf3JE22Ycyqm71SW7B5uGfTyMGFoLCmbCozbLZclCjasPb0flTRw==", "dev": true, "requires": { "debug": "^4.1.0", @@ -12274,12 +12345,6 @@ "requires": { "glob": "^7.1.3" } - }, - "ws": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz", - "integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==", - "dev": true } } }, @@ -13568,27 +13633,33 @@ } }, "socket.io": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz", - "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz", + "integrity": "sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==", "dev": true, "requires": { - "debug": "~3.1.0", - "engine.io": "~3.2.0", + "debug": "~4.1.0", + "engine.io": "~3.4.0", "has-binary2": "~1.0.2", "socket.io-adapter": "~1.1.0", - "socket.io-client": "2.1.1", - "socket.io-parser": "~3.2.0" + "socket.io-client": "2.3.0", + "socket.io-parser": "~3.4.0" }, "dependencies": { "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, @@ -13599,56 +13670,34 @@ "dev": true }, "socket.io-client": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz", - "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz", + "integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==", "dev": true, "requires": { "backo2": "1.0.2", "base64-arraybuffer": "0.1.5", "component-bind": "1.0.0", "component-emitter": "1.2.1", - "debug": "~3.1.0", - "engine.io-client": "~3.2.0", + "debug": "~4.1.0", + "engine.io-client": "~3.4.0", "has-binary2": "~1.0.2", "has-cors": "1.1.0", "indexof": "0.0.1", "object-component": "0.0.3", "parseqs": "0.0.5", "parseuri": "0.0.5", - "socket.io-parser": "~3.2.0", + "socket.io-parser": "~3.3.0", "to-array": "0.1.4" }, "dependencies": { "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { - "ms": "2.0.0" - } - } - } - }, - "socket.io-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", - "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", - "dev": true, - "requires": { - "component-emitter": "1.2.1", - "debug": "~3.1.0", - "isarray": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "isarray": { @@ -13656,6 +13705,74 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "socket.io-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz", + "integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==", + "dev": true, + "requires": { + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + } + } + }, + "socket.io-parser": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz", + "integrity": "sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==", + "dev": true, + "requires": { + "component-emitter": "1.2.1", + "debug": "~4.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, @@ -14089,38 +14206,48 @@ "dev": true }, "streamroller": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.6.tgz", - "integrity": "sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-2.2.4.tgz", + "integrity": "sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==", "dev": true, "requires": { - "async": "^2.6.2", - "date-format": "^2.0.0", - "debug": "^3.2.6", - "fs-extra": "^7.0.1", - "lodash": "^4.17.14" + "date-format": "^2.1.0", + "debug": "^4.1.1", + "fs-extra": "^8.1.0" }, "dependencies": { + "date-format": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", + "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", + "dev": true + }, "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { "ms": "^2.1.1" } }, "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -15182,12 +15309,6 @@ "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==", "dev": true }, - "ultron": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", - "dev": true - }, "unbzip2-stream": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.2.tgz", @@ -16573,15 +16694,10 @@ } }, "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" - } + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz", + "integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==", + "dev": true }, "xdg-basedir": { "version": "3.0.0", diff --git a/package.json b/package.json index 70607d6..08d054b 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "classlist.js": "^1.1.20150312", "core-js": "^3.6.5", "highlight.js": "^10.0.3", - "marked": "^1.0.0", + "marked": "^1.1.0", "node-sass": "^4.14.1", "rxjs": "^6.5.5", "scss-base": "^1.4.0", @@ -77,7 +77,7 @@ "@angular/compiler-cli": "^9.1.7", "@angular/language-service": "^9.1.7", "@types/chartist": "^0.9.48", - "@types/highlight.js": "^9.12.3", + "@types/highlight.js": "^9.12.4", "@types/jasmine": "~3.5.10", "@types/jasminewd2": "~2.0.8", "@types/marked": "^0.7.4", @@ -89,7 +89,7 @@ "jasmine": "^3.5.0", "jasmine-core": "^3.5.0", "jasmine-spec-reporter": "~5.0.2", - "karma": "^5.0.5", + "karma": "^5.0.8", "karma-chrome-launcher": "^3.1.0", "karma-coverage-istanbul-reporter": "^3.0.2", "karma-jasmine": "~3.1.1", @@ -97,7 +97,7 @@ "npm-run-all": "^4.1.5", "npm-watch": "^0.6.0", "protractor": "^7.0.0", - "puppeteer": "^3.0.4", + "puppeteer": "^3.1.0", "ts-node": "^8.10.1", "tslint": "^6.1.2", "typescript": "^3.8.3" diff --git a/src/api/controllers/BaseController.php b/src/api/controllers/BaseController.php index 7f7d110..ec6c75a 100644 --- a/src/api/controllers/BaseController.php +++ b/src/api/controllers/BaseController.php @@ -15,7 +15,6 @@ abstract class BaseController { $this->dbLogger = new DbLogger(); // Default to English - $this->mailer = new Mailer('en'); $this->loadStrings('en'); } @@ -74,6 +73,15 @@ abstract class BaseController { return $status; } + protected function getAdminEmailAddresses($boardId) { + $emails = R::getAll('SELECT email FROM user u ' . + 'JOIN board_user bu ON u.id = bu.user_id ' . + 'WHERE u.security_level < 3 AND u.email <> "" AND board_id = ?', + [$boardId]); + + return count($emails) > 0 ? $emails[0] : []; + } + private function loadStrings($lang) { $json = '{}'; diff --git a/src/api/controllers/Boards.php b/src/api/controllers/Boards.php index 22be026..e28cd8c 100644 --- a/src/api/controllers/Boards.php +++ b/src/api/controllers/Boards.php @@ -83,6 +83,8 @@ class Boards extends BaseController { '(' . $board->name . ').'); $this->apiJson->addData($this->loadAllBoards($request)); + $this->sendEmail($board, $actor, 'newBoard'); + return $this->jsonResponse($response); } @@ -133,6 +135,8 @@ class Boards extends BaseController { '(' . $update->name . ').'); $this->apiJson->addData($this->loadAllBoards($request)); + $this->sendEmail($update, $actor, 'editBoard'); + return $this->jsonResponse($response); } @@ -166,6 +170,8 @@ class Boards extends BaseController { '(' . $before->name . ').'); $this->apiJson->addData($this->loadAllBoards($request)); + $this->sendEmail($before, $actor, 'removeBoard'); + return $this->jsonResponse($response); } @@ -215,5 +221,23 @@ class Boards extends BaseController { return $user; } + + private function sendEmail($board, $actor, $type) { + $data = new EmailData($board->id); + + $data->username = $actor->username; + $data->boardName = $board->name; + $data->type = $type; + + $emails = $this->getAdminEmailAddresses($board->id); + if($actor->email !== '' && !in_array($actor->email, $emails)) { + $emails[] = $actor->email; + } + + $result = $this->mailer->sendMail($emails, $data); + if ($result !== '') { + $this->apiJson->addAlert('info', $result); + } + } } diff --git a/src/api/controllers/Comments.php b/src/api/controllers/Comments.php index a6c9f23..75466f5 100644 --- a/src/api/controllers/Comments.php +++ b/src/api/controllers/Comments.php @@ -52,7 +52,9 @@ class Comments extends BaseController { return $this->jsonResponse($response); } - if (!$this->checkBoardAccess($this->getBoardId($task->id), $request)) { + $boardId = $this->getBoardId($task->id); + + if (!$this->checkBoardAccess($boardId, $request)) { return $this->jsonResponse($response, 403); } @@ -67,6 +69,9 @@ class Comments extends BaseController { $this->apiJson->addData(R::exportAll($task)); $this->apiJson->addAlert('success', $this->strings->api_commentAdded); + $board = R::load('board', $boardId); + $this->sendEmail($board, $task, $comment->text, $actor, 'newComment'); + return $this->jsonResponse($response); } @@ -110,8 +115,9 @@ class Comments extends BaseController { return $this->jsonResponse($response); } - if (!$this->checkBoardAccess( - $this->getBoardId($comment->task_id), $request)) { + $boardId = $this->getBoardId($comment->task_id); + + if (!$this->checkBoardAccess($boardId, $request)) { return $this->jsonResponse($response, 403); } @@ -127,6 +133,9 @@ class Comments extends BaseController { $task = R::load('task', $comment->task_id); $this->apiJson->addData(R::exportAll($task)); + $board = R::load('board', $boardId); + $this->sendEmail($board, $task, $comment->text, $actor, 'editComment'); + return $this->jsonResponse($response); } @@ -159,8 +168,9 @@ class Comments extends BaseController { return $this->jsonResponse($response); } - if (!$this->checkBoardAccess( - $this->getBoardId($comment->task_id), $request)) { + $boardId = $this->getBoardId($comment->task_id); + + if (!$this->checkBoardAccess($boardId, $request)) { return $this->jsonResponse($response, 403); } @@ -177,6 +187,9 @@ class Comments extends BaseController { $task = R::load('task', $comment->task_id); $this->apiJson->addData(R::exportAll($task)); + $board = R::load('board', $boardId); + $this->sendEmail($board, $task, $comment->text, $actor, 'editComment'); + return $this->jsonResponse($response); } @@ -186,5 +199,44 @@ class Comments extends BaseController { return $column->board_id; } + + private function sendEmail($board, $task, $comment, $actor, $type) { + $data = new EmailData($board->id); + $column = R::load('column', $task->column_id); + + $data->comment = $comment ?: ''; + + $data->username = $actor->username ?: ''; + $data->boardName = $board->name ?: ''; + $data->type = $type; + + $data->taskName = $task->title ?: ''; + $data->taskDescription = $task->description ?: ''; + $data->taskDueDate = date('F j, Y, g:i:s A', (int)$task->due_date * 1000); + + $data->taskAssignees = ''; + foreach($task->sharedUserList as $assignee) { + $data->taskAssignees .= $assignee->username ?: '' . ' '; + } + + $data->taskCategories = ''; + foreach($task->sharedCategoryList as $category) { + $data->taskCategories .= $category->name ?: '' . ' '; + } + + $data->taskPoints = $task->points ?: ''; + $data->taskColumnName = $column->name ?: ''; + $data->taskPosition = $task->position ?: ''; + + $emails = $this->getAdminEmailAddresses($board->id); + if($actor->email !== '' && !in_array($actor->email, $emails)) { + $emails[] = $actor->email; // @codeCoverageIgnore + } + + $result = $this->mailer->sendMail($emails, $data); + if ($result !== '') { + $this->apiJson->addAlert('info', $result); // @codeCoverageIgnore + } + } } diff --git a/src/api/controllers/Tasks.php b/src/api/controllers/Tasks.php index d6cf50a..f79b37b 100644 --- a/src/api/controllers/Tasks.php +++ b/src/api/controllers/Tasks.php @@ -70,6 +70,8 @@ class Tasks extends BaseController { $board = R::load('board', $column->board_id); $this->apiJson->addData(R::exportAll($board)); + $this->sendEmail($board, $task, $actor, 'newTask'); + return $this->jsonResponse($response); } @@ -119,8 +121,7 @@ class Tasks extends BaseController { $this->dbLogger->logChange($actor->id, $actor->username . ' updated task ' . $task->title, - json_encode($task), json_encode($update), - 'task', $update->id); + json_encode($task), json_encode($update), 'task', $update->id); $boardId = $this->getBoardId($task->column_id); $board = R::load('board', $boardId); @@ -131,6 +132,8 @@ class Tasks extends BaseController { $this->apiJson->addData(R::exportAll($update)); $this->apiJson->addData(R::exportAll($board)); + $this->sendEmail($board, $update, $actor, 'editTask'); + return $this->jsonResponse($response); } @@ -174,6 +177,8 @@ class Tasks extends BaseController { $board = R::load('board', $boardId); $this->apiJson->addData(R::exportAll($board)); + $this->sendEmail($board, $before, $actor, 'removeTask'); + return $this->jsonResponse($response); } @@ -227,9 +232,10 @@ class Tasks extends BaseController { case ActionTrigger::ASSIGNED_TO_USER(): $prevAssigned = $this->isInList($action->source_id, - isset($before[0]['sharedUser']) ? - $before[0]['sharedUser'] : - []); + isset($before[0]['sharedUser']) + ? $before[0]['sharedUser'] + : [] + ); if ($prevAssigned) { break; @@ -244,9 +250,10 @@ class Tasks extends BaseController { case ActionTrigger::ADDED_TO_CATEGORY(): $prevAssigned = $this->isInList($action->source_id, - isset($before[0]['sharedCategory']) ? - $before[0]['sharedCategory'] : - []); + isset($before[0]['sharedCategory']) + ? $before[0]['sharedCategory'] + : [] + ); if ($prevAssigned) { break; } @@ -264,9 +271,7 @@ class Tasks extends BaseController { 0; if ($points !== (int)$after[0]['points']) { - $this->updateTaskColor($after[0]['id'], - $points, - $after[0]['points']); + $this->updateTaskColor($after[0]['id'], $points, $after[0]['points']); } break; @@ -370,5 +375,42 @@ class Tasks extends BaseController { $this->apiJson->addAlert('info',$this->strings->api_taskAutoColor); R::store($task); } + + private function sendEmail($board, $task, $actor, $type) { + $data = new EmailData($board->id); + $column = R::load('column', $task->column_id ?: ''); + + $data->username = $actor->username ?: ''; + $data->boardName = $board->name ?: ''; + $data->type = $type; + + $data->taskName = $task->title ?: ''; + $data->taskDescription = $task->description ?: ''; + $data->taskDueDate = date('F j, Y, g:i:s A', (int)$task->due_date * 1000); + + $data->taskAssignees = ''; + foreach($task->sharedUserList as $assignee) { + $data->taskAssignees .= $assignee->username . ' '; + } + + $data->taskCategories = ''; + foreach($task->sharedCategoryList as $category) { + $data->taskCategories .= $category->name . ' '; + } + + $data->taskPoints = $task->points ?: ''; + $data->taskColumnName = $column->name ?: ''; + $data->taskPosition = $task->position ?: ''; + + $emails = $this->getAdminEmailAddresses($board->id); + if($actor->email !== '' && !in_array($actor->email, $emails)) { + $emails[] = $actor->email; // @codeCoverageIgnore + } + + $result = $this->mailer->sendMail($emails, $data); + if ($result !== '') { + $this->apiJson->addAlert('info', $result); // @codeCoverageIgnore + } + } } diff --git a/src/api/helpers/EmailData.php b/src/api/helpers/EmailData.php new file mode 100644 index 0000000..4d1b78d --- /dev/null +++ b/src/api/helpers/EmailData.php @@ -0,0 +1,40 @@ +hostUrl = $_SERVER['HTTP_REFERER']; + $this->boardId = $boardId; + + $this->type = ''; + $this->username = ''; + $this->boardName = ''; + + $this->comment = ''; + $this->taskName = ''; + + $this->taskDescription = ''; + $this->taskDueDate = ''; + $this->taskAssignees = ''; + $this->taskCategories = ''; + $this->taskPoints = ''; + $this->taskColumnName = ''; + $this->taskPosition = ''; + } +} diff --git a/src/api/helpers/Mailer.php b/src/api/helpers/Mailer.php index 399bee8..f4081df 100644 --- a/src/api/helpers/Mailer.php +++ b/src/api/helpers/Mailer.php @@ -26,18 +26,19 @@ class Mailer { /** * Send email to one or more users in the provided list. - * @param $users List of users to email (must have at least one). + * + * @param $emails List of email addresses (must have at least one). * @param $data Object containing template type and replacements for template. */ - public function sendMail($users, $data) { + public function sendMail($emails, $data) { $this->initMail(); - if (count($users) < 1) { - return $this->strings->mail_error; + if (count($emails) < 1) { + return ''; } - foreach($users as $user) { - $this->mail->addAddress($user->email); + foreach($emails as $user) { + $this->mail->addAddress($user); } $this->mail->Subject = $this->strings->mail_subject; @@ -47,33 +48,53 @@ class Mailer { return $this->strings->mail_error; } - return $this->strings->mail_sent; + return $this->strings->mail_sent; // @codeCoverageIgnore } - private function parseTemplate($data) { + private function parseTemplate(EmailData $data) { $template = $this->getTemplate($data->type); - str_replace('%username%', $data->username, $template); - str_replace('%boardName%', $data->boardName, $template); - str_replace('%taskName%', $data->taskName, $template); - str_replace('%comment%', $data->comment, $template); - str_replace('%taskDescription%', $data->taskDescription, $template); - str_replace('%taskDueDate%', $data->taskDueDate, $template); - str_replace('%taskAssignees%', $data->taskAssignees, $template); - str_replace('%taskCategories%', $data->taskCategories, $template); - str_replace('%taskPoints%', $data->taskPoints, $template); - str_replace('%taskColumnName%', $data->taskColumnName, $template); - str_replace('%taskPosition%', $data->taskPosition, $template); - str_replace('%hostUrl%', $data->hostUrl, $template); - str_replace('%boardId%', $data->boardId, $template); + $template = str_replace('%hostUrl%', $data->hostUrl, $template); + $template = str_replace('%boardId%', $data->boardId, $template); + + $template = str_replace('%username%', $data->username, $template); + $template = str_replace('%boardName%', $data->boardName, $template); + + $template = str_replace('%comment%', $data->comment, $template); + $template = str_replace('%taskName%', $data->taskName, $template); + + $template = + str_replace('%taskDescription%', $data->taskDescription, $template); + $template = str_replace('%taskDueDate%', $data->taskDueDate, $template); + $template = str_replace('%taskAssignees%', $data->taskAssignees, $template); + $template = + str_replace('%taskCategories%', $data->taskCategories, $template); + $template = str_replace('%taskPoints%', $data->taskPoints, $template); + $template = + str_replace('%taskColumnName%', $data->taskColumnName, $template); + $template = str_replace('%taskPosition%', $data->taskPosition, $template); return $template; } + /** + * @codeCoverageIgnore + */ private function getTemplate($type) { $template = ''; switch($type) { + case 'newBoard': + $template = $this->strings->mail_template_newBoard; + break; + + case 'newComment': + $template = $this->strings->mail_template_newComment; + break; + + case 'newTask': + $template = $this->strings->mail_template_newTask; + case 'editBoard': $template = $this->strings->mail_template_editBoard; break; @@ -86,16 +107,17 @@ class Mailer { $template = $this->strings->mail_template_editTask; break; - case 'newBoard': - $template = $this->strings->mail_template_newBoard; + case 'removeBoard': + $template = $this->strings->mail_template_removeBoard; break; - case 'newComment': - $template = $this->strings->mail_template_newComment; + case 'removeComment': + $template = $this->strings->mail_template_removeComment; break; - case 'newTask': - $template = $this->strings->mail_template_newTask; + case 'removeTask': + $template = $this->strings->mail_template_removeTask; + break; } $template .= $this->strings->mail_template_openBoardLink; @@ -107,28 +129,26 @@ class Mailer { $this->mail = new PHPMailer(); $this->mail->isSendmail(); - $this->mail->setFrom($this->FROM_EMAIL, $this->FROM_NAME); + $this->mail->setFrom(Mailer::FROM_EMAIL, Mailer::FROM_NAME); - if (!$this->USE_SENDMAIL) { + // @codeCoverageIgnoreStart + if (!Mailer::USE_SENDMAIL) { $this->mail->isSMTP(); - $this->mail->Host = $this->SMTP_HOST; - $this->mail->Port = $this->SMTP_PORT; - $this->mail->Username = $this->SMTP_USER; - $this->mail->Password = $this->SMTP_PASS; + $this->mail->Host = Mailer::SMTP_HOST; + $this->mail->Port = Mailer::SMTP_PORT; + $this->mail->Username = Mailer::SMTP_USER; + $this->mail->Password = Mailer::SMTP_PASS; $this->mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; $this->mail->SMTPAuth = true; } + // @codeCoverageIgnoreEnd } private function loadStrings($lang) { $json = '{}'; - if (!$lang) { - $lang = 'en'; - } - try { $json = file_get_contents(__DIR__ . '/../../strings/' . $lang . '_api.json'); diff --git a/src/app/board/column/column.component.html b/src/app/board/column/column.component.html index f006960..23b044f 100644 --- a/src/app/board/column/column.component.html +++ b/src/app/board/column/column.component.html @@ -328,7 +328,7 @@ -