PHPExcel ReadFilter doesnt work - php

I'm still stuck with symfony2 and phpexcel..
First I don't know why my HTML writer doesn't generate <thead></thead> tags..
I don't understand why blanks columns still displaying as you can see on this screenshot, a ReadFilter is applied:
I just want to load the first 13 columns:
public function showClientAction()
{
$excel = glob(''.path.'\\'.tofile.'\\'.$file.'.{xlsx,xls,xlsm,xlsm.ink}', GLOB_BRACE);
$filterSubset = new \PHPExcel_Reader_DefaultReadFilter('A','N');
$objReader = \PHPExcel_IOFactory::createReaderForFile($excel[0]);
$objReader->setReadFilter($filterSubset);
/** Read the list of worksheet names and select the one that we want to load **/
$worksheetList = $objReader->listWorksheetNames($excel[0]);
$sheetname = $worksheetList[0];
/** Advise the Reader of which WorkSheets we want to load **/
$objReader->setLoadSheetsOnly($sheetname);
$objPHPExcel = $objReader->load($excel[0]);
$writer = \PHPExcel_IOFactory::createWriter($objPHPExcel, "HTML");
$writer->generateSheetData();
$writer->generateStyles();
return $this->render('SocietyPerfclientBundle:Default:testexcel.html.twig', array(
'excelHtml'=>$writer
));
}
My Filter :
class PHPExcel_Reader_DefaultReadFilter 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;
}
}
I can't understand why these blank column are here...
ReadFilter is not working with HTML Writer ?
I can't just do :
.column14 { display:none;!important;}
.column15 { display:none;!important;}
.column16 { display:none;!important;}
.column17 { display:none;!important;}
etc...
Because I use jQuery Plugin "floatThead" to create a fixed <thead></thead>
In my view :
var table = $('#sheet0'); // select the table of interest
var thead = $('<thead/>').prependTo(table);
// create <thead></thead>
table.find('tbody tr.row0').appendTo(thead);
// Now the table is ready to have your chosen method applied to fix the position of the thead.
$('table.sheet0').floatThead({
position: 'fixed',
index: '8',
overflow: 'auto'
});
Please help me..

i think :
$this->columns[] = $fromColumn++;
do nothing .
try :
for ($i = $fromColumn; $i != $toColumns ; $i++)
{
$this->columns[$i]=$i;
}

Related

phpoffice/phpspreadsheet - How to exclude no value cells from getHighestRow()

$eanStyle = new \PHPExcel_Style();
$eanStyle->getNumberFormat()->applyFromArray([
'code' => '0000000000000'
]);
/* apply styles */
$mainSheet->duplicateStyle($eanStyle, 'A2:A10000');
Code above generates .xlsx template file, user enters data (7 rows) and upload file and then:
$mainSheet->getHighestRow('A'); // retruns 10000 instead of 8 (7 rows + header)
Thanks in advance for help.
I would advise you create a read filter to read only specific rows and columns. This would prevent the other empty rows being included:
$inputFileType = 'Xls';
$inputFileName = './sampleData/example1.xls';
$sheetname = 'Data Sheet #3';
/** Define a Read Filter class implementing \PhpOffice\PhpSpreadsheet\Reader\IReadFilter */
class MyReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter {
public function readCell($column, $row, $worksheetName = '') {
// Read rows 1 to 7 and columns A to E only
if ($row >= 1 && $row <= 7) {
if (in_array($column,range('A','E'))) {
return true;
}
}
return false;
}
}
/** Create an Instance of our Read Filter **/
$filterSubset = new MyReadFilter();
/** Create a new Reader of the type defined in $inputFileType **/
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType);
/** Tell the Reader that we want to use the Read Filter **/
$reader->setReadFilter($filterSubset);
/** Load only the rows and columns that match our filter to Spreadsheet **/
$spreadsheet = $reader->load($inputFileName);

PHPExcel setWidth doesnt work

i convert an excel file to HTML Table with PHPExcel
(PHPspreadsheet) with Symfony 2.5
I'm trying to set a filter to only load the range ('A','N') , the first 13 columns. not working..
I'm also trying to set the Width of the 'N' Column. not working..
when i dump the column's width value is correct..
I can increase the columns width but not decrease them..
it looks like the text inside the cell is defining the cell's width automatically..
Here is my controller :
public function showClientAction($client)
{
$excel = glob(''.path.'\\'.path.'\\filename_' .$client.'.{xlsx,xls,xlsm,xlsm.ink}', GLOB_BRACE);
$filterSubset = new \PHPExcel_Reader_DefaultReadFilter(1,1000,range('A','N'));
$objReader = \PHPExcel_IOFactory::createReaderForFile($excel[0]);
$objReader->setReadFilter($filterSubset);
/** Read the list of worksheet names and select the one that we want to load **/
$worksheetList = $objReader->listWorksheetNames($excel[0]);
$sheetname = $worksheetList[0];
/** Advise the Reader of which WorkSheets we want to load **/
$objReader->setLoadSheetsOnly($sheetname);
$objPHPExcel = $objReader->load($excel[0]);
$objPHPExcel->getActiveSheet()->getColumnDimensionByColumn('13')->setAutoSize(false);
$objPHPExcel->getActiveSheet()->getColumnDimensionByColumn('13')->setWidth(2.5);
// OUTPUT is : int (13) applied correctly
var_dump($objPHPExcel->getActiveSheet()->getColumnDimensionByColumn('13'));
$writer = \PHPExcel_IOFactory::createWriter($objPHPExcel, "HTML");
$writer->generateSheetData();
$writer->generateStyles();
return $this->render('SocPerfclientBundle:Default:testexcel.html.twig', array(
'excelHtml'=>$writer,
'stylesExcel'=>$writer,
'client'=>$nom_client
));
}
My filter :
class PHPExcel_Reader_DefaultReadFilter implements PHPExcel_Reader_IReadFilter
{
public $_startRow = 0;
public $_endRow = 0;
public $_columns = array();
/** Get the list of rows and columns to read */
public function __construct($startRow, $endRow, $columns) {
$this->_startRow = $startRow;
$this->_endRow = $endRow;
$this->_columns = $columns;
}
public function readCell($column, $row, $worksheetName = '') {
// Only read the rows and columns that were configured
if ($row >= $this->_startRow && $row <= $this->_endRow) {
if (in_array($column,$this->_columns)) {
return true;
}
}
return false;
}
}
my view :
{{ excelHtml.generateSheetData | raw }}
{{ stylesExcel.generateStyles | raw }}
Here a screenshot html view :
We can see the "RCA" column still having the initial width.. my setWidth isnt applied..
if i change the link by a shorter word like : yes.docx , the column decreases.

How to generate a custom CSV export?

I have a page called EventPage that I am managing via a Model admin. Also using Catalog manager: https://github.com/littlegiant/silverstripe-catalogmanager
Question is I need to be able to export all the expired events (And all of the fields).
I have an 'EndDate' => 'Date', field on the EventPage.
So I want to only show EventPages in my CSV export where the EndDate is GreaterThanOrEqual to todays date e.g Expired.
The following generates an CSV export button, but currently it is exporting all the fields, where as I want to filter it so we only show the expired events.
How do I go about this?
<?php
class EventAdmin extends CatalogPageAdmin {
public $showImportForm = false;
private static $managed_models = array(
'EventPage',
'EventCategory',
'EventSubmission',
);
private static $url_segment = 'events';
private static $menu_title = 'Events';
public function getEditForm($id = null, $fields = null) {
$form = parent::getEditForm($id, $fields);
$gridFieldName = 'EventPage';
$gridField = $form->Fields()->fieldByName($gridFieldName);
if ($gridField) {
$gridField->getConfig()->addComponent(new GridFieldExportButton());
}
return $form;
}
}
We can create a custom export button to filter the list of items before exporting them.
First we create a GridFieldExportExpiredEventsButton which extends GridFieldExportButton. This is a complete copy of the current SilverStripe 3.5 generateExportFileData function but with an added filterByCallback on the $items list to filter items that have an EndDate < date('Y-m-d').
class GridFieldExportExpiredEventsButton extends GridFieldExportButton {
public function getHTMLFragments($gridField) {
$button = new GridField_FormAction(
$gridField,
'export',
'Export expired events',
'export',
null
);
$button->setAttribute('data-icon', 'download-csv');
$button->addExtraClass('no-ajax action_export');
$button->setForm($gridField->getForm());
return array(
$this->targetFragment => '<p class="grid-csv-button">' . $button->Field() . '</p>',
);
}
public function generateExportFileData($gridField) {
$separator = $this->csvSeparator;
$csvColumns = $this->getExportColumnsForGridField($gridField);
$fileData = '';
$member = Member::currentUser();
if($this->csvHasHeader) {
$headers = array();
// determine the CSV headers. If a field is callable (e.g. anonymous function) then use the
// source name as the header instead
foreach($csvColumns as $columnSource => $columnHeader) {
$headers[] = (!is_string($columnHeader) && is_callable($columnHeader)) ? $columnSource : $columnHeader;
}
$fileData .= "\"" . implode("\"{$separator}\"", array_values($headers)) . "\"";
$fileData .= "\n";
}
//Remove GridFieldPaginator as we're going to export the entire list.
$gridField->getConfig()->removeComponentsByType('GridFieldPaginator');
$items = $gridField->getManipulatedList();
$items = $items->filterByCallback(function($item) {
// The following line modifies what items are filtered. Change this to change what items are filtered
return $item->EndDate < date('Y-m-d');
});
// #todo should GridFieldComponents change behaviour based on whether others are available in the config?
foreach($gridField->getConfig()->getComponents() as $component){
if($component instanceof GridFieldFilterHeader || $component instanceof GridFieldSortableHeader) {
$items = $component->getManipulatedData($gridField, $items);
}
}
foreach($items->limit(null) as $item) {
if(!$item->hasMethod('canView') || $item->canView($member)) {
$columnData = array();
foreach($csvColumns as $columnSource => $columnHeader) {
if(!is_string($columnHeader) && is_callable($columnHeader)) {
if($item->hasMethod($columnSource)) {
$relObj = $item->{$columnSource}();
} else {
$relObj = $item->relObject($columnSource);
}
$value = $columnHeader($relObj);
} else {
$value = $gridField->getDataFieldValue($item, $columnSource);
if($value === null) {
$value = $gridField->getDataFieldValue($item, $columnHeader);
}
}
$value = str_replace(array("\r", "\n"), "\n", $value);
$columnData[] = '"' . str_replace('"', '""', $value) . '"';
}
$fileData .= implode($separator, $columnData);
$fileData .= "\n";
}
if($item->hasMethod('destroy')) {
$item->destroy();
}
}
return $fileData;
}
}
The extra line that we have added that filters the export items is:
return $item->EndDate < date('Y-m-d');
Alter this to alter the list of items that are exported. I have set this to only return items which have an EndDate that is in the past. Change this as you need.
We then add this export button to our grid field in our event model admin:
class EventAdmin extends CatalogPageAdmin {
private static $managed_models = array(
'EventPage'
);
public function getEditForm($id = null, $fields = null) {
$form = parent::getEditForm($id);
if ($this->modelClass == 'EventPage') {
$gridField = $form->Fields()->fieldByName($this->modelClass);
$gridField->getConfig()->removeComponentsByType('GridFieldExportButton');
$gridField->getConfig()->addComponent(new GridFieldExportExpiredEventsButton('buttons-before-left'));
}
return $form;
}
}
This was originally two answers...
To limit the fields
Have you had a look at the GridFieldExportButton class ?
The constructor
/**
* #param string $targetFragment The HTML fragment to write the button into
* #param array $exportColumns The columns to include in the export
*/
public function __construct($targetFragment = "after", $exportColumns = null) {
$this->targetFragment = $targetFragment;
$this->exportColumns = $exportColumns;
}
So you should be able to pass $exportColumns as an argument.
in your code that would be
if ($gridField) {
$gridField->getConfig()->addComponent(new GridFieldExportButton("after", ["field1", "field2"]));
}
OR - EVEN BETTER SOLUTION
you can define your summary fields on EventPage as such
private static $summary_fields = ["FIeld1", "Field2"];
Then make sure your flush and it should use that as fields.
To filter which items to export in your CSV
So in this case, I think you should create a new class that extends GridFieldExportButton (maybe called EventPageCSVExportButton or something) and override the methods you want. In your case it would probably be generateExportFileData(), just do a check in the loop and exclude data you don't want.
Then use that new class in your EventAdmin.
Have you had a look at the GridFieldExportButton class ?
The constructor
/**
* #param string $targetFragment The HTML fragment to write the button into
* #param array $exportColumns The columns to include in the export
*/
public function __construct($targetFragment = "after", $exportColumns = null) {
$this->targetFragment = $targetFragment;
$this->exportColumns = $exportColumns;
}
So you should be able to pass $exportColumns as an argument.
in your code that would be
if ($gridField) {
$gridField->getConfig()->addComponent(new GridFieldExportButton("after", ["field1", "field2"]));
}
OR - EVEN BETTER SOLUTION
you can define your summary fields on EventPage as such
private static $summary_fields = ["FIeld1", "Field2"];
Then make sure your flush and it should use that as fields.

Laravel 5 - Clean code, where to keep business logic (controller example)

Below example of 'store' method of my controller Admin/MoviesController. It already seems quite big, and 'update' method will be even bigger.
The algoritm is:
Validate request data in CreateMovieRequest and create new movie
with all fillable fields.
Upload poster
Fill and save all important, but not required fields (Meta title, Meta Description..)
Then 4 blocks of code with parsing and attaching to movie of Genres, Actors, Directors, Countries.
Request of IMDB's rating using third-party API
My questions:
Should I just move all this code to Model and divide it into smaller methods like: removeGenres($id), addGenres(Request $request), ...
Are there some best practices? I'm talking not about MVC, but Laravel's features. At the moment to keep some logic behind the scene I'm using only Request for validation.
public function store(CreateMovieRequest $request) {
$movie = Movies::create($request->except('poster'));
/* Uploading poster */
if ($request->hasFile('poster')) {
$poster = \Image::make($request->file('poster'));
$poster->fit(250, 360, function ($constraint) {
$constraint->upsize();
});
$path = storage_path() . '/images/movies/'.$movie->id.'/';
if(! \File::exists($path)) {
\File::makeDirectory($path);
}
$filename = time() . '.' . $request->file('poster')->getClientOriginalExtension();
$poster->save($path . $filename);
$movie->poster = $filename;
}
/* If 'Meta Title' is empty, then fill it with the name of the movie */
if ( empty($movie->seo_title) ) {
$movie->seo_title = $movie->title;
}
/* If 'Meta Description' is empty, then fill it with the description of the movie */
if ( empty($movie->seo_description) ) {
$movie->seo_description = $movie->description;
}
// Apply all changes
$movie->save();
/* Parsing comma separated string of genres
* and attaching them to movie */
if (!empty($request->input('genres'))) {
$genres = explode(',', $request->input('genres'));
foreach($genres as $item) {
$name = mb_strtolower(trim($item), 'UTF-8');
$genre = Genre::where('name', $name)->first();
/* If such genre doesn't exists in 'genres' table
* then we create a new one */
if ( empty($genre) ) {
$genre = new Genre();
$genre->fill(['name' => $name])->save();
}
$movie->genres()->attach($genre->id);
}
}
/* Parsing comma separated string of countries
* and attaching them to movie */
if (!empty($request->input('countries'))) {
$countries = explode(',', $request->input('countries'));
foreach($countries as $item) {
$name = mb_strtolower(trim($item), 'UTF-8');
$country = Country::where('name', $name)->first();
if ( empty($country) ) {
$country = new Country();
$country->fill(['name' => $name])->save();
}
$movie->countries()->attach($country->id);
}
}
/* Parsing comma separated string of directors
* and attaching them to movie */
if (!empty($request->input('directors'))) {
$directors = explode(',', $request->input('directors'));
foreach($directors as $item) {
$name = mb_strtolower(trim($item), 'UTF-8');
// Actors and Directors stored in the same table 'actors'
$director = Actor::where('fullname', trim($name))->first();
if ( empty($director) ) {
$director = new Actor();
$director->fill(['fullname' => $name])->save();
}
// Save this relation to 'movie_director' table
$movie->directors()->attach($director->id);
}
}
/* Parsing comma separated string of actors
* and attaching them to movie */
if (!empty($request->input('actors'))) {
$actors = explode(',', $request->input('actors'));
foreach($actors as $item) {
$name = mb_strtolower(trim($item), 'UTF-8');
$actor = Actor::where('fullname', $name)->first();
if ( empty($actor) ) {
$actor = new Actor();
$actor->fill(['fullname' => $name])->save();
}
// Save this relation to 'movie_actor' table
$movie->actors()->attach($actor->id);
}
}
// Updating IMDB and Kinopoisk ratings
if (!empty($movie->kinopoisk_id)) {
$content = Curl::get('http://rating.kinopoisk.ru/'.$movie->kinopoisk_id.'.xml');
$xml = new \SimpleXMLElement($content[0]->getContent());
$movie->rating_kinopoisk = (double) $xml->kp_rating;
$movie->rating_imdb = (double) $xml->imdb_rating;
$movie->num_votes_kinopoisk = (int) $xml->kp_rating['num_vote'];
$movie->num_votes_imdb = (int) $xml->imdb_rating['num_vote'];
$movie->save();
}
return redirect('/admin/movies');
}
You need to think on how you could re-utilize the code if you need to use it in another classes or project modules. For starting, you could do something like this:
Movie model, can improved in order to:
Manage the way on how the attributes are setted
Create nice functions in functions include/manage the data of relationships
Take a look how the Movie implements the functions:
class Movie{
public function __construct(){
//If 'Meta Title' is empty, then fill it with the name of the movie
$this->seo_title = empty($movie->seo_title)
? $movie->title
: $otherValue;
//If 'Meta Description' is empty,
//then fill it with the description of the movie
$movie->seo_description = empty($movie->seo_description)
? $movie->description
: $anotherValue;
$this->updateKinopoisk();
}
/*
* Parsing comma separated string of countries and attaching them to movie
*/
public function attachCountries($countries){
foreach($countries as $item) {
$name = mb_strtolower(trim($item), 'UTF-8');
$country = Country::where('name', $name)->first();
if ( empty($country) ) {
$country = new Country();
$country->fill(['name' => $name])->save();
}
$movie->countries()->attach($country->id);
}
}
/*
* Update Kinopoisk information
*/
public function updateKinopoisk(){}
/*
* Directors
*/
public function attachDirectors($directors){ ... }
/*
* Actores
*/
public function attachActors($actors){ ... }
/*
* Genders
*/
public function attachActors($actors){ ... }
}
Poster, you may considere using a service provider (I will show this example because I do not know your Poster model
looks like):
public class PosterManager{
public static function upload($file, $movie){
$poster = \Image::make($file);
$poster->fit(250, 360, function ($constraint) {
$constraint->upsize();
});
$path = config('app.images') . $movie->id.'/';
if(! \File::exists($path)) {
\File::makeDirectory($path);
}
$filename = time() . '.' . $file->getClientOriginalExtension();
$poster->save($path . $filename);
return $poster;
}
}
Config file
Try using config files to store relevant application constanst/data, for example, to store movie images path:
'images' => storage_path() . '/images/movies/';
Now, you are able to call $path = config('app.images'); globally. If you need to change the path only setting the config file is necessary.
Controllers as injected class.
Finally, the controller is used as a class where you only need to inject code:
public function store(CreateMovieRequest $request) {
$movie = Movies::create($request->except('poster'));
/* Uploading poster */
if ($request->hasFile('poster')) {
$file = $request->file('poster');
$poster = \PosterManager::upload($file, $movie);
$movie->poster = $poster->filename;
}
if (!empty($request->input('genres'))) {
$genres = explode(',', $request->input('genres'));
$movie->attachGenders($genders);
}
// movie->attachDirectors();
// movie->attachCountries();
// Apply all changes
$movie->save();
return redirect('/admin/movies');
}

Trying to display headers on multiple pages with FPDF

I am using FPDF to create a report from a Mysql database. The PDF is created fine but I would like to display the headers for each column at the top of every page for easier navigation. Does anyone have any idea on how to do this?
Thanks for the help.
try to extend fpdf class, and implement your Ln function: every time you add a line, decrement counter of line number, and when is 0, add new page with header.
class _PDF extends FPDF
{
var $_num_Rows = null;
var $_heightCell = null;
const NUM_ROWS = 48; // set number line in page
function _PDF($orientation = 'P',$unit = 'mm' ,$page = 'A4')
{
$this->_numRows = self::NUM_ROWS;
$this->_heightCell = 4;
$this->SetAutoPageBreak(true,10);
$this->FPDF($orientation, $unit, $page);
}
public function init_numRows()
{
$this->_numRows = self::NUM_ROWS;
}
public function dec_numRows()
{
$this->_numRows--;
}
function Ln()
{
if ( $this->_numRows )
{
parent::Ln();
$this->dec_numRows();
}
else
{
$this->addHeader();
}
}
function addHeader()
{
$this->init_numRows();
$this->AddPage();
// ...
}
}
$pdf = new _PDF();

Categories