<?

################################################################################

class NmailGroupTree {

    var $table = 'nmail_group_tree_sample';		// 테이블명
    var $field_key = "gt_no";		// 필드명 - 키
    var $field_parent = "gt_parent";		// 필드명 - 부모키
    var $field_depth = "gt_depth";		// 필드명 - 깊이
    var $field_sort = "gt_sort";		// 필드명 - 순서
    var $field_name = "gt_name";		// 필드명 - 이름
    //var $field_isuse = "gt_isuse";		// 필드명 - 사용여부
	var $field_name_length = 255;		// 필드길이 - 이름
    var $field_name_text = "그룹";		// 그룹명

	//var $isuse_only = false;		// 사용함으로 설정된 데이타만 사용할지 여부.(true/false)


################################################################################


	/**
		-- 마지막 깊이를 반환한다.
		int getMaxDepth ()
	*/

	function getMaxDepth($q_where = NULL)
	{
		global $db;

		//if ($this->isuse_only == true)
		//	$q_where .= " AND " . $this->field_isuse . "='Y' ";

		$q = "
			SELECT
				MAX(" . $this->field_depth . ")
			FROM
				" . $this->table . "
			WHERE 1=1
			$q_where
			";
		//echo $q;
		$row = db_get_one($q);

		return (strcmp($row, NULL)) ? (int)$row : 0;
	}	// function()


################################################################################


	/**
		-- 특정깊이에서 마지막 순서를 반환한다.
		int getMaxStepByParent (int parent)
	*/

	function getMaxStepByParent($parent=0, $ext_where = NULL)
	{
		global $db;

		// set query
//			$q_where = NULL;
			//if ($this->isuse_only == true)
			//	$q_where .= " AND " . $this->field_isuse . "='Y' ";

		// get
			$q = "
				SELECT
					MAX(" . $this->field_sort . ")
				FROM
					" . $this->table . "
				WHERE
					" . $this->field_parent . " = '$parent'
					$ext_where
				";
			//echo $q;
			$row = db_get_one($q);

		return (strcmp($row, NULL)) ? (int)$row : 0;
	}	// function()


################################################################################


	/**
		-- 특정 키값의 데이타를 반환한다.
		array getRowByKey (int key, bool isview_error)
	*/

	function getRowByKey($key, $isview_error=true, $ext_where = NULL)
	{
		global $db;

		$q = "
			SELECT
				*
			FROM
				" . $this->table . "
			WHERE
				" . $this->field_key . "='$key'
				$ext_where
			";
		//echo $q;
		$row = db_get_row($q);

		if (($isview_error)&&($row == NULL))
		{
			msg("존재하지 않는 ".$this->field_name_text."입니다.", 0);
			exit;
		}	// if()

		return $row;
	}	// function()


################################################################################


	/**
		-- 특정 키값의 부모 데이타들을 모두 반환한다.
		array getParents (int key, bool isview_error)
	*/

	function getParents($key, $isview_error=true, $q_where = NULL)
	{
		global $db;

		if ($key == 0)
			return;

		$rows = array();
		$_parent = 0;

		$this->rockTable();

		$row = $this->getRowByKey($key, $isview_error, $q_where);
		$_parent = $row[$this->field_parent];
		$rows[] = $row;
		//echo "<HR>[getParents]" . $row['mn_depth'] . ". " . $row['mn_name'];

		// [2003-10-01] 기본메뉴의 상위메뉴를 수정했을때 발생하는 무한루프 방지.
		// [2003-10-17] 무한루프 방지로 인해서 현재페이지를 구분못하는 문제가 발생해서 복구함.
		// [2003-10-17] 무한루프 문제는 [테이블 패치]를 통해 기본값을 복구하는 방법으로 해결함.
		while ($row[$this->field_parent] != 0)
		//while (($row[$this->field_parent] != 0)&&($row[$this->field_parent] != $_parent))
		{
			$row = $this->getRowByKey($row[$this->field_parent], $isview_error, $q_where);
			$_parent = $row[$this->field_parent];
			$rows[] = $row;

			//echo "<HR>[getParents]" . $row['mn_depth'] . ". " . $row['mn_name'];
		}	// if()

		$this->unrockTable();


		// '깊이'를 최상위 데이타가 가장 먼저 정렬함.
		for ($i=0; $i<sizeof($rows)-1; $i++)
		{
			for ($j=$i+1; $j<sizeof($rows); $j++)
			{
				if ($rows[$i][$this->field_depth] > $rows[$j][$this->field_depth])
				{
					$_sort = $rows[$i];
					$rows[$i] = $rows[$j];
					$rows[$j] = $_sort;
				}	// if()
			}	// for()
		}	// for()

		//echo "<HR>[getParents/array_multisort]" . $rows[0]['mn_depth'] . ". " . $rows[0]['mn_name'];
		//echo "<HR>[getParents/array_multisort]" . $rows[1]['mn_depth'] . ". " . $rows[1]['mn_name'];
		//echo "<HR>[getParents/array_multisort]" . $rows[2]['mn_depth'] . ". " . $rows[2]['mn_name'];


		return $rows;
	}	// function()


################################################################################


	/**
		-- 데이타들의 전체 이름을 특정 구분자로 합한뒤 반환한다.
		array getParentsName (array parents, string split)
	*/

	function getParentsName($parents, $split=' >> ', $prefix='', $suffix='')
	{
		global $db;

		$parents_name = NULL;

		$parents_cnt = sizeof($parents);

		for ($i=0; $i<$parents_cnt; $i++)
		{
			$parents_name .= $prefix . $parents[$i][$this->field_name] . $suffix;
			if (($i+1) != $parents_cnt)
				$parents_name .= $split;
		}	// for()

		return $parents_name;
	}	// function()


################################################################################


	/**
		-- 특정키값의 갯수를 반환한다.
		int getCount (int key)
	*/

	function getCount($key)
	{
		global $db;

		$q = "
			SELECT
				COUNT(*)
			FROM
				" . $this->table . "
			WHERE
				" . $this->field_key . "='$key'
			";
		//echo $q;
		$row = db_get_one($q);

		return (int)$row;
	}	// function()


################################################################################


	/**
		-- 특정키값의 자식데이타의 갯수를 반환한다.
		int getChildCount (int key)
	*/

	function getChildCount($key, $q_where = NULL)
	{
		global $db;

		$q = "
			SELECT
				COUNT(*)
			FROM
				" . $this->table . "
			WHERE
				" . $this->field_parent . "='$key'
				$q_where
			";
		//echo $q;
		$row = db_get_one($q);

		return (int)$row;
	}	// function()


################################################################################
################################################################################


	/**
		-- 테이블에 락을 건다.
		rockTable ()

		참고사항)
			여러명이 데이타를 [깊이순서별 목록보기/추가/수정/삭제]할 경우의 오류를 방지하기 위함.
			락을 걸지 않을 경우 순서, 깊이, 부모데이타, 자식 데이타에 문제가 발생할 수 있음.
	*/

	function rockTable()
	{
		global $db;

		// Lock - READ:모든 쓰레드에서 쓰기 금지 | WRITE:다른 쓰레드에서 읽고, 쓰는 것을 금지시킴.
			#$q = "LOCK TABLES " . $this->table . " WRITE";
			//echo "<HR>".$q;
			#$db->parseExec($q);
			#$db->parseFree();
	}	// function()


################################################################################


	/**
		-- 테이블에서 락을 푼다.
		unrockTable ()
	*/

	function unrockTable()
	{
		global $db;

		// Lock - READ:모든 쓰레드에서 쓰기 금지 | WRITE:다른 쓰레드에서 읽고, 쓰는 것을 금지시킴.
			#$q = "UNLOCK TABLES";
			//echo "<HR>".$q;
			#$db->parseExec($q);
			#$db->parseFree();
	}	// function()


################################################################################
################################################################################


