Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

商品CSVの分割アップロード #4805

Merged
merged 10 commits into from
Dec 16, 2020
1 change: 1 addition & 0 deletions app/config/eccube/packages/eccube.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ parameters:
eccube_temp_image_dir: '%kernel.project_dir%/html/upload/temp_image'
eccube_csv_size: 5 # post_max_size, upload_max_filesize に任せればよい?
eccube_csv_temp_realdir: '%kernel.cache_dir%/%kernel.environment%/eccube' # upload_tmp_dir に任せればよい?
eccube_csv_split_lines: 100
eccube_default_password: '**********'
eccube_deliv_addr_max: 20
eccube_deliv_date_end_max: 21
Expand Down
47 changes: 46 additions & 1 deletion codeception/_support/Page/Admin/ProductCsvUploadPage.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

class ProductCsvUploadPage extends AbstractAdminPageStyleGuide
{
public static $完了メッセージ = 'div.c-container > div.c-contentsArea > div.alert-success';
public static $完了メッセージ = '#importCsvModal > div > div > div.modal-body.text-left > p';

/**
* ProductCsvUploadPage constructor.
Expand All @@ -32,6 +32,13 @@ public static function go($I)
return $page->goPage('/product/product_csv_upload', '商品CSV登録商品管理');
}

public static function at($I)
{
$page = new self($I);

return $page->atPage('商品CSV登録商品管理');
}

public function 入力_CSVファイル($fileName)
{
$this->tester->attachFile(['id' => 'admin_csv_import_import_file'], $fileName);
Expand All @@ -46,6 +53,44 @@ public function CSVアップロード()
return $this;
}

public function アップロードボタン有効化()
{
// $this->tester->attachFileでイベントが効かずボタンが有効化されないので、テストコードで有効化する.
$this->tester->waitForJS('return $("#upload-button").prop("disabled", false);', 1);

return $this;
}

public function モーダルを表示()
{
$this->tester->click(['id' => 'upload-button']);

return $this;
}

public function CSVアップロード実行()
{
$this->tester->wait(1);
$this->tester->click(['id' => 'importCsv']);

return $this;
}

public function CSVアップロード確認()
{
$this->tester->wait(1);
$this->tester->see('CSVファイルをアップロードしました', ProductCsvUploadPage::$完了メッセージ);

return $this;
}

public function モーダルを閉じる()
{
$this->tester->click(['id' => 'importCsvDone']);

return $this;
}

public function 雛形ダウンロード()
{
$this->tester->click('#download-button');
Expand Down
10 changes: 8 additions & 2 deletions codeception/acceptance/EA03ProductCest.php
Original file line number Diff line number Diff line change
Expand Up @@ -662,8 +662,14 @@ public function product_商品CSV登録(\AcceptanceTester $I)

ProductCsvUploadPage::go($I)
->入力_CSVファイル('product.csv')
->CSVアップロード();
$I->see('CSVファイルをアップロードしました', ProductCsvUploadPage::$完了メッセージ);
->アップロードボタン有効化()
->モーダルを表示()
->CSVアップロード実行()
->CSVアップロード確認()
->モーダルを閉じる()
;

ProductCsvUploadPage::at($I);

ProductManagePage::go($I)->検索('アップロード商品');
$I->see('検索結果:3件が該当しました', ProductManagePage::$検索結果_メッセージ);
Expand Down
144 changes: 141 additions & 3 deletions src/Eccube/Controller/Admin/Product/CsvImportController.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,15 @@
use Eccube\Util\StringUtil;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Validator\Constraints\GreaterThanOrEqual;
use Symfony\Component\Validator\Validator\ValidatorInterface;

Expand Down Expand Up @@ -107,6 +111,12 @@ class CsvImportController extends AbstractCsvImportController

private $errors = [];

protected $isXmlHttpRequest = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

XMLHttpRequestかどうかのフラグではなく、分割アップロードかどうかのフラグだと思うので isSplitCsv に変更をお願いします。


protected $csvFileNo = 1;

protected $currentLineNo = 1;

/**
* CsvImportController constructor.
*
Expand Down Expand Up @@ -167,6 +177,9 @@ public function csvProduct(Request $request, CacheUtil $cacheUtil)
if ('POST' === $request->getMethod()) {
$form->handleRequest($request);
if ($form->isValid()) {
$this->isXmlHttpRequest = $form['is_xml_http_request']->getData();
$this->csvFileNo = $form['csv_file_no']->getData();

$formFile = $form['import_file']->getData();
if (!empty($formFile)) {
log_info('商品CSV登録開始');
Expand Down Expand Up @@ -207,7 +220,8 @@ public function csvProduct(Request $request, CacheUtil $cacheUtil)
$this->entityManager->getConnection()->beginTransaction();
// CSVファイルの登録処理
foreach ($data as $row) {
$line = $data->key() + 1;
$line = $this->convertLineNo($data->key() + 1);
$this->currentLineNo = $line;
if ($headerSize != count($row)) {
$message = trans('admin.common.csv_invalid_format_line', ['%line%' => $line]);
$this->addErrors($message);
Expand Down Expand Up @@ -653,8 +667,10 @@ public function csvProduct(Request $request, CacheUtil $cacheUtil)
}

log_info('商品CSV登録完了');
$message = 'admin.common.csv_upload_complete';
$this->session->getFlashBag()->add('eccube.admin.success', $message);
if (!$this->isXmlHttpRequest) {
$message = 'admin.common.csv_upload_complete';
$this->session->getFlashBag()->add('eccube.admin.success', $message);
}

$cacheUtil->clearDoctrineCache();
}
Expand Down Expand Up @@ -869,6 +885,18 @@ protected function renderWithError($form, $headers, $rollback = true)

$this->removeUploadedFile();

if ($this->isXmlHttpRequest) {
return $this->json([
'success' => !$this->hasErrors(),
'success_message' => trans('admin.common.csv_upload_line_success', [
'%from%' => $this->convertLineNo(2),
'%to%' => $this->currentLineNo]),
'errors' => $this->errors,
'error_message' => trans('admin.common.csv_upload_line_error',[
'%from%' => $this->convertLineNo(2)])
]);
}

return [
'form' => $form->createView(),
'headers' => $headers,
Expand Down Expand Up @@ -1561,4 +1589,114 @@ private function makeProductCategory($Product, $Category, $sortNo)

return $ProductCategory;
}

/**
* @Route("/%eccube_admin_route%/product/csv_split", name="admin_product_csv_split")
* @param Request $request
* @return \Symfony\Component\HttpFoundation\JsonResponse
*/
public function splitCsv(Request $request)
{
$this->isTokenValid();

if (!$request->isXmlHttpRequest()) {
throw new BadRequestHttpException();
}

$form = $this->formFactory->createBuilder(CsvImportType::class)->getForm();
$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {

$dir = $this->eccubeConfig['eccube_csv_temp_realdir'];
if (!file_exists($dir)) {
$fs = new Filesystem();
$fs->mkdir($dir);
}

$data = $form['import_file']->getData();
$src = new \SplFileObject($data->getRealPath());
$src->setFlags(\SplFileObject::READ_CSV | \SplFileObject::READ_AHEAD | \SplFileObject::SKIP_EMPTY | \SplFileObject::DROP_NEW_LINE);

$fileNo = 1;
$fileName = StringUtil::random(8);

$dist = new \SplFileObject($dir.'/'.$fileName.$fileNo.'.csv', 'w');
$header = $src->current();
$src->next();
$dist->fputcsv($header);

$i = 0;
while ($row = $src->current()) {
$dist->fputcsv($row);
$src->next();

if (!$src->eof() && ++$i % $this->eccubeConfig['eccube_csv_split_lines'] === 0) {
$fileNo++;
$dist = new \SplFileObject($dir.'/'.$fileName.$fileNo.'.csv', 'w');
$dist->fputcsv($header);
}
}

return $this->json(['success' => true, 'file_name' => $fileName, 'max_file_no' => $fileNo]);
}

return $this->json(['success' => false, 'message' => $form->getErrors(true ,true)]);
}

/**
* @Route("/%eccube_admin_route%/product/csv_split_import", name="admin_product_csv_split_import")
* @param Request $request
* @return \Symfony\Component\HttpFoundation\JsonResponse
*/
public function importCsv(Request $request, CsrfTokenManagerInterface $tokenManager)
{
$this->isTokenValid();

if (!$request->isXmlHttpRequest()) {
throw new BadRequestHttpException();
}

$files = Finder::create()
->in($this->eccubeConfig['eccube_csv_temp_realdir'])
->name('*.csv')
->files();

$choices = [];
foreach ($files as $file) {
$choices[$file->getBaseName()] = true;
}

$filename = $request->get('file_name');
if (!isset($choices[$filename])) {
throw new BadRequestHttpException();
}

$path = $this->eccubeConfig['eccube_csv_temp_realdir'].'/'.$filename;
$request->files->set('admin_csv_import', ['import_file' => new UploadedFile(
$path,
'import.csv',
'text/csv',
filesize($path),
null,
true
)]);

$request->setMethod('POST');
$request->request->set('admin_csv_import', [
Constant::TOKEN_NAME => $tokenManager->getToken('admin_csv_import')->getValue(),
'is_xml_http_request' => true,
'csv_file_no' => $request->get('file_no'),
]);

return $this->forwardToRoute('admin_product_csv_import');
}

protected function convertLineNo($currentLineNo) {
if ($this->isXmlHttpRequest) {
return ($this->eccubeConfig['eccube_csv_split_lines']) * ($this->csvFileNo - 1) + $currentLineNo;
}

return $currentLineNo;
}
}
12 changes: 12 additions & 0 deletions src/Eccube/Form/Type/Admin/CsvImportType.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@

