Twig templating with Switch HTML Emails

Post Reply
User avatar
gabrielp
Advanced member
Posts: 645
Joined: Fri Aug 08, 2014 4:31 pm
Location: Boston
Contact:

Twig templating with Switch HTML Emails

Post by gabrielp » Mon Sep 29, 2014 7:59 pm

In reference to my other thread, I wanted to follow up with my solution for basic templating with Switch. Essentially, this will allow you to easily manage your HTML emails. So if you had, let's say, 20 different HTML emails and your boss tells you to swap out a logo, you can make the change in one place.



This system uses PHP and Twig (a templating library) to generate HTML which is then saved to .html files. You just run the PHP code once and it updates all of the files. If you make a change it will overwrite them with the same name, meaning you can use Git or some other method of version control to update everything -- and Switch will automatically pick up the changes without re-linking.



You'll need:

- PHP 5.2.4 or later

- Composer

- Twig



Setup

Download Composer using the manual download links. Put the composer.phar in your project directory.



Make a new text file called composer.json with the following contents:

{

"require": {

"twig/twig": "1.*"

}

}

Open up a command prompt. cd to your project directory and run php composer.phar install:





Now composer is going to do it's magic. Once it's complete, you are ready to use Twig.



Using Twig

Twig is a nice templating system written in PHP. I recommend you take a look at the documentation. Essentially, you can do loops, logic statements, including, and extending of template files. This is nice because we will want to include headers/footers or extend a base template. Here is how mine is setup:



In /views/ I created a directory called /Email/ which is the base theme path. There, I added a file called base.html.twig which has the HTML email I've decided to use. Within the main table HTML, I declare a block:

{% block body_cells %}

<td>Error! "body_cells" block not set.</td>

{% endblock %}

This block should always be overwritten when extended, so the default text should only be shown in the case that something goes wrong.

I then create two more templates, each "extending" base.html.twig



one_column.html.twig

{% extends 'Email/base.html.twig' %}



{% block body_cells %}

<td width="500" valign="top">

{% block column %}

{% endblock %}

</td>

{% endblock %}



two_column.html.twig



{% extends 'Email/base.html.twig' %}



{% block body_cells %}

<td valign="center" style="text-align:center;">

{% block left_column %}

{% endblock %}

</td>

<td width="250" valign="top" style="padding-left: 0;">

{% block right_column %}

{% endblock %}

</td>

{% endblock %}



Now we have a one column and a two column variant of the main template which inherits everything from base.html.twig.



Finally, we'll create the actual email -- complete with Switch variables. Going forward, this is all you have to make when you add a new email!



{% extends 'Email/two_column.html.twig' %}



{% block left_column %}

<img src="{{ dashboard_url }}/img/ico/misc/error.png">

<br>

Please contact the CSR who is CC'd on this email with any questions.

{% endblock %}



{% block right_column %}

<span style="font-size:16px;"><a href="#" target="_blank" style="color: #4d5fab; text-decoration: none;">[Job.PrivateData:Key=JobNumber] Proof CSR Rejection</a></span>

<br>

<span><i>Account: [Job.PrivateData:Key=CustomerName]</i></span>

<p>

Dear Prepress team,<br>

The CSR for this job has rejected a proof prior to it being sent to a client. Please address the CSR's concerns and drop a new proof into the proofing workflow as you did before.

<br><br>

<strong>Comments:</strong>

[Metadata.Text:Path="/field-list/field[1]/field-list/field/value",Dataset="Check",Model="XML"]

<br><br>

Description: [Job.PrivateData:Key=JobDescription]

</p>

{% endblock %}



You'll see that this email extends two_column.html.twig which in turn extends base.html.twig. Cool, huh?



Lastly, we need an app page to execute the code and make the files. Here is the quick and dirty page I have for now:

<?php

// Autoload

require_once '../vendor/autoload.php';



// Init twig

$loader = new Twig_Loader_Filesystem('../views/');

$twig = new Twig_Environment($loader);



// Need this for my template

$dashboard_url = 'https://uri.com';



// Render email and save to a variable

$problem_csr = $twig->render('Proof/problem_csr.html.twig', array('dashboard_url' => $dashboard_url));



// Function for outputting html files

function outputHtml($input, $fileNameProper)

{

$output_dir = '../build/';

$finalFilePath = $output_dir.$fileNameProper.'.html';

file_put_contents($finalFilePath, $input);

}



// Output

outputHtml($problem_csr, 'problem_csr');



Now loading app.php runs the code and outputs the HTML for each template and saves it to a .html file. Here is a screenshot of the HTML when run in a web browser so you can get an idea:





Now you can throw this in a Git repo and sync it with your Switch server. In the future, if you had to make a change to the email templates, you would just update base.html.twig, run app.php again, then commit to Git. Then, you would pull the commit on your Switch server and you've just updated every single email with your template change.
Free Switch scripts: open-automation @ GitHub
Free Switch apps: open-automation @ Enfocus appstore

