Add Mailer service and start Track command.

master
Lio Novelli 2021-10-02 03:52:07 +02:00
parent 87e4a5a4fd
commit aeae9ea490
14 changed files with 740 additions and 173 deletions

View File

@ -19,7 +19,8 @@
"php-di/php-di": "^6.3", "php-di/php-di": "^6.3",
"symfony/yaml": "^5.2", "symfony/yaml": "^5.2",
"mpdf/mpdf": "^8.0", "mpdf/mpdf": "^8.0",
"swiftmailer/swiftmailer": "^6.2" "symfony/mailer": "^5.3",
"symfony/google-mailer": "^5.3"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

596
app/composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "cbb7d7281f9622e5b410185c54558b28", "content-hash": "81050635de2f87c3f7f693ec8cb30645",
"packages": [ "packages": [
{ {
"name": "doctrine/lexer", "name": "doctrine/lexer",
@ -764,6 +764,56 @@
], ],
"time": "2021-03-05T17:36:06+00:00" "time": "2021-03-05T17:36:06+00:00"
}, },
{
"name": "psr/event-dispatcher",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/event-dispatcher.git",
"reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
"reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
"shasum": ""
},
"require": {
"php": ">=7.2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\EventDispatcher\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Standard interfaces for event handling.",
"keywords": [
"events",
"psr",
"psr-14"
],
"support": {
"issues": "https://github.com/php-fig/event-dispatcher/issues",
"source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0"
},
"time": "2019-01-08T18:20:26+00:00"
},
{ {
"name": "psr/http-client", "name": "psr/http-client",
"version": "1.0.1", "version": "1.0.1",
@ -1012,81 +1062,6 @@
], ],
"time": "2021-02-11T11:37:01+00:00" "time": "2021-02-11T11:37:01+00:00"
}, },
{
"name": "swiftmailer/swiftmailer",
"version": "v6.2.7",
"source": {
"type": "git",
"url": "https://github.com/swiftmailer/swiftmailer.git",
"reference": "15f7faf8508e04471f666633addacf54c0ab5933"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/15f7faf8508e04471f666633addacf54c0ab5933",
"reference": "15f7faf8508e04471f666633addacf54c0ab5933",
"shasum": ""
},
"require": {
"egulias/email-validator": "^2.0|^3.1",
"php": ">=7.0.0",
"symfony/polyfill-iconv": "^1.0",
"symfony/polyfill-intl-idn": "^1.10",
"symfony/polyfill-mbstring": "^1.0"
},
"require-dev": {
"mockery/mockery": "^1.0",
"symfony/phpunit-bridge": "^4.4|^5.0"
},
"suggest": {
"ext-intl": "Needed to support internationalized email addresses"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.2-dev"
}
},
"autoload": {
"files": [
"lib/swift_required.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Chris Corbyn"
},
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
}
],
"description": "Swiftmailer, free feature-rich PHP mailer",
"homepage": "https://swiftmailer.symfony.com",
"keywords": [
"email",
"mail",
"mailer"
],
"support": {
"issues": "https://github.com/swiftmailer/swiftmailer/issues",
"source": "https://github.com/swiftmailer/swiftmailer/tree/v6.2.7"
},
"funding": [
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/swiftmailer/swiftmailer",
"type": "tidelift"
}
],
"time": "2021-03-09T12:30:35+00:00"
},
{ {
"name": "symfony/console", "name": "symfony/console",
"version": "v5.2.6", "version": "v5.2.6",
@ -1217,6 +1192,393 @@
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2020-09-07T11:33:47+00:00" "time": "2020-09-07T11:33:47+00:00"
}, },
{
"name": "symfony/event-dispatcher",
"version": "v5.3.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "ce7b20d69c66a20939d8952b617506a44d102130"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ce7b20d69c66a20939d8952b617506a44d102130",
"reference": "ce7b20d69c66a20939d8952b617506a44d102130",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1",
"symfony/event-dispatcher-contracts": "^2",
"symfony/polyfill-php80": "^1.16"
},
"conflict": {
"symfony/dependency-injection": "<4.4"
},
"provide": {
"psr/event-dispatcher-implementation": "1.0",
"symfony/event-dispatcher-implementation": "2.0"
},
"require-dev": {
"psr/log": "^1|^2|^3",
"symfony/config": "^4.4|^5.0",
"symfony/dependency-injection": "^4.4|^5.0",
"symfony/error-handler": "^4.4|^5.0",
"symfony/expression-language": "^4.4|^5.0",
"symfony/http-foundation": "^4.4|^5.0",
"symfony/service-contracts": "^1.1|^2",
"symfony/stopwatch": "^4.4|^5.0"
},
"suggest": {
"symfony/dependency-injection": "",
"symfony/http-kernel": ""
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\EventDispatcher\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/event-dispatcher/tree/v5.3.7"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2021-08-04T21:20:46+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
"version": "v2.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher-contracts.git",
"reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/69fee1ad2332a7cbab3aca13591953da9cdb7a11",
"reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"psr/event-dispatcher": "^1"
},
"suggest": {
"symfony/event-dispatcher-implementation": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.4-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
"psr-4": {
"Symfony\\Contracts\\EventDispatcher\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Generic abstractions related to dispatching event",
"homepage": "https://symfony.com",
"keywords": [
"abstractions",
"contracts",
"decoupling",
"interfaces",
"interoperability",
"standards"
],
"support": {
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.4.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2021-03-23T23:28:01+00:00"
},
{
"name": "symfony/google-mailer",
"version": "v5.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/google-mailer.git",
"reference": "6b6aa67cc6a6c24adc88d98c23c642950effb739"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/google-mailer/zipball/6b6aa67cc6a6c24adc88d98c23c642950effb739",
"reference": "6b6aa67cc6a6c24adc88d98c23c642950effb739",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/mailer": "^4.4|^5.0"
},
"require-dev": {
"symfony/http-client": "^4.4|^5.0"
},
"type": "symfony-bridge",
"autoload": {
"psr-4": {
"Symfony\\Component\\Mailer\\Bridge\\Google\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Google Mailer Bridge",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/google-mailer/tree/v5.3.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2021-05-26T17:33:56+00:00"
},
{
"name": "symfony/mailer",
"version": "v5.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/mailer.git",
"reference": "c1f83da2296741110be35dd779f2a9e412cec466"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/mailer/zipball/c1f83da2296741110be35dd779f2a9e412cec466",
"reference": "c1f83da2296741110be35dd779f2a9e412cec466",
"shasum": ""
},
"require": {
"egulias/email-validator": "^2.1.10|^3",
"php": ">=7.2.5",
"psr/log": "^1|^2|^3",
"symfony/deprecation-contracts": "^2.1",
"symfony/event-dispatcher": "^4.4|^5.0",
"symfony/mime": "^5.2.6",
"symfony/polyfill-php80": "^1.16",
"symfony/service-contracts": "^1.1|^2"
},
"conflict": {
"symfony/http-kernel": "<4.4"
},
"require-dev": {
"symfony/http-client-contracts": "^1.1|^2",
"symfony/messenger": "^4.4|^5.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Mailer\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Helps sending emails",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/mailer/tree/v5.3.4"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2021-07-23T15:55:36+00:00"
},
{
"name": "symfony/mime",
"version": "v5.3.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/mime.git",
"reference": "ae887cb3b044658676129f5e97aeb7e9eb69c2d8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/mime/zipball/ae887cb3b044658676129f5e97aeb7e9eb69c2d8",
"reference": "ae887cb3b044658676129f5e97aeb7e9eb69c2d8",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1",
"symfony/polyfill-intl-idn": "^1.10",
"symfony/polyfill-mbstring": "^1.0",
"symfony/polyfill-php80": "^1.16"
},
"conflict": {
"egulias/email-validator": "~3.0.0",
"phpdocumentor/reflection-docblock": "<3.2.2",
"phpdocumentor/type-resolver": "<1.4.0",
"symfony/mailer": "<4.4"
},
"require-dev": {
"egulias/email-validator": "^2.1.10|^3.1",
"phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
"symfony/dependency-injection": "^4.4|^5.0",
"symfony/property-access": "^4.4|^5.1",
"symfony/property-info": "^4.4|^5.1",
"symfony/serializer": "^5.2"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Mime\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Allows manipulating MIME messages",
"homepage": "https://symfony.com",
"keywords": [
"mime",
"mime-type"
],
"support": {
"source": "https://github.com/symfony/mime/tree/v5.3.7"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2021-08-20T11:40:01+00:00"
},
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
"version": "v1.22.1", "version": "v1.22.1",
@ -1279,86 +1641,6 @@
], ],
"time": "2021-01-07T16:49:33+00:00" "time": "2021-01-07T16:49:33+00:00"
}, },
{
"name": "symfony/polyfill-iconv",
"version": "v1.23.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-iconv.git",
"reference": "63b5bb7db83e5673936d6e3b8b3e022ff6474933"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/63b5bb7db83e5673936d6e3b8b3e022ff6474933",
"reference": "63b5bb7db83e5673936d6e3b8b3e022ff6474933",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-iconv": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Iconv\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Iconv extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"iconv",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-iconv/tree/v1.23.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2021-05-27T09:27:20+00:00"
},
{ {
"name": "symfony/polyfill-intl-grapheme", "name": "symfony/polyfill-intl-grapheme",
"version": "v1.22.1", "version": "v1.22.1",
@ -4013,5 +4295,5 @@
"prefer-lowest": false, "prefer-lowest": false,
"platform": [], "platform": [],
"platform-dev": [], "platform-dev": [],
"plugin-api-version": "2.0.0" "plugin-api-version": "2.1.0"
} }

