Итак, бэкэнд был разработан. Пришло время придать форму приложению. Учитывая не обычную структуру, было решено не использовать checkbox-элементы, т.к. они не могут в полной мере описать структуру дерева. Решил запилить свою собственную форму.
Сам интерфейс вытягивается ajax-oм в окошко. В верхней части интерфеса есть input с наименованием файла(только чтение), в него при клике на название софта в таблице(внизу) или при выборе файла(вверху) пишется имя файла. Второй input(доступен для записи) — папка в которую нужно будет положить файл на клиенте. Выбор файла, при выборе которого собственно обновляется верхняя форма и там появляется кнопка загрузить(с подключенной функцией в javascript):
Снизу таблица, которая полностью соответствует данным в MySQL(из них и вытягивается). Над этой таблицей кнопка для ручной выгрузки в MongoDB. При клике на название программы в таблице в первый(заблокированный) input вытягивается имя программы и верхняя форма обновляется: появляется две кнопки «удалить» и «применить» с соответствующими подключенными функциями:
Посередине дерево, у каждого элемента есть псевдо-checkbox. При этом он у верхних двух уровней имеет три положения:
1)пустое окно — элемент не выбран(нижестоящие уровни пусты).
2)окно с плюсом — раскрыт список дерева, сам элемент не выбран(нижестоящие уровни доступны к редактированию).
3)окно с флагом — выбран элемент и все нижестоящие уровни(список скрыт).
Сами значки нарисовал в Adobe Photoshop, т.к. в интернете ничего подходящего не нашел.
Выбор элементов асинхронным XmlHttpRequest пишет в базу при клике значение, по факту записи вторым асинхронным запросом уже вытягивается информация в нижнюю таблицу.
При этом, если первый input(с именем файла) пуст — псевдо-checkbox возвращает сообщение, что не выбран софт:
При попытке загрузить файл, который уже существует — обновляется только файл и содержимое ячейки hash в MySQL. Соответствующее сообщение также выводится:
Аналогичным сообщением подтверждается обновление и удаление файла(ответ идет не от javascript, php, а напрямую из MySQL, исключая обновление «только файла»), а также весь функционал по добавлению и редактированию объектов(функционал легко расширяем):
При этом, при выборе элемента списка, обновляется форма ввода с примером:
При выгрузке в MongoDB — из NoSQL базы возвращается ответ о количестве результате операций:
Ну и касательно собственно кода, вытащим параметры пользователя из БД соответственно поднятой сессии и тут же закроем соединение, оставив нужные данные в переменных. Далее подключимся к MySQL еще раз, уже для работы приложения:
$mysqli = new mysqli("*******", "*****", "********", "update_soft"); if ($mysqli->connect_errno) { echo "Не удалось подключиться к MySQL:".$mysqli->connect_error; exit; } $mysqli->query("SET NAMES utf8"); //для отправки в iRetail if($_GET['obj'] == '**********'){ //об этом читайте в другой части 4 }
Ту часть, что отправляет в cloud умышленно сделано GET-запросом, чтобы можно было отправить по клику, из crona, curlom и т.д. Далее идет проверка прав пользователя на выполнение обновления. Она должна быть после отправки в облако, иначе это будет невозможно сделать из консоли.
if($status_user[2] < 5) die ('Недостаточно прав');
Далее создадим основной текст документа и чуть ниже его разберем. Основной документ выводится, если $_GET[‘obj’] не существует:
//основной документ if(!isset($_GET['obj'])){ echo ' <style type="text/css"> .jurlics{ padding: 0px 0px 0px 12px; } .objects{ padding: 0px 0px 0px 20px; } #inputfile{ padding: 0px 0px 12px 0px; } #softwares{ padding: 12px 0px 0px 0px; } table {border: 1px solid grey;} th {border: 1px solid grey;} td {border: 1px solid grey;} #objecteditor{ padding: 8px 0px 0px 0px; } #process_procedure{ padding: 5px 0px 0px 0px; } #process_primer{ padding: 3px 0px 0px 0px; font-size: 8pt; color: gray; } #O_object, #O_jurlico, #H_holding, #H_jurlico, #G_selector, #G_toval{ width: 150px; } #O_guid, #G_guid{ width: 500px; } #softpath{ width: 300px; } #softname{ width: 300px; } ins, button, select, .holdings img, .jurlics img, .objects img{ cursor: pointer; } #for_script_object, #for_script_jurlico, #for_script_holding, #for_script_noholdjurlico{ visibility: hidden; width: 0px; height: 0px; } </style> <script> function MyListSelect(op, obj, objval) { softname = document.getElementById("softname").value; var object_guid, jurlico_guid, holding_guid; if(document.getElementById("for_script_holding")){ holding_guid = JSON.parse(document.getElementById("for_script_holding").innerHTML.toString())[objval]; } if(document.getElementById("for_script_jurlico")){ jurlico_guid = JSON.parse(document.getElementById("for_script_jurlico").innerHTML.toString())[objval]; } if((jurlico_guid == undefined) || (jurlico_guid == null) || (jurlico_guid == "")){ if(document.getElementById("for_script_noholdjurlico")){ jurlico_guid = JSON.parse(document.getElementById("for_script_noholdjurlico").innerHTML.toString())[objval]; } } if(document.getElementById("for_script_object")){ object_guid = JSON.parse(document.getElementById("for_script_object").innerHTML.toString())[objval]; } if((softname != null) & (softname != "")) { if (op == 0){ //флаг не стоит xmlhttp=new XMLHttpRequest(); xmlhttp.onreadystatechange=function() { if( obj == "holding") { if (this.readyState==4 && this.status==200) { document.getElementById("H." + objval).innerHTML = ""; document.getElementById("HC." + objval).innerHTML = "<img src=\"/update_apt/unselect.png\" width=\"12\" height=\"12\" onClick=\'MyListSelect(1, \"holding\", \\"" + objval + "\\");\'>" + objval + " - {" + holding_guid +"}"; LoadSoftTable(); } } if( obj == "jurlico") { if (this.readyState==4 && this.status==200) { document.getElementById("J." + objval).innerHTML = ""; document.getElementById("JC." + objval).innerHTML = "<img src=\"/update_apt/unselect.png\" width=\"12\" height=\"12\" onClick=\'MyListSelect(1, \"jurlico\", \\"" + objval + "\\");\'>" + objval + " - {" + jurlico_guid +"}"; LoadSoftTable(); } } if( obj == "object") { if (this.readyState==4 && this.status==200) { document.getElementById("OC." + objval).innerHTML = "<img src=\"/update_apt/unselect.png\" width=\"12\" height=\"12\" onClick=\'MyListSelect(2, \"object\", \\"" + objval + "\\");\'>" + objval + " - {" + object_guid +"}"; LoadSoftTable(); } } } xmlhttp.open("GET","/update_apt/update_soft.php?obj=command&command=proc_soft_to¶m1=remove¶m2=" + obj + "¶m3=" + objval + "¶m4=" + softname, true); xmlhttp.send(); } if (op == 1){ //раскрытый список xmlhttp=new XMLHttpRequest(); xmlhttp.onreadystatechange=function() { if (this.readyState==4 && this.status==200) { if( obj == "holding") { document.getElementById("H." + objval).innerHTML = xmlhttp.responseText; document.getElementById("HC." + objval).innerHTML = "<img src=\"/update_apt/plus.png\" width=\"12\" height=\"12\" onClick=\'MyListSelect(2, \"holding\", \\"" + objval + "\\");\'>" + objval + " - {" + holding_guid +"}"; } if( obj == "jurlico") { document.getElementById("J." + objval).innerHTML = xmlhttp.responseText; document.getElementById("JC." + objval).innerHTML = "<img src=\"/update_apt/plus.png\" width=\"12\" height=\"12\" onClick=\'MyListSelect(2, \"jurlico\", \\"" + objval + "\\");\'>" + objval + " - {" + jurlico_guid +"}"; } } } xmlhttp.open("GET","/update_apt/update_soft.php?obj=" + obj + "&objval=" + objval + "&soft_in_table=" + document.getElementById("softname").value, true); xmlhttp.send(); } if (op == 2){ //флаг стоит xmlhttp=new XMLHttpRequest(); xmlhttp.onreadystatechange=function() { if( obj == "holding") { if (this.readyState==4 && this.status==200) { document.getElementById("H." + objval).innerHTML = ""; document.getElementById("HC." + objval).innerHTML = "<img src=\"/update_apt/select.png\" width=\"12\" height=\"12\" onClick=\'MyListSelect(0, \"holding\", \\"" + objval + "\\");\'>" + objval + " - {" + holding_guid +"}"; LoadSoftTable(); } } if( obj == "jurlico") { if (this.readyState==4 && this.status==200) { document.getElementById("J." + objval).innerHTML = ""; document.getElementById("JC." + objval).innerHTML = "<img src=\"/update_apt/select.png\" width=\"12\" height=\"12\" onClick=\'MyListSelect(0, \"jurlico\", \\"" + objval + "\\");\'>" + objval + " - {" + jurlico_guid +"}"; LoadSoftTable(); } } if( obj == "object") { if (this.readyState==4 && this.status==200) { document.getElementById("OC." + objval).innerHTML = "<img src=\"/update_apt/select.png\" width=\"12\" height=\"12\" onClick=\'MyListSelect(0, \"object\", \\"" + objval + "\\");\'>" + objval + " - {" + object_guid +"}"; LoadSoftTable(); } } } xmlhttp.open("GET","/update_apt/update_soft.php?obj=command&command=proc_soft_to¶m1=add¶m2=" + obj + "¶m3=" + objval + "¶m4=" + softname, true); xmlhttp.send(); } } else { alert("Не выбрано ПО!"); } } function LoadSoftWare(name, path) { document.getElementById("softname").value = name; document.getElementById("softpath").value = path; document.getElementById("buttons").innerHTML = "<button onClick=\"EditSoft(\'update\');\">Применить</button> <button onClick=\"EditSoft(\'remove\');\">Удалить</button>"; LoadObjectTable(name); } function LoadSoftTable(){ xmlhttp=new XMLHttpRequest(); xmlhttp.onreadystatechange=function() { if (this.readyState==4 && this.status==200) { document.getElementById("softwares").innerHTML = xmlhttp.responseText; } } xmlhttp.open("GET","/update_apt/update_soft.php?obj=command&command=loadsofttable", false); xmlhttp.send(); } function LoadSoftFile(){ arr = String(document.getElementById("softfile").value).split("\\\"); document.getElementById(\'softname\').value = arr[arr.length - 1]; document.getElementById("softpath").value = ""; document.getElementById("buttons").innerHTML = "<button onClick=\"EditSoft(\'add\');\">Загрузить</button>"; } function EditSoft(param){ xmlhttp=new XMLHttpRequest(); xmlhttp.onreadystatechange=function() { if (this.readyState==4 && this.status==200) { alert(xmlhttp.responseText); LoadSoftTable(); if((param == "add") || (param == "remove")){ LoadObjectTable("all"); document.getElementById("softname").value = ""; document.getElementById("softpath").value = ""; document.getElementById("softfile").value = ""; } } } if(param == "add"){ xmlhttp.open("POST","/update_apt/update_soft.php?obj=command&command=proc_software¶m1=" + param + "¶m2=" + document.getElementById("softname").value + "¶m3=" + document.getElementById("softpath").value + "¶m4=/home/new_update_soft/", true); form=new FormData(); upload_file=document.getElementById("softfile").files[0]; form.append("softfile", upload_file); xmlhttp.send(form); } else { xmlhttp.open("GET","/update_apt/update_soft.php?obj=command&command=proc_software¶m1=" + param + "¶m2=" + document.getElementById("softname").value + "¶m3=" + document.getElementById("softpath").value + "¶m4=/home/new_update_soft/", true); xmlhttp.send(); } } function SelectProc(){ var selected = document.getElementById("process_selector").value; switch(selected){ case "proc_holding_add": document.getElementById("process_procedure").innerHTML = "<input id=\"H_holding\" type=\"text\" value=\"Холдинг\"> <input id=\"H_jurlico\" type=\"text\" value=\"Юрлицо\">"; document.getElementById("process_primer").innerHTML = "ПРИМЕР: Холдинг: farmin, Юрлицо: farmin"; break; case "proc_holding_remove": document.getElementById("process_procedure").innerHTML = "<input id=\"H_holding\" type=\"text\" value=\"Холдинг\">"; document.getElementById("process_primer").innerHTML = "ПРИМЕР: Холдинг: farmin"; break; case "proc_object_add": document.getElementById("process_procedure").innerHTML = "<input id=\"O_object\" type=\"text\" value=\"Объект\"> <input id=\"O_jurlico\" type=\"text\" value=\"Юрлицо\"> <input id=\"O_guid\" type=\"text\" value=\"GUID объекта\">"; document.getElementById("process_primer").innerHTML = "ПРИМЕР: Объект: farmin.apt01, Юрлицо: farmin, GUID: 960E4C49-EF07-11E2-AF2D-782BCB1FF45D"; break; case "proc_object_remove": document.getElementById("process_procedure").innerHTML = "<input id=\"O_object\" type=\"text\" value=\"Объект\">"; document.getElementById("process_primer").innerHTML = "ПРИМЕР: Объект: farmin.apt01"; break; case "proc_object_update": document.getElementById("process_procedure").innerHTML = "<input id=\"O_object\" type=\"text\" value=\"Объект\"> <input id=\"O_jurlico\" type=\"text\" value=\"Юрлицо\"> <input id=\"O_guid\" type=\"text\" value=\"GUID объекта\">"; document.getElementById("process_primer").innerHTML = "ПРИМЕР: Объект: farmin.apt01, Юрлицо: farmin, GUID: 960E4C49-EF07-11E2-AF2D-782BCB1FF45D"; break; case "proc_guid_to": document.getElementById("process_procedure").innerHTML = "<select id=\"G_selector\" size=\"1\"><option value=\"holding\">Холдинг</option><option value=\"jurlico\">Юрлицо</option></select> <input id=\"G_toval\" type=\"text\" value=\"Холдинг или юрлицо\"> <input id=\"G_guid\" type=\"text\" value=\"GUID организации\">"; document.getElementById("process_primer").innerHTML = "ПРИМЕР: Холдинг: farmin, GUID: 960E4C49-EF07-11E2-AF2D-782BCB1FF45D"; break; default: alert( "Произошла исключительная ситуация!" ); } } function EditObject(){ var selected = document.getElementById("process_selector").value; xmlhttp=new XMLHttpRequest(); xmlhttp.onreadystatechange=function() { if (this.readyState==4 && this.status==200) { alert(xmlhttp.responseText); LoadSoftTable(); LoadObjectTable("all"); document.getElementById("softname").value = ""; document.getElementById("softpath").value = ""; document.getElementById("softfile").value = ""; } } switch(selected){ case "proc_holding_add": xmlhttp.open("GET","/update_apt/update_soft.php?obj=command&command=proc_holding¶m1=add¶m2=" + document.getElementById("H_holding").value + "¶m3=" + document.getElementById("H_jurlico").value, true); break; case "proc_holding_remove": xmlhttp.open("GET","/update_apt/update_soft.php?obj=command&command=proc_holding¶m1=remove¶m2=" + document.getElementById("H_holding").value, true); break; case "proc_object_add": xmlhttp.open("GET","/update_apt/update_soft.php?obj=command&command=proc_object¶m1=add¶m2=" + document.getElementById("O_object").value + "¶m3=" + document.getElementById("O_guid").value + "¶m4=" + document.getElementById("O_jurlico").value, true); break; case "proc_object_remove": xmlhttp.open("GET","/update_apt/update_soft.php?obj=command&command=proc_object¶m1=remove¶m2=" + document.getElementById("O_object").value, true); break; case "proc_object_update": xmlhttp.open("GET","/update_apt/update_soft.php?obj=command&command=proc_object¶m1=update¶m2=" + document.getElementById("O_object").value + "¶m3=" + document.getElementById("O_guid").value + "¶m4=" + document.getElementById("O_jurlico").value, true); break; case "proc_guid_to": xmlhttp.open("GET","/update_apt/update_soft.php?obj=command&command=proc_guid_to¶m1=" + document.getElementById("G_selector").value + "¶m2=" + document.getElementById("G_toval").value + "¶m3=" + document.getElementById("G_guid").value, true); break; default: alert( "Произошла исключительная ситуация!" ); } xmlhttp.send(); } function LoadObjectTable(soft_in_table){ xmlhttp=new XMLHttpRequest(); xmlhttp.onreadystatechange=function() { if (this.readyState==4 && this.status==200) { document.getElementById("objecttable").innerHTML = xmlhttp.responseText; } } xmlhttp.open("GET","/update_apt/update_soft.php?obj=command&command=loadobjecttable&soft_in_table=" + soft_in_table, true); xmlhttp.send(); } function SendToCloud(){ xmlhttp=new XMLHttpRequest(); xmlhttp.onreadystatechange=function() { if (this.readyState==4 && this.status==200) { alert(xmlhttp.responseText); } } xmlhttp.open("GET","/update_apt/update_soft.php?obj=sendtocloud", true); xmlhttp.send(); } </script>'; echo '<div id="inputfile"><input id="softname" type="text" disabled> <input id="softpath" type="text"> <input id="softfile" name="software" type="file" onchange="LoadSoftFile();"> <div id="buttons"></div> </div><hr>'; echo '<div id="objecttable">'; GetObject($mysqli, 1, 'all'); echo '</div>'; echo '<div id="objecteditor"><select id="process_selector" size="1" onchange="SelectProc();"> <option value="proc_holding_add">Добавить юрлицо к холдингу</option> <option value="proc_holding_remove">Удалить холдинг</option> <option value="proc_guid_to">Обновить GUID организации</option> <option value="proc_object_add" selected>Добавить объект</option> <option value="proc_object_remove">Удалить объект</option> <option value="proc_object_update">Обновить объект</option> </select> <button onClick="EditObject();">Выполнить</button></div>'; echo '<div id="process_procedure"><input id="O_object" type="text" value="Объект"> <input id="O_jurlico" type="text" value="Юрлицо"> <input id="O_guid" type="text" value="GUID объекта"></div>'; echo '<div id="process_primer">ПРИМЕР: Объект: farmin.apt01, Юрлицо: farmin, GUID: 960E4C49-EF07-11E2-AF2D-782BCB1FF45D</div>'; echo '<hr>'; echo '<div id="sendtocloud"><button onClick="SendToCloud();">Выгрузить в iRetail</button></div>'; echo '<div id="softwares">'; GetSoft($mysqli, 1); echo '</div>'; }
Для придания красоты нашей форме псевдо-checkbox-ов нам необходимо при клике на программу показывать выбранные торговые объекты, юрлица, холдинги с флагом, а не выбранные с пустым квадратом. Т.к. форма у нас генерируется динамически в момент открытия странички, придется её менять теневыми запросами к бэкэнду. Учитывая, что при изменении элементов DOM скрипт уже не может отслеживать их состояние, то нужно привязывать предопределенные функции к событиям новых элементов.
В итоге я написал функцию для выгрузки верхнего уровня дерева(холдинги, юрлица вне холдингов) при получении параметра 1 отдаст по умолчанию, с параметром 2 отдаст с хидером(нужен для XHR):
//верхний уровень дерева function GetObject($mysqli, $param, $soft_inobject){ if($param == 2){ header("Content-type: text/txt; charset=utf-8"); } if ($result = $mysqli->query("SELECT * FROM `guid_for_jur_hold` WHERE `holding` IS NOT NULL ORDER BY `holding`")) { while( $row = $result->fetch_assoc() ){ $link_guid_h[$row['holding']] = $row['guid']; $for_script .= '"'.$row['holding'].'" : "'.$row['guid'].'",'; } echo '<div id="for_script_holding">{'.substr($for_script, 0, -1).'}</div>'; } $for_script = null; if ($result = $mysqli->query("SELECT * FROM `guid_for_jur_hold` WHERE `jurlico` IS NOT NULL ORDER BY `jurlico`")) { while( $row = $result->fetch_assoc() ){ $link_guid_j[$row['jurlico']] = $row['guid']; $for_script .= '"'.$row['holding'].'" : "'.$row['guid'].'",'; } } echo '<div id="for_script_noholdjurlico">{'.substr($for_script, 0, -1).'}</div>'; if($soft_inobject == 'all'){ if ($result = $mysqli->query('SELECT DISTINCT `holding` FROM `link_holding`')) { while( $row = $result->fetch_assoc() ){ echo '<div class="holdings" id="HC.'.$row['holding'].'"><img src="/update_apt/unselect.png" width="12" height="12" onClick=\'MyListSelect(1, "holding","'.$row['holding'].'");\'>'.$row['holding'].' - {'.$link_guid_h[$row['holding']].'}</div><div id="H.'.$row['holding'].'"></div>'; } $result->close(); } if ($result = $mysqli->query('SELECT DISTINCT `jurlico` FROM `link_jurlico` WHERE `jurlico` NOT IN (SELECT `jurlico` FROM `link_holding`) ORDER BY Id')) { echo '<div class="holdings">вне холдингов</div>'; while( $row = $result->fetch_assoc() ){ echo '<div class="jurlics" id="JC.'.$row['jurlico'].'"><img src="/update_apt/unselect.png" width="12" height="12" onClick=\'MyListSelect(1, "jurlico","'.$row['jurlico'].'");\'>'.$row['jurlico'].' - {'.$link_guid_j[$row['jurlico']].'}</div><div id="J.'.$row['jurlico'].'"></div>'; } $result->close(); } } else{ if(isset($soft_inobject)){ $holding_db = GetArraySoft($mysqli, 'holding', $soft_inobject); $jurlico_db = GetArraySoft($mysqli, 'jurlico', $soft_inobject); if ($result = $mysqli->query('SELECT DISTINCT `holding` FROM `link_holding`')) { while( $row = $result->fetch_assoc() ){ if(in_array($row['holding'], $holding_db)){ echo '<div class="holdings" id="HC.'.$row['holding'].'"><img src="/update_apt/select.png" width="12" height="12" onClick=\'MyListSelect(0, "holding","'.$row['holding'].'");\'>'.$row['holding'].' - {'.$link_guid_h[$row['holding']].'}</div><div id="H.'.$row['holding'].'"></div>'; } else { echo '<div class="holdings" id="HC.'.$row['holding'].'"><img src="/update_apt/unselect.png" width="12" height="12" onClick=\'MyListSelect(1, "holding","'.$row['holding'].'");\'>'.$row['holding'].' - {'.$link_guid_h[$row['holding']].'}</div><div id="H.'.$row['holding'].'"></div>'; } } $result->close(); } if ($result = $mysqli->query('SELECT DISTINCT `jurlico` FROM `link_jurlico` WHERE `jurlico` NOT IN (SELECT `jurlico` FROM `link_holding`) ORDER BY Id')) { echo '<div class="holdings">вне холдингов</div>'; while( $row = $result->fetch_assoc() ){ if(in_array($row['jurlico'], $jurlico_db)){ echo '<div class="jurlics" id="JC.'.$row['jurlico'].'"><img src="/update_apt/select.png" width="12" height="12" onClick=\'MyListSelect(0, "jurlico","'.$row['jurlico'].'");\'>'.$row['jurlico'].' - {'.$link_guid_j[$row['jurlico']].'}</div><div id="J.'.$row['jurlico'].'"></div>'; } else { echo '<div class="jurlics" id="JC.'.$row['jurlico'].'"><img src="/update_apt/unselect.png" width="12" height="12" onClick=\'MyListSelect(1, "jurlico","'.$row['jurlico'].'");\'>'.$row['jurlico'].' - {'.$link_guid_j[$row['jurlico']].'}</div><div id="J.'.$row['jurlico'].'"></div>'; } } $result->close(); } } } }
Этой функции необходимо получить список связей холдинга с софтом и юрлица с софтом, понимая что в дальнейшем такая же информация будет нужна и по объектам, оформил в отдельную функцию, которая получив софт и параметр вернет массив холдингов, юрлиц или объектов к которым привязан софт:
//получить объекты по софту function GetArraySoft($mysqlid, $to_arr, $to_soft) { $i=0;$j=0;$k=0; $result = $mysqlid->query("SELECT `name` FROM `holding` WHERE `soft` LIKE '".$to_soft."' ORDER BY Id"); while( $row = $result->fetch_assoc() ){ $holding_db[$i] = $row['name']; $result2 = $mysqlid->query("SELECT `jurlico` FROM `link_holding` WHERE `holding` LIKE '".$row['name']."' ORDER BY Id"); while( $row2 = $result2->fetch_assoc() ){ $jurlico_db[$j] = $row2['jurlico']; $result3 = $mysqlid->query("SELECT `object` FROM `link_jurlico` WHERE `jurlico` LIKE '".$row2['jurlico']."' ORDER BY Id"); while( $row3 = $result3->fetch_assoc() ){ $object_db[$k] = $row3['object']; $k++; } $result3->close(); $j++; } $result2->close(); $i++; } $result->close(); if($to_arr == 'holding'){ return $holding_db; } $result = $mysqlid->query("SELECT `name` FROM `jurlico` WHERE `soft` LIKE '".$to_soft."' ORDER BY Id"); while( $row = $result->fetch_assoc() ){ $jurlico_db[$j] = $row['name']; $result2 = $mysqlid->query("SELECT `object` FROM `link_jurlico` WHERE `jurlico` LIKE '".$row['name']."' ORDER BY Id"); while( $row2 = $result2->fetch_assoc() ){ $object_db[$k] = $row2['object']; $k++; } $result2->close(); $j++; } $result->close(); if($to_arr == 'jurlico'){ return $jurlico_db; } $result = $mysqlid->query("SELECT `name` FROM `object` WHERE `soft` LIKE '".$to_soft."' ORDER BY Id"); while( $row = $result->fetch_assoc() ){ $object_db[$k] = $row['name']; $k++; } $result->close(); if($to_arr == 'object'){ return $object_db; } }
Также при загрузке используется функция по генерации таблицы с софтом внизу интерфейса(аналогично с параметром):
//таблица с ПО внизу(высасывает актуальные данные из БД) function GetSoft($mysqliс, $param){ if($param == 2){ header("Content-type: text/txt; charset=utf-8"); } if ($result = $mysqliс->query('SELECT `name`, `soft` FROM `holding` ORDER BY `soft`')) { while( $row = $result->fetch_assoc() ){ $soft_holding[$row['soft']] .= $row['name'].';'; } $result->close(); } if ($result = $mysqliс->query('SELECT `name`, `soft` FROM `jurlico` ORDER BY `soft`')) { while( $row = $result->fetch_assoc() ){ $soft_jurlico[$row['soft']] .= $row['name'].';'; } $result->close(); } if ($result = $mysqliс->query('SELECT `name`, `soft` FROM `object` ORDER BY `soft`')) { while( $row = $result->fetch_assoc() ){ $soft_object[$row['soft']] .= $row['name'].';'; } $result->close(); } if ($result = $mysqliс->query('SELECT * FROM `software` ORDER BY Id')) { echo '<table><tr><th>ПО</th><th>папка</th><th>холдинги</th><th>юрлица</th><th>объекты</th></tr>'; while( $row = $result->fetch_assoc() ){ echo '<tr><td onClick=\'LoadSoftWare("'.$row['name'].'", "'.$row['path'].'");\'><ins>'.$row['name'].'</ins></td>'; echo '<td>'.$row['path'].'</td>'; echo '<td>'.$soft_holding[$row['name']].'</td>'; echo '<td>'.$soft_jurlico[$row['name']].'</td>'; echo '<td>'.$soft_object[$row['name']].'</td>'; echo '</tr>'; } $result->close(); echo '</table>'; } }
Javascript в странице вытягивает также нижний и средний уровень дерева(в функцию оформлять незачем, т.к. обращения будут только через теневой запрос), но эти уровни должны опять таки учитывать связку с софтом, для чего у нас уже есть функция. И сам код:
//подгрузка объектов для xhr(средний уровень дерева) if(($_GET['obj'] == 'holding') && isset($_GET['objval']) && isset($_GET['soft_in_table'])){ header("Content-type: text/txt; charset=utf-8"); if ($result = $mysqli->query("SELECT * FROM `guid_for_jur_hold` WHERE `jurlico` IS NOT NULL ORDER BY `jurlico`")) { while( $row = $result->fetch_assoc() ){ $link_guid[$row['jurlico']] = $row['guid']; $for_script .= '"'.$row['jurlico'].'" : "'.$row['guid'].'",'; } } echo '<div id="for_script_jurlico">{'.substr($for_script, 0, -1).'}</div>'; if ($result = $mysqli->query("SELECT * FROM `link_holding` WHERE `holding` LIKE '".$_GET['objval']."' ORDER BY `jurlico`")) { $jurlico_db = GetArraySoft($mysqli, 'jurlico', $_GET['soft_in_table']); while( $row = $result->fetch_assoc() ){ if(in_array($row['jurlico'], $jurlico_db)){ echo '<div class="jurlics" id="JC.'.$row['jurlico'].'"><img src="/update_apt/select.png" width="12" height="12" onClick=\'MyListSelect(0, "jurlico","'.$row['jurlico'].'");\'>'.$row['jurlico'].' - {'.$link_guid[$row['jurlico']].'}</div><div id="J.'.$row['jurlico'].'"></div>'; } else { echo '<div class="jurlics" id="JC.'.$row['jurlico'].'"><img src="/update_apt/unselect.png" width="12" height="12" onClick=\'MyListSelect(1, "jurlico","'.$row['jurlico'].'");\'>'.$row['jurlico'].' - {'.$link_guid[$row['jurlico']].'}</div><div id="J.'.$row['jurlico'].'"></div>'; } } $result->close(); } } //подгрузка объектов для xhr(нижний уровень дерева) if(($_GET['obj'] == 'jurlico') && isset($_GET['objval']) && isset($_GET['soft_in_table'])){ header("Content-type: text/txt; charset=utf-8"); if ($result = $mysqli->query("SELECT * FROM `link_object` ORDER BY `object`")) { while( $row = $result->fetch_assoc() ){ $link_guid[$row['object']] = $row['guid']; $for_script .= '"'.$row['object'].'" : "'.$row['guid'].'",'; } } echo '<div id="for_script_object">{'.substr($for_script, 0, -1).'}</div>'; $result->close(); if ($result = $mysqli->query("SELECT * FROM `link_jurlico` WHERE `jurlico` LIKE '".$_GET['objval']."' ORDER BY `object`")) { $object_db = GetArraySoft($mysqli, 'object', $_GET['soft_in_table']); while( $row = $result->fetch_assoc() ){ if(in_array($row['object'], $object_db)){ echo '<div class="objects" id="OC.'.$row['object'].'"><img src="/update_apt/select.png" width="12" height="12" onClick=\'MyListSelect(0, "object","'.$row['object'].'");\'>'.$row['object'].' - {'.$link_guid[$row['object']].'}</div>'; } else { echo '<div class="objects" id="OC.'.$row['object'].'"><img src="/update_apt/unselect.png" width="12" height="12" onClick=\'MyListSelect(2, "object","'.$row['object'].'");\'>'.$row['object'].' - {'.$link_guid[$row['object']].'}</div>'; } } $result->close(); } }
А также опишем функционал работы с базой данных:
//для работы с процедурами в БД if($_GET['obj'] == 'command'){ $log=fopen('/var/log/update_soft/'.date('d.m.Y').'.log', "a+"); header("Content-type: text/txt; charset=utf-8"); /* эту часть не разрабатываю, т.к. пока без надобности if(isset($_GET['command']) && isset($_GET['param1']) && isset($_GET['param2']) && isset($_GET['param3']) && isset($_GET['param4']) && !(preg_match('|^[a-z0-9._-]+$|i', $_GET['command']) && preg_match('|^[a-z0-9._-]+$|', $_GET['param1']) && preg_match('|^[a-z0-9._-]+$|', $_GET['param2']) && preg_match('|^[a-z0-9._-]+$|', $_GET['param3']) && preg_match('|^[a-z0-9._-]+$|', $_GET['param4']))){ echo 'В тексте есть недопустимые символы!'.PHP_EOL; echo 'Разрешены только маленькие английские буквы, цифры, точка, тире и нижнее подчеркивание.'; $mysqli->close(); exit; } if(isset($_GET['command']) && isset($_GET['param1']) && isset($_GET['param2']) && isset($_GET['param3']) && !(preg_match('|^[a-z0-9._-]+$|i', $_GET['command']) && preg_match('|^[a-z0-9._-]+$|', $_GET['param1']) && preg_match('|^[a-z0-9._-]+$|', $_GET['param2']) && preg_match('|^[a-z0-9._-]+$|', $_GET['param3']))){ if(($_GET['command'] == 'proc_holding') && ($_GET['param1'] == 'add')){ //разрешаем передачу null для proc_holding('add'), для удаления юрлица из объектов холдинга if(!isset($_GET['param3']) && isset($_GET['param2']) && preg_match('|^[a-z0-9._-]+$|i', $_GET['param2'])){ goto NoVerification; } if(!isset($_GET['param2']) && isset($_GET['param3']) && preg_match('|^[a-z0-9._-]+$|i', $_GET['param3'])){ goto NoVerification; } } echo 'В тексте есть недопустимые символы!'.PHP_EOL; echo 'Разрешены только маленькие английские буквы, цифры, точка, тире и нижнее подчеркивание.'; $mysqli->close(); exit; } NoVerification: */ if (($_GET['command'] == 'proc_software') && ($_GET['param1'] == 'add')) { fwrite($log, date('H:i:s')." Пользователь ".$login." с IP-адреса ".$_SERVER['REMOTE_ADDR']." выполнил попытку загрузить файл.".PHP_EOL); if((!isset($_FILES['softfile'])) || ($_FILES['softfile']["name"] != $_GET['param2'])) { echo 'Не выбран файл!'; fwrite($log, 'Файл не получен!'.PHP_EOL); fwrite($log, PHP_EOL); fclose($log); $mysqli->close(); exit; } } if (($_GET['command'] == 'proc_soft_to') || ($_GET['command'] == 'proc_software') || ($_GET['command'] == 'proc_object')){ fwrite($log, date('H:i:s')." Пользователь ".$login." с IP-адреса ".$_SERVER['REMOTE_ADDR']." выполнил:".PHP_EOL); fwrite($log, 'CALL '.$_GET['command'].'("'.$_GET['param1'].'", "'.$_GET['param2'].'", "'.$_GET['param3'].'", "'.$_GET['param4'].'")'.PHP_EOL); if($result = $mysqli->query('CALL '.$_GET['command'].'("'.$_GET['param1'].'", "'.$_GET['param2'].'", "'.$_GET['param3'].'", "'.$_GET['param4'].'")')){ $row = $result->fetch_assoc(); $Answer = $row['@Answer']; fwrite($log, 'Получен ответ: '.$row['@Answer'].PHP_EOL); if ($row['@Ctrl'] == 'add') { if(copy($_FILES["softfile"]["tmp_name"],'/home/new_update_soft/'.$_FILES['softfile']["name"])){ $md5 = md5_file('/home/new_update_soft/'.$_FILES['softfile']["name"]); if(isset($md5)){ $mysqli2 = new mysqli("*******", "******", "******", "update_soft"); if ($mysqli2->connect_errno) { echo "Не удалось подключиться к MySQL:".$mysqli2->connect_error; exit; } $mysqli2->query("SET NAMES utf8"); usleep(500); if($mysqli2->query("UPDATE `software` SET `hash`='".$md5."' WHERE `name`='".$_GET['param2']."'")){ fwrite($log, 'Пользователь '.$login.' с IP-адреса '.$_SERVER['REMOTE_ADDR'].' добавил файл /home/new_update_soft/'.$_FILES['softfile']["name"].PHP_EOL); } $mysqli2->close(); } } } if ($row['@Ctrl'] == 'remove') { unlink('/home/new_update_soft/'.$_GET['param2']); fwrite($log, 'Пользователь '.$login.' с IP-адреса '.$_SERVER['REMOTE_ADDR'].' удалил файл /home/new_update_soft/'.$_GET['param2'].PHP_EOL); } if ($row['@Ctrl'] == 'removeandadd') { if(unlink('/home/new_update_soft/'.$_FILES['softfile']["name"])){ if(copy($_FILES["softfile"]["tmp_name"],'/home/new_update_soft/'.$_FILES['softfile']["name"])){ $md5 = md5_file('/home/new_update_soft/'.$_FILES['softfile']["name"]); if(isset($md5)){ $mysqli2 = new mysqli("*******", "*****", "*********", "update_soft"); if ($mysqli2->connect_errno) { echo "Не удалось подключиться к MySQL:".$mysqli2->connect_error; exit; } $mysqli2->query("SET NAMES utf8"); usleep(500); if($mysqli2->query("UPDATE `software` SET `hash`='".$md5."' WHERE `name`='".$_GET['param2']."'")){ $Answer = $_FILES['softfile']["name"].' обновлен только файл!'; fwrite($log, 'Пользователь '.$login.' с IP-адреса '.$_SERVER['REMOTE_ADDR'].' обновил файл /home/new_update_soft/'.$_FILES['softfile']["name"].PHP_EOL); } $mysqli2->close(); } } } } echo $Answer; } else { fwrite($log, 'Выполнено с ошибкой!'.PHP_EOL); echo 'Упс... Что-то пошло не так!(Запрос завершился с ошибкой)'; } fwrite($log, PHP_EOL); } if (($_GET['command'] == 'proc_holding') || ($_GET['command'] == 'proc_guid_to')){ fwrite($log, date('H:i:s')." Пользователь ".$login." с IP-адреса ".$_SERVER['REMOTE_ADDR']." выполнил:".PHP_EOL); fwrite($log, 'CALL '.$_GET['command'].'("'.$_GET['param1'].'", "'.$_GET['param2'].'", "'.$_GET['param3'].'")'.PHP_EOL); if($result = $mysqli->query('CALL '.$_GET['command'].'("'.$_GET['param1'].'", "'.$_GET['param2'].'", "'.$_GET['param3'].'")')){ $row = $result->fetch_assoc(); echo $row['@Answer']; fwrite($log, 'Получен ответ: '.$row['@Answer'].PHP_EOL); } else { fwrite($log, 'Выполнено с ошибкой!'.PHP_EOL); echo 'Упс... Что-то пошло не так!(Запрос завершился с ошибкой)'; } fwrite($log, PHP_EOL); } if ($_GET['command'] == 'loadsofttable'){ GetSoft($mysqli, 2); } if ($_GET['command'] == 'loadobjecttable'){ if(isset($_GET['soft_in_table'])){ GetObject($mysqli, 2, $_GET['soft_in_table']); } else { echo 'Упс... Что-то пошло не так!'; } } fclose($log); } $mysqli->close(); exit;
Из особенностей, помним что при $_GET[‘obj’] == null у нас выводится основной документ, если же $_GET[‘obj’] существует, можно ему назначить разные запросы.
Очень удобно передавать в параметрах имя процедуры(если количество аргументом всех процедур одинаково), принцип такой:
query(‘CALL ‘.$_GET[‘command’].'(«‘.$_GET[‘param1’].'», «‘.$_GET[‘param2’].'», «‘.$_GET[‘param3′].'»)’)
где $_GET[‘command’] — имя процедуры в БД. У нас фактически есть процедуры с 3 и с 4 аргументами.
Изначально не подразумевалась заливка (перезаливка) файлов с одинаковым именем, поэтому для контроля версий решил собирать md5 файла(а также проверки соответствия хранимого файла и записи в MySQL). Ко всему прочему пришлось ловить ответ процедуры и немного менять его в скрипте.
При удалении файла, его имя пишется в отдельную таблицу, откуда будет удаляться при успешной выгрузке в облако.
Путь к папке с файлами жестко зафиксирован в php(с т.з. безопасности и возможности записать), хотя можно и получать из javascript(фактически позволив пользователю менять папку назначения в браузере).
По невыясненным причинам процедуры забивали коннект и пришлось поднимать еще один, для записи md5-суммы файла.
Все действия логируются в целях безопасности.
В случае неудачного получения файла работа должна быть прервана.
Проверку введенных данных в связи с построенной структурой не совсем удобно организовывать в javascript(динамический DOM не позволяет отслеживать изменение значения объектов, а вешать перезагружающийся с объектом скрипт — не очень красиво), поэтому есть смысл её организовать в php. Т.к. пока не потребовалось, работу в этой области не веду.
Из особенностей интерфейса, т.к. у нас динамически генерируемое дерево, то нам необходимо получать uid объектов. При этом они могут изменяться в режиме реального времени. Для поддержания актуальных данных создано пару невидимых блоков в которых в json-формате лежат актуальные данные по uid-ам.
Для лучшего понимания что кликабельно, в css было сделано изменение курсора на наших псевдо-checkbox-ах, на именах файлов в таблице и т.д. Все javascript-ы расписывать не вижу смысла, т.к. ничего сложного они из себя не представляют.
В функциях php было наложение адресов памяти из-за одноименных переменных, поэтому местами $mysqli стала $mysqliс например.
Вот собственно и все причуды интерфейса.