Skip to content

Commit

Permalink
Initial code commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Hamish Friedlander committed May 23, 2016
1 parent 906332c commit 92d568a
Show file tree
Hide file tree
Showing 6 changed files with 457 additions and 1 deletion.
1 change: 0 additions & 1 deletion _config.php
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
<?php

8 changes: 8 additions & 0 deletions _config/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
Name: gridfieldqueuedexport
After:
- 'userforms/*'
---
UserDefinedForm:
extensions:
- UserFormUseQueuedExportExtension
192 changes: 192 additions & 0 deletions code/GenerateCSVJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
<?php

class GenerateCSVJob extends AbstractQueuedJob {

This comment has been minimized.

Copy link
@chillu

chillu May 23, 2016

Member

Namespaces?


public function __construct() {
$this->ID = uniqid();
$this->Seperator = ',';
$this->IncludeHeader = true;
$this->HeadersOutput = false;
$this->totalSteps = 1;
}

public function getJobType() {
return QueuedJob::QUEUED;
}

public function getTitle() {
return "Export a CSV of a Gridfield";
}

public function getSignature() {
return md5(get_class($this) . '-' . $this->ID);
}

function setGridField($gridField) {
$this->GridFieldName = $gridField->getName();
$this->GridFieldURL = $gridField->Link();
}

function setSession($session) {
// None of the gridfield actions are needed, and they make the stored session bigger, so pull
// them out.
$actionkeys = array_filter(array_keys($session), function ($i) {
return strpos($i, 'gf_') === 0;
});

$session = array_diff_key($session, array_flip($actionkeys));

// This causes problems with logins
unset($session['HTTP_USER_AGENT']);

$this->Session = $session;
}

function setColumns($columns) {
$this->Columns = $columns;
}

function setSeparator($seperator) {
$this->Separator = $seperator;
}

function setIncludeHeader($includeHeader) {
$this->IncludeHeader = $includeHeader;
}

protected function getGridField() {
$session = $this->Session;

// Store state in session, and pass ID to client side.
$state = array(
'grid' => $this->GridFieldName,
'actionName' => 'findgridfield',
'args' => null
);

// Ensure $id doesn't contain only numeric characters
$id = 'gf_' . substr(md5(serialize($state)), 0, 8);

// Add new form action into session for GridField to find when Director::test is called below
$session[$id] = $state;
$session['SecurityID'] = '1';

// Construct the URL
$actionKey = 'action_gridFieldAlterAction?' . http_build_query(['StateID' => $id]);
$actionValue = 'Find Gridfield';

$url = $this->GridFieldURL . '?' .http_build_query([
$actionKey => $actionValue,
'SecurityID' => 1
]);

// Restore into the current session the user the job is exporting as
Session::set("loggedInAs", $session['loggedInAs']);

// Then make a sub-query that should return a special SS_HTTPResponse with the gridfield object
$res = Director::test($url, null, new Session($session), 'GET');

// Great, it did, we can return it
if ($res instanceof GridFieldQueuedExportButton_Response) {
$gridField = $res->getGridField();
$gridField->getConfig()->removeComponentsByType('GridFieldPaginator');
$gridField->getConfig()->removeComponentsByType('GridFieldPageCount');

return $gridField;
} else {
user_error('Couldn\'t restore GridField', E_USER_ERROR);
}
}

protected function outputHeader($gridField, $columns) {
$fileData = '';
$separator = $this->Separator;

$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 ($columns as $columnSource => $columnHeader) {
$headers[] = (!is_string($columnHeader) && is_callable($columnHeader)) ? $columnSource : $columnHeader;
}

$fileData .= "\"" . implode("\"{$separator}\"", array_values($headers)) . "\"";
$fileData .= "\n";

file_put_contents('/tmp/' . $this->getSignature() . '.csv', $fileData, FILE_APPEND);

This comment has been minimized.

Copy link
@chillu

chillu May 23, 2016

Member

Should use TEMP_FOLDER?

}

protected function outputRows($gridField, $columns, $start, $count) {
$fileData = '';
$separator = $this->Separator;

$items = $gridField->getManipulatedList();
$items = $items->limit($count, $start);

foreach ($items as $item) {
if (!$item->hasMethod('canView') || $item->canView()) {
$columnData = array();

foreach ($columns as $columnSource => $columnHeader) {

This comment has been minimized.

Copy link
@chillu

chillu May 23, 2016

Member

Lots of code copied from GridFieldExportButton->generateExportFileData(). I guess there's no easy way to reuse this functionality, because it relies on gridfield references. We should really be separating out data formats and export logic from a "UI button controller" class in core, but that's scope creep here...

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";

This comment has been minimized.

Copy link
@chillu

chillu May 23, 2016

Member

GridFieldExportButton uses fgetcsv(), why aren't we here? This also means we'll support custom enclosures

This comment has been minimized.

Copy link
@assertchris

assertchris May 23, 2016

Contributor

If you need something more complex (for some reason): http://csv.thephpleague.com/examples/

This comment has been minimized.

This comment has been minimized.

Copy link
@hafriedlander

hafriedlander May 23, 2016

OK, it was changed between 3.x and 4. This module was dev'ed against 3.3.x, so I copied that code. (Better not to copy at all, but..)

}

if ($item->hasMethod('destroy')) {
$item->destroy();
}
}

file_put_contents('/tmp/' . $this->getSignature() . '.csv', $fileData, FILE_APPEND);

This comment has been minimized.

Copy link
@chillu

chillu May 23, 2016

Member

Should use TEMP_FOLDER?

}


public function setup() {
$gridField = $this->getGridField();
$this->totalSteps = $gridField->getManipulatedList()->count();
}

/**
* Generate export fields for CSV.
*
* @param GridField $gridField
* @return array
*/
public function process() {
$gridField = $this->getGridField();
$columns = $this->Columns ?: singleton($gridField->getModelClass())->summaryFields();

if ($this->IncludeHeader && !$this->HeadersOutput) {
$this->outputHeader($gridField, $columns);
$this->HeadersOutput = true;
}

$this->outputRows($gridField, $columns, $this->currentStep, 100);

This comment has been minimized.

Copy link
@chillu

chillu May 23, 2016

Member

100 should be configurable, depends on system capacity


$this->currentStep += 100;

if ($this->currentStep >= $this->totalSteps) {
$this->isComplete = true;
}
}
}
Loading

0 comments on commit 92d568a

Please sign in to comment.