Dynamically attaching file to Contact Form 7 E-Mail - php

I'm working in WordPress with Contact Form 7. I'm dynamically creating a Word Document based on the users submitted data, and I want to attach that file to the e-mail that the user is sent from Contact Form 7.
To confirm, the file is created and saved in the correct location. My issue is definitely with attaching it to the e-mail from CF7.
I have the following code at the moment:
add_action('wpcf7_before_send_mail', 'cv_word_doc');
function cv_word_doc($WPCF7_ContactForm) {
// Check we're on the CV Writer (212)
if ( $WPCF7_ContactForm->id() === 212 ) {
//Get current form
$wpcf7 = WPCF7_ContactForm::get_current();
// get current SUBMISSION instance
$submission = WPCF7_Submission::get_instance();
if ($submission) {
// get submission data
$data = $submission->get_posted_data();
// nothing's here... do nothing...
if (empty($data))
return;
// collect info to add to word doc, removed for brevity...
// setup upload directory and name the file
$upload_dir = wp_upload_dir();
$upload_dir = $upload_dir['basedir'] . '/cv/';
$fileName = 'cv-' . str_replace(' ', '-', strtolower($firstName)) . '-' . str_replace(' ', '-', strtolower($lastName)) .'-'. time() .'.docx';
// PHPWord stuff, removed for brevity...
// save the doc
$objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'Word2007');
$objWriter->save($upload_dir . $fileName);
// add upload to e-mail
$submission->add_uploaded_file('docx', $upload_dir . $fileName);
// carry on with cf7
return $wpcf7;
}
}
}
Everything works up until $submission->add_uploaded_file('docx', $upload_dir . $fileName);.
There's not much documentation about, but I have read that I need to include something similar to:
add_filter( 'wpcf7_mail_components', 'mycustom_wpcf7_mail_components' );
function mycustom_wpcf7_mail_components( $components ) {
$components['attachments'][] = 'full path of your PDF file';
return $components;
}
(source: https://wordpress.stackexchange.com/questions/239579/attaching-a-pdf-to-contact-form-7-e-mail-via-functions-php)
In order to get the attachment to show. However, I don't know how I can get the specific file that I need, as the file is unique to each submission and all of the variables will be in a separate function.

Fixed this issue.
I added:
// make file variable global
global $CV_file;
$CV_file = $upload_dir . $fileName;
after $submission->add_uploaded_file('docx', $upload_dir . $fileName);
Then seperate to the add_action, I used the filter and referenced the global var:
add_filter( 'wpcf7_mail_components', 'mycustom_wpcf7_mail_components' );
function mycustom_wpcf7_mail_components( $components ) {
global $CV_file;
$components['attachments'][] = $CV_file;
return $components;
}

Here is the working solution:
add_filter('wpcf7_mail_components', 'custom_wpcf7_mail_components');
function custom_wpcf7_mail_components($components)
{
//Get current form
$wpcf7 = WPCF7_ContactForm::get_current();
$attachment_file_path = '';
//check the relevant form id
if ($wpcf7->id == '30830') {
// get current SUBMISSION instance
$submission = WPCF7_Submission::get_instance();
if ($submission) {
// get submission data
$data = $submission->get_posted_data();
// setup upload directory
$upload_dir = wp_upload_dir();
if (isset($data['file_name']) && !empty($data['file_name'])) {
/*
* Form hidden attachment file name Ex: 'ProRail_NA_Gen5.pdf'
* You can hard-code the file name or set file name to hidden form field using JavaScript
*/
$file_name = $data['file_name'];
//get upload base dir path Ex: {path}/html/app/uploads
$base_dir = $upload_dir['basedir'];
//file uploaded folder
$file_dir = 'download';
//set attachment full path
$attachment_file_path = $base_dir .'/'.$file_dir.'/'.$file_name;
//append new file to mail attachments
$components['attachments'][] = $attachment_file_path;
}
}
}
return $components;
}

I'll leave a full example using wpcf7_before_send_mail to create and store the PDF and wpcf7_mail_components to attach it to the email.
PDF created with FPDF.
<?php
/**
* Plugin Name: Create and attach PDF to CF7 email
* Author: brasofilo
* Plugin URL: https://stackoverflow.com/q/48189010/
*/
!defined('ABSPATH') && exit;
require_once('fpdf/fpdf.php');
add_action('plugins_loaded', array(SendFormAttachment::get_instance(), 'plugin_setup'));
class SendFormAttachment {
protected static $instance = NULL;
public $formID = 5067; # YOUR ID
public $theFile = false;
public function __construct() { }
public static function get_instance() {
NULL === self::$instance and self::$instance = new self;
return self::$instance;
}
public function plugin_setup() {
add_action('wpcf7_before_send_mail', function ( $contact_form, $abort, $submission ) {
if ($contact_form->id() == $this->formID) {
$posted_data = $submission->get_posted_data();
$uploads = wp_upload_dir();
$the_path = $uploads['basedir'] . '/cf7_pdf/';
$fname = $this->createPDF($posted_data, $the_path);
$this->theFile = $the_path . $fname;
}
}, 10, 3);
add_filter( 'wpcf7_mail_components', function( $components ) {
if( $this->theFile )
$components['attachments'][] = $this->theFile;
return $components;
});
}
public function createPDF($posted_data, $savepath) {
$pdf = new FPDF();
$pdf->AliasNbPages();
$pdf->AddPage();
$pdf->SetFont('Times','',12);
$pdf->SetCreator('example.com');
$pdf->SetAuthor('Author name', true);
$pdf->SetTitle('The title', true);
$pdf->SetSubject('The subject', true);
for($i=1;$i<=40;$i++)
$pdf->Cell(0,10,'Printing line number '.$i,0,1);
$filename = rand() . '_' . time() . '.pdf';
$pdf->Output('F', $savepath . $filename);
return $filename;
}
}

The original code would have worked up until V5.4 where add_uploaded_file was made private.
There is a replacement public function you can use to attach the file with the absolute file path:
$submission->add_extra_attachments( $absolute_file_path );
It may also work with paths relative to the wp_uploads folder.

Related

WordPress rename attachment file with working thumbnails

I'm trying to write a function to rename the attachment file, both on upload, but also after upload.
I've written a nice function that can do this, but it results in missing thumbnails in the WP dashboard. I suspect it's because the GUID becomes wrong, but WP has made it impossible to change that GUID now (I think), so I'm not sure what else to do. Hoping someone here can help.
This is what I have, based on everything I could find online.
add_action("add_attachment", "rename_attachment", 10, 1);
function rename_attachment($post_id) {
custom_attachment_rename($post_id, "My name filename here");
}
function custom_attachment_rename( $post_id, $new_filename = 'new filename' ){
// Get path info of orginal file
$og_path = get_attached_file($post_id);
$path_info = pathinfo($og_path);
// Santize filename
$safe_filename = wp_unique_filename($path_info['dirname'], $new_filename);
// Build out path to new file
$new_path = $path_info['dirname']. "/" . $safe_filename . "." .$path_info['extension'];
// Rename the file and update it's location in WP
rename($og_path, $new_path);
update_attached_file( $post_id, $new_path );
// URL to the new file
$new_url = wp_get_attachment_url($post_id);
// Update attachment data
$id = wp_update_post([
'ID' => $post_id,
'post_title' => $new_filename,
'guid' => $new_url // Doesn't seem to work
]);
// Try this to reset the GUID for the attachment. Doesn't seem to actually reset it.
// global $wpdb;
// $result = $wpdb->update($wpdb->posts, ['guid' => $new_url], ['ID' => $post_id]);
// Update all links to old "sizes" files, or create it for new upload.
$metadata = get_post_meta($post_id, '_wp_attachment_metadata', true);
if( empty($metadata) ) {
// New upload.
$data = wp_generate_attachment_metadata($post_id, $new_path);
//update_post_meta($post_id, '_wp_attachment_metadata', $data); // Tried this. Doesn't work.
wp_update_attachment_metadata($post_id, $data); // Also doesn't work
} else {
// Regenerating an existing image
// TODO loop through $metadata and update the filename and resave? Maybe just delete it and regenerate instead?
}
// TODO Update use of the old filename in post_content throughout site
}
These have been the helpful posts I've gone over so far.
https://wordpress.stackexchange.com/questions/166943/how-to-rename-an-image-attachment-filename-using-php
https://wordpress.stackexchange.com/questions/30313/change-attachment-filename
https://wordpress.stackexchange.com/a/221254/4562
The weird thing is, if I die at the end of this function, then it works. So I suspect something else in WP is overwriting the _wp_attachment_metadata for this attachment. That is why I suspect the GUID is the issue. Something else is looking up the attachment via GUID and find a URL to a file that no longer exists (as I changed the filename) and running wp_generate_attachment_metadata on a bad file path. That is my hunch.
I don't have any other plugins installed.
The reason your code doesn't work is not related to the GUID.
It is because, in WP core, the function wp_update_attachment_metadata() is called with the original file name at the end of the media upload request handling. And the hook add_attachment is called inside wp_insert_attachment() function.
function media_handle_upload( $file_id, $post_id, $post_data = array(), $overrides = array( 'test_form' => false ) ) {
... ...
// Save the data.
$attachment_id = wp_insert_attachment( $attachment, $file, $post_id, true );
if ( ! is_wp_error( $attachment_id ) ) {
// Set a custom header with the attachment_id.
// Used by the browser/client to resume creating image sub-sizes after a PHP fatal error.
if ( ! headers_sent() ) {
header( 'X-WP-Upload-Attachment-ID: ' . $attachment_id );
}
// The image sub-sizes are created during wp_generate_attachment_metadata().
// This is generally slow and may cause timeouts or out of memory errors.
wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file ) );
}
return $attachment_id;
}
We can use the "wp_update_attachment_metadata" filter to change the metadata with the new file name.
Please check the below code. I tested and it is working well.
class CustomAttachmentRename {
public $new_filename = 'custom file';
private $new_path;
function __construct () {
add_action("add_attachment", array($this, "rename_attachment"), 10, 1);
}
function rename_attachment($post_id) {
// Get path info of orginal file
$og_path = get_attached_file($post_id);
$path_info = pathinfo($og_path);
// Santize filename
$safe_filename = wp_unique_filename($path_info['dirname'], $this->new_filename);
// Build out path to new file
$this->new_path = $path_info['dirname']. "/" . $safe_filename . "." .$path_info['extension'];
// Rename the file and update it's location in WP
rename($og_path, $this->new_path);
update_attached_file( $post_id, $this->new_path );
// Register filter to update metadata.
add_filter('wp_update_attachment_metadata', array($this, 'custom_update_attachment_metadata'), 10, 2);
}
function custom_update_attachment_metadata($data, $post_id) {
return wp_generate_attachment_metadata($post_id, $this->new_path);
}
}
$customAttachmentRename = new CustomAttachmentRename();
$customAttachmentRename->new_filename = "test image" . time();
I turned #chengmin answer into a single function like this:
/**
* Renames a file. Will also regenerate all the thumbnails that go with the file.
* #SEE https://stackoverflow.com/questions/64990515/wordpress-rename-attachment-file-with-working-thumbnails
*
* #param string $post_id The WordPress post_id for the attachment
* #param string $new_file_name The filename (without extension) that you want to rename to
*/
function attachment_rename($post_id, $filename) {
// Get path info of orginal file
$og_url = wp_get_attachment_url($post_id);
$og_path = get_attached_file($post_id);
$path_info = pathinfo($og_path);
$og_meta = get_post_meta($post_id, '_wp_attachment_metadata', true);
// Santize filename
$safe_filename = wp_unique_filename($path_info['dirname'], $filename);
// Build out path to new file
$new_path = $path_info['dirname']. "/" . $safe_filename . "." .$path_info['extension'];
// Rename the file in the file system
rename($og_path, $new_path);
// Delete old image sizes if we have them
if( !empty($og_meta) ) {
delete_attachment_files($post_id);
}
// Now save new path to file in WP
update_attached_file( $post_id, $new_path );
// Register filter to update metadata
$new_data = wp_generate_attachment_metadata($post_id, $new_path);
return add_filter('wp_update_attachment_metadata', function($data, $post_id) use ($new_data) {
return $new_data;
}, 10, 2);
}
/**
* Delete all old image files, if they aren't used post_content anywhere.
* The dont-delete check isn't perfect, it will give a lot of false positives (keeping more files than it should), but it's best I could come up with.
* #SEE https://github.com/WordPress/WordPress/blob/f4cda1b62ffca52115e4b04d9d75047060d69e68/wp-includes/post.php#L5983
*
* #param string $post_id The WordPress post_id for the attachment
*/
function delete_attachment_files($post_id) {
$meta = wp_get_attachment_metadata( $post_id );
$backup_sizes = get_post_meta( $post_id, '_wp_attachment_backup_sizes', true );
$file = get_attached_file( $post_id );
$url = wp_get_attachment_url($post_id);
// Remove og image so it doesn't get deleted in wp_delete_attachment_files()
$meta['original_image'] = "";
// Check if image is used in a post somehwere
$url_without_extension = substr($url, 0 , (strrpos($url, ".")));
$args = [
"s" => $url_without_extension,
"posts_per_page" => 1,
"post_type" => "any"
];
$found = get_posts($args);
if( empty($found) ) {
return wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $file );
}
return false;
}

