钩子系统


简介

Hooks 是一项开发功能,允许开发人员将自定义代码添加到Dolibarr的标准页面,而无需修改Dolibarr的核心文件。与链接到Dolibarr事件的 触发系统(与Dolibarr代码交互的另一种方式)不同,Hooks可以预埋在Dolibarr核心代码的任何时间、任何地点运行。这些是程序中的插入点(被视为代码执行从当前页面“转移”并运行自定义代码,然后返回的点)。

每个Hook都有一个名字,例如,代码:$reshook=$hookmanager->executeHooks('doActions',$parameters,$object,$action)中的“doActions”;然而,Hook的名称通常不是唯一的(可能在各种代码模块中重复使用),因此为了准确识别位置,代码模块具有一个“上下文”名称,该名称将被该代码模块位置中的所有Hooks引用。因此,要使用Hooks,您需要知道它的上下文名称(位置)和Hooks名称。

  • Hook是否处于活动状态取决于上下文(通常每个模块有一个上下文:例如,产品模块的“productcard”,发票模块的“invoicecard”等等)。要查找现有的Hooks,请在php文件中搜索“initHooks(”。

这将找到诸如“$hookmanager->initHooks(array('thirdpartycomm','globalcard'));”的结果,其中“thirdpartycomm”是上下文名称

  • Hooks是用于插入或替换标准代码的函数。要查找可以覆盖的代码,请搜索“executeHooks(”。找不到就可能没有预埋Hook,所以你可以添加自己的Hook(并在Github中提交相应的拉取请求,以便将其包含在未来的版本中)。

这将找到诸如“$reshook = $hookmanager->executeHooks('addMoreBoxStatsCustomer', $parameters, $object, $action);”的结果,其中“addMoreBoxStatsCoustomer”是Hook名称。

添加/预埋hook点以允许插入代码

要在您自己的模块中预埋hook(以便您的模块可以被其他模块“挂接”),您需要执行两个步骤。

这些步骤必须为模块中要预埋hook的每个PHP脚本复制。当然,这也是hook在每个核心Dolibarr模块中实现的方式。

1 - 初始化HookManager对象

对于一个页面,将这段代码放在PHP脚本的开头(在 main 的 include 之后):

// Initialize technical object to manage hooks of thirdparties. Note that conf->hooks_modules contains array array
include_once(DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php');
$hookmanager=new HookManager($db);
$hookmanager->initHooks(array('context'));

$hookmanager->initHooks() 接受1个参数(上下文数组),并启用此脚本的钩子支持。

- 'context' 是包含执行上下文的字符串。这是一个简单的指示符,钩子函数可以使用它来检测何时调用它们(多个页面/模块可以在不同的位置调用同一个钩子,而一个钩子函数可能只想为一个给定的上下文运行,而不想为其他上下文运行)。

注意:您可以同时放置多个上下文(例如,如果您需要多个页面共享一个上下文,但也需要特定于给定页面的上下文)。

对于方法或函数,可以通过以下方式获取钩子管理器:

global $hookmanager;

2 - 然后将

$parameters=array();
$reshook=$hookmanager->executeHooks('hookname',$parameters,$object,$action); // See description below
// Note that $action and $object may have been modified by hook
if (empty($reshook))
{
   ... // standard code that can be disabled/replaced by hook if return code > 0.
}

调用放置在允许添加代码的位置:

$parameters=array();
$reshook=$hookmanager->executeHooks('hookname',$parameters,$object,$action); // See description below
// Note that $action and $object may have been modified by hook
if (empty($reshook))
{
   ... // standard code that can be disabled/replaced by hook if return code > 0.
}

$hookmanager->executeHooks() 接受4个参数并添加钩子(这是脚本和模块外部函数的脚本入口点):

- 'hookname' 是将被调用的hook的名称(可以是您想要的任何内容,也可以遵循 Dolibarr 的命名法,查看下面的钩子列表)。例如:“formObjectOptions”

- $parameters 是一个自定义数组,用于向钩子传递更多自定义数据(钩子中的函数可以处理这些数据)。把你想要的放在这里,它可以是一个文件、一个字符串数组、或任何东西...

例如:

$parameters=array('file'=>'my/path/to/a/file', 'customnames'=>array('henry','david','john'));

- $object 是您要传递给钩子函数的对象,通常是当前模块的数据(例如,如果您在发票模块中,则是发票对象等)。它可以是您想要的任何东西,但请记住,它将是钩子函数使用的主要组件。

- $action 是一个指示当前操作的字符串(可以是null或类似于“create”或“edit”的内容)。

注意:如果要在脚本的不同位置添加多个钩子,则需要多次重复此步骤。

现在您的模块应该可以被挂接了,您可以按照下面的实现钩子中的过程来实现一个钩子函数,该函数将利用您刚才添加的hook(还可以测试它是否有效)。

实现Hook

要使用钩子(即添加或覆盖部分代码),必须首先定义模块描述符(请参阅模块开发#创建模块描述符(必做))。然后,您必须执行以下步骤:

1 - 将模块添加到钩子应该运行的上下文中。这意味着,在给定的上下文中,将调用您的代码。为此,请编辑模块的描述符(/htdocs/yourmodulename/core/modules/modYourModuleName.class.php),并输入变量$this->module_parts,如示例所示:

$this->module_parts = array(
'hooks' => array('hookcontext1','hookcontext2')  // Set here all hooks context you want to support
);

不要忘记将 YourModuleName 更改为您自己的模块名称!

注意:可以通过添加以下内容来查找模块的上下文:

print('Module context: '.$object->context);

(将这段代码添加到钩子调用所在的PHP文件中,并在检索到上下文值后将其删除)。

  警告:不要忘记在模块管理界面中禁用和重新激活模块,以便使更改生效。因为当您将新的上下文名称添加到

$this->module_parts = array(
'hooks' => array('hookcontext1','hookcontext2')  // Set here all hooks context you want to support
);

时,此上下文列表必须存储在数据库中。仅当启用自定义模块时,才会发生这种情况。 因此,当您添加/删除/修改/重命名任何上下文名称时,您必须禁用并启用自定义模块才能使更改生效。

2 - 用您的函数替换现有函数(重载)

在模块中创建 /htdocs/yourmodulename/class/actions_yourmodulename.class.php,代码包含钩子调用的方法(该方法的名称在调用executeHooks时可见)。下面是一个例子:

class ActionsYourModuleName
{ 
	/**
	 * Overloading the doActions function : replacing the parent's function with the one below
	 *
	 * @param   array()         $parameters     Hook metadatas (context, etc...)
	 * @param   CommonObject    &$object        The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...)
	 * @param   string          &$action        Current action (if set). Generally create or edit or null
	 * @param   HookManager     $hookmanager    Hook manager propagated to allow calling another hook
	 * @return  int                             < 0 on error, 0 on success, 1 to replace standard code
	 */
	function doActions($parameters, &$object, &$action, $hookmanager)
	{
		$error = 0; // Error counter
		$myvalue = 'test'; // A result value

		print_r($parameters);
		echo "action: " . $action;
		print_r($object);

		if (in_array('somecontext', explode(':', $parameters['context'])))
		{
		  // do something only for the context 'somecontext'
		}

		if (! $error)
		{
			$this->results = array('myreturn' => $myvalue);
			$this->resprints = 'A text to show';
			return 0; // or return 1 to replace standard code
		}
		else
		{
			$this->errors[] = 'Error message';
			return -1;
		}
	}
}

然后,在调用包含executeHooks的代码时,将自动调用该方法,executeHooks为您的代码提供参数 $parameters、$Object和$action。

参数:

  • $parameters 是一个集成钩子数据的元数据数组(其上下文可通过 $parameters['context'] 访问,但根据具体情况,其他信息可能可用)。
  • $object 是您要处理的对象(例如,productcard上下文中的product)
  • $action 指定要执行的操作(例如 "create", "edit" or "view")。
  • $hookmanager 传递它只是为了让您的钩子可以调用其他钩子。

返回值:

  • 执行成功时挂钩的返回代码必须为0或1;错误时为负数。

返回0。如果后续的核心模块代码被包裹于 if (empty($reshook)) {...},它将正常执行,但数据由自定义函数修改。

返回1。如果后续的核心模块代码被包裹于 if (empty($reshook)) {...},它根本不会执行。这意味着你的钩子所做的完全取代了Dolibarr在调用钩子后所做的。

返回负数。则可以通过设置 $this->errors[]='Error message to report to user' 向用户提供错误消息。

  • 如果该方法将一个数组赋给属性 $this->results,则数组 $hookManager->resArray 将自动加载该数组的内容,以便以后重用。
  • 如果该方法将一个字符串赋给属性 $this->resprints ,则该字符串将在方法退出时由挂接处理程序(executeHook)打印出来。
  • 您的钩子还可以更改$object和$action的值。

Dolibarr中可用的Hooks清单

要查找Dolibarr中可用的挂钩点,只需在源代码中搜索“executeHooks(”即可,您将很容易找到所有已预埋的挂钩点。

以下是一个可用的挂钩点清单(不完整):Category:Hooks...

请注意:此清单会随着版本升级而不断增长,因此如果您真的想知道是否存在特定的钩子或上下文,请使用上面列出的方法直接搜索源代码。

Dolibarr中可用的上下文清单

要查找Dolibarr中可用的上下文,查找过程类似于查找挂钩点。

在源代码中搜索“initHooks(”,您将很容易找到所有已实现的上下文。

这是其中的一小部分(不完整):

adherents\card.php(111): membercard
adherents\type.php(73): membertypecard
categories\categorie.php(96): categorycard
comm\card.php(72): commcard
comm\propal.php(99): propalcard
comm\action\card.php(85): actioncard
comm\action\index.php(112): agenda
comm\mailing\card.php(55): mailingcard
commande\card.php(93): ordercard
compta\facture.php(105): invoicecard
compta\paiement.php(70): paiementcard
compta\deplacement\card.php(50): tripsandexpensescard
compta\dons\card.php(53): doncard
compta\localtax\clients.php(172): externalbalance
compta\salaries\card.php(47): salarycard
compta\tva\card.php(45): taxvatcard
contact\card.php(77): contactcard
contrat\card.php(70): contractcard
expedition\card.php(85): expeditioncard
fichinter\card.php(80): interventioncard
fourn\card.php(54): suppliercard
fourn\commande\card.php(80): ordersuppliercard
fourn\commande\orderstoinvoice.php(88): orderstoinvoicesupplier
fourn\facture\card.php(72): invoicesuppliercard
fourn\facture\paiement.php(71): paymentsupplier
livraison\card.php(68): deliverycard
product\card.php(91): productcard
product\composition\card.php(55): productcompositioncard
product\fournisseurs.php(62): pricesuppliercard
product\stats\commande.php(45): productstatsorder
product\stats\commande_fournisseur.php(45): productstatssupplyorder
product\stats\contrat.php(45): productstatscontract
product\stats\facture.php(48): productstatsinvoice
product\stats\facture_fournisseur.php(47): productstatssupplyinvoice
product\stats\propal.php(45): productstatspropal
product\stock\card.php(54): warehousecard
projet\card.php(48): projectcard
projet\tasks.php(67): projecttaskcard
resource\card.php(60): resource_card
resource\element_resource.php(58): element_resource
societe\agenda.php(41): agendathirdparty
societe\commerciaux.php(40): salesrepresentativescard
societe\consumption.php(80): consumptionthirdparty
societe\info.php(41): infothirdparty
societe\soc.php(80): thirdpartycard
user\card.php(93): usercard
user\list.php(72): userlist
user\passwordforgotten.php(56): passwordforgottenpage
...

请注意:此清单会随着版本升级而不断增长,因此如果您真的想知道是否存在特定的钩子或上下文,请使用上面列出的方法直接搜索源代码。

参考