<?php
namespace YS;
/*************************************************************
 *
 * Copyright (c) 2024 ysrock Co., Ltd.	<info@ysrock.co.jp>
 * Copyright (c) 2024 Yasuo Sugano	<sugano@ysrock.co.jp>
 *
 * Version	: 1.0.2
 * Update	  : 2024.09.19
 *
 *************************************************************/
class Commons{
  /*
   *	<head>内で使用する共通タグ
   *		echoHead( array $JsCssFiles );
   *
   *	メインメニューを表示する
   *		echoNavi( string $url );
   *
   *	再帰的なmkdir
   *		mkdir( string $targetDir, string $limitDir ): bool
   *
   *	アクセス制限をかける
   *		restrictAccess( array $searchContentsRoot, array $requiredAuthorityForThisDir );
   *
   *	このコンテンツのルートのパスを取得する
   *		searchContentsRoot( string $uri, string $authority ): string
   *
   *	指定したパスにアクセスするための必要権限を取得する
   *		isAllowForThisDir( class $db, string $href ): int | false
   *
   *	二つのURLが何階層一致しているか
   *		dirpos( string $url, string $match): int | false
   *
   *	UUIDv4の生成
   *		getUuidV4(): string
   */


  /************************************************************
   *
   * <head>内で使用する共通タグ
   *
   *	使い方
   *		echoHead($JsCssFiles);
   *
   *	パラメータ
   *		$JsCssFiles
   *		（配列）JavaScriptもしくはスタイルシート
   *			public_html直下からの絶対パス
   *
   *	戻り値
   *		なし
   *
   ************************************************************/
  public function variableHead($JsCssFiles=array()){
    $html  = "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no\">\n";
    $html .= "<meta name=\"format-detection\" content=\"telephone=no\">\n";
    // faviconを表示
    if(file_exists($_SERVER['DOCUMENT_ROOT']."favicon.ico") || file_exists($_SERVER['DOCUMENT_ROOT']."favicon.png")){
      $html .= "<link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/vnd.microsoft.icon\">\n";
      if(file_exists($_SERVER['DOCUMENT_ROOT']."favicon.ico")) $html .= "<link rel=\"icon\" href=\"/favicon.ico\" type=\"image/vnd.microsoft.icon\">\n";
      if(file_exists($_SERVER['DOCUMENT_ROOT']."favicon.png")) $html .= "<link rel=\"icon\" type=\"image/png\" href=\"/favicon.png\" sizes=\"64x64\">\n";
    };// END faviconを表示

    // スタイルシートの読み込み
    $html .= "<link rel=\"stylesheet\" href=\"/commons/libraries/jquery.ysrock/latest/jquery.ysrock.css?".rand()."\">\n";
    $css = array();
    $css[] = "/commons/normalize.css";
    $css[] = "/commons/commons.css";
    $css[] = "/commons/fonts/ysrock.css";
    if(defined('THEME')) $css[] = "/commons/themes/".THEME."/commons.css";
    $css[] = str_replace(".html", ".css", $_SERVER['SCRIPT_NAME']);
    foreach($JsCssFiles as $f){
      if(substr($f, -3) !== "css") continue;
      $css[] = $f;
    };
    foreach(array_unique($css) as $f){
      if(strpos($f, "/commons/libraries/") === 0) {
        $html .= "<link rel=\"stylesheet\" href=\"{$f}?\">\n";
        continue;
      };
      $minify = str_replace('.css', '.min.css', $f);
      $ROOT = strpos($f, "/") === 0 ? $_SERVER['DOCUMENT_ROOT'] : "";
      if(file_exists($ROOT.$minify)) $f = $minify;
      else if(!file_exists($ROOT.$f)) continue;
      $html .= "<link rel=\"stylesheet\" href=\"{$f}?".filemtime($ROOT.$f)."\">\n";
    };// END スタイルシートの読み込み

    // JavaScriptの読み込み
    $html .= "<script charset=\"UTF-8\" src=\"/commons/libraries/jquery.js\"></script>\n";
    $html .= "<script charset=\"UTF-8\" src=\"/commons/libraries/jquery-migrate.js\"></script>\n";
    $html .= "<script charset=\"UTF-8\" src=\"/commons/libraries/jquery.cookie.js\"></script>\n";
    $html .= "<script charset=\"UTF-8\" src=\"/commons/libraries/jquery.ysrock/latest/jquery.ysrock.js?".rand()."\"></script>\n";
    $js = array();
    $js[] = "/commons/commons.js";
    if(defined('THEME')) $js[] = "/commons/themes/".THEME."/commons.js";
    $js[] = str_replace(".html", ".js", $_SERVER['SCRIPT_NAME']);
    foreach($JsCssFiles as $f){
      if(substr($f, -2) !== "js") continue;
      $js[] = $f;
    };
    foreach(array_unique($js) as $f){
      if(strpos($f, "/commons/libraries/") === 0) {
        $html .= "<script src=\"{$f}\"></script>\n";
        continue;
      };
      $minify = str_replace('.js', '.min.js', $f);
      $ROOT = strpos($f, "/") === 0 ? $_SERVER['DOCUMENT_ROOT'] : "";
      if(file_exists($ROOT.$minify)) $f = $minify;
      else if(!file_exists($ROOT.$f)) continue;
      $html .= "<script charset=\"UTF-8\" src=\"{$f}?".filemtime($ROOT.$f)."\"></script>\n";
    };
    return $html;
  }
  public function echoHead(...$args){
    echo $this->variableHead(...$args);
  }
  /********** END <head>内で使用する共通タグ ********************/



