奇特なブログ

「殊勝に値する行いや心掛け」を意味する、奇特な人になる為のブログです

サーバー側で全部HTMLエスケープする(CakePHP3の場合)

昔、Phalconで実装したことがありましたが、
Cakeだと、どうやれば良いのかと。
デフォルトで対応してくれれば良いのにと、昔から思ってるんですけどね。


あと、以下の様にプラグインを書いてる方もいらっしゃる様ですが、
プラグインなので、導入できない場合や、あと細かい調整がなぁと。
参考にした方が良さそうな所は、ありましたけどね。


[Cakephp3] もう毎回 h() なんて書きたくない!! - Qiita


なので、以下を書いてみました。
個々のシステムによって、細かい調整が必要かと思いますけどね。
あと、PHPは7.1以上が必要ですが、
Cake3のマイナーバージョンまでは、良く分かりません。

<?php

// 今回の処理と関係無い所は省略しています
class AppController extends Controller
{
    // エスケープしない項目リスト
    // 必要に応じて、子クラスで値を入れておく
    private $notEscapeNameList = [];

    protected function addNotEscapeNameList($name, $value)
    {
        $this->notEscapeNameList[$name] = $value;
    }

    public function beforeRender(Event $event)
    {
        // レンダリングする直前なので、以下のエスケープ処理終了後に書いた方が良いか?
        parent::beforeRender($event);
        // viewVarsに、子クラスで$this->setされた内容が入っている
        foreach ($this->viewVars as $name => $value)
        {
            // エスケープしない項目リストに含まれている場合は、何もしない
            if (false === array_key_exists($name, $this->notEscapeNameList))
            {
                $this->viewVars[$name] = $this->htmlEscape($value);
            }
        }
    }

    protected function htmlEscape($value)
    {
        if (true === is_null($value) ||
            true === is_bool($value) ||
            true === is_callable($value) ||
            true === is_double($value) ||
            true === is_float($value) ||
            true === is_int($value))
        {
            // エスケープ不要な型(のはず)
            return $value;
        }
        else if (true === is_iterable($value))
        {
            // 配列以外に、ResultSetの場合もここに入るので、
            // テンプレート側では、ResultSetでのデータ操作は行えない(けど問題ある?)
            $ret = [];
            foreach ($value as $key => $val)
            {
                $ret[$key] = $this->htmlEscape($val);
            }
            return $ret;
        }
        else if ($value instanceof Entity)
        {
            $array = $value->toArray();
            foreach ($array as $key => $val)
            {
                $value->$key = $this->htmlEscape($val);
            }
            // Entityは一応、配列にしないで型は維持しておく
            return $value;
        }
        else if (true === is_resource($value))
        {
            // Cakeの場合、MySQLのbinary・varbinary・blob型は、Entity内ではリソース型になる模様
            // これは、どうにもならないか?
            return $value;
        }
        else if ($value instanceof \Cake\I18n\FrozenTime)
        {
            // そもそもエスケープしなくても良い気もするが
            // コメントアウトした以下と、どっちが良いか?
            //return $this->htmlEscape($value->format('Y-m-d H:i:s'));
            return new \Cake\I18n\FrozenTime($this->htmlEscape($value->format('Y-m-d H:i:s')));
        }
        else if ($value instanceof \Cake\I18n\FrozenDate)
        {
            // そもそもエスケープしなくても良い気もするが
            // コメントアウトした以下と、どっちが良いか?
            //return $this->htmlEscape($value->format('Y-m-d'));
            return new \Cake\I18n\FrozenDate($this->htmlEscape($value->format('Y-m-d')));
        }
        // オブジェクトは、汎用的過ぎて上手い方法が無い?のでコメントアウト
      	/*
      	else if (true === is_object($value))
      	{
      		$obj = new \stdClass();
        	$properties = get_object_vars($value);
		//var_dump($properties);
        	foreach ($properties as $n => $v)
        	{
          	    $obj->$n = $this->htmlEscape($v);
        	}
        	return $obj;
      	}
      	*/
        else
        {
            return h($value);
        }
    }
}