<?php
namespace MyProject;
//ini_set('display_errors', '1');
$MY_VER_MPDCONFIG=basename(__FILE__,"")." 1.0 (24.08.2025)";
/***************************************************************************************
	mpdconfig mpd.conf Make audio_output 
	http://{Host}/mpdconfig.php{?mpd=on}

	 mpd=on  : Load TaskServer I/F mpdweb Seriesとして動作
     引数なし: sudo Passwordを入力し動作 WebServer下のmpdを対象
               単独で動作する。他のLibraryを使用しない
	mpdweb Series : Libralyがあれば,mpdweb Series Check : ($G_MPDWEB_MODE=1)

	※AJAXは時期シリーズに使用するように構築した。
************************************************************************************* */
if ($_SERVER["REQUEST_METHOD"] === "POST") {				// Post時:$G_MPDWEB_MODEを復元
	 $G_MPDWEB_MODE = $_POST["MPDWEB_MODE"] ?? '';
	 }
else {
	 $G_MPDWEB_MODE=0;
	 }
if(((isset($_GET["mpd"]))&&($_GET["mpd"]=="on"))||($G_MPDWEB_MODE==1)) {	// 引数?mpd=on || Post時:$G_MPDWEB_MODEを復元場合にTaskServer使用
	$require_ses=dirname(__FILE__).'/mpdcmnses.lib.php';				// Mpd Session id Library ※ mpdtask.if.lib.phpより先にrequireすること
	if  (file_exists($require_ses)) { 
		require_once($require_ses);										// Mpd mpdcmnses.lib.phpの存在確認&require
		if  (file_exists(dirname(__FILE__)."/mpdtask.if.lib.php")) {	// Mpd TaskServer i/fLibrary mpdtask.phpの存在確認
			$require_task=dirname(__FILE__)."/mpdtask.if.lib.php";
			ob_start();													// StanderdOutをセット
			require_once($require_task);								// Mpd XML Library mpd{module}.lib.php
			ob_end_clean();
			$SESSION_CONF=GetSessionConfig();							// mpdtask.if.lib.php:$XML_MPDLIBを判定する(GetEnvCommon経由)
			ArgSessionStart($SESSION_CONF);								// Start Session : Used mpdcmnses.lib.php
			$XML_CONF=NULL;
			if (GetEnvTaskCommon() ===false) { } else {					// mpdtask.if.lib.php mpdtask.xml Get&Edit
				$G_MPDWEB_MODE=1;
				}
			}
		}
	}
/**************************************************************************************
	Post Porc : Post Request PHP
***************************************************************************************
	Post形式
	 $_POST[P]:Module , $_POST[{Param}]:ParameterString
------------------------------------------------------------------------------------ */
if ($_SERVER["REQUEST_METHOD"] === "POST") {
	GetMpdConfig();
	$G_MPDWEB_MODE = $_POST["MPDWEB_MODE"] ?? '';
	$p = $_POST["P"] ?? '';
	if($p=="Renew") {
		 PostRenew();
		 }
	else if($p=="Update") {
		 PostUpdate();
		 }
	else if($p=="Restart") {
		 PostRestart();
		 }
    exit;
	}

/* ------------------------------------------------------------------------------------
	PostRenew() : ALSA Device Display Renew
------------------------------------------------------------------------------------ */
function PostRenew() {
	print "mpdconf Post:Renew Start\n";
	print '<html id=idAUDIODEV>';			// Update後のDisplay
	DispMpdDevice();
	print '</html>';
	print "mpdconf Post:Renew End\n";
	}

/* ------------------------------------------------------------------------------------
	PostUpdate() : ALSA Device Update
	$_POST["ARY"] : JSON形式 $Sortkey => {ALSA Device}
	                        {ALSA Device}: mod.confnの形式
				            $Sortkey     : SortKind|"Kind"|"H/WDevice"|"Name"
	$_POST["Pass"]: sudo password
------------------------------------------------------------------------------------ */
function PostUpdate() {
	print "mpdconf Post:Update Start\n";
    $ary = $_POST["ARY"] ?? '';
    $pass = $_POST["Pass"] ?? '';
	$arydev = json_decode($ary);
	if ($arydev === null) {
		echo "mpdconf Post:Udate JSON Decode Error\n";
    	return;
		};
	$ary = (array)$arydev;
	ksort($ary);							 // KeySort
	$str = implode("\n",$ary);
//	echo $str;
	_PostUpdateShell($str,$pass);

	print '<html id=idAUDIODEV>';			// Update後のDisplay
	$result=DispMpdDevice();				// "jsp Array:ARYDEVL/R"の設定jspを終了時実行
	print '</html>';
	print '<jsp>'.$result.'</jsp>';
	print "mpdconf Post:Update End\n";
	}