View File

@ -0,0 +1,10 @@
Hello [[name]],
I'm sending you the invoice for [[month]] [[year]].
This email and invoice were generated and send by rprt-cli.
Kind regards
[[me]]

View File

@ -17,6 +17,10 @@ export:
- 'Hours' - 'Hours'
- 'Rate' - 'Rate'
- 'Price' - 'Price'
email:
tokens:
me: 'Lio Novelli'
another_token: '1234567890'
projects: projects:
'<short name of first project>': '<short name of first project>':
name: '<Project long name>' name: '<Project long name>'

View File

@ -10,6 +10,8 @@ use RprtCli\Utils\CsvReport\CsvReport;
use RprtCli\Utils\CsvReport\CsvReportInterface; use RprtCli\Utils\CsvReport\CsvReportInterface;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use Mpdf\Mpdf; use Mpdf\Mpdf;
use RprtCli\Utils\Mailer\MailerInterface;
use RprtCli\Utils\Mailer\MailerService;
use RprtCli\Utils\PdfExport\PdfExportInterface; use RprtCli\Utils\PdfExport\PdfExportInterface;
use RprtCli\Utils\PdfExport\PdfExportService; use RprtCli\Utils\PdfExport\PdfExportService;
use RprtCli\Utils\TimeTrackingServices\YoutrackInterface; use RprtCli\Utils\TimeTrackingServices\YoutrackInterface;
@ -52,10 +54,21 @@ return [
get('config.service') get('config.service')
), ),
'csv.report' => get(CsvReportInterface::class), 'csv.report' => get(CsvReportInterface::class),
MailerInterface::class => get(MailerService::class),
MailerService::class => create()->constructor(
get('config.service'),
get('pdf_export.service')
),
'mailer' => get(MailerInterface::class),
RprtCommand::class => create()->constructor( RprtCommand::class => create()->constructor(
get('csv.report'), get('csv.report'),
get('config.service'), get('config.service'),
get('youtrack.service'), get('youtrack.service'),
get('pdf_export.service') get('pdf_export.service'),
get('mailer')
), ),
TrackCommand::class => create()->constructor(
get('config.service'),
get('youtrack.service')
)
]; ];

