Skip to content

Commit

Permalink
Added include method and better error handling (#18)
Browse files Browse the repository at this point in the history
* Added include method and better error handling

* Cleanup

* Update changelog

* Add docs for using api in include method

* Add mj-include support
  • Loading branch information
sjelfull authored May 10, 2022
1 parent 38c2ef2 commit cfaa9a5
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 39 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).

## 1.0.6 - 2021-03-17
### Added
- Added `include` method similar to Twig's `include` method so we can cache the MJML template once and then render the dynamic parts with Twig
- Added support for `<mj-include/>` tags for the CLI option
- Added `mjmlCliIncludesPath` config setting

### Changed
- Changed error handling to log more detailed error messages

## 1.0.5 - 2021-03-14
### Added
- Allow for optional CLI config settings (e.g. minify)
Expand Down
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,56 @@ Dynamic example with MJML CLI:

To use the API instead, swap `mjmlCli` with `mjml`.

### Caching

The above examples will be cached. If you are passing Twig variables, each output will however be unique, rendering the cache ineffective.

In this instance you probably would like to use the `include` method:

```twig
{{ craft.mjml.include('path/to/template.twig', {
subject: 'Static subject',
email: contact.email,
}) }}
```

The `include` method uses the CLI option by default, but you can set it to use the MJML API by passing `api` as the third option, like so:

```twig
{{ craft.mjml.include('path/to/template.twig', {
subject: 'Static subject',
email: contact.email,
}, 'api') }}
```

