source: branches/version-2_13-dev/data/class/pages/admin/system/LC_Page_Admin_System_Bkup.php @ 22795

Revision 22795, 20.4 KB checked in by kimoto, 11 years ago (diff)

#2213 バックアップ管理 プラグインのテーブルをバックアップできずエラーとなる

クオート文字がmysqlで不適切なので修正しました

  • 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 * This file is part of EC-CUBE
4 *
5 * Copyright(c) 2000-2013 LOCKON CO.,LTD. All Rights Reserved.
6 *
7 * http://www.lockon.co.jp/
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22 */
23
24// {{{ requires
25require_once CLASS_EX_REALDIR . 'page_extends/admin/LC_Page_Admin_Ex.php';
26
27/**
28 * バックアップ のページクラス.
29 *
30 * @package Page
31 * @author LOCKON CO.,LTD.
32 * @version $Id$
33 */
34class LC_Page_Admin_System_Bkup extends LC_Page_Admin_Ex
35{
36
37    /** リストア中にエラーが発生したか */
38    var $tpl_restore_err = false;
39
40    /** 対象外とするシーケンス生成器 */
41    var $arrExcludeSequence = array(
42        'plsql_profiler_runid', // Postgres Plus Advanced Server 9.1
43        'snapshot_num',         // Postgres Plus Advanced Server 9.1
44    );
45
46    // }}}
47    // {{{ functions
48
49    /**
50     * Page を初期化する.
51     *
52     * @return void
53     */
54    function init()
55    {
56        parent::init();
57        $this->tpl_mainpage = 'system/bkup.tpl';
58        $this->tpl_mainno = 'system';
59        $this->tpl_subno = 'bkup';
60        $this->tpl_maintitle = 'システム設定';
61        $this->tpl_subtitle = 'バックアップ管理';
62
63        $this->bkup_dir = DATA_REALDIR . 'downloads/backup/';
64        $this->bkup_ext = '.tar.gz';
65    }
66
67    /**
68     * Page のプロセス.
69     *
70     * @return void
71     */
72    function process()
73    {
74        $this->action();
75        $this->sendResponse();
76    }
77
78    /**
79     * Page のアクション.
80     *
81     * @return void
82     */
83    function action()
84    {
85
86        $objFormParam = new SC_FormParam_Ex;
87
88        // パラメーターの初期化
89        $this->initParam($objFormParam, $_POST);
90
91        $arrErrTmp  = array();
92        $arrForm = array();
93
94        $this->mode = $this->getMode();
95        switch ($this->mode) {
96
97            // バックアップを作成する
98            case 'bkup':
99
100                // データ型エラーチェック
101                $arrErrTmp[1] = $objFormParam->checkError();
102
103                // データ型に問題がない場合
104                if (SC_Utils_Ex::isBlank($arrErrTmp[1])) {
105                    // データ型以外のエラーチェック
106                    $arrErrTmp[2] = $this->lfCheckError($objFormParam->getHashArray(), $this->mode);
107                }
108
109                // エラーがなければバックアップ処理を行う
110                if (SC_Utils_Ex::isBlank($arrErrTmp[1]) && SC_Utils_Ex::isBlank($arrErrTmp[2])) {
111
112                    $arrData = $objFormParam->getHashArray();
113
114                    $work_dir = $this->bkup_dir . $arrData['bkup_name'] . '/';
115                    // バックアップデータの事前削除
116                    SC_Helper_FileManager_Ex::deleteFile($work_dir);
117                    // バックアップファイル作成
118                    $res = $this->lfCreateBkupData($arrData['bkup_name'], $work_dir);
119                    // バックアップデータの事後削除
120                    SC_Helper_FileManager_Ex::deleteFile($work_dir);
121
122                    $arrErrTmp[3] = array();
123                    if ($res !== true) {
124                        $arrErrTmp[3]['bkup_name'] = 'バックアップに失敗しました。(' . $res . ')';
125                    }
126
127                    // DBにデータ更新
128                    if (SC_Utils_Ex::isBlank($arrErrTmp[3])) {
129                        $this->lfUpdBkupData($arrData);
130                    } else {
131                        $arrForm = $arrData;
132                        $arrErr = $arrErrTmp[3];
133                    }
134
135                    $this->tpl_onload = "alert('バックアップ完了しました');";
136                } else {
137                    $arrForm = $objFormParam->getHashArray();
138                    $arrErr = array_merge((array)$arrErrTmp[1],(array)$arrErrTmp[2]);
139                }
140                break;
141
142            // リストア
143            case 'restore_config':
144            case 'restore':
145                // データベースに存在するかどうかチェック
146                $arrErr = $this->lfCheckError($objFormParam->getHashArray(), $this->mode);
147
148                // エラーがなければリストア処理を行う
149                if (SC_Utils_Ex::isBlank($arrErr)) {
150                    $arrData = $objFormParam->getHashArray();
151
152                    $msg = '「' . $arrData['list_name'] . '」のリストアを開始します。';
153                    GC_Utils_Ex::gfPrintLog($msg);
154
155                    $success = $this->lfRestore($arrData['list_name'], $this->bkup_dir, $this->bkup_ext, $this->mode);
156
157                    $msg = '「' . $arrData['list_name'] . '」の';
158                    $msg .= $success ? 'リストアを終了しました。' : 'リストアに失敗しました。';
159
160                    $this->tpl_restore_msg .= $msg . "\n";
161                    GC_Utils_Ex::gfPrintLog($msg);
162                }
163                break;
164
165            // 削除
166            case 'delete':
167
168                // データベースに存在するかどうかチェック
169                $arrErr = $this->lfCheckError($objFormParam->getHashArray(), $this->mode);
170
171                // エラーがなければリストア処理を行う
172                if (SC_Utils_Ex::isBlank($arrErr)) {
173
174                    $arrData = $objFormParam->getHashArray();
175
176                    // DBとファイルを削除
177                    $this->lfDeleteBackUp($arrData, $this->bkup_dir, $this->bkup_ext);
178                }
179
180                break;
181
182                // ダウンロード
183            case 'download' :
184
185                // データベースに存在するかどうかチェック
186                $arrErr = $this->lfCheckError($objFormParam->getHashArray(), $this->mode);
187
188                // エラーがなければダウンロード処理を行う
189                if (SC_Utils_Ex::isBlank($arrErr)) {
190
191                    $arrData = $objFormParam->getHashArray();
192
193                    $filename = $arrData['list_name'] . $this->bkup_ext;
194                    $dl_file = $this->bkup_dir.$arrData['list_name'] . $this->bkup_ext;
195
196                    // ダウンロード開始
197                    Header("Content-disposition: attachment; filename=${filename}");
198                    Header("Content-type: application/octet-stream; name=${filename}");
199                    header('Content-Length: ' .filesize($dl_file));
200                    readfile ($dl_file);
201                    exit();
202                    break;
203                }
204
205            default:
206                break;
207        }
208
209        // 不要になった変数を解放
210        unset($arrErrTmp);
211
212        // バックアップリストを取得する
213        $arrBkupList = $this->lfGetBkupData('ORDER BY create_date DESC');
214        // テンプレートファイルに渡すデータをセット
215        $this->arrErr = isset($arrErr) ? $arrErr : array();
216        $this->arrForm = isset($arrForm) ? $arrForm : array();
217        $this->arrBkupList = $arrBkupList;
218
219    }
220
221    /**
222     * デストラクタ.
223     *
224     * @return void
225     */
226    function destroy()
227    {
228        parent::destroy();
229    }
230
231    /**
232     * パラメーター初期化.
233     *
234     * @param object $objFormParam
235     * @param array  $arrParams  $_POST値
236     * @return void
237     */
238    function initParam(&$objFormParam, &$arrParams)
239    {
240
241        $objFormParam->addParam('バックアップ名', 'bkup_name', STEXT_LEN, 'a', array('EXIST_CHECK', 'MAX_LENGTH_CHECK', 'NO_SPTAB', 'FILE_NAME_CHECK_BY_NOUPLOAD'));
242        $objFormParam->addParam('バックアップメモ', 'bkup_memo', MTEXT_LEN, 'KVa', array('MAX_LENGTH_CHECK'));
243        $objFormParam->addParam('バックアップ名(リスト)', 'list_name', STEXT_LEN, 'a', array('MAX_LENGTH_CHECK', 'NO_SPTAB', 'FILE_NAME_CHECK_BY_NOUPLOAD'));
244        $objFormParam->setParam($arrParams);
245        $objFormParam->convParam();
246
247    }
248
249    /**
250     * データ型以外のエラーチェック.
251     *
252     * @param array  $arrForm
253     * @param string $mode
254     * @return $arrErr
255     */
256    function lfCheckError(&$arrForm, $mode)
257    {
258
259        switch ($mode) {
260            case 'bkup':
261                $name = $arrForm['bkup_name'];
262                break;
263
264            case 'restore_config':
265            case 'restore':
266            case 'download':
267            case 'delete':
268                $name = $arrForm['list_name'];
269                break;
270
271            default:
272                trigger_error('不明な処理', E_USER_ERROR);
273                break;
274        }
275
276        // 重複・存在チェック
277        $ret = $this->lfGetBkupData('', $name);
278        if (count($ret) > 0 && $mode == 'bkup') {
279            $arrErr['bkup_name'] = 'バックアップ名が重複しています。別名を入力してください。';
280        } elseif (count($ret) <= 0 && $mode != 'bkup') {
281            $arrErr['list_name'] = '選択されたデータがみつかりませんでした。既に削除されている可能性があります。';
282        }
283
284        return $arrErr;
285    }
286
287    /**
288     * バックアップファイル作成.
289     *
290     * @param string $bkup_name
291     * @return boolean|int 結果。true:成功 int:失敗 FIXME 本来は int ではなく、エラーメッセージを戻すべき
292     */
293    function lfCreateBkupData($bkup_name, $work_dir)
294    {
295        $objQuery =& SC_Query_Ex::getSingletonInstance();
296        $csv_autoinc = '';
297        $arrData = array();
298
299        $success = mkdir($work_dir, 0777, true);
300        if (!$success) {
301            return __LINE__;
302        }
303
304        // 全テーブル取得
305        $arrTableList = $objQuery->listTables();
306
307        // 各テーブル情報を取得する
308        foreach ($arrTableList as $table) {
309
310            if ($table == 'dtb_bkup' || $table == 'mtb_zip') {
311                continue;
312            }
313
314            // dataをCSV出力
315            $csv_file = $work_dir . $table . '.csv';
316            $fp = fopen($csv_file, 'w');
317            if (!$fp) {
318                return __LINE__;
319            }
320
321            // 全データを取得
322            $sql = 'SELECT * FROM ' . $objQuery->conn->quoteIdentifier($table);
323
324            $this->fpOutput =& $fp;
325            $this->first_line = true;
326            $success = $objQuery->doCallbackAll(array(&$this, 'cbOutputCSV'), $sql);
327            unset($this->fpOutput);
328
329            if ($success === false) {
330                return __LINE__;
331            }
332
333            fclose($fp);
334
335            // タイムアウトを防ぐ
336            SC_Utils_Ex::sfFlush();
337        }
338
339        // 自動採番型の構成を取得する
340        $csv_autoinc = $this->lfGetAutoIncrement();
341
342        $csv_autoinc_file = $work_dir . 'autoinc_data.csv';
343
344        // CSV出力
345
346        // 自動採番をCSV出力
347        $fp = fopen($csv_autoinc_file,'w');
348        if ($fp) {
349            if ($csv_autoinc != '') {
350                $success = fwrite($fp, $csv_autoinc);
351                if (!$success) {
352                    return __LINE__;
353                }
354            }
355            fclose($fp);
356        }
357
358        //圧縮フラグTRUEはgzip圧縮をおこなう
359        $tar = new Archive_Tar($this->bkup_dir . $bkup_name . $this->bkup_ext, TRUE);
360
361        //bkupフォルダに移動する
362        chdir($work_dir);
363
364        //圧縮をおこなう
365        $zip = $tar->create('./');
366
367        return true;
368    }
369
370    /**
371     * CSV作成 テンポラリファイル出力 コールバック関数
372     *
373     * @param mixed $data 出力データ
374     * @return boolean true (true:固定 false:中断)
375     */
376    function cbOutputCSV($data)
377    {
378        $line = '';
379        if ($this->first_line) {
380            // カラム名
381            $line .= SC_Helper_CSV_Ex::sfArrayToCsv(array_keys($data)) . "\n";
382            $this->first_line = false;
383        }
384        $line .= SC_Helper_CSV_Ex::sfArrayToCsv($data);
385        $line .= "\n";
386        SC_Utils_Ex::extendTimeOut();
387        return fwrite($this->fpOutput, $line);
388    }
389
390    /**
391     * シーケンス一覧をCSV出力形式に変換する.
392     *
393     * シーケンス名,シーケンス値 の形式に出力する.
394     *
395     * @return string シーケンス一覧の文字列
396     * @return string $ret
397     */
398    function lfGetAutoIncrement()
399    {
400        $objQuery =& SC_Query_Ex::getSingletonInstance();
401        $arrSequences = $objQuery->listSequences();
402
403        foreach ($arrSequences as $name) {
404            if (in_array($name, $this->arrExcludeSequence, true)) {
405                continue 1;
406            }
407
408            // XXX SC_Query::currVal は、PostgreSQL で nextval と等しい値を戻すケースがある。欠番を生じうるが、さして問題無いと推測している。
409            $seq = $objQuery->currVal($name);
410
411            // TODO CSV 生成の共通処理を使う
412            $ret .= $name . ',';
413            $ret .= is_null($seq) ? '0' : $seq;
414            $ret .= "\r\n";
415        }
416        return $ret;
417    }
418
419    // バックアップテーブルにデータを更新する
420    function lfUpdBkupData($data)
421    {
422        $objQuery =& SC_Query_Ex::getSingletonInstance();
423
424        $arrVal = array();
425        $arrVal['bkup_name'] = $data['bkup_name'];
426        $arrVal['bkup_memo'] = $data['bkup_memo'];
427        $arrVal['create_date'] = 'CURRENT_TIMESTAMP';
428
429        $objQuery->insert('dtb_bkup', $arrVal);
430    }
431
432    /**
433     * バックアップの一覧を取得する
434     */
435    function lfGetBkupData($sql_option = '', $filter_bkup_name = '')
436    {
437        $objQuery =& SC_Query_Ex::getSingletonInstance();
438
439        // テーブルから取得
440        $arrVal = array();
441
442        $sql = 'SELECT bkup_name, bkup_memo, create_date FROM dtb_bkup';
443        if (strlen($filter_bkup_name) >= 1) {
444            $sql .= ' WHERE bkup_name = ?';
445            $arrVal[] = $filter_bkup_name;
446        }
447        if ($sql_option != '') {
448            $sql .= ' ' . $sql_option;
449        }
450
451        $ret = $objQuery->getAll($sql, $arrVal);
452
453        // ファイルのみのものを取得
454        $glob = glob($this->bkup_dir . '*' . $this->bkup_ext);
455        if (is_array($glob)) {
456            foreach ($glob as $path) {
457                $bkup_name = basename($path, $this->bkup_ext);
458                if (strlen($filter_bkup_name) >= 1 && $bkup_name !== $filter_bkup_name) {
459                    continue 1;
460                }
461                unset($row);
462                foreach ($ret as $key => $value) {
463                    if ($ret[$key]['bkup_name'] == $bkup_name) {
464                        $row =& $ret[$key];
465                    }
466                }
467                if (!isset($row)) {
468                    $ret[] = array();
469                    $row =& $ret[array_pop(array_keys($ret))];
470                    $row['bkup_name'] = $bkup_name;
471                    $row['bkup_memo'] = '(記録なし。バックアップファイルのみ。)';
472                    $row['create_date'] = date('Y-m-d H:i:s', filemtime($path));
473                }
474            }
475        }
476
477        return $ret;
478    }
479
480    /**
481     * バックアップファイルをリストアする
482     *
483     * @param string $bkup_name
484     * @param string $bkup_dir
485     * @param string $bkup_ext
486     * @return void
487     */
488    function lfRestore($bkup_name, $bkup_dir, $bkup_ext, $mode)
489    {
490        $objQuery =& SC_Query_Ex::getSingletonInstance();
491
492        $bkup_filepath = $bkup_dir . $bkup_name . $bkup_ext;
493        $work_dir = $bkup_dir . $bkup_name . '/';
494
495        //圧縮フラグTRUEはgzip解凍をおこなう
496        $tar = new Archive_Tar($bkup_filepath, TRUE);
497
498        //指定されたフォルダ内に解凍する
499        $success = $tar->extract($work_dir);
500
501        if (!$success) {
502            $msg = 'バックアップファイルの展開に失敗しました。' . "\n";
503            $msg .= '展開元: ' . $bkup_filepath . "\n";
504            $msg .= '展開先: ' . $work_dir;
505            trigger_error($msg, E_USER_ERROR);
506        }
507
508        // トランザクション開始
509        $objQuery->begin();
510
511        // INSERT実行
512        $success = $this->lfExeInsertSQL($objQuery, $work_dir, $mode);
513
514        // シーケンス生成器を復元する
515        if ($success) $this->restoreSequence($objQuery, $work_dir . 'autoinc_data.csv');
516
517        // リストア成功ならコミット失敗ならロールバック
518        if ($success) {
519            $objQuery->commit();
520            $this->tpl_restore_err = true;
521        } else {
522            $objQuery->rollback();
523            $this->tpl_restore_name = $bkup_name;
524        }
525
526        // FIXME この辺りで、バックアップ時と同等の一時ファイルの削除を実行すべきでは?
527
528        SC_Utils_Ex::extendTimeOut();
529
530        return $success;
531    }
532
533    /**
534     * CSVファイルからインサート実行.
535     *
536     * @param object $objQuery
537     * @param string $dir
538     * @param string $mode
539     * @return void
540     */
541    function lfExeInsertSQL(&$objQuery, $dir, $mode)
542    {
543
544        $tbl_flg = false;
545        $col_flg = false;
546        $ret = true;
547        $pagelayout_flg = false;
548        $arrVal = array();
549        $arrCol = array();
550        $arrAllTableList = $objQuery->listTables();
551
552        $objDir = dir($dir);
553        while (false !== ($file_name = $objDir->read())) {
554            if (!preg_match('/^((dtb|mtb|plg)_(\w+))\.csv$/', $file_name, $matches)) {
555                continue;
556            }
557            $file_path = $dir . $file_name;
558            $table = $matches[1];
559
560            // テーブル存在チェック
561            if (!in_array($table, $arrAllTableList)) {
562                if ($mode === 'restore_config') {
563                    continue;
564                }
565                return false;
566            }
567
568            // csvファイルからデータの取得
569            $fp = fopen($file_path, 'r');
570            if ($fp === false) {
571                trigger_error($file_name . ' のファイルオープンに失敗しました。', E_USER_ERROR);
572            }
573
574            GC_Utils_Ex::gfPrintLog('リストア実行: ' . $table);
575            $objQuery->delete($table);
576
577            $line = 0;
578            $arrColName = array();
579            while (!feof($fp)) {
580                $line++;
581                $arrCsvLine = fgetcsv($fp, 1024 * 1024);
582
583                // 1行目: 列名
584                if ($line === 1) {
585                    $arrColName = $arrCsvLine;
586                    continue;
587                }
588
589                // 空行を無視
590                // false との比較は PHP 5.2.x Windows バグ対応
591                // 参考: http://www.php.net/manual/ja/function.fgetcsv.php#98502
592                if ($arrCsvLine === array(null) || $arrCsvLine === false) {
593                    continue;
594                }
595
596                $arrVal = array_combine($arrColName, $arrCsvLine);
597                $objQuery->insert($table, $arrVal);
598
599                SC_Utils_Ex::extendTimeOut();
600            }
601
602            fclose($fp);
603        }
604
605        return $ret;
606    }
607
608    /**
609     * シーケンス生成器を復元する
610     */
611    function restoreSequence(&$objQuery, $csv)
612    {
613        // csvファイルからデータの取得
614        $arrCsvData = file($csv);
615
616        foreach ($arrCsvData as $line) {
617            list($name, $currval) = explode(',', trim($line));
618
619            if (in_array($name, $this->arrExcludeSequence, true)) {
620                continue 1;
621            }
622
623            // FIXME テーブルと同様に整合チェックを行う。また不整合時はスキップして続行する。
624
625            // XXX +1 ではなく、nextVal を呼ぶべきかも。
626            $objQuery->setVal($name, $currval + 1);
627        }
628    }
629
630    // 選択したバックアップをDBから削除
631    function lfDeleteBackUp(&$arrForm, $bkup_dir, $bkup_ext)
632    {
633
634        $objQuery =& SC_Query_Ex::getSingletonInstance();
635
636        $del_file = $bkup_dir.$arrForm['list_name'] . $bkup_ext;
637        // ファイルの削除
638        if (is_file($del_file)) {
639            $ret = unlink($del_file);
640        }
641
642        $objQuery->delete('dtb_bkup', 'bkup_name = ?', array($arrForm['list_name']));
643
644    }
645
646}
Note: See TracBrowser for help on using the repository browser.