View File

@ -8,6 +8,7 @@ namespace RprtCli\Commands;
use RprtCli\Utils\Configuration\ConfigurationInterface; use RprtCli\Utils\Configuration\ConfigurationInterface;
use RprtCli\Utils\CsvReport\CsvReportInterface; use RprtCli\Utils\CsvReport\CsvReportInterface;
use RprtCli\Utils\Mailer\MailerInterface;
use RprtCli\Utils\PdfExport\PdfExportInterface; use RprtCli\Utils\PdfExport\PdfExportInterface;
use RprtCli\Utils\TimeTrackingServices\YoutrackInterface; use RprtCli\Utils\TimeTrackingServices\YoutrackInterface;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
@ -38,12 +39,14 @@ class RprtCommand extends Command
ConfigurationInterface $configuration, ConfigurationInterface $configuration,
YoutrackInterface $youtrack, YoutrackInterface $youtrack,
PdfExportInterface $pdf_export, PdfExportInterface $pdf_export,
MailerInterface $mailer,
?string $name = null ?string $name = null
) { ) {
$this->csv = $csv; $this->csv = $csv;
$this->configuration = $configuration; $this->configuration = $configuration;
$this->youtrack = $youtrack; $this->youtrack = $youtrack;
$this->pdfExport = $pdf_export; $this->pdfExport = $pdf_export;
$this->mailer = $mailer;
parent::__construct($name); parent::__construct($name);
} }
@ -112,18 +115,19 @@ class RprtCommand extends Command
$table->render(); $table->render();
if ($pdf = $input->getOption('pdf')) { if ($pdf = $input->getOption('pdf')) {
$nice_data = $this->csv->arangeDataForPdfExport($data); $nice_data = $this->csv->arangeDataForDefaultPdfExport($data);
// @TODO method gatherTokens(); // @TODO method gatherTokens();
if ($output = $input->getOption('output')) { if ($output = $input->getOption('output')) {
$this->pdfExport->setOutput($output); $this->pdfExport->setOutput($output);
} }
$output_path = $this->pdfExport->fromDataToPdf($nice_data); $output_path = $this->pdfExport->fromDefaultDataToPdf($nice_data);
} }
// return Command::SUCCESS; // return Command::SUCCESS;
} }
if ($send = $input->getOption('send') && $output_path) { if ($send = $input->getOption('send') && $output_path) {
// Send email to configured address. // Send email to configured address.
$this->mailer->sendDefaultMail($output_path);
} }
// $this->dummyOutput($input, $output); // $this->dummyOutput($input, $output);

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace RprtCli\Commands;
use RprtCli\Utils\Configuration\ConfigurationInterface;
use RprtCli\Utils\TimeTrackingServices\YoutrackInterface;
use Symfony\Component\Console\Command\Command;
/**
* Track time from comfortableness of your terminal.
*
* Later connect this command to the Emacs and have your time tracked directly
* from orgmode.
*/
class TrackCommand extends Command {
protected $config;
protected $youtrack;
public function __construct(
ConfigurationInterface $config,
YoutrackInterface $youtrack,
?string $name = null
) {
$this->config = $config;
$this->youtrack = $youtrack;
parent::__construct($name);
}
protected function configure(): void {
$this->setName('youtrack');
$this->setDescription('Track time into your youtrack service');
$this->addUsage('rprt-cli youtrack --issue=[issue-name] --minutes=[minutes] --date=[days-ago] --description=[text] --work-type=[work-type]');
// Options or arguments? Technically they are arguments but default value could be provided by config.
// Options are more suitable.
}
}

