I need to write a plugin that exports data both in CSV and XLSX formats. October CMS - export-to-excel

I need to write a plugin that exports data both in CSV and XLSX formats*.
As a base I follow Vojta's plugin code (1) that uses ImportExport backend behavior.
The main plugin controller DataImportExport inherits a import-export Behavior from the Backend that is located in the ImportExportController.
class DataImportExport extends Controller
{​​​​​​​
public$implement = [
'Backend.Behaviors.ImportExportController',
];
...
}
It's that Behavior that has methods to export data into CSV file. Yet, if I want to export into XLSX file I am supposed to add corresponding methods into that behavior, namely into the exporting controller by overriding some of its existing methods. Yet, as I do that inside DataImportExport controller, it does not work... as I try/test the export process remains the same as it was prior to that with only CSV...
One seems to be not able to override/extend the backend behavior, namely modules\backend\behaviors\ImportExportController.php.
For example, at the DataimportExport controller I try to override the exportFromList() method of ImportExportController behavior by adding code for the XLSX export -
Yet, no positive change... as I try it.
class DataImportExport extends Controller
{
public $implement = [
'Backend.Behaviors.ImportExportController',
];
public function __construct()
{
parent::__construct();
BackendMenu::setContext('RainLab.User', 'user', $this->action);
// extending the ImportExportController behavior
\Backend\Behaviors\ImportExportController::extend(function($model) {
$model->addDynamicProperty('tagsCache', null);
$model->addDynamicMethod('writeXLSX', function() use ($model) {
});
$model->addDynamicMethod('getTagsAttribute', function() use ($model) {
if ($this->tagsCache) {
return $this->tagsCache;
} else {
return $this->tagsCache = $model->tags()->lists('name');
}
});
});
}
...
// method to override
public function exportFromList($definition = null, $options = [])
{
...
$response = Response::make();
if ($options['export_type'] == 'csv') {
/*
* Response CSV
*/
$response->header('Content-Type', 'text/csv');
$response->header('Content-Transfer-Encoding', 'binary');
$response->header('Content-Disposition', sprintf('%s; filename="%s"', 'attachment', $filename));
$response->setContent((string) $csv);
} else {
// export into xlsx code
}
return $response;
}
}
Note
Vojta's plugin exports/imports only into/from CSV files.

maybe you should look at https://octobercms.com/plugin/vdomah-excel where you can import and export CSV and Excel files ? The documentation is very well, and i use it on many projects to import datas and it's working well.
best,
Lucas

Related

Maatwebsite Excel - how to name multiple sheets?

I've got an Maatwebsite (v3.1.25) Excel Export as follows. What is the simplest way to add a custom name for each sheet? Currently they just appear as "Worksheet", "Worksheet 1" etc.
I did see the official website's documentation, but couldn't figure out how to apply it to my specific situation.
class BizdevReport implements WithMultipleSheets
{
public function sheets(): array
{
$sheets = [
new BizdevReportTeachers(),
new BizdevReportClasses(),
new BizdevReportGMV()
];
return $sheets;
}
}
I'm trying to help answer.
although you asked 9 months ago, but maybe this will be useful for people who need answers.
inside BizdevReportTeachers class, you implement WithTitle and add title function
use Maatwebsite\Excel\Concerns\WithTitle;
class BizdevReportTeachers implements WithTitle
{
public function title(): string
{
return 'Lorem Ipsum';
}
}
reference: https://docs.laravel-excel.com/3.1/exports/multiple-sheets.html
You are almost there . Just add the name you required to sheets array like below
class BizdevReport implements WithMultipleSheets
{
public function sheets(): array
{
$sheets = [
'BizdevReportTeachers' => new BizdevReportTeachers(),
'BizdevReportClasses' => new BizdevReportClasses(),
'BizdevReportGMV' => new BizdevReportGMV()
];
return $sheets;
}
}
Thats all.

How to export csv file with shift-jis encoding in laravel?

I am using laravel-excel to export csv file. To export, the code is like below,
return Excel::download(new Export(results,$header), "test.csv");
And the Export.php file is like,
namespace App\AllClass;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\WithHeadings;
class Export implements FromCollection,WithHeadings
{
private $myArray;
private $header;
public function __construct($myArray,$header){
$this->myArray = $myArray;
$this->header = $header;
}
public function collection()
{
$data = mb_convert_encoding($this->myArray,"SJIS", "UTF-8");
// dump($data);
return collect($data);
}
public function headings(): array
{
$header = mb_convert_encoding($this->header,"SJIS", "UTF-8");
// dump($header);
return $header;
}
}
As you can see, I am converting the data before creating excel. Without converting I can export perfectly in UTF-8 format. But after converting to shift-jis, it is deleting all Japanese character. However, if I dump the header before returning, it is showing me gibberish data; not empty string like the csv file.
I resolved it.
Let's me share my solution here.
Laravel Excel not support it by default.
But we can do it by simple way.
Get csv content before download: \Excel::raw
Convert to another encoding: mb_convert_encoding
https://docs.laravel-excel.com/3.1/exports/collection.html#storing-raw-contents
Download csv.
$exportedObject= new \App\Exports\ClassExport($exportDataArray, $fileName);
$csvContent = \Excel::raw($exportedObject, $exportedObject->writerType);
$csvContent = mb_convert_encoding($csvContent, 'SJIS', 'auto');
// In my case, I upload my csv to S3.
$storageInstance = \Storage::disk('s3_import_csvs');
$putFileOnStorage = $storageInstance->put($fileName, $csvContent);
In config/excel.php, you should change CSV Settings
'use_bom' => true,
It's work well in Japanese

