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

Revision 23388, 12.7 KB checked in by Seasoft, 7 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
Line 
1<?php
2  /*
3   * Copyright(c) 2000-2013 LOCKON CO.,LTD. All Rights Reserved.
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   */
15class SC_Helper_CSV
16{
17    /** 項目英名 */
18    public $arrSubnavi;
19
20    /** 項目名 */
21    public $arrSubnaviName;
22
23    /** ヘッダーを出力するか (cbOutputCSV 用) */
24    private $output_header = false;
25
26    /**
27     * デフォルトコンストラクタ.
28     */
29    public function __construct()
30    {
31        $this->init();
32    }
33
34    /**
35     * 項目情報を初期化する.
36     *
37     * @access private
38     * @return void
39     */
40    public function init()
41    {
42        $this->arrSubnavi = array(
43            1 => 'product',
44            2 => 'customer',
45            3 => 'order',
46            4 => 'review',
47            5 => 'category',
48        );
49
50        $this->arrSubnaviName = array(
51            1 => '商品管理',
52            2 => '会員管理',
53            3 => '受注管理',
54            4 => 'レビュー',
55            5 => 'カテゴリ',
56        );
57    }
58
59    /**
60     * CSVファイルを送信する
61     *
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
68     */
69    public function sfDownloadCsv($csv_id, $where = '', $arrVal = array(), $order = '', $is_download = false)
70    {
71        $objQuery =& SC_Query_Ex::getSingletonInstance();
72
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
80        // 商品の場合
81        if ($csv_id == 1) {
82            // この WHERE 句を足さないと無効な規格も出力される。現行仕様と合わせる為追加。
83            $inner_where = 'dtb_products_class.del_flg = 0';
84            $from = SC_Product_Ex::prdclsSQL($inner_where);
85        }
86        // 会員の場合
87        else if ($csv_id == 2) {
88            $from = 'dtb_customer';
89        }
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        }
102
103        $objQuery->setOrder($order);
104        $sql = $objQuery->getSql($cols, $from, $where);
105
106        return $this->sfDownloadCsvFromSql($sql, $arrVal, $this->arrSubnavi[$csv_id], $arrOutput['disp_name'], $is_download);
107    }
108
109    /**
110     * CSV 項目を出力する.
111     *
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 項目の配列
117     */
118    public function sfGetCsvOutput($csv_id = '', $where = '', $arrVal = array(), $order = 'rank, no')
119    {
120        $objQuery =& SC_Query_Ex::getSingletonInstance();
121
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';
124
125        if (SC_Utils_Ex::sfIsInt($csv_id)) {
126            if ($where == '') {
127                $where = 'csv_id = ?';
128            } else {
129                $where = "$where AND csv_id = ?";
130            }
131            $arrVal[] = $csv_id;
132        }
133        $objQuery->setOrder($order);
134
135        $arrRet = $objQuery->select($cols, $table, $where, $arrVal);
136
137        return $arrRet;
138    }
139
140    /**
141     * CSVが出力設定でインポート可能かのチェック
142     *
143     * @param array sfGetCsvOutputで取得した内容(またはそれと同等の配列)
144     * @return boolean true:インポート可能、false:インポート不可
145     */
146    public function sfIsImportCSVFrame(&$arrCSVFrame)
147    {
148        $result = true;
149        foreach ($arrCSVFrame as $val) {
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            ) {
155                //必須フィールド
156                $result = false;
157            }
158        }
159
160        return $result;
161    }
162
163    /**
164     * CSVが出力設定で更新可能かのチェック
165     *
166     * @param array sfGetCsvOutputで取得した内容(またはそれと同等の配列)
167     * @return boolean true:更新可能、false:新規追加のみ不可
168     */
169    public function sfIsUpdateCSVFrame(&$arrCSVFrame)
170    {
171        $result = true;
172        foreach ($arrCSVFrame as $val) {
173            if ($val['status'] != CSV_COLUMN_STATUS_FLG_ENABLE
174                && $val['rw_flg'] == CSV_COLUMN_RW_FLG_KEY_FIELD
175            ) {
176                //キーフィールド
177                $result = false;
178            }
179        }
180
181        return $result;
182    }
183
184    /**
185     * CSVファイルのカウント数を得る.
186     *
187     * @param  resource $fp fopenを使用して作成したファイルポインタ
188     * @return integer  CSV のカウント数
189     */
190    public function sfGetCSVRecordCount($fp)
191    {
192        $count = 0;
193        while (!feof($fp)) {
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
205    /**
206     * CSV作成 テンポラリファイル出力 コールバック関数
207     *
208     * @param  mixed   $data 出力データ
209     * @return boolean true (true:固定 false:中断)
210     */
211    public function cbOutputCSV($data)
212    {
213        // 1行目のみヘッダーを出力する
214        if ($this->output_header) {
215            fputcsv($this->fpOutput, array_keys($data));
216            $this->output_header = false;
217        }
218        fputcsv($this->fpOutput, $data);
219        SC_Utils_Ex::extendTimeOut();
220
221        return true;
222    }
223
224    /**
225     * SQL文からクエリ実行し CSVファイルを送信する
226     *
227     * @param  integer $sql         SQL文
228     * @param  array   $arrVal      プリペアドステートメントの実行時に使用される配列。配列の要素数は、クエリ内のプレースホルダの数と同じでなければなりません。
229     * @param  string       ファイル名の頭に付ける文字列
230     * @param  array|null   ヘッダ出力列配列。null の場合、SQL 文の列名を出力する。
231     * @param  boolean      true:ダウンロード用出力までさせる false:CSVの内容を返す(旧方式、メモリを食います。)
232     * @return mixed   $is_download = true時 成功失敗フラグ(boolean) 、$is_downalod = false時 string
233     */
234    public function sfDownloadCsvFromSql($sql, $arrVal = array(), $file_head = 'csv', $arrHeader = null, $is_download = false)
235    {
236        $objQuery =& SC_Query_Ex::getSingletonInstance();
237
238        if (!$is_download) {
239            ob_start();
240        }
241
242        $this->fpOutput =& SC_Helper_CSV_Ex::fopen_for_output_csv();
243
244        // ヘッダー構築
245        $this->output_header = false;
246        if (is_array($arrHeader)) {
247            fputcsv($this->fpOutput, $arrHeader);
248        } elseif (is_null($arrHeader)) {
249            // ループバック内でヘッダーを出力する
250            $this->output_header = true;
251        }
252
253        $objQuery->doCallbackAll(array(&$this, 'cbOutputCSV'), $sql, $arrVal);
254
255        // コールバック内でヘッダー出力する場合、0行時にヘッダーを生成できない。
256        // コールバックが呼ばれていない場合、念のため CRLF を出力しておく。
257        // XXX WEB画面前提で、アラート表示する流れのほうが親切かもしれない。
258        if ($this->output_header) {
259            fwrite($this->fpOutput, "\r\n");
260        }
261
262        fclose($this->fpOutput);
263
264        // CSV 用の HTTP ヘッダーを送出する。
265        if ($is_download) {
266            $file_name = $file_head . '_' . date('ymd_His') .'.csv';
267            SC_Response_Ex::headerForDownload($file_name);
268            $return = true;
269        }
270        // 戻り値にCSVデータをセットする
271        else {
272            $return = ob_get_clean();
273        }
274
275        return $return;
276    }
277
278    /**
279     * 前方互換用
280     *
281     * @deprecated 2.13.2 fputcsv を使うこと。(sfDownloadCsvFromSql や cbOutputCSV の実装を参照)
282     */
283    public function sfArrayToCsv($fields, $delimiter = ',', $enclosure = '"', $arrayDelimiter = '|')
284    {
285        trigger_error('前方互換用メソッドが使用されました。', E_USER_WARNING);
286        if (strlen($delimiter) != 1) {
287            trigger_error('delimiter must be a single character', E_USER_WARNING);
288
289            return '';
290        }
291
292        if (strlen($enclosure) < 1) {
293            trigger_error('enclosure must be a single character', E_USER_WARNING);
294
295            return '';
296        }
297
298        foreach ($fields as $key => $value) {
299            $field =& $fields[$key];
300
301            // 配列を「|」区切りの文字列に変換する
302            if (is_array($field)) {
303                $field = implode($arrayDelimiter, $field);
304            }
305
306            /* enclose a field that contains a delimiter, an enclosure character, or a newline */
307            if (is_string($field)
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        }
313
314        return implode($delimiter, $fields);
315    }
316
317    /**
318     * 前方互換用
319     *
320     * @deprecated 2.13.2
321     */
322    public function lfDownloadCsv($arrData, $prefix = '')
323    {
324        trigger_error('前方互換用メソッドが使用されました。', E_USER_WARNING);
325        if ($prefix == '') {
326            $dir_name = SC_Utils_Ex::sfUpDirName();
327            $file_name = $dir_name . date('ymdHis') .'.csv';
328        } else {
329            $file_name = $prefix . date('ymdHis') .'.csv';
330        }
331        SC_Response_Ex::headerForDownload($file_name);
332
333        /* データを出力 */
334        $fp =& SC_Helper_CSV_Ex::fopen_for_output_csv();
335        foreach ($arrData as $lineArray) {
336            fputcsv($fp, $lineArray);
337        }
338        fclose($fp);
339    }
340
341    /**
342     * 前方互換用
343     *
344     * @deprecated 2.13.2
345     */
346    public function lfDownloadCSVFile($filepath, $prefix = '')
347    {
348        trigger_error('前方互換用メソッドが使用されました。', E_USER_WARNING);
349        $file_name = $prefix . date('YmdHis') . '.csv';
350        SC_Response_Ex::headerForDownload($file_name);
351
352        /* データを出力 */
353        // file_get_contentsはメモリマッピングも自動的に使ってくれるので高速&省メモリ
354        echo file_get_contents($filepath);
355    }
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    }
371}
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.