use Eccube\Common\EccubeConfig;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints as Assert;

Expand Down Expand Up @@ -52,6 +54,16 @@ public function buildForm(FormBuilderInterface $builder, array $options)
'maxSize' => $this->csvMaxSize.'M',
]),
],
])
->add('is_xml_http_request', CheckboxType::class, [
'label' => false,
'mapped' => false,
'required' => false,
])
->add('csv_file_no', IntegerType::class, [
'label' => false,
'mapped' => false,
'required' => false,
]);
}

Expand Down
9 changes: 9 additions & 0 deletions src/Eccube/Resource/locale/messages.en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,8 @@ admin.common.next: Next
admin.common.first: Go to First
admin.common.last: Go to Last
admin.common.back: Go back
admin.common.open_detail: 'Show details'
admin.common.close_detail: 'Close details'

# Generic Labels, Messages
admin.common.show: Display
Expand Down Expand Up @@ -469,6 +471,9 @@ admin.common.csv_invalid_can_not: '%name% is invalid in the line %line%'
admin.common.csv_invalid_image: 'Your are not allowed to use "/" or "../" as suffix in %name% in the %line%'
admin.common.csv_invalid_foreign_key: 'You are unable to delete %name% in the line %line% because it has related data'
admin.common.csv_invalid_description_detail_upper_limit: '%name% should be less than %max% characters in the line %line%.'
admin.common.csv_upload_in_progress: 'Uploading CSV file ...'
admin.common.csv_upload_line_success: 'The %from% to %to% lines have been registered.'
admin.common.csv_upload_line_error: 'An error has occurred. The registration process after the %from% line has been cancelled.'
admin.common.drag_and_drop_description: You can change the order of the items by drag & drop.
admin.common.drag_and_drop_image_description: Drag & drop the images or
admin.common.delete_modal__title: Delete
Expand Down Expand Up @@ -744,6 +749,10 @@ admin.product.category_csv.parent_category_id_description: ''
admin.product.category_csv.delete_flag_col: Category Deletion Flag
admin.product.category_csv.delete_flag_description: 'Specify 0: Upload or 1: Delete. If unspecified, it is set to 0.'