  /************************************************************
   *
   * メインメニューを表示する
   *
   *	使い方
   *		echoNavi( $url );
   *
   *	パラメータ
   *		$url
   *		（文字列）該当リンクをアクティブ化
   *			メニュー内のhrefとURLがヒットすれば自動的にアクティブ化
   *			URLではヒットさせられない場合にヒットさせたい$urlを指定する
   *
   *	戻り値
   *		なし
   *
   ************************************************************/
  public function variableNavi( $url="" ){
    // DBが未定義
    if(!defined('DB_HOST') || !defined('DB_NAME') || !defined('DB_USER') || !defined('DB_PASS') || !defined('DB_PREFIX')) return "";

    // メニュー配列
    $nav = array();
    // データベースに接続
    $db = new DB(DB_HOST, DB_NAME, DB_USER, DB_PASS);
    $db->Connect();
    // メニュー配列を取得
    $nav = $this->select__master_nav($db, null);
    // データベースから切断
    $db->Disconnect();

    // 引数が未指定の場合、現在のURLを代入する
    if(!$url) $url = $_SERVER['REQUEST_URI'];
    // ディレクトリパス
    $dir = substr($url, 0, strrpos($url, "/"));

    // 多次元配列からHTMLを作成
    return " <nav>".$this->tree_nav($dir, $nav)."</nav>\n";
  }
  public function echoNavi(...$args){
    echo $this->variableNavi(...$args);
  }

  /***** メニュー配列の取得 *****/
  public function select__master_nav($db, $parent){
    $query  = "SELECT `MN`.`id`";
    $query .= "      ,`MN`.`text`";
    $query .= "      ,`MN`.`icon`";
    $query .= "      ,`MN`.`href`";
    $query .= "  FROM `".DB_PREFIX."master_nav` AS `MN`";
    $query .= "  LEFT OUTER JOIN `".DB_PREFIX."master_authority` AS `t1`";
    $query .= "               ON `t1`.`name` = `MN`.`authority`";
    $query .= " WHERE `MN`.`parent` " . (is_null($parent) ? "IS " : "=") . ":parent";
    $query .= "   AND CASE";
    $query .= "         WHEN `MN`.`authority` IS NULL THEN true";
    $query .= "         WHEN `t1`.`priority` >=:authority THEN true";
    $query .= "         ELSE false";
    $query .= "       END";
    $query .= " ORDER BY `MN`.`priority` ASC";
    $query .= ";";
    $bind = array(
      'parent' => $parent
     ,'authority' => isset($_REQUEST['uuid']) && isset($_SESSION[$_REQUEST['uuid']]['authority_priority']) ? $_SESSION[$_REQUEST['uuid']]['authority_priority'] : ""
    );
    $stmt = $db->Query($query, $bind, array('line'=>__LINE__));
    $result = array();
    while($fetch=$stmt->fetch(PDO::FETCH_ASSOC)){
      // アンカー属性
      $fetch['attr'] = $this->select__master_nav_attr($db, $fetch['id']);
      // 子階層
      $child = $this->select__master_nav($db, $fetch['id']);
      if(count($child) > 0) $fetch['child'] = $child;
      // 配列に追加
      $result[] = $fetch;
    };
    return $result;
  }
  /***** END メニュー配列の取得 *****/

  /***** アンカー属性を取得 *****/
  public function select__master_nav_attr($db, $nav){
    $query  = "SELECT `attr`";
    $query .= "      ,`values`";
    $query .= "  FROM `".DB_PREFIX."master_nav_attr`";
    $query .= " WHERE `nav` =:nav";
    $query .= ";";
    $bind = array(
      'nav' => $nav
    );
    $stmt = $db->Query($query, $bind, array('line'=>__LINE__));
    $result = array();
    while($fetch=$stmt->fetch(PDO::FETCH_ASSOC)){
      if(!isset($result[ $fetch['attr'] ])) $result[ $fetch['attr'] ] = array();
      $result[ $fetch['attr'] ][] = $fetch['values'];
    };
    return $result;
  }
  /***** END アンカー属性を取得 *****/