Here is an example passing a contact in a [newsletter template inside the Campaign plugin](https://putyourlightson.com/plugins/campaign#mjml). The template path here is relative to your site templates root.

This will first render the MJML template once, cache it, then it will render the dynamic parts with Twig for each instance.

### Includes

A caveat if you want to use includes:

Twig's built-in `include` method won't work in combination with MJML inside the template passed to the plugin `include` method.

This is because MJML is rendered first, before Twig, so if you include MJML in a partial that won't be rendered.

#### Workaround for MJML includes

_Note that this is only supported for the CLI option._

A workaround for partials is to use the `<mj-include />` tag to. Any partials referenced here will be relative to the Site templates root.

```html
<mj-include path="./mjml-partial.twig" />
```

Note that you have to append the file extension here. This will resolve to `/templates/mjml-partial.twig`.

Another caveat with `mj-include` is that the content of partials isn't currently included when checking the cache of a rendered MJML template.

This means that if you render a MJML template that in turns has a `<mj-include />` partial, then changes the content of the partial, the cache will be stale and your template won't reflect the changes.

A workaround for now is to clear the `storage/runtime/temp/mjml` folder in case this happens to you.

Brought to you by [Superbig](https://superbig.co)
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "superbig/craft-mjml",
"description": "Render Twig emails with MJML, the only framework that makes responsive email easy.",
"type": "craft-plugin",
"version": "1.0.5",
"version": "1.0.6",
"keywords": [
"craft",
"cms",
Expand Down
12 changes: 0 additions & 12 deletions src/MJML.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,16 @@
*/
class MJML extends Plugin
{
// Static Properties
// =========================================================================

/**
* @var MJML
*/
public static $plugin;

// Public Properties
// =========================================================================

/**
* @var string
*/
public $schemaVersion = '1.0.0';

// Public Methods
// =========================================================================

/**
* @inheritdoc
*/
Expand Down Expand Up @@ -98,9 +89,6 @@ function(Event $event) {
);
}

// Protected Methods
// =========================================================================

/**
* @inheritdoc
*/
Expand Down
33 changes: 33 additions & 0 deletions src/exceptions/MJMLException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php
/**
* MJML plugin for Craft CMS 3.x
*
* Render Twig emails with MJML, the only framework that makes responsive email easy.
*
* @link https://superbig.co
* @copyright Copyright (c) 2018 Superbig
*/

namespace superbig\mjml\exceptions;

use yii\base\Exception;

/**
* @author Superbig
* @package MJML
* @since 1.0.0
*/
class MJMLException extends Exception
{
public $cliCommand = '';

public function setCliCommand(string $cmd)
{
$this->cliCommand = $cmd;
}

public function getCliCommand()
{
return $this->cliCommand;
}
}
13 changes: 11 additions & 2 deletions src/models/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ class Settings extends Model
*/
public $mjmlCliConfigArgs = '';

/**
* @var string
*/
public $mjmlCliIncludesPath = '';

/**
* @var string
*/
Expand All @@ -50,8 +55,12 @@ class Settings extends Model
*/
public $secretKey = '';

// Public Methods
// =========================================================================
public function init()
{
$this->mjmlCliIncludesPath = Craft::$app->getView()->getTemplatesPath();

parent::init();
}

/**
* @inheritdoc
Expand Down
83 changes: 60 additions & 23 deletions src/services/MJMLService.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
use craft\helpers\FileHelper;
use craft\helpers\Json;
use craft\helpers\Template;
use craft\web\View;
use GuzzleHttp\Client;
use mikehaertl\shellcommand\Command;
use superbig\mjml\MJML;

use Craft;
use craft\base\Component;
use superbig\mjml\exceptions\MJMLException;
use superbig\mjml\models\MJMLModel;

/**
Expand All @@ -39,10 +41,10 @@ class MJMLService extends Component
public function parse($html)
{
$settings = MJML::$plugin->getSettings();
$hash = md5($html);
$client = new Client([
$hash = md5($html);
$client = new Client([
'base_uri' => 'https://api.mjml.io/v1/',
'auth' => [$settings->appId, $settings->secretKey],
'auth' => [$settings->appId, $settings->secretKey],
]);

try {
Expand Down Expand Up @@ -73,32 +75,70 @@ public function parse($html)
}
}

public function include(string $template = '', $variables = [], $renderMethod = 'cli')
{
try {
$templatePath = Craft::$app->getView()->resolveTemplate($template, View::TEMPLATE_MODE_SITE);

if (!$templatePath) {
throw new MJMLException('Could not find template: ' . $template);
}

$html = file_get_contents($templatePath);
$hash = md5($html);
/** @var MJMLModel|null $output */
$output = Craft::$app->getCache()->getOrSet("mjml-{$hash}-{$renderMethod}", function() use ($html, $renderMethod) {
return $renderMethod === 'cli' ? $this->parseCli($html) : $this->parse($html);
});

if (!$output) {
throw new MJMLException('Could not render template: ' . $template);
}

return Craft::$app->getView()->renderString($output->output(), $variables);
} catch (MJMLException $e) {
Craft::error('Could not generate output: ' . $e->getMessage(), 'mjml');
}
}

/**
* @param null $html
*
* @return MJMLModel
* @return MJMLModel|null
* @throws \yii\base\ErrorException
*/
public function parseCli($html = null)
{
$settings = MJML::$plugin->getSettings();
$mjmlPath = "{$settings->nodePath} {$settings->mjmlCliPath}";
$hash = md5($html);
$configArgs = "{$settings->mjmlCliConfigArgs}";
$tempPath = Craft::$app->getPath()->getTempPath() . "/mjml/mjml-{$hash}.html";
$settings = MJML::$plugin->getSettings();
$configArgs = "{$settings->mjmlCliConfigArgs}";

if (!empty($settings->mjmlCliIncludesPath)) {
$configArgs = "{$configArgs} --config.filePath {$settings->mjmlCliIncludesPath}";
}

$mjmlPath = "{$settings->nodePath} {$settings->mjmlCliPath}";
$hash = md5($html);
$tempPath = Craft::$app->getPath()->getTempPath() . "/mjml/mjml-{$hash}.html";
$tempOutputPath = Craft::$app->getPath()->getTempPath() . "/mjml/mjml-output-{$hash}.html";

if (file_exists($tempOutputPath) == false or Craft::$app->request->getIsPreview()) {
FileHelper::writeToFile($tempPath, $html);
try {
if (!file_exists($tempOutputPath)) {
FileHelper::writeToFile($tempPath, $html);

$cmd = "$mjmlPath $tempPath $configArgs -o $tempOutputPath";
$cmd = "$mjmlPath $tempPath $configArgs -o $tempOutputPath";

$message = $this->executeShellCommand($cmd);

// Log Cli output if in devMode
if(Craft::$app->getConfig()->general->devMode){
Craft::info($message, 'mjml');
$this->executeShellCommand($cmd);
}
} catch (MJMLException $e) {
Craft::error('Could not generate output: ' . $e->getMessage(), 'mjml');

return null;
}

if (!file_exists($tempOutputPath)) {
Craft::error('Could not find generated output: ' . $tempOutputPath, 'mjml');

return null;
}

$output = file_get_contents($tempOutputPath);
Expand Down Expand Up @@ -132,13 +172,10 @@ protected function executeShellCommand(string $command): string
}

// Return the result of the command's output or error
if ($shellCommand->execute()) {
$result = $shellCommand->getOutput();
}
else {
$result = $shellCommand->getError();
if (!$shellCommand->execute()) {
throw new MJMLException("Failed to run {$command}: " . $shellCommand->getError());
}

return $result;
return $shellCommand->getOutput();
}
}
13 changes: 12 additions & 1 deletion src/variables/MJMLVariable.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

namespace superbig\mjml\variables;

use craft\helpers\Template;
use superbig\mjml\MJML;

use Craft;
Expand All @@ -26,7 +27,7 @@ class MJMLVariable
// =========================================================================

/**
* @param null $html
* @param null|string $html
*
* @return MJMLModel|null
*/
Expand All @@ -35,6 +36,16 @@ public function parse($html = null)
return MJML::$plugin->mjmlService->parse($html);
}

/**
* @param string $template
*
* @return MJMLModel|null
*/
public function include(string $template = '', $variables = null, $renderMethod = 'cli')
{
return Template::raw(MJML::$plugin->mjmlService->include($template, $variables, $renderMethod));
}

/**
* @param null $html
*
Expand Down

0 comments on commit cfaa9a5

Please sign in to comment.