Need your advice on how to make a mail with two attachments (one-time downloadable links) and send it using PHP.
For now, I have a code that sends only one link and it works fine. Actually how I've built my program is that I have two PHP files: url.php and get_file.php. In get_file.php I create a one-time downloadable URL to the document I want to send via email using url.php.
Here is a code of get_file.php:
<?php
/* Retrieve the given token: */
$token = $_GET['q'];
if( strlen($token)<32 )
{
die("Invalid token!");
}
/* Define the file for download: */
$secretfile = "file1.pdf";
/* This variable is used to determine if the token is valid or not: */
$valid = 0;
/* Define what file holds the ids. */
$file = "urls.txt";
/* Read the whole token-file into the variable $lines: */
$lines = file($file);
/* Truncate the token-file, and open it for writing: */
if( !($fd = fopen("urls.txt","w")) )
die("Could not open $file for writing!");
/* Aquire exclusive lock on $file. */
if( !(flock($fd,LOCK_EX)) )
die("Could not equire exclusive lock on $file!");
/* Loop through all tokens in the token-file: */
for( $i = 0; $i < count($lines); $i++ )
{
/* Is the current token the same as the one defined in $token? */
if( $token == rtrim($lines[$i]) )
{
$valid = 1;
}
/* The code below will only get executed if $token does NOT match the
current token in the token file. The result of this will be that
a valid token will not be written to the token file, and will
therefore only be valid once. */
else
{
fwrite($fd,$lines[$i]);
}
}
/* We're done writing to $file, so it's safe release the lock. */
if( !(flock($fd,LOCK_UN)) )
die("Could not release lock on $file!");
/* Save and close the token file: */
if( !(fclose($fd)) )
die("Could not close file pointer for $file!");
/* If there was a valid token in $token, output the secret file: */
if( $valid )
{
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($secretfile).'"';
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
/*header('Content-Length: ' . filesize($file));*/
readfile($secretfile);
}
else
{
print "Invalid URL!";
}
?>
Here is a code of url.php:
<?php
$token = md5(uniqid(rand(),1));
$file = "urls.txt";
if( !($fd = fopen($file,"a")) )
die("Could not open $file!");
if( !(flock($fd,LOCK_EX)) )
die("Could not aquire exclusive lock on $file!");
if( !(fwrite($fd,$token."\n")) )
die("Could not write to $file!");
if( !(flock($fd,LOCK_UN)) )
die("Could not release lock on $file!");
if( !(fclose($fd)) )
die("Could not close file pointer for $file!");
$cwd = substr($_SERVER['PHP_SELF'],0,strrpos($_SERVER['PHP_SELF'],"/"));
function clean_string($string) {
$bad = array("content-type","bcc:","to:","cc:","href");
return str_replace($bad,"",$string);
}
$a = "http://".$_SERVER['HTTP_HOST']."$cwd/get_file.php?q=$token";
$email_to = $_POST['email'];
$email_to_bcc = "mail#mail.com";
$email_from = "mail#mail.com";
$email_subject = "download link";
$email_message = "Thank you for your purchase.\n\n";
$comments = $a;
$email_message .= "Here is your one-time link to download the file:\n".clean_string($comments);
$headers = 'From: '.$email_from."\r\n" .
'BCC: '.$email_to_bcc."\r\n" .
'Reply-To: '.$email_from."\r\n" .
'X-Mailer: PHP/' . phpversion();
mail($email_to, $email_subject, $email_message, $headers);
?>
The question is whether in my get_file.php I can define two files for download, create two downloadable links and than create two attachments in the header?
i have found this answer ,
PHP Converting CSV to XLS - phpExcel error
but i have tried it in Laravel 4 and i am not able to get it to work , any help would be appreciated.
My Code
public function CsvExcelConverter($filename){
$objReader = Excel::createReader('CSV');
$objReader->setDelimiter(";");
$objPHPExcel = $objReader->load('uploads/'.$filename);
$objWriter = Excel::createWriter($objPHPExcel, 'Excel5');
//new file
$new_filename = explode('.',$filename);
$new_name = $new_filename[1];
$objWriter->save($new_name.'.xls');
return $new_name.'.xls';
}
thank for the answers, but for some reason we cant seem to set the delimiter on load but i have found that you can set it in the config file .
vendeor/maatwebsite/excel/src/config/csv.php
then just specify the delimiter. this way when loading the file it actually separates each entry and when converting it each entry is in its own cell.
thanks for all the help.
/* Get the excel.php class here: http://www.phpclasses.org/browse/package/1919.html */
require_once("../classes/excel.php");
$inputFile=$argv[1];
$xlsFile=$argv[2];
if( empty($inputFile) || empty($xlsFile) ) {
die("Usage: ". basename($argv[0]) . " in.csv out.xls\n" );
}
$fh = fopen( $inputFile, "r" );
if( !is_resource($fh) ) {
die("Error opening $inputFile\n" );
}
/* Assuming that first line is column headings */
if( ($columns = fgetcsv($fh, 1024, "\t")) == false ) {
print( "Error, couldn't get header row\n" );
exit(-2);
}
$numColumns = count($columns);
/* Now read each of the rows, and construct a
big Array that holds the data to be Excel-ified: */
$xlsArray = array();
$xlsArray[] = $columns;
while( ($rows = fgetcsv($fh, 1024, "\t")) != FALSE ) {
$rowArray = array();
for( $i=0; $i<$numColumns;$i++ ) {
$key = $columns[$i];
$val = $rows[$i];
$rowArray["$key"] = $val;
}
$xlsArray[] = $rowArray;
unset($rowArray);
}
fclose($fh);
/* Now let the excel class work its magic. excel.php
has registered a stream wrapper for "xlsfile:/"
and that's what triggers its 'magic': */
$xlsFile = "xlsfile://".$xlsFile;
$fOut = fopen( $xlsFile, "wb" );
if( !is_resource($fOut) ) {
die( "Error opening $xlsFile\n" );
}
fwrite($fOut, serialize($xlsArray));
fclose($fOut);
exit(0);
If you use the maatwebsite/excel library in Laravel, you can only use native PHPExcel instance methods, not static methods. To convert from CSV to excel, this code can be found at Documentation page
Excel::load($filename, function($file) {
// modify file content
})->setFileName($new_name)->store('xls');
In theory, you should create your custom class to set delimiter:
class CSVExcel extends Excel {
protected $delimiter = ';';
}
and now you could use:
CSVExcel::load('csvfilename.csv')->setFileName('newfilename')->export('xls');
But the problem is, that $delimiter isn't used in this case. Delimiter support seems to be added not long time ago, so maybe there is a bug or it needs to be used in the other way. I've added issue just in case for that: https://github.com/Maatwebsite/Laravel-Excel/issues/262
I am Using this simple hit counter script in my website. but its value resets to zero or changes to a lower value frequently (no specific time interval i noticed). is there any problem in scripts. please help ?
<?php
// SETUP YOUR COUNTER
// Detailed information found in the readme.htm file
// Count UNIQUE visitors ONLY? 1 = YES, 0 = NO
$count_unique = 1;
// Number of hours a visitor is considered as "unique"
$unique_hours = 1;
// Minimum number of digits shown (zero-padding). Set to 0 to disable.
$min_digits = 5;
#############################
# DO NOT EDIT BELOW #
#############################
/* Turn error notices off */
error_reporting(E_ALL ^ E_NOTICE);
/* Get page and log file names */
$page = input($_GET['page']) or die('ERROR: Missing page ID');
$logfile = 'logs/' . $page . '.txt';
/* Does the log exist? */
if (file_exists($logfile))
{
/* Get current count */
$count = intval(trim(file_get_contents($logfile))) or $count = 0;
$cname = 'tcount_unique_'.$page;
if ($count_unique==0 || !isset($_COOKIE[$cname]))
{
/* Increase the count by 1 */
$count = $count + 1;
$fp = #fopen($logfile,'w+') or die('ERROR: Can\'t write to the log file ('.$logfile.'), please make sure this file exists and is CHMOD to 666 (rw-rw-rw-)!');
flock($fp, LOCK_EX);
fputs($fp, $count);
flock($fp, LOCK_UN);
fclose($fp);
/* Print the Cookie and P3P compact privacy policy */
header('P3P: CP="NOI NID"');
setcookie($cname, 1, time()+60*60*$unique_hours);
}
/* Is zero-padding enabled? */
if ($min_digits > 0)
{
$count = sprintf('%0'.$min_digits.'s',$count);
}
/* Print out Javascript code and exit */
echo 'document.write(\''.$count.'\');';
exit();
}
else
{
die('ERROR: Invalid log file!');
}
/* This functin handles input parameters making sure nothing dangerous is passed in */
function input($in)
{
$out = htmlentities(stripslashes($in));
$out = str_replace(array('/','\\'), '', $out);
return $out;
}
?>
As far as my assumption, this reset occurs when there are simultaneous visits from users leading bad I/O of file, thus resetting it.
Solution:
Create a timely backup of txt file and compare values to publish the value from the file which has higher counter.
Switch to database!
i have a zip file and user can only access the file once they complete their payment and i am using paypal payment gateway,so i have applied the condition that the download link will only be visible to user once they have completed their transaction,but my requirement id this that user must not be able to open this zip file through url,but once they complete their transaction then the user can download the file from the link given by me 1.e
Download Zip, i have hosted my website on godaddy.
<?php
session_start();
if(isset($_SESSION['item_name']) && $_SESSION['item_name']!=''){
if($_SESSION['item_name']=='Android Apps'){
?>
Download Zip
<?php }
} else{
header('Location: abc.com/rrr/form1.html');
}
?>
use the following methods in your code. this way you don't need to redirect the user to the real url of zip file. you can serve the zip from any php script e.g. your payment confirmation page can redirect to a script with a random token which expires after first download/time-
//call this method for sending download file.
function sendDL($filename)
{
header("Content-length:".filesize($filename));
header('Content-Type: application/x-gzip'); // ZIP file
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="download.gz"');
header('Content-Transfer-Encoding: binary');
ob_end_clean(); //output buffer cleanup
_readfileChunked($filename);
}
function _readfileChunked($filename, $retbytes=true) {
$chunksize = 1*(1024*1024); // how many bytes per chunk
$buffer = '';
$cnt =0;
// $handle = fopen($filename, 'rb');
$handle = fopen($filename, 'rb');
if ($handle === false) {
return false;
}
while (!feof($handle)) {
$buffer = fread($handle, $chunksize);
echo $buffer;
ob_flush();
flush();
if ($retbytes) {
$cnt += strlen($buffer);
}
}
$status = fclose($handle);
if ($retbytes && $status) {
return $cnt; // return num. bytes delivered like readfile() does.
}
return $status;
}
consider these like utility methods. from any script which doesn't push any other out (ie. no echo) the first method can be used for sending a file to user. call that like this
<?php
$dlToken = $_GET['dltoken'];
$filename = '/path/to/secret.file';
//Check if this dlToken is in database/memcache or not. and if yes its expired or not.
if($yes)
{
sendDL($filename);
}
else
{
sendNack();
}
function sendNack()
{
echo '___DATA_NOT_FOUND___'; //NOTICE this is the only echo in this script. and it means we are not sending the file after all.
//header("HTTP/1.0 404 Not Found");
exit();
}
//put the two methods there
function sendDL($filename)
{
//...
}
function _readfileChunked($filename, $retbytes=true)
{
//...
}
?>
At the page/script where you give the download link. generate a random unique token. you can use uniqid or mt_rand or both. save this in database along with a timestamp value (which you can use in download script mentioned above to check if the token has expired). create a download url with that token as something like following
download.php?file=test.zip&token=the_unique_token×tamp=unix_timestamp
I have some PHP code that runs a query on a database, saves the results to a csv file, and then allows the user to download the file. The problem is, the csv file contains page HTML around the actual csv content.
I've read all the related questions here already, including this one. Unfortunately my code exists within Joomla, so even if I try to redirect to a page that contains nothing but headers, Joomla automatically surrounds it with its own navigation code. This only happens at the time of download; if I look at the csv file that's saved on the server, it does not contain the HTML.
Can anyone help me out with a way to force a download of the actual csv file as it is on the server, rather than as the browser is editing it to be? I've tried using the header location, like this:
header('Location: ' . $filename);
but it opens the file in the browser, rather than forcing the save dialog.
Here's my current code:
//set dynamic filename
$filename = "customers.csv";
//open file to write csv
$fp = fopen($filename, 'w');
//get all data
$query = "select
c.firstname,c.lastname,c.email as customer_email,
a.email as address_email,c.phone as customer_phone,
a.phone as address_phone,
a.company,a.address1,a.address2,a.city,a.state,a.zip, c.last_signin
from {$dbpre}customers c
left join {$dbpre}customers_addresses a on c.id = a.customer_id order by c.last_signin desc";
$votes = mysql_query($query) or die ("File: " . __FILE__ . "<br />Line: " . __LINE__ . "<p>{$query}<p>" . mysql_error());
$counter = 1;
while ($row = mysql_fetch_array($votes,1)) {
//put header row
if ($counter == 1){
$headerRow = array();
foreach ($row as $key => $val)
$headerRow[] = $key;
fputcsv($fp, $headerRow);
}
//put data row
fputcsv($fp, $row);
$counter++;
}
//close file
fclose($fp);
//redirect to file
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=".$filename);
header("Content-Transfer-Encoding: binary");
readfile($filename);
exit;
EDITS
Full URL looks like this:
http://mysite.com/administrator/index.php?option=com_eimcart&task=customers
with the actual download link looking like this:
http://mysite.com/administrator/index.php?option=com_eimcart&task=customers&subtask=export
MORE EDITS
Here's a shot of the page that the code is on; the generated file still is pulling in the html for the submenu. The code for the selected link (Export as CSV) is now
index.php?option=com_eimcart&task=customers&subtask=export&format=raw
Now here is a screenshot of the generated, saved file:
It shrank during the upload here, but the text highlighted in yellow is the html code for the subnav (list customers, add new customer, export as csv). Here's what my complete code looks like now; if I could just get rid of that last bit of html it would be perfect.
$fp= fopen("php://output", 'w');
$query = "select c.firstname,c.lastname,c.email as customer_email,
a.email as address_email,c.phone as customer_phone,
a.phone as address_phone, a.company, a.address1,
a.address2,a.city,a.state,a.zip,c.last_signin
from {$dbpre}customers c
left join {$dbpre}customers_addresses a on c.id = a.customer_id
order by c.last_signin desc";
$votes = mysql_query($query) or die ("File: " . __FILE__ . "<br />Line: " . __LINE__ . "<p>{$query}<p>" . mysql_error());
$counter = 1;
//redirect to file
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=customers.csv");
header("Content-Transfer-Encoding: binary");
while ($row = mysql_fetch_array($votes,1)) {
//put header row
if ($counter == 1){
$headerRow = array();
foreach ($row as $key => $val)
$headerRow[] = $key;
fputcsv($fp, $headerRow);
}
//put data row
fputcsv($fp, $row);
$counter++;
}
//close file
fclose($fp);
UPDATE FOR BJORN
Here's the code (I think) that worked for me. Use the RAW param in the link that calls the action:
index.php?option=com_eimcart&task=customers&subtask=export&format=raw
Because this was procedural, our link was in a file called customers.php, which looks like this:
switch ($r['subtask']){
case 'add':
case 'edit':
//if the form is submitted then go to validation
include("subnav.php");
if ($r['custFormSubmitted'] == "true")
include("validate.php");
else
include("showForm.php");
break;
case 'delete':
include("subnav.php");
include("process.php");
break;
case 'resetpass':
include("subnav.php");
include("resetpassword");
break;
case 'export':
include("export_csv.php");
break;
default:
include("subnav.php");
include("list.php");
break;
}
So when a user clicked on the link above, the export_csv.php file is automatically included. That file contains all the actual code:
<?
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=customers.csv");
header("Content-Transfer-Encoding: binary");
$fp= fopen("php://output", 'w');
//get all data
$query = "select
c.firstname,c.lastname,c.email as customer_email,
a.email as address_email,c.phone as customer_phone,
a.phone as address_phone,
a.company,a.address1,a.address2,a.city,a.state,a.zip, c.last_signin
from {$dbpre}customers c
left join {$dbpre}customers_addresses a on c.id = a.customer_id order by c.last_signin desc";
$votes = mysql_query($query) or die ("File: " . __FILE__ . "<br />Line: " . __LINE__ . "<p>{$query}<p>" . mysql_error());
$counter = 1;
while ($row = mysql_fetch_array($votes,1)) {
//put header row
if ($counter == 1){
$headerRow = array();
foreach ($row as $key => $val)
$headerRow[] = $key;
fputcsv($fp, $headerRow);
}
//put data row
fputcsv($fp, $row);
$counter++;
}
//close file
fclose($fp);
This is a piece of sample code that I just cooked up to help you out. Use it as an action method in your controller.
function get_csv() {
$file = JPATH_ADMINISTRATOR . DS . 'test.csv';
// Test to ensure that the file exists.
if(!file_exists($file)) die("I'm sorry, the file doesn't seem to exist.");
// Send file headers
header("Content-type: text/csv");
header("Content-Disposition: attachment;filename=test.csv");
// Send the file contents.
readfile($file);
}
This alone will not be enough, because the file you download will still contain the surrounding html. To get rid of it and only receive the csv file's contents you need to add format=raw parameter to your request. In my case the method is inside the com_csvexample component, so the url would be:
/index.php?option=com_csvexample&task=get_csv&format=raw
EDIT
In order to avoid using an intermediate file substitute
//set dynamic filename
$filename = "customers.csv";
//open file to write csv
$fp = fopen($filename, 'w');
with
//open the output stream for writing
//this will allow using fputcsv later in the code
$fp= fopen("php://output", 'w');
Using this method you have to move the code that sends headers before anything is written to the output. You also won't need the call to the readfile function.
Add this method to your controller:
function exportcsv() {
$model = & $this->getModel('export');
$model->exportToCSV();
}
Then add a new model called export.php, code below. You will need to change or extend the code to your situation.
<?php
/**
* #package TTVideo
* #author Martin Rose
* #website www.toughtomato.com
* #version 2.0
* #copyright Copyright (C) 2010 Open Source Matters. All rights reserved.
* #license http://www.gnu.org/copyleft/gpl.html GNU/GPL
*/
//No direct acesss
defined('_JEXEC') or die();
jimport('joomla.application.component.model');
jimport( 'joomla.filesystem.file' );
jimport( 'joomla.filesystem.archive' );
jimport( 'joomla.environment.response' );
class TTVideoModelExport extends JModel
{
function exportToCSV() {
$files = array();
$file = $this->__createCSVFile('#__ttvideo');
if ($file != '') $files[] .= $file;
$file = $this->__createCSVFile('#__ttvideo_ratings');
if ($file != '') $files[] .= $file;
$file = $this->__createCSVFile('#__ttvideo_settings');
if ($file != '') $files[] .= $file;
// zip up csv files to be delivered
$random = rand(1, 99999);
$archive_filename = JPATH_SITE.DS.'tmp'.DS.'ttvideo_'. strval($random) .'_'.date('Y-m-d').'.zip';
$this->__zip($files, $archive_filename);
// deliver file
$this->__deliverFile($archive_filename);
// clean up
JFile::delete($archive_filename);
foreach($files as $file) JFile::delete(JPATH_SITE.DS.'tmp'.DS.$file);
}
private function __createCSVFile($table_name) {
$db = $this->getDBO();
$csv_output = '';
// get table column names
$db->setQuery("SHOW COLUMNS FROM `$table_name`");
$columns = $db->loadObjectList();
foreach ($columns as $column) {
$csv_output .= $column->Field.'; ';
}
$csv_output .= "\n";
// get table data
$db->setQuery("SELECT * FROM `$table_name`");
$rows = $db->loadObjectList();
$num_rows = count($rows);
if ($num_rows > 0) {
foreach($rows as $row) {
foreach($row as $col_name => $value) {
$csv_output .= $value.'; ';
}
$csv_output .= "\n";
}
}
$filename = substr($table_name, 3).'.csv';
$file = JPATH_SITE.DS.'tmp'.DS.$filename;
// write file to temp directory
if (JFile::write($file, $csv_output)) return $filename;
else return '';
}
private function __deliverFile($archive_filename) {
$filesize = filesize($archive_filename);
JResponse::setHeader('Content-Type', 'application/zip');
JResponse::setHeader('Content-Transfer-Encoding', 'Binary');
JResponse::setHeader('Content-Disposition', 'attachment; filename=ttvideo_'.date('Y-m-d').'.zip');
JResponse::setHeader('Content-Length', $filesize);
echo JFile::read($archive_filename);
}
/* creates a compressed zip file */
private function __zip($files, $destination = '') {
$zip_adapter = & JArchive::getAdapter('zip'); // compression type
$filesToZip[] = array();
foreach ($files as $file) {
$data = JFile::read(JPATH_SITE.DS.'tmp'.DS.$file);
$filesToZip[] = array('name' => $file, 'data' => $data);
}
if (!$zip_adapter->create( $destination, $filesToZip, array() )) {
global $mainframe;
$mainframe->enqueueMessage('Error creating zip file.', 'message');
}
}
}
?>
Then go to your default view.php and add a custom buttom, e.g.
// custom export to set raw format for download
$bar = & JToolBar::getInstance('toolbar');
$bar->appendButton( 'Link', 'export', 'Export CSV', 'index.php?option=com_ttvideo&task=export&format=raw' );
Good luck!
You can use Apache's mod_cern_meta to add HTTP headers to static files. Content-Disposition: attachment. The required .htaccess and .meta files can be created by PHP.
Another way to output CSV data in a Joomla application is to create a view using CSV rather than HTML format. That is, create a file as follows:
components/com_mycomp/views/something/view.csv.php
And add content similar to the following:
<?php
// No direct access
defined('_JEXEC') or die;
jimport( 'joomla.application.component.view');
class MyCompViewSomething extends JViewLegacy // Assuming a recent version of Joomla!
{
function display($tpl = null)
{
// Set document properties
$document = &JFactory::getDocument();
$document->setMimeEncoding('text/csv');
JResponse::setHeader('Content-disposition', 'inline; filename="something.csv"', true);
// Output UTF-8 BOM
echo "\xEF\xBB\xBF";
// Output some data
echo "field1, field2, 'abc 123', foo, bar\r\n";
}
}
?>
Then you can create file download links as follows:
/index.php?option=com_mycomp&view=something&format=csv
Now, you would be right to question the 'inline' part in the Content-disposition. If I recall correctly when writing this code some years ago, I had problems with the 'attachment' option. This link which I just googled now seemed familiar as the driver for it: https://dotanything.wordpress.com/2008/05/30/content-disposition-attachment-vs-inline/ . I've been using 'inline' ever since and am still prompted to save the file appropriately from any browsers I test with. I haven't tried using 'attachment' any time recently, so it may work fine now of course (the link there is 7 years old now!)