  /***** 多次元配列からHTMLを作成 *****/
  public function tree_nav(&$dir, $args){
    $html  = "<ul>";
    foreach($args as $array){
      // テキストが無い場合は処理しない
      if(!$array['text']) continue;
      // リンク先を定義する
      if(!isset($array['href']) || !$array['href']) $array['href'] = "#";
      // クラスを定義する
      if(!isset($array['attr']['class'])) $array['attr']['class'] = array();
      // 同じディレクトリ
      if($dir == substr($array['href'], 0, strrpos($array['href'], "/"))) $array['attr']['class'][] = "blue";

      // アイコン
      if(isset($array['icon']) && $array['icon']) $array['text'] = "<span class=\"{$array['icon']}\"></span>" . $array['text'];
      // リンク先
      $anchor_attr = " href=\"{$array['href']}\"";
      // アンカー属性
      foreach($array['attr'] as $key=>$values) $anchor_attr .= " {$key}=\"".implode(" ", $values)."\"";
      // HTMLを作成
      $html .= "<li>";
      // 子メニュー
      if(isset($array['child'])) $html .= $this->tree_nav($dir, $array['child']);
      // アンカーリンク
      $html .= "<a{$anchor_attr}>{$array['text']}</a>";
      $html .= "</li>";
    };// END foreach
    $html .= "</ul>";
    return $html;
  }
  /***** END 多次元配列からHTMLを作成 *****/

  /********** END メインメニューを表示する ********************/



  /*************************************************************
   *
   * 再帰的なmkdir
   *
   *	使い方
   *		mkdir($targetDir, $limitDir);
   *
   *	パラメーター
   *		$targetDir
   *		（文字列）作成するディレクトリパス
   *		$limitDir
   *		（文字列）これ以上上位のディレクトリは作成しない
   *
   *	戻り値
   *		（真偽値）
   *
   *************************************************************/
  public function mkdir($targetDir="", $limitDir=""){
    $arr_targetDir = explode("/", $targetDir);
    $int_limitDir = strlen($limitDir);
    $tmpDir = "";
    foreach($arr_targetDir as $d){
      if(!$d) continue;
      $tmpDir .= "/{$d}";
      if($int_limitDir >= strlen($tmpDir)) continue;
      if(is_dir($tmpDir)) continue;
      mkdir($tmpDir, 0775);
      chmod($tmpDir, 0775);
    };
    return is_dir($targetDir);
  }
  /********** END 再帰的なmkdir ********************/