	/**
		-- 지정한 깊이사이의 데이타들을 깊이/순서대로 병합해서 반환한다.
		array getRowsByParent (int parent, int last_depth, int first_depth)
	*/

	function getRowsByParent($parent=0, $last_depth=0, $now_depth=1, $q_where = NULL)
	{
		global $db;

		$this->rockTable();

		if ($last_depth < 1)
			$last_depth = $this->getMaxDepth($q_where);

		$list = array();
		$list = $this->_getRowsByParent($list, $parent, $last_depth, $now_depth, NULL, $q_where);

		$this->unrockTable();

		return $list;
	}	// function()


################################################################################


	/**
		-- [클래스 내부함수]특정 깊이의 데이타들을 반환한다.
		array _getRowsByParent (array list, int parent, int last_depth, int now_depth)
	*/

	function _getRowsByParent($list, $parent=0, $last_depth, $now_depth=1, $fields=NULL, $q_where = NULL, $parent_full=NULL)
	{
		global $db;

		//$q_where2 = NULL;
		//if ($this->isuse_only == true)
		//	$q_where2 = $q_where . " AND " . $this->field_isuse . "='Y' ";
		//else
		$q_where2 = $q_where;

		$q = "
			SELECT
				*
			FROM
				" . $this->table . "
			WHERE
				" . $this->field_parent . "='$parent'
				AND " . $this->field_depth. "='$now_depth'
				$q_where2
			ORDER BY
				" . $this->field_sort . ", " . $this->field_name . "
			";
		//echo "<HR>[_getRowsByParent]" . $q;
		$rows = db_get_rows($q, true);
		$rows_cnt = sizeof($rows);

		if ($fields == NULL)
		{
			//$fields = $db->fetchFieldAll();
			foreach ($rows as $key=>$val)
			{
				$fields[] = $key;
			}	// foreach()
		}	// if()

		if ($rows_cnt > 0)
		{
			// 재귀적 호출
			if ($now_depth <= $last_depth)
			{
				$now_depth++;
				$list_cnt = (is_array($rows[$this->field_key])) ? sizeof($rows[$this->field_key]) : 0;
				for ($i=0; $i<$list_cnt; $i++)
				{
					// 모든결과값을 하나의 배열로 카테고리 출력순서대로 합침.(기본필드이외의 데이타도 함께 처리함)
					for ($j=0; $j<sizeof($fields); $j++)
					{
						$list[$fields[$j]][] = $rows[$fields[$j]][$i];
					}	// for()

					// [2025-11-24] 편지함 트리 전체 부모 경로
					$child_parent_full = null;
					if ($parent_full === null) {
						$child_parent_full = $rows['mb_id'][$i];
					} else {
						$child_parent_full = $parent_full . '-' . $rows['mb_id'][$i];
					}
//					echo 'id=' . $rows['mb_id'][$i] . ' / depth=' . $rows['mb_depth'][$i] . ' / child_parent_full=' . $child_parent_full . "<br>";
					$list['mb_parent_full'][] = $child_parent_full;

					// 자식데이타가 존재할때만 호출함.
					if (($list['mb_child_count'][] = $this->getChildCount($rows[$this->field_key][$i], $q_where)) > 0)
					{
						$list = $this->_getRowsByParent($list, $rows[$this->field_key][$i], $last_depth, $now_depth, $fields, $q_where, $child_parent_full);
					}	// if()
				}	// for()
			}	// if()
		}	// if()

		return $list;
	}	// function()


################################################################################

	/**
		-- 데이타를 추가한다.
		addRow (string name, int parent, string ext_fields, string ext_datas, string isuse)

		ex)
			$ext_fields = ", comment1, comment2";
			$ext_datas = ", '코멘트1', '코멘트2'";
	*/