If I've helped you and you'd like to say thanks, consider adding me on LinkedIn and leaving me an endorsement or recommendation. It helps a lot!

ArielRauch
Advanced member
Posts: 231
Joined: Thu Aug 07, 2014 10:04 am

Twig templating with Switch HTML Emails

Post by ArielRauch » Tue Sep 30, 2014 6:45 am

Very impressive!

Will try to get into it

Thank you, Gabriel

User avatar
gabrielp
Advanced member
Posts: 645
Joined: Fri Aug 08, 2014 4:31 pm
Location: Boston
Contact:

Twig templating with Switch HTML Emails

Post by gabrielp » Mon Nov 03, 2014 7:08 pm

I've converted all of my emails over to this templating method and I have to say, it's much easier to work with.



Here's an updated app.php page. This way, you just have to add a new element to $emails (lines 13-22) to add a new email to the list of what is generated:

<?php

// Autoload

require_once '../vendor/autoload.php';



// Init twig

$loader = new Twig_Loader_Filesystem('../views/');

$twig = new Twig_Environment($loader);



// Need this for my template

$dashboard_url = 'https://client.shawmutdelivers.com';



// Register emails which are generated

$emails = new stdClass();

$emails->proof_problem_csr = 'Proof/problem_csr.html.twig';

$emails->proof_problem_mailing = 'Proof/problem_mailing.html.twig';

$emails->proof_new_mailing = 'Proof/new_mailing.html.twig';

$emails->proof_new_csr = 'Proof/new_csr.html.twig';

$emails->proof_problem_client = 'Proof/problem_client.html.twig';

$emails->proof_approved_client = 'Proof/problem_client.html.twig';

$emails->proof_notify_client = 'Proof/approved_client_web.html.twig';

$emails->proof_error_jobnumber_not_found = 'Proof/error_jobnumber_not_found.html.twig';

$emails->proof_notify_shipper_hard_copy = 'Proof/notify_shipper_hard_copy.html.twig';



// Function for rendering and outputting html files

function outputHtml($twig, $template, $name, $parameters)

{

// Render in Twig

$rendered = $twig->render($template, $parameters);



// Build paths

$output_dir = '../build/';

$finalFilePath = $output_dir.$name.'.html';



// Check if file exists

if(file_exists($finalFilePath)){

$existingContents = file_get_contents($finalFilePath);

// Compare to new

if($existingContents == $rendered) return false;

}



// Write out if something changed

file_put_contents($finalFilePath, $rendered);



return true;

}



// Output action

foreach($emails as $name => $template)

{

$parameters = array();

$parameters['dashboard_url'] = $dashboard_url;

$outputResult = outputHtml($twig, $template, $name, $parameters);



if($outputResult){

echo "Built: '$name.html'<hr>";

} else {

echo "No change to '$template'<hr>";

}

}



Now when you run app.php, it will build any templates which have changed since the last revision, and will let you know which ones weren't updated:

Free Switch scripts: open-automation @ GitHub
Free Switch apps: open-automation @ Enfocus appstore

If I've helped you and you'd like to say thanks, consider adding me on LinkedIn and leaving me an endorsement or recommendation. It helps a lot!

User avatar
gabrielp
Advanced member
Posts: 645
Joined: Fri Aug 08, 2014 4:31 pm
Location: Boston
Contact:

Twig templating with Switch HTML Emails

Post by gabrielp » Fri Nov 14, 2014 6:24 pm

Made another update today by porting over to Silex. I'll make a repo of the bones of this at some point. I've become pretty reliant on this method of templating. Managing email templates any other way with a growing number of flows seems unmanagable.



Update composer.json

{

"require": {

"silex/silex": "~1.1",

"twig/twig": "1.*",

"twig/extensions": "*"

}

}



Then, run php composer.phar update



Updated app.php

<?php

// Autoload

require_once '../vendor/autoload.php';



$app = new SilexApplication();

$app['debug'] = true;



// Init twig

$loader = new Twig_Loader_Filesystem('../views/');

$twig = new Twig_Environment($loader, array('debug' => true));

$twig->addExtension(new Twig_Extension_Debug());



// Need this for my template

$dashboard_url = 'https://XXX.com';



// Register emails which are generated

$emails = new stdClass();

// Proofs

$emails->proof_problem_csr = 'Proof/problem_csr.html.twig';

$emails->proof_problem_mailing = 'Proof/problem_mailing.html.twig';

$emails->proof_new_mailing = 'Proof/new_mailing.html.twig';

$emails->proof_new_csr = 'Proof/new_csr.html.twig';

$emails->proof_problem_client = 'Proof/problem_client.html.twig';

$emails->proof_approved_client = 'Proof/problem_client.html.twig';

$emails->proof_notify_client = 'Proof/approved_client_web.html.twig';

$emails->proof_error_jobnumber_not_found = 'Proof/error_jobnumber_not_found.html.twig';

