source: branches/version-2_13-dev/data/class/helper/SC_Helper_CSV.php @ 23388

Revision 23388, 12.7 KB checked in by Seasoft, 10 years ago (diff)

#2534 (CSV 出力で一時ファイルが異常な状態で放置されることがある)
#2535 (CSV 生成の独自処理を減らす)
#2448 (typo修正・ソース整形・ソースコメントの改善 for 2.13.2)

  • 下記メソッドは不要となったが、マイナーバージョンアップまでは前方互換用に残す。
    • SC_Helper_CSV#sfArrayToCsv
    • SC_Helper_CSV#lfDownloadCsv
    • SC_Helper_CSV#lfDownloadCSVFile
  • Property svn:eol-style set to LF
  • Property svn:keywords set to Id
  • Property svn:mime-type set to text/x-httpd-php; charset=UTF-8
RevLine 
[15535]1<?php
2  /*
[22206]3   * Copyright(c) 2000-2013 LOCKON CO.,LTD. All Rights Reserved.
[15535]4   *
5   * http://www.lockon.co.jp/
6   */
7
8  /**
9   * CSV 関連 のヘルパークラス.
10   *
11   * @package Page
12   * @author LOCKON CO.,LTD.
13   * @version $Id$
14   */
[22856]15class SC_Helper_CSV
[22567]16{
[15535]17    /** 項目英名 */
[23124]18    public $arrSubnavi;
[15535]19
20    /** 項目名 */
[23124]21    public $arrSubnaviName;
[15535]22
[23375]23    /** ヘッダーを出力するか (cbOutputCSV 用) */
24    private $output_header = false;
25
[15535]26    /**
27     * デフォルトコンストラクタ.
28     */
[23124]29    public function __construct()
[22567]30    {
[15535]31        $this->init();
32    }
33
[15566]34    /**
[20280]35     * 項目情報を初期化する.
36     *
37     * @access private
38     * @return void
39     */
[23124]40    public function init()
[22567]41    {
[20280]42        $this->arrSubnavi = array(
[21527]43            1 => 'product',
44            2 => 'customer',
45            3 => 'order',
46            4 => 'review',
47            5 => 'category',
48        );
[20280]49
50        $this->arrSubnaviName = array(
[21527]51            1 => '商品管理',
52            2 => '会員管理',
53            3 => '受注管理',
54            4 => 'レビュー',
55            5 => 'カテゴリ',
56        );
[20280]57    }
58
59    /**
[20737]60     * CSVファイルを送信する
61     *
[23124]62     * @param  integer $csv_id      CSVフォーマットID
63     * @param  string  $where       WHERE条件文
64     * @param  array   $arrVal      プリペアドステートメントの実行時に使用される配列。配列の要素数は、クエリ内のプレースホルダの数と同じでなければなりません。
65     * @param  string  $order       ORDER文
66     * @param  boolean $is_download true:ダウンロード用出力までさせる false:CSVの内容を返す(旧方式、メモリを食います。)
67     * @return mixed   $is_download = true時 成功失敗フラグ(boolean) 、$is_downalod = false時 string
[20737]68     */
[23124]69    public function sfDownloadCsv($csv_id, $where = '', $arrVal = array(), $order = '', $is_download = false)
[22567]70    {
[22739]71        $objQuery =& SC_Query_Ex::getSingletonInstance();
72
[20737]73        // CSV出力タイトル行の作成
74        $arrOutput = SC_Utils_Ex::sfSwapArray($this->sfGetCsvOutput($csv_id, 'status = ' . CSV_COLUMN_STATUS_FLG_ENABLE));
75        if (count($arrOutput) <= 0) return false; // 失敗終了
76        $arrOutputCols = $arrOutput['col'];
77
78        $cols = SC_Utils_Ex::sfGetCommaList($arrOutputCols, true);
79
[22738]80        // 商品の場合
81        if ($csv_id == 1) {
[22739]82            // この WHERE 句を足さないと無効な規格も出力される。現行仕様と合わせる為追加。
[20737]83            $inner_where = 'dtb_products_class.del_flg = 0';
[22739]84            $from = SC_Product_Ex::prdclsSQL($inner_where);
[22738]85        }
[22739]86        // 会員の場合
87        else if ($csv_id == 2) {
88            $from = 'dtb_customer';
[20737]89        }
[22739]90        // 注文の場合
91        else if ($csv_id == 3) {
92            $from = 'dtb_order';
93        }
94        // レビューの場合
95        else if ($csv_id == 4) {
96            $from = 'dtb_review AS A INNER JOIN dtb_products AS B on A.product_id = B.product_id';
97        }
98        // カテゴリの場合
99        else if ($csv_id == 5) {
100            $from = 'dtb_category';
101        }
[22738]102
[22739]103        $objQuery->setOrder($order);
104        $sql = $objQuery->getSql($cols, $from, $where);
105
[20737]106        return $this->sfDownloadCsvFromSql($sql, $arrVal, $this->arrSubnavi[$csv_id], $arrOutput['disp_name'], $is_download);
107    }
108
109    /**
[15566]110     * CSV 項目を出力する.
111     *
[23124]112     * @param  integer $csv_id CSV ID
113     * @param  string  $where  SQL の WHERE 句
114     * @param  array   $arrVal WHERE 句の要素
115     * @param  array   $order  SQL の ORDER BY 句
116     * @return array   CSV 項目の配列
[15566]117     */
[23124]118    public function sfGetCsvOutput($csv_id = '', $where = '', $arrVal = array(), $order = 'rank, no')
[22567]119    {
[20507]120        $objQuery =& SC_Query_Ex::getSingletonInstance();
[20540]121
[19752]122        $cols = 'no, csv_id, col, disp_name, rank, status, rw_flg, mb_convert_kana_option, size_const_type, error_check_types';
123        $table = 'dtb_csv';
[20540]124
[21441]125        if (SC_Utils_Ex::sfIsInt($csv_id)) {
[21514]126            if ($where == '') {
127                $where = 'csv_id = ?';
[21441]128            } else {
[19752]129                $where = "$where AND csv_id = ?";
130            }
[18616]131            $arrVal[] = $csv_id;
[15566]132        }
[19752]133        $objQuery->setOrder($order);
[20540]134
[19752]135        $arrRet = $objQuery->select($cols, $table, $where, $arrVal);
[22856]136
[19752]137        return $arrRet;
[15566]138    }
139
[19752]140    /**
141     * CSVが出力設定でインポート可能かのチェック
142     *
[19999]143     * @param array sfGetCsvOutputで取得した内容(またはそれと同等の配列)
[19752]144     * @return boolean true:インポート可能、false:インポート不可
145     */
[23124]146    public function sfIsImportCSVFrame(&$arrCSVFrame)
[22567]147    {
[19752]148        $result = true;
[21927]149        foreach ($arrCSVFrame as $val) {
[21684]150            if ($val['status'] != CSV_COLUMN_STATUS_FLG_ENABLE
151                && $val['rw_flg'] == CSV_COLUMN_RW_FLG_READ_WRITE
152                && $val['error_check_types'] != ''
153                && strpos(strtoupper($val['error_check_types']), 'EXIST_CHECK') !== FALSE
154            ) {
[19752]155                //必須フィールド
156                $result = false;
157            }
158        }
[22856]159
[19752]160        return $result;
161    }
[20540]162
[19752]163    /**
164     * CSVが出力設定で更新可能かのチェック
165     *
[19999]166     * @param array sfGetCsvOutputで取得した内容(またはそれと同等の配列)
[19752]167     * @return boolean true:更新可能、false:新規追加のみ不可
168     */
[23124]169    public function sfIsUpdateCSVFrame(&$arrCSVFrame)
[22567]170    {
[19752]171        $result = true;
[21927]172        foreach ($arrCSVFrame as $val) {
[21684]173            if ($val['status'] != CSV_COLUMN_STATUS_FLG_ENABLE
174                && $val['rw_flg'] == CSV_COLUMN_RW_FLG_KEY_FIELD
[23388]175            ) {
[19752]176                //キーフィールド
177                $result = false;
178            }
179        }
[22856]180
[19752]181        return $result;
182    }
[20540]183
[19752]184    /**
185     * CSVファイルのカウント数を得る.
186     *
[23124]187     * @param  resource $fp fopenを使用して作成したファイルポインタ
188     * @return integer  CSV のカウント数
[19752]189     */
[23124]190    public function sfGetCSVRecordCount($fp)
[22567]191    {
[19752]192        $count = 0;
[21441]193        while (!feof($fp)) {
[19752]194            $arrCSV = fgetcsv($fp, CSV_LINE_MAX);
195            $count++;
196        }
197        // ファイルポインタを戻す
198        if (rewind($fp)) {
199            return $count-1;
200        } else {
201            return FALSE;
202        }
203    }
204
[20280]205    /**
206     * CSV作成 テンポラリファイル出力 コールバック関数
207     *
[23124]208     * @param  mixed   $data 出力データ
[20280]209     * @return boolean true (true:固定 false:中断)
210     */
[23124]211    public function cbOutputCSV($data)
[22567]212    {
[23375]213        // 1行目のみヘッダーを出力する
214        if ($this->output_header) {
[23388]215            fputcsv($this->fpOutput, array_keys($data));
[23375]216            $this->output_header = false;
217        }
[23388]218        fputcsv($this->fpOutput, $data);
[22021]219        SC_Utils_Ex::extendTimeOut();
[22856]220
[19742]221        return true;
222    }
[18616]223
[20280]224    /**
[20318]225     * SQL文からクエリ実行し CSVファイルを送信する
226     *
[23124]227     * @param  integer $sql         SQL文
228     * @param  array   $arrVal      プリペアドステートメントの実行時に使用される配列。配列の要素数は、クエリ内のプレースホルダの数と同じでなければなりません。
[23375]229     * @param  string       ファイル名の頭に付ける文字列
230     * @param  array|null   ヘッダ出力列配列。null の場合、SQL 文の列名を出力する。
231     * @param  boolean      true:ダウンロード用出力までさせる false:CSVの内容を返す(旧方式、メモリを食います。)
[23124]232     * @return mixed   $is_download = true時 成功失敗フラグ(boolean) 、$is_downalod = false時 string
[20318]233     */
[23375]234    public function sfDownloadCsvFromSql($sql, $arrVal = array(), $file_head = 'csv', $arrHeader = null, $is_download = false)
[22567]235    {
[20507]236        $objQuery =& SC_Query_Ex::getSingletonInstance();
[20318]237
[23388]238        if (!$is_download) {
239            ob_start();
240        }
[23375]241
[23388]242        $this->fpOutput =& SC_Helper_CSV_Ex::fopen_for_output_csv();
243
[23375]244        // ヘッダー構築
[23388]245        $this->output_header = false;
[21441]246        if (is_array($arrHeader)) {
[23388]247            fputcsv($this->fpOutput, $arrHeader);
[23375]248        } elseif (is_null($arrHeader)) {
249            // ループバック内でヘッダーを出力する
250            $this->output_header = true;
[20318]251        }
[20285]252
[20280]253        $objQuery->doCallbackAll(array(&$this, 'cbOutputCSV'), $sql, $arrVal);
[20276]254
[23375]255        // コールバック内でヘッダー出力する場合、0行時にヘッダーを生成できない。
256        // コールバックが呼ばれていない場合、念のため CRLF を出力しておく。
257        // XXX WEB画面前提で、アラート表示する流れのほうが親切かもしれない。
258        if ($this->output_header) {
259            fwrite($this->fpOutput, "\r\n");
260        }
261
[20276]262        fclose($this->fpOutput);
263
[23388]264        // CSV 用の HTTP ヘッダーを送出する。
[21441]265        if ($is_download) {
[23388]266            $file_name = $file_head . '_' . date('ymd_His') .'.csv';
267            SC_Response_Ex::headerForDownload($file_name);
268            $return = true;
[20276]269        }
[23388]270        // 戻り値にCSVデータをセットする
271        else {
272            $return = ob_get_clean();
273        }
[20318]274
[23388]275        return $return;
[20276]276    }
277
[15535]278    /**
[23388]279     * 前方互換用
[20737]280     *
[23388]281     * @deprecated 2.13.2 fputcsv を使うこと。(sfDownloadCsvFromSql や cbOutputCSV の実装を参照)
[17799]282     */
[23124]283    public function sfArrayToCsv($fields, $delimiter = ',', $enclosure = '"', $arrayDelimiter = '|')
[22567]284    {
[23388]285        trigger_error('前方互換用メソッドが使用されました。', E_USER_WARNING);
[21442]286        if (strlen($delimiter) != 1) {
[17799]287            trigger_error('delimiter must be a single character', E_USER_WARNING);
[23124]288
[21514]289            return '';
[17799]290        }
[20540]291
[21442]292        if (strlen($enclosure) < 1) {
[17799]293            trigger_error('enclosure must be a single character', E_USER_WARNING);
[23124]294
[21514]295            return '';
[17799]296        }
[20540]297
[21935]298        foreach ($fields as $key => $value) {
[17799]299            $field =& $fields[$key];
[20540]300
[17799]301            // 配列を「|」区切りの文字列に変換する
302            if (is_array($field)) {
303                $field = implode($arrayDelimiter, $field);
304            }
[20540]305
[17799]306            /* enclose a field that contains a delimiter, an enclosure character, or a newline */
[21527]307            if (is_string($field)
[17799]308                && preg_match('/[' . preg_quote($delimiter) . preg_quote($enclosure) . '\\s]/', $field)
309            ) {
310                $field = $enclosure . preg_replace('/' . preg_quote($enclosure) . '/', $enclosure . $enclosure, $field) . $enclosure;
311            }
312        }
[20540]313
[17799]314        return implode($delimiter, $fields);
315    }
[20540]316
[17799]317    /**
[23388]318     * 前方互換用
[20737]319     *
[23388]320     * @deprecated 2.13.2
[17799]321     */
[23124]322    public function lfDownloadCsv($arrData, $prefix = '')
[22567]323    {
[23388]324        trigger_error('前方互換用メソッドが使用されました。', E_USER_WARNING);
[21514]325        if ($prefix == '') {
[20484]326            $dir_name = SC_Utils_Ex::sfUpDirName();
[21514]327            $file_name = $dir_name . date('ymdHis') .'.csv';
[17799]328        } else {
[21514]329            $file_name = $prefix . date('ymdHis') .'.csv';
[17799]330        }
[23388]331        SC_Response_Ex::headerForDownload($file_name);
[17799]332
333        /* データを出力 */
[23388]334        $fp =& SC_Helper_CSV_Ex::fopen_for_output_csv();
[20737]335        foreach ($arrData as $lineArray) {
[23388]336            fputcsv($fp, $lineArray);
[17799]337        }
[23388]338        fclose($fp);
[17799]339    }
[20540]340
[19742]341    /**
[23388]342     * 前方互換用
[20737]343     *
[23388]344     * @deprecated 2.13.2
[19742]345     */
[23124]346    public function lfDownloadCSVFile($filepath, $prefix = '')
[22567]347    {
[23388]348        trigger_error('前方互換用メソッドが使用されました。', E_USER_WARNING);
[21514]349        $file_name = $prefix . date('YmdHis') . '.csv';
[23388]350        SC_Response_Ex::headerForDownload($file_name);
[20540]351
[19742]352        /* データを出力 */
353        // file_get_contentsはメモリマッピングも自動的に使ってくれるので高速&省メモリ
354        echo file_get_contents($filepath);
355    }
[23388]356
357    /**
358     * CSV 出力用のファイルポインタリソースを開く
359     *
360     * @return resource ファイルポインタリソース
361     */
362    public static function &fopen_for_output_csv($filename = 'php://output')
363    {
364        $fp = fopen($filename, 'w');
365
366        stream_filter_append($fp, 'convert.iconv.utf-8/cp932');
367        stream_filter_append($fp, 'convert.eccube_lf2crlf');
368
369        return $fp;
370    }
[15542]371}
[23388]372
373/**
374 * 改行コードを CRLF に変換するフィルター
375 *
376 * @package php_user_filter
377 * @author Seasoft 塚田将久 (新規作成)
378 * @version $Id$
379 */
380class php_user_filter_lf2crlf extends php_user_filter
381{
382    function filter($in, $out, &$consumed, $closing)
383    {
384        while ($bucket = stream_bucket_make_writeable($in)) {
385            $bucket->data = preg_replace("/[\r\n]+$/", "\r\n", $bucket->data);
386            $consumed += $bucket->datalen;
387            stream_bucket_append($out, $bucket);
388        }
389        return PSFS_PASS_ON;
390    }
391}
392stream_filter_register('convert.eccube_lf2crlf', 'php_user_filter_lf2crlf');
Note: See TracBrowser for help on using the repository browser.