HEX
Server: Apache
System: Linux s198.coreserver.jp 5.15.0-151-generic #161-Ubuntu SMP Tue Jul 22 14:25:40 UTC 2025 x86_64
User: nagasaki (10062)
PHP: 7.1.33
Disabled: NONE
Upload Files
File: /virtual/nagasaki/public_html/ec/tests/Eccube/Tests/Web/Admin/Product/CsvImportControllerTest.php
<?php

namespace Eccube\Tests\Web\Admin\Product;

use Eccube\Tests\Web\Admin\AbstractAdminWebTestCase;
use Symfony\Component\HttpFoundation\File\UploadedFile;

class CsvImportControllerTest extends AbstractAdminWebTestCase
{
    protected $Products;
    protected $filepath;
    
    private $categoriesIdList = array();

    public function setUp()
    {
        parent::setUp();
        $this->filepath = __DIR__.'/products.csv';
        copy(__DIR__.'/../../../../../Fixtures/products.csv', $this->filepath); // 削除されてしまうのでコピーしておく
    }

    public function tearDown()
    {
        if (file_exists($this->filepath)) {
            unlink($this->filepath);
        }
        parent::tearDown();
    }

    /**
     * CSVを生成するための配列を返す.
     *
     * @param boolean $has_header ヘッダ行を含める場合 true
     * @return array CSVを生成するための配列
     * @see CsvImportController::getProductCsvHeader()
     */
    public function createCsvAsArray($has_header = true)
    {
        $faker = $this->getFaker();

        $price01 = $faker->randomNumber(5);
        if (mt_rand(0, 1)) {
            $price01 = number_format($price01);
        }

        $price02 = $faker->randomNumber(5);
        if (mt_rand(0, 1)) {
            $price02 = number_format($price02);
        }

        $csv = array(
            '商品ID' => null,
            '公開ステータス(ID)' => 1,
            '商品名' => "商品名".$faker->word."商品名",
            'ショップ用メモ欄' => "ショップ用メモ欄".$faker->paragraph."ショップ用メモ欄",
            '商品説明(一覧)' => "商品説明(一覧)".$faker->paragraph."商品説明(一覧)",
            '商品説明(詳細)' => "商品説明(詳細)".$faker->text."商品説明(詳細)",
            '検索ワード' => "検索ワード".$faker->word."検索ワード",
            'フリーエリア' => "フリーエリア".$faker->paragraph."フリーエリア",
            '商品削除フラグ' => 0,
            '商品画像' => $faker->word.'.jpg,'.$faker->word.'.jpg',
            '商品カテゴリ(ID)' => '5,6',
            'タグ(ID)' => '1,2',
            '商品種別(ID)' => 1,
            '規格分類1(ID)' => 3,
            '規格分類2(ID)' => 6,
            '発送日目安(ID)' => 1,
            '商品コード' => $faker->word,
            '在庫数' => 100,
            '在庫数無制限フラグ' => 0,
            '販売制限数' => null,
            '通常価格' => $price01,
            '販売価格' => $price02,
            '送料' => 0,
            '商品規格削除フラグ' => 0
        );
        $result = array();
        if ($has_header) {
            $result[] = array_keys($csv);
        }
        $result[] = array_values($csv);
        return $result;
    }

    /**
     * 引数の配列から CSV を生成し, リソースを返す.
     */
    public function createCsvFromArray(array $csv, $filename = 'products.csv')
    {
        $dir = sys_get_temp_dir();
        $filepath = $dir.'/'.$filename;
        $fp = fopen($filepath, 'w');
        if ($fp !== false) {
            foreach ($csv as $row) {
                fputcsv($fp, $row);
            }
        } else {
            throw new \Exception('create error!');
        }
        fclose($fp);
        return $filepath;
    }