$emails->proof_notify_shipper_hard_copy = 'Proof/notify_shipper_hard_copy.html.twig';

// File Transfer

$emails->filetransfer_new_csr = 'FileTransfer/new_csr.html.twig';

$emails->filetransfer_new_unknown = 'FileTransfer/new_unknown.html.twig';

// Virtual Prepress

$emails->preflight_ready_csr = 'VirtualPrepress/preflight_ready_csr.html.twig';



// Function for rendering and outputting html files

function outputHtml($twig, $template, $name, $parameters)

{

// Render in Twig

$rendered = $twig->render($template, $parameters);



// Build paths

$output_dir = '../build/';

$finalFilePath = $output_dir.$name.'.html';



// Build return object

$return = new stdClass();

$return->name = $name;

$return->template = $template;

$return->build_path = $finalFilePath;

$return->type = 'updated';



// Check if file exists

if(file_exists($finalFilePath)){

// Get original filesize

$return->size = round(filesize($finalFilePath)/1024);

// Get the existing

$existingContents = file_get_contents($finalFilePath);

// Compare to new

if($existingContents == $rendered){

$return->type = 'no_change';

return $return;

};

} else {

$return->type = 'new';

}



// Write out if something changed

file_put_contents($finalFilePath, $rendered);



// Get updated filesize

$return->size = round(filesize($finalFilePath)/1024);



// Send successful return if a change was made

return $return;

}







$app->get('/', function () use ($emails, $dashboard_url, $twig) {



// Output action

$totalOutputResults = array();

foreach($emails as $name => $template)

{

$parameters = array();

$parameters['dashboard_url'] = $dashboard_url;

$outputResult = outputHtml($twig, $template, $name, $parameters);



array_push($totalOutputResults, $outputResult);

}



// Render in Twig

$rendered = $twig->render('Default/compile.html.twig', array("total_output_results" => $totalOutputResults));

return $rendered;

});



// Run the app

$app->run();



Defaultcompile.html.twig

{% extends 'base.html.twig' %}



{% block body %}



<div class="row">

<div class="col-md-4">

<h3>Emails</h3>

<div class="list-group">



{% for result in total_output_results %}

<a href="../{{ result.build_path }}" target="_blank" class="list-group-item {% spaceless %}

{% if result.type == 'new' %}list-group-item-success{% elseif result.type == 'updated' %}list-group-item-warning{% endif %}

{% endspaceless %}">

{% if result.type == 'new' %}<i class="fa fa-star-o"></i>{% elseif result.type == 'updated' %}<i class="fa fa-pencil-square-o"></i>{% else %}<i class="fa fa-circle-o"></i>{% endif %}

{{ result.name }}

<span class="badge pull-right">{{ result.size }} KB</span>

</a>

{% endfor %}



</div>

</div>

<div class="col-md-4">

<h3>Options</h3>

<a href="{{ app.request.uri }}" class="btn btn-default btn-lg"><i class="fa fa-refresh"></i> Rebuild</a>

</div>

</div>



{% endblock %}



Now the app will generate and in a nice way, display which templates have changed or are new. Each entry in the list is a link which brings you to the compiled html template.

Free Switch scripts: open-automation @ GitHub
Free Switch apps: open-automation @ Enfocus appstore

If I've helped you and you'd like to say thanks, consider adding me on LinkedIn and leaving me an endorsement or recommendation. It helps a lot!

User avatar
gabrielp
Advanced member
Posts: 645
Joined: Fri Aug 08, 2014 4:31 pm
Location: Boston
Contact:

Re: Twig templating with Switch HTML Emails

Post by gabrielp » Fri May 13, 2016 6:16 pm

Rebuilt using Node, Jade, and Gulp. Much simpler now.

https://github.com/dominickp/switch-email-generator
Free Switch scripts: open-automation @ GitHub
Free Switch apps: open-automation @ Enfocus appstore

If I've helped you and you'd like to say thanks, consider adding me on LinkedIn and leaving me an endorsement or recommendation. It helps a lot!

User avatar
gabrielp
Advanced member
Posts: 645
Joined: Fri Aug 08, 2014 4:31 pm
Location: Boston
Contact:

Re: Twig templating with Switch HTML Emails

Post by gabrielp » Tue May 31, 2016 5:22 pm

gabrielp wrote:Rebuilt using Node, Jade, and Gulp. Much simpler now.

https://github.com/dominickp/switch-email-generator
Jade converts quote characters to HTML entities which will mess with Switch variables containing quotes. To bypass this, use the != operator to escape raw HTML:

Code: Select all

 p
        span(style="font-size:14px;")="VPP Dataset"
        br
        strong="VPP Action: "
        !='[Job.PrivateData:Key="VPP Action"]'
Free Switch scripts: open-automation @ GitHub
Free Switch apps: open-automation @ Enfocus appstore

If I've helped you and you'd like to say thanks, consider adding me on LinkedIn and leaving me an endorsement or recommendation. It helps a lot!

Post Reply