  /*************************************************************
   *
   * アクセス制限をかける
   *
   *	使い方
   *		restrictAccess($searchContentsRoot, $requiredAuthorityForThisDir);
   *
   *	パラメーター
   *		$searchContentsRoot
   *		（配列）引数
   *		$requiredAuthorityForThisDir
   *		（配列）引数
   *
   *	戻り値
   *		権限が足りなければエラー画面に推移
   *
   *************************************************************/
  public function restrictAccess($searchContentsRoot, $requiredAuthorityForThisDir){
    // 自身の権限
    $authority_priority = isset($_REQUEST['uuid']) && isset($_SESSION[$_REQUEST['uuid']]['authority_priority']) ? $_SESSION[$_REQUEST['uuid']]['authority_priority'] : null;
    // このコンテンツのルートのパスを取得する
    $requiredAuthorityForThisDir[1] = $this->searchContentsRoot(...$searchContentsRoot);
    // アクセス権限があるか調べる
    $required_priority = $this->requiredAuthorityForThisDir(...$requiredAuthorityForThisDir);
    // 権限がある場合はこれ以上処理しない
    if($authority_priority <= $required_priority) return;

    // Ajaxの時はエラーを返す
    if(isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') return 'アクセス権がありません';
    // エラー画面があるとき
    if(is_file("{$_SERVER['DOCUMENT_ROOT']}/login/message/index.html")){
      // 変数に代入
      $GLOBALS['message'] = 'アクセス権がありません';
      // 表示する
      require_once "{$_SERVER['DOCUMENT_ROOT']}/login/message/index.html";
    };// END 
    // ルートへ移動
    header('Location: /');
  }
  /********** END アクセス制限をかける ********************/



  /*************************************************************
   *
   * このコンテンツのルートのパスを取得する
   *
   *	使い方
   *		searchContentsRoot($uri, $authority);
   *
   *	パラメーター
   *		$uri
   *		（文字列）URI
   *		$authority
   *		（文字列）権限
   *
   *	戻り値
   *		（文字列）メニューに登録されているであろうパス
   *
   *************************************************************/
  public function searchContentsRoot($uri="", $authority=""){
    // URIの指定が無い
    if(!$uri) $uri = $_SERVER['REQUEST_URI'];
    // 権限の指定が無い
    if(!$authority) $authority = isset($_REQUEST['uuid']) && isset($_SESSION[$_REQUEST['uuid']]['authority']) ? $_SESSION[$_REQUEST['uuid']]['authority'] : NULL;
    // URIからディレクトリ部分だけを取得する
    $dir = substr($uri, 1, strrpos($uri, "/"));
    // ディレクトリごとに配列に収める
    $dirs = explode("/", $dir);

    // パス
    $path = "";
    // ルート階層から順番に処理する
    foreach($dirs as $d){
      // パスに追加する
      $path .= "/{$d}";
      // index.htmlがなければ終了
      if(!is_file("{$_SERVER['DOCUMENT_ROOT']}{$uri}{$path}/index.html")) break;
    };// END foreach ルート階層から順番に処理する

    return $path."/";
  }
  /********** END このコンテンツのルートのパスを取得する ********************/



  /*************************************************************
   *
   * 指定したパスにアクセスするための必要権限を取得する
   *
   *	使い方
   *		isAllowForThisDir($db, $href);
   *
   *	パラメーター
   *		$db
   *		（クラス）new DB()
   *		$href
   *		（文字列）メニューに登録されているパス
   *
   *	戻り値
   *		（数値）権限の優先順位
   *			falseの場合、メニューに存在しなかった
   *
   *************************************************************/
  public function requiredAuthorityForThisDir($db, $href=""){
    $query  = "SELECT `MA`.`priority`";
    $query .= "  FROM `".DB_PREFIX."master_nav` AS `MN`";
    $query .= "  LEFT OUTER JOIN `".DB_PREFIX."master_authority` AS `MA`";
    $query .= "               ON `MA`.`name` = `MN`.`authority`";
    $query .= " WHERE `MN`.`href`=:href";
    $query .= " ORDER BY `MN`.`id` ASC";
    $query .= " LIMIT 0,1;";
    $bind = array(
      'href' => $href
    );
    $stmt = $db->Query($query, $bind, array('line'=>__LINE__));
    $fetch = $stmt->fetch(PDO::FETCH_ASSOC);
    return isset($fetch['priority']) ? $fetch['priority'] : false;
  }
  /********** END 指定したパスにアクセスするための必要権限を取得する ********************/



  /*************************************************************
   *
   * 二つのURLが何階層一致しているか
   *
   *	使い方
   *		dirpos($url, $match);
   *
   *	パラメーター
   *		$url
   *		（文字列）URL
   *		$match
   *		（文字列）$urlより上層と思われるURL
   *
   *	戻り値
   *		（数値）一致した階層数
   *		（真偽値）完全一致の場合、真を返す
   *
   *************************************************************/
  public function dirpos($url="", $match=""){
    // パラメーターが不正
    if(!$url || !$match) return 0;
    // 完全一致の場合、真を返す
    if($url == $match) return true;
    // ディレクトリを配列に格納する
    $urls = explode("/", $url);
    $matches = explode("/", $match);
    // 戻り値
    $ret = 0;
    // 一致する階層数を調べる
    for($i=0, $len=count($matches); $i<$len; $i++){
      // 階層が存在しない
      if(!isset($urls[$i]) || !isset($matches)) return $ret;
      // ディレクトリが不一致
      if($urls[$i] != $matches[$i]) return $ret;
      $ret++;
    };// END for 一致する階層数を調べる
    return $ret;
  }
  /********** END 二つのURLが何階層一致しているか ********************/



  /*************************************************************
   *
   * UUIDv4の生成
   *
   *	使い方
   *		getUuidV4();
   *
   *	パラメーター
   *
   *	戻り値
   *		（文字列）
   *
   *************************************************************/
  public function getUuidV4(){
    // パターン
    $pattern = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
    // 文字列を配列に変換
    $chars = str_split($pattern);
    // 一文字ずつ処理する
    foreach($chars as $idx => $char){
      // 置換：0-F
      if($char === "x") $chars[$idx] = dechex( random_int(0, 15) );
      // 置換：8-B
      else if($char === "y") $chars[$idx] = dechex( random_int(8, 11) );
    };// END foreach 一文字ずつ処理する

    // 配列を文字列に変換
    return implode("", $chars);
  }
  /********** END UUIDv4の生成 ********************/

};
/********** END Commons ********************/
?>