    public function testCsvProduct()
    {
        // 3商品生成
        $csv = $this->createCsvAsArray();
        $csv = array_merge($csv, $this->createCsvAsArray(false));

        // 規格1のみの商品
        $csvClass1Only = $this->createCsvAsArray(false);
        $csvClass1Only[0][14] = null; // 規格分類2(ID)
        $csvClass1Only[0][16] = 'class1-only'; // 商品コード
        $csv = array_merge($csv, $csvClass1Only);

        $this->filepath = $this->createCsvFromArray($csv);

        $crawler = $this->scenario();

        $Products = $this->app['eccube.repository.product']->findAll();

        $this->expected = 5;    // 3商品 + 既存2商品
        $this->actual = count($Products);
        $this->verify();

        // ProductCategoryTest
        //カテゴリーIDs
        foreach($csv as $csvRow){
            $csvCat[md5($csvRow[2])] = $csvRow[10];
        }
        foreach ($Products as $Product){
            $nameHash = md5($Product->getName());
            if(!isset($csvCat[$nameHash])){
                continue;
            }
            // expected categories is
            $expectedIds = $this->getExpectedCategoriesIdList($csvCat[$nameHash]);
            $actualIds = array();
            /* @var $Product \Eccube\Entity\Product */
            foreach ($Product->getProductCategories() as $ProductCategory){
                /* @var $ProductCategory \Eccube\Entity\ProductCategory */
                $actualIds[$ProductCategory->getCategoryId()] = $ProductCategory->getCategoryId();
                $this->expected = $expectedIds[$ProductCategory->getCategoryId()];
                $this->actual = $ProductCategory->getCategoryId();
                $this->verify();
            }
            foreach($expectedIds as $catId){
                $this->expected = $catId;
                $this->actual = $actualIds[$catId];
                $this->verify();
            }
        }

        $this->assertRegexp('/商品登録CSVファイルをアップロードしました。/u',
            $crawler->filter('div.alert-success')->text());

        // 規格1のみ商品の確認
        // dtb_product_class.del_flg = 1 の確認をしたいので PDO で取得
        $pdo = $this->app['orm.em']->getConnection()->getWrappedConnection();
        $sql = "SELECT * FROM dtb_product_class WHERE product_code = 'class1-only' ORDER BY del_flg DESC";
        $stmt = $pdo->prepare($sql);
        $stmt->execute();
        $result = $stmt->fetchAll();

        $this->expected = 2;
        $this->actual = count($result);
        $this->verify('取得できるのは2行');

        $this->expected = 1;
        $this->actual = $result[0]['del_flg'];
        $this->verify('result[0] は del_flg = 1');

        $this->expected = null;
        $this->actual = $result[0]['class_category_id1'];
        $this->verify('class_category_id1 は null');

        $this->expected = null;
        $this->actual = $result[0]['class_category_id2'];
        $this->verify('class_category_id2 は null');

        // del_flg = 0 の行の確認
        $this->expected = 0;
        $this->actual = $result[1]['del_flg'];
        $this->verify('result[1] は del_flg = 0');

        $this->expected = 3;
        $this->actual = $result[1]['class_category_id1'];
        $this->verify('class_category_id1 は 3');

        $this->expected = null;
        $this->actual = $result[1]['class_category_id2'];
        $this->verify('class_category_id2 は null');
    }

    public function testCsvImportWithExistsProducts()
    {
        $crawler = $this->scenario();

        $Products = $this->app['eccube.repository.product']->findAll();

        $this->expected = 12;
        $this->actual = count($Products);
        $this->verify();

        $new_count = 0;
        foreach ($Products as $Product) {
            $ProductClasses = $Product->getProductClasses();
            foreach ($ProductClasses as $ProductClass) {
                if (preg_match('/fork-0[0-9]-new/', $ProductClass->getCode())) {
                    $new_count++;
                }
            }
            
            // categories
            $dateTimeNow = new \DateTime('-20 minutes');
            // check only new records
            if($Product->getUpdateDate() > $dateTimeNow){
                $expectedCategoryIds = array();
                $actualCategoryIds = array();
                foreach ($Product->getProductCategories() as $ProductCategory){
                    $expectedCategoryIds[$ProductCategory->getCategoryId()] = $ProductCategory->getCategoryId();
                    $actualCategoryIds[$ProductCategory->getCategoryId()] = $ProductCategory->getCategoryId();
                    foreach($this->getParentsCategoriesId($ProductCategory->getCategoryId()) as $catId){
                        $expectedCategoryIds[$catId] = $catId;
                    }
                }
                foreach ($expectedCategoryIds as $catId){
                    $this->expected = $catId;
                    $this->actual = $actualCategoryIds[$catId];
                    $this->verify();
                }
            }
        }

        $this->expected = 3;
        $this->actual = $new_count;
        $this->verify('fork-0[0-9]-new に商品コードを変更したのは '.$this->expected.'商品規格');

        $this->assertRegexp('/商品登録CSVファイルをアップロードしました。/u',
            $crawler->filter('div.alert-success')->text());

    }

