Joomla自动化任务组件开发

在托管的Joomla站点上工作时,经常需要将公司应用程序与之集成。例如,将公司的ERP系统与基于Joomla的电子商务解决方案Virtuemart集成:每天需要在线发布数百种产品,并且这些产品的价格在一天之内会多次变动。此外,使用托管站点通常也意味着没有直接的数据库访问权限。

为了解决这个问题,创建了一个Joomla组件,它可以接收远程命令并将其发送到底层数据库,并支持事务处理。这个组件的架构相当简单:它发布了一个页面,用于处理传入的XML格式消息。它执行XML消息中包含的命令,并发送回响应消息。

消息交换不使用任何标准;既不是SOAP也不是其他RPC协议。创建了一个专有的基于HTTP的协议。

由于页面是公开的,如果没有安全检查,它可能会成为一个巨大的安全漏洞。因此,引入了一个基于密钥交换的简单安全机制。组件安装后,会生成一个唯一的安全令牌。这个安全令牌必须出现在请求参数中,才能被服务器接受。

Joomla组件实现了一个管理面板和一个站点部分。管理面板显示必须使用的安全密钥,而站点页面仅处理XML请求。不会解释如何创建Joomla组件,因为已经有太多关于这个话题的教程(可以从这里开始)。

有趣的点是使用安装后脚本生成唯一的安全密钥,以及处理XML请求的站点页面。

在Joomla安装包中,名为“component.xml”的XML文件包含所有安装指令。指令<scriptfile>script.php</scriptfile>指示安装过程查找一个名为component_nameInstallerScript的类,该类包含在安装包根文件夹中的script.php文件中。

这个类的一些明确定义的public方法在安装过程中被调用:function install($parent)在安装过程中调用。在这里,可以指定在这个阶段执行的附加步骤;function uninstall($parent)在卸载过程中调用;function update($parent)在更新过程中调用;function preflight($type, $parent)在安装和更新之前调用;function postflight($type, $parent)在安装和更新之后调用。

在postflight函数中使用,以写入包含安全令牌的XML文件: function postflight($type, $parent) { $string = ' '. md5(uniqid(rand(), true)). ' '; $xml = new SimpleXMLElement($string); $xml->asXML(JPATH_SITE.'/components/com_sqlxml/sec.xml'); }

站点部分由一个控制器管理,该控制器仅处理XML输出。请求必须指定URL参数“format=xml”。它注册了一个名为"cmdep.execcmd"的函数,用于处理请求并准备要发送回的XML响应。

这个函数的第一步是安全检查。如果令牌不匹配,它会回复错误。一旦验证了安全令牌,函数就读取请求的内容,并针对指定的每个命令执行所需的操作。

让看看消息结构: <?xml version="1.0" encoding="utf-16"?> <msg-req xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <commands> <command type="sql" action="select" mandatory="false"></command> <command type="sql" action="select" mandatory="false"></command> </commands> </msg-req> 如所见,msg-req包含一个commands集合,然后是一个或多个command。整个过程包含在一个事务中;每个command可以指定是否是事务的一部分。

一个command,现在只能是SQL语句,可以指定一些参数:"mandatory"参数指示command必须成功才能完成事务。如果设置为true并且命令失败,事务将回滚。"action"参数指示要执行的SQL语句类型。管理的类型有"INSERT"、"UPDATE"、"DELETE"、"SELECT"、"CALL"。参数值与命令的实际内容之间没有语义检查,这个参数只管理SQL命令的执行方式以及它将如何返回执行结果。因此,"INSERT"命令将返回最后插入的自动生成的ID,而"UPDATE"或"DELETE"命令返回受影响的行数。

"save"参数用于指定一个变量名,返回值将临时保存以供下一个命令使用。例如: "TABLE1"有3个字段:"ID"是一个自动生成的数字字段,也是主键,后面跟着两个文本字段"FIELD1"和"FIELD2"。 "TABLE2"也有3个字段:一个自动生成的"ID","REF_ID"是"TABLE1"中"ID"字段的引用,以及类型为文本的"FIELD1"。 如果需要在这些表中插入相关记录,必须创建2个INSERT命令,强制(所以事务只有在两个命令都成功执行时才提交),第一个命令将返回值保存在名为"SAVED_ID"的变量中,第二个命令在下一个SQL语句中引用该变量,形式为"$[SAVED_ID]": <command type="sql" action="insert" mandatory="true" save="ID"> <![CDATA[ INSERT INTO TABLE1 (FIELD1, FIELD2) VALUES ('VALUE1', 'VALUE2') ]]&gt; &lt;/command&gt; &lt;command type="sql" action="insert" mandatory="true"&gt; <![CDATA[ INSERT INTO TABLE2 (REF_ID, FIELD1) VALUES ($[SAVED_ID], 'VALUE2') ]]&gt; &lt;/command&gt; </code> 当第二个命令执行时,所有形式为$[...]的参数都会被替换为相应的值。因此,$[SAVED_ID]的值被替换为第一个语句中保存的值。

在PHP中,这个参数替换是通过使用关联数组实现的。返回值被保存到一个全局数组中,使用"saved"参数的值作为键: $res_values = array(); $cmd_attr = $cmd->attributes(); $save = (string)$cmd_attr['save']; if($save!=null) $res_values[$save]=$query_result[0]; 然后,SQL语句中所有匹配$[...]的参数都会被替换,查询数组: $tmp = (string)$cmd; $ct = preg_match_all("/\$\[((?:\[\S*\]|[^\[])*)\]/", $tmp, $arr); for($i=0; $i<$ct; $i++){ $tmp = str_replace($arr[0][$i], $res_values[$arr[1][$i]], $tmp); }

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485