<?php
/**
 * Pickles 2
 */
namespace picklesFramework2;

/**
 * Pickles 2 core class
 *
 * @author Tomoya Koyanagi <tomk79@gmail.com>
 */
class px{
	/**
	 * Pickles 2 のホームディレクトリのパス
	 * @access private
	 */
	private $realpath_homedir;

	/**
	 * コンテンツディレクトリのパス
	 * @access private
	 */
	private $path_content;

	/**
	 * オブジェクト
	 * @access private
	 */
	private $conf, $fs, $req, $site, $bowl, $pxcmd;

	/**
	 * ディレクトリインデックス(省略できるファイル名の一覧)
	 * @access private
	 */
	private $directory_index = array();

	/**
	 * プロセス関連情報
	 * @access private
	 */
	private $proc_type, $proc_id, $proc_num = 0;

	/**
	 * HTTP Response Header List
	 */
	private $response_headers = array(
		"Content-type" => array("text/html")
	);

	/**
	 * 関連ファイルのURL情報
	 * @access private
	 */
	private $relatedlinks = array();

	/**
	 * エラーメッセージ情報
	 */
	private $errors = array();

	/**
	 * 応答ステータスコード
	 * @access private
	 */
	private $response_status = 200;

	/**
	 * 応答ステータスメッセージ
	 * @access private
	 */
	private $response_message = 'OK';

	/**
	 * Pickles Framework のバージョン番号を取得する。
	 *
	 * Pickles Framework のバージョン番号はこのメソッドにハードコーディングされます。
	 *
	 * バージョン番号発行の規則は、 Semantic Versioning 2.0.0 仕様に従います。
	 * - [Semantic Versioning(英語原文)](http://semver.org/)
	 * - [セマンティック バージョニング(日本語)](http://semver.org/lang/ja/)
	 *
	 * *[ナイトリービルド]*

	 * バージョン番号が振られていない、開発途中のリビジョンを、ナイトリービルドと呼びます。

	 * ナイトリービルドの場合、バージョン番号は、次のリリースが予定されているバージョン番号に、
	 * ビルドメタデータ `+nb` を付加します。
	 * 通常は、プレリリース記号 `alpha` または `beta` を伴うようにします。
	 * - 例：1.0.0-beta.12+nb (=1.0.0-beta.12リリース前のナイトリービルド)
	 *
	 * @return string バージョン番号を示す文字列
	 */
	public function get_version(){
		return '2.0.40';
	}

