I am trying to export some data gathered by webforms, and match it up with one or more files included for each webform. My chosen format is .xls not .xlsx for backwards compatibility.
I've learned here, and on the internet in general, that multiple links IS possible if we use shapes or images/thumbnails to add hyperlinks to, but I can't seem to make it work with PHPSpreadsheet and xls files.
So far I've managed to set hyperlink to individual cells, but I can't seem to make it work on drawings.
Working code for cells:
$coordinates = $sheet->getCellByColumnAndRow($column,$row)->getCoordinate(); // get coordinate like "A1", "B5" etc.
$sheet->setCellValueByColumnAndRow($column,$row,$cellValue); // set link text
$sheet->getStyle($coordinates)->getFont()->setUnderline('single'); // set underline like links have
$sheet->getStyle($coordinates)->getFont()->getColor()->setRGB('#0000FF'); // set default link color
$sheet->getCellByColumnAndRow($column,$row)->getHyperlink()->setUrl('http://www.test.com'); // setting url or local link
This works great, but in my spreadsheet I would like to have multiple links in one cell if there are more than one file sent in by a single user.
Attempt at making it work for drawings:
// create a new drawing object
$drawing = new Drawing();
// set properties
$drawing->setName('Testname');
$drawing->setDescription('Test description');
$drawing->setPath($url); // put your path and image here
$drawing->setCoordinates($coordinates);
$drawing->setOffsetX($width);
$drawing->setHeight($height);
//$drawing->getHyperlink()->setUrl('http://www.test.com'); // error: Call to a member function setUrl() on null
//$drawing->getHyperlink()->setTooltip('tooltip works?'); // error: Call to a member function setTooltip() on null
// Connect drawn image to the spreadsheet
$drawing->setWorksheet($sheet);
The images works great, and I can place multiple images in one cell, but when I try to add hyperlink to each image, PHPSpreadsheet fails me. Are there any other way, perhaps with shapes or other things that I haven't thought about that might do the trick?
And if adding hyperlinks to multiple shapes / images with hyperlinks inside one cell is impossible with standard PHPSpreadsheet, is there a way to force one or more excel functions into one cell, achieving the same thing somehow?
I found a workaround. I answered my own question for future reference and hopefully to help others. :)
The solution was to add a new row for each extra link I needed, and merge all other cells vertically in the columns that was not the link column. This made it possible to seemingly make 2 or more cells inside one cell, not affecting the other columns. E.g. one result that needed 3 links for the file cell, would be taking up 3 rows in the spreadsheet, but all the other columns corresponding to that result would be merged individually vertically, making it look like one row with a file cell containing 3 cells.
Because of the limitation on one link per cell, this is what needed to be done:
require_once '/vendor/autoload.php';
// set folder to unzip the corresponding files
$filesFolder = 'Files';
// Create new Spreadsheet object
$spreadsheet = new Spreadsheet();
// set active sheet
$sheet = $spreadsheet->getActiveSheet();
// get form results data prepped for my .xls file
list($header, $allRows) = getResults(); // privat function setting 2x arrays with info gathered
// set headers
$sheet->fromArray([$header], NULL, 'A1');
$fileFieldIndex = 3; // the column index of the files
$counter = 2; // Start below the headers
foreach($allRows as $row => $innerArray){
// Add some data
$sheet->fromArray([$innerArray], NULL, 'A'.$counter);
// fetching fileinfo
$aFileInfo = getFileInfo(); // privat function setting 2x array with fileinfo corresponding to this specific result
// loop through and add rows for each extra file link
foreach($aFileInfo as $innerRow => $fileInfo) {
if($innerRow>=1){
// on second or more occurrence of files, add extra row
$counter++; // update counter
// Add one row starting at column 'A'
$sheet->fromArray([$innerArray], NULL, 'A'.$counter);
// set link text
$sheet->setCellValueByColumnAndRow($fileFieldIndex,$counter,$fileInfo['filename']);
// get coordinates (using only numbers to get the letter/number combination)
$coordinates = $sheet->getCellByColumnAndRow($fileFieldIndex,$counter)->getCoordinate();
// separate letter-column and number-row
preg_match_all('/(\d)|(\w)/', $coordinates, $matches);
$letterColumnFiles = implode($matches[2]);
// loop through columns
// Get the highest column letter referenced in the worksheet
$highestColumn = $sheet->getHighestColumn();
$prevRow = ($counter-1);
// stop when you reach the highest column
for ($col = 'A'; $col != $highestColumn; ++$col) {
if($col != $letterColumnFiles){
// merge cell with cell above if not the column with the files
$topCell = $col.$prevRow;
$sheet->getStyle($topCell)->getAlignment()->setVertical('top');
$sheet->mergeCells("$topCell:$col$counter");
}
}
// merge highest column too, we wouldn't want to forget this one
$sheet->getStyle($highestColumn.$prevRow)->getAlignment()->setVertical('top');
$sheet->mergeCells("$highestColumn$prevRow:$highestColumn$counter");
}
// get coordinate like "A1", "B5" etc. needed for getStyle
$coordinates = $sheet->getCellByColumnAndRow($fileFieldIndex,$counter)->getCoordinate();
// set underline like links have
$sheet->getStyle($coordinates)->getFont()->setUnderline('single');
// set default link color
$sheet->getStyle($coordinates)->getFont()->getColor()->setRGB('#0000FF');
// setting local link to specified local folder
$sheet->getCellByColumnAndRow($fileFieldIndex,$counter)->getHyperlink()->setUrl($filesFolder.'\\'.$fileInfo['filename']);
}
}
Related
Background:
I'm successfully inserting rows of data into a spreadsheet using the following to add text data:
$sheet->fromArray($user, NULL, 'B' . $counter);
And the following to add an image to the beggining of that row:
$drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
$drawing->setName('Avatar');
$drawing->setDescription('Avatar');
$drawing->setPath('image.jpg');
$drawing->setCoordinates('A' . $counter);
$drawing->setWidthAndHeight(50, 50);
$drawing->setWorksheet($spreadsheet->getActiveSheet());
This works fine
Problem:
When I then go to sort that data, the data is sorted successfully, but the images stay in their original place.
Question:
How can I ensure when the spreadsheet is sorted, the images move with the row as well?
I am creating an excel file with different sheets. The different sheets are created by iteration. But my iteration results in an extra sheet named worksheet. My code is:
$result = fetch results from database;
$count = COUNT($result);
foreach ( $result as $key=>$value){
$objPHPExcel->createSheet($key);
$objPHPExcel->getActiveSheet()
->setTitle($value['title']);
}
My database has got 3 results and it generates three worksheets along with a fourth one which is named as 'Worksheet'.
If I am using a checking condition with
if ($key > 0) {
execute above code
}
else {
$objPHPExcel->setActiveSheetIndex(0)->setTitle($value['title']);
}
it works fine. why is it so? Where is the mistake?
There is nothing wrong with your code. When you instantiate a new PHPExcel object using $objPHPExcel = new PHPExcel(), it is created with a single sheet called "worksheet"; delete that if you want to create only your own sheets
$objPHPExcel->removeSheetByIndex(0);
Mark Baker's accepted answer as always helped me but I want to expand and explain further on it.
I found that both $objPHPExcel = new PHPExcel() and $objPHPExcel->createSheet() create a single sheet called "worksheet". So use either one.
In the following sample which creates multiple sheets:
$objPHPExcel = new PHPExcel();
// First sheet
$objWorkSheet = $objPHPExcel->createSheet(); // NOT NEEDED
$objWorkSheet = $objPHPExcel->getActiveSheet();
// code for putting first sheet data
// Second sheet
$objWorkSheet = $objPHPExcel->createSheet();
// code for putting second sheet data
// Third, fourth sheets etc
createSheet() isn't needed for the first sheet where I've commented and it'll create that extra blank sheet. new PHPExcel() already made my first one me as he said.
So putting $objPHPExcel->removeSheetByIndex(0) at the end to remove it will work -- But just removing this unneeded line solves it the 'right' way and removes that redundancy. It seems like your code roughly does it like this.
So I'm using PHPExcel to generate spreadsheets with data from a database. I have specific columns set to specific widths. When ever I try to use a default font for the entire spreadsheet though, these widths seem to be ignored and the sheet widens. Am i using this wrong? Where is #Mark Baker?
$objPHPExcel = new PHPExcel();
$phpColor = new PHPExcel_Style_Color();
$phpColor->setRGB('051C48');
$objPHPExcel->getDefaultStyle()->getFont()->setColor($phpColor);
$objPHPExcel->getDefaultStyle()->getFont()->setName('Gotham Book');
$objPHPExcel->getDefaultStyle()->getFont()->setSize(10);
$activeSheet = $objPHPExcel->getActiveSheet();
$activeSheet->setShowGridlines(false);
foreach(range('A','F') as $columnID){
$objPHPExcel->getActiveSheet()->getColumnDimension($columnID)->setAutoSize(false);
}
///////////////////////////////// FIRST PHASE /////////////////////////////////////////////////////////
$activeSheet->getColumnDimension('A')->setWidth(22.71);
$activeSheet->getColumnDimension('B')->setWidth(31.71);
$activeSheet->getColumnDimension('C')->setWidth(9.71);
$activeSheet->getColumnDimension('D')->setWidth(9.71);
$activeSheet->getColumnDimension('E')->setWidth(11.85);
$activeSheet->getColumnDimension('F')->setWidth(17.57);
NO DEFAULT FONT SET:
DEFAULT FONT SET:
If you would like any additional information, just let me know. I realize those screenshots may not be the best method for showing whats happening. I can however just set a range for the cells, I was just hoping for cleaner code by setting a default value. Also if I'm being to specific and needy, just ignore me
$activeSheet->getStyle('A1:F1')->applyFromArray($firstRowStyle);
$activeSheet->getStyle('B2:B50')->applyFromArray($gothamBook10);
$activeSheet->getStyle('C2:C50')->applyFromArray($gothamBook10);
$activeSheet->getStyle('D2:D50')->applyFromArray($gothamBook10);
$activeSheet->getStyle('E2:E50')->applyFromArray($gothamBook10);
$activeSheet->getStyle('F2:F50')->applyFromArray($gothamBook10);
I have this set of codes from JpGraph to help me with creating a bar chart.
<?php
require_once ('src/jpgraph.php');
require_once ('src/jpgraph_bar.php');
$datay=array(1992,1993,1995,1996,1997,1998,2001);
// Size of graph
$width=400;
$height=500;
// Set the basic parameters of the graph
$graph = new Graph($width,$height);
$graph->SetScale('textlin');
$top = 60;
$bottom = 30;
$left = 80;
$right = 30;
$graph->Set90AndMargin($left,$right,$top,$bottom);
// Nice shadow
$graph->SetShadow();
// Setup labels
$lbl = array("Andrew\nTait","Thomas\nAnderssen","Kevin\nSpacey","Nick\nDavidsson",
"David\nLindquist","Jason\nTait","Lorin\nPersson");
$graph->xaxis->SetTickLabels($lbl);
// Label align for X-axis
$graph->xaxis->SetLabelAlign('right','center','right');
// Label align for Y-axis
$graph->yaxis->SetLabelAlign('center','bottom');
// Titles
$graph->title->Set('Number of incidents');
// Create a bar pot
$bplot = new BarPlot($datay);
$bplot->SetFillColor('orange');
$bplot->SetWidth(0.5);
$bplot->SetYMin(1990);
?>
However, this will only work if I put it right at the top of my code. If I put it anywhere else, it will fail to display. Is there any way to overcome this so that if I put the code specifically at one place for example under it will appear there? Also, I'll be using some data from my own database as values for this graph.
Thank you.
JpGraph creates an image, which is displayed. You can't output text and images at the same time.
Requires/includes are resolved at runtime, not during parse. This allows them to be invoked dynamically. Therefore, they must be completed before any of their code can be referenced.
Alternative methods to look at are autoload, spl_autoload. This allows class files to be loaded on first reference.
However, from your comments, it appears that the issue is the usage of JpGraph requiring the sending of headers. You'll need to check studentcourse.php to see if any output is generated (including inadvertent whitespace).
Well, you can't escape the fact that when you call new Graph(), the class should already exist, so if you include the files after that line it'll never work.
This generates an image, right?
I would put this in a file by itself, and then on the page where you want the graph to show up, just do:
<img src="myGraph.php">
I have a PHP parser using PHPExcel that reads in a Excel file and stores the contents into an Oracle database.
The problem is that the parser reads every line and does not set up any distinction between headers of rows and the data contained within those rows. When the info is read from the database it is read in a flat file listing and is not easy to navigate.
I am currently reading the data into an EXTJS Grid. I would like to be able to read the Excel, store it in the DB, then pull it out and view it in a new EXTJS GroupingGrid, where the group would be the 'header' for each worksheet in the Excel file.
Has anyone ever used PHPExcel or know how to use PHPExcel to read the Excel file and output the header (1,1) in each worksheet, so that I can store it in the database and pull it out and show it in the JSON so the groupingGrid will give me the ability to have a plus sign for each header so that I can click the plus sign and view all the contents under that header within the grid?
Hy, you can use this php-excell-reader http://code.google.com/p/php-excel-reader/ . I'm using it and works perfect. You can manipulate every cell of every row.
Count the cells
Count the rows
Make a loop to that count and manipulate data (you can add +1 if you want to skip
first row or first cell.
// Include the PHPExcel library
require_once './Classes/PHPExcel.php';
// Load the Excel File
$objReader = PHPExcel_IOFactory::createReader('Excel5');
$objPHPExcel = $objReader->load("myExcelFile.xls");
// Loop through every worksheet
foreach ($objPHPExcel->getWorksheetIterator() as $worksheet) {
// Read the value at Cell A1 in the current worksheet
$cellValue = $worksheet->getCell('A1')->getValue();
// Do whatever you want with $cellValue
// Store it in a database
// Whatever...
}
of course, you might want a bit of error handling in there as well, in case myExcelFile.xls doesn't exist, or you don't have read permissions, or isn't really an Excel file, but just wrap it in a try/catch block and handle as you wish.
You can also set up a read filter if you only want to load the first line of each worksheet rather than every single row and column:
// Include the PHPExcel library
require_once './Classes/PHPExcel.php';
/** Define a Read Filter class implementing PHPExcel_Reader_IReadFilter */
class firstRowFilter implements PHPExcel_Reader_IReadFilter
{
public function readCell($column, $row, $worksheetName = '') {
// Only read the heading row
return ($row == 1);
}
// Create an instance of our Read Filter
$firstRowFilter = new firstRowFilter();
// Instantiate the correct Reader
$objReader = PHPExcel_IOFactory::createReader('Excel5');
// Tell the Reader only to load cells that match our Read Filter
$objReader->setReadFilter($firstRowFilter)
// Load the Excel File
$objPHPExcel = $objReader->load("myExcelFile.xls");
// Loop through every worksheet
foreach ($objPHPExcel->getWorksheetIterator() as $worksheet) {
// Read the value at Cell A1 in the current worksheet
$cellValue = $worksheet->getCell('A1')->getValue();
// Do whatever you want with $cellValue
// Store it in a database
// Whatever...
}