diff --git a/app/composer.json b/app/composer.json index 67466a3..42153a7 100644 --- a/app/composer.json +++ b/app/composer.json @@ -19,7 +19,8 @@ "php-di/php-di": "^6.3", "symfony/yaml": "^5.2", "mpdf/mpdf": "^8.0", - "swiftmailer/swiftmailer": "^6.2" + "symfony/mailer": "^5.3", + "symfony/google-mailer": "^5.3" }, "autoload": { "psr-4": { diff --git a/app/composer.lock b/app/composer.lock index 232e484..3291587 100644 --- a/app/composer.lock +++ b/app/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "cbb7d7281f9622e5b410185c54558b28", + "content-hash": "81050635de2f87c3f7f693ec8cb30645", "packages": [ { "name": "doctrine/lexer", @@ -764,6 +764,56 @@ ], "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", "version": "1.0.1", @@ -1012,81 +1062,6 @@ ], "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", "version": "v5.2.6", @@ -1217,6 +1192,393 @@ "homepage": "https://symfony.com", "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", "version": "v1.22.1", @@ -1279,86 +1641,6 @@ ], "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", "version": "v1.22.1", @@ -4013,5 +4295,5 @@ "prefer-lowest": false, "platform": [], "platform-dev": [], - "plugin-api-version": "2.0.0" + "plugin-api-version": "2.1.0" } diff --git a/app/config/email-template.txt b/app/config/email-template.txt new file mode 100644 index 0000000..2029323 --- /dev/null +++ b/app/config/email-template.txt @@ -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]] diff --git a/app/config/rprt.example.config.yaml b/app/config/rprt.example.config.yaml index 34d8d46..afc3585 100644 --- a/app/config/rprt.example.config.yaml +++ b/app/config/rprt.example.config.yaml @@ -17,6 +17,10 @@ export: - 'Hours' - 'Rate' - 'Price' +email: + tokens: + me: 'Lio Novelli' + another_token: '1234567890' projects: '': name: '' diff --git a/app/dependencies.php b/app/dependencies.php index df9929c..5d8c5e2 100644 --- a/app/dependencies.php +++ b/app/dependencies.php @@ -10,6 +10,8 @@ use RprtCli\Utils\CsvReport\CsvReport; use RprtCli\Utils\CsvReport\CsvReportInterface; use GuzzleHttp\Client; use Mpdf\Mpdf; +use RprtCli\Utils\Mailer\MailerInterface; +use RprtCli\Utils\Mailer\MailerService; use RprtCli\Utils\PdfExport\PdfExportInterface; use RprtCli\Utils\PdfExport\PdfExportService; use RprtCli\Utils\TimeTrackingServices\YoutrackInterface; @@ -52,10 +54,21 @@ return [ get('config.service') ), '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( get('csv.report'), get('config.service'), get('youtrack.service'), - get('pdf_export.service') + get('pdf_export.service'), + get('mailer') ), + TrackCommand::class => create()->constructor( + get('config.service'), + get('youtrack.service') + ) ]; diff --git a/app/src/Commands/RprtCommand.php b/app/src/Commands/RprtCommand.php index 0846d26..28c837c 100644 --- a/app/src/Commands/RprtCommand.php +++ b/app/src/Commands/RprtCommand.php @@ -8,6 +8,7 @@ namespace RprtCli\Commands; use RprtCli\Utils\Configuration\ConfigurationInterface; use RprtCli\Utils\CsvReport\CsvReportInterface; +use RprtCli\Utils\Mailer\MailerInterface; use RprtCli\Utils\PdfExport\PdfExportInterface; use RprtCli\Utils\TimeTrackingServices\YoutrackInterface; use Symfony\Component\Console\Command\Command; @@ -38,12 +39,14 @@ class RprtCommand extends Command ConfigurationInterface $configuration, YoutrackInterface $youtrack, PdfExportInterface $pdf_export, + MailerInterface $mailer, ?string $name = null ) { $this->csv = $csv; $this->configuration = $configuration; $this->youtrack = $youtrack; $this->pdfExport = $pdf_export; + $this->mailer = $mailer; parent::__construct($name); } @@ -112,18 +115,19 @@ class RprtCommand extends Command $table->render(); if ($pdf = $input->getOption('pdf')) { - $nice_data = $this->csv->arangeDataForPdfExport($data); + $nice_data = $this->csv->arangeDataForDefaultPdfExport($data); // @TODO method gatherTokens(); if ($output = $input->getOption('output')) { $this->pdfExport->setOutput($output); } - $output_path = $this->pdfExport->fromDataToPdf($nice_data); + $output_path = $this->pdfExport->fromDefaultDataToPdf($nice_data); } // return Command::SUCCESS; } if ($send = $input->getOption('send') && $output_path) { // Send email to configured address. + $this->mailer->sendDefaultMail($output_path); } // $this->dummyOutput($input, $output); diff --git a/app/src/Commands/TrackCommand.php b/app/src/Commands/TrackCommand.php new file mode 100644 index 0000000..08fae40 --- /dev/null +++ b/app/src/Commands/TrackCommand.php @@ -0,0 +1,43 @@ +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. + } + + + +} diff --git a/app/src/Utils/CsvReport/CsvReport.php b/app/src/Utils/CsvReport/CsvReport.php index 88bba6b..1bcf488 100644 --- a/app/src/Utils/CsvReport/CsvReport.php +++ b/app/src/Utils/CsvReport/CsvReport.php @@ -75,7 +75,7 @@ class CsvReport implements CsvReportInterface return []; } - public function arangeDataForPdfExport(array $data): array { + public function arangeDataForDefaultPdfExport(array $data): array { [$rows, $totalHours, $totalPrice] = [[], 0, 0]; $projectsConfig = $this->configurationService->get('projects'); $header = $this->configurationService->get('export.labels', null); diff --git a/app/src/Utils/CsvReport/CsvReportInterface.php b/app/src/Utils/CsvReport/CsvReportInterface.php index eff3e6a..3a6af84 100644 --- a/app/src/Utils/CsvReport/CsvReportInterface.php +++ b/app/src/Utils/CsvReport/CsvReportInterface.php @@ -18,4 +18,13 @@ interface CsvReportInterface * Project key as key and number of hours as value. */ 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; + } diff --git a/app/src/Utils/Mailer/MailerInterface.php b/app/src/Utils/Mailer/MailerInterface.php new file mode 100644 index 0000000..4361b7b --- /dev/null +++ b/app/src/Utils/Mailer/MailerInterface.php @@ -0,0 +1,12 @@ +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!'); + } + +} diff --git a/app/src/Utils/PdfExport/PdfExportInterface.php b/app/src/Utils/PdfExport/PdfExportInterface.php index bccbdd9..2e9026c 100644 --- a/app/src/Utils/PdfExport/PdfExportInterface.php +++ b/app/src/Utils/PdfExport/PdfExportInterface.php @@ -41,4 +41,42 @@ interface PdfExportInterface { */ 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; + } diff --git a/app/src/Utils/PdfExport/PdfExportService.php b/app/src/Utils/PdfExport/PdfExportService.php index 1f0256c..d6a5899 100644 --- a/app/src/Utils/PdfExport/PdfExportService.php +++ b/app/src/Utils/PdfExport/PdfExportService.php @@ -22,8 +22,7 @@ class PdfExportService implements PdfExportInterface { $this->mpdf = $mpdf; } - public function getTemplatePath(): ?string - { + public function getTemplatePath(): ?string { if (!isset($this->templatePath)) { $template_path = $this->config->get('export.template_path', FALSE); if (!$template_path) { @@ -42,7 +41,7 @@ class PdfExportService implements PdfExportInterface { $this->templatePath = $path; return; } - throw new Exception('Template file not found!'); + throw new Exception('Invoice template file not found!'); } // @TODO move this method to CsvReport. @@ -104,14 +103,16 @@ class PdfExportService implements PdfExportInterface { * 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 = 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) = [[], []]; $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) { if (isset($runtime_tokens[$token_key])) { $tokens[$token_key] = $runtime_tokens[$token_key]; @@ -145,24 +146,28 @@ class PdfExportService implements PdfExportInterface { return $this->output; } $output = $this->config->get('export.output', NULL) ?? readline('Enter output file path: '); - $output = str_replace('[[month]]', date('F'), $output); - $output = str_replace('[[year]]', date('Y'), $output); + $date = strtotime("-1 month"); + $output = str_replace('[[month]]', date('F', $date), $output); + $output = str_replace('[[year]]', date('Y', $date), $output); $this->output = $output; return $output; } - public function setOutput(string $path) { + public function setOutput(string $path): void { $this->output = $path; } - public function fromDataToPdf(array $data, array $tokens = []): bool { + public function fromDefaultDataToPdf(array $data, array $tokens = []): string { $template_path = $this->getTemplatePath(); $tokens = $this->defaultTokens(); $tokens['table'] = $this->parsedDataToHtmlTable($data); $tokens = $this->gatherTokensForTemplate($template_path, FALSE, $tokens); $html = $this->replaceTokensInTemplate($template_path, $tokens); $success = $this->pdfExport($html); - return $success; + if ($success) { + return $this->getOutput(); + } + return ''; } /** diff --git a/app/src/Utils/TimeTrackingServices/YoutrackService.php b/app/src/Utils/TimeTrackingServices/YoutrackService.php index c5a2fba..c5cc765 100644 --- a/app/src/Utils/TimeTrackingServices/YoutrackService.php +++ b/app/src/Utils/TimeTrackingServices/YoutrackService.php @@ -64,7 +64,7 @@ class YoutrackService implements YoutrackInterface public function downloadReport(string $report_id): ?string { $path = "youtrack/api/reports/$report_id/export/csv"; - $query = ['$top' => -1]; + $query = ['\$top' => -1]; $yt_token = $this->getYtToken(); $headers = [ 'Accept' => 'text/plain, */*',