View File

@ -75,7 +75,7 @@ class CsvReport implements CsvReportInterface
return []; return [];
} }
public function arangeDataForPdfExport(array $data): array { public function arangeDataForDefaultPdfExport(array $data): array {
[$rows, $totalHours, $totalPrice] = [[], 0, 0]; [$rows, $totalHours, $totalPrice] = [[], 0, 0];
$projectsConfig = $this->configurationService->get('projects'); $projectsConfig = $this->configurationService->get('projects');
$header = $this->configurationService->get('export.labels', null); $header = $this->configurationService->get('export.labels', null);

View File

@ -18,4 +18,13 @@ interface CsvReportInterface
* Project key as key and number of hours as value. * Project key as key and number of hours as value.
*/ */
public function getReportData(string $filePath) : array; public function getReportData(string $filePath) : array;
/**
* Data for default drunomics pdf export.
*
* @param array $data
* Parsed data from csv report.
*/
public function arangeDataForDefaultPdfExport(array $data): array;
} }

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace RprtCli\Utils\Mailer;
/**
* Methods for symfony (swift)mailer service.
*/
interface MailerInterface {
}

View File

@ -0,0 +1,146 @@
<?php
declare(strict_types=1);
// src/Utils/Mailer/MailerService.php
namespace RprtCli\Utils\Mailer;
use RprtCli\Utils\Configuration\ConfigurationInterface;
use RprtCli\Utils\PdfExport\PdfExportInterface;
use Symfony\Component\Mime\Email;
use \Exception;
use Symfony\Component\Mailer\Transport;
use Symfony\Component\Mailer\Mailer;
/**
* Send emails with invoices as attachments.
*
* https://symfony.com/doc/current/mailer.html
*/
class MailerService implements MailerInterface {
protected $config;
protected $pdf;
// Data properties.
protected $to;
protected $subject;
protected $text;
protected $attachment;
protected $templatePath;
protected $email;
public function __construct(ConfigurationInterface $config, PdfExportInterface $pdf) {
$this->config = $config;
$this->pdf = $pdf;
}
public function setRecipents(array $to): void
{
$this->to = $to;
}
public function setSubject(string $subject): void {
$this->subject = $subject;
}
public function setAttachment(string $path): void {
// @TODO - add some error handling.
$this->attachment = $path;
}
public function getProperty(string $property) {
// Only for simple value properies - string and numbers.
// from, to, subject.
if (!isset($this->{$property})) {
$value = $this->config->get('email.' . $property, FALSE);
if (!$value) {
$value = readline("Property {$property} is not configured. Enter value: ");
}
$this->{$property} = $value;
}
return $this->{$property};
}
public function sendMail(string $from, array $to, string $subject, string $text, array $attachment = []): void {
$email = new Email();
$email->from($from);
$email->to($to[0]);
// @TODO use twig for templates. Create new template service.
// https://github.com/symfony/mailer
$email->subject($subject);
$email->text($text);
if (!empty($attachment)) {
if (!isset($attachment['path'])) {
var_dump('Attachment path missing!');
}
else {
$email->attachFromPath($attachment['path'], $attachment['name'] ?? null, $attachment['type'] ?? null);
}
}
// Not sure whether it would be nicer to use class property instead of variable.
$transport = $this->getTransport();
$mailer = $this->getMailer($transport);
$mailer->send($email);
}
public function getTransport() {
// @TODO remove username and password from config.
$username = rawurlencode($this->getProperty('username'));
$password = rawurlencode($this->getProperty('password'));
// If your credentials contain special characters, you must URL-encode them.
$mailer_dsn = "gmail+smtp://{$username}:{$password}@default";
return Transport::fromDsn($mailer_dsn);
}
public function getMailer($transport) {
return new Mailer($transport);
}
public function sendDefaultMail(string $output): void {
$tokens = $this->pdf->gatherTokensForTemplate($this->getEmailTemplatePath(), FALSE, $this->getDefaultTokens(), 'email.tokens');
$text = $this->pdf->replaceTokensInTemplate($this->getEmailTemplatePath(), $tokens);
$this->sendMail(
$this->getProperty('from'),
$this->getProperty('to'),
$this->getProperty('subject'),
$text,
['path' => $output, 'Invoice', 'application/pdf']
);
}
public function getDefaultTokens(): array {
$tokens = [];
$date = strtotime('-1 month');
$tokens['month'] = date('F', $date);
$tokens['year'] = date('Y', $date);
return $tokens;
}
protected function getEmailTemplatePath(): ?string {
if (!isset($this->templatePath)) {
$template_path = $this->config->get('email.template_path', FALSE);
if (!$template_path) {
$template_path = readline('Enter template file path: ');
}
if (!file_exists($template_path)) {
throw new Exception('Template file not found!');
}
$this->templatePath = $template_path;
}
return $this->templatePath;
}
public function setEmailTemplatePath(string $path): void {
if (file_exists($path)) {
$this->templatePath = $path;
return;
}
throw new Exception('Email template file not found!');
}
}

