I have a project where people can book places on events. There are dozens of events. I have been requested to generate a report listing attendees so they can be checked in at the door. Because there are so many events, I would like to add an "Attendees Report" button to the editform for each event (inside the CMS). I have created the button, and it works.
BUT... instead of getting the data in a CSV, I get the (correct) data displaying in the CMS, as if it was being echoed.
Here is my function
public function getCustomersForEvent($data, $form){
$attendees = CustomerOrderLine::get()->filter(array("EventID" => $data["ID"]));
if(!$attendees){
return false;
}
$fileName = "report.csv";
$separator = ",";
$csvColumns = array("ID", "Description", "Customer");
$fileData = "";
foreach($attendees as $row){
$fileData .= $row->Event()->EventName . $separator;
$fileData .= $row->CustomerOrder()->Customer()->FirstName . " " . $row->CustomerOrder()->Customer()->Surname . "\n";
}
return SS_HTTPRequest::send_file($fileData, $fileName, 'text/csv');
}
I have had a look at this http://www.silverstripe.org/community/forums/general-questions/show/15325 but the data echoed to the CMS anyway.
Can someone please tell me what I am doing wrong? As I said, the data is fine, but I'm not getting the CSV Open or Save dialog.
The solution will be in how you are calling this. I would advise that you make a complete new controller or modify a module like...
https://github.com/axyr/silverstripe-phpexcel
...where you can copy the php code and modify it to return just the data you require from above.
To make a controller that is only focused on downloading a file...
class CustomersForEvent_Controller extends Page_Controller {
private static $allowed_actions = array (
'downloadcsv',
);
public function downloadcsv() {
return SS_HTTPRequest::send_file("my content",'myfile.csv','text/csv');
}
}
and add that controller to the routes in _config/config.yml...
Director:
rules:
'attendees': 'CustomersForEvent_Controller'
...and then you can just link to yousite.com/attendees/downloadcsv
This has been tested locally and confirmed to work
Is this report to be generated from within the CMS itself? If so, do you have a ModelAdmin for managing CustomerOrderLine objects? If so, can you not use the GridField's "Export to CSV" button?
In order to output anything more than just those fields displayed by the GridField itself, you'll need to create/alter your model class's $getExportFields() method to return an array of all the fields on your model you wish to be exported in this manner.
Related
I'm creating a webpage where students and teachers log in, teachers create levels and students can do that level.
I explain, I am using php with twig and I want to render a view passing parameters in a function. I've created a budle called Professors where I have the directory Controllers, Models and Templates.
In templates I have professors.html, where I show some information and a button to create a level for students, and also I have crearNivell.html, where the teacher will be able to create the level. When I'm in the page views professors my URL is this one:
When I click the button "CREATE LEVEL" I want my URL to be like this:
Instead I get this URL, and this one returns me an error.
In Controllers/ProfessorsController.php I have that code:
class ProfessorController extends Controller{
public function process($params)
{
/*var_dump($params);
die();*/
if(empty($params[0])){
$this->getProfessor(); //Here I return the view professor
}elseif(isset($params[0]) && $params[0] == "crearNivell"){
$this->twig = "crearNivell.html";
}
}
public function getProfessor(){
$this->twig = "professor.html";
}
}
Can someone help me with my code?
When I use var_dump() I get this:
and it should be like that:
I think what you're looking for is the API of Twig.
More specifically, you need the line below to render a template passing some parameters in array:
echo $template->render(['the' => 'variables', 'go' => 'here']);
If you use PHP the fastest way to "print" some content to the browser is echo, so don't hesitate to use it.
I found the answer to the questions a few days ago. I was complicating myself, it's just like this:
First you create the views, in Templates folder.
The $params are refered to the URL. For example:
alumne is the bundle
AlumneController.php
Then in AlumneController.php put the name of the views without the .html and you show it using $this->twig = "name_Of_The_View.html";
<?php
class AlumneController extends Controller{
public function process($params)
{
/*var_dump($params);
die();*/
if(empty($params[0])){
$this->getAlumne();
/*echo $usuari = $_SESSION["username"];*/
}else if(isset($params[0]) && $params[0] == "instruccions"){
$this->twig = "instruccions.html";
/*echo $usuari = $_SESSION["username"];*/
}else if(isset($params[0]) && $params[0] == "resultats"){
$this->twig = "resultats.html";
/*echo $usuari = $_SESSION["username"];*/
}
}
public function getAlumne(){
$this->twig = "alumne.html";
}
}
alumne.html
And in the button or link you are using to go to the page you write the path in Instruccions
<a class="mdl-navigation__link" href="alumne/instruccions">Instruccions</a>
<a class="mdl-navigation__link" href="alumne/resultats">Resultats</a>
I've created a backend button in my extension which looks like this:
class ButtonBarHook {
public function getButtons(array $params, ButtonBar $buttonBar) {
$buttons = $params['buttons'];
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
$playController = new PlayController();
$button = $buttonBar->makeLinkButton();
$button->setIcon($iconFactory->getIcon('actions-document-export-csv', Icon::SIZE_SMALL));
$button->setTitle('Export als XML');
$button->setHref(***Link to my function***);
$buttons[ButtonBar::BUTTON_POSITION_LEFT][1][] = $button;
return $buttons;
}
}
So far so good. It appears in the backend of TYPO3. Now I want to link the button to call a php function I wrote in a different class. I already tried $button->setOnClick()but this only creates a javascript onClick event.
I think $button->setHref() is the better approach but I don't know what to put in here.
How can I link to my php function/action?
)
You have to link to a module or ajax route, that you have registered
https://docs.typo3.org/typo3cms/InsideTypo3Reference/CoreArchitecture/Backend/Routing/Index.html
And then you can use the method BackendUtility::getModuleUrl method to get a path to insert into setHref
I'm using Codeigniter + Grocery Crud.
So, GC working good but is blank.. shows results from database, but search option and css style doesn't load.
My structure is:
application
assets
system
GC is inside assets.
My controller load view and send output to view:
public function output($output = null) {
$this->load->view('welcome_message', $output);
}
public function users() {
$this->load->library('grocery_CRUD');
$this->output((object) array('output' => '', 'js_files' => array(), 'css_files' => array()));
try {
$crud = new grocery_CRUD();
$crud->set_theme('flexigrid');
$crud->set_table('suppliers');
$output = $crud->render();
$this->output($output);
} catch (Exception $e) {
show_error($e->getMessage() . ' --- ' . $e->getTraceAsString());
}
}
After that, i'm using CG official tutorial to include jQuery and CSS files in the view. They are there. When i click over (in view source) - i can see them. It's not problem in folders.
I saw in View source double html tags... Is it exist any codeigniter autoload library to do that?
I'm defined autoload helpers - url and utility, which i made for loading assets folder.
I'm supposing the problem is ajax, jquery...but how to fix it?
Grocery CRUD output contains an array for adding java-script files
If you want to add extra/custom js file then you have to do something like this:
$output = $crud->render();
array_push($output->js_files, base_url("assets/my_js_folder/myjs.file.js"));
array_push($output->js_files, base_url("assets/another_js/config.module.js"));
$this->output($output);
I really hope this help you :)
I'm just taking my OOP lessons and I've found on a practice that I did not understand the theory as I thought... basically for practicing I tried to create a class which contains all the scripts I need to use on a single page, and I went as follows:
class pageScripts {
protected $scripts = array(); //an array to storage the links to the scripts
public function setScripts($link) {
$this->scripts[] = $link; //filling the array with the links
}
public function __toString() {
$output = '';
foreach($this->scripts as $values) {
$output .= "<script src=" . $values . "></script>";
}
return $output;
}
}
$scripts = new pageScripts;
$scripts->setScripts('link to the script');
$scripts->setScripts('link to the script2');
//var_dump ($scripts);
print($scripts);
Now, in my dreams, it supposed to concatenate the links and make a cute script list, however it doesn't, also I made a var_dump() and the array is filled, I cannot understand what I am doing wrong.
Any ideas?
The output & the code are fine (works for me), but by default PHP renders output with content type text/html, which "hides" the <script> tag.
To reveal the <script> tag, you can either set the content type to text/plain (which does not make sense), or review it in source code.
I am writing an CSV/Excel-->MySQL import manager for an MVC application (Kohana/PHP).
I have a controller named "ImportManager" which has an action named "index" (default) which displays in a grid all the valid .csv and .xls files that are in a specific directory and ready for import. The user can then choose the files he wants to import.
However, since .csv files import into one database table and .xls files import into multiple database tables, I needed to handle this abstraction. Hence I created a helper class called SmartImportFile to which I send each file be it .csv or .xls and then I get then ask this "smart" object to add the worksheets from that file (be they one or many) to my collection. Here is my action method in PHP code:
public function action_index()
{
$view = new View('backend/application/importmanager');
$smart_worksheets = array();
$raw_files = glob('/data/import/*.*');
if (count($raw_files) > 0)
{
foreach ($raw_files as $raw_file)
{
$smart_import_file = new Backend_Application_Smartimportfile($raw_file);
$smart_worksheets = $smart_import_file->add_smart_worksheets_to($smart_worksheets);
}
}
$view->set('smart_worksheets', $smart_worksheets);
$this->request->response = $view;
}
The SmartImportFile class looks like this:
class Backend_Application_Smartimportfile
{
protected $file_name;
protected $file_extension;
protected $file_size;
protected $when_file_copied;
protected $file_name_without_extension;
protected $path_info;
protected $current_smart_worksheet = array();
protected $smart_worksheets = array();
public function __construct($file_name)
{
$this->file_name = $file_name;
$this->file_name_without_extension = current(explode('.', basename($this->file_name)));
$this->path_info = pathinfo($this->file_name);
$this->when_file_copied = date('Y-m-d H:i:s', filectime($this->file_name));
$this->file_extension = strtolower($this->path_info['extension']);
$this->file_extension = strtolower(pathinfo($this->file_name, PATHINFO_EXTENSION));
if(in_array($this->file_extension, array('csv','xls','xlsx')))
{
$this->current_smart_worksheet = array();
$this->process_file();
}
}
private function process_file()
{
$this->file_size = filesize($this->file_name);
if(in_array($this->file_extension, array('xls','xlsx')))
{
if($this->file_size < 4000000)
{
$this->process_all_worksheets_of_excel_file();
}
}
else if($this->file_extension == 'csv')
{
$this->process_csv_file();
}
}
private function process_all_worksheets_of_excel_file()
{
$worksheet_names = Import_Driver_Excel::get_worksheet_names_as_array($this->file_name);
if (count($worksheet_names) > 0)
{
foreach ($worksheet_names as $worksheet_name)
{
$this->current_smart_worksheet['name'] = basename($this->file_name).' ('.$worksheet_name.')';
$this->current_smart_worksheet['kind'] = strtoupper($this->file_extension);
$this->current_smart_worksheet['file_size'] = $this->file_size;
$this->current_smart_worksheet['when_file_copied'] = $this->when_file_copied;
$this->current_smart_worksheet['table_name'] = $this->file_name_without_extension.'__'.$worksheet_name;
$this->assign_database_table_fields();
$this->smart_worksheets[] = $this->current_smart_worksheet;
}
}
}
private function process_csv_file()
{
$this->current_smart_worksheet['name'] = basename($this->file_name);
$this->current_smart_worksheet['kind'] = strtoupper($this->file_extension);
$this->current_smart_worksheet['file_size'] = $this->file_size;
$this->current_smart_worksheet['when_file_copied'] = $this->when_file_copied;
$this->current_smart_worksheet['table_name'] = $this->file_name_without_extension;
$this->assign_database_table_fields();
$this->smart_worksheets[] = $this->current_smart_worksheet;
}
private function assign_database_table_fields()
{
$db = Database::instance('import_excel');
$sql = "SHOW TABLE STATUS WHERE name = '".$this->current_smart_worksheet['table_name']."'";
$result = $db->query(Database::SELECT, $sql, FALSE)->as_array();
if(count($result))
{
$when_table_created = $result[0]['Create_time'];
$when_file_copied_as_date = strtotime($this->when_file_copied);
$when_table_created_as_date = strtotime($when_table_created);
if($when_file_copied_as_date > $when_table_created_as_date)
{
$this->current_smart_worksheet['status'] = 'backend.application.import.status.needtoreimport';
}
else
{
$this->current_smart_worksheet['status'] = 'backend.application.import.status.isuptodate';
}
$this->current_smart_worksheet['when_table_created'] = $when_table_created;
}
else
{
$this->current_smart_worksheet['when_table_created'] = 'backend.application.import.status.tabledoesnotexist';
$this->current_smart_worksheet['status'] = 'backend.application.import.status.needtoimport';
}
}
public function add_smart_worksheets_to(Array $smart_worksheets = array())
{
return array_merge($smart_worksheets, $this->get_smart_worksheets());
}
public function get_smart_worksheets()
{
if ( ! is_array($this->smart_worksheets))
{
return array();
}
return $this->smart_worksheets;
}
}
In a code review I was told that it might be better not to have a helper class like this but to keep the bulk of the code in the controller action method itself. The argumentation was that you should be able to look at the code in a controller action and see what it does instead of having it call external helper classes outside of itself. I disagree. My argumentation is:
you should create a helper class anytime it makes code clearer, as in this case, it abstracts away the fact that some files have one worksheet or many worksheets in them, and allows for easy future extension, if, say, we want to also import from sqlite files or even directories with files in them, this class abstraction would be able to handle this nicely.
moving the bulk of the code from this helper class back into the controller would force me to create internal variables in the controller which make sense for this particular action, but may or may not make sense to other action methods within the controller.
if I were programming this in C# I would make this helper class a nested class which would literally be an internal data structure that is inside of and only available to the controller class, but since PHP does not allow nested classes, I need to call a class "outside" the controller to help manage this abstraction in a way that makes the code clear and readable
Based on your experience of programming in the MVC pattern, should the above helper class be refactored back into the controller or not?
There are two approaches to controllers: make it thin or thick. When I started my adventure with MVC I made a mistake of creating thick controllers - now I prefer make it as thin as possible. Your solution is good in my opinion.
Here is how I would redesigned your code even further:
class Backend_Application_SmartImport {
public function __construct( $raw_files ) {
}
public function process() {
foreach ($raw_files as $raw_file) {
// (...)
$oSmartImportFileInstance = $this->getSmartImportFileInstance( $smart_import_file_extension );
}
}
protected function getSmartImportFileInstance( $smart_import_file_extension ) {
switch ( $smart_import_file_extension ) {
case 'xml':
return new Backend_Application_SmartImportFileXml();
// (...)
}
}
}
abstract class Backend_Application_SmartImportFile {
// common methods for importing from xml or cvs
abstract function process();
}
class Backend_Application_SmartImportFileCVS extends Backend_Application_SmartImportFile {
// methods specified for cvs importing
}
class Backend_Application_SmartImportFileXls extends Backend_Application_SmartImportFile {
// methods specified for xls importing
}
The idea is to have two classes responsible for processing xml and cvs inheriting from a base class. The main class uses a special method to detect how the data should be processed (Strategy Pattern). The controller just passed a list of files to the instance of Backend_Application_SmartImport class and passes result of process method to the view.
The advantage of my solution is that code is more decoupled and you can easily and in a clean way add new types of processing like xml, pdf, etc.
I agree with you Edward.
Your ImportController does what a Controller is meant to do. It generates the list of files for the user to view and act on, it then passes that list to the View for it to display. I am presuming that you have a process action or similar which is handles the request when a user has selected a file, this file is then passed on to the Helper in question.
The Helper is a perfect example of abstraction and entirely justified in its usage and existence. It is not coupled with the Controller in anyway and doesn't need to be. The Helper could be easily used in other scenarios where the Controller is not present, for example a CRON task, a public API which users can call programmatically without your ImportController.
Your right on the ball with this one. Stick it to 'em!