/*
	_PostUpdateShell($str_newaudio,$pass) : Update Shell kick
	$str_newaudio: i  : New Audio_Output String
	$pass        : i  : sudo password , $G_MPDWEB_MODE=offのとき有効
*/
function _PostUpdateShell($str_newaudio,$pass) {
	global $GMPDCONF;

	$cmd = <<<'EOS'
perl -e '
use strict;
use warnings;

my $STR_NEWAUDIO =   <<EOT;
{$str_newaudio}
EOT

my $CONF_FILE = "{$GMPDCONF}";
my $NOW_FILE  = "{$GMPDCONF}.now";
my $ORG_FILE  = "{$GMPDCONF}.org";
open(my $in, "<", $CONF_FILE) or die $!;
open(my $out, ">", $NOW_FILE) or die $!;
my $in_block = 0;
my $inserted = 0;
while (my $line = <$in>) {
	chomp $line;
	if ($line =~ /^[ \t]*audio_output[ \t]*{/) {
		$in_block = 1;
		if (!$inserted) {
			$STR_NEWAUDIO =~ s/\s+$/\n/s;  # 最後の改行群を1つにしておく（保険）
			print $out $STR_NEWAUDIO, "\n";  # ← ここを "\n\n" から "\n" に変更
			$inserted = 1;
		}
        next;
	}
	if ($in_block && $line =~ /^[ \t]*}[ \t]*$/) {
		$in_block = 0;
		my $nextline = <$in>;
		if (defined $nextline && $nextline !~ /^[ \t]*$/) {
			print $out $nextline;
 			}
		next;
		}
	print $out "$line\n" unless $in_block;
	}
close $in;
close $out;
if (! -e $ORG_FILE) {
	system("cp", $CONF_FILE, $ORG_FILE);
	}
system("cp", $NOW_FILE, $CONF_FILE);
system("chmod", "666", $CONF_FILE) == 0 or die "chmod failed";
system("chmod", "666", $NOW_FILE) == 0 or die "chmod failed";
'
EOS;
	// Here document <<<'EOC'の場合{$PHP-ITEM}は置換しないのでReplase (perlは${項目名}なので記述安さを優先
	global $G_MPDWEB_MODE;

	$cmd=str_replace('{$str_newaudio}',$str_newaudio,$cmd);
	$cmd=str_replace('{$GMPDCONF}',$GMPDCONF,$cmd);
	$cmd = "echo %sudo-pw% | sudo -S bash -c " . escapeshellarg($cmd);	// sudoで実行
	if ($G_MPDWEB_MODE==1) {
		$trmstr="";$ret="";
		$output=GetCmdExec($cmd,$termstr,true);
		if ($output===false) {
			$ret=false;
			}
		 }
	else {
		 $cmd=str_replace("%sudo-pw%",$pass,$cmd);			// Password SearchShellを使わない場合有効し,上Lineの // $cmd=ArgMakeSvrCmd($cmd);コメントアウト
		 exec($cmd, $output, $ret);	// 実行
		 }
	if ($ret === 0) {				// 結果確認
		 print "mpdconf:Update OK:" . implode("\n", $output)."\n";
		 }
	else {
		 echo "mpdconf:Update Error:Code=Exit(" . implode("\n", $output).")\n";
		 }
	}

/* ------------------------------------------------------------------------------------
	PostRestart() : mpd Restart
------------------------------------------------------------------------------------ */
function PostRestart() {
	global $G_MPDWEB_MODE;

	print "mpdconf Post:mpd Restart Start\n";
	$cmd = "echo %sudo-pw% | sudo -S systemctl restart mpd";	// sudoで実行
	if ($G_MPDWEB_MODE==1) {
		 $trmstr="";$ret="";
		 $output=GetCmdExec($cmd,$termstr,true);
		 if ($output===false) {
			$ret=false;
			}
		 }
	else {
		 $cmd=str_replace("%sudo-pw%",$pass,$cmd);
		 exec($cmd, $output, $ret);	// 実行
		 }
	if($ret===false) {
		 print "mpdconf Post:mpd Restart False AbEnd\n";
		 }
	else {
		 print "mpdconf Post:mpd Restart End\n";
		 }
	}

/**************************************************************************************
	HTML Item
**************************************************************************************/
GetMpdConfig();

$my_self = htmlspecialchars($_SERVER['SCRIPT_NAME'], ENT_QUOTES, 'UTF-8');
$SCR_WIDTH=480;
echo <<<EOM
<html lang="ja"><link rel="shortcut icon" href="./mpdwebfavicon.ico"><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" ><meta http-equiv="Expires" content="1000">
<meta id="idMeta" name="viewport" name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.7">
<script type="text/javascript">
var POSTMOD="{$_SERVER['PHP_SELF']}";		// POST ServerModule
/* View Port 設定 */
var getDevice = (function(){
		var ua = navigator.userAgent;
		if(ua.indexOf('iPhone') > 0 || ua.indexOf('iPod') > 0 || ua.indexOf('Android') > 0 && ua.indexOf('Mobile') > 0){
			 return 'sp';
			 }
		else if(ua.indexOf('iPad') > 0 || ua.indexOf('Android') > 0){
			 return 'tab';
			 }
		else {
			 return 'other';
			 }
		})();
if(window.outerWidth==0) {											// window.outerWidthが0になるケースがある。(反則的な対応)
	window.outerWidth=window.innerWidth;
	}
if ((getDevice == 'sp' )||(getDevice == 'tab' )){
	 scale=Math.floor((window.outerWidth/{$SCR_WIDTH})*100)/100;	// 幅比率で表示(小数3位切り捨て)
	 var viewportContent="width=380,initial-scale="+String(scale)+",maximum-scale=1.7";
	 document.getElementById('idMeta').setAttribute('content',viewportContent);
	 }
</script>
<title>mpdconfig Set AudioOutput</title>
</head>
<body>