    /**
     * 既存の規格なし商品に商品規格を追加する.
     */
    public function testCsvImportWithExistsProductsAddProductClass()
    {
        // 商品生成
        $csv = $this->createCsvAsArray();
        $csv[1][0] = 2;                        // 商品ID = 2 に規格を追加する
        $csv[1][16] = 'add-class';             // 商品コード

        $this->filepath = $this->createCsvFromArray($csv);

        $crawler = $this->scenario();

        $Products = $this->app['eccube.repository.product']->findAll();

        $this->expected = 2;    // 既存2商品
        $this->actual = count($Products);
        $this->verify();

        $this->assertRegexp('/商品登録CSVファイルをアップロードしました。/u',
            $crawler->filter('div.alert-success')->text());

        // 規格1のみ商品の確認
        // dtb_product_class.del_flg = 1 の確認をしたいので PDO で取得
        $pdo = $this->app['orm.em']->getConnection()->getWrappedConnection();
        $sql = "SELECT * FROM dtb_product_class WHERE product_id = 2 ORDER BY del_flg DESC";
        $stmt = $pdo->prepare($sql);
        $stmt->execute();
        $result = $stmt->fetchAll();

        $this->expected = 2;
        $this->actual = count($result);
        $this->verify('取得できるのは2行');

        $this->expected = 1;
        $this->actual = $result[0]['del_flg'];
        $this->verify('result[0] は del_flg = 1');

        $this->expected = null;
        $this->actual = $result[0]['class_category_id1'];
        $this->verify('class_category_id1 は null');

        $this->expected = null;
        $this->actual = $result[0]['class_category_id2'];
        $this->verify('class_category_id2 は null');

        // del_flg = 0 の行の確認
        $this->expected = 0;
        $this->actual = $result[1]['del_flg'];
        $this->verify('result[1] は del_flg = 0');

        $this->expected = 3;
        $this->actual = $result[1]['class_category_id1'];
        $this->verify('class_category_id1 は 3');

        $this->expected = 6;
        $this->actual = $result[1]['class_category_id2'];
        $this->verify('class_category_id2 は 6');
        
        // ProductCategoryTest
        //カテゴリーIDs
        foreach($csv as $csvRow){
            $csvCat[md5($csvRow[2])] = $csvRow[10];
        }
        foreach ($Products as $Product){
            $nameHash = md5($Product->getName());
            if(!isset($csvCat[$nameHash])){
                continue;
            }
            // expected categories is
            $expectedIds = $this->getExpectedCategoriesIdList($csvCat[$nameHash]);
            $actualIds = array();
            /* @var $Product \Eccube\Entity\Product */
            foreach ($Product->getProductCategories() as $ProductCategory){
                /* @var $ProductCategory \Eccube\Entity\ProductCategory */
                $actualIds[$ProductCategory->getCategoryId()] = $ProductCategory->getCategoryId();
                $this->expected = $expectedIds[$ProductCategory->getCategoryId()];
                $this->actual = $ProductCategory->getCategoryId();
                $this->verify();
            }
            foreach($expectedIds as $catId){
                $this->expected = $catId;
                $this->actual = $actualIds[$catId];
                $this->verify();
            }
        }
        
    }

    public function testCsvTemplateWithProduct()
    {
        // 一旦別の変数に代入しないと, config 以下の値を書きかえることができない
        $config = $this->app['config'];
        $config['csv_export_encoding'] = 'UTF-8'; // SJIS だと比較できないので UTF-8 に変更しておく
        $this->app['config'] = $config;

        $this->expectOutputString('商品ID,公開ステータス(ID),商品名,ショップ用メモ欄,商品説明(一覧),商品説明(詳細),検索ワード,フリーエリア,商品削除フラグ,商品画像,商品カテゴリ(ID),タグ(ID),商品種別(ID),規格分類1(ID),規格分類2(ID),発送日目安(ID),商品コード,在庫数,在庫数無制限フラグ,販売制限数,通常価格,販売価格,送料,商品規格削除フラグ'."\n");

        $crawler = $this->client->request(
            'GET',
            $this->app->path('admin_product_csv_template', array('type' => 'product'))
        );

        $this->assertTrue($this->client->getResponse()->isSuccessful());
    }

