2015年1月26日 星期一

用 jQuery EasyUI 打造輕量級 CMS (十)

今天在開無聊的會時把應用程式上傳安裝功能大翻修, 拿掉了原先的登錄按鈕, 因為先上傳再登錄感覺多此一舉, 所以合併為上傳同時登錄.

首先在系統安裝程式 install.php 裡添加 sys_apps 資料表, 用來記錄應用程式的安裝資訊 :

    //建立 sys_apps 資料表
    $data_array["id"]="smallint(6) NOT NULL AUTO_INCREMENT PRIMARY KEY";
    $data_array["app_name"]="varchar(255)";        //應用程式名稱
    $data_array["installed"]="char(1)";            //已安裝 "Y"/"N"
    $data_array["show_tabs"]="char(1)";            //顯示頁籤 "Y"/"N"
    $data_array["tab_names"]="varchar(255)";       //頁籤名稱
    $data_array["table_names"]="varchar(255)";     //資料表名稱
    $data_array["remark"]="varchar(255)";          //安裝結果
    $result=create_table("sys_apps",$data_array);
    if ($result) {$msg .= "建立資料表 sys_apps ... 完成!<br>";}
    $data_array=NULL;

這裡欄位 tab_names 用來記錄應用程式的頁籤名稱 (以逗號隔開), 以便於應用程式 app.php 中讀取後產生頁籤的 div 元素. 欄位 table_names 則是記錄應安裝程式 app_install.php 本身所建立的資料表名稱 (也是以逗號隔開),, 以便要刪除該應用程式時可以讀取此欄位值, 刪除資料庫中所有此應用程式之資料表. 安裝成功後, 欄位 installed 會設定為 "Y" 表示已安裝, 避免二次安裝. 而欄位 show_tabs 是從 jQueryUI 架站機來的, 此處似乎用不到, 可刪除. 

系統頁籤資料表 sys_tabs 中也要加入一筆紀錄 :

    //插入 sys_tabs 之應用程式標籤
    $data_array["tab_name"]="apps";
    $data_array["tab_label"]="應用程式";
    $data_array["tab_link"]="sys.php?op=apps";
    $data_array["tab_level"]=9;
    $data_array["tab_tip"]="應用程式";
    $data_array["tab_order"]=96;
    $result=insert("sys_tabs", $data_array);
    $data_array=NULL;

在頁籤 apps 中, 其資料來源 (href) 是取自系統程式 sys.php 中的 apps 模組, 因此要在 sys.php 裡添加一個 case, 裡面主要是一個放頁籤內容的 div 元素 :

  case "apps" : {
?>
<div class="tab" title="應用程式">
</div>
<?php
    break;
    }