How to implement required File upload attribute, on Update action, in Yii 2?

I need file upload field to be required for both Create and Update actions, and the required validation and validation of types to be performed in both cases.
This is how my form looks like (Note: It's a form, not an Active Record model):
<?php
namespace app\models;
use Yii;
use yii\base\Model;
use yii\base\Object;
use yii\helpers\FileHelper;
class MyCustomForm extends Model
{
public $file_image;
public function rules()
{
return [
[
[['file_image'], 'file', 'skipOnEmpty' => false, 'extensions' => 'jpg, jpeg, png, bmp, jpe']
]
];
}
public function scenarios()
{
$scenarios = parent::scenarios();
//Scenarios Attributes that will be validated
$scenarios['action_add'] = ['file_image'];
$scenarios['action_edit'] = ['file_image'];
return $scenarios;
}
}
And this is how my controller actions looks like.
The Create action works as expected (on POST request I'm taking the Uploaded File with UploadedFile::getInstance command)
public function actionCreate()
{
$model_form = new MyCustomForm(['scenario' => 'action_add']);
if (Yii::$app->request->isPost && $model_form->load(Yii::$app->request->post())) {
$model_form->file_image = \yii\web\UploadedFile::getInstance($model_form, "file_image");
if($model_form->validate()) {
if(isset($model_form->file_image)){
//I'm uploading my image to some cloud server here
//creating corresponding $model_entity for database, fill with the data from the form and save it
$model_entity->save();
}
}
}
}
But I'm facing with an issue when doing the same on Update action. In database I have the URL of image that is on third party cloud server and I can access it and display the image in my form (so GET request on Update works, I'm getting the corresponding entity from database and fill the form with data). But for POST, file validation is failing, if I don't have file assigned in the model and the POST request for Update is not working.
public function actionUpdate($id)
{
$model_entity = $this->findModel($id);
$image_URL = //I'm having URL to corresponding image here;
$model_form = new MyCustomForm(['scenario' => 'action_edit']);
$model_form_data = [$model_form->formName() => $model_entity->attributes];
$model_form->load($model_form_data);
if (Yii::$app->request->isPost && $model_form->load(Yii::$app->request->post())) {
//if we upload image again this will work
//NOTE: I have another text fields in the form and If I only change them
//and if I don't change the file image, the following result will return NULL and validation will fail
$file_uploaded_image = \yii\web\UploadedFile::getInstance($model_form, "file_image");
if(isset($file_uploaded_image)){
//new image is uploaded on update, this scenario will be OK
} else {
//no image is uploaded on update, this scenario will fail
}
if($model_form->validate()) {
//this will fail if I don't upload image on Update
}
}
}
I have tried many things in Update action, before validation, in order to find a workaround to get the image and validation to not fail. For example:
$dataImg = file_get_contents($image_URL);
$model_form->file_image = $dataImg;
or trying to do get with temporary file:
$dataImg = file_get_contents($dataImg);
$filePath = \yii\helpers\FileHelper::createDirectory("/images/tmp/test.png", 777);
file_put_contents($filePath, $dataImg);
$model_form->file_image = $filePath;
But none of them is working. Is there any solution to this scenario?
Note that I will have to use a Froms (as above) and not the ActiveRecord, since my real project is more complex that example listed.
Write this code in your Model(MyCustomForm) :
class MyCustomForm extends Model
{
public $file_image;
public function rules()
{
return [
[['file_image',],'required','on'=>['create','update']],
[['file_image'], 'file','extensions' => 'jpg, jpeg, png, bmp, jpe'],
];
}
}
Write this code in your actionCreate() :
$model_form = new MyCustomForm();
$model_form->scenario = "create";
Write this code in your actionUpdate() :
$model_form = new MyCustomForm();
$model_form->scenario = "update";
Or you can add scenario by this :
$model_form = new MyCustomForm(['scenario' => 'create']);
I have tried this and it is working.

Magento Custom Router loading controller but nothing else

I'm trying to get some custom routing going on in Magento using the following code (which I've only slightly modified from here https://stackoverflow.com/a/4158571/1069232):
class Company_Modulename_Controller_Router extends Mage_Core_Controller_Varien_Router_Standard {
public function match(Zend_Controller_Request_Http $request){
$path = explode('/', trim($request->getPathInfo(), '/'));
// If path doesn't match your module requirements
if ($path[1] == 'home.html' || (count($path) > 2 && $path[0] != 'portfolios')) {
return false;
}
// Define initial values for controller initialization
$module = $path[0];
$realModule = 'Company_Modulename';
$controller = 'index';
$action = 'index';
$controllerClassName = $this->_validateControllerClassName(
$realModule,
$controller
);
// If controller was not found
if (!$controllerClassName) {
return false;
}
// Instantiate controller class
$controllerInstance = Mage::getControllerInstance(
$controllerClassName,
$request,
$this->getFront()->getResponse()
);
// If action is not found
if (!$controllerInstance->hasAction($action)) {
return false;
}
// Set request data
$request->setModuleName($module);
$request->setControllerName($controller);
$request->setActionName($action);
$request->setControllerModule($realModule);
// Set your custom request parameter
$request->setParam('url_path', $path[1]);
// dispatch action
$request->setDispatched(true);
$controllerInstance->dispatch($action);
// Indicate that our route was dispatched
return true;
}
}
The result is a page where the template has loaded but with no content. If I comment out the $this->loadLayout() / $this->renderLayout() in my controller I can print to screen. But when I try and load a Template and/or Block it breaks somewhere.
home.html also loads fine (as the method returns false if the path is home.html).
Any assistance would be greatly appreciated.
I was implementing something similar to this and came across the same problem(That makes sense, because I copypasted your code)
before $request->setDispatched(true);
I added $request->setRouteName('brands'); (brands is the frontname of my module).
And It worked.Don't know if It'll work for you, but definetely there was something missing so that magento didn't know what layout to apply, because I could tell that teh controller was being reached.

Codeigniter change database config at runtime

Can I change the database config per method in a controller?
$db['default']['db_debug'] = TRUE;
The default is TRUE, while I need to make it false in a certain method to catch the error and do something else (for example show 404 page).
When I tried $this->config->load('database') it fails.
Another question :
Can I check an incorrect query and catch it to some variables rather than displaying it to users other than setting the db_debug config to FALSE?
I checked the code of system/database/DB_Driver and found that:
$this->db->db_debug = FALSE;
will work in my controller to enable/disable the debug thing on the fly.
Expanding on the answer by comenk, you can extend the database class and implement various methods by which to achieve your goal.
First, you'll need to extend the core Loader class by creating a MY_Loader.php file
class MY_Loader extends CI_Loader
{
function __construct()
{
parent::__construct();
}
/**
* Load the Standard and/or Extended Database function & Driver class
*
* #access public
* #return string
*/
function database( $params = '', $return = FALSE, $active_record = NULL )
{
$ci =& get_instance();
if (class_exists('CI_DB') AND $return == FALSE AND $active_record == NULL AND isset($ci->db) AND is_object($ci->db))
{
return FALSE;
}
$my_db = config_item('subclass_prefix').'DB';
$my_db_file = APPPATH.'core/'.$my_db.EXT;
if(file_exists($my_db_file))
{
require_once($my_db_file);
}
else
{
require_once(BASEPATH.'database/DB'.EXT);
}
// Load the DB class
$db =& DB($params, $active_record);
$my_driver = config_item('subclass_prefix').'DB_'.$db->dbdriver.'_driver';
$my_driver_file = APPPATH.'core/'.$my_driver.EXT;
if(file_exists($my_driver_file))
{
require_once($my_driver_file);
$db = new $my_driver(get_object_vars($db));
}
if ($return === TRUE)
{
return $db;
}
// Initialize the db variable. Needed to prevent
// reference errors with some configurations
$ci->db = '';
$ci->db = $db;
}
}
By implementing the above this will allow you to create a MY_DB_mysqli_driver.php whereby mysqli is replaced by whatever driver you're using in your CI database.php config.
At this point you'd add comenk's answer to MY_DB_mysqli_driver.php
function debug_on() {
return $this->db_debug = TRUE;
}
function debug_off() {
return $this->db_debug = FALSE;
}
function in_error() {
return (bool) $this->_error_number();
}
Then in your model/controller,
$this->db->debug_off();
$this->db->query('SELECT * FROM `table`');
if( $this->db->in_error() ) {
show_404();
}
$this->db->debug_on();
you must add function on system/database/DB_driver.php
function debug_on()
{
$this->db_debug = TRUE;
return TRUE;
}
function debug_off()
{
$this->db_debug = FALSE;
return FALSE;
}
after that you can simply do this command to changes at run-time
$this->db->debug_off();
$this->db->reconnect();
$this->db->db_debug = 0; // 0: off, 1: on
That worx for me...
You can look at the $GLOBALS variable to locate this generic setting.
To hide bad SQL (and other errors) from users, you need to set the php error reporting level. CodeIgniter ships in basically development mode.
Go to index.php and replace this
error_reporting(E_ALL);
with this
error_reporting(0);
This is the quick way to do it. You can also implement this using a hook, so you don't have to touch CI files. You can also add logic to that hook so that it only sets it on the production server.
For debugging SQL, you can create a class that inherits from CI_Model, then create all your model classes to extend that class. In that class, you can add code for running queries that writes the queries to the log so that you can debug them easier. This won't help if the query itself is bad, but you should be able to figure that out before you get to that point.

Resources