[原]iTop自定义修改相关时间字段的实现要点记录
[gzg@operation itop]$ cd /var/www/html/itop [gzg@operation itop]$ mkdir yh [gzg@operation itop]$ cd yh [gzg@operation itop]$ touch upticketdates.php
开发简要说明:
字典文件位置: itop/env-production/dictionaries/zh-cn.dict.php 'Class:UserRequest/Attribute:resolutiondate' => '解决时间', 'Class:UserRequest/Attribute:resolutiondate+' => '',
iTop的底层数据支撑框架为CMDB
一个数据模型类的继承层次为(如:UserRequest):
UserRequest < Ticket < _Ticket < cmdbAbstractObject < CMDBObject < DBObject
这些类的文件位置是:
- UserRequest => O:\itop\env-production\itop-request-mgmt-itil\model.itop-request-mgmt-itil.php
- Ticket => O:\itop\env-production\itop-tickets\model.itop-tickets.php
- _Ticket => O:\itop\env-toolkit\itop-tickets\main.itop-tickets.php
- cmdbAbstractObject => O:\itop\application\cmdbabstract.class.inc.php
- CMDBObject => O:\itop\core\cmdbobject.class.inc.php
- DBObject => O:\itop\core\dbobject.class.php
DBObject安装注释中的说明: "Class dbObject: the root of persistent classes" 它是“可持久化”的“数据模型类”的“根类”,而DBObject中操作数据库依靠的的更底层的CMDBSource(itop/core/cmdbsource.class.inc.php) CMDBSource中的数据操作则依靠“mysqli”
self::$m_oMysqli = new mysqli(self::$m_sDBHost, self::$m_sDBUser, self::$m_sDBPwd);
例如其 DBUpdate() 方法中的两行代码: $sSQL = $oFilter->MakeUpdateQuery($aChanges); CMDBSource::Query($sSQL);
引入三个文件后,可以使用 MetaModel, 装载更新数据记录
require_once('../approot.inc.php');
require_once(APPROOT.'/application/application.inc.php');
require_once(APPROOT.'/application/startup.inc.php');
类MetaModel定义位于: itop/core/metamodel.class.php
静态方法MetaModel::GetObject可以装载指定数据类 GetObject($sClass, $iKey, $bMustBeFound = true, $bAllowAllData = false, $aModifierProperties = null) 参数说明: $sClass:数据类名,例如表“itopticket”的类名为“Ticket”,可以使用基类装载子类,也可以用子类直接load $iKey:数据行的id $bMustBeFound:该id的数据行是否必须存在, 默认值为false,未找到返回null,如果设置为true,未找到抛出异常;MySQLException $bAllowAllData:是否load全部字段?? $aModifierProperties:可修改字段数组?? 使用举例: $data = MetaModel::GetObject('Ticket',1296); getclass($data); // UserRequest, 这是一条request $data->get('resolutiondate'); // 获取解决时间 $data->Set('resolutiondate','2019-12-19 12:33:34'); // 设置解决时间 $data->DBUpdate(); // 更新数据库
最后更新时间的修改
Ticket的三个子类 UserRequest,Problem,Incident 都覆盖了基类的OnUpdate方法,其中主要是做了更新追踪记录,设置最后更新时间 因此想要覆盖更新这个字段,只能绕开 数据模型类,幸运的是这个字段是在 基类表 Ticket中,因此可以使用CMDBSource直接修改数据库中的字段值, 从而实现最后更新时间的修改。
CMDBSource::query("update itop_ticket set last_update='$last_update' where id = $ticket_id");
整个过程实现源码:
<?php require_once('../approot.inc.php'); require_once(APPROOT.'/application/application.inc.php'); require_once(APPROOT.'/application/startup.inc.php'); // "http://192.168.0.105/itop/pages/UI.php?c[menu]=UserRequest:OpenRequests" $tbls = array('UserRequest'=>'itop_ticket_request','Incident'=>'itop_ticket_incident','Problem'=>'itop_ticket_problem'); $baseFlds = array('start_date','end_date','close_date'); $extFlds = array('assignment_date','resolution_date'); $ticket_id=$start_date=$end_date=$assignment_date=$close_date=$resolution_date=$last_update=""; $error_msg = $msg= ''; $btn=''; /** this function , just for example */ function __update(){ $t=MetaModel::GetObject('Ticket',1296); $rd=$t->Get('resolution_date'); $t->Set('resolution_date','2019-12-19 12:33:34'); $t->DBUpdate(); // direct access $ds = CMDBSource::query("update itop_ticket set start_date='2012-12-12 12:12:12' where id =1295"); // print $ds; // this will display: 1; } function pm($name){ return $_POST[$name]; } function has_pm($name){ return array_key_exists($name,$_POST); } /** startup script */ if ($_SERVER["REQUEST_METHOD"] == "POST"){ try{ $ticket_id=pm('ticket_id'); //echo("<pre>ticket_id=[$ticket_id]</pre>"); if(strlen($ticket_id)==0){ $error_msg='请输入id!'; }else{ $data = MetaModel::GetObject('Ticket',$ticket_id, false); $dataClass = get_class($data); // ex: UserRequest //echo("data loaded=$data"); if(!$data){ echo("<script>var oDateLoaded = false;</script>"); $error_msg='数据未找到,该ID并不存在!'; goto END; } if(has_pm('btn_load')){ $btn = 'btn_load'; if($data){ $start_date = $data->get('start_date'); $end_date=$data->get('end_date'); $assignment_date=$data->get('assignment_date'); $close_date=$data->get('close_date'); $resolution_date=$data->get('resolution_date'); $last_update=$data->get('last_update'); echo("<script>var oDateLoaded = true;</script>"); $msg="已成功查询到数据!"; }else{ $error_msg = "Ticket id=[" + $ticket_id +"] 未找到!"; } }else{ /** btn_save */ $btn = 'btn_save'; $start_date = pm('start_date'); $end_date=pm('end_date'); $assignment_date=pm('assignment_date'); $close_date=pm('close_date'); $resolution_date=pm('resolution_date'); $last_update=pm('last_update'); if($start_date)$data->set('start_date',$start_date); if($end_date)$data->set('end_date',$end_date); if($assignment_date)$data->set('assignment_date',$assignment_date); if($close_date)$data->set('close_date',$close_date); if($resolution_date)$data->set('resolution_date',$resolution_date); if($last_update)$data->set('last_update',$last_update); //不起作用!!$data->onUpdate=function(){}; $data->DBUpdate(); // if($last_update){ CMDBSource::query("update itop_ticket set last_update='$last_update' where id = $ticket_id"); } // echo("<script>var oDateLoaded = true;</script>"); $msg = "ok, 保存成功了! 您可以继续修改当前记录。"; } } }catch(Exception $ex){ $error_msg = $ex->getMessage(); } }else{ /** GET */ echo("<script>var oDateLoaded = false;</script>"); } /** end of HTTP method */ END: echo '' ?> <div class="form" id="pane_up_ticket_dates"> <style scoped="scoped"> .grid-x, .button-group{ display: flex; margin:8px; } .button-group.align-center{ justify-content: center; } .msg-info{ color: lightblue; font-size: 1.2em; } </style> <form method="post" action="../yh/up_ticket_dates.php" name="form_up_ticket_dates" id='form_up_ticket_dates' "> <h2>[请首先输入id并查询]</h2> <div class="grid-x"> <label>事件ID[形如:1298]:<input type="number" name="ticket_id" value="<?php echo $ticket_id; ?>"></label> <button name="btn_load">查询</button> </div> <hr/><p class="msg-info"> <?php if($msg){ ?> <?php echo $msg ?> <?php } ?> </p><hr/> <br/><br/> <div class="grid-x"> <label>起始日期:<input type="datetime" name="start_date" value="<?php echo $start_date; ?>"></label> </div> <div class="grid-x"> <label>结束日期:<input type="datetime" name="end_date" value="<?php echo $end_date; ?>"></label> </div> <div class="grid-x"> <label>指派日期:<input type="datetime" name="assignment_date" value="<?php echo $assignment_date; ?>"></label> </div class="grid-x"> <div class="grid-x"> <label>关闭日期:<input type="datetime" name="close_date" value="<?php echo $close_date; ?>"></label> </div> <div class="grid-x"> <label>解决日期:<input type="datetime" name="resolution_date" value="<?php echo $resolution_date; ?>"></label> </div> <div class="grid-x"> <label>最后更新:<input type="datetime" name="last_update" value="<?php echo $last_update; ?>"></label> </div> <div class="grid-x button-group align-center"> <button name="btn_reset">清除</button><div style="width:5em;"></div> <button name="btn_save" >更新</button> </div> </form> <br/> <div id="error_pad" style="color:red;"> <?php if($error_msg){ ?> <?php echo($error_msg); ?> <?php } ?> </div> <script> $(function(){ var pane= $('#pane_up_ticket_dates'), frm= pane.find('form'),url=frm.attr('action'), ppad = $('.ui-layout-content'); var errBox= pane.find('#error_pad'), msgBox = pane.find('.msg-info'); if(!oDateLoaded){ pane.find('button[name="btn_save"]').attr("disabled",true); } frm.find('button').on('click',function(){ var btn=$(this), btnName=btn.attr('name'); if(btnName=='btn_reset'){ oDateLoaded = false; frm.find('input').val(''); errBox.html(''); return false; } if(btnName === 'btn_save' && !oDateLoaded){ var msg="请首先查询,然后修改保存!"; errBox.html(msg); alert(msg); return false; } msgBox.html('请稍等......'); var ps = frm.serialize() + "&" + btnName + "="; $.ajax({url: url,data: ps,type: 'post', dataType:'html'}).done(function(rep,textStatus,jqXHR){ ppad.html(rep); }).fail(function (jqXHR, textStatus, errorThrown) { ppad.html(jqXHR.responseText); }); //.always(hbusy); return false; }); }); </script> </div>
二、插入到管理菜单组,实现功能界面动态装载到iTop界面的右侧工作区域。
1,实现MyWebPageMenuNode
想要添加自己定义的菜单,就要用到iTop的应用菜单类ApplicationMenu,这个类的实现位于itop/application/menunode.class.inc.php
首先找到WebPageMenuNode这个类,由于需要实现ajax动态装载,我们不能直接使用这个类,复制一份本类的源码,改名为MyWebPageMenuNode,对其GetHyperlink方法稍作修改
class MyWebPageMenuNode extends MenuNode { protected $sHyperlink; /** * Create a menu item that points to any web page (not only UI.php) * @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary) * @param string $sHyperlink URL to the page to load. Use relative URL if you want to keep the application portable ! * @param integer $iParentIndex ID of the parent menu * @param float $fRank Number used to order the list, any number will do, but for a given level (i.e same parent) all menus are sorted based on this value * @param string $sEnableClass Name of class of object * @param integer $iActionCode Either UR_ACTION_READ, UR_ACTION_MODIFY, UR_ACTION_DELETE, UR_ACTION_BULKREAD, UR_ACTION_BULKMODIFY or UR_ACTION_BULKDELETE * @param integer $iAllowedResults Expected "rights" for the action: either UR_ALLOWED_YES, UR_ALLOWED_NO, UR_ALLOWED_DEPENDS or a mix of them... * @return MenuNode */ public function __construct($sMenuId, $sHyperlink, $iParentIndex, $fRank = 0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null) { parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus); $this->sHyperlink = $sHyperlink; $this->aReflectionProperties['url'] = $sHyperlink; } public function GetHyperlink($aExtraParams) { $aExtraParams['c[menu]'] = $this->GetMenuId(); //return $this->AddParams( $this->sHyperlink, $aExtraParams); $link=$this->AddParams( $this->sHyperlink, $aExtraParams); return "javascript:openMyPages('$link')"; } public function RenderContent(WebPage $oPage, $aExtraParams = array()) { assert(false); // Shall never be called, the external web page will handle the display by itself } }
2,修改ApplicationMenu类,添加自定义方法
/** * ---------------------------------------------------------------------------------------------------------------------------- * ---- 定制菜单 * ---------------------------------------------------------------------------------------------------------------------------- */ static public function AddMyMenu($oPage){ //public function __construct($sMenuId, $sHyperlink, $iParentIndex, $fRank = 0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null) //AdminTools $link="../yh/up_ticket_dates.php"; //$link= "../pages/UI.php?c[menu]=UpTicketDates:Modify"; $mi = new MyWebPageMenuNode('UpTicketDates',$link,0); self::InsertMenu($mi,1,20); $oPage->add_ready_script('window.openMyPages=function(link){$(".ui-layout-content").load(link);};'); }
在这个方法中,实现了两个功能,创建一个新的菜单项, 然后在页面中注册了点击菜单时需要用到的js函数(注意:js函数名称和上面自定义类中的名称呼应)。
3,修改DisplayMenu方法,调用上面的自定义方法:
static public function DisplayMenu($oPage, $aExtraParams) { self::LoadAdditionalMenus(); // Sort the root menu based on the rank usort(self::$aRootMenus, array('ApplicationMenu', 'CompareOnRank')); // 添加自定义菜单 self::AddMyMenu($oPage); // $iAccordion = 0; $iActiveMenu = self::GetMenuIndexById(self::GetActiveNodeId()); foreach(self::$aRootMenus as $aMenu) { $oMenuNode = self::GetMenuNode($aMenu['index']); if (!$oMenuNode->IsEnabled()) continue; // Don't display a non-enabled menu $oPage->AddToMenu('<h3>'.$oMenuNode->GetTitle().'</h3>'); $oPage->AddToMenu('<div>'); $aChildren = self::GetChildren($aMenu['index']); if (count($aChildren) > 0) { $oPage->AddToMenu('<ul>'); $bActive = self::DisplaySubMenu($oPage, $aChildren, $aExtraParams, $iActiveMenu); $oPage->AddToMenu('</ul>'); if ($bActive) { //$oPage->add_ready_script("$('#accordion').accordion('activate', $iAccordion);"); // $oPage->add_ready_script("$('#accordion').accordion('option', {collapsible: true});"); // Make it auto-collapsible once it has been opened properly $oPage->add_ready_script("$('#accordion').accordion('option', {collapsible: true, active: $iAccordion});"); // Make it auto-collapsible once it has been opened properly } } $oPage->AddToMenu('</div>'); $iAccordion++; } }