	function addRow($name, $parent=0, $ext_fields=NULL, $ext_datas=NULL, $isuse='Y')
	{
		global $db;

		$this->rockTable();

		$error_msg = NULL;

		// set data
			if ($parent == 0)
			{
				$depth = 1;
			} else
			{
				$row = $this->getRowByKey($parent, false);
				if ($row == NULL)
				{
					$error_msg = "존재하지 않는 상위 ".$this->field_name_text."입니다.\\n\\n=> " . $parent;
					return $error_msg;
				}	// if()
				$depth = $row[$this->field_depth] + 1;
			}	// if()
			//$step = $this->getMaxStepByParent($parent) + 1;


		// db string
			$name = to_db_str($name, $this->field_name_length);


		// db insert
			$q = "
				INSERT INTO
					" . $this->table . "
					(
						" . $this->field_key . ",
						" . $this->field_parent . ",
						" . $this->field_depth . ",
						" . $this->field_name . ",
						$ext_fields
					)
				VALUES
					(
						".db_sqc_next($this->table).",
						'$parent',
						'$depth',
						'$name',
						$ext_datas
					)
				";
			//echo $q;
			db_query($q);

		$this->unrockTable();

		return $error_msg;
	}	// function()


################################################################################


	/**
		-- 일반 데이타(순서,이름,사용여부)를 수정한다.
		modifyRow (int key, string name, int parent, int step, string isuse, string ext_set, string ext_where)

		참고사항)
			순서는 잘못된 연산으로 인해 중복되더라도 정렬에만 쓰이므로 크게 문제되지 않는다.
			부모키가 변경될 경우 깊이와 순서는 자동 재지정.
			에러메세지가 있을때만 리턴값이 존재함.

		제약조건)
			부모관련 데이타(부모키,깊이)를 수정할때 자식데이타가 존재할 경우 수정불가.
			즉, 최하단에 위치하는 데이타만 위치변경 가능.

		ex)
			$ext_set = ", comment1='코멘트1'";
			$ext_where = " AND comment1='코멘트1' ";
	*/

	function modifyRow($key, $name, $parent=NULL, $step=NULL, $isuse=NULL, $ext_set=NULL, $ext_where=NULL)
	{
		global $db;

		$this->rockTable();

		$error_msg = NULL;

		// set data
			$ismodify_parent = false;
			$q_set = NULL;

			// 변경할 부모키를 넘겨받았고, 현재 부모키와 다를때.
				$row_current = $this->getRowByKey($key, false, $ext_where);

				// [2012-10-16]
				if ($key == $parent)
				{
					$error_msg = "자기 자신을 상위 ".$this->field_name_text."으로 설정할 수 없습니다.";
				} else
				{
					if ((strcmp($parent,NULL))&&($row_current[$this->field_parent] != $parent))
					{
						// 존재하는 부모키일때.  (단 최상위메뉴인 '0'은 제외)
							$row_parent = $this->getRowByKey($parent, false, $ext_where);
							if (($row_parent != NULL)||(!strcmp($parent,"0")))
							{
								// 현재키에 자식데이타가 없을때 업데이트 쿼리문 추가.
									$q_set .= ", " . $this->field_parent . "='" . $parent . "'";
									$q_set .= ", " . $this->field_depth . "='" . ($row_parent[$this->field_depth] + 1) . "'";
									$q_set .= ", " . $this->field_sort . "='" . ($this->getMaxStepByParent($parent) + 1) . "'";

									if ($this->getChildCount($key) < 1)
									{
										$ismodify_parent = true;
									} else
									{
										//$error_msg = "하위 조직이 존재하므로 위치를 변경할 수 없습니다.";
										// [2008-07-16] 하위조직이 존재하더라도 parent 는 변경되지 않으므로,  depth만 일괄 가감시킴.
										// [2012-10-16] 하위 그룹이 있는 그룹을 최상위 그룹으로 옮길 경우, 하위 그룹의 depth 가 갱신되지 않아 보이지 않는 문제 해결
										//$rows = $this->getRowsByParent($key, $last_depth=0, $first_depth=1, $ext_where);
										$rows = $this->getRowsByParent($key, $last_depth=0, $row_current[$this->field_depth]+1, $ext_where);
										if ($rows[$this->field_key])
										{
											$new_depth = $row_parent[$this->field_depth] - $row_current[$this->field_depth] + 1;
											$q = "
												UPDATE
													" . $this->table . "
												SET
													" . $this->field_depth . " = (" . $this->field_depth . " + (".$new_depth."))
												WHERE
													" . $this->field_key . " IN (".implode_keys($rows[$this->field_key]).")
													$ext_where
												";
											db_query($q);
										}	// if()
									}	// if()
							} else
							{
								$error_msg = "존재하지 않는 상위 ".$this->field_name_text."입니다.\\n\\n=> " . $parent;
							}	// if()
					}	// if()
				}	// if()



			// 부모키가 변경되지 않았을때 '순서' 업데이트 쿼리문 추가.
				if (($ismodify_parent == false)&&(strcmp($step,NULL))) {
					$q_set .= ", " . $this->field_sort . "='" . $step . "'";
				}

			//if (strcmp($isuse,NULL))
			//	$q_set .= ", " . $this->field_isuse . "='" . $isuse . "'";

		// db string
			$name = to_db_str($name, $this->field_name_length);


		// db update
			if ($error_msg === NULL)
			{
				$q = "
					UPDATE
						" . $this->table . "
					SET
						" . $this->field_name . "='$name'
						$q_set
						$ext_set
					WHERE
						" . $this->field_key . "='$key'
						$ext_where
					";
				db_query($q);
			}	// if()

		$this->unrockTable();

		return $error_msg;
	}	// function()


################################################################################


