I am getting an error something like below when writing a file
Error: Call to a member function getCacheData() on null
File E:\websites\d3a-qa\vendor\PHPExcel\PHPExcel\Worksheet.php
Line: 2554
Line 2554:
public function garbageCollect() {
// Flush cache
$this->_cellCollection->getCacheData('A1');
I know this error is coming because $this->_cellCollection is null but I dont know why is it null. I am calling $writer->save($saving_name); where it gives this issue.
I tried doing all sort of things. From backtracing to commenting the disconnectWorksheet code. resetting the variables for PHPExcel object but nothing works.
The funny part is sometimes it works and sometimes it doesn't. There's not much documentation or answers to queries aboout PHPExcel on internet. Therefore at last I have come here so that I could get any answer or at least a hint of what needs to be done.
Thanks in Advance.
EDIT
Here's my code where I am using PHPExcel. It appears that the error is coming at $writer->save($saving_name);.
$parts is an array having several number of XLS that needs to be merged into one XLS sheet.
PS: The error comes only when heavy processing is done (or nearly 45 sheets). For smaller xls counts its working fine.
if(!empty($insertSummary)) {
$PHPExcel = new \PHPExcel();
$PHPExcel = $PHPExcel->createSheet();
$PHPExcel->setTitle(_EXPORT_DES_SUMMARY);
}
foreach ($parts as $part) {
$bigExcel = '';
if(!empty($insertSummary))
$bigExcel = $this->addSummarySheet($insertSummary, $part, $PHPExcel);
$bigExcel = $this->processPartFiles($part, $bigExcel);
$writer = \PHPExcel_IOFactory::createWriter($bigExcel, 'Excel5');
$returnFilename = $dbName . '_' . 'DES' . '_' . date('Y-m-d-h-i-s') . '_' . 'Part_' . ++$count . '.xls';
// name of file, which needs to be attached during email sending
$saving_name = _DES_PATH_WEBROOT . DS . $returnFilename;
$zipFiles[] = $saving_name;
$writer->save($saving_name);
//Log::write('debug', 'Line(' . __LINE__ .') memory_get_peak_usage - ' . memory_get_peak_usage(true));
//Log::write('debug', 'Line(' . __LINE__ .') memory_get_usage - ' . memory_get_usage());
}
EDIT-2
$this->addSummarySheet()
public function addSummarySheet($insertSummary, $part, $PHPExcel) {
$keys = array_keys($part);
$bigExcel = $this->CommonInterface->readXlsOrCsv($insertSummary, false);
//Copy & paste values of range of cells
$cellValuesTitle = $bigExcel->getActiveSheet()->rangeToArray('A1:C1');
$cellValues = $bigExcel->getActiveSheet()->rangeToArray('A' . ($keys[0] + 1) .':C' . ($keys[count($keys) - 1] + 1));
$bigExcel->removeSheetByIndex(0);
$bigExcel->addSheet($PHPExcel);
$bigExcel->getActiveSheet()->fromArray($cellValuesTitle, null, 'A1');
$bigExcel->getActiveSheet()->fromArray($cellValues, null, 'A2');
foreach($keys as $rowCount => $sheet) {
$cellCordinateRow = $rowCount + 2;
$bigExcel->getActiveSheet()->getCell('A' . $cellCordinateRow)->getHyperlink()->setUrl("sheet://'Data $sheet'!A{$cellCordinateRow}");
}
return $bigExcel;
}
$this->CommonInterface->readXlsOrCsv()
public function readXlsOrCsv($filename = null, $unlinkFile = true, $sheetnames = null) {
require_once(ROOT . DS . 'vendor' . DS . 'PHPExcel' . DS . 'PHPExcel' . DS . 'IOFactory.php');
/** Identify the type of $inputFileName **/
$inputFileType = \PHPExcel_IOFactory::identify($filename);
$objReader = \PHPExcel_IOFactory::createReader($inputFileType);
if(!empty($sheetnames)) {
//$objReader->setReadDataOnly(true);
$objReader->setLoadSheetsOnly($sheetnames);
}
$objPHPExcel = $objReader->load($filename);
//$objPHPExcel = \PHPExcel_IOFactory::load($filename);
if ($unlinkFile == true)
$this->unlinkFiles($filename); // Delete The uploaded file
return $objPHPExcel;
}
I've solved the issue I was facing with PHPExcel.
I have changed removed $bigExcel = $this->CommonInterface->readXlsOrCsv($insertSummary, false); from $this->addSummarySheet() and placed it under if(!empty($insertSummary)) { as stated in EDIT 2.
Also, I have added below line of code in $this->addSummarySheet()
$PHPExcelSheet = new \PHPExcel();
$PHPExcelSheet->removeSheetByIndex(0);
$PHPExcel = new \PHPExcel();
$PHPExcel = $PHPExcel->createSheet();
$PHPExcel->setTitle(_EXPORT_DES_SUMMARY);
SO My final code in $this->addSummarySheet() becomes
public function addSummarySheet($insertSummary, $part, $PHPExcel, $summaryExcel) {
$keys = array_keys($part);
//$summaryExcel= $this->CommonInterface->readXlsOrCsv($insertSummary, false);
//Copy & paste values of range of cells
$cellValuesTitle = $summaryExcel->getActiveSheet()->rangeToArray('A1:C1');
$cellValues = $summaryExcel->getActiveSheet()->rangeToArray('A' . ($keys[0] + 1) .':C' . ($keys[count($keys) - 1] + 1));
$PHPExcelSheet = new \PHPExcel();
$PHPExcelSheet->removeSheetByIndex(0);
$PHPExcel = new \PHPExcel();
$PHPExcel = $PHPExcel->createSheet();
$PHPExcel->setTitle(_EXPORT_DES_SUMMARY);
$PHPExcelSheet->addSheet($PHPExcel);
$PHPExcelSheet->getActiveSheet()->fromArray($cellValuesTitle, null, 'A1');
$PHPExcelSheet->getActiveSheet()->fromArray($cellValues, null, 'A2');
foreach($keys as $rowCount => $sheet) {
$cellCordinateRow = $rowCount + 2;
$PHPExcelSheet->getActiveSheet()->getCell('A' . $cellCordinateRow)->getHyperlink()->setUrl("sheet://'Data $sheet'!A{$cellCordinateRow}");
}
return $PHPExcelSheet;
}
Thanks Mark for providing the $this->addSummarySheet() hint. It truly helped.
Related
I tried to store the image in DB along with some other data in another table ,but I get
"message": "Undefined variable $filenameA"
//for picture refrences
if (!empty($request->picture_a)) {
$fileA = $request->file('picture_a');
$extensionA = $fileA->getClientOriginalExtension();
$filenameA = 'picture_a' . '_' . time() . '.' . $extensionA;
$fileA->storeAs('uploads/picture_refrences', $filenameA);
$data['picture_a'] = 'public/uploads/' . $filenameA;
}
if (!empty($request->picture_b)) {
$fileB = $request->file('picture_b');
$extensionB = $fileB->getClientOriginalExtension();
$filenameB = 'picture_b' . '_' . time() . '.' . $extensionB;
$fileB->storeAs('uploads/picture_refrences', $filenameB);
$data['picture_b'] = 'public/uploads/' . $filenameB;
}
if (!empty($request->picture_c)) {
$fileC = $request->file('picture_c');
$extensionC = $fileC->getClientOriginalExtension();
$filenameC = 'picture_c' . '_' . time() . '.' . $extensionC;
$fileC->storeAs('uploads/picture_refrences', $filenameC);
$data['picture_c'] = 'public/uploads/' . $filenameC;
}
This is what I used and it was working when I used it to store in public directory now since I tried to store in storage it does not upload images
enter image description here
you can use this code, it is working and tested
$image = $request->file('picture_a');
if (isset($image))
{
$currentDate = Carbon::now()->toDateString();
$imageName = $currentDate.'-'. uniqid() .'.'. $image->getClientOriginalExtension();
if (!file_exists('uploads/photos')){
mkdir('uploads/photos',0777,true);
}
$image->move('uploads/photos',$imageName);
}
zipping some directory on the server I receive extra folder inside zip-archive under Windows and Linux.
Extra folder "3b84" is the ending of using md5(integer) function to set filename inside getExportPaths() method. If I try to make filename shorter - it makes me a zip archive where all files placed in one directory without their subdirectories like "CSS", "fonts" etc.
And one important thing why I'm using md5() - I need to paste into one directory a lot of zip archives. So their name should be unique.
How to fix this and remove such extra folder with a name like "3b84"?
public function actionExportdocument($id)
{
$model = Order::findOne(['id' => $id, 'user_id' => Yii::$app->user->getId()]);
if (!$model) {
throw new BadRequestHttpException('Export not found.');
}
$paths = Order::getExportPaths($model->id, $model->design->path);
$prepareFiles = Order::prepareFiles($paths, $model);
$zip = new ZipArchive();
$zip->open($paths['exportRoot'] . DIRECTORY_SEPARATOR . $paths['exportFile'],
ZipArchive::CREATE | ZipArchive::OVERWRITE);
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($paths['exportPath']),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $name => $file)
{
// Skip directories (they would be added automatically)
if (!$file->isDir())
{
$filePath = $file->getRealPath();
$relativePath = substr($filePath, strlen($paths['designPath']) + 1);
$zip->addFile($filePath, $relativePath);
}
}
$zip->close();
FileHelper::sendFile($paths['exportRoot'] . DIRECTORY_SEPARATOR . $paths['exportFile'], 'script.zip');
}
Order class contains:
public static function getExportPaths($modelId, $designPath)
{
$paths['orderIdHash'] = md5($modelId);
$paths['designPath'] = \Yii::getAlias('#app') . DIRECTORY_SEPARATOR . 'designs' . DIRECTORY_SEPARATOR . $designPath;
$paths['exportRoot'] = \Yii::getAlias('#webroot') . DIRECTORY_SEPARATOR . 'export-document';
$paths['exportPath'] = $paths['exportRoot'] . DIRECTORY_SEPARATOR . $paths['orderIdHash'];
$paths['exportFile'] = $paths['orderIdHash'] . '.zip';
return $paths;
}
public static function prepareFiles($paths, $model)
{
$exportDir = \Yii::getAlias('#webroot') . DIRECTORY_SEPARATOR . 'export-document';
if (!is_dir($exportDir)) {
mkdir($exportDir);
}
FileHelper::deleteDirectory($paths['exportPath']);
FileHelper::copyRecursive($paths['designPath'], $paths['exportPath']);
$content = '';
foreach ($model->orderTemplates as $template) {
$content .= $template->value;
}
$str = file_get_contents($paths['exportPath'] . DIRECTORY_SEPARATOR . 'index.html');
$str = str_replace("{{CONTENT}}", $content, $str);
file_put_contents($paths['exportPath'] . DIRECTORY_SEPARATOR . 'index.html', $str);
}
If I've read your question correctly; you wish to remove folders like /3b84/ from being generated.
Look inside your Order class:
public static function getExportPaths($modelId, $designPath)
{
$paths['orderIdHash'] = md5($modelId);
$paths['designPath'] = \Yii::getAlias('#app') . DIRECTORY_SEPARATOR . 'designs' . DIRECTORY_SEPARATOR . $designPath;
$paths['exportRoot'] = \Yii::getAlias('#webroot') . DIRECTORY_SEPARATOR . 'export-document';
$paths['exportPath'] = $paths['exportRoot'] . DIRECTORY_SEPARATOR . $paths['orderIdHash'];
$paths['exportFile'] = $paths['orderIdHash'] . '.zip';
return $paths;
}
See $paths['exportPath'] = $paths['exportRoot'] . DIRECTORY_SEPARATOR . $paths['orderIdHash']; ?
Remove that and you your new path will no longer have folders like /3b84/.
The problem was that I've specified wrong $relativePath.
This string was incorrect
$relativePath = substr($filePath, strlen($paths['designPath']) + 1);
And instead of it
$relativePath = substr($filePath, strlen($paths['exportPath']) + 1);
I'm using the mailReader script found here: https://github.com/stuporglue/mailreader
I am trying to copy the file(s) after upload to another folder.
The file(s) upload correctly to the folder where the script resides.
When I try to run a copy command, the filename variable is empty.
Here is the portion of the code I am working with: The last three lines are what I added.
private function saveFile($filename,$contents,$mimeType = 'unknown'){
$filename = preg_replace('/[^a-zA-Z0-9_-]/','_',$filename);
$unlocked_and_unique = FALSE;
while(!$unlocked_and_unique){
// Find unique
$name = time() . "_" . $filename;
$name = substr_replace($name,".pdf",-4); // added 1-19-2016
while(file_exists($this->save_directory . $name)) {
$name = time() . "_" . $filename;
$name = substr_replace($name,".pdf",-4);
}
// Attempt to lock
$outfile = fopen($this->save_directory.$name,'w');
if(flock($outfile,LOCK_EX)){
$unlocked_and_unique = TRUE;
}else{
flock($outfile,LOCK_UN);
fclose($outfile);
}
}
fwrite($outfile,$contents);
fclose($outfile);
if (copy($this->save_directory.$name, "/attachments/" . TRANS_ID . "/". $name)) {
unlink( $this->save_directory.$name );
}
I receive confirmation by email that the file(s) are uploaded, then another email with the error message.
Warning: copy(/attachments/W7652222-546/1453406138_residential-print_from_td.pdf): failed to open stream: No such file or directory in /home/myhost/public_html/mailreader/mailReader.php on line 224
224 being the line number of my added code.
The source filename is missing from in front of /attachments...
Anyone have any thoughts?
$name is defined in the while loop and may not be accessible on the upper scopes my suggestion is to change your code to this:
private function saveFile($filename,$contents,$mimeType = 'unknown'){
$filename = preg_replace('/[^a-zA-Z0-9_-]/','_',$filename);
$unlocked_and_unique = FALSE;
$name = '';
while(!$unlocked_and_unique){
// Find unique
$name = time() . "_" . $filename;
$name = substr_replace($name,".pdf",-4); // added 1-19-2016
while(file_exists($this->save_directory . $name)) {
$name = time() . "_" . $filename;
$name = substr_replace($name,".pdf",-4);
}
// Attempt to lock
$outfile = fopen($this->save_directory.$name,'w');
if(flock($outfile,LOCK_EX)){
$unlocked_and_unique = TRUE;
}else{
flock($outfile,LOCK_UN);
fclose($outfile);
}
}
fwrite($outfile,$contents);
fclose($outfile);
if (copy($this->save_directory.$name, "/attachments/" . TRANS_ID . "/". $name)) {
unlink( $this->save_directory.$name );
}
I hope this solves your problem
I ended up defining a constant of email_id in the private function saveToDb, then running a script after everything else is finished to query the table using the email_id and looping through the records moving the files.
I have a class that uses Assetic to generate some css files to disk. I'll jump right into the code.
In my layout header, I'm doing something like this:
$assetify = new Assetify();
$assetify->setDebug(true);
$assetify->setAssetDirectory(BASE_DIR . '/public/assets');
$assetify->setOutputDirectory(BASE_DIR . '/public/assets/generated');
$assetify
->addStylesheet('/assets/css/bootstrap-2.3.2.css')
->addStylesheet('/assets/css/select2-3.4.3.css')
->addStylesheet('/assets/css/main.css');
echo $assetify->dump();
My "Assetify" class runs this through Assetic. I'll paste what I hope are only the relevant portions from the dump() function:
// The Asset Factory allows us to not have to do all the hard work ourselves.
$factory = new AssetFactory($this->assetDirectory, $this->debug);
$factory->setDefaultOutput('/generated/*.css');
// The Filter Manager allows us to organize filters for the asset handling.
// For other filters, see: https://github.com/kriswallsmith/assetic
$fm = new FilterManager();
$fm->set('yui_css', new Yui\CssCompressorFilter('/usr/local/bin/yuicompressor-2.4.7.jar'));
$fm->set('yui_js', new Yui\JsCompressorFilter('/usr/local/bin/yuicompressor-2.4.7.jar'));
$factory->setFilterManager($fm);
// The Asset Manager allows us to keep our assets organized.
$am = new AssetManager();
$factory->setAssetManager($am);
// The cache-busting worker prefixes every css with what amounts to a version number.
$factory->addWorker(new CacheBustingWorker());
$assetCollection = array();
foreach ($assetGroups as $assetGroup) {
foreach ($assetGroup as $media => $items) {
$fileCollection = array();
foreach ($items as $item) {
// Add this asset to the asset collection.
$fileCollection[] = new FileAsset($item);
}
$assetCollection[] = new AssetCollection($fileCollection);
}
}
$assetCollection = new AssetCollection($assetCollection);
$am->set('base_css', $assetCollection);
// Generate the required assets. Prefixing a filter name with a question mark
// will cause that filter to be omitted in debug mode.
$asset = $factory->createAsset(
array('#base_css'),
array('?yui_css')
);
// Configure an internal file system cache so we don't regenerate this file on every load.
$cache = new AssetCache(
$asset,
new FilesystemCache($this->outputDirectory)
);
// And generate static versions of the files on disk.
$writer = new AssetWriter($this->assetDirectory);
$writer->writeAsset($cache);
This generates two different files, 87229eb-f47a352.css and a37c1589762f39aee5bd24e9405dbdf9. The contents of the files are exactly the same. The 87229eb-f47a352.css file seems to get generated every single time, and the other file is not regenerated unless the contents of the files change (this is what I would like). If I comment out the $writer->writeAsset($cache), no files are written to disk.
What obvious configuration am I missing? I appreciate the help, thank you.
I was able to roughly replicate your code and got the same results.
I was trying to get the same results as what I think you require but ended up writing my own code to cache and serve static files.
It's not complete by any means but it is working. It has the following features:
You can choose to cache files for different pages if you specify $filename
You can choose to create versions of your released files or delete previous versions
A cached file will be generated to your target folder only if changes have made to a source file
You just need to put the code in to a class or function and return the url to serve.
Hope it helps :)
<?php
use Assetic\Factory\AssetFactory;
use Assetic\AssetManager;
use Assetic\FilterManager;
use Assetic\Asset\AssetCollection;
use Assetic\Asset\FileAsset;
use Assetic\Filter\JSMinFilter;
// JavaScript Collection
$js_collection[] = new FileAsset(SCRIPT_PATH . 'jquery.js');
$js_collection[] = new FileAsset(SCRIPT_PATH . 'production.js');
if (file_exists(SCRIPT_PATH . $page_info['name'] . '.js')) {
$js_collection[] = new FileAsset(SCRIPT_PATH . $page_info['name'] . '.js');
}
// CSS Collection
$css_collection[] = new FileAsset(STYLE_PATH . 'theme.css');
if (file_exists(STYLE_PATH . $page_info['name'] . '.css')) {
$css_collection[] = new FileAsset(STYLE_PATH . $page_info['name'] . '.css');
}
// The Filter Manager allows us to organize filters for the asset handling.
$fm = new FilterManager();
$fm->set('js', new JSMinFilter());
$js = new AssetCollection (
$js_collection
);
$js->setTargetPath(SCRIPT_PATH . 'static');
$css = new AssetCollection (
$css_collection
);
$css->setTargetPath(STYLE_PATH . 'static');
$am = new AssetManager();
$am->set('js', $js);
$am->set('css', $css);
//** TO DO: put the below in a class and return the static file names **//
// options
$seperator = '-';
$filename = $page_info['name'];
$versions = false;
// get a list of all collection names
$collections = $am->getNames();
// get each collection
foreach ($collections as $collection_name) {
// get the collection object
$collection = $am->get($collection_name);
// ensure file types are identical
$last_ext = false;
foreach ($collection as $leaf) {
$ext = strtolower(pathinfo($leaf->getSourcePath(), PATHINFO_EXTENSION));
if (!$last_ext || $ext == $last_ext) {
$last_ext = $ext;
} else {
throw new \RuntimeException('File type mismatch.');
}
}
// get the highest last-modified value of all assets in the current collection
$modified_time = $collection->getLastModified();
// get the target path
$path = $collection->getTargetPath();
// the target path must be set
if (!$path) {
throw new \RuntimeException('Target path not specified.');
}
// build the filename to check
$file = ($filename) ? $filename . $seperator . $modified_time . '.' . $ext : $modified_time . '.' . $ext;
$cached_file = $path . '/' . $file;
// the file doesn't exist so we need to minify, dump and save as new cached file
if (!file_exists($cached_file)) {
// create the output dir if it doesnt exist
if (!is_dir($path) && false === #mkdir($path, 0777, true)) {
throw new \RuntimeException('Unable to create directory ' . $path);
}
// apply the filters
if ($fm->has($collection_name)) {
$collection->ensureFilter($fm->get($collection_name));
}
// If not versioned, delete previous version of this file
if (!$versions) {
if ($filename) {
foreach (glob($path . '/' . $filename . $seperator . '*.' . $ext) as $searchfile) {
#unlink($searchfile);
}
} else {
foreach (glob($path . '/*.' . $ext) as $searchfile) {
#unlink($searchfile);
}
}
}
// put the contents in the file
if (false === #file_put_contents($cached_file, $collection->dump())) {
throw new \RuntimeException('Unable to write file ' . $cached_file);
}
}
// return the cached file
echo 'output: ' . $cached_file . '<br>';
}
exit;
?>
Running Apache 2.2 with PHP 5.3 on Windows 8. Trying to get the PHP class ImapMailbox to download attachments, but each time I getMail(), the attachments value is empty on every email that has attachments.
All the rest of the email info is downloaded correctly.
I've looked through the class code but can't identify where the problem might be.
Here is my current code:
$mailbox = new ImapMailbox('{testsite.com:110/pop3/novalidate-cert}INBOX', 'testemail#testsite.com', 'MyPaSs', ATTACH_DIR, 'utf-8');
$mails = array();
foreach($mailbox->searchMailbox('SUBJECT "test attach" SINCE "' . date('m/d/Y', strtotime('-1 week')) . '"') as $mailId) {
$mail = $mailbox->getMail($mailId);
$mails[] = $mail;
}
After dumping the $data var in getMail(), it appears there are attachments in winmail.dat format. The code cannot get to these because no attachmentId value is being assigned due to an empty 'ifid' value. Decoding the winmail.dat attachments can be done, but only if they are detected and written to file.
Any ideas how create a workaround in the ImapMailbox code for this?
Here is what I wrote that fixes this problem.
At the beginning of initMailPart() method, add the following:
static $altAttachmentId = 0;
At the end of the IF block for if($this->attachmentsDir) { add the following where the closing } bracket is:
} elseif (!empty($params['fileName']) || !empty($params['filename']) || !empty($params['name'])) { // Process attachments that are not inline.
// Check if need to decode TNEF (Winmail.dat) file.
if ($partStructure->ifsubtype && $partStructure->subtype == 'MS-TNEF') {
require_once 'path_to_your/tnef_decoder.php';
$Tnef = new tnef_decoder;
$un_tnef = $Tnef->decompress($data);
$attached_files = array();
foreach ($un_tnef as $f) {
if (!empty($f['name']) && !empty($f['stream'])) {
$attachment = new IncomingMailAttachment();
$attachment->id = $altAttachmentId;
$attachment->name = $f['name'];
$attachment->filePath = $this->attachmentsDir . DIRECTORY_SEPARATOR . preg_replace('~[\\\\/]~', '', $f['name']);
$mail->addAttachment($attachment);
if (file_exists($attachment->filePath) && md5($f['stream']) != md5_file($attachment->filePath)) {
$attachment->filePath = $this->attachmentsDir . DIRECTORY_SEPARATOR . preg_replace('~[\\\\/]~', '', $mail->id . '_' . $altAttachmentId . '_' . $f['name']);
}
file_put_contents($attachment->filePath, $f['stream']);
$altAttachmentId++;
}
}
} else {
if (!empty($params['filename'])) {
$fileName = $params['filename']; // Account for random camel-case mistake on element.
} elseif (!empty($params['fileName'])) {
$fileName = $params['fileName'];
} else {
$fileName = $params['name'];
}
$attachment = new IncomingMailAttachment();
$attachment->id = $altAttachmentId;
$attachment->name = $fileName;
$attachment->filePath = $this->attachmentsDir . DIRECTORY_SEPARATOR . preg_replace('~[\\\\/]~', '', $mail->id . '_' . $altAttachmentId . '_' . $fileName);
$mail->addAttachment($attachment);
file_put_contents($attachment->filePath, $data);
$altAttachmentId++;
}
}
Note that you must include the tnef_decoder.php file found in the Roundcube Webmail package for the TNEF decoding to work. I got inspiration for the TNEF solution here.
This modification will process all TNEF encoded files in a Winmail.dat file and any other attachment that is not attached inline. Watch your memory usage on large files.
It also won't overwrite existing files that are the same name if they are not exactly the same.