<script type="text/javascript">
/**************************************************************************************
	postRequest(p,msgObj) : AJAX Post Request
	p       : i  : Post Module
	msgObj  : i  : Message : 連想配列 => msgObj{ "Para": {Message} }
	callback: i  : Object 指定時CallBack実行
	Post結果のFormat
	 Message形式
	  {Message} Start\n
	  {Message} End\n →先頭にも同じ行を追加し上で終了状態を確認
	 HTML形式
	  <html id={HTML-id}>{HTML}</html>:対象のIDを表示する
	 JSP形式
	  <jsp>{jsp}</jsp>:複数OK
	 <html><jsp>以外は{Message}として扱う
	※使用方法
	例1)  const msg = {};
	      msg["ARY"] = str;	Loop中で使用
	例2)  const msg = { "ARY": str };
	postRequest("Update",msg);
**************************************************************************************/
function postRequest(p, msgObj, callback = null) {
	const params = new URLSearchParams();
	params.append("P", p);
	for (const key in msgObj) {
		params.append(key, msgObj[key]);
		}
	fetch("{$my_self}", { 						// 自phpにPOST
		method: "POST",							// ServerにRequestを送る
		headers: { "Content-Type": "application/x-www-form-urlencoded" },
		body: params.toString()
		})
	.then(response => response.text())			// fetch()のresponseを受け取る=>responseをTextとしてGet
	.then(result => {							// Text変換された結果をGet
		let resultText = result;
		const htmlMatch = result.match(/<html id=([^>]+)>([\s\S]*?)<\/html>/);	// Display HTML (<html id={id}>{html} </html>)
		if (htmlMatch) {
			const htmlId = htmlMatch[1];
			const htmlContent = htmlMatch[2];
			const targetElem = document.getElementById(htmlId);		// <html id={id}の要素にHTML内容を反映
			if (targetElem) {
				targetElem.innerHTML = htmlContent;
				}
			resultText = result.replace(htmlMatch[0], "").trim();		// Text<html>{HTML}</html>を表示
			}
		let jsMatch;												// <jsp>{jsp}<jsp> 処理（複数対応）
		const jsTagRegex = /<jsp>([\s\S]*?)<\/jsp>/g;
		while ((jsMatch = jsTagRegex.exec(resultText)) !== null) {
			const jsCode = jsMatch[1];
			try {
				new Function(jsCode)();									 // 即時関数(evalより安全)
			} catch (e) {
				console.error("Error in <jsp> script:", e);
				}
			}
		resultText = resultText.replace(jsTagRegex, "").trim();
		const lines = resultText.split('\\n');						// Line Endを検出（'End'を含む）
		const endLine = lines.findLast(line => line.includes('End'));
		if (endLine) {
			resultText = endLine + '\\n' + resultText;					// Line Endがあれば先頭に追加
			}
		document.getElementById("idStatus").value = resultText;			// StatusにText部を表示
		if (typeof callback === 'function') {						// ユーザー定義の callback があれば実行
			callback(resultText);
			}
		})
	.catch(error => {
		console.error("POST Error:", error);
		alert("POST Error: " + error);
		});
	}

/* ----------------------------------------------------------------------------
	RequestMessageBox(title,str,top,left) : MessageBox (mpdweb標準モジュールにStringLength追加)
	 title: i  : Box Title
	 str  : i  : Message
----------------------------------------------------------------------------- */
function RequestMsgBox(title,str,top,left) {
	str=str.replace(/<BR>/g, "\\n");
	var ary=str.split("\\n");
	var leng=0;
	for (i = 0; i < ary.length; i++) {
		if (ary[i].length > leng) {
			leng=ary[i].length;
			}
		}
	if (leng > 50){
		  document.getElementById('idMsgBoxstr').style.fontSize = "9px";
		  document.getElementById('idMsgBoxstr').style.top = "36px";
		  document.getElementById('idMsgBoxstr').style.lineHeight = "130%";
		  }
	else {
		 document.getElementById('idMsgBoxstr').style.fontSize = "13px";
		 document.getElementById('idMsgBoxstr').style.top = "60px";
		 document.getElementById('idMsgBoxstr').style.lineHeight = "20px";
		 }
	if (title == null) { title="Message Box"; }
	if (top == null) { top=160; }
	document.getElementById('idMsgBox').style.display='block';
	document.getElementById('idMsgBox').style.top=top;
	if (left !== null) {
		document.getElementById('idMsgBox').style.left=left;
		}
	document.getElementById('idMsgBoxtitle').textContent=title;
	document.getElementById('idMsgBoxstr').textContent=str;
	}

/* ------------------------------------------------------------------------------------
	ArgMpdRestart() : mpd restart
------------------------------------------------------------------------------------ */
function ArgMpdRestart() {
	const msg = { "MPDWEB_MODE": "{$G_MPDWEB_MODE}" };
	postRequest("Restart",msg);				// Post Send
	}

/* ------------------------------------------------------------------------------------
	ArgDevAryRenew() : ALSA Device Display Renew
------------------------------------------------------------------------------------ */
function ArgDevAryRenew() {
	const msg = { "MPDWEB_MODE": "{$G_MPDWEB_MODE}" };
	postRequest("Renew",msg);				// Post Send
	}

/* ------------------------------------------------------------------------------------
	ArgDevAryMake(update) : AudioDevice Array Make
	update : i  : 0:Only,Input Check ,1: Only,Input Check & Update
	@return: o  : False:Input Error , Update or Check OK
------------------------------------------------------------------------------------ */
function ArgDevAryUpdate(update) {
	const checkboxes = document.querySelectorAll('input.dcheck');
	const selected = {};
	const overlaped={};
	var flagChk=0;flagErr=0;
	checkboxes.forEach(cb => {
		if (cb.checked) {
			const table = cb.closest('table');  // 所属しているテーブル要素
			const tableTitle = table.querySelector('th')?.textContent ?? '(Table??)';
			if(cb.value.split("|")[1]=="alsa") {
				 var str = cb.value.substring(cb.value.indexOf("|") + 1, cb.value.lastIndexOf("|") + 1);
				 if (str in overlaped) {
					RequestMsgBox('mpdconfig Error Message','Same device is checked in ALSA DEVICE , mpd.conf !');
					flagErr=1;
					return;
					}
				 }
			else {
				 if (cb.value in selected) {
					 RequestMsgBox('mpdconfig Error Message','Same device is checked in ALSA DEVICE , mpd.conf !');
					 flagErr=1;
					 return;
					 }
				 }
			overlaped[str]=cb.value;
			if(tableTitle.includes('ALSA')) {
				 selected[cb.value]=ARYDEVL[cb.value];
				 flagChk=1;
				 }
			else {
				 selected[cb.value]=ARYDEVR[cb.value];
				 flagChk=1;
				 }
			}
		});
		if(flagChk==0) {
			RequestMsgBox('mpdconfig Error Message','There is no check mark for audio device !');
			flagErr=1;
			}
	if(flagErr==1) { return(false); }
	if(update==1) {
		var pass=ObjCode.value;
		const str = JSON.stringify(selected);	// 配列をjson形式に変換
		const msg = { "ARY": str , "Pass": pass , "MPDWEB_MODE": "{$G_MPDWEB_MODE}" };
		postRequest("Update", msg,function() { ArgParentClick(); } );				// Post Send
		}
	return(true);
	}