Email attachments from outside sources not working

I've recently created a page on our site where users can upload an image and email it to an email address set up specifically to keep the uploaded documents.
I've tested this myself and it works, with the attachments arriving in gmail as expected.
However, whenever someone from outside uses this feature the attachment in the email is unavailable, or not could not be loaded, when we try to open it.
The code is split between 2 files, a controller and a helper. Here's the code (For the sake of saving some space I've removed all error checks, but in the actual code they are all still in place and not picking up any errors whatsoever):
controller
$helper = [GET HELPER];
/** Upload the file to a temp location so that we can attach it to an email */
$uploader = new Varien_File_Uploader('filename');
$uploader->setAllowedExtensions(array(
'image/jpeg',
'image/jpg',
'image/png',
'application/pdf'
))
->setAllowRenameFiles(true)
->setFilesDispersion(false);
$path = $helper->getFileStorageLocation(); // Will store files in /tmp
if (!is_dir($path))
{
mkdir($path, 0775, true);
}
$uploader->save($path, $_FILES['filename']['name']);
$result = $helper->sendMail($_FILES['filename']['name']);
if ($result)
{
$uploadSuccess = true;
/** Remove the temp file */
unlink($path . DS . $_FILES['filename']['name']);
}
helper
/** Declare variables */
$order = Mage::getModel('sales/order')->load($orderId);
$file_incremented_id = $order->getIncrementId();
$copyTo = $this->getCopyTo();
$copyFrom = $this->getCopyFrom();
$subject = 'proof of upload for ' . $file_incremented_id;
$copyTo = explode(',', $copyTo);
$body = '<span>Please see attachment</span>';
$file = $this->getFileStorageLocation() . DS . $filename; // function receives filename from whatever is calling it
$attachment = file_get_contents($file);
$extension = pathinfo($file, PATHINFO_EXTENSION);
if (!$copyTo)
{
return false;
}
$mail = Mage::getModel('core/email_template');
$mail->setSenderName('Uploader');
$mail->setSenderEmail($copyFrom);
$mail->setTemplateSubject($subject);
$mail->setTemplateText($body);
$mail->getMail()->createAttachment(
$attachement,
Zend_Mime::TYPE_OCTETSTREAM,
Zend_Mime::DISPOSITION_ATTACHMENT,
Zend_Mime::ENCODING_BASE64,
$file_incremented_id . '.' . $extension // Set order number as file name
);
try
{
$mail->send($copyTo);
return true;
}
catch (Exception $e)
{
return false;
}
Can anyone see anything that might be causing the issue, or think of what it might be based on my explanation of the setup?
So the problem, in the end, was filesize. My fault for not posting the $_FILES variable.
I saw it a bit later and the variable had error = 1, meaning that the file's size was larger than what was allowed by the max_upload_filesize in the php.ini