    public function testCsvCategory()
    {
        $this->filepath = __DIR__.'/categories.csv';
        copy(__DIR__.'/../../../../../Fixtures/categories.csv', $this->filepath); // 削除されてしまうのでコピーしておく

        $crawler = $this->scenario('admin_product_category_csv_import', 'categories.csv');

        $Categories = $this->app['eccube.repository.category']->findAll();

        $this->expected = 6;
        $this->actual = count($Categories);
        $this->verify();

        $this->assertRegexp('/カテゴリ登録CSVファイルをアップロードしました。/u',
            $crawler->filter('div.alert-success')->text());
    }

    public function testCsvCategoryWithNew()
    {
        $csv = array(
            array('カテゴリID', 'カテゴリ名', '親カテゴリID'),
            array('', '新カテゴリ', '')
        );
        $this->filepath = $this->createCsvFromArray($csv, 'categories.csv');

        $crawler = $this->scenario('admin_product_category_csv_import', 'categories.csv');

        $Categories = $this->app['eccube.repository.category']->findBy(array('name' => '新カテゴリ'));

        $this->expected = 1;
        $this->actual = count($Categories);
        $this->verify();

        $this->assertRegexp('/カテゴリ登録CSVファイルをアップロードしました。/u',
            $crawler->filter('div.alert-success')->text());
    }

    public function testCsvTemplateWithCategory()
    {
        // 一旦別の変数に代入しないと, config 以下の値を書きかえることができない
        $config = $this->app['config'];
        $config['csv_export_encoding'] = 'UTF-8'; // SJIS だと比較できないので UTF-8 に変更しておく
        $this->app['config'] = $config;

        $this->expectOutputString('カテゴリID,カテゴリ名,親カテゴリID'."\n");

        $crawler = $this->client->request(
            'GET',
            $this->app->path('admin_product_csv_template', array('type' => 'category'))
        );

        $this->assertTrue($this->client->getResponse()->isSuccessful());
    }

    /**
     * $this->filepath のファイルを CSV アップロードし, 完了画面の crawler を返す.
     */
    public function scenario($bind = 'admin_product_csv_import', $original_name = 'products.csv')
    {
        $file = new UploadedFile(
            $this->filepath,    // file path
            $original_name,     // original name
            'text/csv',         // mimeType
            null,               // file size
            null,               // error
            true                // test mode
        );

        $crawler = $this->client->request(
            'POST',
            $this->app->path($bind),
            array(
                'admin_csv_import' => array(
                    '_token' => 'dummy',
                    'import_file' => $file
                )
            ),
            array('import_file' => $file)
        );
        return $crawler;
    }
    
    private function getExpectedCategoriesIdList($categoriesStr)
    {
        $catIds = array();
        $tmp = explode(',', $categoriesStr);
        foreach($tmp as $id){
            $id = trim($id);
            if(is_numeric($id)){
                $catIds[$id] = (int) $id;
                foreach ($this->getParentsCategoriesId($id) as $parentCategoryId){
                    $catIds[$parentCategoryId] = $parentCategoryId;
                }
            }
        }
        return $catIds;
    }
    
    private function getParentsCategoriesId($categoryId)
    {
        if(!isset($this->categoriesIdList[$categoryId])){
            $this->categoriesIdList[$categoryId] = array();
            $Category = $this->app['eccube.repository.category']->find($categoryId);
            if($Category){
                $this->categoriesIdList[$categoryId][$Category->getId()] = $Category->getId(); 
                foreach($Category->getPath() as $ParentCategory){
                    $this->categoriesIdList[$categoryId][$ParentCategory->getId()] = $ParentCategory->getId(); 
                }
            }
        }
        
        return $this->categoriesIdList[$categoryId];
    }
}