	/**
	 * Constructor
	 *
	 * @param string $path_homedir Pickles のホームディレクトリのパス
	 */
	public function __construct( $path_homedir ){
		// initialize PHP
		if( !extension_loaded( 'mbstring' ) ){
			trigger_error('mbstring not loaded.');
		}
		if( is_callable('mb_internal_encoding') ){
			mb_internal_encoding('UTF-8');
			@ini_set( 'mbstring.internal_encoding' , 'UTF-8' );
			@ini_set( 'mbstring.http_input' , 'UTF-8' );
			@ini_set( 'mbstring.http_output' , 'UTF-8' );
		}
		@ini_set( 'default_charset' , 'UTF-8' );
		if( is_callable('mb_detect_order') ){
			@ini_set( 'mbstring.detect_order' , 'UTF-8,SJIS-win,eucJP-win,SJIS,EUC-JP,JIS,ASCII' );
			mb_detect_order( 'UTF-8,SJIS-win,eucJP-win,SJIS,EUC-JP,JIS,ASCII' );
		}
		@header_remove('X-Powered-By');
		$this->set_status(200);// 200 OK

		if( realpath('/') !== '/' ) {
			// Windows + PHP7 で CSV を正しく読み込めない問題への対策。
			// Windows では、 ja_JP.UTF-8 ロケールが正しく受け付けられない問題がある。
			// 代わりに、Cロケールを設定することで回避できる。
			setlocale(LC_CTYPE, 'C');
		}

		if( !array_key_exists( 'REMOTE_ADDR' , $_SERVER ) ){
			// commandline only
			if( realpath($_SERVER['SCRIPT_FILENAME']) === false ||
				dirname(realpath($_SERVER['SCRIPT_FILENAME'])) !== realpath('./')
			){
				if( array_key_exists( 'PWD' , $_SERVER ) && is_file($_SERVER['PWD'].'/'.$_SERVER['SCRIPT_FILENAME']) ){
					$_SERVER['SCRIPT_FILENAME'] = realpath($_SERVER['PWD'].'/'.$_SERVER['SCRIPT_FILENAME']);
				}else{
					// for Windows
					// .px_execute.php で chdir(__DIR__) されていることが前提。
					$_SERVER['SCRIPT_FILENAME'] = realpath('./'.basename($_SERVER['SCRIPT_FILENAME']));
				}
			}
		}
		if( !@array_key_exists( 'HTTP_USER_AGENT' , $_SERVER ) ){
			// HTTP_USER_AGENT が存在しない場合、空白文字列で初期化する。
			$_SERVER['HTTP_USER_AGENT'] = '';
		}

		// load Config
		$this->realpath_homedir = $path_homedir;
		if( is_file($this->realpath_homedir.DIRECTORY_SEPARATOR.'config.json') ){
			$this->conf = json_decode( file_get_contents( $this->realpath_homedir.DIRECTORY_SEPARATOR.'config.json' ) );
		}elseif( is_file($this->realpath_homedir.DIRECTORY_SEPARATOR.'config.php') ){
			$this->conf = include( $this->realpath_homedir.DIRECTORY_SEPARATOR.'config.php' );
		}
		if( !@is_array($this->conf->paths_enable_sitemap) ){
			// sitemap のロードを有効にするべきパス。
			$this->conf->paths_enable_sitemap = array(
				'*.html',
				'*.htm',
			);
		}

		if ( @strlen($this->conf->default_timezone) ) {
			@date_default_timezone_set($this->conf->default_timezone);
		}
		if ( !@is_callable($this->conf->path_files) && !@strlen($this->conf->path_files) ) {
			$this->conf->path_files = '{$dirname}/{$filename}_files/';
		}
		if ( !@strlen($this->conf->copyright) ) {
			// NOTE: 2016-03-15 $conf->copyright 追加
			// 古い環境で Notice レベルのエラーが出る可能性があることを考慮し、初期化する。
			$this->conf->copyright = null;
		}

		// make instance $bowl
		$this->bowl = new bowl();

		// Apply command-line option "--command-php"
		$req = new \tomk79\request();
		if( strlen(@$req->get_cli_option( '--command-php' )) ){
			@$this->conf->commands->php = $req->get_cli_option( '--command-php' );
		}
		if( strlen(@$req->get_cli_option( '-c' )) ){
			@$this->conf->path_phpini = $req->get_cli_option( '-c' );
		}

		// make instance $fs
		$conf = new \stdClass;
		$conf->file_default_permission = $this->conf->file_default_permission;
		$conf->dir_default_permission  = $this->conf->dir_default_permission;
		$conf->filesystem_encoding	 = $this->conf->filesystem_encoding;
		$this->fs = new \tomk79\filesystem( $conf );
		$this->realpath_homedir = $this->fs()->get_realpath($this->realpath_homedir.'/');

		// make instance $req
		$conf = new \stdClass;
		$conf->session_name   = $this->conf->session_name;
		$conf->session_expire = $this->conf->session_expire;
		$conf->directory_index_primary = $this->get_directory_index_primary();
		$this->req = new \tomk79\request( $conf );
		if( strlen(@$this->req->get_cli_option( '-u' )) ){
			$_SERVER['HTTP_USER_AGENT'] = @$this->req->get_cli_option( '-u' );
		}

		// 公開キャッシュフォルダが存在しない場合、作成する
		if( strlen(@$this->conf->public_cache_dir) && !@is_dir( './'.$this->conf->public_cache_dir ) ){
			$this->fs()->mkdir( './'.$this->conf->public_cache_dir );
		}
		// _sysフォルダが存在しない場合、作成する
		foreach( array(
			'/_sys/',
			'/_sys/ram/',
			'/_sys/ram/applock/',
			'/_sys/ram/caches/',
			'/_sys/ram/data/',
			'/_sys/ram/publish/'
		) as $tmp_path_sys_dir ){
			if( strlen($this->realpath_homedir.$tmp_path_sys_dir) && !@is_dir( $this->realpath_homedir.$tmp_path_sys_dir ) ){
				$this->fs()->mkdir( $this->realpath_homedir.$tmp_path_sys_dir );
			}
		}

		// 環境変数 `$_SERVER['DOCUMENT_ROOT']` をセット
		// `$px->get_realpath_docroot()` は、`$conf`, `$fs` を参照するので、
		// これらの初期化の後が望ましい。
		if( !array_key_exists( 'DOCUMENT_ROOT' , $_SERVER ) || !strlen( @$_SERVER['DOCUMENT_ROOT'] ) ){
			// commandline only
			$_SERVER['DOCUMENT_ROOT'] = $this->get_realpath_docroot();
			$_SERVER['DOCUMENT_ROOT'] = realpath( $_SERVER['DOCUMENT_ROOT'] );
		}

		// デフォルトの Content-type を出力
		@$this->header('Content-type: '.$this->get_default_mime_type());

		// ignore されたコンテンツをレスポンスする
		$fnc_response_ignore = function($px){
			$px->set_status(403);// 403 Forbidden
			print 'ignored path';
			exit;
		};
		// pass 判定されたコンテンツをレスポンスする
		$fnc_response_pass = function($px){
			if( !$px->fs()->is_file( './'.$px->req()->get_request_file_path() ) ){
				@$px->header('Content-type: text/html');
				$px->set_status(404);// 404 NotFound
				$px->bowl()->send('
404 - File not found.
');
				return true;
			}
			$src = $px->fs()->read_file( './'.$px->req()->get_request_file_path() );
			$px->bowl()->send($src);
			return true;
		};

		$pxcmd = $this->get_px_command();
		if( is_null($pxcmd) || !count($pxcmd) ){
			// PXコマンドが無効かつ ignore か pass の場合、
			// この時点でレスポンスを返す。
			if( $this->is_ignore_path( $this->req()->get_request_file_path() ) ){
				// ignore のレスポンス
				$fnc_response_ignore($this);
				return;
			}
			if( $this->get_path_proc_type( $this->req()->get_request_file_path() ) === 'pass' ){
				// pass のレスポンス
				$fnc_response_pass($this);
				return;
			}
		}

		// make instance $pxcmd
		$this->pxcmd = new pxcmd($this);

		// funcs: Before sitemap
		$this->fnc_call_plugin_funcs( @$this->conf->funcs->before_sitemap, $this );

		// make instance $site
		$is_enable_sitemap = $this->is_path_enable_sitemap( $this->req()->get_request_file_path() );
		if( is_object( $this->site ) ){
			// 既にセットされている場合はそのまま利用する
		}elseif( $is_enable_sitemap ){
			$this->site = new site($this);
		}else{
			$this->site = false;
		}

		// execute Content
		$this->path_content = $this->req()->get_request_file_path();
		$ext = $this->get_path_proc_type( $this->req()->get_request_file_path() );
		if($ext !== 'direct' && $ext !== 'pass'){
			if( $is_enable_sitemap ){
				$current_page_info = $this->site()->get_page_info( $this->req()->get_request_file_path() );
				$this->path_content = null;
				if( is_array($current_page_info) && array_key_exists('content', $current_page_info) ){
					$this->path_content = $current_page_info['content'];
				}
				if( is_null( $this->path_content ) ){
					$this->path_content = $this->req()->get_request_file_path();
				}
				unset($current_page_info);
			}

		}

		foreach( array_keys( get_object_vars( $this->conf->funcs->processor ) ) as $tmp_ext ){
			if( $this->fs()->is_file( './'.$this->path_content.'.'.$tmp_ext ) ){
				$ext = $tmp_ext;
				$this->path_content = $this->path_content.'.'.$tmp_ext;
				break;
			}
		}

		$this->proc_type = $ext;
		unset($ext);

		// funcs: Before contents
		$this->fnc_call_plugin_funcs( @$this->conf->funcs->before_content, $this );

		// PXコマンドが有効、かつ ignore か pass の場合、
		// この時点でレスポンスを返す。
		// ここまでの before sitemap, before contents でレスポンスされた場合は、ここは通らない。
		if( $this->is_ignore_path( $this->req()->get_request_file_path() ) ){
			// ignore のレスポンス
			$fnc_response_ignore($this);
			return;
		}
		if( $this->get_path_proc_type( $this->req()->get_request_file_path() ) === 'pass' ){
			// pass のレスポンス
			$fnc_response_pass($this);
			return;
		}

		// execute content
		self::exec_content( $this );

		// funcs: process functions
		$this->fnc_call_plugin_funcs( @$this->conf->funcs->processor->{$this->proc_type}, $this );

		// funcs: Before output
		$this->fnc_call_plugin_funcs( @$this->conf->funcs->before_output, $this );

	}

	/**
	 * Destructor
	 * @return null
	 */
	public function __destruct(){
		if(is_callable(array($this->site(),'__destruct'))){
			$this->site()->__destruct();
		}
	}

	/**
	 * call plugin functions
	 *
	 * @param mixed $func_list List of plugins function
	 * @return bool 成功時 `true`、失敗時 `false`
	 */
	private function fnc_call_plugin_funcs( $func_list ){
		if( is_null($func_list) ){ return false; }
		$param_arr = func_get_args();
		array_shift($param_arr);

		if( @!empty( $func_list ) ){
			// functions
			if( is_object($func_list) ){
				$func_list = get_object_vars( $func_list );
			}
			if( is_string($func_list) ){
				$fnc_name = $func_list;
				$fnc_name = preg_replace( '/^\\\\*/', '\\', $fnc_name );
				$option_value = null;
				preg_match( '/^(.*?)(?:\\((.*)\\))?$/s', $fnc_name, $matched );
				if(array_key_exists( 1, $matched )){
					$fnc_name = @$matched[1];
				}
				if(array_key_exists( 2, $matched )){
					$option_value = @$matched[2];
				}
				unset($matched);
				if( strlen( trim($option_value) ) ){
					$option_value = json_decode( $option_value );
				}else{
					$option_value = null;
				}
				// var_dump($fnc_name);
				// var_dump($option_value);
				$this->proc_num ++;
				if( @!strlen($this->proc_id) ){
					$this->proc_id = $this->proc_type.'_'.$this->proc_num;
				}
				array_push( $param_arr, $option_value );
				call_user_func_array( $fnc_name, $param_arr);
				$this->proc_id = null;

			}elseif( is_array($func_list) ){
				foreach( $func_list as $fnc_id=>$fnc_name ){
					if( is_string($fnc_name) ){
						$fnc_name = preg_replace( '/^\\\\*/', '\\', $fnc_name );
					}
					if( is_string($fnc_id) && !preg_match('/^[0-9]+$/', $fnc_id) ){
						$this->proc_id = $fnc_id;
					}
					$param_arr_dd = $param_arr;
					array_unshift( $param_arr_dd, $fnc_name );
					call_user_func_array( array( $this, 'fnc_call_plugin_funcs' ), $param_arr_dd);
				}
			}
			unset($fnc_name);
		}
		return true;
	}

	/**
	 * `$fs` オブジェクトを取得する。
	 *
	 * `$fs`(class [tomk79\filesystem](tomk79.filesystem.html))のインスタンスを返します。
	 *
	 * @see https://github.com/tomk79/filesystem
	 * @return object $fs オブジェクト
	 */
	public function fs(){
		return $this->fs;
	}

	/**
	 * `$req` オブジェクトを取得する。
	 *
	 * `$req`(class [tomk79\request](tomk79.request.html))のインスタンスを返します。
	 *
	 * @see https://github.com/tomk79/request
	 * @return object $req オブジェクト
	 */
	public function req(){
		return $this->req;
	}

	/**
	 * `$site` オブジェクトを取得する。
	 *
	 * `$site`(class [picklesFramework2\site](picklesFramework2.site.html))のインスタンスを返します。
	 *
	 * `$site` は、主にサイトマップCSVから読み込んだページの一覧情報を管理するオブジェクトです。
	 *
	 * `$conf->paths_enable_sitemap` の設定によって、 `$site` がロードされない場合があります。
	 * ロードされない場合は、 `false` が返されます。
	 *
	 * また、サイトマップをロードする前段階(`before_sitemap`) では、 `null` を返します。
	 *
	 * @return object|bool `$site` オブジェクト。ロード前は `null`、ロードされない場合は `false`。
	 */
	public function site(){
		return $this->site;
	}

	/**
	 * `$site` オブジェクトをセットする
	 *
	 * 外部から `$site`(class [picklesFramework2\site](picklesFramework2.site.html))のインスタンスを受け取ります。
	 *
	 * このメソッドを通じて、プラグインから `$site` の振る舞いを変更することができます。
	 *
	 * @param object $site `$site` オブジェクト
	 * @return bool 成功時 `true`、失敗時 `false` を返します。
	 */
	public function set_site( $site ){
		if( !is_object($site) ){
			return false;
		}
		$this->site = $site;
		return true;
	}

	/**
	 * `$pxcmd` オブジェクトを取得する。
	 *
	 * @return object $pxcmd オブジェクト
	 */
	public function pxcmd(){
		return $this->pxcmd;
	}

	/**
	 * `$bowl` オブジェクトを取得する。
	 *
	 * `$bowl`(class [picklesFramework2\bowl](picklesFramework2.bowl.html))のインスタンスを返します。
	 *
	 * @return object $bowl オブジェクト
	 */
	public function bowl(){
		return $this->bowl;
	}

	/**
	 * `$conf` オブジェクトを取得する。
	 * @return object $conf オブジェクト
	 */
	public function conf(){
		return $this->conf;
	}

	/**
	 * ホームディレクトリの絶対パスを取得する (deprecated)
	 * このメソッドの使用は推奨されません。
	 * 代わりに `$px->get_realpath_homedir()` を使用してください。
	 * @return string ホームディレクトリの絶対パス
	 */
	public function get_path_homedir(){
		return $this->get_realpath_homedir();
	}

	/**
	 * ホームディレクトリの絶対パスを取得する。
	 *
	 * ホームディレクトリは、 Pickles 2 のフレームワークが使用するファイルが置かれるディレクトリで、
	 * 主には `config.php`, `sitemaps/*`, `_sys/ram/*` などが格納されているディレクトリのことです。
	 *
	 * pickles2/px-fw-2.x@2.0.29 で `get_path_homedir()` からの名称変更として追加されました。
	 *
	 * @return string ホームディレクトリの絶対パス
	 */
	public function get_realpath_homedir(){
		return $this->realpath_homedir;
	}

	/**
	 * コンテンツのパスを取得する。
	 * @return string コンテンツのパス
	 */
	public function get_path_content(){
		return $this->path_content;
	}

	/**
	 * `directory_index` (省略できるファイル名) の一覧を得る。
	 *
	 * @return array ディレクトリインデックスの一覧
	 */
	public function get_directory_index(){
		if( count($this->directory_index) ){
			return $this->directory_index;
		}
		$tmp_di = $this->conf->directory_index;
		$this->directory_index = array();
		foreach( $tmp_di as $file_name ){
			$file_name = trim($file_name);
			if( !strlen($file_name) ){ continue; }
			array_push( $this->directory_index, $file_name );
		}
		if( !count( $this->directory_index ) ){
			array_push( $this->directory_index, 'index.html' );
		}
		return $this->directory_index;
	}//get_directory_index()

	/**
	 * `directory_index` のいずれかにマッチするための pregパターン式を得る。
	 *
	 * @param string $delimiter pregパターンのデリミタ。省略時は `/` (`preg_quote()` の実装に従う)。
	 * @return string pregパターン
	 */
	public function get_directory_index_preg_pattern( $delimiter = null ){
		$directory_index = $this->get_directory_index();
		foreach( $directory_index as $key=>$row ){
			$directory_index[$key] = preg_quote($row, $delimiter);
		}
		$rtn = '(?:'.implode( '|', $directory_index ).')';
		return $rtn;
	}//get_directory_index_preg_pattern()

	/**
	 * 最も優先されるインデックスファイル名を得る。
	 *
	 * @return string 最も優先されるインデックスファイル名
	 */
	public function get_directory_index_primary(){
		$directory_index = $this->get_directory_index();
		return $directory_index[0];
	}//get_directory_index_primary()

	/**
	 * ファイルの処理方法を調べる。
	 *
	 * 設定値 `$conf->paths_proc_type` の連想配列にもとづいてパスを評価し、
	 * 処理方法の種類を調べて返します。
	 *
	 * @param string $path パス
	 * @return string 処理方法
	 * - ignore = 対象外パス。Pickles 2 のアクセス可能範囲から除外します。このパスにへのアクセスは拒絶され、パブリッシュの対象からも外されます。
	 * - direct = 物理ファイルを、ファイルとして読み込んだでから加工処理を通します。 (direct以外の通常の処理は、PHPファイルとして `include()` されます)
	 * - pass = 物理ファイルを、そのまま無加工で出力します。 (デフォルト)
	 * - その他 = extension名
	 */
	public function get_path_proc_type( $path = null ){
		static $rtn = array();
		static $rtn_after_sitemap = array();
		if( is_null($path) ){
			$path = $this->req()->get_request_file_path();
		}
		$path = preg_replace('/\#.*$/', '', $path);
		$path = preg_replace('/\?.*$/', '', $path);
		$path = $this->fs()->get_realpath( '/'.$path );
		if( is_dir('./'.$path) ){
			$path .= '/'.$this->get_directory_index_primary();
		}
		if( preg_match('/(?:\/|\\\\)$/', $path) ){
			$path .= $this->get_directory_index_primary();
		}
		$path = $this->fs()->normalize_path($path);

		if( is_object( $this->site ) ){
			if( @!is_null($rtn_after_sitemap[$path]) ){
				return $rtn_after_sitemap[$path];
			}
			$page_info = $this->site()->get_page_info( $path );
			if( @strlen( $page_info['proc_type'] ) ){
				$rtn_after_sitemap[$path] = $page_info['proc_type'];
				return $rtn_after_sitemap[$path];
			}
		}

		if( @!is_null($rtn[$path]) ){
			return $rtn[$path];
		}

		$realpath_controot = $this->fs()->normalize_path( $this->fs()->get_realpath( $this->get_path_docroot().$this->get_path_controot() ) );
		foreach( $this->conf->paths_proc_type as $row => $type ){
			if(!is_string($row)){continue;}
			$preg_pattern = preg_quote($this->fs()->normalize_path($this->fs()->get_realpath($row)), '/');
			if( preg_match('/\*/',$preg_pattern) ){
				// ワイルドカードが使用されている場合
				$preg_pattern = preg_quote($row,'/');
				$preg_pattern = preg_replace('/'.preg_quote('\*','/').'/','(?:.*?)',$preg_pattern);//ワイルドカードをパターンに反映
				$preg_pattern = $preg_pattern.'$';//前方・後方一致
			}elseif(is_dir($realpath_controot.$row)){
				$preg_pattern = preg_quote($this->fs()->normalize_path($this->fs()->get_realpath($row)).'/','/');
			}elseif(is_file($realpath_controot.$row)){
				$preg_pattern = preg_quote($this->fs()->normalize_path($this->fs()->get_realpath($row)),'/');
			}
			if( preg_match( '/^'.$preg_pattern.'$/s' , $path ) ){
				$rtn[$path] = $type;
				return $rtn[$path];
			}
		}
		$rtn[$path] = 'pass';// <- default
		return $rtn[$path];
	}//get_path_proc_type();

	/**
	 * サイトマップのロードが有効なパスか調べる。
	 *
	 * 設定値 `$conf->paths_enable_sitemap` の配列にもとづいてパスを評価し、
	 * サイトマップをロードするべきかどうかを判定します。
	 *
	 * @param string $path パス
	 * @return bool 有効の場合 true, 無効の場合 false.
	 */
	public function is_path_enable_sitemap( $path = null ){
		static $rtn = array();
		if( is_null($path) ){
			$path = $this->req()->get_request_file_path();
		}
		$path = $this->fs()->get_realpath( '/'.$path );
		if( is_dir('./'.$path) ){
			$path .= '/'.$this->get_directory_index_primary();
		}
		if( preg_match('/(?:\/|\\\\)$/', $path) ){
			$path .= $this->get_directory_index_primary();
		}
		$path = $this->fs()->normalize_path($path);

		if( @is_bool($rtn[$path]) ){
			return $rtn[$path];
		}

		foreach( $this->conf->paths_enable_sitemap as $row ){
			if(!is_string($row)){continue;}
			$preg_pattern = preg_quote($this->fs()->normalize_path($this->fs()->get_realpath($row)), '/');
			if( preg_match('/\*/',$preg_pattern) ){
				// ワイルドカードが使用されている場合
				$preg_pattern = preg_quote($row,'/');
				$preg_pattern = preg_replace('/'.preg_quote('\*','/').'/','(?:.*?)',$preg_pattern);//ワイルドカードをパターンに反映
				$preg_pattern = $preg_pattern.'$';//前方・後方一致
			}elseif(is_dir($row)){
				$preg_pattern = preg_quote($this->fs()->normalize_path($this->fs()->get_realpath($row)).'/','/');
			}elseif(is_file($row)){
				$preg_pattern = preg_quote($this->fs()->normalize_path($this->fs()->get_realpath($row)),'/');
			}
			if( preg_match( '/^'.$preg_pattern.'$/s' , $path ) ){
				$rtn[$path] = true;
				return $rtn[$path];
			}
		}
		$rtn[$path] = false;// <- default
		return $rtn[$path];
	}//is_path_enable_sitemap();

	/**
	 * 除外ファイルか調べる。
	 *
	 * @param string $path パス
	 * @return bool 除外ファイルの場合 `true`、それ以外の場合に `false` を返します。
	 */
	public function is_ignore_path( $path ){
		$type = $this->get_path_proc_type($path);
		if( $type == 'ignore' ){
			return true;
		}
		return false;
	}//is_ignore_path();

	/**
	 * デフォルトの MIME Type を取得する。
	 *
	 * @param string $path パス
	 * @return string MIME Type
	 */
	private function get_default_mime_type( $path = null ){
		$rtn = null;
		if(@!strlen($path)){
			$path = $this->req()->get_request_file_path();
		}
		$extension = $this->get_path_proc_type( $path );
		switch( strtolower( $extension ) ){
			case 'pass':
			case 'ignore':
			case 'direct':
				$extension = pathinfo( $path , PATHINFO_EXTENSION );
				break;
			default:
				break;
		}

		switch( strtolower( $extension ) ){
			case 'css':
				$rtn = 'text/css';
				break;
			case 'js':
				$rtn = 'text/javascript';
				break;
			case 'html':
			case 'htm':
			case 'shtml':
			case 'shtm':
			case 'xbm':
				$rtn = 'text/html';
				break;
			case 'png': $rtn = 'image/png'; break;
			case 'gif': $rtn = 'image/gif'; break;
			case 'jpg':case 'jpeg':case 'jpe': $rtn = 'image/jpeg'; break;
			case 'svg':case 'svgz': $rtn = 'image/svg+xml'; break;
			case 'csv': $rtn = 'text/comma-separated-values'; break;
			case 'tsv': $rtn = 'text/tab-separated-values'; break;
			case 'pdf': $rtn = 'application/pdf'; break;
			case 'swf': $rtn = 'application/x-shockwave-flash'; break;
			case 'txt':case 'text':
			default:
				$rtn = 'text/plain'; break;
		}
		return $rtn;
	} // get_default_mime_type()

	/**
	 * Pickles 2 内部でサブリクエストを発行し、結果を受け取る
	 *
	 * @param string $request_path リクエストを発行する対象のパス
	 * @param array $options Pickles 2 へのコマンド発行時のオプション
	 * - output = 期待する出力形式。`json` を指定すると、サブリクエストに `-o json` オプションが加えられ、JSON形式で解析済みのオブジェクトが返されます。
	 * - user_agent = `HTTP_USER_AGENT` 文字列。 `user_agent` が空白の場合、または文字列 `PicklesCrawler` を含む場合には、パブリッシュツールからのアクセスであるとみなされます。
	 * @param int &$return_var コマンドの終了コードで上書きされます
	 * @return mixed サブリクエストの実行結果。
	 * 通常は 得られた標準出力をそのまま文字列として返します。
	 * `output` オプションに `json` が指定された場合、 `json_decode()` された値が返却されます。
	 *
	 * サブリクエストから標準エラー出力を検出した場合、 `$px->error( $stderr )` に転送します。
	 */
	public function internal_sub_request($request_path, $options = null, &$return_var = null){
		if(!is_string($request_path)){
			$this->error('Invalid argument supplied for 1st option $request_path in $px->internal_sub_request(). It required String value.');
			return false;
		}
		if(!strlen($request_path)){ $request_path = '/'; }
		if(is_null($options)){ $options = array(); }
		$php_command = array();
		array_push( $php_command, addslashes($this->conf()->commands->php) );
			// ↑ Windows でこれを `escapeshellarg()` でエスケープすると、なぜかエラーに。

		if( strlen(@$this->conf()->path_phpini) ){
			$php_command = array_merge(
				$php_command,
				array(
					'-c', escapeshellarg(@$this->conf()->path_phpini),// ← php.ini のパス
				)
			);
		}
		if( strlen(@$this->req()->get_cli_option( '-d' )) ){
			$php_command = array_merge(
				$php_command,
				array(
					'-d', escapeshellarg(@$this->req()->get_cli_option( '-d' )),// ← php.ini definition
				)
			);
		}
		array_push($php_command, escapeshellarg( realpath($_SERVER['SCRIPT_FILENAME']) ));
		if( @$options['output'] == 'json' ){
			array_push($php_command, '-o');
			array_push($php_command, 'json');
		}
		if( @strlen($options['user_agent']) ){
			array_push($php_command, '-u');
			array_push($php_command, escapeshellarg($options['user_agent']));
		}
		array_push($php_command, escapeshellarg($request_path));

		$cmd = implode( ' ', $php_command );

		// コマンドを実行
		ob_start();
		$proc = proc_open($cmd, array(
			0 => array('pipe','r'),
			1 => array('pipe','w'),
			2 => array('pipe','w'),
		), $pipes);
		$io = array();
		foreach($pipes as $idx=>$pipe){
			$io[$idx] = stream_get_contents($pipe);
			fclose($pipe);
		}
		$return_var = proc_close($proc);
		ob_get_clean();

		$bin = $io[1]; // stdout
		if( strlen( $io[2] ) ){
			$this->error($io[2]); // stderr
		}

		if( @$options['output'] == 'json' ){
			$bin = json_decode($bin);
		}

		return $bin;
	} // internal_sub_request()

	/**
	 * response status code を取得する。
	 *
	 * `$px->set_status()` で登録した情報を取り出します。
	 *
	 * @return int ステータスコード (100〜599の間の数値)
	 */
	public function get_status(){
		return $this->response_status;
	}
	/**
	 * response ステータスメッセージを取得する。
	 *
	 * `$px->set_status()` で登録した情報を取り出します。
	 *
	 * @return string ステータスメッセージ
	 */
	public function get_status_message(){
		return $this->response_message;
	}

	/**
	 * response status code をセットする
	 * @param int $code ステータスコード (100〜599の間の数値)
	 * @param string $message ステータスメッセージ
	 * @return bool 成功時 `true`, 失敗時 `false`
	 */
	public function set_status($code, $message = null){
		$code = intval($code);
		if( strlen( $code ) != 3 ){
			return false;
		}
		if( !($code >= 100 && $code <= 599) ){
			return false;
		}
		$this->response_status = intval($code);
		if( !strlen($message) ){
			switch( $this->response_status ){
				case 200: $message = 'OK'; break;
				case 403: $message = 'Forbidden'; break;
				case 404: $message = 'NotFound'; break;
				default:break;
			}
		}
		$this->response_message = $message;

		@header('HTTP/1.1 '.$this->response_status.' '.$this->response_message);
		return true;
	}

	/**
	 * エラーメッセージを送信する。
	 *
	 * @param $message string エラーメッセージ
	 * @return bool true
	 */
	public function error( $message = null ){
		@array_push( $this->errors , $message );
		@header('X-PXFW-ERROR: '.$message.'', false);
		return true;
	}

	/**
	 * 送信されたエラーメッセージの一覧を取得する。
	 *
	 * @return array 送信済みのエラーメッセージの一覧
	 */
	public function get_errors(){
		return $this->errors;
	}

	/**
	 * 拡張ヘッダ `X-PXFW-RELATEDLINK` にリンクを追加する。
	 *
	 * 拡張ヘッダ `X-PXFW-RELATEDLINK` は、サイトマップや物理ディレクトリから発見できないファイルを、Pickles Framework のパブリッシュツールに知らせます。
	 *
	 * 通常、Pickles Framework のパブリッシュツールは 動的に生成されたページなどを知ることができず、パブリッシュされません。このメソッドを通じて、明示的に知らせる必要があります。
	 *
	 * @param string $path リンクのパス
	 * @return bool 正常時 `true`、失敗した場合 `false`
	 */
	public function add_relatedlink( $path ){
		$path = trim($path);
		if(!strlen($path)){
			return false;
		}
		array_push( $this->relatedlinks , $path );
		@header('X-PXFW-RELATEDLINK: '.$path.'', false);

		return true;
	}

	/**
	 * 拡張ヘッダ `X-PXFW-RELATEDLINK` に追加されたリンクを取得する。
	 *
	 * 拡張ヘッダ `X-PXFW-RELATEDLINK` に、既に追加されているリンクの一覧を取得します。
	 *
	 * リンクは、 `add_relatedlink()` を通じて追加されます。
	 *
	 * @return array 正常時 `true`、失敗した場合 `false`
	 */
	public function get_relatedlinks(){
		return $this->relatedlinks;
	}

	/**
	 * PX Command を取得する。
	 * @return array コマンド配列(ドットで区切られた結果の配列)
	 */
	public function get_px_command(){
		if( !$this->conf()->allow_pxcommands && !$this->req()->is_cmd() ){
			return null;
		}
		$rtn = array();
		$cmd = $this->req()->get_param('PX');
		if( is_string($cmd) ){
			$rtn = explode('.', $cmd);
		}
		return $rtn;
	}// get_px_command()

	/**
	 * コンテンツを実行する。
	 * @param object $px picklesオブジェクト
	 * @return bool true
	 */
	private static function exec_content( $px ){
		if( !$px->fs()->is_file( './'.$px->get_path_content() ) ){
			@$px->header('Content-type: text/html');
			$px->set_status(404);// 404 NotFound
			$px->bowl()->send('
404 - File not found.
');
			return true;
		}
		if( $px->proc_type === 'direct' ){
			$src = $px->fs()->read_file( './'.$px->get_path_content() );
		}else{
			ob_start();
			include( './'.$px->get_path_content() );
			$src = ob_get_clean();
		}
		$px->bowl()->send($src);
		return true;
	}

	/**
	 * Contents Manifesto のソースを取得する。
	 * @return string HTMLコード
	 */
	public function get_contents_manifesto(){
		$px = $this;
		$rtn = '';
		if( !strlen( @$this->conf()->contents_manifesto ) ){
			return '';
		}
		$realpath = $this->get_realpath_docroot().$this->href( @$this->conf()->contents_manifesto );
		if( !$this->fs()->is_file( $realpath ) ){
			return '';
		}
		ob_start();
		include( $realpath );
		$rtn = ob_get_clean();
		return $rtn;
	}

	/**
	 * 実行者がパブリッシュツールかどうか調べる。
	 *
	 * @return bool パブリッシュツールの場合 `true`, それ以外の場合 `false` を返します。
	 */
	public function is_publish_tool(){
		if( !strlen( @$_SERVER['HTTP_USER_AGENT'] ) ){
			// UAが空白なら パブリッシュツール
			return true;
		}
		if( preg_match( '/PicklesCrawler/', @$_SERVER['HTTP_USER_AGENT'] ) ){
			// "PicklesCrawler" が含まれていたら パブリッシュツール
			return true;
		}
		return false;
	}

	/**
	 * リンク先のパスを生成する。
	 *
	 * 引数には、リンク先のパス、またはページIDを受け取り、
	 * 完成されたリンク先情報(aタグのhref属性にそのまま適用できる状態)にして返します。
	 *
	 * Pickles Framework がドキュメントルート直下にインストールされていない場合、インストールパスを追加した値を返します。
	 *
	 * `http://` などスキーマから始まる情報を受け取ることもできます。
	 *
	 * 実装例：
	 *
<?php
	 * // パスを指定する例
	 * print '<a href="'.htmlspecialchars( $px->href('/aaa/bbb.html') ).'">リンク</a>';
	 *
	 * // ページIDを指定する例
	 * print '<a href="'.htmlspecialchars( $px->href('A-00') ).'">リンク</a>';
	 * ?>

	 *
	 * インストールパスがドキュメントルートではない場合の例:
	 *
<?php
	 * // インストールパスが "/installpath/" の場合
	 * print '<a href="'.htmlspecialchars( $px->href('/aaa/bbb.html') ).'">リンク</a>';
	 *	 // "/installpath/aaa/bbb.html" が返されます。
	 * ?>

	 *
	 * @param string $linkto リンク先の パス または ページID
	 *
	 * @return string href属性値
	 */
	public function href( $linkto ){
		$parsed_url = parse_url($linkto);
		$tmp_page_info_by_id = null;
		if( $this->site() !== false ){
			$tmp_page_info_by_id = $this->site()->get_page_info_by_id($linkto);
			if( !$tmp_page_info_by_id ){
				$tmp_page_info_by_id = $this->site()->get_page_info_by_id(@$parsed_url['path']);
			}
		}
		$path = $linkto;
		$path_type = false;
		$path_type = $this->get_path_type( $linkto );
		if(
			is_array($tmp_page_info_by_id) && array_key_exists('id', $tmp_page_info_by_id) &&
			(@$tmp_page_info_by_id['id'] == $linkto || @$tmp_page_info_by_id['id'] == @$parsed_url['path'])
		){
			$path = @$tmp_page_info_by_id['path'];
			$path_type = $this->get_path_type( $path );
		}
		unset($tmp_page_info_by_id);

		if( preg_match( '/^alias[0-9]*\:(.+)/' , $path , $tmp_matched ) ){
			//  エイリアスを解決
			$path = $tmp_matched[1];
		}elseif( $path_type == 'dynamic' ){
			//  ダイナミックパスをバインド
			// $sitemap_dynamic_path = $this->site()->get_dynamic_path_info( $linkto );
			// $tmp_path = $sitemap_dynamic_path['path_original'];
			$tmp_path = $path;
			$path = '';
			while( 1 ){
				if( !preg_match( '/^(.*?)\{(\$|\*)([a-zA-Z0-9\_\-]*)\}(.*)$/s' , $tmp_path , $tmp_matched ) ){
					$path .= $tmp_path;
					break;
				}
				$path .= $tmp_matched[1];
				if(!strlen($tmp_matched[3])){
					//無名のパラメータはバインドしない。
				}elseif( !is_null( $this->req()->get_path_param($tmp_matched[3]) ) ){
					$path .= $this->req()->get_path_param($tmp_matched[3]);
				}else{
					$path .= $tmp_matched[3];
				}
				$tmp_path = $tmp_matched[4];
				continue;
			}
			unset($tmp_path , $tmp_matched);
		}

		$tmp_path = $this->fs()->normalize_path($path);
		$path_type = $this->get_path_type( $tmp_path );
		switch( $path_type ){
			case 'full_url':
			case 'data':
			case 'javascript':
			case 'anchor':
				break;
			default:
				// apply normalize_path()
				$path = $tmp_path;

				// index.htmlを省略
				$path = preg_replace('/\/'.$this->get_directory_index_preg_pattern().'((?:\?|\#).*)?$/si','/$1',$path);
				if( preg_match( '/^\\//' , $path ) ){
					//  スラッシュから始まる絶対パスの場合、
					//  インストールパスを起点としたパスに書き変えて返す。
					// $path = preg_replace( '/^\/+/' , '' , $path );
					$path = preg_replace('/\/$/', '', $this->get_path_controot()).$path;
				}
				$parsed_url_fin = parse_url($path);
				$path = $this->fs()->normalize_path( $parsed_url_fin['path'] );

				// パラメータを、引数の生の状態に戻す。
				if( array_key_exists('query', $parsed_url) && strlen($parsed_url['query']) ){
					$path .= '?'.$parsed_url['query'];
				}elseif( array_key_exists('query', $parsed_url_fin) && strlen(@$parsed_url_fin['query']) ){
					$path .= '?'.@$parsed_url_fin['query'];
				}
				if( array_key_exists('fragment', $parsed_url) && strlen($parsed_url['fragment']) ){
					$path .= '#'.$parsed_url['fragment'];
				}elseif( array_key_exists('fragment', $parsed_url_fin) && strlen(@$parsed_url_fin['fragment']) ){
					$path .= '#'.@$parsed_url_fin['fragment'];
				}
				break;
		}

		return $path;
	}//href()

	/**
	 * リンク先の正式なURLを生成する。
	 *
	 * スキーマ、ドメインが補完された完全なURLを返します。
	 * このメソッドは、 設定項目 `scheme` と `domain` に影響を受けます。 `domain` が設定されていない場合にエラーを発行します。
	 *
	 * 実装例:
	 * ```
	 * <link rel="canonical" href="<?= $px->canonical() ?>" />
	 * ```
	 *
	 * @param string $linkto リンク先の パス または ページID (省略時はカレントページ)
	 *
	 * @return string href属性値
	 */
	public function canonical( $linkto = null ){
		if( !strlen($linkto) ){
			$linkto = $this->site()->get_current_page_info('id');
		}
		$href = $this->href( $linkto );
		$scheme = @$this->conf()->scheme;
		if( !strlen($scheme) ){ $scheme = 'http'; }
		$domain = @$this->conf()->domain;
		if( !strlen($domain) ){
			$this->error('Config "domain" is required.');
			return $href;
		}
		return $scheme.'://'.$domain.$href;
	}//canonical()

	/**
	 * リンクタグ(aタグ)を生成する。
	 *
	 * リンク先の パス または ページID を受け取り、aタグを生成して返します。リンク先は、`href()` メソッドを通して調整されます。
	 *
	 * このメソッドは、受け取ったパス(またはページID)をもとに、サイトマップからページ情報を取得し、aタグを自動補完します。
	 * 例えば、パンくず情報をもとに `class="current"` を付加したり、ページタイトルをラベルとして採用します。
	 *
	 * この動作は、引数 `$options` に値を指定して変更することができます。
	 *
	 * 実装例:
	 *
<?php
	 * // ページ /aaa/bbb.html へのリンクを生成
	 * print $px->mk_link('/aaa/bbb.html');
	 *
	 * // ページ /aaa/bbb.html へのリンクをオプション付きで生成
	 * print $px->mk_link('/aaa/bbb.html', array(
	 *	 'label'=>'リンクラベル', //リンクラベルを文字列で指定
	 *	 'class'=>'class_a class_b', //CSSのクラス名を指定
	 *	 'no_escape'=>true, //エスケープ処理をキャンセル
	 *	 'current'=>true //カレント表示にする
	 * ));
	 * ?>

	 *
	 * 第2引数に文字列または関数としてリンクラベルを渡す方法もあります。
	 * この場合、その他のオプションは第3引数に渡すことができます。
	 *
	 * 実装例:
	 *
<?php
	 * // ページ /aaa/bbb.html へのリンクをオプション付きで生成
	 * print $px->mk_link('/aaa/bbb.html',
	 *   'リンクラベル' , //リンクラベルを文字列で指定
	 *   array(
	 *	 'class'=>'class_a class_b',
	 *	 'no_escape'=>true,
	 *	 'current'=>true
	 *   )
	 * );
	 * ?>

	 *
	 * リンクのラベルはコールバック関数でも指定できます。
	 * コールバック関数には、リンク先のページ情報を格納した連想配列が引数として渡されます。
	 *
	 * 実装例:
	 *
<?php
	 * //リンクラベルをコールバック関数で指定
	 * print $px->mk_link(
	 *   '/aaa/bbb.html',
	 *   function($page_info){return htmlspecialchars($page_info['title_label']);}
	 * );
	 * ?>

	 *
	 * @param string $linkto リンク先のパス。PxFWのインストールパスを基点にした絶対パスで指定。
	 * @param array $options options: [as string] Link label, [as function] callback function, [as array] Any options.
	 *

	 *
mixed $options['label']

	 *
リンクのラベルを変更します。文字列か、またはコールバック関数が利用できます。

	 *
bool $options['canonical']

	 *
true または false を指定します。この値が true の場合、リンク先の生成に `$px->href()` の代わりに `$px->canonical()` が使用されます。 デフォルトは false です。

	 *
bool $options['current']

	 *
true または false を指定します。class="current" を強制的に付加または削除します。このオプションが指定されない場合は、自動的に選択されます。

	 *
bool $options['no_escape']

	 *
true または false を指定します。この値が false の場合、リンクラベルに含まれるHTML特殊文字が自動的にエスケープされます。デフォルトは、false、$options['label']が指定された場合は自動的に true になります。

	 *
mixed $options['class']

	 *
スタイルシートの クラス名 を文字列または配列で指定します。

	 *

	 * 第2引数にリンクラベルを直接指定することもできます。この場合、オプション配列は第3引数に指定します。
	 *
	 * @return string HTMLソース(aタグ)
	 */
	public function mk_link( $linkto, $options = array() ){
		$parsed_url = parse_url($linkto);
		$args = func_get_args();
		$page_info = null;
		if( $this->site() !== false ){
			$page_info = $this->site()->get_page_info($linkto);
			if( !$page_info ){
				$page_info = $this->site()->get_page_info(@$parsed_url['path']);
			}
		}
		if( is_array($options) && array_key_exists('canonical', $options) && !is_null( $options['canonical'] ) && !@empty($options['canonical']) ){
			$href = $this->canonical($linkto);
			$hrefc = $this->canonical($this->req()->get_request_file_path());
		}else{
			$href = $this->href($linkto);
			$hrefc = $this->href($this->req()->get_request_file_path());
		}
		$label = $page_info['title_label'];
		$page_id = $page_info['id'];

		$options = array();
		if( count($args) >= 2 && is_array($args[count($args)-1]) ){
			//  最後の引数が配列なら
			//  オプション連想配列として読み込む
			$options = $args[count($args)-1];
			if( array_key_exists('label', $options) && (is_string(@$options['label']) || (is_object(@$options['label']) && is_callable(@$options['label'])) ) ){
				$label = $options['label'];
				if( !is_array($options) || !array_key_exists('no_escape', $options) || is_null($options['no_escape']) ){
					$options['no_escape'] = true;
				}
			}
		}
		if( array_key_exists(1, $args) && ( @is_string($args[1]) || (@is_object($args[1]) && @is_callable($args[1])) ) ){
			//  第2引数が文字列、または function なら
			//  リンクのラベルとして採用
			$label = $args[1];
			if( !is_array($options) || !array_key_exists('no_escape', $options) || is_null($options['no_escape']) ){
				$options['no_escape'] = true;
			}
		}

		$is_current = false;
		if( $this->site() !== false ){
			if( is_array($options) && array_key_exists('current', $options) && !is_null( $options['current'] ) ){
				$is_current = !@empty($options['current']);
			}elseif($href==$hrefc){
				$is_current = true;
			}elseif( $this->site()->is_page_in_breadcrumb($page_info['id']) ){
				$is_current = true;
			}
		}

		$is_popup = false;
		if( $this->site() !== false ){
			if( strpos( $this->site()->get_page_info($linkto,'layout') , 'popup' ) === 0 ){ // <- 'popup' で始まるlayoutは、ポップアップとして扱う。
				$is_popup = true;
			}
		}

		$label = (!is_null($label)?$label:$href); // labelがnullの場合、リンク先をラベルとする

		$classes = array();
		// CSSのクラスを付加
		if( is_array($options) && array_key_exists('class', $options) && is_string($options['class']) ){
			$options['class'] = preg_split( '/\s+/', trim($options['class']) );
		}
		if( is_array($options) && array_key_exists('class', $options) && is_array($options['class']) ){
			foreach($options['class'] as $class_row){
				array_push($classes, trim($class_row));
			}
		}
		if( $is_current ){
			array_push($classes, 'current');
		}

		if( is_object($label) && is_callable($label) ){
			$label = $label($page_info);
			if( !is_array($options) || !array_key_exists('no_escape', $options) || is_null($options['no_escape']) ){
				$options['no_escape'] = true;
			}
		}
		if( !array_key_exists('no_escape', $options) || !@$options['no_escape'] ){
			// no_escape(エスケープしない)指示がなければ、
			// HTMLをエスケープする。
			$label = htmlspecialchars($label);
		}

		$rtn = ''.$label.'';
		return $rtn;
	}

	/**
	 * スキーマ名を取得する。
	 *
	 * `$conf->scheme` が設定されている場合、これを返します。
	 * 設定されていない場合は、環境変数 `$_SERVER['REQUEST_SCHEME']` から取得して返します。
	 * `$_SERVER['REQUEST_SCHEME']` がセットされない場合は、 `$_SERVER['SERVER_PORT']` から推測して決定します。
	 * ただし、Pickles 2 は ウェブサーバー上で実行されているとは限らないため、 環境変数は意図通りに取得できないことがあります。
	 * `$conf->scheme` を正しく設定する方が望ましい結果が得られます。
	 * @return string ドメイン名
	 */
	public function get_scheme(){
		static $rtn;
		if( !is_null($rtn) ){
			return $rtn;
		}
		if( @strlen( $this->conf->scheme ) ){
			$rtn = $this->conf->scheme;
			return $rtn;
		}
		if( @strlen( $_SERVER['REQUEST_SCHEME'] ) ){
			// ただし、値がセットされていることはPHPによって保証されてはいない
			$rtn = $_SERVER['REQUEST_SCHEME'];
			return $rtn;
		}
		if( @strlen( $_SERVER['SERVER_PORT'] ) && $_SERVER['SERVER_PORT'] == '443' ){
			// ただし、ポート 443 だからといって https とは限らない
			$rtn = 'https';
			return $rtn;
		}
		$rtn = 'http';
		return $rtn;
	}

	/**
	 * ドメイン名を取得する。
	 *
	 * `$conf->domain` が設定されている場合、これを返します。
	 * 設定されていない場合は、環境変数 `$_SERVER['SERVER_NAME']` から取得して返します。
	 * ただし、Pickles 2 は ウェブサーバー上で実行されているとは限らないため、 `$_SERVER['SERVER_NAME']` は取得できないことがあります。
	 * `$conf->domain` を正しく設定する方が望ましい結果が得られます。
	 * @return string ドメイン名
	 */
	public function get_domain(){
		static $rtn;
		if( !is_null($rtn) ){
			return $rtn;
		}
		if( @strlen( $this->conf->domain ) ){
			$rtn = $this->conf->domain;
			return $rtn;
		}
		$rtn = @$_SERVER['SERVER_NAME'];
		return $rtn;
	}

	/**
	 * コンテンツルートディレクトリのパス(=install path) を取得する。
	 *
	 * コンテンツルートディレクトリは、PHPの実行ファイル (通常は `.px_execute.php`)が設置されたディレクトリを指します。
	 *
	 * 通常、これは ドキュメントルート(`$px->get_realpath_docroot()` から得られる) と同じディレクトリですが、
	 * より深い物理階層に Pickles 2 をインストールすることもできます。
	 * この場合、 `$conf->path_controot` に ドキュメントルート以下のパスを設定してください。
	 * `get_path_controot()` は、このパスを整形して返します。
	 *
	 * @return string コンテンツディレクトリのパス(HTTPクライアントから見た場合のパス)
	 */
	public function get_path_controot(){
		static $rtn;
		if( !is_null($rtn) ){
			return $rtn;
		}
		$rtn = $this->fs()->normalize_path( dirname( $_SERVER['SCRIPT_NAME'] ) );
		if( $rtn != '/' ){
			$rtn .= '/';
		}
		if( $this->req()->is_cmd() ){
			$rtn = '/';
			if( @strlen( $this->conf->path_controot ) ){
				$rtn = $this->conf->path_controot;
				$rtn = preg_replace('/^(.*?)\/*$/s', '$1/', $rtn);
				$rtn = $this->fs()->normalize_path($rtn);
				return $rtn;
			}
		}
		$rtn = $this->fs()->normalize_path($rtn);
		return $rtn;
	}

	/**
	 * DOCUMENT_ROOT のパスを取得する。 (deprecated)
	 * このメソッドの使用は推奨されません。
	 * 代わりに `$px->get_realpath_docroot()` を使用してください。
	 * @return string ドキュメントルートのパス
	 */
	public function get_path_docroot(){
		return $this->get_realpath_docroot();
	}

	/**
	 * DOCUMENT_ROOT のパスを取得する。
	 *
	 * pickles2/px-fw-2.x@2.0.29 で `get_path_docroot()` からの名称変更として追加されました。
	 *
	 * @return string ドキュメントルートのパス
	 */
	public function get_realpath_docroot(){
		$path_controot = $this->fs()->normalize_path( $this->fs()->get_realpath( $this->get_path_controot() ) );
		$path_cd = $this->fs()->normalize_path( $this->fs()->get_realpath( dirname($_SERVER['SCRIPT_FILENAME']) ).DIRECTORY_SEPARATOR );
		$rtn = preg_replace( '/'.preg_quote( $path_controot, '/' ).'$/s', '', $path_cd ).DIRECTORY_SEPARATOR;
		$rtn = $this->fs()->normalize_path($rtn);
		return $rtn;
	}

	/**
	 * ローカルリソースディレクトリのパスを得る。
	 *
	 * @param string $localpath_resource ローカルリソースのパス
	 * @return string ローカルリソースの実際の絶対パス
	 */
	public function path_files( $localpath_resource = null ){
		if( $this->site() !== false ){
			$tmp_page_info = $this->site()->get_current_page_info();
			$path_content = $tmp_page_info['content'];
			unset($tmp_page_info);
		}
		if( @is_null($path_content) ){
			$path_content = $this->req()->get_request_file_path();
		}

		$rtn = '';
		if( is_callable($this->conf->path_files) ){
			// コールバック関数が設定された場合
			$rtn = call_user_func($this->conf->path_files, $this->fs()->normalize_path($path_content) );
		}elseif( is_string($this->conf->path_files) && strpos(trim($this->conf->path_files), 'function') === 0 ){
			// function で始まる文字列が設定された場合
			$rtn = call_user_func(eval('return '.$this->conf->path_files.';'), $this->fs()->normalize_path($path_content) );
		}else{
			$rtn = $this->conf->path_files;
			$data = array(
				'dirname'=>$this->fs()->normalize_path(dirname($path_content)),
				'filename'=>basename($this->fs()->trim_extension($path_content)),
				'ext'=>strtolower($this->fs()->get_extension($path_content)),
			);
			$rtn = str_replace( '{$dirname}', $data['dirname'], $rtn );
			$rtn = str_replace( '{$filename}', $data['filename'], $rtn );
			$rtn = str_replace( '{$ext}', $data['ext'], $rtn );
		}

		$rtn = preg_replace( '/^\/*/', '/', $rtn );
		$rtn = preg_replace( '/\/*$/', '', $rtn ).'/';
		$rtn = $rtn.$localpath_resource;
		if( $this->fs()->is_dir('./'.$rtn) ){
			$rtn .= '/';
		}
		$rtn = $this->href( $rtn );
		$rtn = $this->fs()->normalize_path($rtn);
		$rtn = preg_replace( '/^\/+/', '/', $rtn );
		return $rtn;
	}//path_files()

	/**
	 * ローカルリソースディレクトリのサーバー内部パスを得る。
	 *
	 * @param string $localpath_resource ローカルリソースのパス
	 * @return string ローカルリソースのサーバー内部パス
	 */
	public function realpath_files( $localpath_resource = null ){
		$rtn = $this->path_files( $localpath_resource );
		$rtn = $this->fs()->get_realpath( $rtn );
		$rtn = $this->fs()->get_realpath( $this->get_realpath_docroot().$rtn );
		return $rtn;
	}//realpath_files()

	/**
	 * ローカルリソースのキャッシュのパスを得る。
	 *
	 * ローカルリソースキャッシュディレクトリは、
	 * `$conf->public_cache_dir` の設定により決定されます。
	 *
	 * `$conf->public_cache_dir` が設定されていない場合に `false` を返します。
	 *
	 * @param string $localpath_resource ローカルリソースのパス
	 * @return string ローカルリソースキャッシュのパス
	 */
	public function path_files_cache( $localpath_resource = null ){
		if( !strlen( @$this->conf()->public_cache_dir ) ){
			return false;
		}
		if( $this->site() !== false ){
			$tmp_page_info = $this->site()->get_current_page_info();
			$path_content = $tmp_page_info['content'];
			unset($tmp_page_info);
		}
		if( @is_null($path_content) ){
			$path_content = $this->req()->get_request_file_path();
		}

		$path_original = $this->get_path_controot().$path_content;
		$path_original = $this->fs()->get_realpath($this->fs()->trim_extension($path_original).'_files/'.$localpath_resource);
		$rtn = $this->get_path_controot().'/'.$this->conf()->public_cache_dir.'/c'.$path_content;
		$rtn = $this->fs()->get_realpath($this->fs()->trim_extension($rtn).'_files/'.$localpath_resource);
		$this->fs()->mkdir_r( dirname( $this->get_realpath_docroot().$rtn ) );

		if( file_exists( $this->get_realpath_docroot().$path_original ) ){
			if( is_dir($this->get_realpath_docroot().$path_original) ){
				$this->fs()->mkdir_r( $this->get_realpath_docroot().$rtn );
			}else{
				$this->fs()->mkdir_r( dirname( $this->get_realpath_docroot().$rtn ) );
			}
			$this->fs()->copy_r( $this->get_realpath_docroot().$path_original, $this->get_realpath_docroot().$rtn );
		}

		$rtn = $this->fs()->normalize_path($rtn);
		$rtn = preg_replace( '/^\/+/', '/', $rtn );
		$this->add_relatedlink($rtn);
		return $rtn;
	}//path_files_cache()

	/**
	 * ローカルリソースのキャッシュディレクトリのサーバー内部パスを得る。
	 *
	 * ローカルリソースキャッシュディレクトリは、
	 * `$conf->public_cache_dir` の設定により決定されます。
	 *
	 * `$conf->public_cache_dir` が設定されていない場合に `false` を返します。
	 *
	 * @param string $localpath_resource ローカルリソースのパス
	 * @return string ローカルリソースキャッシュのサーバー内部パス
	 */
	public function realpath_files_cache( $localpath_resource = null ){
		if( !strlen( @$this->conf()->public_cache_dir ) ){
			return false;
		}
		$rtn = $this->path_files_cache( $localpath_resource );
		$rtn = $this->fs()->get_realpath( $this->get_realpath_docroot().$rtn );
		return $rtn;
	}//realpath_files_cache()

	/**
	 * コンテンツ別の非公開キャッシュディレクトリのサーバー内部パスを得る。
	 *
	 * @param string $localpath_resource ローカルリソースのパス
	 * @return string コンテンツ別の非公開キャッシュのサーバー内部パス
	 */
	public function realpath_files_private_cache( $localpath_resource = null ){
		if( $this->site() !== false ){
			$tmp_page_info = $this->site()->get_current_page_info();
			$path_content = $tmp_page_info['content'];
			unset($tmp_page_info);
		}
		if( @is_null($path_content) ){
			$path_content = $this->req()->get_request_file_path();
		}

		$path_original = $this->get_path_controot().$path_content;
		$path_original = $this->fs()->get_realpath($this->fs()->trim_extension($path_original).'_files/'.$localpath_resource);
		$rtn = $this->get_realpath_homedir().'/_sys/ram/caches/c'.$path_content;
		$rtn = $this->fs()->get_realpath($this->fs()->trim_extension($rtn).'_files/'.$localpath_resource);
		$this->fs()->mkdir_r( dirname( $rtn ) );

		$rtn = $this->fs()->normalize_path( $rtn );
		$rtn = preg_replace( '/^\/+/', '/', $rtn );
		$rtn = $this->fs()->get_realpath( $rtn );
		return $rtn;
	}//realpath_files_private_cache()

	/**
	 * プラグイン別公開キャッシュのパスを得る。
	 *
	 * プラグイン別公開キャッシュディレクトリは、
	 * `$conf->public_cache_dir` の設定により決定されます。
	 *
	 * `$conf->public_cache_dir` が設定されていない場合に `false` を返します。
	 *
	 * @param string $localpath_resource リソースのパス
	 * @return string 公開キャッシュのパス
	 */
	public function path_plugin_files( $localpath_resource = null ){
		if( !strlen( @$this->conf()->public_cache_dir ) ){
			return false;
		}
		$rtn = $this->get_path_controot().'/'.$this->conf()->public_cache_dir.'/p/'.urlencode($this->proc_id).'/';
		$rtn = $this->fs()->get_realpath( $rtn.'/' );
		if( !$this->fs()->is_dir( $this->get_realpath_docroot().$rtn ) ){
			$this->fs()->mkdir_r( $this->get_realpath_docroot().$rtn );
		}
		if( !strlen( $localpath_resource ) ){
			return $this->fs()->normalize_path($rtn);
		}
		$rtn = $this->fs()->get_realpath( $rtn.'/'.$localpath_resource );
		if( $this->fs()->is_dir( $this->get_realpath_docroot().$rtn ) ){
			$rtn .= '/';
		}
		if( !$this->fs()->is_dir( dirname($this->get_realpath_docroot().$rtn) ) ){
			$this->fs()->mkdir_r( dirname($this->get_realpath_docroot().$rtn) );
		}
		$rtn = $this->fs()->normalize_path($rtn);
		$rtn = preg_replace( '/^\/+/', '/', $rtn );
		$this->add_relatedlink($rtn);
		return $rtn;
	}

	/**
	 * プラグイン別公開キャッシュのサーバー内部パスを得る。
	 *
	 * プラグイン別公開キャッシュディレクトリは、
	 * `$conf->public_cache_dir` の設定により決定されます。
	 *
	 * `$conf->public_cache_dir` が設定されていない場合に `false` を返します。
	 *
	 * @param string $localpath_resource リソースのパス
	 * @return string プライベートキャッシュのサーバー内部パス
	 */
	public function realpath_plugin_files( $localpath_resource = null ){
		if( !strlen( @$this->conf()->public_cache_dir ) ){
			return false;
		}
		$rtn = $this->path_plugin_files( $localpath_resource );
		$rtn = $this->fs()->get_realpath( $this->get_realpath_docroot().$rtn );
		return $rtn;
	}

	/**
	 * プラグイン別プライベートキャッシュのサーバー内部パスを得る。
	 *
	 * @param string $localpath_resource リソースのパス
	 * @return string プライベートキャッシュのサーバー内部パス
	 */
	public function realpath_plugin_private_cache( $localpath_resource = null ){
		$lib_realpath = $this->get_realpath_homedir().'_sys/ram/caches/p/'.urlencode($this->proc_id).'/';
		$rtn = $this->fs()->get_realpath( $lib_realpath.'/' );
		if( !$this->fs()->is_dir( $rtn ) ){
			$this->fs()->mkdir_r( $rtn );
		}
		if( !strlen( $localpath_resource ) ){
			return $rtn;
		}
		$rtn .= '/'.$localpath_resource;
		// if( $this->fs()->is_dir( $rtn ) ){
		// 	$rtn .= '/';
		// }
		$rtn = $this->fs()->get_realpath( $rtn );
		if( !$this->fs()->is_dir( dirname($rtn) ) ){
			$this->fs()->mkdir_r( dirname($rtn) );
		}
		return $rtn;
	}

	/**
	 * テキストを、指定の文字セットに変換する。
	 *
	 * @param mixed $text テキスト
	 * @param string $encode 変換後の文字セット。省略時、`mb_internal_encoding()` から取得
	 * @param string $encodefrom 変換前の文字セット。省略時、自動検出
	 * @return string 文字セット変換後のテキスト
	 */
	public function convert_encoding( $text, $encode = null, $encodefrom = null ){
		if( !is_callable( 'mb_internal_encoding' ) ){ return $text; }
		if( !strlen( $encode ) ){ $encode = mb_internal_encoding(); }
		if( !strlen( $encodefrom ) ){ $encodefrom = mb_internal_encoding().',UTF-8,SJIS-win,eucJP-win,SJIS,EUC-JP,JIS,ASCII'; }

		switch( strtolower( $encode ) ){
			case 'sjis':
			case 'sjis-win':
			case 'shift_jis':
			case 'shift-jis':
			case 'x-sjis':
				// ※ 'ms_kanji'、'windows-31j'、'cp932' は、もともと SJIS-win になる
				$encode = 'SJIS-win';
				break;
			case 'eucjp':
			case 'eucjp-win':
			case 'euc-jp':
				$encode = 'eucJP-win';
				break;
		}

		if( is_array( $text ) ){
			$RTN = array();
			if( !count( $text ) ){ return $text; }
			$TEXT_KEYS = array_keys( $text );
			foreach( $TEXT_KEYS as $Line ){
				$KEY = mb_convert_encoding( $Line , $encode , $encodefrom );
				if( is_array( $text[$Line] ) ){
					$RTN[$KEY] = $this->convert_encoding( $text[$Line] , $encode , $encodefrom );
				}else{
					$RTN[$KEY] = @mb_convert_encoding( $text[$Line] , $encode , $encodefrom );
				}
			}
		}else{
			if( !strlen( $text ) ){ return $text; }
			$RTN = @mb_convert_encoding( $text , $encode , $encodefrom );
		}
		return $RTN;
	}

	/**
	 * パス文字列を受け取り、種類を判定する。
	 *
	 * 次の基準で判定されます。
	 *
	 * - `javascript:` から始まる場合 => 'javascript'
	 * - `data:` から始まる場合 => 'data'
	 * - `#` から始まる場合 => 'anchor'
	 * - `http://` などURLスキーマ名から始まる場合, `//` から始まる場合 => 'full_url'
	 * - その他で `alias:` から始まる場合 => 'alias'
	 * - `{$xxxx}` または `{*xxxx}` を含む場合 => 'dynamic'
	 * - `/` から始まる場合 => 'normal'
	 * - どれにも当てはまらない不明な形式の場合に、`false` を返します。
	 *
	 * このメソッドは、以前は `$site` に実装されていました。
	 * `$conf->paths_enable_sitemap` が導入され、 `$site` が存在しない場合が考慮されるようになったことにより、
	 * `$px` に移管されました。
	 *
	 * @param string $path 調べるパス
	 * @return string|bool 判定結果
	 */
	public function get_path_type( $path ) {
		if( preg_match( '/^(?:alias[0-9]*\:)?javascript\:/i' , $path ) ) {
			//  javascript: から始まる場合
			//  サイトマップ上での重複を許容するために、
			//  自動的にalias扱いとなることを考慮した正規表現。
			$path_type = 'javascript';
		}elseif( preg_match( '/^(?:alias[0-9]*\:)?data\:/i' , $path ) ) {
			//  data: から始まる場合
			//  サイトマップ上での重複を許容するために、
			//  自動的にalias扱いとなることを考慮した正規表現。
			$path_type = 'data';
		} else if( preg_match( '/^(?:alias[0-9]*\:)?\#/' , $path ) ) {
			//  # から始まる場合
			//  サイトマップ上での重複を許容するために、
			//  自動的にalias扱いとなることを考慮した正規表現。
			$path_type = 'anchor';
		} else if( preg_match( '/^(?:alias[0-9]*\:)?[a-zA-Z0-90-9]+\:\/\//' , $path ) ) {
			//  http:// などURLスキーマから始まる場合
			//  サイトマップ上での重複を許容するために、
			//  自動的にalias扱いとなることを考慮した正規表現。
			$path_type = 'full_url';
		} else if( preg_match( '/^alias[0-9]*\:/' , $path ) ) {
			//  alias:から始まる場合
			//  サイトマップデータ上でpathは一意である必要あるので、
			//  alias と : の間に、後から連番を降られる。
			//  このため、数字が含まれている場合を考慮した。(@tomk79)
			$path_type = 'alias';
		} else if( preg_match( '/\{(?:\$|\*)(?:[a-zA-Z0-9\_\-]*)\}/' , $path ) ) {
			//  {$xxxx} または {*xxxx} を含む場合(ダイナミックパス)
			$path_type = 'dynamic';
		} else if( preg_match( '/^\/\//' , $path ) ) {
			//  //から始まる場合
			$path_type = 'full_url';
		} else if( preg_match( '/^\//' , $path ) ) {
			//  /から始まる場合
			$path_type = 'normal';
		} else {
			//  どれにも当てはまらない場合はfalseを返す
			$path_type = false;
		}
		return $path_type;
	}//get_path_type()

	/**
	 * HTTP ヘッダを送信する。
	 *
	 * このメソッドは、 PHP の `header()` の代わりに使用します。
	 *
	 * PHP の `header()` は、SAPI に依存しており、 CLI SAPI 上ではヘッダーを送信できません。
	 * このメソッドは CLI SAPI の環境下でも、HTTPレスポンスヘッダーの処理をエミュレートするために用意されました。
	 * `$px->header()` を通して送信されたHTTPヘッダーは、 `$px->header_list()` から取得でき、 JSON形式の出力(コマンドラインオプション `-o json` を付加して実行)時、`header` に格納されます。
	 *
	 * ただし、 `$px->header()` は、次のヘッダーは扱いません。
	 *
	 * - HTTPステータスコード : 代わりに `$px->set_status()` を使用してください。 `$px->header()` にこのヘッダーを渡すと、 `$px->set_status()` にフォワードされます。
	 * - X-PXFW-ERROR : 代わりに `$px->error()` を使用してください。
	 * - X-PXFW-RELATEDLINK : 代わりに `$px->add_relatedlink()` を使用してください。
	 *
	 * @param string $string ヘッダー文字列
	 * @param bool $replace ヘッダが 前に送信された類似のヘッダを置換するか、または、同じ形式の二番目の ヘッダを追加するかどうかを指定する
	 * @return null 値を返しません。
	 */
	public function header($string, $replace = true){
		if( preg_match( '/^(.*?)\:(.*)$/', $string, $matched ) ){
			$key = @strtolower(trim($matched[1]));
			$val = @trim($matched[2]);
			switch($key){
				case 'content-type': $key = 'Content-type'; break;
			}
			if( $replace === true ){
				unset( $this->response_headers[$key] );
			}
			if( !@is_array($this->response_headers[$key]) ){
				$this->response_headers[$key] = array();
			}
			array_push( $this->response_headers[$key], $val );
		}elseif( preg_match( '/^HTTP\/([0-9\.]*?) ([0-9]{3}) (.*)$/', $string, $matched ) ){
			// ステータスコードは $px が管理しているので転送する
			$this->set_status($matched[2], $matched[3]);
		}

		@header($string, $replace);
		return;
	}

	/**
	 * 送信した (もしくは送信される予定の) レスポンスヘッダの一覧を返す。
	 *
	 * `$px->header()` から送られたHTTPレスポンスヘッダーの一覧を返します。
	 * このメソッドは CLI SAPI の環境下でも、HTTPレスポンスヘッダーの処理をエミュレートするために用意されました。
	 * 従って、実際に送信されたヘッダー情報を管理するものではありません。
	 *
	 * @return array ヘッダを、数値添字の配列で返します。
	 */
	public function header_list(){
		$rtn = array();
		foreach($this->response_headers as $key=>$vals){
			foreach($vals as $val){
				array_push($rtn, $key.': '.$val);
			}
		}
		return $rtn;
	}

	/**
	 * アプリケーションロックする。
	 *
	 * @param string $app_name アプリケーションロック名
	 * @param int $expire 有効時間(秒) (省略時: 60秒)
	 * @return bool ロック成功時に `true`、失敗時に `false` を返します。
	 */
	public function lock( $app_name, $expire = 60 ){
		$lockfilepath = $this->get_realpath_homedir().'_sys/ram/applock/'.urlencode($app_name).'.lock.txt';
		$timeout_limit = 5;

		if( !@is_dir( dirname( $lockfilepath ) ) ){
			$this->fs()->mkdir_r( dirname( $lockfilepath ) );
		}

		// PHPのFileStatusCacheをクリア
		clearstatcache();

		$i = 0;
		while( $this->is_locked( $app_name, $expire ) ){
			$i ++;
			if( $i >= $timeout_limit ){
				return false;
				break;
			}
			sleep(1);

			// PHPのFileStatusCacheをクリア
			clearstatcache();
		}
		$src = '';
		$src .= 'ProcessID='.getmypid()."\r\n";
		$src .= @date( 'Y-m-d H:i:s' , time() )."\r\n";
		$RTN = $this->fs()->save_file( $lockfilepath , $src );
		return	$RTN;
	} // lock()

	/**
	 * アプリケーションロックされているか確認する。
	 *
	 * @param string $app_name アプリケーションロック名
	 * @param int $expire 有効時間(秒) (省略時: 60秒)
	 * @return bool ロック中の場合に `true`、それ以外の場合に `false` を返します。
	 */
	public function is_locked( $app_name, $expire = 60 ){
		$lockfilepath = $this->get_realpath_homedir().'_sys/ram/applock/'.urlencode($app_name).'.lock.txt';
		$lockfile_expire = $expire;

		// PHPのFileStatusCacheをクリア
		clearstatcache();

		if( $this->fs()->is_file($lockfilepath) ){
			if( ( time() - filemtime($lockfilepath) ) > $lockfile_expire ){
				// 有効期限を過ぎていたら、ロックは成立する。
				return false;
			}
			return true;
		}
		return false;
	} // is_locked()

	/**
	 * アプリケーションロックを解除する。
	 *
	 * @param string $app_name アプリケーションロック名
	 * @return bool ロック解除成功時に `true`、失敗時に `false` を返します。
	 */
	public function unlock( $app_name ){
		$lockfilepath = $this->get_realpath_homedir().'_sys/ram/applock/'.urlencode($app_name).'.lock.txt';

		// PHPのFileStatusCacheをクリア
		clearstatcache();

		return @unlink( $lockfilepath );
	} // unlock()

	/**
	 * アプリケーションロックファイルの更新日を更新する。
	 *
	 * @param string $app_name アプリケーションロック名
	 * @return bool 成功時に `true`、失敗時に `false` を返します。
	 */
	public function touch_lockfile( $app_name ){
		$lockfilepath = $this->get_realpath_homedir().'_sys/ram/applock/'.urlencode($app_name).'.lock.txt';

		// PHPのFileStatusCacheをクリア
		clearstatcache();
		if( !is_file( $lockfilepath ) ){
			return false;
		}

		return touch( $lockfilepath );
	} // touch_lockfile()

	/**
	 * Pickles 2 の SVG ロゴソースを取得する。
	 * @param array $opt オプション
	 *

	 *
color

ロゴの色コード

	 *

	 * @return string Pickles 2 ロゴの SVG ソースコード
	 */
	public function get_pickles_logo_svg( $opt = array() ){
		$logo_color = '#f6f6f6';
		if( @strlen( $opt['color'] ) ){
			$logo_color = $opt['color'];
		}
		$svg_src = file_get_contents( __DIR__.'/files/svg/pickles2-logo.svg' );
		$svg_src = str_replace( '#000000', htmlspecialchars($logo_color), $svg_src );
		return $svg_src;
	}//get_pickles_logo_svg()

}


<?php
namespace Addons\Core\Http;

use Closure;
use Illuminate\Support\Str;
use Illuminate\Routing\Router as BaseRouter;

class Router extends BaseRouter {

	/**
	 * version for api
	 * @example $router->api('v1', function($router){ });
	 * @example this is equal: $router->group(['prefix' => 'v1', 'namespace' => 'Api\\V1'], $callback);
	 *
	 * @param  [type]  $version  the api's version
	 * @param  Closure $callback [description]
	 * @return [type]            [description]
	 */
	public function api($version, $second, $third = null)
	{
		if (func_num_args() == 2)
			list($version, $callback, $attributes) = array_merge(func_get_args(), [[]]);
		else
			list($version, $attributes, $callback) = func_get_args();
		$_attributes = ['prefix' => $version, 'namespace' => 'Api\\'.Str::studly($version)];
		$attributes = array_merge($_attributes, $attributes);
		$this->group($attributes, $callback);
	}

	/**
	 * crud routes
	 * include resource route, and data/export/print
	 *
	 * @example $this->crud('member', 'MemeberController');
	 * @example $this->crud(['member' => 'MemberController', 'role' => 'RoleController'])
	 *
	 * @param  [array|string] $route_name
	 * @param  [string] $controller NULL or controller
	 */
	public function crud($name, $controller = NULL)
	{
		$list = !is_array($name) ? [$name => $controller] : $name;
		foreach($list as $name => $controller)
		{
			$this->get($name.'/data', $controller.'@data');
			$this->post($name.'/data', $controller.'@data');
			$this->get($name.'/export', $controller.'@export');
			$this->get($name.'/print', $controller.'@print');
			$this->resource($name, $controller);
		}
	}

	/**
	 * actions
	 *
	 * @example $this->actions(['member' => ['index', 'edit', 'delete']]);
	 * @example $this->actions(['member' => ['index', 'e' => 'edit', 'd' => 'delete']]);
	 *
	 * @param  [string] $controllers
	 * @param  [string] $method
	 */
	public function actions(array $controllers, $method = 'any')
	{
		foreach ($controllers as $name => $actions)
		{
			$controller = Str::studly($name).'Controller';
			foreach($actions as $k => $action)
			{
				is_numeric($k) && $k = $action;
				$this->$method($name.($k == 'index' ? '' : '/'.$k), $controller.'@'.Str::camel($action));
			}
		}
	}

}


<?php

namespace Addons\Core\Middleware;

use Closure;
use Illuminate\Http\Response;

class CrossDomain
{
	/**
	 * Handle an incoming request.
	 *
	 * @param  \Illuminate\Http\Request  $request
	 * @param  \Closure  $next
	 * @return mixed
	 */
	public function handle($request, Closure $next, $domain = '*')
	{

		$response = strtoupper($request->method()) == 'OPTIONS' ? new Response() : $next($request);

		$response->headers->set('Access-Control-Allow-Origin', $domain);
		$response->headers->set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, DELETE, PUT, PATCH, HEAD');
		$response->headers->set('Access-Control-Allow-Credentials', 'true');
		$response->headers->set('Access-Control-Allow-Headers', 'accept, accept-encoding, authorization, content-type, dnt, origin, user-agent, x-csrftoken, x-requested-with, cookie');

		return $response;
	}
}


<?php
/**
 * PX Commands "encodingconverter"
 */
namespace picklesFramework2\processors\encodingconverter;

/**
 * PX Commands "encodingconverter"
 */
class encodingconverter{

	/**
	 * Picklesオブジェクト
	 */
	private $px;

	/**
	 * 出力エンコード
	 */
	private $output_encoding;

	/**
	 * 出力改行コード
	 */
	private $output_eol_coding;

	/**
	 * 変換対象の拡張子
	 */
	private $ext;

	/**
	 * Starting function
	 * @param object $px Picklesオブジェクト
	 * @param object $options オプション
	 *

	 *
$options->output_encoding

	 *
出力エンコーディング。mb_convert_encoding() が取り扱えるエンコード名で指定します。

	 *
$options->output_eol_coding

	 *
出力改行コード。文字列で、crlf、cr、または lf のいずれかで指定します。

	 *
$options->ext (v2.0.40 で追加)

	 *
対象とする拡張子。文字列 または 配列で複数指定できます。

	 *

	 *
	 * 省略時は、`$px->conf()` から、同名の値を参照します。
	 *
	 * どちらにも設定がない場合、encodingconverter は変換を実行しません。
	 */
	public static function exec( $px, $options = null ){
		$me = new self( $px, $options );
		$px->bowl()->each( array($me, 'apply') );
	}

	/**
	 * constructor
	 * @param object $px Picklesオブジェクト
	 * @param object $options オプション
	 *
	 * `exec()` が受け取った値が転送されます。
	 */
	public function __construct( $px, $options = null ){
		$this->px = $px;

		if( is_object($options) ){
			if( property_exists($options, 'output_encoding') && strlen($options->output_encoding) ){
				$this->output_encoding = $options->output_encoding;
			}
			if( property_exists($options, 'output_eol_coding') && strlen($options->output_eol_coding) ){
				$this->output_eol_coding = $options->output_eol_coding;
			}
			if( property_exists($options, 'ext') && $options->ext ){
				if( is_string($options->ext) ){
					$this->ext = array($options->ext);
				}elseif( is_array($options->ext) ){
					$this->ext = $options->ext;
				}
			}
		}

		// オプションなしの場合、$px->conf() を参照
		$conf = $this->px->conf();
		if( !strlen($this->output_encoding) && property_exists($conf, 'output_encoding') ){
			$this->output_encoding = $conf->output_encoding;
		}
		if( !strlen($this->output_eol_coding) && property_exists($conf, 'output_eol_coding') ){
			$this->output_eol_coding = $conf->output_eol_coding;
		}

		// それでも設定なしならデフォルト値
		if( !strlen($this->output_encoding) ){
			$this->output_encoding = 'UTF-8';
		}
		if( !strlen($this->output_eol_coding) ){
			$this->output_eol_coding = 'crlf';
		}

	}

	/**
	 * apply output filter
	 * @param string $src HTML, CSS, JavaScriptなどの出力コード
	 * @return string 加工後の出力コード
	 */
	public function apply($src){

		if( is_array( $this->ext ) ){
			// 対象の拡張子か調べる (pickles2/px-fw-2.x v2.0.40 で追加されたオプション)
			$ext = $this->px->get_path_proc_type( $this->px->req()->get_request_file_path() );
			$ext = strtolower($ext);
			$is_target = false;
			foreach($this->ext as $target_ext){
				if($ext == strtolower($target_ext)){
					$is_target = true;
					break;
				}
			}
			if(!$is_target){
				return $src;
			}
		}

		if( strlen( $this->output_encoding ) ){
			$output_encoding = $this->output_encoding;
			if( !strlen($output_encoding) ){ $output_encoding = 'UTF-8'; }

			//出力ソースの文字コード変換(HTML)
			$src = preg_replace('/\@charset\s+"[a-zA-Z0-9\_\-\.]+"\;/si','@charset "'.htmlspecialchars($output_encoding).'";',$src);
			$src = preg_replace('/\@charset\s+\'[a-zA-Z0-9\_\-\.]+\'\;/si','@charset \''.htmlspecialchars($output_encoding).'\';',$src);
			$src = preg_replace('/(<meta\s+charset\=")[a-zA-Z0-9\_\-\.]+("\s*\/?'.'>)/si','$1'.htmlspecialchars($output_encoding).'$2',$src);
			$src = preg_replace('/(<meta\s+http\-equiv\="Content-Type"\s+content\="[a-zA-Z0-9\_\-\+]+\/[a-zA-Z0-9\_\-\+]+\;\s*charset\=)[a-zA-Z0-9\_\-\.]+("\s*\/?'.'>)/si','$1'.htmlspecialchars($output_encoding).'$2',$src);
			$src = $this->px->convert_encoding( $src, $output_encoding );
		}

		if( strlen( $this->output_eol_coding ) ){
			//出力ソースの改行コード変換
			$eol_code = "\r\n";
			switch( strtolower( $this->output_eol_coding ) ){
				case 'cr':     $eol_code = "\r"; break;
				case 'lf':     $eol_code = "\n"; break;
				case 'crlf':
				default:       $eol_code = "\r\n"; break;
			}
			$src = preg_replace('/\r\n|\r|\n/si', $eol_code, $src);
		}

		return $src;
	}

}


<?php

namespace Clio\Widgets\Tables;

use ANSI\Color\Color;
use Clio\Clio;
use Clio\Styling\Style;

/**
 * This class is part of the Clio Open Source project, Clio.1happyplace.com
 * Copyright, Katie Ayres, katie@1happyplace.com
 *
 * Available through the MIT license
 * @license MIT
 *
 * The BarTable class generates a basic table, with additional features. There can be an overarching title along the top with no columns
 * and a single title.  Below that will be the column headers.  Both of these lines are filled with specified text and fill color.
 *
 */
class BarTable extends Table
{

    /**
     * The title along the top, one full bar with the singular text centered
     * @var null
     */
    private $title = null;

    /**
     * Whether to show the last empty line that is underlined
     * @var bool
     */
    private $showBottomLine = true;

    /**
     * The bar text color
     * @var null | Color
     */
    private $barText = null;

    /**
     * The bar fill color
     * @var null | Color
     */
    private $barFill = null;

    /**
     * The body (showing data) text color
     * @var null
     */
    private $bodyText = null;

    /**
     * The body (showing data) fill color
     * @var null | Color
     */
    private $bodyFill = null;

    /**
     * BarTable constructor.
     * @param Clio $clio
     * @param Column[] $columnDefinitions
     * @param null|string $title  - the title at the top (if desired)
     * @param bool $showBottomLine - whether to draw an empty line at the bottom with an underscore
     */
    public function __construct($clio, $columnDefinitions, $title = null, $showBottomLine = true)
    {
        // run the parent constructor
        parent::__construct($clio, $columnDefinitions);

        // save the title and bottom line flag
        $this->title = $title;
        $this->showBottomLine = $showBottomLine;

    }

    /**
     * Set the title text (if desired, if this is not specified, then the title bar is not shown)
     * @param $text
     */
    public function setTitle($text) {

        // save the title
        $this->title = $text;
    }

    /**
     * Set the colors, you can send in anything a Color object constructor can take (most everything)
     *
     * @param string | int | Color $barText
     * @param string | int | Color $barFill
     * @param string | int | Color $bodyText
     * @param string | int | Color $bodyFill
     */
    public function setColors($barText, $barFill, $bodyText, $bodyFill) {

        // save the bar text and fill
        $this->barText = new Color($barText);
        $this->barFill = new Color($barFill);

        // save the body text and fill
        $this->bodyText = new Color($bodyText);
        $this->bodyFill = new Color($bodyFill);

    }

    /**
     * Setter the for the last line with a underline
     *
     * @param boolean $showBottomLine
     */
    public function setShowBottomLine($showBottomLine)
    {
        $this->showBottomLine = $showBottomLine;
    }

    /**
     * Draw the table of data
     * @param $data
     */
    public function draw($data) {

        // if the data is sane, and the widths have been set (if not calculate them)
        if ($this->dataCheck($data)) {

            $barStyle = new Style();

            // set the colors to the bar text and fill
            $barStyle->setColors($this->barText, $this->barFill);

            $bodyStyle = new Style();

            // set the colors to the body text and fill
            $bodyStyle->setColors($this->bodyText, $this->bodyFill);

            // calculate the widths
            $this->calculateWidths($data);

            // calculate the total width
            $totalWidth = $this->calcTotalWidth();

            // if at least one of the columns has header text
            if ($this->hasHeader()) {

                $headers = $this->getHeaderText();

                // if a title was specified
                if ($this->title) {

                    // center the title
                    $justifiedText = $this->clio->justify($this->title, "center", $totalWidth);

                    // create a title style
                    $titleStyle = new Style();
                    $titleStyle->setBold()->setTextColor($this->bodyText);

                    // ask Clio to draw it
                    $this->clio->style($titleStyle)->display($justifiedText)->clear()->newLine();
                }

                // draw the row of cells
                $this->drawRow($headers, $barStyle);

            }

            // go through each row of data
            for ($row = 0; $row<count($data); ++$row) {

                // draw the row of cells
                $this->drawRow($data[$row], $bodyStyle);

            }

            // if the last line is desired
            if ($this->showBottomLine) {

                // now add an empty line
                $emptyLine = str_pad("",$totalWidth);

                // underline it to create a bottom line with the bar fill
                $underline = (new Style())->setUnderscore()->setColors($this->barFill, $this->bodyFill);

                // ask Clio to display it
                $this->clio->style($underline)->display($emptyLine);
            }

        }

    }

}


<?php

namespace Zeeml\Algorithms;

use Zeeml\Algorithms\Formulas\FormulasInterface;
use Zeeml\Algorithms\Formulas\FormulasResults;
use Zeeml\Algorithms\Exceptions\WrongUsageException;
use Zeeml\DataSet\DataSet;

/**
 * This class is used as a maestro for the formulas classes
 * It is an enhanced proxy to the formulas classes
 * Using this calculator, you can execute multiple formulas in a row :
 *
 * (new Calculator($dataSet))
 *     ->calculate(new Formula1())
 *     ->then(new Formula2())
 *     ->then(new Formula3())
 *     ...
 *     ->getResults()
 *     ->last()
 *
 * Class Calculator
 * @package Zeeml\Algorithms
 */
class Calculator
{
    protected $dataSet;

    /**
     * constructs takes in the dataSet on which to perform the calculus
     */
    public function __construct()
    {
        $this->results = new FormulasResults();
    }

    /**
     * if one of the formulas needs the dataSet, this function transmits the dataSet to the formulas
     * @param DataSet $dataSet
     * @return Calculator
     */
    public function using(DataSet $dataSet): Calculator
    {
        $this->dataSet = $dataSet;

        return $this;
    }

    /**
     * Starts a new calculus using the formula sent.
     * It resets the calculus by emptying the history of calls
     *
     * @param FormulasInterface $formula
     * @return Calculator
     */
    public function calculate(FormulasInterface $formula): Calculator
    {
        $formula
            ->using($this->dataSet)
            ->knowing($this->results)
            ->calculate();

        $this->results->save($formula);

        return $this;
    }

    /**
     * Chains the calculation to a previous one (previous calculate or previous then)
     * it is  a proxy for the calculate method used solely to propose a user friendly experience
     *
     * @param FormulasInterface $formula
     * @return Calculator
     * @throws WrongUsageException
     */
    public function then(FormulasInterface $formula): Calculator
    {
        return $this->calculate($formula);
    }

    /**
     * Gets the results of all the formulas called
     * @return FormulasResults
     */
    public function getResult(): FormulasResults
    {
        return $this->results;
    }
}


<?php declare(strict_types=1);
namespace Imatic\Bundle\ViewBundle\Templating\Helper\Layout;

use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Layout helper.
 *
 * @author Pavel Batecko <pavel.batecko@imatic.cz>
 */
class LayoutHelper
{
    /** @var RequestStack */
    private $requestStack;
    /** @var string|null */
    private $title;
    /** @var string|null */
    private $fullTitle;

    public function __construct(RequestStack $requestStack)
    {
        $this->requestStack = $requestStack;
    }

    /**
     * See if the layout should be enabled.
     *
     * @return bool
     */
    public function hasLayout()
    {
        $masterRequest = $this->requestStack->getMasterRequest();
        $currentRequest = $this->requestStack->getCurrentRequest();

        if (
            $masterRequest->isXmlHttpRequest()
            || 'off' === $masterRequest->get('_layout')
            || (
                $currentRequest !== $masterRequest
                && !$currentRequest->attributes->has('exception')
                && !$currentRequest->attributes->get('_layout', false)
            )
        ) {
            return false;
        }
        return true;
    }

    /**
     * See if the request comes from inside of a modal dialog.
     *
     * @return bool
     */
    public function isModalDialog()
    {
        return $this->requestStack->getMasterRequest()->headers->has('X-Modal-Dialog');
    }

    /**
     * Get title.
     *
     * @return string|null
     */
    public function getTitle()
    {
        return $this->title;
    }

    /**
     * Set title.
     *
     * @param string|null $title
     */
    public function setTitle($title)
    {
        $this->title = ('' !== $title ? $title : null);
    }

    /**
     * Check for title.
     *
     * @return bool
     */
    public function hasTitle()
    {
        return null !== $this->title;
    }

    /**
     * Get full title.
     *
     * @return string|null
     */
    public function getFullTitle()
    {
        return $this->fullTitle;
    }

    /**
     * Set full title.
     *
     * @param string|null $fullTitle
     */
    public function setFullTitle($fullTitle)
    {
        $this->fullTitle = ('' !== $fullTitle ? $fullTitle : null);
    }

    /**
     * Check for full title.
     *
     * @return bool
     */
    public function hasFullTitle()
    {
        return null !== $this->fullTitle;
    }
}


<?php

namespace Clio\Styling;

use ANSI\Color\Color;
use ANSI\Color\ColorInterface;
use ANSI\TerminalState;
use ANSI\TerminalStateInterface;

/**
 * This class is part of the Clio Open Source project, Clio.1happyplace.com
 * Copyright, Katie Ayres, katie@1happyplace.com
 *
 * Available through the MIT license
 * @license MIT
 *
 * Class Style stores the styling including:
 *      Bold            - whether or not to bold text
 *      Underscore      - whether or not to underscore text
 *      Text Color      - the text color (@see Color)
 *      Fill Color      - the fill color (@see Color)
 *
 * Bold and underscore have three states:
 *      null - it will have no effect
 *      false - it is specifically turned off
 *      true - it is specifically turned on
 *
 * Styles can then pancake on one another, so if you override a styling, but
 * a property is set to null, it won't change the current styling, if it is
 * set to false, then it will be specifically turned off.  This is true of
 * colors as well, if they are set to null.
 *
 */
class Style implements StyleInterface
{

    /**
     * Current state of bold (on or off)
     * var bool | null - if null, it means to have no effect on the current state (particularly in an override)
     */
    protected $bold = null;

    /**
     * Current state of underscoring (on or off)
     * @var bool | null - if null, it means to have no effect on the current state (particularly in an override)
     */
    protected $underscore = null;

    /**
     * Store the current text color
     * @var Color | null
     */
    protected $textColor = null;

    /**
     * Store the current fill color
     * @var Color |  null
     */
    protected $fillColor = null;

    /**
     * Style constructor.
     * @param null $bold
     * @param null $underscore
     * @param ColorInterface | null $textColor
     * @param ColorInterface | null $fillColor
     *
     */
    public function __construct($bold = null, $underscore = null, ColorInterface $textColor = null, ColorInterface $fillColor = null)
    {
        // set all the properties
        $this->bold = $bold;
        $this->underscore = $underscore;
        $this->textColor = $textColor;
        $this->fillColor = $fillColor;

        // chaining
        return $this;
    }

    /**
     * Initialize a style to the state of a terminal
     *
     * @param TerminalStateInterface $state
     * @return $this
     */
    public function initialize(TerminalStateInterface $state) {

        // both bold and underscore will have a value of true or false (not undefined
        $this->setBold($state->isBold());
        $this->setUnderscore($state->isUnderscore());

        // for colors, if the color is not valid, then it will stay undefined in this context

        // get the text color
        $textColor = $state->getTextColor();

        // if the text color is valid
        if ($textColor->isValid()) {

            // set it
            $this->setTextColor($textColor);

        } else {

            // the text color is not valid, null out the text color
            $this->setTextColor(null);
        }

        // get the fill color
        $fillColor = $state->getFillColor();

        // if the fill color is valid
        if ($fillColor->isValid()) {

            // set it
            $this->setFillColor($fillColor);

        } else {

            // the fill color is not valid, null out the fill color
            $this->setFillColor(null);
        }

        // chaining
        return $this;

    }

    /**
     * Return a Terminal State object based on the styling settings
     * NOTE: The styling interface and terminal interfaces are similar
     *       in structure, but not how they work, so they have been
     *       kept separate. Terminal states are concrete, bold and
     *       underscore are on or off, colors are instantiated, but
     *       might be no color.  Styles are desired states and therefore
     *       have a third state which is undefined, which means let another
     *       style define this state (e.g. if bold is undefined, but another
     *       style in a stack turns bold on, then it will be on.
     *
     * @return TerminalState
     */
    public function getState() {

        // build the returned object
        $ret = new TerminalState();

        // only set the bold value if it is true, it is false if it
        // is undefined (null) or false
        if ($this->bold === true) {

            // turn on bolding
            $ret->setBold(true);

        }

        // only set the underscore value if it is true, it is false if it
        // is undefined (null) or false
        if ($this->underscore === true) {

            // turn on underscoring
            $ret->setUnderscore(true);

        }

        // if the text color is not null
        if ($this->textColor) {

            // if it is valid
            if ($this->textColor->isValid()) {

                // set the text color
                $ret->setTextColor($this->textColor);
            }
        }

        // if the fill color is not null
        if ($this->fillColor) {

            // if it is valid
            if ($this->fillColor->isValid()) {

                // set the fill color
                $ret->setFillColor($this->fillColor);
            }
        }

        // return the terminal state
        return $ret;

    }

    /**
     * Override a style, if anything is set to null, then don't change the value of the recipient object
     *
     * @param StyleInterface $style
     * @return $this
     *
     */
    public function overrideMembersTo(StyleInterface $style) {

        // text color
        $styleTextColor = $style->getTextColor();

        // only change if there is a text color set
        if ($styleTextColor) {

            // clone the text color
            $this->textColor = clone $styleTextColor;
        }

        // fill color
        $styleFillColor = $style->getFillColor();

        // only change if there is a fill color set
        if ($styleFillColor) {

            // clone the fill color
            $this->fillColor = clone $styleFillColor;
        }

        // font styling

        // get the overriding bold
        $bold = $style->getBold();

        // only change the bold, if it is set to something
        if (!is_null($bold)) {

            // save the new bold state
            self::setBold($bold);

        }

        // get the overriding underscore
        $underscore = $style->getUnderscore();

        // only change the underscore, if it is set to something
        if (!is_null($underscore)) {

            // save the new underscore state
            self::setUnderscore($underscore);
        }

        // chaining
        return $this;
    }

    /**
     * Clear out all formatting
     *
     * @return $this
     */
    public function clearStyling() {

        // reset the colors to null
        $this->textColor = $this->fillColor = null;

        // reset the underscore and bold to false
        $this->underscore = $this->bold = null;

        // chaining
        return $this;
    }

    /////////////////////////////////////////////////////////////////////////////////////////
    //                                     font styling                                    //
    /////////////////////////////////////////////////////////////////////////////////////////

    /**
     * Setter for bold
     *
     * @param boolean $on
     * @return $this
     */
    public function setBold($on = true) {

        // save it
        $this->bold = $on;

        // chaining
        return $this;
    }

    /**
     * Clear any bold styling, it will have no effect, nor override
     */
    public function clearBoldStyling() {

        // clear out the bold
        $this->bold = null;

        // chaining
        return $this;
    }

    /**
     * Getter for bold
     *
     * @return boolean - whether bold is turned on or off
     */
    public function getBold() {

        // return the value
        return $this->bold;
    }

    /**
     * Whether bolding is on
     *
     * @return boolean
     */
    public function isBoldOn() {

        // return whether bolding is currently on
        return $this->bold === true;

    }

    /**
     * Set the underscore setting
     *
     * @param boolean $on
     * @return $this
     */
    public function setUnderscore($on = true) {

        // save it
        $this->underscore = $on;

        // chaining
        return $this;
    }

    /**
     * Clear any underscore styling, it will have no effect, nor override
     */
    public function clearUnderscoreStyling() {

        // clear out the bold
        $this->underscore = null;

        // chaining
        return $this;
    }

    /**
     * Getter for the underscore setting
     *
     * @return boolean - whether underscore is turned on
     */
    public function getUnderscore() {

        // return the value
        return $this->underscore;

    }

    /**
     * Whether the underscore is turned on
     *
     * @return boolean
     */
    public function isUnderscoreOn() {

        // return whether underscoring is currently on
        return $this->underscore === true;

    }

    /**
     * Turn off both underscore and bold
     *
     * @return $this
     */
    public function normal() {

        // turn off bold
        $this->bold = false;

        // turn off underscore
        $this->underscore = false;

        // chaining
        return $this;

    }

    /////////////////////////////////////////////////////////////////////////////////////////
    //                                       Colors                                        //
    /////////////////////////////////////////////////////////////////////////////////////////

    /**
     * Set the text color
     *
     * @param Color | string | int | array $color
     *      - Color - a Color object
     *      - String - a W3C color index name "darkblue"
     *      - integer - a number between 0-255 for the XTerm escape code
     *      - array - RGB values in the format [R,G,B]
     * @return $this
     */
    public function setTextColor($color) {

        // if it is null, just remember null
        if (is_null($color)) {

            // undefined
            $this->textColor = null;

        // it is an instance of Color
        } elseif ($color instanceof Color) {

            // clone it
            $this->textColor = clone $color;

        // it is some other type
        } else {

            // let the Color object sort it out
            $this->textColor = new Color($color);

        }

        // chaining
        return $this;

    }

    /**
     * Getter for the text color
     *
     * @return ColorInterface | null - null if it has been cleared, otherwise the Color object stored
     */
    public function getTextColor() {

        // return it
        return $this->textColor;
    }

    /**
     * Getter for the text color name
     *
     * @return String | null - null if no color defined, or the name
     */
    public function getTextColorName() {

        // if there is a text color defined
        if ($this->textColor) {

            // return the name
            return $this->textColor->getName();

        // there is not text color defined
        } else {

            // return nothing
            return null;

        }

    }

    /**
     * Whether a text color has been defined, or has been cleared
     *
     * @return bool
     */
    public function hasTextColor() {

        // return true if it is not null
        return !is_null($this->textColor);

    }

    /**
     * Clear out the text color
     *
     * @return $this
     */
    public function clearTextColor() {

        // clear out the text color
        $this->textColor = null;

        // chaining
        return $this;
    }

    /**
     * Set the fill color
     *
     * @param Color | string | int | array $color
     *      - Color - a Color object
     *      - string - a W3C color index name "darkblue"
     *      - integer - a number between 0-255 for the XTerm escape code
     *      - array - RGB values in the format [R,G,B]
     * @return $this
     */
    public function setFillColor($color) {

        // if it is null, just remember null
        if (is_null($color)) {

            // undefined
            $this->fillColor = null;

        // if it is an instance of a color
        } elseif ($color instanceof Color) {

            // clone it
            $this->fillColor = clone $color;

        } else
        {

            // it is something else, let the Color object sort it out
            $this->fillColor = new Color($color);

        }

        // chaining
        return $this;
    }

    /**
     * Getter for fill color
     *
     * @return null | ColorInterface - null if it is cleared, otherwise the color object stored
     */
    public function getFillColor() {

        // return fill color
        return $this->fillColor;

    }

    /**
     * Getter for the text color name
     *
     * @return string | null - null if no color defined, or the name
     */
    public function getFillColorName() {

        // if there is a text color defined
        if ($this->fillColor) {

            // return the name
            return $this->fillColor->getName();

            // there is not text color defined
        } else {

            // return nothing
            return null;

        }

    }

    /**
     * Whether a fill color has been defined
     *
     * @return bool
     */
    public function hasFillColor() {

        // return true if the fill color is not null
        return !is_null($this->fillColor);
    }

    /**
     * Clear out the text color
     *
     * @return $this
     */
    public function clearFillColor() {

        // clear out the fill color
        $this->fillColor = null;

        // chaining
        return $this;
    }

    /**
     * Set both text and fill colors
     *
     * @param Color | string | int | array $textColor
     * @param Color | string | int | array $fillColor
     *      - String - a W3C color index name "darkblue"
     *      - integer - a number between 0-255 for the XTerm escape code
     *      - array - RGB values in the format [R,G,B]
     * @return $this
     */
    public function setColors($textColor, $fillColor) {

        // set the text color
        self::setTextColor($textColor);

        // set the fill color
        self::setFillColor($fillColor);

        // chaining
        return $this;
    }

    /**
     * Switch the text and fill colors
     *
     * @return $this
     */
    public function reverseColors() {

        // switch them
        $text = $this->textColor;
        $this->textColor = $this->fillColor;
        $this->fillColor = $text;

        return $this;
    }

}