	/**
		-- 데이타를 삭제한다.
		delRow (int key, string ext_where)

		참고사항)
			에러메세지가 있을때만 리턴값이 존재함.

		제약조건)
			자식데이타가 존재할 경우 삭제불가.
			즉, 최하단에 위치하는 데이타만 삭제가능.

		ex)
			$ext_where = " AND comment1='코멘트1' ";
	*/

	function delRow($key, $ext_where=NULL)
	{
		global $db;

		$this->rockTable();

		$error_msg = NULL;

		// 현재키에 자식데이타가 없을때 삭제함.
			if ($this->getChildCount($key) < 1)
			{
				// db delete
					$q = "
						DELETE
						FROM
							" . $this->table . "
						WHERE
							" . $this->field_key . "='$key'
							$ext_where
						";
					//echo $q;
					db_query($q);
			} else
			{
				$error_msg = "하위 ".$this->field_name_text."이 존재하므로 삭제할 수 없습니다.";
			}	// if()

		$this->rockTable();

		return $error_msg;
	}	// function()


	function getRowsCount($cg_dataall)
	{
		return (is_array($cg_dataall[$this->field_key])) ? sizeof($cg_dataall[$this->field_key]) : 0;
	}	// function()

	function getSelectOption($cg_dataall, $field_key = false)
	{
		$cg_dataall_cnt = $this->getRowsCount($cg_dataall);
		$cg_dataall_option = array();
		for ($i=0; $i<$cg_dataall_cnt; $i++)
		{
			$_val = NULL;
			for ($j=1; $j<$cg_dataall[$this->field_depth][$i]; $j++)
			{
				$_val .= " &nbsp;&nbsp; ";
			}	// for()

			if ($_val != '')
				$_val .= "┗ ";

			if ($field_key == $cg_dataall[$this->field_key][$i])		// 선택된 상위메뉴일때
			{
				$_val .= $cg_dataall[$this->field_name][$i] . "(*)";
			} else
			{
				$_val .= $cg_dataall[$this->field_name][$i];
			}	// if()

			$cg_dataall_option['val'][] = $_val;
			$cg_dataall_option['key'][] = $cg_dataall[$this->field_key][$i];
		}	// for()

		return $cg_dataall_option;
	}	// function()


################################################################################


} // end class


################################################################################

?>