I use library PHPExcel 1.7.9 to work with Excel files. First, I create a template, stylise and polish it. Then, to avoid style hardcoding, using the above mentioned library I open that template, change some values and save it as a new .xlsx file.
First, we fetch that style from cells.
$this->styles = array() ;
$this->styles['category'] = $sheet->getStyle("A4");
$this->styles['subcategory'] = $sheet->getStyle("A5");
Here is the recursive function, that displays categories and subcategories.
private function displayCategories($categories, &$row, $level = 0){
$sheet = $this->content ;
foreach($categories as $category){
if ($category->hasChildren() || $category->hasItems()){ //Check if the row has changed.
$sheet->getRowDimension($row)->setRowHeight(20);
$sheet->mergeCells(Cell::NUMBER . $row . ":" . Cell::TOTAL . $row) ;
$name = ($level == 0) ? strtoupper($category->name) : str_repeat(" ", $level*6) ."- {$category->name}" ;
$sheet->setCellValue(Cell::NUMBER . $row, $name) ;
$sheet->duplicateStyle((($level == 0) ? $this->styles['category'] : $this->styles['subcategory']), Cell::NUMBER . $row);
$row++ ;
if ($category->hasChildren()){
$this->displayCategories($category->children, $row, $level+1);
}
}
}
}
The problem
If $sheet->duplicateStyle() is used, it will be impossible to save document because of infinite recursion.
Maximum function nesting level of '200' reached, aborting! <- FATAL ERROR
The problem is in the next piece of code, inside PHPExcel_Style_Fill class, one object is referencing himself over and over.
public function getHashCode() { //class PHPExcel_Style_Fill
if ($this->_isSupervisor) {
var_dump($this === $this->getSharedComponent()); //Always true 200 times
return $this->getSharedComponent()->getHashCode();
}
return md5(
$this->getFillType()
. $this->getRotation()
. $this->getStartColor()->getHashCode()
. $this->getEndColor()->getHashCode()
. __CLASS__
);
}
Is any workaround to solve this? I would also accept any ideas on how to apply a complete style of one cell to another.
Solution:
As #MarkBaker said in comments, branch develop on GitHub really contains fixes to the bug.
EDIT I added a pull request with a fix: https://github.com/PHPOffice/PHPExcel/pull/251
This happens when you try to duplicate cell's style to the same cell; Take a look at this:
$phpe = new PHPExcel();
$sheet = $phpe->createSheet();
$sheet->setCellValue('A1', 'hi there') ;
$sheet->setCellValue('A2', 'hi again') ;
$sheet->duplicateStyle($sheet->getStyle('A1'), 'A2');
$writer = new PHPExcel_Writer_Excel2007($phpe);
$writer->save('./test.xlsx');
It will work just fine. BUT if I add another line like this:
$sheet->duplicateStyle($sheet->getStyle('A1'), 'A1');
then bang, infinite recursion starts after calling the save method
To fix your code, you should modify this part:
$sheet->duplicateStyle((($level == 0) ? $this->styles['category'] : $this->styles['subcategory']), Cell::NUMBER . $row);
To something along the lines of:
$style = ($level == 0) ? $this->styles['category'] : $this->styles['subcategory'];
$targetCoords = Cell::NUMBER . $row;
if($style->getActiveCell() != $targetCoords) {
$sheet->duplicateStyle($style, $targetCoords);
}
Without knowing the specifics of the library...
How about changing this:
public function getHashCode() { //class PHPExcel_Style_Fill
if ($this->_isSupervisor) {
To this:
public function getHashCode() { //class PHPExcel_Style_Fill
if ($this->_isSupervisor && ( $this != $this->getSharedComponent() ) ) {
If the hash code logic after the if statement does not apply to _isSupervisor, then add another logic statement and return a fixed value, like this:
public function getHashCode() { //class PHPExcel_Style_Fill
if ($this->_isSupervisor) {
if ( $this == $this->getSharedComponent() )
return md5(0);
Related
I already asked a question belonging this issue here
The code from the accepted answer by RiggsFolly works really proper but there is a small issue with that. I needed some days to test it out and searched for the reason why this is not the best way to solve the main goal. I was pleased to open a new question.
The code from RiggsFolly is based on $current_provider so the while-loop checks on every round if $current_provider has changed. So far so good. BUT now I needed to add a comprehensive logic. It meens that I added a true/false variable that simply checks if a value from an fetched object is equal to a certain string. This comparison is focused on a specific list item and not to the basic $current_provider.
So the goal is that $current_provider checks each fetched object for true/false and will be independent from $current_provider. At the moment I try to extend with a second loop but just want to give an example in the hope that it will be clear what to achieve:
$service = $db->query("SELECT * FROM `system` ORDER BY provider, artist");
$provider = NULL;
$close = false;
while ($data = $service->fetch_object()) {
$amount_1 = $data->digit_1; //db-structure: float
$amount_2 = $data->digit_2; //db-structure: float
if ($amount_1 == $amount_2) {
$close = true;
}
if ( $current_provider != $data->provider ) {
if ( $current_provider !== NULL ) {
echo '</div>close container in case of current_provider is already set';
}
echo '<div class="provider">open a new container in case of current_provider is not like data->provider or empty';
$current_provider = $data->provider;
}
echo 'some styling for each object';
if ($close === true ) {
echo '<div class="specific">if the amount_1 is same like amount_2 for a single object add only for this object a certain div';
} else {
echo '<div>show standard container even on specific object';
}
echo '</div><!--close container provider-->';
}
Kind regards.
I am still not sure I understand what you are actually trying to achieve here but maybe if you lay your decision making out like this it will all seem more obvious what you may need to do in addition to what I am suggesting.
The moving of
$amount_1 = $data->digit_1;
$amount_2 = $data->digit_2;
from a perfectly good object properties to 2 scalar variables is totally unnecessary why store everything twice, you will eventually run out of memory, but more importantly if you leave them in the object and test them using if ($data->digit_1 == $$data->digit_2) { its never confusing where this data came from!
Also testing the digits at the top of the loop only to set ANOTHER scalar variable to use later at the bottom of the loop is a waste of time. Those properties dont change between top and bottom of the loop so test the actual object where you want to make the decision and then put out the required HTML right there and then. Another potential confusion averted and 8 to 16 bytes of memory not wasted!
$service = $db->query("SELECT * FROM `system` ORDER BY provider, artist");
$current_provider = NULL;
while ($data = $service->fetch_object()) {
if ( $current_provider != $data->provider ) {
if ( $current_provider !== NULL ) {
echo '</div>close container in case of current_provider is already set';
}
echo '<div class="provider">open a new container in case of current_provider is not like data->provider or empty';
$current_provider = $data->provider;
}
echo 'some styling for each object';
// at this point we test the 2 digits and if equal
// add an extra bit of HTML to the output
if ($data->digit_1 == $data->digit_2) {
echo '<div class="specific">if the amount_1 same like amount_2 for a single object add only for this object an certain div';
} else {
echo '<div>show standard container even on specific object';
}
echo '</div>;
}
To avoid playing with opening and closing element better to store them in array and in the end output them.
Look at my example, it's simple:
$service = $db->query("SELECT * FROM `system` ORDER BY provider, artist");
$provider = NULL;
$lines = array();
while ($data = $service->fetch_object()) {
$close = false;
if ($something === $another) {
$close = true;
}
if ( $provider != $data->provider ) {
$lines[] = '<div class="provider">'.$data->provider.'</div>';
$provider = $data->provider;
}
if ($close === true ) {
$lines[] = '<div class="specific">add container for just a specific object when close === true within the while loop</div>';
} else {
$lines[] = '<div>show standard container on specific object</div>';
}
}
foreach($lines AS $line) {
echo $line;
}
I am trying to extend a Pico navigation plugin to exclude items from the navigation tree where the page's utilizes Twig template engine header tags.
My question is how do I get specific header tags from the .md files in the below PHP function and filter them to be excluded in the navigation tree?
The plugin currently implements the ability to omit items (pages and folders) from the tree with the following settings in a config.php file:
// Exclude pages and/or folders from navigation header
$config['navigation']['hide_page'] = array('a Title', 'another Title');
$config['navigation']['hide_folder'] = array('a folder', 'another folder');
The current function in the plugs' file uses the above config.php as follows:
private function at_exclude($page) {
$exclude = $this->settings['navigation'];
$url = substr($page['url'], strlen($this->settings['base_url'])+1);
$url = (substr($url, -1) == '/') ? $url : $url.'/';
foreach ($exclude['hide_page'] as $p) {
$p = (substr($p, -1*strlen('index')) == 'index') ? substr($p, 0, -1*strlen('index')) : $p;
$p = (substr($p, -1) == '/') ? $p : $p.'/';
if ($url == $p) {
return true;
}
}
foreach ($exclude['hide_folder'] as $f) {
$f = (substr($f, -1) == '/') ? $f : $f.'/';
$is_index = ($f == '' || $f == '/') ? true : false;
if (substr($url, 0, strlen($f)) == $f || $is_index) {
return true;
}
}
return false;
}
I need to add the ability of omitting items (or pages) from the tree using the Twig header tags 'Type' and 'Status' like so in the .md files:
/*
Title: Post01 In Cat01
Description: This post01 in cat01
Date: 2013-10-28
Category:
Type: post // Options: page, post, event, hidden
Status: draft // Options: published, draft, review
Author: Me
Template:
*/
...
The MarkDown content . . .
So if a user wants to remove items tagged with "post" in the 'type' tag and/or "draft" from the 'draft' tag (see header above), they would then add the linked tags in the array below that I added into the config.php:
// Exclude taged items:
$config['navigation']['hide_status'] = array('draft', 'maybe some other status tag');
$config['navigation']['hide_type'] = array('post', 'etc');
I also added the following to the bottom of the at_exclude() function:
private function at_exclude($page) {
. . .
foreach ($exclude['hide_staus'] as $s) {
$s = $headers['status'];
if ($s == 'draft' || 'review') {
return true;
}
}
foreach ($exclude['hide_type'] as $t) {
$t = $headers['type'];
if ($t == 'post' || 'hidden') {
return true;
}
return true;
}
. . .
This is obviously not working for me (because my PHP knowledge is limited). Any help with what I am missing, doing wrong or how I can add this functionality will be greatly appreciated.
I dived into the (not so beautiful) Pico code and those are my findings.
First of all, Pico doesn't read every custom field you add to the content header.
Instead, it has an internal array of fields to parse. Luckily, an hook called before_read_file_meta is provided to modify the array.
In at_navigation.php we'll add:
/**
* Hook to add custom file meta to the array of fields that Pico will read
*/
public function before_read_file_meta(&$headers)
{
$headers['status'] = 'Status';
$headers['type'] = 'Type';
}
This will result in Pico reading the headers, but it won't add the fields to the page data yet. We need another hook, get_page_data. In the same file:
/**
* Hook to add the custom fields to the page data
*/
public function get_page_data(&$data, $page_meta)
{
$data['status'] = isset($page_meta['status']) ? $page_meta['status'] : '';
$data['type'] = isset($page_meta['type']) ? $page_meta['type'] : '';
}
Now, in the at_exclude function, we can add the new logic.
(Instead of cycling, We configure an array of status and types we want to exclude, and we'll check if there is a match with the current page status/type)
private function at_exclude($page)
{
[...]
if(in_array($page['status'], $exclude['status']))
{
return true;
}
if(in_array($page['type'], $exclude['type']))
{
return true;
};
return false;
}
Now let's customize our config.php (I standardized the configuration with the plugin standards):
$config['at_navigation']['exclude']['status'] = array('draft', 'review');
$config['at_navigation']['exclude']['type'] = array('post');
All done!
But if you are just starting out, I'd advise you to use a more mature and recent flat file cms. Unless you are stuck with PHP5.3
I decided to simplify the function to omit it from the config.php since it really isn't needed to be set by the end-user. By doing so, the at_exclude() function is much simpler and quicker on the back-end by omitting all the checks via other files:
at_exclude {
. . .
$pt = $page['type'];
$ps = $page['status'];
$home = ($pt == "home");
$post = ($pt == "post");
$event = ($pt == "event");
$hide = ($pt == "hide");
$draft = ($ps == "draft");
$review = ($ps == "review");
$type = $home || $post || $event || $hide;
$status = $draft || $review;
if ( $type || $status ) {
return true;
};
return false;
}
Obviously it needs some tidying up but you get the picture. Thnx
Trying to locate the first blank cell in a column. The idea is to pick a column that I know has to have a value (in this case, JOB_NUMBER) and scan through it until a blank cell is found. The code below, in my mind, should do that. However, it never stops. I imagine it is stuck in the while loop, but I don't understand why.
Code:
<?php
require('./Classes/PHPExcel/IOFactory.php');
ini_set('max_execution_time', 800);
ini_set('memory_limit', 2000000000);
$inputFileType = 'Excel2007';
$inputFileName = $_FILES['file']['tmp_name'];
class MyReadFilter implements PHPExcel_Reader_IReadFilter {
public function __construct($fromColumn, $toColumn) {
$this->columns = array();
$toColumn++;
while ($fromColumn !== $toColumn) {
$this->columns[] = $fromColumn++;
}
}
public function readCell($column, $row, $worksheetName = '') {
// Read columns from 'A' to 'AF'
if (in_array($column, $this->columns)) {
return true;
}
return false;
}
}
$filterSubset = new MyReadFilter('A', 'AF');
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$objReader->setReadFilter($filterSubset);
$objReader->setLoadSheetsOnly( array("NORTH") );
$objPHPExcelReader = $objReader->load($inputFileName);
$r = 3500;
while(isset($maxrow_north) != 1){
$cellvalue = $objPHPExcelReader->getSheetByName('NORTH')->getCellByColumnAndRow(2, $r);
if(isset($cellvalue) != 1){
$maxrow_north = $r;
} elseif($r > 4000) {
echo "It's over 4000!";
} else {
$r = $r++;
}
}
echo $maxrow_north;
?>
Some more background
I am having admins upload .xlsx .xls or .csv files into an html form. The code, above, is the handler. I have limited the number of columns seen because the original creator of the .xlsx file thought it would be a great idea to have the columns go all the way out to XCF.
The rows also go all the way out to somewhere around 10,000. So, I want to find the first blank row and stop there.
TIA!
Don't use
if(isset($cellvalue) != 1){
A cell value always exists even if it's an empty string or a null: and you're not testing the actual cell value, but the existence of a cell.... simply get() ting a cell will create a new empty cell object if one didn't already exist
You need to test the actual value stored in the cell
if($cellvalue->getValue() === NULL || $cellvalue->getValue() === '') {
$maxrow_north = $r;
And if you're trying to find the first blank cell in the column, then break once you've found it rather than carry on iterating till you reach your max
(Note, doesn't check for rich text in cells)
EDIT
Example, that also allows for merged cells
function testInMergeRangeNotParent($objWorksheet, $cell)
{
$inMergeRange = false;
foreach($objWorksheet->getMergeCells() as $mergeRange) {
if ($cell->isInRange($mergeRange)) {
$range = PHPExcel_Cell::splitRange($mergeRange);
list($startCell) = $range[0];
if ($cell->getCoordinate() !== $startCell) {
$inMergeRange = true;
}
break;
}
}
return $inMergeRange;
}
$column = 2; // Column to check
$max = 4000;
echo 'Get First blank row in column ', $column, PHP_EOL;
$r = 3500; // Starting row
while(true){
$cell = $objPHPExcelReader->getSheetByName('NORTH')->getCellByColumnAndRow($column, $r);
if ($cell->getValue() === NULL &&
!testInMergeRangeNotParent($objPHPExcelReader->getSheetByName('NORTH'), $cell)) {
break;
}elseif($r > $max) {
echo "It's over $max !";
break;
}
$r++;
}
echo 'First blank row in column ', $column, ' is ', $r, PHP_EOL;
So this is the first PHP script (if it's even called that?) that I've ever written from scratch, and I'm having an issue in that when it's applied to the existing (and otherwise working) page, the page shows up blank. I was hoping one of the many people who are better and more experienced than I am can take a look and find what is no doubt a blatant syntax error. Thank you in advance to anyone that shows me the light!
<?php
$sql = "SELECT * FROM 'jos_downloads_files'";
$rows = $db->fetch_all_array($sql);
foreach($rows as $row) {
$filename = $row['filetitle'];
$filepath = $row['realname'];
$featured = $row['featured'];
$id = $row['containerid'];
}
foreach ($id as $containername) {
if ($id == 2) {
$containername ="Incidental Information";
}
if ($id == 3) {
$containername ="Monitoring Reports";
}
if ($id == 4) {
$containername ="Agendas";
}
if ($id == 5) {
$containername ="Decision Prep";
}
if ($id == 6) {
$containername ="Agendas";
}
if ($id == 7) {
$containername ="Policy Governance";
}
echo '<div class = "moduletable">
<h3>' . $containername . '</h3>';
foreach ($featured as $featureedtrue) {
if ($featuredtrue == 1) {
echo '<ul class="weblinks">
<li>
<a>' . $filename . '</a>
</li>';
}
}
}
?>
As celeriko mentioned, you never declared $db prior to using it. As Xatenev mentioned, I'm not sure what fetch_all_array is, perhaps fetchAll()? Finally, as Valery Statichny mentioned, $id will never be an array.
In the future, it is helpful to turn on error reporting so that you can see where your scripts are crashing. You can do so by adding the following:
ini_set('display_errors',1);
error_reporting(E_ALL);
In production environments, turn error reporting off so that users don't see error messages.
Edit
More from Xatenev:
Why are you looping foreach $featured? $featured can only be ONE entry at the moment. You have to write $featured[] = $row['featured']; to get an array where u can loop through. Same to $id. 5. You are looping through $featured but use $filename then? $filename will print an array EVERY TIME you loop through $featured. So for example when you have 100 entries in $featured, you have 100x the same content when you print out $filename. – Xatenev
function func() {
// ...
}
I have the function name "func", but not its definition.
In JavaScript, I'd just use alert() to see the definition.
Is there a similar function in PHP?
You can use the methods getFileName(), getStartLine(), getEndLine() defined in ReflectionFunctionAbstract to read the source code of functions/methods from their source file (if there is any).
e.g. (without error handling)
<?php
printFunction(array('Foo','bar'));
printFunction('bar');
class Foo {
public function bar() {
echo '...';
}
}
function bar($x, $y, $z) {
//
//
//
echo 'hallo';
//
//
//
}
//
function printFunction($func) {
if ( is_array($func) ) {
$rf = is_object($func[0]) ? new ReflectionObject($func[0]) : new ReflectionClass($func[0]);
$rf = $rf->getMethod($func[1]);
}
else {
$rf = new ReflectionFunction($func);
}
printf("%s %d-%d\n", $rf->getFileName(), $rf->getStartLine(), $rf->getEndLine());
$c = file($rf->getFileName());
for ($i=$rf->getStartLine(); $i<=$rf->getEndLine(); $i++) {
printf('%04d %s', $i, $c[$i-1]);
}
}
I don't know of one.See code at bottom. There is a function to list all the defined functions. There's another to get the values of all the arguments to the current function, and the number of arguments. And there's one to see if a function exists. But there doesn't seem to be one to name the current function, nor any means of listing formal parameters.
Even when a runtime error occurs, it doesn't list a call stack, nor state the function that's active:
PHP Warning: Division by zero in t.php on line 6
Edit: For the code to identify where it is, add this:
echo "At line " .__LINE__ ." of file " . __FILE__ ."\n";
It gives the output
At line 7 of file /home/wally/t.php
Edit 2: I found this function in my code which looks to be what you want:
function traceback ($showvars)
{
$s = "";
foreach (debug_backtrace($showvars) as $row)
{
$s .= "$row[file]#$row[line]: ";
if(isset($row['class']))
$s .= "$row[class]$row[type]$row[function]";
else $s .= "$row[function]";
if (isset($row['args']))
$s .= "('" . join("', '",$row['args']) . "')";
$s .= "<br>\n";
}
return $s;
}
For example, it produces:
[wally#zf ~]$ php -f t.php
/home/wally/t.php#24: traceback('1')<br>
/home/wally/t.php#29: t('1', '2', '3')<br>
/home/wally/t.php#30: x('2', '1')<br>
/home/wally/t.php#31: y('2', '1')<br>
/home/wally/t.php#33: z('1', '2')<br>