Skip to content
Snippets Groups Projects
Commit f410029f authored by Kevin Kaland's avatar Kevin Kaland
Browse files

Issue #1266174: Add enough Rules integration for e-mail attachment.

Way too much to list here. See http://drupal.org/node/1266174.
parent d07ac337
No related branches found
No related tags found
No related merge requests found
...@@ -227,28 +227,34 @@ function fillpdf_parse_uri() { ...@@ -227,28 +227,34 @@ function fillpdf_parse_uri() {
* @param $flatten * @param $flatten
* Boolean. If TRUE, flatten the PDF so that fields cannot be edited. * Boolean. If TRUE, flatten the PDF so that fields cannot be edited.
* Otherwise leave fields editable. * Otherwise leave fields editable.
* @param $handle
* Boolean. If TRUE, handle the PDF, which usually consists of sending it to
* the users's browser or saving it as a file.
* *
* @return * @return
* Nothing. * When $handle is FALSE, this function returns the variable it would have
* used to invoke hook_fillpdf_merge_pre_handle().
*
* When $handle is TRUE, it returns nothing.
* *
* @see fillpdf_pdf_link() * @see fillpdf_pdf_link()
* for $_GET params * for $_GET params
*/ */
function fillpdf_merge_pdf($fid, $nids = NULL, $webform_arr = NULL, $sample = NULL, $force_download = FALSE, $skip_access_check = FALSE, $flatten = TRUE) { function fillpdf_merge_pdf($fid, $nids = NULL, $webform_arr = NULL, $sample = NULL, $force_download = FALSE, $skip_access_check = FALSE, $flatten = TRUE, $handle = TRUE) {
// Case 1: No $fid // Case 1: No $fid
if (is_null($fid)) { if (is_null($fid)) {
drupal_set_message(t('Fill PDF Form ID required to print a PDF.'), 'warning'); drupal_set_message(t('Fill PDF Form ID required to print a PDF.'), 'warning');
drupal_goto(); drupal_goto();
} }
$fillpdf_info = db_query("SELECT title, default_nid, url, destination_path, replacements, destination_redirect FROM {fillpdf_forms} WHERE fid = :fid", array(':fid' => $fid))->fetch(); $fillpdf_info = fillpdf_load($fid);
// Case 1.5: $fid is not valid. // Case 1.5: $fid is not valid.
if ($fillpdf_info === FALSE) { if ($fillpdf_info === FALSE) {
drupal_set_message(t('Non-existent Fill PDF Form ID.'), 'error'); drupal_set_message(t('Non-existent Fill PDF Form ID.'), 'error');
drupal_not_found(); drupal_not_found();
drupal_exit(); drupal_exit();
} }
$fillpdf_info->replacements = _fillpdf_replacements_to_array($fillpdf_info->replacements);
global $user; global $user;
...@@ -462,7 +468,7 @@ function fillpdf_merge_pdf($fid, $nids = NULL, $webform_arr = NULL, $sample = NU ...@@ -462,7 +468,7 @@ function fillpdf_merge_pdf($fid, $nids = NULL, $webform_arr = NULL, $sample = NU
)); ));
} }
// Allow modules to step in here and change the way the PDF is handled // Assemble some metadata that will be useful for the handling phase.
// @todo: Convert function parameters to use $options // @todo: Convert function parameters to use $options
// and add those into $fillpdf_info. // and add those into $fillpdf_info.
...@@ -474,72 +480,144 @@ function fillpdf_merge_pdf($fid, $nids = NULL, $webform_arr = NULL, $sample = NU ...@@ -474,72 +480,144 @@ function fillpdf_merge_pdf($fid, $nids = NULL, $webform_arr = NULL, $sample = NU
'nodes' => $nodes, 'nodes' => $nodes,
'webforms' => $webforms, 'webforms' => $webforms,
); );
$fillpdf_object->token_objects = $token_objects;
$fillpdf_object->options = array( $fillpdf_object->options = array(
'download' => $force_download, 'download' => $force_download,
'flatten' => $flatten, 'flatten' => $flatten,
); );
module_invoke_all('fillpdf_merge_pre_handle', $fillpdf);
if ($handle === TRUE) {
// Allow modules to step in here and change the way the PDF is handled
module_invoke_all('fillpdf_merge_pre_handle', $fillpdf_object);
// Perform the default action on the PDF - in other words, the one it was
// configured to do in the administrative area.
fillpdf_merge_handle_pdf($fillpdf_object->info, $fillpdf_object->data, $fillpdf_object->token_objects, 'default', $force_download);
}
// If not handling, then send back all the metadata to the caller.
else {
return $fillpdf_object;
}
}
/**
* Figure out what to do with the PDF and do it.
*
* @return Nothing.
* @param $pdf_info An object containing the loaded record from {fillpdf_forms}.
* @param $pdf_data A string containing the content of the merged PDF.
* @param $token_objects An array of objects to be used in replacing tokens.
* Here, specifically, it's for generating the filename of the handled PDF.
* @param $action One of the following keywords: default, download, save,
* redirect. These correspond to performing the configured action (from
* admin/structure/fillpdf/%), sending the PDF to the user's browser, saving it
* to a file, and saving it to a file and then redirecting the user's browser to
* the saved file.
* @param $force_download If set, this function will always end the request by
* sending the filled PDF to the user's browser.
*/
function fillpdf_merge_handle_pdf($pdf_info, $pdf_data, $token_objects, $action = 'download', $force_download = FALSE) {
if (in_array($action, array('default', 'download', 'save', 'redirect')) === FALSE) {
// Do nothing if the function is called with an invalid action.
return;
}
// Generate the filename of downloaded PDF from title of the PDF set in // Generate the filename of downloaded PDF from title of the PDF set in
// admin/structure/fillpdf/%fid // admin/structure/fillpdf/%fid
$output_name = _fillpdf_process_filename($fillpdf_info->title, $token_objects); $output_name = _fillpdf_process_filename($pdf_info->title, $token_objects);
if (!empty($fillpdf_info->destination_path)) { if ($action == 'default') {
$destination_path = _fillpdf_process_destination_path($fillpdf_info->destination_path, $token_objects); // Determine the default action, then re-set $action to that.
$path_exists = file_prepare_directory($destination_path, FILE_CREATE_DIRECTORY + FILE_MODIFY_PERMISSIONS); if (empty($pdf_info->destination_path) === FALSE) {
if ($path_exists === FALSE) { $action = 'save';
watchdog('fillpdf', "The path %destination_path does not exist and could not be
automatically created. Therefore, the previous submission was not saved. If
the URL contained download=1, then the PDF was still sent to the user's browser.
If you were redirecting them to the PDF, they were sent to the homepage instead.
If the destination path looks wrong and you have used tokens, check that you have
used the correct token and that it is available to Fill PDF at the time of PDF
generation.",
array('%destination_path' => $destination_path));
} }
else { else {
// Full steam ahead! $action = 'download';
$saved_file_path = file_unmanaged_save_data($data, $destination_path . "/$output_name", FILE_EXISTS_RENAME); }
if ($force_download === FALSE) { }
if (isset($_GET['destination']) === FALSE) {
// Should we send the user directly to the saved PDF? If so, do that. // Initialize variable containing whether or not we send the user's browser to
if ($fillpdf_info->destination_redirect) { // the saved PDF after saving it (if we are)
drupal_goto(file_create_url($saved_file_path)); $redirect_to_file = FALSE;
}
// Get a load of this switch...they all just fall through!
switch ($action) {
case 'redirect':
$redirect_to_file = $pdf_info->destination_redirect;
case 'save':
fillpdf_save_to_file($pdf_info, $pdf_data, $token_objects, $output_name, !$force_download, $redirect_to_file);
// Fill PDF classic!
case 'download':
drupal_add_http_header("Pragma", "public");
drupal_add_http_header('Expires', 0);
drupal_add_http_header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0');
drupal_add_http_header('Content-type', 'application-download');
// This must be strlen(), not drupal_strlen() because the length in bytes,
// not in characters, is what is needed here.
drupal_add_http_header('Content-Length', strlen($pdf_data));
drupal_add_http_header('Content-disposition', 'attachment; filename="' . $output_name . '"');
drupal_add_http_header('Content-Transfer-Encoding', 'binary');
echo $pdf_data;
drupal_exit();
break;
}
}
function fillpdf_save_to_file($pdf_info, $pdf_data, $token_objects, $output_name, $redirect = TRUE, $redirect_to_file = FALSE, $destination_path_override = NULL) {
if (isset($destination_path_override) && empty($destination_path_override) !== FALSE) {
$destination_path = $destination_path_override;
}
if (empty($pdf_info->destination_path) && empty($destination_path_override)) {
// If this function is called and the PDF isn't set up with a destination
// path, give it one.
$destination_path = 'fillpdf';
}
else {
$destination_path = $pdf_info->destination_path;
}
$destination_path = _fillpdf_process_destination_path($pdf_info->destination_path, $token_objects);
$path_exists = file_prepare_directory($destination_path, FILE_CREATE_DIRECTORY + FILE_MODIFY_PERMISSIONS);
if ($path_exists === FALSE) {
watchdog('fillpdf', "The path %destination_path does not exist and could not be
automatically created. Therefore, the previous submission was not saved. If
the URL contained download=1, then the PDF was still sent to the user's browser.
If you were redirecting them to the PDF, they were sent to the homepage instead.
If the destination path looks wrong and you have used tokens, check that you have
used the correct token and that it is available to Fill PDF at the time of PDF
generation.",
array('%destination_path' => $destination_path));
}
else {
// Full steam ahead!
$saved_file_path = file_unmanaged_save_data($pdf_data, $destination_path . "/$output_name", FILE_EXISTS_RENAME);
if ($redirect === TRUE) {
if (isset($_GET['destination']) === FALSE) {
// Should we send the user directly to the saved PDF? If so, do that.
if ($redirect_to_file) {
drupal_goto(file_create_url($saved_file_path));
} }
} }
} }
}
if ($force_download === FALSE) { if ($redirect === TRUE) {
// Allow the "destination" query string parameter to be used // Allow the "destination" query string parameter to be used
// e.g. fillpdf?nid=1&fid=1&destination=node/1 // e.g. fillpdf?nid=1&fid=1&destination=node/1
// If no destination is provided, drupal_goto() will send the // If no destination is provided, drupal_goto() will send the
// user to the front page. // user to the front page.
drupal_goto(); drupal_goto();
}
} }
drupal_add_http_header("Pragma", "public"); return $saved_file_path;
drupal_add_http_header('Expires', 0);
drupal_add_http_header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0');
drupal_add_http_header('Content-type', 'application-download');
// This must be strlen(), not drupal_strlen() because the length in bytes,
// not in characters, is what is needed here.
drupal_add_http_header('Content-Length', strlen($data));
drupal_add_http_header('Content-disposition', 'attachment; filename="' . $output_name . '"');
drupal_add_http_header('Content-Transfer-Encoding', 'binary');
echo $data;
drupal_exit();
} }
// @todo: Put the hooks together
// @todo: Document hooks
/** /**
* Implementation of fillpdf_merge_pre_handle(). * Implementation of fillpdf_merge_pre_handle().
* Set up the data then invoke the Rules event. * Set up the data then invoke the Rules event.
*/ */
function fillpdf_fillpdf_merge_pre_handle($fillpdf) { function fillpdf_fillpdf_merge_pre_handle($fillpdf) {
// @todo: Implement module_invoke_all() call in fillpdf_merge_pdf at
// appropriate place and implement Rules event invocation here.
rules_invoke_event('fillpdf_merge_pre_handle', $fillpdf); rules_invoke_event('fillpdf_merge_pre_handle', $fillpdf);
} }
...@@ -597,6 +675,10 @@ function _fillpdf_process_filename($original, $token_objects) { ...@@ -597,6 +675,10 @@ function _fillpdf_process_filename($original, $token_objects) {
return $output_name; return $output_name;
} }
function fillpdf_build_filename($original, $token_objects) {
return _fillpdf_process_filename($original, $token_objects);
}
/** /**
* Utility function to allow other functions to merge PDFs with the various methods in a consistent way. * Utility function to allow other functions to merge PDFs with the various methods in a consistent way.
* @param string $method The service or program being used. Possible values: local, remote, pdftk. Currently, only pdftk is supported. * @param string $method The service or program being used. Possible values: local, remote, pdftk. Currently, only pdftk is supported.
...@@ -845,19 +927,24 @@ function _fillpdf_process_destination_path($destination_path, $token_objects) { ...@@ -845,19 +927,24 @@ function _fillpdf_process_destination_path($destination_path, $token_objects) {
} }
function _fillpdf_replacements_to_array($replacements) { function _fillpdf_replacements_to_array($replacements) {
$standardized_replacements = str_replace(array("\r\n", "\r"), "\n", $replacements); if (empty($replacements) !== TRUE) {
$lines = explode("\n", $standardized_replacements); $standardized_replacements = str_replace(array("\r\n", "\r"), "\n", $replacements);
$return = array(); $lines = explode("\n", $standardized_replacements);
foreach ($lines as $replacement) { $return = array();
if (!empty($replacement)) { foreach ($lines as $replacement) {
$split = explode('|', $replacement); if (!empty($replacement)) {
if (count($split) == 2) { // Sometimes it isn't; don't know why. $split = explode('|', $replacement);
$return[$split[0]] = preg_replace('|<br />|', ' if (count($split) == 2) { // Sometimes it isn't; don't know why.
$return[$split[0]] = preg_replace('|<br />|', '
', $split[1]); ', $split[1]);
}
} }
} }
return $return;
}
else {
return array();
} }
return $return;
} }
/** /**
...@@ -881,3 +968,26 @@ function _fillpdf_transform_field_value($value, $pdf_replacements, $field_replac ...@@ -881,3 +968,26 @@ function _fillpdf_transform_field_value($value, $pdf_replacements, $field_replac
} }
} }
/**
* Whoa, a load function! Fill PDF is growing up!
*/
function fillpdf_load($fid, $reset = FALSE) {
static $fillpdf = array();
if (isset($fillpdf[$fid]) && $reset === FALSE) {
// I'm a placeholder if statement!
}
else {
$fillpdf[$fid] = db_query("SELECT * FROM {fillpdf_forms} WHERE fid = :fid", array(':fid' => $fid))->fetch();
}
if ($fillpdf[$fid]) {
// Turn replacements (textarea content) into an array.
$fillpdf[$fid]->replacements = _fillpdf_replacements_to_array($fillpdf[$fid]->replacements);
}
if ($fillpdf[$fid]) {
return $fillpdf[$fid];
}
else {
return FALSE;
}
}
...@@ -19,7 +19,13 @@ ...@@ -19,7 +19,13 @@
* See http://drupal.org/node/905632 * See http://drupal.org/node/905632
*/ */
function fillpdf_rules_data_info() { function fillpdf_rules_data_info() {
return array(); return array(
'fillpdf' => array(
'label' => t('Fill PDF metadata'),
'group' => t('Fill PDF'),
'property info' => _fillpdf_rules_metadata_info(),
),
);
} }
/** /**
...@@ -35,7 +41,7 @@ function fillpdf_rules_event_info() { ...@@ -35,7 +41,7 @@ function fillpdf_rules_event_info() {
'module' => 'fillpdf', 'module' => 'fillpdf',
); );
return array( return array(
'fillpdf_pre_handle' => $defaults + array( 'fillpdf_merge_pre_handle' => $defaults + array(
'label' => t('Filled PDF is about to be handled'), 'label' => t('Filled PDF is about to be handled'),
'variables' => array( 'variables' => array(
'fillpdf' => array('type' => 'fillpdf', 'label' => 'Fill PDF metadata'), 'fillpdf' => array('type' => 'fillpdf', 'label' => 'Fill PDF metadata'),
...@@ -47,14 +53,95 @@ function fillpdf_rules_event_info() { ...@@ -47,14 +53,95 @@ function fillpdf_rules_event_info() {
/** /**
* Implements hook_rules_action_info(). * Implements hook_rules_action_info().
* @todo: Define the following actions: * @todo: Define the following actions:
* - Fill a PDF * - Fill a PDF with data from content
* - Fill a PDF with data from Webform submissions
* - Send PDF to user's browser * - Send PDF to user's browser
* - Save PDF to a file * - Generate a Fill PDF link (saves new variable - could be useful for e-mail
* - Perform the default action on the PDF * templates and such)
*/ */
function fillpdf_rules_action_info() { function fillpdf_rules_action_info() {
// @todo: Define the "Save PDF to a file" rule $defaults = array(
return array(); 'group' => t('Fill PDF'),
);
return array(
'fillpdf_load' => $defaults + array(
'label' => t('Load a Fill PDF configuration'),
'base' => 'fillpdf_rules_action_load_fillpdf',
'provides' => array(
'fillpdf' => array(
'type' => 'fillpdf',
'label' => t('Fill PDF metadata'),
),
),
'parameter' => array(
'fid' => array(
'type' => 'integer',
'label' => t('Fill PDF Form ID'),
)
),
),
'fillpdf_merge_webform' => $defaults + array(
'label' => t('Fill a PDF with webform data'),
'base' => 'fillpdf_rules_action_merge_webform',
'description' => t('Populates the PDF with webform data and updates the
Rules variable with all information necessary to handle it.'),
'parameter' => array(
'fillpdf' => array(
'type' => 'fillpdf',
'label' => t('Fill PDF metadata'),
),
'webform_nid' => array(
'type' => 'integer',
'label' => t('Webform Node ID'),
'optional' => TRUE,
'description' => t('If you leave this blank, the <em>Default Node ID</em> from the Fill PDF configuration will be used.'),
),
'webform_sids' => array(
'type' => 'list<integer>',
'label' => t('Webform Submission ID(s)'),
'optional' => TRUE,
'description' => t('If you leave this blank, the most recent submission will be used. The last ID you specify will be checked first.'),
),
),
),
'fillpdf_handle_default' => $defaults + array(
'label' => t('Perform the default action on the PDF'),
'description' => t('Handle the PDF according to its Fill PDF configuration.'),
'base' => 'fillpdf_rules_action_handle_default',
'parameter' => array(
'fillpdf' => array(
'type' => 'fillpdf',
'label' => t('Fill PDF metadata'),
)
),
),
'fillpdf_save_to_file' => $defaults + array(
'label' => t('Save PDF to a file'),
'base' => 'fillpdf_rules_action_save_to_file',
'provides' => array(
'fillpdf_saved_file_path' => array(
'type' => 'text',
'label' => t('Path to saved PDF'),
),
),
'parameter' => array(
'fillpdf' => array(
'type' => 'fillpdf',
'label' => t('Fill PDF metadata'),
)
),
),
'fillpdf_delete_saved_file' => $defaults + array(
'label' => t('Delete saved PDF'),
'base' => 'fillpdf_rules_action_delete_file',
'parameter' => array(
'filename' => array(
'type' => 'text',
'label' => t('Filename of PDF to delete'),
),
),
)
);
} }
/** /**
...@@ -68,25 +155,68 @@ function fillpdf_rules_condition_info() { ...@@ -68,25 +155,68 @@ function fillpdf_rules_condition_info() {
} }
/** /**
* Implements hook_default_rules_configuration(). * *****************
* *Rules callbacks*
* *****************
*/ */
function fillpdf_rules_default_rules_configuration() {
$default_rules = array();
// @todo: Export the default rule once I create it via the UI and copy the
// code here.
// For attachment-based rules // Action callbacks //
if (module_exists('mimemail')) {
function fillpdf_rules_action_load_fillpdf($fid) {
$fillpdf = new stdClass;
$fillpdf->info = fillpdf_load($fid);
return array(
'fillpdf' => $fillpdf,
);
}
/**
* Populates a loaded Fill PDF configuration's PDF with data.
*/
function fillpdf_rules_action_merge_webform($fillpdf, $webform_nid = '', $webform_sids = array()) {
$skip_access_check = FALSE;
$flatten = TRUE;
$webforms = array();
foreach ($webform_sids as $sid) {
$webforms[] = array('nid' => $webform_nid,
'sid' => $sid,
);
}
if (empty($webforms) && empty($webform_nid) !== TRUE) {
$webforms[0]['nid'] = $webform_nid;
} }
return $default_rules;
// @todo: Parameterize $skip_access_check and $flatten in Rules.
$fillpdf = fillpdf_merge_pdf($fillpdf->info->fid, NULL, $webforms, NULL, FALSE, $skip_access_check, $flatten, FALSE);
return array('fillpdf' => $fillpdf);
} }
/** /**
* ***************** * Save the PDF to a file and return the file path.
* *Rules callbacks* */
* ***************** function fillpdf_rules_action_save_to_file($fillpdf) {
$saved_file_path = fillpdf_save_to_file($fillpdf->info, $fillpdf->data, $fillpdf->token_objects, _fillpdf_process_filename($fillpdf->info->title, $fillpdf->token_objects), FALSE);
return array(
'fillpdf_saved_file_path' => $saved_file_path,
);
}
/**
* Perform the default action on the PDF. This always ends in a drupal_goto() or a drupal_exit().
*/ */
function fillpdf_rules_action_handle_default($fillpdf) {
fillpdf_merge_handle_pdf($fillpdf->info, $fillpdf->data, $fillpdf->token_objects, 'default');
}
// @todo: Write these. function fillpdf_rules_action_delete_file($filename) {
file_unmanaged_delete($filename);
}
// Condition callbacks //
// Metadata callbacks //
function _fillpdf_rules_metadata_info() {
return array();
}
<?php
/**
* Implements hook_default_rules_configuration().
*/
function fillpdf_default_rules_configuration() {
$default_rules = array();
// Attach the filled PDF to an e-mail.
if (module_exists('mimemail') && module_exists('webform_rules') && module_exists('webform')) {
$default_rules['rules_webform_fillpdf_attachment'] = rules_import('{ "rules_send_webform_submission_confirmation_with_filled_pdf_as_attachment" : {
"LABEL" : "Send Webform submission confirmation e-mail with filled PDF as attachment",
"PLUGIN" : "reaction rule",
"ACTIVE" : false,
"REQUIRES" : [ "rules", "fillpdf", "mimemail", "webform_rules" ],
"ON" : [ "webform_rules_submit" ],
"IF" : [ { "data_is" : { "data" : [ "node:nid" ], "value" : "2" } } ],
"DO" : [
{ "fillpdf_load" : {
"USING" : { "fid" : "6" },
"PROVIDE" : { "fillpdf" : { "fillpdf" : "Fill PDF metadata" } }
}
},
{ "fillpdf_merge_webform" : {
"fillpdf" : [ "fillpdf" ],
"webform_nid" : "2",
"webform_sids" : { "value" : [] }
}
},
{ "fillpdf_save_to_file" : {
"USING" : { "fillpdf" : [ "fillpdf" ] },
"PROVIDE" : { "fillpdf_saved_file_path" : { "fillpdf_saved_file_path" : "Path to saved PDF" } }
}
},
{ "mimemail" : {
"to" : [ "site:mail" ],
"subject" : "Copy of completed PDF for your records",
"body" : "Thank you for filling out the form on our website. A copy of your completed PDF is attached.",
"plaintext" : " Thank you for filling out the form on our website. A copy of your completed PDF is attached.",
"attachments" : [ "fillpdf-saved-file-path" ]
}
},
{ "fillpdf_delete_saved_file" : { "filename" : [ "fillpdf-saved-file-path" ] } }
]
}
}');
}
return $default_rules;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment