Zebra Stripping with phpspreadsheet - php

I was looking around for examples and/or guides on how to use phpspreadsheet's conditional formatting. Specifically looking so that I could zebra stripe my spreadsheet in a way that allows for resorting of the sheet. I came up very empty with only a few spotty SO questions using the old PHPExcel library and just one example from the documentation. Given that zebra stripes are pretty common style to apply I was mildly outraged that I couldn't find a copy/paste job. So I figured it out myself and now will leave it here for posterity and hopefully myself in a few years.
I am actually going to spell out a lot more as I have used phpspreadsheet for a few years now and out of the box the defaults don't really give you a nice looking spreadsheet. I will also note I do not care how it looks; its just data to me but, my bosses must have quality formatting.

Zebra Stripping
$range = 'A3:'.$spreadsheet->getActiveSheet()->getHighestDataColumn.
$spreadsheet->getActiveSheet()->getHighestDataRow();
$conditional1 = new \PhpOffice\PhpSpreadsheet\Style\Conditional();
$conditional1->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_EXPRESSION)
->setOperatorType(\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_EQUAL)
->addCondition('MOD(ROW(),2)=0');
$conditional1->getStyle()->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)
->getStartColor()->setARGB('DFDFDF');
$conditional1->getStyle()->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)
->getEndColor()->setARGB('DFDFDF');
$spreadsheet->getActiveSheet()->getStyle($range)->setConditionalStyles([$conditional1]);
The range starts at A3 because I have a logo in row 1 and headers in row 2. Also you can apply as many conditions as you need which is why the final setConditionalStyles is wrapped in an array.
Freeze 1 columns and 2 rows
$sheet->freezePane('B2');
This will freeze that cell B2 and everything to the left and above. Super helpful for long and wide sheets letting you keep your primary_key and headers in view at all times.
Insert Logo in 1st cell
$spreadsheet->getActiveSheet()->insertNewRowBefore(1, 1);
$drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
$drawing->setCoordinates('A1');
$drawing->getShadow()->setVisible(true);
$drawing->setName({{alt text}});
$drawing->setPath(resource_path().{{filepath}});
$drawing->setWorksheet($spreadsheet->getActiveSheet());
$spreadsheet->getActiveSheet()->getRowDimension('1')->setRowHeight(55);
Hiding a sheet
$spreadsheet->getActiveSheet()->setSheetState('veryHidden');
This is one thing I really like to do as I have written some automation that relies on a user making
edits to a spreadsheet and returning the sheet to a specific email address that I can access via api and download and reprocess based on the edits to the sheet. I use this mainly for storing information our erp needs to access records such as a customer_id or contract_number so that when the sheet comes back I don't have to re-query ident info I can just grab it from the hidden sheet. Bonus of using very hidden is a normal user can't unhide it unless they know enough to open the dev window and get in VB script.
Put Filters on all headers
$dimensions = 'A2' . ':' . $spreadsheet->getActiveSheet()->getHighestDataColumn() .
$spreadsheet->getActiveSheet()->getHighestDataRow();
$spreadsheet->getActiveSheet()->setAutoFilter($dimensions);
$autoFilter = $spreadsheet->getActiveSheet()->getAutoFilter();
$autoFilter->showHideRows();
The trick on this one is the last line it won't actually apply the filters until there is some kind of action so I just grabbed a random function that wouldn't have any effect on my sheet.
Auto Width Columns
$colNumber = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString(
$spreadsheet->getActiveSheet()->getHighestDataColumn);
for ($col = 1; $col <= $colNumber; $col++) {
$colAlpha = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($col);
$spreadsheet->getActiveSheet()->getColumnDimension($colAlpha)->setAutoSize(true);
}
$sheet->calculateColumnWidths();
Making Spreadsheets with multiple tabs
First let me note I have come up with a somewhat standard structure I use whenever I make a spreadsheet and then I feed it through one function that does all of the above things. So here is my standard.
$report = json_encode([
'filepath' => 'Program Size/',
'filename' => $customer_name->company_name.' Program Size '.Carbon::parse('now')->format('Ymd').'.xlsx',
'Type' => 'buildMultiSheet',
'1' => [
'request' => [
'TabName' => 'Program Size',
'header' => array_keys($source[0]),
'body' => $source,
'formatArray'=> [
'aboveHeader'=>['Total Value:',$sum],
'zebra' => 'stripe',
'F:G' => '"$"#,##0.00_-'
]
]
],
'2' => [
'request' => [
'TabName' => 'config_sheet',
'header' => [],
'body' => $queryLog,
'formatArray'=> [
'hidden'=> 1
]
]
]
]);
This structure has every thing you need to make a spreadsheet with multiple tabs. Source for me is generally the result of a DB call. This isn't complete detail for example a tabname longer than 31 characters will freak out so you have to check for it. One of these days I might tidy things up and publish a package to go along side phpspreadsheet but, we will see if/when that ever happens.
$spreadsheet = new Spreadsheet();
$filename = $requestD["filename"];
unset($requestD["filename"]);
$filepath = $requestD["filepath"];
unset($requestD["filepath"]);
$tabCounter = 0;
$spreadsheet->setActiveSheetIndex($tabCounter);
foreach ($requestD as $tab) {
$sheet = $spreadsheet->getActiveSheet();
$body = $tab["request"]["body"];
$spreadsheet->getActiveSheet()->setTitle($tab["request"]["TabName"]);
$headers = $tab["request"]["header"];
$sheet->fromArray($headers, NULL, 'A1');
$sheet->fromArray($body, NULL, 'A2', false);
//processing here, sheet formatting , etc.
$tabCounter++;
$spreadsheet->createSheet();
$spreadsheet->setActiveSheetIndex($tabCounter);
}
//remove last empty sheet before resetting index
$spreadsheet->setActiveSheetIndex(0);
$spreadsheet->removeSheetByIndex($tabCounter);
Well that's all the tips I have. If I have done anything really dumb please do let me know how I can improve.

Related

Testing Symfony validation with Panther

I'm testing my validations and I send wrong values in all my input :
$crawler = $this->client->getCrawler();
$form = $crawler->selectButton('Créer')->form();
$form->setValues([
'Contractor[lastName]' => str_repeat('maxLength', self::OVER_MAX_LENGTH,),
'Contractor[firstName]' => str_repeat('maxLength', self::OVER_MAX_LENGTH,),
'Contractor[email]' => str_repeat('maxLength', self::OVER_MAX_LENGTH,).'#society.com',
'Contractor[phone]' => str_repeat('0', self::UNDER_MIN_LENGTH,),
'Contractor[password][password][first]' => 'first',
'Contractor[password][password][second]' => 'second',
'Contractor[status]' => 'admin.crud.user.field.choices.boss'
]);
$this->client->submitForm('Créer');
$this->client->waitFor('.invalid-feedback');
$this->client->waitForVisibility('.invalid-feedback');
$this->client->takeScreenshot('add.png');
$totalErrors = $crawler->filter('div.invalid-feedback')->count();
$errorExpected = 5;
$this->assertNotCount($totalErrors, [$errorExpected]);
When I test I ask to wait until the errors are displayed. Then I count the number of errors and I compare. The problem is when this line is test $totalErrors = $crawler->filter('div.invalid-feedback')->count(); I've got an error which say :
Facebook\WebDriver\Exception\StaleElementReferenceException: stale element reference: element is not attached to the page document.
In the screenshot, the errors are displayed.
I really don't understand why because I asked to wait for the element to be in the DOM and I had no errors.
Any idea ?
It's possible that the Crawler instance you have has a "stale" HTML in its state. I don't know the exact internals, but what helped me with a similar case was to get a fresh crawler from the Client object:
// some actions that redraws the Client HTML
$crawler = $client->getCrawler();
$totalErrors = $crawler->filter('div.invalid-feedback')->count();

phpspreadsheet: How to add some margin between a cell and the image?

When inserting an image into a cell, it gets inserted to the very left of the cell, I'd like to add some margin, how to do so?
I tried many things, I'm not the first to ask this, it's being asked since the days of PHPexcel and there's no answer, I tried every solution to no avail, like this one, which should center whatever is in the cell, it centers text but not images
function center(){
$styleArray = [
'alignment' => [
'vertical' => \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER,
'horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER,
],
];
return $styleArray;
}
$sheet->getStyle('B2')->applyFromArray(center()));
I'm not necessarily trying to center it, whatever works, all I need is some margin between the cell and the image.
use setOffsetX like this
$drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
$drawing->setWorksheet($spreadsheet->getActiveSheet());
$drawing->setPath('../assets/img/logo.png');
$drawing->setWidthAndHeight(158, 72);
$drawing->setResizeProportional(true);
$drawing->setOffsetX(10); // this is how
$drawing->setOffsetY(3); // this is how

How to add Rating to sort list in Magento 1.7

Looking for some help adding sort by Rating in Magento. I have added code snippets to toolbar.php which seem to add the sort by Rating but when trying to select it, it gets stuck until I reload the page. Any help would be greatly appreciated. Code can be found below: This is the Toolbar.php file.
// Begin new Code
$this->getCollection()->joinField('rating',
'review/review_aggregate',
'rating_summary',
'entity_pk_value=entity_id',
'{{table}}.store_id=1',
'left');
// End new Code
AND
// Add rating to "Sort by"
$_availableOrder = $this->_availableOrder;
$_availableOrder['rating'] = 'Rating';
return $_availableOrder;
$this->_availableOrder = array(
‘rating_summary’ => Mage::helper(’catalog’)->__(’Rating’),
‘price’ => Mage::helper(’catalog’)->__(’Price’),
‘newest’ => Mage::helper(’catalog’)->__(’Newest’),
‘name’ => Mage::helper(’catalog’)->__(’Name’)
);
Best is to make this in a module but here you go:
First we shall alter the way products are retrieved from the database, to include the overall rating (shown as the number of stars on the product) along with the rest of the product attributes. Copy the file app/code/core/Mage/Catalog/Block/Product/List.php to app/code/local/Mage/Catalog/Block/Product/List.php and open it for editing.
In the new List.php file find the following line (around line 86):
$this->_productCollection = $layer->getProductCollection();
After this add the following:
$this->_productCollection->joinField('rating_summary', 'review_entity_summary', 'rating_summary', 'entity_pk_value=entity_id', array('entity_type'=>1, 'store_id'=> Mage::app()->getStore()->getId()), 'left');
Now we need to add in an option so that the customer can select "Rating" as an attribute to sort by. Copy the file app/code/core/Mage/Catalog/Model/Config.php to app/code/local/Mage/Catalog/Model/Config.php and edit.
In the new Config.php file find the following code (which should start around line 298):
$options = array(
'position' => Mage::helper('catalog')->__('Position')
);
Replace with code with:
$options = array(
'position' => Mage::helper('catalog')->__('Position'),
'rating_summary' => Mage::helper('catalog')->__('Rating')
);
Now when viewing categories on your website you should have an option of "Rating" in addition to the others. Note that the sort order defaults to ascending so the lowest rated products will be displayed first. The sort order can be changed by the customer by clicking the arrow next to the drop-down box. Aside from this caveat the new sort is fairly easy to implement and extends the usefulness of the ratings.
Credits: https://www.fontis.com.au/blog/sort-products-rating

PHP OOP: how should I declare my component properties

I'm learning OOP a little and I want to get myself some good habits.
I'm writing an app which uses 'components'.
Each component is being included in component View, when $_GET['component_name'] is proper.
Components are placed in /components/component_name/ and contains files like index.php, helper.php, controller.php.
I'm doing index.php this way:
$name = "newsModule";
$helper = $name."Helper";
global $component;
$component = new $name;
$component->name = $name;
$component->template = 1;
$component->prefix = "com_";
$component->legend = array(
"time" => "create date",
"edit" => "edit",
"remove" => "delete"
);
$component->db = $component->prefix.$name;
$component->id = $_GET['id'];
$component->itemList = $helper::itemList(array(
'fields' => '*',
'db' => $component->db,
'where-field' => "title",
'where-value' => $_SESSION['keywords_'.$name]
));
Now, the $component is visible in Component View in $GLOBALS array, so I do:
$c = $GLOBALS['component'];
and using $c->db for example. And it works.
But finally - what's my point? I just think this solution is not good enough, no-oop enough etc.
I wonder if someone could share some good practices, some info and ideas about how could this code be better.
Thank you
Try these links for a good solid introduction to OOP in php.
http://code.tutsplus.com/tutorials/object-oriented-php-for-beginners--net-12762
http://www.tutorialspoint.com/php/php_object_oriented.htm
And as for good habits, find a good working example that more or less does what you want and tweak it to your needs. If you write from scratch you'll quite likely fall flat on your face at every opportunity (speaking from experience).

Netsuite: How to attach custom fields to sales orders

The documentation for Netsuite is quite lacking, they cover the basics and then let you loose to explore. Anyone without a vast knowledge of PHP trying to use their php toolkit would be on their knees begging for mercy.
At any point throughout this whole project it's been trail and error and trying to make sense out of everything until stuff started to work.
I'm stumped on assigning custom fields to sales orders, I know it has to be an object of an object of an object in order for it to tier down the xml for the soap to take over but what with what with what?
I have some code I worked that is getting somewhere but it is complaining it's not the right RecordRef type. If anyone worked with Netsuite and feels my pain please lend me your knowledge before I pull out all my hair.
Thanks in advance.
Code:
$customFields = array('internalId' => 'custbody_new_die_yn','value' => array('name' => 'custbody_new_die_yn','internalId' => 'NO'));
$customObject = new nsComplexObject("SelectCustomFieldRef");
$customObject->setFields($customFields);
$salesOrderFields = array(
'entity' => new nsRecordRef(array('internalId' => $userId)),
'paymentMethod' => array('internalId' => 8),
'ccNumber' => 4111111111111111,
'ccExpireDate' => date("c", mktime(0,0,0,11,1,2011)),
'ccName' => 'Test Testerson',
'itemList' => array(
'item' => array(
'item' => array('internalId' => 5963),
'quantity' => 5
)
),
'department' => new nsRecordRef(array('internalId' => 1)),
'class' => new nsRecordRef(array('internalId' => 47)),
'customFieldList' => $customObject
);
I am not familiar using PHP with Netsuite but I have done a good amount of c#/.net Netsuite work. As Craig mentioned I find it much easier using a language such c#/.net with a Visual Studio generated interface to figure out what is available in the Netsuite SuiteTalk web service API.
There is a fair amount of documentation around this stuff in the NetSuite Help Center - by no means everythign you will need but a good start. Netsuite Help Center
Check out the SuiteFlex/SuiteTalk (Web Services) section specifically this page on Ids & References.
Using Internal Ids, External Ids, and References
With that said I will try to help with a .net example & explanation of adding a custom field to a Sales Order.
Here are a few examples of adding different CustomFieldRefs:
//A list object to store all the customFieldRefs
List<CustomFieldRef> oCustomFieldRefList = new List<CustomFieldRef>();
//List or Record Type reference
SelectCustomFieldRef custbody_XXX_freight_terms = new SelectCustomFieldRef();
custbody_XXX_freight_terms.internalId = "custbody_XXX_freight_terms";
ListOrRecordRef oFreightTermsRecordRef = new ListOrRecordRef();
oFreightTermsRecordRef.internalId = <internalId of specific record in Netsuite>;
//See the References link above for more info on this - trying to figure out typeId caused me a lot of pain.
oFreightTermsRecordRef.typeId = <internalId of the List Record Type in Netsuite>;
custbody_XXX_freight_terms.value = oFreightTermsRecordRef;
oCustomFieldRefList.Add(custbody_XXX_freight_terms);
//Freeform text sorta field
StringCustomFieldRef objStringCustomFieldRef = new StringCustomFieldRef();
objStringCustomFieldRef.internalId = "custbody_XXX_tracking_link";
objStringCustomFieldRef.value = "StringValue";
oCustomFieldRefList.Add(objStringCustomFieldRef);
//Checkbox field type
BooleanCustomFieldRef custbody_XXX_if_fulfilled = new BooleanCustomFieldRef();
custbody_XXX_if_fulfilled.internalId = "custbody_XXX_if_fulfilled";
custbody_XXX_if_fulfilled.value = true;
oCustomFieldRefList.Add(custbody_XXX_if_fulfilled);
//By far the most complicated example a multi-select list referencing other records in Netsuite
MultiSelectCustomFieldRef custrecord_XXX_transaction_link = new MultiSelectCustomFieldRef();
//internal id of field you are updating
custrecord_XXX_transaction_link.internalId = "custrecord_XXX_transaction_link";
List<ListOrRecordRef> oListOrRecordRefList = new List<ListOrRecordRef>();
ListOrRecordRef oListOrRecordRefItemFulfillment = new ListOrRecordRef();
oListOrRecordRefItemFulfillment.name = "Item Fulfillment";
oListOrRecordRefItemFulfillment.internalId = <ItemFulfillmentInternalId>;
//Item Fulfillment is record type (Transaction -30) - this is from the above Reference links
oListOrRecordRefItemFulfillment.typeId = "-30";
oListOrRecordRefList.Add(oListOrRecordRefItemFulfillment);
ListOrRecordRef oListOrRecordRefSalesOrder = new ListOrRecordRef();
oListOrRecordRefSalesOrder.name = "Sales Order";
oListOrRecordRefSalesOrder.internalId = <SalesOrderInternalId>;
//Sales Order is record type (Transaction -30) - this is from the above Reference links
oListOrRecordRefSalesOrder.typeId = "-30";
oListOrRecordRefList.Add(oListOrRecordRefSalesOrder);
//Add array of all the ListOrRecordRefs to the MultiSelectCustomFieldRef
custrecord_XXX_transaction_link.value = oListOrRecordRefList.ToArray();
oCustomFieldRefList.Add(custrecord_XXX_transaction_link);
//And then add all these to the Custom Record List (Array) on the Sales Order Record
objSalesOrder.customFieldList = oCustomFieldRefList.ToArray();
From what I can tell in your above example I think your issue is with the ListOrRecordRef typeId. Its hard to tell from your example what typeId you are referencing but if you can figure that out and set the TypeId on your SelectCustomFieldRef I think that should fix your issue.
The Custom Field Ref Internal ID is the reference ID on the record you are trying to update. This can be found in the Transaction Body fields for that record within Netsuite.
The Internal ID for the ListOrRecordRef is the internal ID for the actual list item or record that you want to attach to the previously mentioned record
The typeID for the ListOrRecordRef is the internal ID for the custom list/record. This is the parent ID for the previous internal ID, and is not inherently tied to the original record.

Categories