View File

@ -41,4 +41,42 @@ interface PdfExportInterface {
*/ */
public function pdfExport(string $html) : bool; public function pdfExport(string $html) : bool;
/**
* Goes through the whole process of creating a pdf for the invoice.
*
* @param array $nice_data
* Parsed csv report export data.
*
* @return string
* Path of the pdf file.
*/
public function fromDefaultDataToPdf(array $nice_data, array $tokens = []): string;
/**
* Sets output file override via command paramater.
*
* @param string $output
* Path of the output pdf file ('/tmp/test.pdf').
*/
public function setOutput(string $output): void;
// @TODO support multiple templates by adding template id in config.
// @TODO implement twig.
/**
* Get tokens to replace in the template.
*
* @param string $template_path
* Path to the template file (long term plan is to support multiple templates).
* @param bool $skip_missing
* Just skip missing tokens.
* @param array $runtime_tokens
* Provide tokens at runtime of the application (not supported yet). @TODO
* @param string $config
* Config path to tokens.
*
* @return array
* Token keys and values array.
*/
public function gatherTokensForTemplate(string $template_path, bool $skip_missing, array $runtime_tokens = [], string $config = 'export.token'): array;
} }

View File

@ -22,8 +22,7 @@ class PdfExportService implements PdfExportInterface {
$this->mpdf = $mpdf; $this->mpdf = $mpdf;
} }
public function getTemplatePath(): ?string public function getTemplatePath(): ?string {
{
if (!isset($this->templatePath)) { if (!isset($this->templatePath)) {
$template_path = $this->config->get('export.template_path', FALSE); $template_path = $this->config->get('export.template_path', FALSE);
if (!$template_path) { if (!$template_path) {
@ -42,7 +41,7 @@ class PdfExportService implements PdfExportInterface {
$this->templatePath = $path; $this->templatePath = $path;
return; return;
} }
throw new Exception('Template file not found!'); throw new Exception('Invoice template file not found!');
} }
// @TODO move this method to CsvReport. // @TODO move this method to CsvReport.
@ -104,14 +103,16 @@ class PdfExportService implements PdfExportInterface {
* Just skip missing tokens. * Just skip missing tokens.
* @param array $runtime_tokens * @param array $runtime_tokens
* Provide tokens at runtime of the application (not supported yet). @TODO * Provide tokens at runtime of the application (not supported yet). @TODO
* @param string $config
* Config path to tokens.
* *
* @return array * @return array
* Token keys and values array. * Token keys and values array.
*/ */
public function gatherTokensForTemplate(string $template_path, bool $skip_missing = FALSE, array $runtime_tokens = []): array { public function gatherTokensForTemplate(string $template_path, bool $skip_missing = FALSE, array $runtime_tokens = [], string $config = 'export.tokens'): array {
list($tokens, $missing) = [[], []]; list($tokens, $missing) = [[], []];
$token_keys = $this->getTokensInTemplate(file_get_contents($template_path)); $token_keys = $this->getTokensInTemplate(file_get_contents($template_path));
$config_tokens = $this->config->get('export.tokens'); $config_tokens = $this->config->get($config);
foreach ($token_keys as $token_key) { foreach ($token_keys as $token_key) {
if (isset($runtime_tokens[$token_key])) { if (isset($runtime_tokens[$token_key])) {
$tokens[$token_key] = $runtime_tokens[$token_key]; $tokens[$token_key] = $runtime_tokens[$token_key];
@ -145,24 +146,28 @@ class PdfExportService implements PdfExportInterface {
return $this->output; return $this->output;
} }
$output = $this->config->get('export.output', NULL) ?? readline('Enter output file path: '); $output = $this->config->get('export.output', NULL) ?? readline('Enter output file path: ');
$output = str_replace('[[month]]', date('F'), $output); $date = strtotime("-1 month");
$output = str_replace('[[year]]', date('Y'), $output); $output = str_replace('[[month]]', date('F', $date), $output);
$output = str_replace('[[year]]', date('Y', $date), $output);
$this->output = $output; $this->output = $output;
return $output; return $output;
} }
public function setOutput(string $path) { public function setOutput(string $path): void {
$this->output = $path; $this->output = $path;
} }
public function fromDataToPdf(array $data, array $tokens = []): bool { public function fromDefaultDataToPdf(array $data, array $tokens = []): string {
$template_path = $this->getTemplatePath(); $template_path = $this->getTemplatePath();
$tokens = $this->defaultTokens(); $tokens = $this->defaultTokens();
$tokens['table'] = $this->parsedDataToHtmlTable($data); $tokens['table'] = $this->parsedDataToHtmlTable($data);
$tokens = $this->gatherTokensForTemplate($template_path, FALSE, $tokens); $tokens = $this->gatherTokensForTemplate($template_path, FALSE, $tokens);
$html = $this->replaceTokensInTemplate($template_path, $tokens); $html = $this->replaceTokensInTemplate($template_path, $tokens);
$success = $this->pdfExport($html); $success = $this->pdfExport($html);
return $success; if ($success) {
return $this->getOutput();
}
return '';
} }
/** /**

View File

@ -64,7 +64,7 @@ class YoutrackService implements YoutrackInterface
public function downloadReport(string $report_id): ?string public function downloadReport(string $report_id): ?string
{ {
$path = "youtrack/api/reports/$report_id/export/csv"; $path = "youtrack/api/reports/$report_id/export/csv";
$query = ['$top' => -1]; $query = ['\$top' => -1];
$yt_token = $this->getYtToken(); $yt_token = $this->getYtToken();
$headers = [ $headers = [
'Accept' => 'text/plain, */*', 'Accept' => 'text/plain, */*',