PHP returning specific data from another class

PHP question on how to return a specific value from another class in a seperate file. So I have created a uploader class to handle and store files uploaded by users. But trying to connect the dots back to the orginal request which if for lets say a specific page - how can i pass a specific value back after I set the values in the other file... code example below.
Thanks Citti
//file 1 //store page request
public function store()
{
//pass the uploaded file to be uploaded and stored
$this->uploader->upload(RequestFile::file('assetfile'));
$page = new Page;
$page->user_id = $asset->user()->id;
$page->assetfile = $file->user_id; <--- ? see below
$page->save();
}
//file 2 //store physical file and create record in DB
class Uploader {
public function upload($file)
{
$extension = $file->getClientOriginalExtension();
$string = str_random(30);
$uploadName = $string . '.' . $extension;
$destinationPath = storage_path() . '/app/uploads/' . Request::user()->id;
$file->move($destinationPath, $uploadName);
$file = new File;
$file->user_id = $asset->user()->id;
$file->assetfile = $uploadId;
$file->save();
$return $file->user_id; ?!?
What you could try is session variables?
Be sure to start sessions on both file 1 and file 2 via:
session_start();
Now, in your upload function, you can declare a session variable
$_SESSION['specificValue'] = $valueSetInUpload;
Now, back on your first file you can access the variable inside of your store function with
$valueSetInUpload = $_SESSION['specificValue'];
I believe this is what you are asking, but I could be wrong.
To return values from a function, you use "return value" and you just have to store that to a variable or output it. Something like:
public function store() {
$fileId = $this->uploader->upload(RequestFile::file('assetfile'));
$page = new Page;
$page->user_id = $asset->user()->id;
$page->assetfile = $fileId;
$page->save();
}
//file 2 //store physical file and create record in DB
class Uploader {
public function upload($file) {
$extension = $file->getClientOriginalExtension();
$string = str_random(30);
$uploadName = $string . '.' . $extension;
$destinationPath = storage_path() . '/app/uploads/' . Request::user()->id;
$file->move($destinationPath, $uploadName);
$file = new File;
$file->user_id = $asset->user()->id;
$file->assetfile = $uploadId;
$file->save();
return $file->user_id;
}
}

How to add/set images on PHPOffice/PHPWord Template?

I have an instance of a template on PHPWord. Is it possible to replace or add an image? Something like a setImageValue?
$phpWord = new \PhpOffice\PhpWord\Template('a.docx');
$phpWord->setImageValue('IMAGE_PLACEHOLDER', 'a.jpg');
$phpWord->saveAs('b.docx');
Is something like this possible?
Following code is the updated version of the one from TotPeRo (thanks again for your code!), for last phpOffice (0.11) that has evolved a little
/**
* Set a new image
*
* #param string $search
* #param string $replace
*/
public function setImageValue($search, $replace)
{
// Sanity check
if (!file_exists($replace))
{
return;
}
// Delete current image
$this->zipClass->deleteName('word/media/' . $search);
// Add a new one
$this->zipClass->addFile($replace, 'word/media/' . $search);
}
Can be called with:
$document->setImageValue('image1.jpg', 'my_image.jpg');
If you want to add, remove or replace image in template.docx you can add this to your TemplateProcessor.php file, (it works for me with PhpWord 0.14.0 version):
1.
// add this in the class
protected $_rels;
protected $_types;
public function __construct($documentTemplate){
// add this line to this function
$this->_countRels=100;
}
2.
public function save()
{
//add this snippet to this function after $this->zipClass->addFromString('word/document.xml', $this->tempDocumentMainPart);
if($this->_rels!=""){
$this->zipClass->addFromString('word/_rels/document.xml.rels', $this->_rels);
}
if($this->_types!=""){
$this->zipClass->addFromString('[Content_Types].xml', $this->_types);
}
}
3. Add this function too:
public function setImg( $strKey, $img){
$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>';
$toAdd = $toAddImg = $toAddType = '';
$aSearch = array('RID', 'IMG');
$aSearchType = array('IMG', 'EXT');
$countrels=$this->_countRels++;
//I'm work for jpg files, if you are working with other images types -> Write conditions here
$imgExt = 'jpg';
$imgName = 'img' . $countrels . '.' . $imgExt;
$this->zipClass->deleteName('word/media/' . $imgName);
$this->zipClass->addFile($img['src'], 'word/media/' . $imgName);
$typeTmpl = '<Override PartName="/word/media/'.$imgName.'" ContentType="image/EXT"/>';
$rid = 'rId' . $countrels;
$countrels++;
list($w,$h) = getimagesize($img['src']);
if(isset($img['swh'])) //Image proportionally larger side
{
if($w<=$h)
{
$ht=(int)$img['swh'];
$ot=$w/$h;
$wh=(int)$img['swh']*$ot;
$wh=round($wh);
}
if($w>=$h)
{
$wh=(int)$img['swh'];
$ot=$h/$w;
$ht=(int)$img['swh']*$ot;
$ht=round($ht);
}
$w=$wh;
$h=$ht;
}
if(isset($img['size']))
{
$w = $img['size'][0];
$h = $img['size'][1];
}
$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->tempDocumentMainPart=str_replace('<w:t>' . $strKey . '</w:t>', $toAddImg, $this->tempDocumentMainPart);
//print $this->tempDocumentMainPart;
if($this->_rels=="")
{
$this->_rels=$this->zipClass->getFromName('word/_rels/document.xml.rels');
$this->_types=$this->zipClass->getFromName('[Content_Types].xml');
}
$this->_types = str_replace('</Types>', $toAddType, $this->_types) . '</Types>';
$this->_rels = str_replace('</Relationships>', $toAdd, $this->_rels) . '</Relationships>';
}
4. And this:
function limpiarString($str) {
return str_replace(
array('&', '<', '>', "\n"),
array('&', '<', '>', "\n" . '<w:br/>'),
$str
);
}
5. How to use:
//open your template
$templateProcessor = new \PhpOffice\PhpWord\TemplateProcessor('files/template.docx');
//add image to selector
$templateProcessor->setImg('selector',array('src' => 'image.jpg','swh'=>'200', 'size'=>array(0=>$width, 1=>$height));
// You can also clone row if you need
//$templateProcessor->cloneRow('NAME_IN_TEMPLATE', NUMBER_OF_TABLE_RECORDS);
$templateProcessor->cloneRow('SELECTOR', 4);
//save
header("Content-Disposition: attachment; filename='helloWord.docx'");
$templateProcessor->saveAs('php://output');
his is pretty much untested. but this is working for me (although mine is slightly different):
add the following function to PHPWord/Template.php :
public function save_image($id,$filepath,&$document=null) {
if(file_exists($filepath))
{
$this->_objZip->deleteName('word/media/'.$id);
$this->_objZip->addFile ($filepath,'word/media/'.$id);
//$document->setValue($id.'::width', "300px");
//$document->setValue($id.'::height', "300px");
}
}
create a document with an actual image to be used as a place holder (this solution don't allow setting the with & height of the image or multiple extensions).
unzip the the documnt and check for the file name in word/media folder. use this file name as the $id for the save_image function.
you can now use:
$document->save_image('image1',$image_path,$document);
I created some functions to extend Jerome's solution. The problem was that in order to change the picture we had to know it's reference name in the docx file. This could be difficult to find out for an average user (you have to "unZip" the docx, and find your picture manually).
My solution makes it possible to reference pictures by alt text (it needs to be in usual format: ${abc} )
so one doesn't have to know how word names the placeholder picture, the variable is enough.
Here's a link about how to add alt texts: http://accessproject.colostate.edu/udl/modules/word/tut_alt_text.php?display=pg_2
I only modified TemplateProcessor.php
First add this to the class:
/**
* Content of document rels (in XML format) of the temporary document.
*
* #var string
*/
private $temporaryDocumentRels;
The constructor function (public function __construct($documentTemplate)) needs to be extended at the end. Add this:
$this->temporaryDocumentRels = $this->zipClass->getFromName('word/_rels/document.xml.rels');
Now you can read stuff from document.xml.rels by using $this->temporaryDocumentRels
Keep Jerome's code:
/**
* Set a new image
*
* #param string $search
* #param string $replace
*/
public function setImageValue($search, $replace){
// Sanity check
if (!file_exists($replace))
{
return;
}
// Delete current image
$this->zipClass->deleteName('word/media/' . $search);
// Add a new one
$this->zipClass->addFile($replace, 'word/media/' . $search);
}
This function returns with the rId of the image you labeled:
/**
* Search for the labeled image's rId
*
* #param string $search
*/
public function seachImagerId($search){
if (substr($search, 0, 2) !== '${' && substr($search, -1) !== '}') {
$search = '${' . $search . '}';
}
$tagPos = strpos($this->temporaryDocumentMainPart, $search);
$rIdStart = strpos($this->temporaryDocumentMainPart, 'r:embed="',$tagPos)+9;
$rId=strstr(substr($this->temporaryDocumentMainPart, $rIdStart),'"', true);
return $rId;
}
And this returns the images filename, if you know it's rId:
/**
* Get img filename with it's rId
*
* #param string $rId
*/
public function getImgFileName($rId){
$tagPos = strpos($this->temporaryDocumentRels, $rId);
$fileNameStart = strpos($this->temporaryDocumentRels, 'Target="media/',$tagPos)+14;
$fileName=strstr(substr($this->temporaryDocumentRels, $fileNameStart),'"', true);
return $fileName;
}
The modifications in TemplateProcessor.php are done.
Now you can replace the image by calling this:
$templateProcessor->setImageValue($templateProcessor->getImgFileName($templateProcessor->seachImagerId("abc")),$replace);
You can also create a functionin TemplateProcessor.php that calls it like:
public function setImageValueAlt($searchAlt, $replace){
$this->setImageValue($this->getImgFileName($this->seachImagerId($searchAlt)),$replace);
}
I was looking for a solution to add an image. I read a lot of articles, I only found suitable solutions for older versions. Changing and finalized the decision to get the code.
Let us proceed to change the file TemplateProcessor.php
public function __construct($documentTemplate)
{
//add to this function
$this->_countRels=100; //start id for relationship between image and document.xml
}
public function save()
{
//add to this function after $this->zipClass->addFromString('word/document.xml', $this->tempDocumentMainPart);
if($this->_rels!="")
{
$this->zipClass->addFromString('word/_rels/document.xml.rels', $this->_rels);
}
if($this->_types!="")
{
$this->zipClass->addFromString('[Content_Types].xml', $this->_types);
}
}
//add function
public function setImg( $strKey, $img){
$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>';
$toAdd = $toAddImg = $toAddType = '';
$aSearch = array('RID', 'IMG');
$aSearchType = array('IMG', 'EXT');
$countrels=$this->_countRels++;
//I'm work for jpg files, if you are working with other images types -> Write conditions here
$imgExt = 'jpg';
$imgName = 'img' . $countrels . '.' . $imgExt;
$this->zipClass->deleteName('word/media/' . $imgName);
$this->zipClass->addFile($img['src'], 'word/media/' . $imgName);
$typeTmpl = '<Override PartName="/word/media/'.$imgName.'" ContentType="image/EXT"/>';
$rid = 'rId' . $countrels;
$countrels++;
list($w,$h) = getimagesize($img['src']);
if(isset($img['swh'])) //Image proportionally larger side
{
if($w<=$h)
{
$ht=(int)$img['swh'];
$ot=$w/$h;
$wh=(int)$img['swh']*$ot;
$wh=round($wh);
}
if($w>=$h)
{
$wh=(int)$img['swh'];
$ot=$h/$w;
$ht=(int)$img['swh']*$ot;
$ht=round($ht);
}
$w=$wh;
$h=$ht;
}
if(isset($img['size']))
{
$w = $img['size'][0];
$h = $img['size'][1];
}
$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->tempDocumentMainPart=str_replace('<w:t>' . $strKey . '</w:t>', $toAddImg, $this->tempDocumentMainPart);
//print $this->tempDocumentMainPart;
if($this->_rels=="")
{
$this->_rels=$this->zipClass->getFromName('word/_rels/document.xml.rels');
$this->_types=$this->zipClass->getFromName('[Content_Types].xml');
}
$this->_types = str_replace('</Types>', $toAddType, $this->_types) . '</Types>';
$this->_rels = str_replace('</Relationships>', $toAdd, $this->_rels) . '</Relationships>';
}
//add function
function limpiarString($str) {
return str_replace(
array('&', '<', '>', "\n"),
array('&', '<', '>', "\n" . '<w:br/>'),
$str
);
}
//HOW TO USE???
$templateProcessor = new \PhpOffice\PhpWord\TemplateProcessor('templ.docx');
//static zone
$templateProcessor->setValue('date', htmlspecialchars(date('d.m.Y G:i:s')));
//$templateProcessor->cloneRow('NAME_IN_TEMPLATE', NUMBER_OF_TABLE_RECORDS);
$templateProcessor->cloneRow('AVTOR', 3);
//variant 1
//dynamic zone
$templateProcessor->setValue('AVTOR#1', htmlspecialchars('Garry'));
$templateProcessor->setValue('NAME#1', htmlspecialchars('Black Horse'));
$templateProcessor->setValue('SIZES#1', htmlspecialchars('100x300'));
/*$img = array(
'src' => 'image.jpg',//path
'swh'=>'350',//Image proportionally larger side
'size'=>array(580, 280)
);*/
$templateProcessor->setImg('IMGD#1',array('src' => 'image.jpg','swh'=>'250'));
$templateProcessor->setValue('AVTOR#2', htmlspecialchars('Barry'));
$templateProcessor->setValue('NAME#2', htmlspecialchars('White Horse'));
$templateProcessor->setValue('SIZES#2', htmlspecialchars('200x500'));
$templateProcessor->setImg('IMGD#2',array('src' => 'image2.jpg','swh'=>'250'));
$templateProcessor->setValue('AVTOR#3', htmlspecialchars('Backer'));
$templateProcessor->setValue('NAME#3', htmlspecialchars('Another Side'));
$templateProcessor->setValue('SIZES#3', htmlspecialchars('120x430'));
$templateProcessor->setImg('IMGD#3',array('src' => 'image3.jpg','swh'=>'250'));
//variant 2
$templateProcessor->cloneRow('AVTOR', count($output['ID']));
for($i=0;$i<count($output['ID']);$i++)
{
$templateProcessor->setValue('AVTOR'.'#'.($i+1), htmlspecialchars($output['AVTOR'][$i]));
$templateProcessor->setValue('NAME'.'#'.($i+1), htmlspecialchars($output['PNAM'][$i]));
//GetImg($output['ID'][$i]) my function return image path
$templateProcessor->setImg('IMGD'.'#'.($i+1), array('src'=>GetImg($output['ID'][$i]),'swh'=>'250'));
}
//Save
$templateProcessor->saveAs('testTemplate.docx');
modify the name of the *.docx to *.zip
open the zip folder
view the "word/media/****" the imgage name is to replace name
for setImageValue($search, $replace)
$search must be equls $replace OR use $this->zipClass->renameName ($replace , $search);
Just a note for anyone reading this in 2020 and later, PhpWord now has setImageValue() method, docs can be found here: https://phpword.readthedocs.io/en/latest/templates-processing.html#setimagevalue
If you open your .docx template as a .zip archive, you will find all media files in the word/media/ folder, then you need to find your placeholder image and replace it. I did it in the following way:
$this->templateProcessor->zip()->pclzipAddFile($imageUrl, 'word/media/image1.png');
In this case, the image styles remain the same as in the template.
Thanks to all the marvellous answers in this post, finally I had came out with my solution of replacing image in phpword 1.0.0 version and would like to share my code here : (Most thanks goes to #csanády-bálint for the detail coding)
My issue,
how can I know the rId of the image?
how can I know image is jpg or png or others?
I found out the image will begin with naming pattern of image1, image2, image3, and so on, therefore, I modified the concept to instead of getting the image by rId, I switch to get the image by name and can just ignore the image extension as well at the same time.
I only modified TemplateProcessor.php
First add this to the class :
/**
* Content of document rels (in XML format) of the temporary document.
*
* #var string
*/
private $temporaryDocumentRels;
Next, add following to the end of the constructor function (public function __construct($documentTemplate))
$this->temporaryDocumentRels = $this->zipClass->getFromName($this->getRelationsName($this->getMainPartName()));
And finally, just add following new function to TemplaceProcessor.php
/**
* Replace image by name
*
* #param string $search
* #param mixed $replace
*/
public function replaceImage($search, $replace): void
{
$fileNameStart = strpos($this->temporaryDocumentRels, $search);
$fileName = strstr(substr($this->temporaryDocumentRels, $fileNameStart), '"', true);
$this->zipClass->addFromString("word/media/" . $fileName, $replace);
}
Last but not least, the usage:
$templateProcessor = new \PhpOffice\PhpWord\TemplateProcessor('files/template.docx');
// image in file, eg. image1, image2, and so on
$search = 'image1';
// replacement image, can be from global http as well,
// like https://legacy.gscdn.nl/archives/images/HassVivaCatFight.jpg
$replace = 'replacement_image.png';
$fileGetContents = file_get_contents($replace);
if ($fileGetContents !== false) {
$templateProcessor->replaceImage($search, $fileGetContents);
}
The image replace is done by following code :
$this->zipClass->addFromString("word/media/" . $fileName, $replace);
where I found the code in the description of the zip() class (public function zip())
To replace an image:
$templateProcessor->zip()->AddFromString("word/media/image1.jpg",
file_get_contents($file));
Hope it helps!

Add File Uploader to Joomla Admin Component

I made Joomla admin component according to Joomla guide - http://docs.joomla.org/Developing_a_Model-View-Controller_Component/2.5/Developing_a_Basic_Component
In that i need to have file uploader which let user to upload single file.
In administrator\components\com_invoicemanager\models\forms\invoicemanager.xml i have defined
<field name="invoice" type="file"/>
In the controller administrator\components\com_invoicemanager\controllers\invoicemanager.php im trying to retrieve that file like below. But its not working (can't retrieve file)
Where am i doing it wrong ?
How can i get file and save it on disk ?
class InvoiceManagerControllerInvoiceManager extends JControllerForm
{
function save(){
$file = JRequest::getVar( 'invoice', '', 'files', 'array' );
var_dump($file);
exit(0);
}
}
make sure that you have included enctype="multipart/form-data" in the form that the file is being submitting. This is a common mistake
/// Get the file data array from the request.
$file = JRequest::getVar( 'Filedata', '', 'files', 'array' );
/// Make the file name safe.
jimport('joomla.filesystem.file');
$file['name'] = JFile::makeSafe($file['name']);
/// Move the uploaded file into a permanent location.
if (isset( $file['name'] )) {
/// Make sure that the full file path is safe.
$filepath = JPath::clean( $somepath.'/'.strtolower( $file['name'] ) );
/// Move the uploaded file.
JFile::upload( $file['tmp_name'], $filepath );}
Think i found the solution :)
$file = JRequest::getVar('jform', null, 'files', 'array');
Saving part is mentioned here - http://docs.joomla.org/Secure_coding_guidelines
For uploading the file from your component, you need to write your code in the controller file and you can extend the save() method. check the code given below -
public function save($data = array(), $key = 'id')
{
// Neccesary libraries and variables
jimport('joomla.filesystem.file');
//Debugging
ini_set("display_error" , 1);
error_reporting(E_ALL);
// Get input object
$jinput = JFactory::getApplication()->input;
// Get posted data
$data = $jinput->get('jform', null, 'raw');
$file = $jinput->files->get('jform');
// renaming the file
$file_ext=explode('.',JFile::makeSafe($file['invoice']['name'])); // invoice - file handler name
$filename = round(microtime(true)) . '.' . strtolower(end($file_ext));
// Move the uploaded file into a permanent location.
if ( $filename != '' ) {
// Make sure that the full file path is safe.
$filepath = JPath::clean( JPATH_ROOT."/media/your_component_name/files/". $filename );
// Move the uploaded file.
if (JFile::upload( $file['invoice']['tmp_name'], $filepath )) {
echo "success :)";
} else {
echo "failed :(";
}
$data['name'] = $filename ; // getting file name
$data['path'] = $filepath ; // getting file path
$data['size'] = $file['invoice']['size'] ; // getting file size
}
JRequest::setVar('jform', $data, 'post');
$return = parent::save($data);
return $return;
}
Joomla 2.5 & 3 style:
$app = JFactory::getApplication();
$input = $app->input;
$file= $input->files->get('file');
if(isset($file['name']))
{
jimport('joomla.filesystem.file');
$file['name'] = strtolower(JFile::makeSafe($file['name']));
$fileRelativePath = '/pathToTheRightFolder/'.$file['name'];
$fileAbsolutePath = JPath::clean( JPATH_ROOT.$fileRelativePath);
JFile::upload( $file['tmp_name'], $fileAbsolutePath );
}
http://docs.joomla.org/How_to_use_the_filesystem_package
has a full upload sample.
Little sample where admin choose the file type or all, enter the users to access the form upload. Folder to upload files in Joomla directory or with absolute path. Only selected users access the form upload.

Categories