然後我們要在這個空的頁籤放一個 table 元素轉化的 datagrid, 用來表列已上傳登錄的程式 :

  case "apps" : {
?>
<div class="tab" title="應用程式">
  <!--應用程式 sys_apps 列表-->
  <table id="sys_apps" title="應用程式列表" style="width:auto" data-options="tools:'#apps_tools',toolbar:'#apps_toolbar'"></table>
  <div id="apps_tools"> 
    <a href="#" id="reload_apps" class="icon-reload" title="重新載入"></a>
  </div>
  <div id="apps_toolbar" style="text-align:right;padding:2px;">
    <a href="#" id="upload_app" class="easyui-linkbutton" data-options="iconCls:'icon-add'">上傳</a>
    <a href="#" id="install_app" class="easyui-linkbutton" data-options="iconCls:'icon-ok'">安裝</a>
    <a href="#" id="remove_app" class="easyui-linkbutton" data-options="iconCls:'icon-remove'">移除</a>
  </div>   
  <!--上傳應用程式表單對話框-->
  <div id="upload_app_dialog" class="easyui-dialog" title="上傳應用程式" style="width:420px;height:200px;"  data-options="closed:'true',buttons:'#upload_app_buttons'">
    <form id="upload_app_form" style="padding:15px" method="post" enctype="multipart/form-data" target="upload_target">
      <div style="margin:10px">
        <label style="width:60px;display:inline-block;">主檔案 : </label>
        <input name="uploader[]" id="main" class="easyui-filebox"  data-options="missingMessage:'此欄位為必填',required:true,buttonText:'選擇主檔案',prompt:'app.php'" style="width:270px">
      </div>
      <div style="margin:10px">
        <label style="width:60px;display:inline-block;">安裝檔 : </label>
        <input name="uploader[]" id="install" class="easyui-filebox"  data-options="missingMessage:'此欄位為必填',required:true,buttonText:'選擇安裝檔',prompt:'app_install.php'" style="width:270px">
      </div>
      <div>
        <iframe id="upload_target" name="upload_target" src="#"           style="width:0;height:0;border:0px solid #ffffff;">
        </iframe>
      </div>
    </form>
  </div>
  <div id="upload_app_buttons" style="padding-right:15px;">
    <a href="#" id="clear_app" class="easyui-linkbutton" iconCls="icon-clear" style="width:90px">重設</a>
    <a href="#" id="upload_app_go" class="easyui-linkbutton" iconCls="icon-ok" style="width:90px">上傳</a>
  </div>
</div>


這裡除了作為 datagrid 的 table 外, 還有一個上傳應用程式檔案的 form (預設是關閉的 closed:true), 其編碼類型 enctype 必須為 multipart/form-data, 而其 target 屬性則指定一個 iframe 元素, 亦即後端程式的回應要投射到 iframe 裡, 這樣做的原因是因為我們無從得知後端何時可完成上傳作業,   乾脆讓後端完成後自己把回應訊息用一個函式 丟給 iframe 去顯示, 這函式原先放在 sys.php 中, 在 Chrome/Firefox 均無問題, 但在 IE 就會有語法錯誤, 我判斷是原因可能是此函式用 Ajax 傳回之故, 為了解決此問題, 便將此函式移到上一層的  main.php 中了 :  

    <!-- 應用程式上傳回呼函式 -->
    //不可放在 sys.php 內 (IE 會語法錯誤)
    function upload_app_callback(status,msg) { //必須在最上層, 不可放在 $ 內
      $("#upload_app_dialog").dialog("close");  //關閉上傳對話框
      $("#sys_apps").datagrid("reload",{op:"list_apps"});  //重載 datagrid
      if (status=="success") { //顯示上傳結果
        var msg=msg + '檔案上傳全部成功! <br>';
        var icon="info";
        }
      else {
        var msg=msg + '檔案上傳失敗, 請重新上傳! <br>';
        var icon="error";
        }
      $.messager.alert("上傳結果",msg,icon);
      }

應用程式由主程式 app.php 與其安裝檔 app_install.php 組成, 我們用兩個 filebox 元件來選取要上傳的檔案, 其 name 均為 uploader[], 上傳後會放在根目錄的 apps 子目錄下.  上面的 apps 應用程式模組中的 datagrid 與上傳表單的控制程式如下 :

  $(function(){
    //應用程式 sys_apps
    $('#sys_apps').datagrid({
      columns:[[
        {field:'id',title:'id',sortable:true},
        {field:'app_name',title:'名稱',sortable:true},
        {field:'installed',title:'已安裝',sortable:true},
        {field:'show_tabs',title:'顯示頁籤',sortable:true},
        {field:'tab_names',title:'頁籤名稱',align:'center',sortable:true},
        {field:'table_names',title:'資料表名稱',sortable:true},
        {field:'remark',title:'備註',sortable:true}
        ]],
      url:"sys.php",
      queryParams:{op:"list_apps"},
      fitColumns:true,
      singleSelect:true,
      pagination:true,
      pageSize:10,
      rownumbers:true
      });
    $("#clear_app").bind("click",function(){
      $("#upload_app_form")[0].reset();
      });
    $("#upload_app_go").bind("click",function(){
      var main=$("#main").filebox("getText");
      main=main.substr(main.lastIndexOf("\\") + 1, main.length);
      var app_main=main.split(".")[0]; //主檔名
      var install=$("#install").filebox("getText");
      install=install.substr(install.lastIndexOf("\\") + 1, install.length);
      var install_main=install.split("_")[0]; //安裝主檔名
      var reg=/.php$/i; //副檔名均為 .php
      if (!reg.test(main) || !reg.test(install)) {
        var msg="主程式與其安裝檔必須均為 .php 檔!<br> 請重新選取.";
        $.messager.alert("訊息",msg,"error");
        return;
        }
      reg=/_install.php$/i; //過濾出安裝檔
      if (!reg.test(install)) {
        var msg="安裝檔檔名格式不正確!<br>格式 : APP_install.php<br>";
        $.messager.alert("訊息",msg,"error");
        return;    
        }    
      if (app_main != install_main) { //主檔名不同
        msg="此兩檔案並非同一組應用程式! <br>" +
            "主程式與其安裝檔之檔名格式 : " +
            "APP.php 與 APP_install.php<br>請重新選取.<br>";
        $.messager.alert("訊息",msg,"error");
        return;
        } //end of if
      reg=/\W/g; //非英數字與底線
      if (reg.test(app_main) || reg.test(install_main)) {
        var msg="應用程式及其安裝檔檔名必須均為英數字或底線!<br>" +
                "請重新選取.<br>"; ;
        $.messager.alert("訊息",msg,"error");
        return;    
        }
      $("#upload_app_form").form("submit",{
        url:"sys.php?op=upload_app&app_main=" + app_main
        });
      });
    $("#upload_app").bind("click",function(){ //顯示上傳表單
      $("#upload_app_dialog").dialog("open").dialog("setTitle","上傳應用程式");
      $("#upload_app_form").form("clear");
      });
    $("#reload_apps").bind("click",function(){
      $("#sys_apps").datagrid("load",{op:"list_apps"});
      });
    $("#install_app").bind("click",function(){
      var row=$("#sys_apps").datagrid("getSelected");
      if (row) {
        if (row.installed=="N") {
          var url="apps/" + row.app_name + "_install.php";
          $.ajax({
            type:"POST",
            url:url,
            cache:false,
            dataType:"json",
            success:function(data) {
              $("#sys_apps").datagrid("reload",{op:"list_apps"}); //更新列表
              if (data.result=="success") {
                $.messager.alert("訊息","應用程式安裝成功!","info");
                }
              else {$.messager.alert("訊息",data.error,"error");}              
              },
            error:function(xhr, thrownError) {
              var msg='<p>應用程式安裝失敗!<br>狀態 : ' +
                      xhr.status + " - " + thrownError + '</p>';
              $.messager.alert("訊息",msg,"error");                      
              }
            });
          }
        else {$.messager.alert("訊息","此應用程式已安裝過了!","warning");}
        }
      else {$.messager.alert("訊息","請選擇要安裝的應用程式!","info");}
      });
    $("#remove_app").bind("click",function(){
      var row=$("#sys_apps").datagrid("getSelected");
      if (row) {
        $.messager.confirm("確認","確定要刪除這個應用程式嗎?",function(btn){
          if (btn){
            var params={op:"remove_app",id:row.id};
            var callback=function(data){
              if (data.status==="success"){
                $("#sys_apps").datagrid("reload",{op:"list_apps"});
                }
              else {$.messager.alert("訊息",data.msg,"error");}          
              };              
            $.post("sys.php",params,callback,"json");
            }
          })
        }
      else {$.messager.alert("訊息","請選擇要刪除的應用程式!","info");}
      });
    }); //end of jQuery

首先是 datagrid 的設定, 資料來源為 sys.php 中的 list_apps 模組, 其程式如下 :

  case "list_apps" : {
    $page=isset($_REQUEST['page']) ? intval($_REQUEST['page']) : 1;
    $rows=isset($_REQUEST['rows']) ? intval($_REQUEST['rows']) : 10;
    $sort=isset($_REQUEST['sort']) ? $_REQUEST['sort'] : 'id';
    $order=isset($_REQUEST['order']) ? $_REQUEST['order'] : 'asc';
    if (isset($_REQUEST['search_field'])) { //有 search
      $where="WHERE ".$_REQUEST['search_field']." LIKE '%".
             $_REQUEST['search_what']."%'";
      }
    else {$where="";} //無 search
    $start=($page-1) * $rows;  //本頁第一個列索引 (0 起始)
    $SQL="SELECT COUNT(*) FROM `sys_apps`";
    $RS=run_sql($SQL);
    $total=$RS[0][0]; //紀錄總筆數
    $SQL="SELECT * FROM sys_apps ".$where." ORDER BY ".
         $sort." ".$order." LIMIT ".$start.",".$rows;
    $RS=run_sql($SQL);
    $apps=Array();
    if (is_array($RS)) {
      for ($i=0; $i<count($RS); $i++) {
        $apps[$i]=Array("id" => $RS[$i]["id"],
                        "app_name" => $RS[$i]["app_name"],
                        "installed" => $RS[$i]["installed"],
                        "show_tabs" => $RS[$i]["show_tabs"],
                        "tab_names" => $RS[$i]["tab_names"],
                        "table_names" => $RS[$i]["table_names"],
                        "remark" => $RS[$i]["remark"]
                        );
        }
      }
    $arr=array("total" => $total, "rows" => $apps);
    echo json_encode($arr);
    break;
    }

這邊其實用不到 search 功能 (因為應用程式不會很多, 不需要, 甚至根本不需要分頁, 但我是整組複製過來改, 懶得刪除了).

當按下上傳鈕 upload_app 時, 會顯示上傳表單 :

    $("#upload_app").bind("click",function(){ //顯示上傳表單
      $("#upload_app_dialog").dialog("open").dialog("setTitle","上傳應用程式");
      $("#upload_app_form").form("clear");
      });


按檔案盒右側的按鈕會彈出檔案選取視窗, 分別選好主程式與安裝程式後, 按上傳即可, 下圖是上傳工作日誌應用程式 :


注意, 選好檔案後, filebox 的文字欄位顯示的不是真實的位址, 而是一律以 fakepath 代替. 按鈕 upload_app_go 的 click 事件處理會先檢查上傳檔名是否符合既定的格式 (APP.php 與 APP_install.php), 首先必須從檔案盒的文字欄位取出路徑與檔名, 這必須使用 EasyUI 的 filebox 繼承自 textbox 的 getText 方法, 不能用 jQuery 的 val() 方法, 因為 EasyUI 又對 jQuery 物件進行了包裝. 然後用 lastIndexOf 取出最後一個倒斜線後面的檔名, 再辨別是否副檔名均為 php, 安裝檔是否以 "_install.php" 結尾等等, 通過後就將表單附帶應用程式主檔名提交給後端 sys.php 的 upload_app 模組處理 :

  case "upload_app" : { //ajax:處理 app 檔案上傳用
    $status="success";       //預設上傳成功
    $msg="";                 //執行結果字串
    $upload_dir="./apps/";   //設定上傳目錄
    $result=upload_files("uploader", $upload_dir);
    if (is_array($result)) { //成功傳回陣列
      for ($i=0; $i<count($result); $i++) {
        $msg .= $result[$i]["name"]." 上傳成功(".$result[$i]["size"].
                " bytes)<br>";
        }
      //登錄於 sys_apps 資料表
      $data_array["app_name"]=$_REQUEST["app_main"];
      $data_array["installed"]="N";
      $data_array["show_tabs"]="N";    //安裝時更新
      $data_array["tab_names"]="";     //安裝時填入
      $data_array["table_names"]="";   //安裝時填入
      $data_array["remark"]="";        //安裝時更新
      $result=insert("sys_apps", $data_array);
      }
    else { //上傳失敗
      $msg .= "檔案上傳失敗 : <br>".$result;
      $status="failure";
      } //end of else
    $para='"'.$status.'","'.$msg.'"'; //回傳回呼函式之參數
?>
<!-- 輸出 script 到 $op=apps 中的 iframe 並執行其中的回呼函式 -->
<script language="javascript" type="text/javascript">
  window.top.window.upload_app_callback(<?php echo $para; ?>);
</script>
<?php
    break;
    }

此模組主要是呼叫 upload_files() 這個上傳函式來處理檔案上傳, 成功的話就在 sys_apps 資料表中紀錄此應用程式檔案名稱與未安裝狀態, 最後把上傳結果放在呼叫最上層方法 upload_app_callback() 的參數字串 $para 中, 傳回給要求者 (sys.php 的 apps 模組). 這樣就會執行此最上層回呼函式來顯示上傳結果. 這個 upload_app_callback() 函式是放在版面布局程式 main.php 中, 已如上述.

上面這個上傳程式的主角是 upload_files() 函式, 收錄在 lib/files.php 函式庫中 :

/*-----------------------------------------------------------------------------
upload_files($uploader,$path="./")
功能 :
  此函數將上傳之多個檔案, 由暫存區移至指定路徑下.
參數 :                                
  $uploader : 上傳元件名稱, 乃陣列形式, 例如下列 input 中之 name 值
              檔案1 : <input type="file" name="uploader[]">
              檔案2 : <input type="file" name="uploader[]">
              上傳表單之 enctype 須設為 multipart/form-data :
              <form action="upload.php" method="post"
              enctype="multipart/form-data">
  $path     : 檔案儲存路徑, 例如 "./images" (預設值為目前程式所在目錄 ./)
傳回值 :
  成功傳回一個二維陣列, 失敗傳回錯誤字串. 陣列第一維為數字索引, 第二維為關聯式,
  有三個元素 : 例如第一個上傳檔案 :
  $result[0]["name"]=檔案名稱
  $result[0]["type"]=檔案類型
  $result[0]["size"]=檔案大小
範例 :
  <input type="file" name="uploader[]">
  $result=upload_files("uploader", "./images");
  for ($i=0; $i<count($result); $i++) {
       echo $result[$i]["name"]."上傳成功(".$result[$i]["size"]." bytes)<br>";
       }
-----------------------------------------------------------------------------*/
function upload_files($uploader,$path="./") {
  $counts=count($_FILES[$uploader]["name"]); //上傳檔案數
  for ($i=0; $i<$counts; $i++) {
       if ($_FILES[$uploader]["error"][$i]==0) { //上傳成功
           $file_name=$_FILES[$uploader]["name"][$i];
           $tmp_name=$_FILES[$uploader]["tmp_name"][$i];
           $new_name=$path.mb_convert_encoding($file_name,"big5","utf-8");
           if (move_uploaded_file($tmp_name, $new_name)) { //移動成功
               $result[$i]["name"]=$_FILES[$uploader]["name"][$i];
               $result[$i]["type"]=$_FILES[$uploader]["type"][$i];
               $result[$i]["size"]=$_FILES[$uploader]["size"][$i];
               } //end of if
           else {$result.=$file_name." 移動失敗<br>";} //移動失敗
           } //end of if
       else {$result.=$file_name." 上傳失敗<br>";} //上傳失敗
       } //end of for
  return $result;
  }

這個函式與之前在檔案上傳功能時用的 upload_file() 不同之處在於 upload_files() 可傳多檔, 而 upload_file() 只能傳單檔. 所以這裡必須有個迴圈來逐一處理上傳的每個檔案. PHP 函式 mb_convert_encoding() 用來處理中文檔名問題, 但這裡用不到 (檔名僅允許英數字與底線).

上傳成功時結果如下 :


這時點選該列應用程式, 再按安裝鈕, 就會執行上面 Script 中的事件處理程序 :

$("#install_app").bind("click",function(){...參考上面}

此程式會將上傳對話框中的表單以 Ajax 方式提交給已上傳到 ./apps/ 下的 APP_install.php 安裝程式執行, 該安裝程式必須將本身所建立的資料表名稱填入系統資料表 sys_apps 的 table_names 欄位 (以逗號隔開), 以及將所有自己的頁籤填入 tab_names 欄位 (這是繼承舊系統規劃而來, 用不到, 以後可能刪除), 然後傳回執行結果. 由於安裝程式可能出錯, 因此這裡採用 jQuery 最低層的 ajax() 方法來執行非同步請求, 以便能取得較多的錯誤訊息 :

          var url="apps/" + row.app_name + "_install.php";
          $.ajax({... 參考上面});

應用程式安裝成功後, 除了顯示結果訊息外, 同時也重新載入 datagrid 內容, 這時就會顯示已安裝 :


這個安裝程式 APP_install.php 範本如下 :

<?php
/*-----------------------------------------------------------------------------
Title        : 應用程式安裝檔
Author       : Tony
Version      : v1.0.0
Prototype    : 2015-01-07
Last Updated : 2015-01-09
Usage        : 安裝應用程式所使用之資料表, 填入初始值
Note         :
-----------------------------------------------------------------------------*/
/*=== 系統固定的部分 (勿修改) ===*/
session_start(); //啟動 session 功能
header('Content-type: text/html; charset=utf-8');
//檢查是否已登入, 否則回登入畫面
if (!isset($_SESSION["user_account"])) {header("Location: index.php");}
//設定台北時間
date_default_timezone_set("Asia/Taipei");
//匯入資料庫設定與函式庫
require_once("../db.php");           //匯入資料庫設定檔 (必須)
require_once("../lib/mysql.php");    //匯入資料庫模組   (必須)
//變數設定
$success=FALSE;   //整體成功或失敗旗標
$error=Array();   //儲存錯誤訊息用
$tabs=Array();    //儲存頁籤用
$tables=Array();  //儲存表單用

/*=== 應用程式頁籤範本 (只改 APP 名稱 & 複製修改) ===*/
//建立 APP_tabs 資料表 (必須)
$tables[]="APP_tabs";  //"APP" 要改
$data_array["id"]="smallint(6) NOT NULL AUTO_INCREMENT PRIMARY KEY";
$data_array["tab_name"]="varchar(255)";
$data_array["tab_label"]="varchar(255)";
$data_array["tab_link"]="varchar(255)";
$data_array["tab_level"]="tinyint(4)"; //1 (使用者) ~9 (管理者)
$data_array["tab_tip"]="varchar(255)";
$data_array["tab_order"]="tinyint(4)";
$result=create_table("APP_tabs",$data_array);    //"APP" 要改
if ($result===TRUE) {$success=TRUE;}
else {$error[]="建立資料表 APP_tabs ... 失敗!";} //"APP" 要改
$data_array=NULL;

//插入 APP_tabs (範本)
$tabs[]="TAB1";  //顯示頁籤標題用
$data_array["tab_name"]="TAB1";
$data_array["tab_label"]="TAB1";
$data_array["tab_link"]="apps/APP.php?op=TAB1";  //"APP" 要改
$data_array["tab_level"]=1;
$data_array["tab_tip"]="TAB1";
$data_array["tab_order"]=1;
$result=insert("APP_tabs", $data_array);         //"APP" 要改
if ($result===TRUE) {$success=TRUE;}
else {$error[]="插入資料表 APP_tabs ... 失敗!";}  //"APP" 要改
$data_array=NULL;

//插入 APP_tabs (範本)
$tabs[]="TAB2";  //顯示頁籤標題用
$data_array["tab_name"]="TAB2";
$data_array["tab_label"]="TAB2";
$data_array["tab_link"]="apps/APP.php?op=TAB2";  //"APP" 要改
$data_array["tab_level"]=1;
$data_array["tab_tip"]="TAB2";
$data_array["tab_order"]=1;
$result=insert("APP_tabs", $data_array);         //"APP" 要改
if ($result===TRUE) {$success=TRUE;}
else {$error[]="插入資料表 APP_tabs ... 失敗!";} //"APP" 要改
$data_array=NULL;

/*--- 新增其他頁籤請複製上面的 TAB1/TAB2 範本於此改寫 ---*/

/*=== 安裝其他資料表範本 (請複製修改, 完成後刪除範本) ===*/
//建立 APP_table1 資料表 (範本)
$tables[]="APP_table1"; //刪除 APP 時用到 (必須)
$data_array["id"]="int(10) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY";
$data_array["field1"]="varchar(255)";
$data_array["field2"]="char(1)";
$data_array["field3"]="text";      
$data_array["field4"]="date";
$data_array["field5"]="datetime";
$data_array["field6"]="smallint(6) unsigned NOT NULL DEFAULT '0'";
$data_array["field7"]="tinyint(3) unsigned NOT NULL DEFAULT '10'";
$data_array["field8"]="int(10) unsigned";          
$data_array["field9"]="bigint(20) unsigned";                        
$data_array["field10"]="float unsigned";
$result=create_table("APP_table1",$data_array);
if ($result===TRUE) {$success=TRUE;}
else {$error[]="建立資料表 APP_table1 ... 失敗!";} //"APP_table1" 要改
$data_array=NULL;

//插入 APP_table1 (AUTO_INCREMENT 不用填) (範本)
$SQL="INSERT INTO `app_table1` (".
     "`field1`,".
     "`field2`,".
     "`field3`,".
     "`field4`,".
     "`field5`,".
     "`field6`,".
     "`field7`,".
     "`field8`,".
     "`field9`,".
     "`field10`".
     "`) VALUES (".
     "'',".
     "'',".
     "'',".
     "'',".
     "'',".
     ",".
     ",".
     ",".
     ",".
     "".
     ")";
$result=run_sql($SQL);
if ($result===TRUE) {$success=TRUE;}
else {$error[]="插入資料表 APP_table1 ... 失敗!";} //"APP_table1" 要改
$data_array=NULL;

/*--- 新增其他資料表請複製上面的 APP_table1 範本於此改寫 ---*/

/*=== 更新系統 sys_apps 資料表 (只改 APP 名稱)===*/
//更新系統 sys_apps 資料表的 app_name='程式名稱' 欄位
$data_array["installed"]="Y";                  //已安裝 "Y"/"N"
$data_array["show_tabs"]="Y";                  //顯示頁籤 "Y"/"N"
$data_array["tab_names"]=join(",",$tabs);      //儲存 tabs 以便產生頁籤
$data_array["table_names"]=join(",",$tables);  //儲存 tables 以便刪除時清理
$data_array["remark"]="OK";                    //安裝結果
$result=update("sys_apps", $data_array, "app_name", "APP"); //"APP" 要改
if ($result===TRUE) {$success=TRUE;}
else {$error[]="更新資料表 sys_apps ... 失敗!";}
$data_array=NULL;

//在 sys_header_links 插入應用程式入口超連結
$data_array["title"]="APP";             //"APP" 要改            
$data_array["url"]="javascript:APP()";  //"APP" 要改                
$data_array["target"]="_self";
$data_array["sequence"]=1;
$data_array["hint"]="APP";              //"APP" 要改
$result=insert("sys_header_links", $data_array);
if ($result===TRUE) {$success=TRUE;}
else {$error[]="插入資料表 sys_header_links ... 失敗!";}
$data_array=NULL;

/*====== 輸出 JSON 格式之安裝結果 (不用改) ======*/
$arr["result"]=$success ? "success" : "failure";
$arr["error"]=join("<br>",$error);
echo json_encode($arr);
?>

注意,這裡我們用 join() 函式把所記錄的資料表名稱以逗號串接後寫入 table_names 欄位裡, 以備將來要刪除應用程式時斬草除根之用.

而主程式 APP.php 範本如下 :

<?php
/*-----------------------------------------------------------------------------
Title        : 應用程式
Translator   : Tony
Version      : v1.0.0
Prototype    : 2015-01-05
Last Updated : 2015-01-05
Usage        : 應用程式的主要功能寫在此檔中
Note         :
請根據安裝檔內寫入 tabs 的各頁籤 $tab 與各作業 $op 撰寫功能
Ajax/DataTable 的 url 格式 :
$url="apps/app.php?op=get_xxxx";
$url="apps/app.php?op=list_xxxx";
$url="apps/app.php?op=add_xxxx";
$url="apps/app.php?op=remove_xxxx";
$url="apps/app.php?op=update_xxxx";
-----------------------------------------------------------------------------*/
session_start(); //必須啟動才能用 session
header('Content-Type: text/html;charset=UTF-8');
require_once("../db.php");           //匯入資料庫設定檔 (必須)
require_once("../lib/mysql.php");    //匯入資料庫模組   (必須)
require_once("../lib/tools.php");    //匯入工具模組     (選項)
require_once("../lib/parse.php");    //匯入剖析模組     (選項)
require_once("../lib/file.php");     //匯入file模組     (選項)
require_once("../lib/http.php");     //匯入http模組     (選項)
$op=$_REQUEST["op"];   //功能
switch ($op) {
  case "TAB1" : { //for ajax
    echo "Hello TAB1! The time is ".date("Y-m-d H:i:s");
    break;
    }
  case "TAB2" : { //for ajax
    echo "Hello TAB2! The time is ".date("Y-m-d H:i:s");
    break;
    }
  default : { //for rendering
?>
    <div id="APP-tab" class="easyui-tabs" data-options="fit:'true'">
<?php
    $RS=search("APP_tabs");  //"APP" 要改
    if (is_array($RS)) {
      for ($i=0; $i<count($RS); $i++) {
?>
      <div class="tab" title="<?php echo $RS[$i]["tab_name"] ?>" data-options="href:'<?php echo $RS[$i]["tab_link"] ?>'">
      </div>
<?php
        } //for
      } //if
    } //default
  } //switch
?>
    </div>

以後要寫任何掛在此系統上的應用程式就用此兩範本去擴展即可.

最後, 點選要刪除的 app 列, 再按右上角的刪除鈕就可以刪除應用程式, 它會執行下列事件處理程序 :

    $("#remove_app").bind("click",function(){ ... 參考上面});

經過確認後會呼叫 sys.php 的 remove_app 模組來處理刪除工作 :

  case "remove_app" : {
    $id=$_REQUEST["id"];
    $RS=search("sys_apps","id",$id);
    $status="success";
    $msg="";
    if (is_array($RS)) { //有登錄
      //刪除此 app 所有資料表
      $app_name=$RS[0]["app_name"];
      $installed=$RS[0]["installed"];
      if ($installed=="Y") { //已安裝
        $tables=explode(",",$RS[0]["table_names"]);
        for ($i=0; $i<count($tables); $i++) {
          $result=drop_table($tables[$i]);
          if ($result===FALSE) {
            $status="failure";
            $msg .= "刪除資料表 ".$tables[$i]." 失敗<br>";
            }
          }
        //從 sys_header_links 刪除此 app 超連結
        $result=delete_record("sys_header_links","title",$app_name);
        if ($result===FALSE) {
          $status="failure";
          $msg .= "刪除應用程式在 sys_header_links 之超連結失敗<br>";
          }
        } //installed
      //從 sys_apps 刪除此 app 登錄
      $result=delete_record("sys_apps","id",$id);
      if ($result===FALSE) {
        $status="failure";
        $msg .= "刪除應用程式在 sys_apps 之登錄失敗<br>";
        }
      //刪除應用程式檔案
      $result=delete_file("./apps/".$app_name.".php");
      if ($result==FALSE) {
        $status="failure";
        $msg .= $app_name.".php 刪除失敗<br>";
        }
      $result=delete_file("./apps/".$app_name."_install.php");
      if ($result==FALSE) {
        $status="failure";
        $msg .= $app_name."_install.php 刪除失敗<br>";
        }
      } //registered
    else { //未上傳登錄
      $status="failure";
      $msg="刪除失敗 : 應用程式不存在!";
      }
    $arr["status"]=$status;
    $arr["msg"]=$msg;
    echo json_encode($arr);
    break;
    }


這裡我們先去讀取 sys_apps 中該應用程式的 table_names 欄位, 用 explode() 方法拆解出此應用所屬之資料表後, 在迴圈中用 drop_table 來刪除資料表. 另外就是刪除 sys_header_links 上的超連結紀錄. 最後去 apps/ 目錄下刪除此應用程式及其安裝檔案即可, 其中使用了收錄在 lib/file.php 函式庫中的 delete_file(), 此函式其實很簡單, 就是 PHP 的 unlink :

/*-----------------------------------------------------------------------------
delete_file($filename)
功能 :
  此函數指定路徑下之檔案與目錄列表.
參數 :
  $filename : 檔案名稱 (相對路徑, 例如 log/visitors.txt)
傳回值 :
  成功傳回 TRUE, 失敗傳回 FALSE.
範例 :
  $result=delete_file("a.txt");
  $result=delete_file("./a.txt");
  $result=delete_file("files/a.txt");
-----------------------------------------------------------------------------*/
function delete_file($filename) {
  return unlink($filename);
  }

因今天 (1/27) 下午才會進辦公室, 所以趁著早上閒閒把應用程式部分記錄完了, 希望以後要回頭參考時自己還看得懂, 雖然花了很多時間來紀錄, 但是值得, 因為這是此簡易架站系統最重要的部分, 而且隨著年華老去, 記性已不若以往, 上個月寫的東西這個月可能就印象模糊了, 唉.

3 則留言 :

Unknown 提到...

Dear Tony, 近日研究 EasyUI 發現您的 blog有非常多有用的資源,也看到有其他朋友向您索取 EsyUICMS 源碼,是否方便也向您索取一份研究學習呢?謝謝。
jack3389@gmail.com

小狐狸事務所 提到...

Dear Jack,

源碼沒問題, 隨信附上目前的 zip 檔, 獻醜了. 因最近分心研究 Arduino, EasyUICMS 還有一些功能未寫完, 且 PHP & MySQL 資安部分尚未周全, 等我 Arduino 搞定後回師再戰. 有發現 BUG 記得告訴我.

Tony 敬上

Unknown 提到...

Hi Tony,

好像沒有收到您的 zip 檔,再麻煩您寄一次囉。

謝謝