dolibarr 25.0.0-alpha
actions_ai.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2026 Nick Fragoulis
3 * Copyright (C) 2026 Braito <braito4@hotmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
31{
35 public $db;
36
40 public $error = '';
41
45 public $results = array();
46
52 public function __construct($db = null)
53 {
54 $this->db = $db;
55 }
56
66 public function addMoreActionsEmailCollector($parameters, &$object, &$action, $hookmanager)
67 {
68 global $langs;
69
70 require_once DOL_DOCUMENT_ROOT.'/ai/class/emailcleaner.class.php';
71
72 if (!EmailCleaner::isRuntimeAvailable()) return 0;
73 if (!getDolGlobalInt('AI_EMAILCLEANER_EXPOSE_OPERATION', 0)) return 0;
74
75 $arrayoftypes = (!empty($parameters['arrayoftypes']) && is_array($parameters['arrayoftypes'])) ? $parameters['arrayoftypes'] : array();
76 $langs->load('ai');
77 $arrayoftypes['hook_ai_emailcleaner'] = $langs->trans('AIEmailCleanerOperation');
78 $this->results = $arrayoftypes;
79
80 return 1;
81 }
82
92 public function doCollectImapOneCollector($parameters, &$object, &$action, $hookmanager)
93 {
94 require_once DOL_DOCUMENT_ROOT.'/ai/class/emailcleaner.class.php';
95
96 if (!EmailCleaner::isRuntimeAvailable()) return 0;
97 if (!EmailCleaner::isEmailCollectorHookAction($action)) return 0;
98 if (!getDolGlobalInt('AI_EMAILCLEANER_ENABLED', 0)) return 0;
99
100 $emailCleaner = new EmailCleaner($this->db);
101 $result = $emailCleaner->processEmailCollectorMessage($parameters, $object);
102 if (!empty($result['file'])) {
103 $this->results['ai_emailcleaner_file'] = (string) $result['file'];
104 }
105
106 return 0;
107 }
108
112 public $errors = array();
113
117 public $resprints = '';
118
122 private $listRowIds = array();
123
128 private $listPageSeen = false;
129
143 public function printFieldListValue($parameters, &$object, &$action)
144 {
145 $this->listPageSeen = true;
146 if (count($this->listRowIds) < 100 && isset($parameters['obj']) && is_object($parameters['obj'])) {
147 // Lists alias the primary key inconsistently in their SELECT:
148 // societe uses "rowid", facture uses "f.rowid as id" - accept both.
149 $rowid = (int) ($parameters['obj']->rowid ?? $parameters['obj']->id ?? 0);
150 if ($rowid > 0) {
151 $this->listRowIds[] = $rowid;
152 }
153 }
154
155 return 0;
156 }
157
184 public function printCommonFooter($parameters, &$object, &$action)
185 {
186 global $conf, $user;
187
188 if (!isModEnabled('ai') || !getDolGlobalString('AI_ASSISTANT_ENABLED')) {
189 return 0;
190 }
191 if (empty($user->id) || !$user->hasRight('ai', 'assistant', 'use')) {
192 return 0;
193 }
194 // printCommonFooter's call site passes a hardcoded null object; the
195 // page's main object is only reachable via the global scope here.
196 $pageObject = $object;
197 if (!is_object($pageObject) || empty($pageObject->id)) {
198 $pageObject = isset($GLOBALS['object']) ? $GLOBALS['object'] : null;
199 }
200 // Card vs list discrimination: the per-row hook fired = list page,
201 // whatever the template $object happens to hold; and a card emission
202 // additionally requires the URL to actually address the object (card
203 // pages always carry id or ref), so a list page whose template object
204 // carries a stray id can never masquerade as a card.
205 $urlIds = array((int) ($_GET['id'] ?? $_POST['id'] ?? 0), (int) ($_GET['facid'] ?? $_POST['facid'] ?? 0), (int) ($_GET['socid'] ?? $_POST['socid'] ?? 0));
206 $urlRef = dol_string_nohtmltag((string) ($_GET['ref'] ?? $_POST['ref'] ?? ''));
207 $looksLikeCard = is_object($pageObject) && !empty($pageObject->id)
208 && (in_array((int) $pageObject->id, $urlIds, true) || ($urlRef !== '' && $urlRef == ($pageObject->ref ?? '')));
209 if ($this->listPageSeen || !empty($this->listRowIds) || !$looksLikeCard || empty($pageObject->element)) {
210 // List/dashboard pages: no single object, but the user's own active
211 // filters are context enough - emitted uninterpreted, the model
212 // maps them onto tool arguments (which validate as always).
213 // Dolibarr's list search form submits by POST, so a filter the user
214 // just typed never appears in $_GET: read both, GET last so a
215 // bookmarked/paginated URL wins over a stale POST body.
216 $filters = array();
217 foreach (array_merge($_POST, $_GET) as $k => $v) {
218 if (!is_string($v) || $v === '' || strlen($v) > 200) {
219 continue;
220 }
221 if (preg_match('/^(search_[a-z0-9_]+|sall|search_all|sortfield|sortorder|contextpage)$/', $k)) {
222 $filters[$k] = $v;
223 }
224 }
225 $listElement = (is_object($pageObject) && !empty($pageObject->element)) ? (string) $pageObject->element : '';
226
227 // Module dashboards (index pages): no object, no rows - the page
228 // identity itself is the context ("what am I looking at?" and
229 // module-scoped statistics questions). Whitelisted paths only, so
230 // admin or third-party pages never leak their URLs into prompts.
231 if (!$this->listPageSeen && empty($filters)) {
232 $dashboards = array(
233 '/index.php' => 'main home',
234 '/societe/index.php' => 'thirdparties',
235 '/compta/facture/index.php' => 'customer invoices',
236 '/fourn/facture/index.php' => 'supplier invoices',
237 '/commande/index.php' => 'sales orders',
238 '/fourn/commande/index.php' => 'supplier orders',
239 '/comm/propal/index.php' => 'commercial proposals',
240 '/supplier_proposal/index.php' => 'supplier proposals',
241 '/product/index.php' => 'products and services',
242 '/product/stock/index.php' => 'stock and warehouses',
243 '/projet/index.php' => 'projects',
244 '/contrat/index.php' => 'contracts',
245 '/fichinter/index.php' => 'interventions',
246 '/ticket/index.php' => 'tickets',
247 '/expedition/index.php' => 'shipments',
248 '/reception/index.php' => 'receptions',
249 '/adherents/index.php' => 'members',
250 '/don/index.php' => 'donations',
251 '/expensereport/index.php' => 'expense reports',
252 '/comm/action/index.php' => 'agenda',
253 '/mrp/index.php' => 'manufacturing',
254 '/recruitment/index.php' => 'recruitment'
255 );
256 // Exact match on the path relative to DOL_URL_ROOT - a suffix
257 // match on '/index.php' would hit every module index for the
258 // home entry.
259 $self = (string) ($_SERVER['PHP_SELF'] ?? '');
260 $urlRoot = (string) constant('DOL_URL_ROOT');
261 $rel = ($urlRoot !== '' && strpos($self, $urlRoot) === 0) ? substr($self, strlen($urlRoot)) : $self;
262 if (isset($dashboards[$rel])) {
263 print "\n".'<script nonce="'.getNonce().'">window.aiPageContext = '.json_encode(array('dashboard' => $dashboards[$rel])).';</script>'."\n";
264
265 return 0;
266 }
267 }
268 if (($this->listPageSeen || !empty($filters) || !empty($this->listRowIds)) && !empty($listElement)) {
269 $ctx = array('element' => $listElement, 'list' => 1, 'filters' => $filters);
270 if (!empty($this->listRowIds)) {
271 $ctx['ids'] = $this->listRowIds; // rows this page actually shows
272 }
273 print "\n".'<script nonce="'.getNonce().'">window.aiPageContext = '.json_encode($ctx).';</script>'."\n";
274 }
275
276 return 0;
277 }
278
279 $context = array(
280 'element' => (string) $pageObject->element,
281 'id' => (int) $pageObject->id,
282 'ref' => (string) ($pageObject->ref ?? ''),
283 'socid' => (int) (get_object_vars($pageObject)['socid'] ?? 0)
284 );
285
286 // printCommonFooter output convention: print directly (the caller does
287 // not echo resPrint for this hook).
288 print "\n".'<script nonce="'.getNonce().'">window.aiPageContext = '.json_encode($context).';</script>'."\n";
289
290 return 0;
291 }
292}
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
Class ActionsAi.
addMoreActionsEmailCollector($parameters, &$object, &$action, $hookmanager)
Add AI Email Cleaner action in EmailCollector operations.
__construct($db=null)
Constructor.
doCollectImapOneCollector($parameters, &$object, &$action, $hookmanager)
Execute AI cleaner when EmailCollector operation type is "hook_ai_emailcleaner".
printFieldListValue($parameters, &$object, &$action)
Collect the id of each rendered list row.
printCommonFooter($parameters, &$object, &$action)
Emit the current page context for the assistant popover.
Class EmailCleaner.
static isRuntimeAvailable()
Check if the runtime dependencies for EmailCleaner are available.
static isEmailCollectorHookAction($action)
Check if the EmailCollector hook action targets EmailCleaner.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
dol_string_nohtmltag($stringtoclean, $removelinefeed=1, $pagecodeto='UTF-8', $strip_tags=0, $removedoublespaces=1)
Clean a string from all HTML tags and entities.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
getNonce()
Return a random string to be used as a nonce value for js.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
$context
@method int call_trigger(string $triggerName, ?User $user)
Definition logout.php:42