Skip to content

Commit

Permalink
Element exporting
Browse files Browse the repository at this point in the history
Re: #994
  • Loading branch information
brandonkelly committed Apr 3, 2019
1 parent 0d359c1 commit 9c7fe6e
Show file tree
Hide file tree
Showing 14 changed files with 433 additions and 61 deletions.
1 change: 1 addition & 0 deletions CHANGELOG-v3.2.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This update introduces a few changes in behavior to be aware of:
- The `site` element query params now support passing multiple site handles, or `'*'`, to query elements across multiple sites at once. ([#2854](https://github.com/craftcms/cms/issues/2854))
- Added the `unique` element query param, which can be used to prevent duplicate elements when querying elements across multiple sites.
- Element index pages are now paginated for non-Structure views. ([#818](https://github.com/craftcms/cms/issues/818))
- Element index pages now have an “Export…” button that will export all of the elements in the current view (across all pages) or up to a custom limit, in either CSV, XLS, XLSX, or ODS format. ([#994](https://github.com/craftcms/cms/issues/994))
- The `_layouts/cp` Control Panel template now supports a `footer` block, which will be output below the main content area.
- Added `craft\base\ElementInterface::pluralDisplayName()`, which element type classes can use to define the plural of their display name.
- Added `craft\models\Section::$propagationMethod`.
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"yiisoft/yii2-debug": "^2.0.10",
"yiisoft/yii2-swiftmailer": "^2.1.0",
"yiisoft/yii2-queue": "2.1.0",
"phpoffice/phpspreadsheet": "^1.6.0",
"zendframework/zend-feed": "^2.8.0"
},
"require-dev": {
Expand Down
38 changes: 38 additions & 0 deletions src/controllers/ElementIndexesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use yii\web\BadRequestHttpException;
use yii\web\ForbiddenHttpException;
use yii\web\Response;
use yii\web\ServerErrorHttpException;

/**
* The ElementIndexesController class is a controller that handles various element index related actions.
Expand Down Expand Up @@ -246,6 +247,43 @@ public function actionGetSourceTreeHtml(): Response
]);
}

/**
* Creates an export token.
*
* @return Response
* @throws BadRequestHttpException
* @throws ServerErrorHttpException
* @since 3.2.0
*/
public function actionCreateExportToken(): Response
{
if (!$this->sourceKey) {
throw new BadRequestHttpException('Request missing required body param');
}

if ($this->context !== 'index') {
throw new BadRequestHttpException('Request missing index context');
}

$request = Craft::$app->getRequest();

$token = Craft::$app->getTokens()->createToken([
'export/export',
[
'elementType' => $this->elementType,
'sourceKey' => $this->sourceKey,
'criteria' => $request->getBodyParam('criteria', []),
'format' => $request->getRequiredBodyParam('format'),
]
], 1, (new \DateTime())->add(new \DateInterval('PT1H')));

if (!$token) {
throw new ServerErrorHttpException(Craft::t('app', 'Could not create a Live Preview token.'));
}

return $this->asJson(compact('token'));
}

// Protected Methods
// =========================================================================

Expand Down
132 changes: 132 additions & 0 deletions src/controllers/ExportController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php
/**
* @link https://craftcms.com/
* @copyright Copyright (c) Pixel & Tonic, Inc.
* @license https://craftcms.github.io/license/
*/

namespace craft\controllers;

use Craft;
use craft\base\ElementInterface;
use craft\helpers\ElementHelper;
use craft\helpers\FileHelper;
use craft\web\Controller;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Csv;
use PhpOffice\PhpSpreadsheet\Writer\Ods;
use PhpOffice\PhpSpreadsheet\Writer\Xls;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use yii\web\BadRequestHttpException;
use yii\web\Response;

/**
* The ElementIndexesController class is a controller that handles various element index related actions.
* Note that all actions in the controller require an authenticated Craft session via [[allowAnonymous]].
*
* @author Pixel & Tonic, Inc. <[email protected]>
* @since 3.2.0
*/
class ExportController extends Controller
{
// Properties
// =========================================================================

/**
* @inheritdoc
*/
public $defaultAction = 'export';

/**
* @inheritdoc
*/
protected $allowAnonymous = true;

// Public Methods
// =========================================================================

/**
* Exports element data.
*
* @param string $elementType
* @param string $sourceKey
* @param array $criteria
* @param string $format
* @return Response
* @throws BadRequestHttpException
*/
public function actionExport(string $elementType, string $sourceKey, array $criteria, string $format): Response
{
$this->requireToken();

/** @var string|ElementInterface $elementType */
$query = $elementType::find();
$source = ElementHelper::findSource($elementType, $sourceKey, 'index');

if ($source === null) {
throw new BadRequestHttpException('Invalid source key: ' . $sourceKey);
}

// Does the source specify any criteria attributes?
if (isset($source['criteria'])) {
Craft::configure($query, $source['criteria']);
}

// Override with the request's params
if ($criteria !== null) {
if (isset($criteria['trashed'])) {
$criteria['trashed'] = (bool)$criteria['trashed'];
}
Craft::configure($query, $criteria);
}

/** @var array $results */
$results = $query->asArray()->all();
$columns = array_keys(reset($results));

foreach ($results as &$result) {
$result = array_values($result);
}
unset($result);

// Populate the spreadsheet
$spreadsheet = new Spreadsheet();
if (!empty($results)) {
$worksheet = $spreadsheet->setActiveSheetIndex(0);
$worksheet->fromArray($columns, null, 'A1');
$worksheet->fromArray($results, null, 'A2');
}

// Could use the writer factory with a $format <-> phpspreadsheet string map, but this is more simple for now.
switch ($format) {
case 'csv':
$writer = new Csv($spreadsheet);
break;
case 'xls':
$writer = new Xls($spreadsheet);
break;
case 'xlsx':
$writer = new Xlsx($spreadsheet);
break;
case 'ods':
$writer = new Ods($spreadsheet);
break;
default:
throw new BadRequestHttpException('Invalid export format: ' . $format);
}

$file = tempnam(sys_get_temp_dir(), 'export');
$writer->save($file);
$contents = file_get_contents($file);
unlink($file);

$filename = mb_strtolower($elementType::pluralDisplayName()) . '.' . $format;
$mimeType = FileHelper::getMimeTypeByExtension($filename);

$response = Craft::$app->getResponse();
$response->content = $contents;
$response->format = Response::FORMAT_RAW;
$response->setDownloadHeaders($filename, $mimeType);
return $response;
}
}
3 changes: 2 additions & 1 deletion src/templates/_layouts/elementindex.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@


{% block footer %}
<div id="count-container" class="light">&nbsp;</div>
<div id="count-container" class="light flex-grow">&nbsp;</div>
<div id="export-btn" class="btn">{{ 'Export…'|t('app') }}</div>
{% endblock %}


Expand Down
5 changes: 5 additions & 0 deletions src/web/assets/cp/CpAsset.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,10 @@ private function _registerTranslations(View $view)
'Enter the name of the folder',
'Enter your password to continue.',
'Enter your password to log back in.',
'Export',
'Export…',
'Failed',
'Format',
'Give your tab a name.',
'Handle',
'Heading',
Expand All @@ -153,6 +156,7 @@ private function _registerTranslations(View $view)
'Keep both',
'Keep me logged in',
'License transferred.',
'Limit',
'Log out now',
'Login',
'Make not required',
Expand All @@ -173,6 +177,7 @@ private function _registerTranslations(View $view)
'New {group} category',
'New {section} entry',
'Next Page',
'No limit',
'OK',
'Options',
'Password',
Expand Down
9 changes: 9 additions & 0 deletions src/web/assets/cp/dist/css/_main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1900,6 +1900,15 @@ $checkboxPadding: 20px;
}
}

.export-form {
position: relative;
.spinner {
position: absolute;
bottom: 0;
@include right(-24px);
}
}

.thumbviewhelper {
margin: -7px;
padding: 7px;
Expand Down
5 changes: 5 additions & 0 deletions src/web/assets/cp/dist/css/craft.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/web/assets/cp/dist/css/craft.css.map

Large diffs are not rendered by default.

Loading

0 comments on commit 9c7fe6e

Please sign in to comment.