EOM;
echo <<<EOM
/* ------------------------------------------------------------------------------------
	ArgParentClick() : Child Window ,Prent_renew
------------------------------------------------------------------------------------ */
function ArgParentClick() {
	if(("{$XML_SUBWIN['ParrentElment']}" =="")||("{$XML_SUBWIN['Action']}" =="")) {
		return;
		}
	if (window.opener && !window.opener.closed) {					// 親から開かれたか？
		const parent_renew = window.opener.document.getElementById("{$XML_SUBWIN['ParrentElment']}");	// 親ウィンドウの要素 const parent_renew = window.opener.document.getElementById('idMpdRenew')
		if (parent_renew) {
			parent_renew.{$XML_SUBWIN['Action']};					// 親ウィンドウのAction:parent_renew.click();
			}
		}
	}

/* -----End of JS Common Script ---------------------------------------------------- */
</script>

<style type="text/css">
/* ------------------------------------------------------------------------------------
	Style Sheet
------------------------------------------------------------------------------------ */
body  { background-color:#000000;color:#ffffff;font-size:12px; }
table { table-layout:fixed;font-size:12px; }
th    { background-color:#191970;color:#ffffff; }

.tbcss { position:fixed;top:60px;background-color:#000000;color:#ffffff; }
/* Header Function */
.headPs { position:fixed;top:38px;text-align:center; }
.headfc { width:60px;cursor:pointer; }
.headTx { position:fixed;top:40px;left:80px;color:#ffdead; }
.headRs { width:70px;height:20px;font-size:9px;border: 1px solid #2e8b57;background-color:#008000;color:#ffffff;cursor:pointer; }

/* Divice Detail */
.dcheck { width:10px;cursor:pointer; }
.dtypem { width:30px;font-size:12px;overflow:hidden; }
.dtypes { width:30px;font-size:11px;overflow:hidden; }
.detail { width:150px;height:13px;line-height:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;word-break:break-all;cursor:pointer; }
/* Box Upup */
.Popup     { position:fixed;top:20%;width:236px;padding:6px;padding-bottom:50px;background:#fffff0;color:#000000;border:2px solid #000000;min-height:160px;box-sizing:border-box;z-index:1000; }
.PopupTitl { font-weight:bold;margin-bottom:10px;white-space:pre; }
.Popupcont { white-space:pre;overflow-x:auto;overflow-y: hidden;max-width: 100%; }
.PopupCls  { position:absolute; bottom:10px; right:10px;cursor:pointer; }

/* Modal Box Confirmation */
.Modal  { position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,0.5);display:none; }
.boxcs  { position:fixed;top:20px;left:80px;width:300px;height:130px;text-align:center;background:#282828; border-radius:6px;padding:20px; margin:100px auto; }
.boxti  { position:absolute;top:10px;left:20px;font-size:14px;color:#fffff0; }
.boxtyp { position:absolute;top:10px;left:156px;font-size:15px;color:#ffffff; }
.boxsb  { position:absolute;top:40px;left:20px;fontsize;9px;color:#ff69b4; }
.boxcf  { position:absolute;top:100px;left:40px;fontsize;9px;color:#ffdab9; }
.boxlb  { position:absolute;top:72px;left:20px; }
.boxin  { position:absolute;top:70px;left:140px; }
.boxok  { position:absolute;bottom:20px;left:40px;width:60px; }
.boxng  { position:absolute;bottom:20px;right:40px;width:60px; }
.boxtx  { position:absolute;top:66px;width:300px;text-align:center;font-size:16px;color:#7fffd4; }

/* Status Line */
.status { position:fixed;bottom:0px;left:1px;width:476px;font-size:11px;background-color:#000000;z-index:400; }
textarea { border:1.4px solid #000000;width:480px;height:20;font-size:10px;color:#BDBDBD;line-height:140%;padding:4 6 6 6;background-color:#2E2E2E;resize:none;overflow-y:scroll;overflow-x:hidden;white-space:pre-wrap;scrollbar-width:none; }
textarea::-webkit-scrollbar { display: none; }
/* Message Box */
.box {border: 1px solid #888888;position:fixed;top:200px;left:170px;
      width:300px;height:238px;font-size:10p;z-index:120;
      background-color:#2f2f2f;
      display: none;
      }
.boxtitle { position:absolute;top:0px;left:0px;width:460px;height:12px;font-size:12px;background-color:#00008b;color:#b0c4de;line-height:70%;text-align:left;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;padding:8px 10 2px;}
.buttn { position:absolute;left:10px;width:50px;height:14px;font-size:12px;line-height:110%;background-color:#a9a9a9;color:#000000;text-align:center;border-style:none;padding:3px 6 2px;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius: 4px;border: 1px solid #696969;cursor:pointer; }
.buttn:active{ color:#808080; }
/* Header Title */
.Title  { position:fixed;top:7px;left:12px;font-size:14px;color:#fffff0; }
.ver    { position:fixed;top:6px;left:300px;font-size:9px;color:#a9a9a9; }
.host   { position:fixed;top:20px;left:300px;font-size:9px;color:#ffffff; }
</style>
EOM;
if ($G_MPDWEB_MODE==1) {
	 $serverip=$XML_HOST["Host"];
	 if($serverip=="localhost") { $serverip=$_SERVER['SERVER_ADDR']; }
	 }
else {
	 $serverip=$_SERVER['SERVER_ADDR'];
	 }
echo <<<EOM
<div class="Title">MPDCondifg Search audio_output</div>
<div class="ver">{$MY_VER_MPDCONFIG}</div>
<div class="host">config server:{$serverip}</div>
EOM;
/**************************************************************************************
	PHP Main Proc
**************************************************************************************/

print '<div id="idAUDIODEV">';
DispMpdDevice();
print '</div>';

/* Function Update*/
echo <<<EOM
<div class="headPs" style="left:10px;">
<input type="button" value="Renew" class="headfc" onclick="ArgDevAryRenew();">
</div>
<div class="headTx" style="">Set AudioDdevice to Ready and "Renew"</div>
<div class="headPs" style="left:340px;">
<input type="button" id="idRestart" value="mpd Restart" class="headRs">
<div class="headPs" style="left:420px;">
<input type="button" id="idUpdate" value="Update" class="headfc">
</div>
EOM;
if($G_MPDWEB_MODE==1) {
echo <<<EOM
<div id="idConfirm" class="Modal">
  <div class="boxcs">
    <div class="boxti">Confirmation Box :</div>
	<div id="idType" class="boxtyp">update</div>
    <div class="boxtx">Are you sure you want to proceed ?</div>
	<input type="hidden" id="idCode" value="password" />
    <button id="idOkBtn" class="boxok">OK</button>
    <button id="idCancel" class="boxng">Cancel</button>
  </div>
</div>
EOM;
	 }
else {
echo <<<EOM
<div id="idConfirm" class="Modal">
  <div class="boxcs">
    <div class="boxti">Confirmation Box :</div>
	<div id="idType" class="boxtyp">update</div>
    <div class="boxsb">No password, no permissions, no backup!</div>
	<div class="boxlb" style="font-size:14px;">sudo password</div>
    <input type="text" id="idCode" class="boxin">
    <div class="boxcf">sudo permissions persist for a while</div>
    <button id="idOkBtn" class="boxok">OK</button>
    <button id="idCancel" class="boxng">Cancel</button>
  </div>
</div>
EOM;
	}

\MyProject\DispMsgBox();			// MessageBox(Form)をDisplay:noneで作成しておく。

/* Post Result */
echo <<<EOM
<div class=status style="">
<textarea cols="56" rows="1" readonly id="idStatus"></textarea>
</div>

<script>
/*******************************************************************************
	HTML Event Listener
*******************************************************************************/
const ObjidRestart = document.getElementById('idRestart');
const ObjUpdate  = document.getElementById('idUpdate');
const ObjConfirm = document.getElementById('idConfirm');
const ObjCancel  = document.getElementById('idCancel');
const ObjCode  = document.getElementById('idCode');
const ObjOkBtn = document.getElementById('idOkBtn');
const ObjType  = document.getElementById('idType');

/* -----------------------------------------------------------------------------
	Update Botton EventListener
----------------------------------------------------------------------------- */
ObjidRestart.addEventListener('click', function() {
	if (ArgDevAryUpdate(0)===false) { return; }
	ObjConfirm.style.display = 'block';
	if (ObjCode && ObjCode.type !== 'hidden') {
		ObjCode.value = '';
		}
	ObjType.textContent = 'Restart';			// ConfirmBox Type
	});

/* -----------------------------------------------------------------------------
	Update Botton EventListener
----------------------------------------------------------------------------- */
ObjUpdate.addEventListener('click', function() {
	if (ArgDevAryUpdate(0)===false) { return; }
	ObjConfirm.style.display = 'block';
	if (ObjCode && ObjCode.type !== 'hidden') {
		ObjCode.value = '';
		}
	ObjType.textContent = 'Update';				// ConfirmBox Type
	});

/* -----------------------------------------------------------------------------
	ConfirmBox
----------------------------------------------------------------------------- */
/*
	ConfirmBox Cancel Botton EventListener
*/
ObjCancel.addEventListener('click', function() {
	ObjConfirm.style.display = 'none';
	});

/*
	ConfirmBox OK Botton EventListener
	ObjType(idType):ConfirmBox Typeを設定しておく
*/
ObjOkBtn.addEventListener('click', function() {
	if (ObjType.textContent == 'Update') {
		 ArgDevAryUpdate(1);
		 }
	else if (ObjType.textContent == 'Restart') {
		 ArgMpdRestart() 
		 }
	ObjConfirm.style.display = 'none';
	});
</script>

EOM;
echo "</body>";

/*******************************************************************************
	DispMpdDevice() : Display mpd Device
	@return  : o  : Make Array jsp : ARYDEVL,ARYDEVR
*******************************************************************************/
function DispMpdDevice() {
	global $GMPDCONF;

	$result="";
	$out_std=ArgMakeDevice();
	if($out_std===false) {
		print '<div style="position:fixed;bottom:300px;left:6px;">ArgMakeDevice():False</div>';
		$out_std=array();
		}
	$devary=ArgMakeDeviceEdit($out_std);
	$result =DispDeviceArray("ALSA DEVICE",$devary["alsa"],"L");
	$result.=DispDeviceArray(basename($GMPDCONF),$devary["conf"],"R","1");
//print '<div style="position:fixed;bottom:300px;left:6px;">';
//print '<pre>';print_r($out_std);print '</pre>';
//print $result;
//print_r($devary["alsa"]);
//print_r($devary["conf"]);
//print '</div>';
	return($result);
	}

/*******************************************************************************
	GetMpdConfig() :Get mpd Config
*******************************************************************************/
function GetMpdConfig() {
	global $GMPDCONF,$XML_SUBWIN;

	$GMPDCONF='/etc/mpd.conf';													// mpdsetting_mpd.xml Tag:mpd_conf[path]
	$XML_SUBWIN=array("ParrentElment"=>"idMpdRenew","Action"=>"click();");		// mpdsetting_mpd.xml Tag:subwindow
// Test時戻し忘れを防ぐため mpdsetting_mpd.xmlに設定して行うこと!
	if ((!function_exists("get_loadxml"))||(!function_exists("get_confxml"))) {
		return;
		}
	$XML_MPDCONFIG="setting/mpdsetting_mpd.xml";
	if  (!file_exists($XML_MPDCONFIG)) { return; }
	try {
		$XML_MPDCONF=get_loadxml($XML_MPDCONFIG);
		} catch( Exception $e ) {
		 	return;
			}
	$filed=array("path"=>"/etc/mpd.conf");
	get_confxml($XML_MPDCONF,"mpd_conf",$filed);
	$GMPDCONF=$filed["path"];
	get_confxml($XML_MPDCONF,"subwindow",$XML_SUBWIN);
//print "<BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>";
//print "---------------------------------------------------------------------------<BR>";
//print '$GMPDCONF['.$GMPDCONF."]<BR>";
//print_r($XML_SUBWIN);
	}

/*******************************************************************************
	mpdweb Series : $G_MPDWEB_MODE=1
	---------------------------------------------------------------------------
	GetCmdExec($cmd,$TermStr,$multi) : Server Shellを実行
	 $cmd    : i  : Excution Shell Command
	 $TermStr: io : Terminate Sting
	 $multi  : i  : Default:False ,False:LineExec ,True:一括実行,HereDocumentなど。
	                True :一括実行:Perl,HereDocumentなど実行できるが、占有する。
	                False:受信後、即実行
	 @return : o  : Array Shell
*******************************************************************************/
function GetCmdExec($cmd,&$TermStr="",$multi=false) {
	global $XML_HOST,$XML_SHELL;						// $XML_HOST:require module '/mpdtask.php'の設定を共有

	$errno="";$errstr="";
	$host="";$rhost="";
	$cmd=ArgMakeSvrCmd($cmd);
//	$cmd=str_replace("%sudo-pw%",$XML_SHELL["sudo-pw"],$cmd);			// Password SearchShellを使わない場合有効し,上Lineの // $cmd=ArgMakeSvrCmd($cmd);コメントアウト
	$TermStr=$XML_HOST["TermStr"];
	if (isset($_SERVER["REMOTE_ADDR"])) {
		$rhost=$_SERVER["REMOTE_ADDR"];
		}
	if (isset($_SERVER["HTTP_HOST"])) {
		$urlary = parse_url($_SERVER["HTTP_HOST"]);		// {host}:{88}
		if (isset($urlary["host"])) { $host=$urlary["host"]; }
		}
// Task Serverが HTTP Serverと同じ場合,Shellで求める
	$mount=0;$mode=0;
	if ($XML_HOST["Force"] == "off") {					// $XML_HOST["Force"]:on時常にTaskServer使用
		if (($XML_HOST["Host"]=="localhost")||($XML_HOST["Host"]=="127.0.0.1")||(($host !=="")&&($host==$XML_HOST["Host"]))||(($rhost !=="")&&($rhost==$XML_HOST["Host"]))) {
			 $mount=1;
			 }
		}
	if (($XML_HOST["Excludesu"] == "on")&&(strpos($cmd,"sudo") !== false)) {		// sudoはTaskServerで行う。exec:WebからパーミッションをChildProcessに継承するため
		$mount=0;
		}
//$mount=0;
	if ($mount==1) {
//		 print 'exec $cmd['.$cmd.']<BR>';
		 msglog('[mpdsetting][exec]['.$cmd.']');			// Used Debug
		 exec($cmd,$ret);
		 }
	else {
//		 print '$cmd['.$cmd.'] $XML_HOST["Host"]['.$XML_HOST["Host"].'] $XML_HOST["Port"]['.$XML_HOST["Port"].'] $XML_HOST["TermStr"]['.$XML_HOST["TermStr"].'] $mode['.$mode.'] $errno['.$errno.'] $errstr['.$errstr.']<BR>';
		 msglog('[mpdsetting][SockCmdsend]['.$cmd.']Host['.$XML_HOST["Host"].']Port"]['.$XML_HOST["Port"].']');			// Used Debug
		 $TermStr="\e";
		 $ret=SockCmdsend($XML_HOST["Accunt"],$cmd,$XML_HOST["Host"],$XML_HOST["Port"],$XML_HOST["Timeout"],$TermStr,$mode,$errno,$errstr,$multi);
		 }
//global $DEBUG;$save=$DEBUG;$DEBUG=2;msglog($cmd);$DEBUG=$save;
	return($ret);
	}

/*******************************************************************************
	DispDeviceArray($title,$devary,$position,$checked) : Display Device
	$title   : i  : Table Title
	$devary  : i  : Display Array : key:{kind}|{tpye}|{device}}|{name}=> "audio_output...."
	$position: i  : Display Postion "L"|"R"左右どちらに表示するか設定
	$checked : i  : Check Box 状態
	@return  : o  : Make Array jsp : ARYDEV{$position}
**YYY*****************************************************************************/
function DispDeviceArray($title,$devary,$position,$checked="") {
	if($position=="L") { $left=6; } else { $left=244; }
    print '<div class="tbcss" style="left:'.$left.'px;width:230px;">';
    print '<table border="3" cellpadding="5" frame="void">';
	print '<tr><th colspan="3">'.$title.'</th></tr>';				// Table Title
    foreach ($devary as $key => $item) {
		$devkey = explode("|", $key);
		print '<tr>';		        // 1行目
		$str="";
		if($checked=="1") {
			$str='checked="checked "' ;
			}
		print '<td rowspan="2"><input type="checkbox" '.$str.'class="dcheck" value="' . htmlspecialchars($key) . '"></td>';		// チェックボックス: rowspan=2
		$str="dtypem";
		if (strlen($devkey[1]) > 4) {
			$str="dtypes";
			}
		print '<td rowspan="2"><div class="'.$str.'">';   									// $val[1]:Type: rowspan=2
		print isset($devkey[1]) ? htmlspecialchars($devkey[1]) : '';
		print '</div></td>';
		print '<td><div class="detail" onclick="DispPopup'.$position.'(\''.$key.'\');">';
		if((isset($devkey[2]))&&($devkey[2] !=="")) {				// $val[2]:Device (Line:1)
			 print $devkey[2] ? htmlspecialchars($devkey[2]) : '';
			 }
		else {
			 print '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-';
			 }
		print '</div></td>';
		$item=str_replace(array("\t", "\n"),array("    ", '<BR>'),htmlspecialchars($item));
		print '<td rowspan="2" style="display:none;"><div id="'.$key.'">'.$item.'</div></td>';	// hidden "audio_output"
		print '</tr>';
		print '<tr>';
		print '<td><div class="detail" onclick="DispPopup'.$position.'(\''.$key.'\');">';
		if((isset($devkey[3]))&&($devkey[3] !=="")) {				// $val[2]:Device (Line:1)
			 print $devkey[3] ? htmlspecialchars($devkey[3]) : '';
			 }
		else {
			 print '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-';
			 }
		print '</div></td>';
		print '</tr>';
		}
	print '</table>';
	print '</div>';

	$str="";
	foreach ($devary as $key => $item) {
		if($str !== "") { $str=$str.","; }
		$str_item=str_replace(array("\t", "\n",'"'),array('\t','\n','\"'),$item);
		$str=$str.'"'.$key.'" : '.'"'.$str_item.'"';
		}
	$JSARY='ARYDEV'.$position." = { ".$str." };";

echo <<<EOM
<script type="text/javascript">

var {$JSARY}

/* ------------------------------------------------------------------------------------
	DispPopup{$position}(key) : Display : Popup,audio_output
------------------------------------------------------------------------------------- */
function DispPopup{$position}(key) {
	var content = document.getElementById(key).innerHTML;
	var title = key.split('|').slice(1).join('|');
	document.getElementById('idPopup{$position}-title').innerHTML = title;  // タイトルにkey表示
	document.getElementById('idPopup{$position}-content').innerHTML = content;
	document.getElementById('idPopup{$position}').style.display = 'block';
	}
</script>
<div id="idPopup{$position}" class="Popup" style="display:none; left:{$left}px;">
	<div id="idPopup{$position}-title" class="PopupTitl"></div>
	<div id="idPopup{$position}-content" class="Popupcont"></div>
	<button onclick="document.getElementById('idPopup{$position}').style.display='none';" class="PopupCls">Close</button>
</div>

EOM;
	return($JSARY);
	}

/*******************************************************************************
	ArgMakeDevice() : Make mpd.conf Device Image
	@return : o  : array : ALSA ,Separater:"msg:mpdconf\n", mpd.conf
*******************************************************************************/
function ArgMakeDevice() {
	global $GMPDCONF;

//	/proc/asound/cards
	$mpd_audio = <<<'EOC'
perl -e '
use strict;
use warnings;

#------------------------------------------------------------------------------- #
#     ALSA Device 
#------------------------------------------------------------------------------- #
open(my $fhcards, "<", "/proc/asound/cards") or die $!;
my @lines = <$fhcards>;
close($fhcards);

my @outputs;

while (my $line = shift @lines) {
	if ($line =~ /^\s*(\d+)\s+\[(\S+)\s*\]:\s+(\S+)\s+-\s+(.+)/) {
		my $cardnum = $1;
		my $id      = $2;     # カードID (例: vsound)
		my $type    = $3;     # e.g., USB-Audio
		my $desc    = $4;     # e.g., Device Name

		shift @lines;         # skip longdesc line

		# 再生PCMデバイス番号を探す
		my $devnum = "";
		my $pcm_path = "/proc/asound/card$cardnum";
		if (opendir(my $dh, $pcm_path)) {
			my @pcm_dirs = grep { /^pcm\d+p/ } readdir($dh);
			closedir($dh);
			if (@pcm_dirs) {
				@pcm_dirs = sort @pcm_dirs;
				if ($pcm_dirs[0] =~ /^pcm(\d+)p/) {
					$devnum = $1;
				}
			}
		}

		my $device_str = (defined $devnum && $devnum ne "")
			? "hw:$id,$devnum"
			: "hw:$id";

		push @outputs, {
			name   => $desc,
			type   => $type,
			device => $device_str,
		};
	}

}

foreach my $out (@outputs) {
	if($out->{name} =~ /MemoryPlayHost/) { next; }
	print "audio_output {\n";
	print "\ttype\t\"alsa\"\n";
##	print "\tenabled \"no\"\n";			# Enableは今回セットしない
	print "\tname\t\"$out->{name}\"\n";
	print "\tdevice\t\"$out->{device}\"\n";

	if($out->{type} =~ /USB-Audio/) {
		print "\tmixer_type\t\"none\"\n";
		print "\tdop\t\"yes\"\n";
		}
	if($out->{name} =~ /snd_rpi_/) {	# 確証はないがI2S
		print "\tmixer_type\t\"none\"\n";
		print "\tdop\t\"yes\"\n";
		}
	print "}\n";
	}

print "msg:mpdconf\n";		# Separater

#------------------------------------------------------------------------------- #
#      mpd.conf Audio Device
#------------------------------------------------------------------------------- #
my $filename = "{$GMPDCONF}";
open my $fh, "<", $filename or die "Cannot open $filename: $!";
my $in_audio_output = 0;
my @block;

while (my $line = <$fh>) {
	 if ($line =~ /^\s*[^#]*\baudio_output\s*{/) {
		$in_audio_output = 1;
		@block = ($line);
		next;
		}

	if ($in_audio_output) {
		push @block, $line;
		if ($line =~ /^\s*}/) {
			print @block;
			print "\n";  # セクション間に空行
			$in_audio_output = 0;
			}
		}
	}
close $fh;
' # End Of Perl
EOC;
	// Here document <<<'EOC'の場合{$PHP-ITEM}は置換しないのでReplase (perlは${項目名}なので記述安さを優先
	$mpd_audio=str_replace('{$GMPDCONF}',$GMPDCONF,$mpd_audio);
	$out_std =array();
	global $G_MPDWEB_MODE;
	if ($G_MPDWEB_MODE==1) {
		$trmstr="";$result="";
		$out_std=GetCmdExec($mpd_audio,$termstr,true);
		if ($out_std===false) {
			$result=false;
			}
		 }
	else {
		 exec($mpd_audio,$out_std, $result);
		 }
	if($result ===false) { return(false); }
	return($out_std);
	}

/* -----------------------------------------------------------------------------
	ArgMakeDeviceEdit($out_std) : Make mpd.conf Device Image Edit
	$out_std: i  : in  array form ; result ,  exec : Line Array
	@return : o  : out array $devary["alsa"]:AlSA Device ( Used : Sort )
 	                         $devary["conf"]:mpd.conf audio_output
----------------------------------------------------------------------------- */
function ArgMakeDeviceEdit($out_std) {
////// mpd Strimng ///////
	$out_alsa=array();
	$out_conf=array();
	$key=array_search("msg:mpdconf",$out_std);	// Shell:perl実行はALSA/mpd.confが1Arrayになっているので分離
	if($key) {
		 if($key > 0) {								// ALSA Device:"msg:mpdconf"より前
			 $out_alsa=array_slice($out_std,0,$key);
			 }
		 if(($key+1) < (count($out_std))) {			// Conf Device:"msg:mpdconf"から後
			$out_conf=array_slice($out_std,($key+1));
			}
		 }

												// ALSA DeviceにStreaming 追加
	$mpd_http = <<<'EOC'
audio_output {
	type "httpd"
	name "HTTP Server"
	port "8000"
	encoder "lame"
	bitrate "320"
	tags "yes"
	always_on "yes"
	format "44100:16:2"
}

EOC;

	$str = implode(",",$out_std);
	if(stripos($str,'Bluetooth') === false){	// Bluetoothがなければ追加
	$mpd_blue = <<<'EOC'
audio_output {
	type "alsa"
	name "ALSA Bluetooth"
	device "_audioout"
	mixer_type "software"
}

EOC;
		$mpd_http=$mpd_blue.$mpd_http;
		}

	$ary = explode("\n",$mpd_http);
	$out_alsa=array_merge($out_alsa,$ary);

	$out_alsa=_ArgMakeDeviceArray($out_alsa);
	$out_conf=_ArgMakeDeviceArray($out_conf);
	$devary["alsa"]=$out_alsa;
	$devary["conf"]=$out_conf;
	return($devary);
	}

/* -----------------------------------------------------------------------------
	_ArgMakeDeviceArray() : Make mpd.conf Device Image Array
	$devary : i  : in  array form ; audio_output {string} }{string} audio_output
	@return : o  : out array form : key:{kind} | {tpye} | {device}} | {name}=>"audio_output..... "
                   audio_outputのarray key: セパレータ:"|"
                     kind : ALSA:"1" , EtherDev:"2" , ALSA hwがない(Bluetoothなど) , "httpd":"4"
	                 device,name,Deviceがない場合,""
----------------------------------------------------------------------------- */
function _ArgMakeDeviceArray($devary) {
	$str=implode("\n", $devary);
	$result = preg_replace('/\}[^}]*?audio_output/', "}\n\eaudio_output",$str);
	$ary=explode("\e",$result);						// Make Array: 1定義を配列化

	preg_match_all('/audio_output\s*\{(.*?)\}/s', $str, $blocks);	// Key部だけの配列を作成
	$r = [];
	foreach ($blocks[1] as $block) {
		$lines = explode("\n", $block);
		$type = null;$device = null;$name =null;
	    foreach ($lines as $line) {
			$line = trim($line);
			if (preg_match('/^\s*[#;]/', $line)) { continue; }		// コメント無視

			if (preg_match('/^type\s+"([^"]+)"/', $line, $m)) {
				$type = $m[1];
				}
			if (preg_match('/^device\s+"([^"]+)"/', $line, $m)) {
				$device = $m[1];
				}
			if (preg_match('/^name\s+"([^"]+)"/', $line, $m)) {
				$name = $m[1];
				}
			}
		if ($type !== null) {					// type:必須
			$kind="0";
			if ((isset($device))&&((stripos($device,'Dirett') !== false)||(stripos($device,'vsound') !== false))) { $kind="2"; }
			else if ((isset($device))&&(stripos($device,'hw') === false)) { $kind="3"; }
			else if ($type=="alsa") { $kind="1"; }
			else if ($type=="httpd") { $kind="4"; }				// Array　Form:{kind}|{type}|{device}|{name}
			$r[] = $kind."|".$type . '|' . ($device ?? '') . '|' . ($name ?? '');
			}
		}
	$values = array_values($ary);				// Old:Array (連想配列を数値Key:変更して耐えるように)
	$ary = array_combine($r,$values);			// New Array :Key部を付け替える
	return($ary);
	}

/* ----------------------------------------------------------------------------
	DispMsgBox() : Display MessageBox (Init)
----------------------------------------------------------------------------- */
function DispMsgBox() {
	$width=360;
	print '<div id=idMsgBox class=box style="width:'.$width.'px;height:160px;position:fixed;top:200px;left:'.((480-$width)/2).'px;background-color:#f5f5f5;z-index:1000;display:none;">';
	print '<div id=idMsgBoxtitle class=boxtitle align=left style="width:'.($width-20).'px;cursor:pointer;"></div>';
	print '<div id=idMsgBoxstr align=center style="overflow:hidden;word-wrap: break-word;white-space: pre-wrap;text-overflow:ellipsis;width:'.($width-40).'px;height:100px;font-size:15px;position:absolute;top:60px;left:20px;line-height:20px;text-align:center;color:#000000;"></div>';
	print '<input type="button" name="Submit" class="buttn" value="OK" onclick="document.getElementById(\'idMsgBox\').style.display=\'none\';" style="position:absolute;top:120px;left:'.(($width/2)-20).'px;font-size:12px;width:60px;height:24px;" />';
	print '</div>';
	}

//----- Source End --------------------------------------------------------------
?>