# Product CSV
admin.product.product_csv_upload__title: 'Upload product CSV'
admin.product.product_csv_upload__message: 'Upload the product CSV file. Is it OK?'

#------------------------------------------------------------------------------------
# Orders
#------------------------------------------------------------------------------------
Expand Down
9 changes: 9 additions & 0 deletions src/Eccube/Resource/locale/messages.ja.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,8 @@ admin.common.next: 次へ
admin.common.first: 最初へ
admin.common.last: 最後へ
admin.common.back: 戻る
admin.common.open_detail: '詳細を表示'
admin.common.close_detail: '詳細を閉じる'

# 汎用ラベル, メッセージ
admin.common.show: 表示
Expand Down Expand Up @@ -469,6 +471,9 @@ admin.common.csv_invalid_can_not: '%line%行目の%name%は設定できません
admin.common.csv_invalid_image: '%line%行目の%name%には末尾に"/"や"../"を使用できません'
admin.common.csv_invalid_foreign_key: '%line%行目の%name%は関連するデータがあるため削除できません'
admin.common.csv_invalid_description_detail_upper_limit: '%line%行目の%name%は%max%文字以下の文字列を指定してください。'
admin.common.csv_upload_in_progress: 'CSVファイルのアップロード中...'
admin.common.csv_upload_line_success: '%from%行目〜%to%行目を登録しました'
admin.common.csv_upload_line_error: 'エラーが発生しました。%from%行目以降の登録処理はキャンセルされました'
admin.common.drag_and_drop_description: 項目の順番はドラッグ&ドロップでも変更可能です。
admin.common.drag_and_drop_image_description: 画像をドラッグ&ドロップまたは
admin.common.delete_modal__title: 削除します
Expand Down Expand Up @@ -744,6 +749,10 @@ admin.product.category_csv.parent_category_id_description: ''
admin.product.category_csv.delete_flag_col: カテゴリ削除フラグ
admin.product.category_csv.delete_flag_description: 0:登録 1:削除を指定します。未指定の場合、0として扱います。

# 商品CSV
admin.product.product_csv_upload__title: '商品CSVをアップロードします'
admin.product.product_csv_upload__message: '商品CSVファイルをアップロードします。よろしいですか?'

#------------------------------------------------------------------------------------
# 受注
#------------------------------------------------------------------------------------
Expand Down