source: branches/version-2_12-dev/data/class/plugin/SC_Plugin_TemplateTransformer.php @ 21514

Revision 21514, 17.9 KB checked in by Seasoft, 12 years ago (diff)

#1625 (typo修正・ソース整形・ソースコメントの改善)

Line 
1<?php
2/*
3 * This file is part of EC-CUBE
4 *
5 * Copyright(c) 2000-2012 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
25/**
26 * テンプレートトランスフォーマークラス
27 *
28 * @package Plugin
29 * @author LOCKON CO.,LTD.
30 * @version $Id: $
31 */
32class SC_Plugin_TemplateTransformer {
33
34    var $objDOM;
35    var $arrDomHash;
36    var $current_plugin;
37    var $arrSmartyTagsOrg;
38    var $arrSmartyTagsSub;
39    var $smarty_tags_idx;
40    var $arrErr;
41    var $arrElementTree;
42
43    const ERR_TARGET_ELEMENT_NOT_FOUND = 1;
44
45
46    /**
47     * コンストラクタ
48     *
49     * @param string $tmpl 変形前テンプレートファイルのフルパス
50     * @return void
51     */
52    function SC_Plugin_TemplateTransformer($tmpl) {
53        $this->objDOM = new DOMDocument();
54        $this->objDOM->strictErrorChecking = false;
55        $this->snip_count = 0;
56        $this->smarty_tags_idx = 0;
57        $this->arrErr = array();
58        $this->arrElementTree = array();
59
60        // ファイルの内容を全て文字列に読み込む
61        $html = file_get_contents(SMARTY_TEMPLATES_REALDIR . $tmpl);
62        $err_msg = null;
63
64        // 対象のパスが存在するかを検証する,
65        if ($html === false) {
66            $err_msg = SMARTY_TEMPLATES_REALDIR . $tmpl. 'は存在しないか、読み取れません';
67        } elseif (!in_array(mb_detect_encoding($html), array('ASCII', 'UTF-8'))) {
68            $err_msg = $tmpl. 'の文字コードがUTF-8ではありません';
69        }
70
71        if (!is_null($err_msg)) {
72            // TODO エラー処理
73        }
74
75        // JavaScript内にSmartyのタグが存在するものを、コメント形式に置換
76        $html = preg_replace_callback(
77            '/<script.+?\/script>/s',
78            array($this, 'captureSmartyTags2Comment'),
79            $html
80        );
81
82        // HTMLタグ内にSmartyのタグが存在するものを、いったんダミーのタグに置換する
83        $html = preg_replace_callback(
84            '/<(?:[^<>]*?(?:(<\!--\{.+?\}-->)|(?R))[^<>]*?)*?>/s',
85            array($this, 'captureSmartyTagsInTag'),
86            $html
87        );
88
89        // 通常のノードに属する部分を、コメント形式に置換
90        $html = preg_replace_callback(
91            '/<\!--{.+?\}-->/s',
92            array($this, 'captureSmartyTags2Comment'),
93            $html
94        );
95
96        $html = '<meta http-equiv="content-type" content="text/html; charset=UTF-8" /><html><body><!--TemplateTransformer start-->'.$html.'<!--TemplateTransformer end--></body></html>';
97        // TODO エラー処理
98        @$this->objDOM->loadHTML($html);
99        $this->arrDomHash = array('name' => array());
100
101        $this->scanChild($this->objDOM);
102
103    }
104
105    /**
106     * これから処理を行おうとするプラグインの名前をセットする
107     *
108     * @param string $plugin_name  プラグイン名
109     * @return void
110     */
111    function setCurrentPlugin($plugin_name) {
112        $this->current_plugin = $plugin_name;
113    }
114
115    /**
116     * DOMの処理の邪魔になるSmartyのタグを代理文字に置換する preg_replace_callback のコールバック関数
117     *
118     * コメント形式への置換
119     *
120     * @param array $arrMatches マッチしたタグの情報
121     * @return string 代わりの文字列
122     */
123    function captureSmartyTags2Comment(array $arrMatches) {
124        $substitute_tag = sprintf('<!--###%08d###-->', $this->smarty_tags_idx);
125        $this->arrSmartyTagsOrg[$this->smarty_tags_idx] = $arrMatches[0];
126        $this->arrSmartyTagsSub[$this->smarty_tags_idx] = $substitute_tag;
127        $this->smarty_tags_idx++;
128        return $substitute_tag;
129    }
130
131    /**
132     * DOMの処理の邪魔になるSmartyのタグを代理文字に置換する preg_replace_callback のコールバック関数
133     *
134     * HTMLエレメント内部の処理
135     *
136     * @param array $arrMatches マッチしたタグの情報
137     * @return string 代わりの文字列
138     */
139    function captureSmartyTagsInTag(array $arrMatches) {
140        // Smartyタグ内のクォートを処理しやすいよう、いったんダミーのタグに
141        $html = preg_replace_callback('/<\!--{.+?\}-->/s', array($this, 'captureSmartyTags2Temptag'), $arrMatches[0]);
142        $html = preg_replace_callback('/\"[^"]*?\"/s', array($this, 'captureSmartyTagsInQuote'), $html);
143        $html = preg_replace_callback('/###TEMP(\d{8})###/s', array($this, 'captureSmartyTags2Attr'), $html);
144        return $html;
145    }
146
147    /**
148     * DOMの処理の邪魔になるSmartyのタグを代理文字に置換する preg_replace_callback のコールバック関数
149     *
150     * ダミーへの置換実行
151     *
152     * @param array $arrMatches マッチしたタグの情報
153     * @return string 代わりの文字列
154     */
155    function captureSmartyTags2Temptag(array $arrMatches) {
156        $substitute_tag = sprintf('###TEMP%08d###', $this->smarty_tags_idx);
157        $this->arrSmartyTagsOrg[$this->smarty_tags_idx] = $arrMatches[0];
158        $this->arrSmartyTagsSub[$this->smarty_tags_idx] = $substitute_tag;
159        $this->smarty_tags_idx++;
160        return $substitute_tag;
161    }
162
163    /**
164     * DOMの処理の邪魔になるSmartyのタグを代理文字に置換する preg_replace_callback のコールバック関数
165     *
166     * クォート内(=属性値)内にあるSmartyタグ(ダミーに置換済み)を、テキストに置換
167     *
168     * @param array $arrMatches マッチしたタグの情報
169     * @return string 代わりの文字列
170     */
171    function captureSmartyTagsInQuote(array $arrMatches) {
172        $html = preg_replace_callback('/###TEMP(\d{8})###/s', array($this, 'captureSmartyTags2Value'), $arrMatches[0]);
173        return $html;
174    }
175
176    /**
177     * DOMの処理の邪魔になるSmartyのタグを代理文字に置換する preg_replace_callback のコールバック関数
178     *
179     * テキストへの置換実行
180     *
181     * @param array $arrMatches マッチしたタグの情報
182     * @return string 代わりの文字列
183     */
184    function captureSmartyTags2Value(array $arrMatches) {
185        $tag_idx = (int)$arrMatches[1];
186        $substitute_tag = sprintf('###%08d###', $tag_idx);
187        $this->arrSmartyTagsSub[$tag_idx] = $substitute_tag;
188        return $substitute_tag;
189    }
190
191    /**
192     * DOMの処理の邪魔になるSmartyのタグを代理文字に置換する preg_replace_callback のコールバック関数
193     *
194     * エレメント内部にあって、属性値ではないものを、ダミーの属性として置換
195     *
196     * @param array $arrMatches マッチしたタグの情報
197     * @return string 代わりの文字列
198     */
199    function captureSmartyTags2Attr(array $arrMatches) {
200        $tag_idx = (int)$arrMatches[1];
201        $substitute_tag = sprintf('rel%08d="######"', $tag_idx);
202        $this->arrSmartyTagsSub[$tag_idx] = $substitute_tag;
203        return ' '.$substitute_tag.' '; // 属性はパース時にスペースが詰まるので、こちらにはスペースを入れておく
204    }
205
206    /**
207     * DOM Element / Document を走査し、name、class別に分類する
208     *
209     * @param  DOMNode $objDOMElement DOMNodeオブジェクト
210     * @param string $parent_selector 親セレクタ
211     * @return void
212     */
213    function scanChild(DOMNode $objDOMElement, $parent_selector = '') {
214        $objNodeList = $objDOMElement->childNodes;
215
216        if (is_null($objNodeList)) return;
217        foreach ($objNodeList as $element) {
218
219            $arrAttr = array();
220            // エレメントの場合、tag名を配列に入れる.
221            if ($element instanceof DOMElement) {
222                $arrAttr[] = $element->tagName;
223            }
224
225            // getAttributeメソッドを持つかを検証
226            if (method_exists($element, 'getAttribute')) {
227                // id属性を持つ場合.
228                if ($element->hasAttribute('id')) {
229                    // idの値を配列に格納(ex: [0] => #hoge)
230                    $arrAttr[] = '#'.$element->getAttribute('id');
231                }
232                // class属性を持つ場合.
233                if ($element->hasAttribute('class')) {
234                    // class名毎に配列に格納(ex: [0] => .hoge [1] => .huga)
235                    $arrClasses = preg_split('/\s+/', $element->getAttribute('class'));
236                    foreach ($arrClasses as $classname) $arrAttr[] = '.'.$classname;
237                }
238                // name属性を持つ場合.
239                if ($element->hasAttribute('name')) {
240                    $this->arrDomHash['name'][$element->getAttribute('name')][] = $element;
241                }
242            }
243            // tag名と属性値を結合してセレクター名とする (ex: body #hoge.huga)
244            $this_selector = $parent_selector.' '.implode('', $arrAttr);
245            // セレクター名をキーにエレメントを格納.
246            $this->arrElementTree[] = array($this_selector, $element);
247            // エレメントがDOMNode場合は更に子要素を取り出す.
248            if ($element instanceof DOMNode) {
249                $this->scanChild($element, $this_selector);
250            }
251        }
252    }
253
254    /**
255     * jQueryライクなセレクタを用いてエレメントを検索する
256     *
257     * @param string  $selector      セレクタ
258     * @param integer $index         インデックス(指定がある場合)
259     * @param boolean $require       エレメントが見つからなかった場合、エラーとするか
260     * @param string  $err_msg       エラーメッセージ
261     * @param SC_Plugin_TemplateSelector $objSelector セレクタオブジェクト
262     * @param string  $parent_index セレクタ検索時の親要素の位置(子孫要素検索のため)
263     * @return SC_Plugin_TemplateSelector
264     */
265    function find($selector, $index = NULL, $require = true, $err_msg = NULL, SC_Plugin_TemplateSelector $objSelector = NULL, $parent_index = NULL) {
266
267        if (is_null($objSelector)) $objSelector = new SC_Plugin_TemplateSelector($this, $this->current_plugin);
268
269        // jQueryライクなセレクタを正規表現に
270        $selector = preg_replace('/ *> */', ' >', $selector);
271
272        $regex = '/';
273        if (!is_null($parent_index)) $regex .= preg_quote($this->arrElementTree[$parent_index][0], '/');
274        // セレクターを配列にします.
275        $arrSelectors = explode(' ', $selector);       
276
277        // セレクタから正規表現を生成.
278        foreach ($arrSelectors as $sub_selector) {
279            if (preg_match('/^(>?)([\w\-]+)?(#[\w\-]+)?(\.[\w\-]+)*$/', $sub_selector, $arrMatch)) {
280                if (isset($arrMatch[1]) && $arrMatch[1]) $regex .= ' ';
281                else $regex .= '.* ';
282                if (isset($arrMatch[2]) && $arrMatch[2]) $regex .= preg_quote($arrMatch[2], '/');
283                else $regex .= '([\w\-]+)?';
284                if (isset($arrMatch[3]) && $arrMatch[3]) $regex .= preg_quote($arrMatch[3], '/');
285                else $regex .= '(#(\w|\-|#{3}[0-9]{8}#{3})+)?';
286                if (isset($arrMatch[4]) && $arrMatch[4]) $regex .= '(\.(\w|\-|#{3}[0-9]{8}#{3})+)*'.preg_quote($arrMatch[4], '/').'(\.(\w|\-|#{3}[0-9]{8}#{3})+)*'; // class指定の時は前後にもclassが付いているかもしれない
287                else $regex .= '(\.(\w|\-|#{3}[0-9]{8}#{3})+)*';
288            }
289        }
290        $regex .= '$/i';
291
292        $cur_idx = 0;
293        // 絞込み検索のときは、前回見つけた位置から検索を開始する
294        $startIndex = is_null($parent_index) ? 0 : $parent_index;
295
296        // エレメントツリーのセレクタを先ほど作成した正規表現で順に検索.
297        for ($iLoop=$startIndex; $iLoop < count($this->arrElementTree); $iLoop++) {
298
299            if (preg_match($regex, $this->arrElementTree[$iLoop][0])) {
300                if (is_null($index) || $cur_idx == $index) {
301                    // 検索にかかったエレメントをセレクターのメンバ変数の配列に入れる
302                    $objSelector->addElement($iLoop, $this->arrElementTree[$iLoop]);
303                }
304                $cur_idx++;
305            }
306        }
307
308        if ($require && $cur_idx == 0) {
309            $this->setError(
310                $this->current_plugin,
311                $selector,
312                SC_Plugin_TemplateTransformList::ERR_TARGET_ELEMENT_NOT_FOUND,
313                $err_msg
314            );
315        }
316
317        return $objSelector;
318    }
319
320    /**
321     * DOMを用いた変形を実行する
322     *
323     * @param string $mode       実行するメソッドの種類
324     * @param string $target_key 変形対象のエレメントの完全なセレクタ
325     * @param string $html_snip  HTMLコード
326     * @return boolean
327     */
328    function setTransform($mode, $target_key, $html_snip) {
329
330        $substitute_tag = sprintf('<!--###%08d###-->', $this->smarty_tags_idx);
331
332        $this->arrSmartyTagsOrg[$this->smarty_tags_idx] = $html_snip;
333        $this->arrSmartyTagsSub[$this->smarty_tags_idx] = $substitute_tag;
334        $this->smarty_tags_idx++;
335
336        $objSnip = $this->objDOM->createDocumentFragment();
337        $objSnip->appendXML($substitute_tag);
338
339        $objElement = false;
340        if (isset($this->arrElementTree[$target_key]) && $this->arrElementTree[$target_key][0]) {
341            $objElement = &$this->arrElementTree[$target_key][1];
342        }
343
344        if (!$objElement) return false;
345
346        try {
347            if ($mode == 'appendChild') {
348                $objElement->appendChild($objSnip);
349            } elseif ($mode == 'insertBefore') {
350                if (!is_object($objElement->parentNode)) return false;
351                $objElement->parentNode->insertBefore($objSnip, $objElement);
352            } elseif ($mode == 'insertAfter') {
353                if ($objElement->nextSibling) {
354                     $objElement->parentNode->insertBefore($objSnip, $objElement->nextSibling);
355                } else {
356                     $objElement->parentNode->appendChild($objSnip);
357                }
358            } elseif ($mode == 'replaceChild') {
359                if (!is_object($objElement->parentNode)) return false;
360                $objElement->parentNode->replaceChild($objSnip, $objElement);
361            }
362            $this->snip_count++;
363        } catch (Exception $e) {
364            // TODO エラー処理
365        }
366        return true;
367    }
368
369    /**
370     * セレクタエラーを記録する
371     *
372     * @param string  $plugin_name プラグイン名
373     * @param string  $selector    セレクタ
374     * @param integer $type        エラーの種類
375     * @param string  $err_msg     エラーメッセージ
376     * @return void
377     */
378    function setError($plugin_name, $selector, $type, $err_msg = NULL) {
379        $this->arrErr[] = array(
380            'plugin_name' => $plugin_name,
381            'selector'    => $selector,
382            'type'        => $type,
383            'err_msg'     => $err_msg
384        );
385    }
386
387    /**
388     * HTMLに戻して、Transform用に付けたマーカーを削除し、Smartyのタグを復元する
389     *
390     * @return mixed トランスフォーム済みHTML。まったくトランスフォームが行われなかった場合はfalse。
391     */
392    function getHTML() {
393        if (count($this->arrErr)) {
394            // エラーメッセージ組み立て
395            $err_msg = '';
396            foreach ($this->arrErr as $arrErr) {
397                if ($arrErr['err_msg']) {
398                    $err_msg .= '<br />'.$arrErr['err_msg'];
399                } else {
400                    if ($arrErr['type'] == SC_Plugin_TemplateTransformList::ERR_TARGET_ELEMENT_NOT_FOUND) {
401                        $err_msg .= "<br />${arrErr['selector']} が存在しません";
402                    } else {
403                        $err_msg .= '<br />'.print_r($arrErr, true);
404                    }
405                }
406            }
407            // TODO エラー処理
408            // ECC_Plugin_Engine::dispError(FREE_ERROR_MSG, 'テンプレートの操作に失敗しました。'.$err_msg);
409
410        } elseif ($this->snip_count) {
411            $html = $this->objDOM->saveHTML();
412            // 置換 $htmlの$this->arrSmartyTagsSubを$this->arrSmartyTagsOrgに置換
413            $html = str_replace($this->arrSmartyTagsSub, $this->arrSmartyTagsOrg, $html);
414            $html = preg_replace('/^.*<\!--TemplateTransformer start-->/s', '', $html);
415            $html = preg_replace('/<\!--TemplateTransformer end-->.*$/s', '', $html);
416            return $html;
417
418        } else {
419            return false;
420        }
421    }
422
423    /**
424     * 変形済みファイルを書き出す
425     *
426     * @param string $filename ファイル名.
427     * @param boolean $test_mode
428     * @return mixed  書き出しに成功した場合は,書き出したファイルのパス. 失敗した場合は false.
429     */
430    function saveHTMLFile($filename, $test_mode = false) {
431        $html = $this->getHTML();
432        if ($html && $test_mode == false) {
433            // 成功し、かつ test_mode でなければファイルに書き出す
434            $filepath = PLUGIN_TMPL_CACHE_REALDIR . $filename;
435            $dir = dirname($filepath);
436
437            if (!file_exists($dir)) mkdir($dir, PLUGIN_DIR_PERMISSION, true);
438            if (!file_put_contents($filepath, $html)) return false;
439            return $filepath;
440        } else {
441            return false;
442        }
443    }
444
445}
Note: See TracBrowser for help on using the repository browser.