大学課題のぐち

大学のPHP課題がなぞだったのでその愚痴と提出課題をあれやこれやと...
[カカワップ氏](http://kakawup.github.io/)に協力いただいたので、その報告もかねて。

まず問題設定から見ていきましょう。
CSV形式で保存された名簿から，/etc/passwd 形式のファイルを生成する．
これが課題の目的。
CSVファイルのフォーマットは【"学生番号","氏名"，"name"】
passwdファイルは【login_name:*:UID:GID::::GECOS:home_dir:login_shell】
たとえば
`"25115773","山下　七海","Yamasita Nanami"`
に対して、
`yamasita\:X\:251773:2511::::yamasita nanami:/home/yamasita:/bin/sh
`
というのを作成する。がいくつか制約が。

```
login_nameに関して
・すべて小文字，文字は「アルファベット，数字，_，-，.」のみ使用可能で，先頭はアルファベットであること。
・重複がないこと
・最大8文字まで
・重複しない限り，姓　のローマ字表記を基本とすること．
・ローマ字表記を統一すること
si->shi, sya->sha, syu->shu, syo->sho, jya->ja, jyu->ju, jyo->jo, tu->tsu, ti->chi, tya->cha, tyu->chu, tyo->cho, hu->fu
```

ここで疑問。ユーザー名ってシステム側で勝手に変えていいの？？
もちろん課題だから規則に従った名前にはします。それでもシステム側から提示された候補しか利用できないのはおかしい。（課題ではローマ字変換ようするに、上の逆を用いて候補を提示するようなものもありました）
というわけで！！ブラウザを通してユーザーに名前をもらおうじゃないかと。
要するに

```
"25115774","奥野　香耶","Okuno Kaya"
"25115775","奥野　香耶","Okuno Kaya"
"25115776","奥野　香耶","Okuno Kaya"
```

に対して、

```
okuno\:X\:251773:2511::::okuno kaya:/home/okuno:/bin/sh <-普通に
okuno_ka\:X\:251773:2511::::okuno kaya:/home/okuno:/bin/sh <-重複するから8文字以下で姓_名にした
okuno???\:X\:251773:2511::::okuno kaya:/home/okuno???:/bin/sh <-これどうする？
```

これをブラウザ側でユーザーに聞こうというわけです。
どうやって？？とりあえずformでpostでもしてりゃいいんじゃね？と
課題がindex.phpを提出して、それにブラウザでアクセスすることでpasswdファイルを作成できるようにすることでした。
で、普通にformつかうと、画面が更新されるわけでして、そうしたら、phpファイルの処理が頭から再開させるわけで、重複が複数人あったりするとどうなる？とかいろいろ考えて、悩んで、[WUG](http://wug-portal.jp/)以外考えられない頭になってしまってカカワップ氏に質問させてもらったわけで、それで得たのがAjaxなわけです。
本当にカカワップ氏のおかげで課題が出来たようなもんなんで本当に感謝感謝です。
カカワップ氏には感謝の印として、この[山下七海の声優ゆめ日記](https://shop.gakken.co.jp/shop/order/k_ok/goodsdisp.asp?code=3200002588)プレゼントしません。

というわけで最終的に仕様なんですが、phpファイルを二つに分けました。
1つ目で最初の処理をして、2つ目で完成させる感じです。
ブラウザからcsvファイルのパスを受け取って、それを読み込みpasswdファイルのテンプレを作成。重複したところは\_\_collision__を頭につけて、ユーザー名と、学籍番号、GIDをブラウザに返送。ブラウザでユーザーに入力してもらって2つ目のphpファイルに送信。受け取ったものを元に、最終的なpasswdファイルを作る。ここで再び重複が起きると、サイドブラウザに送信して、2つ目のphpのほうでサイド処理する。結構ガバガバな実装だったりしますが、そこはまぁねｗｗｗｗ

```html:index.php
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <div>
            <!-- Main -->
            <p>input CSV file name( student-list.txt )</p>
            <input id="fileName" type="text" class="form-control input-lg" /><!--入力スペース-->
            <span class="input-group-btn">
                <input id="submitBtn" class="btn btn-submit" value="Submit" type="submit" /><!--ボタン-->
            </span>
            <p id ="message"></p><!--メッセージ表示用-->
            <!-- Script --><!--こっからスクリプト：JS-->
            <script src="http://code.jquery.com/jquery-2.1.1.min.js"></script><!--jQueryのロード-->
            <script>
               var responseData = [];
                $("#submitBtn").click(function() {//ボタンが押されたときの処理
                 data = { "fileName": $("#fileName").val() };//ファイル名をJSONで送信
                 post(data,"php1.php");//postメソッドの呼び出し～
                 return false;
               });
               function post(data,url){//ポストリクエストの関数
                 $.ajax({
                   type: "post",
                   url: url,
                   data: JSON.stringify(data),//でーたをジェイソンに変換
                   contentType: 'application/JSON',//その他オプション
                   dataType : 'JSON',
                   scriptCharset: 'utf-8',
                   success: function(response) {//成功したときに呼ばれる
                        responseData=[];
                        if(check(JSON.stringify(response))){//jsonの中身ちぇっく
                            ask(JSON.stringify(response));//入ってたらそれにしたがってユーザーネームをもらう。
                            response=null;
                        }else{//空なら成功って事。
                            $("#message").html("<p>passwd file successfly genelated</p>");
                        }
                    },
                   error: function(xhr,textStatus,err) {//失敗したら呼ばれるjson以外のエラーも有るよ。
                        alert("errorたぶんJSON");//エラーメッセージ↓
                        $("#message").append("<br>readyState: " + xhr.responseText);
                        $("#message").append("<br>readyState: " + xhr.readyState);
                        $("#message").append("<br>responseText: "+ xhr.responseText);
                        $("#message").append("<br>status: " + xhr.status);
                        $("#message").append("<br>text status: " + textStatus);
                        $("#message").append("<br>error: " + err);
                   }                                                                                                         
                 });                                                                                                         
               }
               function check(res){//jsonが空なら重複がないと判定
                   var data = JSON.parse(res);//jsonをパース
                   var array = Array.prototype.slice.call(data);//配列に変換：これナイトもっと面倒になる。
                   if(array.length>0){
                       return true;
                   }
                   return false;
               }                   
               function ask(res){
                   var data = JSON.parse(res);
                   var array = Array.prototype.slice.call(data);
                   for(var i =0;i<array.length;i++){
                       askPrompt(array[i]);//プロンプトを出してユーザー名をもらう
                   }
                   post(responseData,"php2.php");//JSONを積んだ配列を送信
               }
               function askPrompt(data){//プロンプトのラッパー関数
                   _name = data.name;
                   id = data.id;
                   example1 = data.ex1;
                   example2 = data.ex2;
                    while(true){//nullとか空文字をける。8文字制限もここでやる。
                        var name=prompt('残念ユーザー名('+_name+':'+id+')が重複しました。別のものを入力してください。※8文字以下(例:'+ ((example2=="null")?example1 : example1+","+example2 ) +' etc...)', ((example2=="null")?example1 : example2));
                        if(name!==null && name!=='' && name.length <9){
                            break;
                        }else{
                            alert("input error");
                        }
                    }
                    responseData.push({name:_name,newName:name,id:id});//Json形式配列につんどく
            }
            </script>
        </div>
    </body>
</html>
```

実際これ提出はphpファイルなんすけどねｗｗｗ
JsonデコードしたのJSでは配列じゃなくてObjectの塊みたいな扱い受けて、ぜんぜん使い勝手悪いんすわ。だヵら、`Array.prototype.slice.call`つかって配列に。いろいろやり方有るのかな？って感じです。気になるならggってください。web初心者の自分には今得た知識で十分です。ちなみにこれ配列に出来なかったら、JSだから平気でundefinedとかなってます。JS怖すぎ。
と、Ajaxのerror時の関数、これリクエスが成功したかどうかだけじゃないんですね。
ようするに、error関数が呼ばれて、`xhr.status`が200なんて良く有るんですね。成功だけど失敗ツンデレさんかな？？と思ったんですけど、これリクエスト成功したぜって話で、実際JSONの構文おかしかったりすると、parseErrorとかがでて、通信は成功だけどもらったのは何か変だぜとかも、errorなんですね。
あおとは、自分が珍しく2項演算子（？演算子）使ってる。
わんらいなーとかいうやつですか？知りません。別に競技プログラマじゃないんで。

あ、もうWUG要素ないっすよ。たぶん。

```php:php1.php
<?php
/*
 *うるせえファイル名適当とか言うな。
 *うるせえ変数名意味不明とか言うな俺もわからんのじゃｗｗｗｗｗ
 */



//ajax以外の接続を拒否
$request = isset($_SERVER['HTTP_X_REQUESTED_WITH']) ? strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) : '';
if($request !== 'xmlhttprequest'){
    echo "error";
    exit;
}
 
$jsonString = file_get_contents('php://input');//jsonを取得
$json = json_decode($jsonString,true);//jsonをデコード、配列にぶっこむ
$fileName  = $json["fileName"];//jsonからファイル名を取得
if(file_exists("./etc")===False){//etcディレクトリが有るかチェック
    if(mkdir("./etc",0666)===False){//作る無理だったらゲームオーバー
        echo "<p>directiry error</p>";
        exit;
    }
}
if(($passFile =  fopen("./etc/.passwdtmp", "w"))===False){//パスファイルのテンプれファイルを作成
    echo "<p>passwd file open error...</p>";
    exit;
}
if($fileName===null || $fileName === False || ($file=fopen($fileName, "r"))===False){//ファイルオープン
    echo "file open error";
    exit;
}else{
    $content;
    $collision=array();//衝突用のSONの配列
    $collisionIndex=0;//インデックス
    while($content = fgets($file)){//CSVの読み込み
        $strAry = explode(",", $content); //0:ID 1:名前 2:name
        for($i = 0;$i<sizeof($strAry);$i++){
            $strAry[$i] = preg_replace("/\"/", "", $strAry[$i]);//”を削除             
        }
        $strAry[2] = preg_replace("/\n/", "", "$strAry[2]");//nameの改行削除
        $strAry[2] = preg_replace("/[\s]+/", " ", "$strAry[2]");//nameの空白連続を削除
        $strAry[2] = replaceRomaji(mb_strtolower($strAry[2]));//小文字に変換
        $tmpAry = explode(" ",$strAry[2]);//名前を空白で区切る
        //基本的に全部関数にしてます。
        $GID=getGID($strAry[0]);
        $login_name=getLoginName($tmpAry[0],$tmpAry[1],$GID);//ログイン名生成のため姓と名とGID（重複チェック）を渡す
        $UID=getUID($strAry[0]);
        $GECOS=getGECOS($strAry[2]);
        $home_dir="/home/".$login_name;
        $login_shell="/bin/sh";
        $tmpStr = preg_replace("/__collision__/","",$login_name);//以下重複時の記述重複したら__collision__をつける
        if(preg_match("/__collision__/",$login_name)===1){//jsonで渡すのは名前UIDと名前の候補の三つ(4つ）
            array_push($collision,array("name"=> $tmpStr,
                                            "id"=>$UID,
                                            "ex1"=>  substr($tmpStr,0,5)."123",
                                            "ex2"=>  genelateName($tmpStr)));
        }
        //login_name:*:UID:GID::::GECOS:home_dir:login_shell
        $content = $login_name.":x:".$UID.":".$GID."::::".$GECOS.":".$home_dir.":".$login_shell."\n";//passwdようの文字列
        fputs($passFile, $content, strlen($content));//書き込み
    }
    if(sizeof($collision)<1){//重複なしなら
        fclose($file);
        rename($fileName,"./etc/passwd");//テンプレをpasswdにかえる。
        header( 'Content-Type: application/json; charset=utf-8' );
        echo  json_encode($collision);        
    }else{//重複あるならそのままGO！
        ob_clean();
        header( 'Content-Type: application/json; charset=utf-8' );
        echo  json_encode($collision);
    }
}
function getGECOS($str){
    //関数作ったけど実装するもんなかった～～～ｗｗｗｗｗｗｗ
    return $str;
}
function getUID($num){
    return substr($num, 0,3).substr($num,-3);
}    
function getGID($num){
    return substr($num,0,4);
}
function getLoginName($name,$name2,$num){//重複チェック込み仕様変更
    $name = substr($name, 0,8);
    if(isUsed($name,$num)===True){//重複チェック
        if(strlen($name)<7){//姓_名にしてみる
            $name = $name."_".substr($name2, 0,(8-strlen($name)-1));
            if(isUsed($name,$num)===True){//それでも重複したら__collision__を付加
                $name = "__collision__".$name;
            }
        }
    }
    return $name;
}
function isUsed($name,$num){//使用チェックファイルを開いて捜査
    if(($file = @fopen("./etc/.passwdtmp", "r"))===False){
        echo "pass file open error";
        exit;
    }
    while(False !== ($str = fgets($file))){
        $tmp = explode(":", $str);
        $str = $tmp[0];
        $gid = $tmp[3];
        if($str===$name && $gid === $num){//名前とGID重複
            fclose($file);
            return True;
        }
    }
    fclose($file);
    return False;
}
function genelateName($str){//ローマ字をごにょごにょ名前の例を生む
    $tmp = $str;
    $replaceAry=array(
        array("hu","fu"),
        array("si","shi"),
        array("sya","sha"),
        array("syu","shu"),
        array("syo","sho"),
        array("jya","ja"),
        array("jyu","ju"),
        array("jyo","jo"),
        array("tu","tsu"),
        array("ti","chi"),
        array("tya","cha"),
        array("tyu","chu"),
        array("tyo","cho"),
    );
    for($i=0;$i<sizeof($replaceAry);$i++){
        $str = str_replace($replaceAry[$i][1], $replaceAry[$i][0], $str);
    }
    if($str==$tmp){
        return "null";
    }
    return substr($str,0,8);//8文字制限        
}
function replaceRomaji($str){//ローマ字をごにょごにょ整形する
    //si->shi, sya->sha, syu->shu, syo->sho, jya->ja, jyu->ju, jyo->jo, tu->tsu, ti->chi, tya->cha, tyu->chu, tyo->cho, hu->fu
    $replaceAry=array(
        array("hu","fu"),//ゴリ押しなんだな～～ｗｗｗｗｗ　shuがsfuになるのchuがcfuになるのはfix済み
        array("si","shi"),
        array("sya","sha"),
        array("syu","shu"),
        array("syo","sho"),
        array("jya","ja"),
        array("jyu","ju"),
        array("jyo","jo"),
        array("tu","tsu"),
        array("ti","chi"),
        array("tya","cha"),
        array("tyu","chu"),
        array("tyo","cho"),
        array("cfu","chu"),
        array("sfu","shu")        
    );
    for($i=0;$i<sizeof($replaceAry);$i++){
        $str = str_replace($replaceAry[$i][0], $replaceAry[$i][1], $str);
    }
    return $str;
}
```
いい感じのグダグダプログラムですね。
`$jsonString = file_get_contents('php://input');`
これでjsonが受け取れるんですね。`php://input`は読み込み専用のストリームで、リクエストのbody部から生のデータを読み込むことができるものなんですね。まぁ、最初はファイル名しか飛んでこないんですけど。
あとは、にょにょにょにょにょ～って感じでpasswdファイル作成とか重複チェックとかしてます。
返すときは、ヘッダつけて配列をjson_encodeしてechoするだけ。思った以上に簡単なわけです。

```php:php2.php
<?php
//ajax以外の接続を拒否
$request = isset($_SERVER['HTTP_X_REQUESTED_WITH']) ? strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) : '';
if($request !== 'xmlhttprequest'){
    echo "error";
    exit;
}
$json_string  = file_get_contents("php://input");
echo $json_string;
$response = json_decode($json_string,true);//jsonの配列
if(sizeof($response)<1){
    echo "done?";
    exit;
}
$tmpfile = fopen("./etc/.passwdtmp","r");
$passfile = fopen("./etc/passwd","w");
if($passfile===false || $tmpfile===false){
    echo "<script>alert('Server Erorr');</script>";
    exit;
}
$responseIndex=0;
$flg=true;//フラグ：変更項目の有り無しを意味する。セグメントエラー予防
$collision = array();
while(($content=fgets($tmpfile))!==False){
    //login_name:*:UID:GID::::GECOS:home_dir:login_shell
    //{name:元の名前,newName:新しい名前,id:id});
    $inputBuffer = "";
    $array = explode(":", $content);
    if($flg == true &&
       $array[0]===("__collision__".$response[$responseIndex]["name"]) &&
       $array[2]===$response[$responseIndex]["id"]){//衝突したのとチェック入れ替え
        if(isUsed($response[$responseIndex]["newName"],$array[3])){//新しいのが重複してたら突っぱねる
            array_push($collision , array("name" => $response[$responseIndex]["name"],
                                          "id" =>$response[$responseIndex]["id"],
                                          "ex1"=> substr($response[$responseIndex]["name"],0,5)."123",
                                          "ex2"=> genelateName($response[$responseIndex]["name"])));
            $inputBuffer = $content;
            $responseIndex++;
            if($responseIndex>=sizeof($response)){
                $flg =false;
            }
        } else {
            $array[0]=$response[$responseIndex]["newName"];
            $str = "__collision__".$response[$responseIndex]["name"];
            $array[sizeof($array)-2] = preg_replace("/$str/",
                                                    $response[$responseIndex]["newName"],
                                                    $array[sizeof($array)-2]);
            for($i = 0; $i < sizeof($array)-1;$i++){
                $inputBuffer = $inputBuffer.$array[$i].":";
            }
            $inputBuffer = $inputBuffer.$array[sizeof($array)-1];
            $responseIndex++;
            if($responseIndex>=sizeof($response)){
                $flg =false;
            }
        }
    }else{
        $inputBuffer = $content;
    }
    fputs($passfile, $inputBuffer, strlen($inputBuffer));//書き込み
}
    fclose($tmpfile);
    fclose($passfile);
if(sizeof($collision)>0){
    if(unlink("./etc/.passwdtmp")===false){//てんぷらファイル削除
        echo "server error...";
        exit;
    }else{
        if(rename("./etc/passwd","./etc/.passwdtmp")===false){//修正版をてんぷらに
            echo "server error...";
            exit;
        }
    }
    ob_clean();
    header( 'Content-Type: application/json; charset=utf-8' );
    echo  json_encode($collision);
}else{
    if(unlink("./etc/.passwdtmp")===false){//てんぷらファイル削除
        echo "server error...";
        exit;
    }
    ob_clean();
    header( 'Content-Type: application/json; charset=utf-8' );
    echo  json_encode($collision);
}

function isUsed($name,$num){
    if(($file = @fopen("./etc/passwd", "r"))===False){
        echo "pass file open error";
        exit;
    }
    while(False !== ($str = fgets($file))){
        $tmp = explode(":", $str);
        $str = $tmp[0];
        $gid = $tmp[3];
        if($str===$name && $gid === $num){
            fclose($file);
            return True;
        }
    }
    fclose($file);
    return False;
}
function genelateName($str){
    $tmp = $str;
    $replaceAry=array(
        array("hu","fu"),
        array("si","shi"),
        array("sya","sha"),
        array("syu","shu"),
        array("syo","sho"),
        array("jya","ja"),
        array("jyu","ju"),
        array("jyo","jo"),
        array("tu","tsu"),
        array("ti","chi"),
        array("tya","cha"),
        array("tyu","chu"),
        array("tyo","cho"),
    );
    for($i=0;$i<sizeof($replaceAry);$i++){
        $str = str_replace($replaceAry[$i][1], $replaceAry[$i][0], $str);
    }
    if($str==$tmp){
        return null;
    }
    return substr($str,0,8);//8文字制限        
}
```
ほとんど一緒なんすよね。
特に難しいことやってないし、読めばわかるし、読まなくてもいいですｗｗ
ファイル更新してますね。ちなみに、Json送る前に、ストリームをお掃除しないと変なの一緒に送って、JS側で意味不明なエラーに襲われて吐きそうになるので気をつけましょう。
`ob_clean();`これだけはとりあえず書いときましょう。


以上ですかね？？課題も終わって夏休みです！！！
とりあえず、[わぐばん！](http://www.nicovideo.jp/watch/1438763483)やばいんで見てくださいね！！！
この動画のおかげでがんばれました。最後まで皆さんありがとうございます。
次回は
大学課題（超がんばって1日で完成させた）Java×自然言語処理×DocomoAPIの話
or
IoTで遊んでみた
のどっちかですね。

プWUGラマーとしてこれからもがんばっぺ！！
それでWUG～ :open_hands: