dolibarr 25.0.0-alpha
api_recruitments.class.php
1<?php
2/* Copyright (C) 2022 Thibault FOUCART <support@ptibogxiv.net>
3 * Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
4 * Copyright (C) 2025 Frédéric France <frederic.france@free.fr>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20use Luracast\Restler\RestException;
21
22dol_include_once('/recruitment/class/recruitmentjobposition.class.php');
23dol_include_once('/recruitment/class/recruitmentcandidature.class.php');
24require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
25
26
27
41{
45 public $jobposition;
49 public $candidature;
50
51
57 public function __construct()
58 {
59 global $db;
60 $this->db = $db;
61 $this->jobposition = new RecruitmentJobPosition($this->db);
62 $this->candidature = new RecruitmentCandidature($this->db);
63 }
64
65
79 public function getJobPosition($id)
80 {
81 if (!DolibarrApiAccess::$user->hasRight('recruitment', 'recruitmentjobposition', 'read')) {
82 throw new RestException(403);
83 }
84
85 $result = $this->jobposition->fetch($id);
86 if (!$result) {
87 throw new RestException(404, 'JobPosition not found');
88 }
89
90 if (!DolibarrApi::_checkAccessToResource('recruitment', $this->jobposition->id, 'recruitment_recruitmentjobposition')) {
91 throw new RestException(403, 'Access to instance id='.$this->jobposition->id.' of object not allowed for login '.DolibarrApiAccess::$user->login);
92 }
93
94 return $this->_cleanObjectDatas($this->jobposition);
95 }
96
110 public function getCandidature($id)
111 {
112 if (!DolibarrApiAccess::$user->hasRight('recruitment', 'recruitmentjobposition', 'read')) {
113 throw new RestException(403);
114 }
115
116 $result = $this->candidature->fetch($id);
117 if (!$result) {
118 throw new RestException(404, 'Candidature not found');
119 }
120
121 if (!DolibarrApi::_checkAccessToResource('recruitment', $this->candidature->id, 'recruitment_recruitmentcandidature')) {
122 throw new RestException(403, 'Access to instance id='.$this->candidature->id.' of object not allowed for login '.DolibarrApiAccess::$user->login);
123 }
124
125 return $this->_cleanObjectDatas($this->candidature);
126 }
127
148 public function indexJobPosition($sortfield = "t.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $sqlfilters = '', $properties = '', $pagination_data = false)
149 {
150 $obj_ret = array();
151 $tmpobject = new RecruitmentJobPosition($this->db);
152
153 if (!DolibarrApiAccess::$user->hasRight('recruitment', 'recruitmentjobposition', 'read')) {
154 throw new RestException(403);
155 }
156
157 $socid = DolibarrApiAccess::$user->socid ?: 0;
158
159 $restrictonsocid = 0; // Set to 1 if there is a field socid in table of object
160
161 // If the internal user must only see his customers, force searching by him
162 $search_sale = 0;
163 if ($restrictonsocid && !DolibarrApiAccess::$user->hasRight('societe', 'client', 'voir') && !$socid) {
164 $search_sale = DolibarrApiAccess::$user->id;
165 }
166
167 $sql = "SELECT t.rowid";
168 $sql .= " FROM ".MAIN_DB_PREFIX.$tmpobject->table_element." AS t";
169 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX.$tmpobject->table_element."_extrafields AS ef ON (ef.fk_object = t.rowid)"; // Modification VMR Global Solutions to include extrafields as search parameters in the API GET call, so we will be able to filter on extrafields
170 $sql .= " WHERE 1 = 1";
171 if ($tmpobject->ismultientitymanaged) {
172 $sql .= ' AND t.entity IN ('.getEntity($tmpobject->element).')';
173 }
174 if ($restrictonsocid && $socid) {
175 $sql .= " AND t.fk_soc = ".((int) $socid);
176 }
177 // Search on sale representative
178 if ($search_sale && $search_sale != '-1') {
179 if ($search_sale == -2) {
180 $sql .= " AND ".getSalesRepresentativeSqlFilter('t.fk_soc', 0, 1);
181 } elseif ($search_sale > 0) {
182 $sql .= " AND ".getSalesRepresentativeSqlFilter('t.fk_soc', (int) $search_sale);
183 }
184 }
185 if ($sqlfilters) {
186 $errormessage = '';
187 $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage);
188 if ($errormessage) {
189 throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage);
190 }
191 }
192
193 //this query will return total orders with the filters given
194 $sqlTotals = str_replace('SELECT t.rowid', 'SELECT count(t.rowid) as total', $sql);
195
196 $sql .= $this->db->order($sortfield, $sortorder);
197 if ($limit) {
198 if ($page < 0) {
199 $page = 0;
200 }
201 $offset = $limit * $page;
202
203 $sql .= $this->db->plimit($limit + 1, $offset);
204 }
205
206 $result = $this->db->query($sql);
207 $i = 0;
208 if ($result) {
209 $num = $this->db->num_rows($result);
210 $min = min($num, ($limit <= 0 ? $num : $limit));
211 while ($i < $min) {
212 $obj = $this->db->fetch_object($result);
213 $tmp_object = new RecruitmentJobPosition($this->db);
214 if ($tmp_object->fetch($obj->rowid)) {
215 $obj_ret[] = $this->_filterObjectProperties($this->_cleanObjectDatas($tmp_object), $properties);
216 }
217 $i++;
218 }
219 } else {
220 throw new RestException(503, 'Error when retrieving jobposition list: '.$this->db->lasterror());
221 }
222
223 //if $pagination_data is true the response will contain element data with all values and element pagination with pagination data(total,page,limit)
224 if ($pagination_data) {
225 $totalsResult = $this->db->query($sqlTotals);
226 $total = $this->db->fetch_object($totalsResult)->total;
227
228 $tmp = $obj_ret;
229 $obj_ret = [];
230
231 $obj_ret['data'] = $tmp;
232 $obj_ret['pagination'] = [
233 'total' => (int) $total,
234 'page' => $page, //count starts from 0
235 'page_count' => ceil((int) $total / $limit),
236 'limit' => $limit
237 ];
238 }
239
240 return $obj_ret;
241 }
242
263 public function indexCandidature($sortfield = "t.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $sqlfilters = '', $properties = '', $pagination_data = false)
264 {
265 global $db, $conf;
266
267 $obj_ret = array();
268 $tmpobject = new RecruitmentCandidature($this->db);
269
270 if (!DolibarrApiAccess::$user->hasRight('recruitment', 'recruitmentjobposition', 'read')) {
271 throw new RestException(403);
272 }
273
274 $socid = DolibarrApiAccess::$user->socid ?: 0;
275
276 $restrictonsocid = 0; // Set to 1 if there is a field socid in table of object
277
278 // If the internal user must only see his customers, force searching by him
279 $search_sale = 0;
280 if ($restrictonsocid && !DolibarrApiAccess::$user->hasRight('societe', 'client', 'voir') && !$socid) {
281 $search_sale = DolibarrApiAccess::$user->id;
282 }
283
284 $sql = "SELECT t.rowid";
285 $sql .= " FROM ".MAIN_DB_PREFIX.$tmpobject->table_element." AS t";
286 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX.$tmpobject->table_element."_extrafields AS ef ON (ef.fk_object = t.rowid)"; // Modification VMR Global Solutions to include extrafields as search parameters in the API GET call, so we will be able to filter on extrafields
287 $sql .= " WHERE 1 = 1";
288 if ($tmpobject->ismultientitymanaged) {
289 $sql .= ' AND t.entity IN ('.getEntity($tmpobject->element).')';
290 }
291 if ($restrictonsocid && $socid) {
292 $sql .= " AND t.fk_soc = ".((int) $socid);
293 }
294 // Search on sale representative
295 if ($search_sale && $search_sale != '-1') {
296 if ($search_sale == -2) {
297 $sql .= " AND ".getSalesRepresentativeSqlFilter('t.fk_soc', 0, 1);
298 } elseif ($search_sale > 0) {
299 $sql .= " AND ".getSalesRepresentativeSqlFilter('t.fk_soc', (int) $search_sale);
300 }
301 }
302 if ($sqlfilters) {
303 $errormessage = '';
304 $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage);
305 if ($errormessage) {
306 throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage);
307 }
308 }
309
310 //this query will return total orders with the filters given
311 $sqlTotals = str_replace('SELECT t.rowid', 'SELECT count(t.rowid) as total', $sql);
312
313 $sql .= $this->db->order($sortfield, $sortorder);
314 if ($limit) {
315 if ($page < 0) {
316 $page = 0;
317 }
318 $offset = $limit * $page;
319
320 $sql .= $this->db->plimit($limit + 1, $offset);
321 }
322
323 $result = $this->db->query($sql);
324 $i = 0;
325 if ($result) {
326 $num = $this->db->num_rows($result);
327 $min = min($num, ($limit <= 0 ? $num : $limit));
328 while ($i < $min) {
329 $obj = $this->db->fetch_object($result);
330 $tmp_object = new RecruitmentCandidature($this->db);
331 if ($tmp_object->fetch($obj->rowid)) {
332 $obj_ret[] = $this->_filterObjectProperties($this->_cleanObjectDatas($tmp_object), $properties);
333 }
334 $i++;
335 }
336 } else {
337 throw new RestException(503, 'Error when retrieving candidature list: '.$this->db->lasterror());
338 }
339
340 //if $pagination_data is true the response will contain element data with all values and element pagination with pagination data(total,page,limit)
341 if ($pagination_data) {
342 $totalsResult = $this->db->query($sqlTotals);
343 $total = $this->db->fetch_object($totalsResult)->total;
344
345 $tmp = $obj_ret;
346 $obj_ret = [];
347
348 $obj_ret['data'] = $tmp;
349 $obj_ret['pagination'] = [
350 'total' => (int) $total,
351 'page' => $page, //count starts from 0
352 'page_count' => ceil((int) $total / $limit),
353 'limit' => $limit
354 ];
355 }
356
357 return $obj_ret;
358 }
359
372 public function postJobPosition($request_data = null)
373 {
374 if (!DolibarrApiAccess::$user->hasRight('recruitment', 'recruitmentjobposition', 'write')) {
375 throw new RestException(403);
376 }
377
378 // Check mandatory fields
379 $result = $this->_validate($request_data, $this->jobposition);
380
381 foreach ($request_data as $field => $value) {
382 if ($field === 'caller') {
383 // Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again with the caller
384 $this->jobposition->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
385 continue;
386 }
387
388 $this->jobposition->$field = $this->_checkValForAPI($field, $value, $this->jobposition);
389 }
390
391 // Clean data
392 // $this->jobposition->abc = sanitizeVal($this->jobposition->abc, 'alphanohtml');
393
394 if ($this->jobposition->create(DolibarrApiAccess::$user) < 0) {
395 throw new RestException(500, "Error creating jobposition", array_merge(array($this->jobposition->error), $this->jobposition->errors));
396 }
397 return $this->jobposition->id;
398 }
399
412 public function postCandidature($request_data = null)
413 {
414 if (!DolibarrApiAccess::$user->hasRight('recruitment', 'recruitmentjobposition', 'write')) {
415 throw new RestException(403);
416 }
417
418 // Check mandatory fields. Validate against the candidature model so the API
419 // rejects with the actual missing candidature fields (e.g. email) instead of
420 // the unrelated job position fields (see issue #38429).
421 $result = $this->_validate($request_data, $this->candidature);
422
423 foreach ($request_data as $field => $value) {
424 if ($field === 'caller') {
425 // Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again with the caller
426 $this->candidature->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
427 continue;
428 }
429
430 $this->candidature->$field = $this->_checkValForAPI($field, $value, $this->candidature);
431 }
432
433 // Clean data
434 // $this->jobposition->abc = sanitizeVal($this->jobposition->abc, 'alphanohtml');
435
436 if ($this->candidature->create(DolibarrApiAccess::$user) < 0) {
437 throw new RestException(500, "Error creating candidature", array_merge(array($this->candidature->error), $this->candidature->errors));
438 }
439 return $this->candidature->id;
440 }
441
455 public function putJobPosition($id, $request_data = null)
456 {
457 if (!DolibarrApiAccess::$user->hasRight('recruitment', 'recruitmentjobposition', 'write')) {
458 throw new RestException(403);
459 }
460
461 $result = $this->jobposition->fetch($id);
462 if (!$result) {
463 throw new RestException(404, 'jobposition not found');
464 }
465
466 if (!DolibarrApi::_checkAccessToResource('recruitment', $this->jobposition->id, 'recruitment_recruitmentjobposition')) {
467 throw new RestException(403, 'Access to instance id='.$this->jobposition->id.' of object not allowed for login '.DolibarrApiAccess::$user->login);
468 }
469
470 foreach ($request_data as $field => $value) {
471 if ($field == 'id') {
472 continue;
473 }
474 if ($field === 'caller') {
475 // Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again with the caller
476 $this->jobposition->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
477 continue;
478 }
479
480 $this->jobposition->$field = $this->_checkValForAPI($field, $value, $this->jobposition);
481 }
482
483 // Clean data
484 // $this->jobposition->abc = sanitizeVal($this->jobposition->abc, 'alphanohtml');
485
486 if ($this->jobposition->update(DolibarrApiAccess::$user, 0) > 0) {
487 return $this->getJobPosition($id);
488 } else {
489 throw new RestException(500, $this->jobposition->error);
490 }
491 }
492
506 public function putCandidature($id, $request_data = null)
507 {
508 if (!DolibarrApiAccess::$user->hasRight('recruitment', 'recruitmentjobposition', 'write')) {
509 throw new RestException(403);
510 }
511
512 $result = $this->candidature->fetch($id);
513 if (!$result) {
514 throw new RestException(404, 'candidature not found');
515 }
516
517 if (!DolibarrApi::_checkAccessToResource('recruitment', $this->candidature->id, 'recruitment_recruitmentcandidature')) {
518 throw new RestException(403, 'Access to instance id='.$this->candidature->id.' of object not allowed for login '.DolibarrApiAccess::$user->login);
519 }
520
521 foreach ($request_data as $field => $value) {
522 if ($field == 'id') {
523 continue;
524 }
525 if ($field === 'caller') {
526 // Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again with the caller
527 $this->candidature->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
528 continue;
529 }
530
531 $this->candidature->$field = $this->_checkValForAPI($field, $value, $this->candidature);
532 }
533
534 // Clean data
535 // $this->jobposition->abc = sanitizeVal($this->jobposition->abc, 'alphanohtml');
536
537 if ($this->candidature->update(DolibarrApiAccess::$user, 0) > 0) {
538 return $this->getCandidature($id);
539 } else {
540 throw new RestException(500, $this->candidature->error);
541 }
542 }
543
544
557 public function deleteJobPosition($id)
558 {
559 if (!DolibarrApiAccess::$user->hasRight('recruitment', 'recruitmentjobposition', 'delete')) {
560 throw new RestException(403);
561 }
562 $result = $this->jobposition->fetch($id);
563 if (!$result) {
564 throw new RestException(404, 'jobposition not found');
565 }
566
567 if (!DolibarrApi::_checkAccessToResource('recruitment', $this->jobposition->id, 'recruitment_recruitmentjobposition')) {
568 throw new RestException(403, 'Access to instance id='.$this->jobposition->id.' of object not allowed for login '.DolibarrApiAccess::$user->login);
569 }
570
571 if (!$this->jobposition->delete(DolibarrApiAccess::$user)) {
572 throw new RestException(500, 'Error when deleting jobposition : '.$this->jobposition->error);
573 }
574
575 return array(
576 'success' => array(
577 'code' => 200,
578 'message' => 'jobposition deleted'
579 )
580 );
581 }
582
595 public function deleteCandidature($id)
596 {
597 if (!DolibarrApiAccess::$user->hasRight('recruitment', 'recruitmentjobposition', 'delete')) {
598 throw new RestException(403);
599 }
600 $result = $this->candidature->fetch($id);
601 if (!$result) {
602 throw new RestException(404, 'candidature not found');
603 }
604
605 if (!DolibarrApi::_checkAccessToResource('recruitment', $this->candidature->id, 'recruitment_recruitmentcandidature')) {
606 throw new RestException(403, 'Access to instance id='.$this->candidature->id.' of object not allowed for login '.DolibarrApiAccess::$user->login);
607 }
608
609 if (!$this->candidature->delete(DolibarrApiAccess::$user)) {
610 throw new RestException(500, 'Error when deleting candidature : '.$this->candidature->error);
611 }
612
613 return array(
614 'success' => array(
615 'code' => 200,
616 'message' => 'candidature deleted'
617 )
618 );
619 }
620
621
622 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
632 protected function _cleanObjectDatas($object)
633 {
634 // phpcs:enable
635 $object = parent::_cleanObjectDatas($object);
636
637 unset($object->rowid);
638 unset($object->canvas);
639
640 /*unset($object->name);
641 unset($object->lastname);
642 unset($object->firstname);
643 unset($object->civility_id);
644 unset($object->statut);
645 unset($object->state);
646 unset($object->state_id);
647 unset($object->state_code);
648 unset($object->region);
649 unset($object->region_code);
650 unset($object->country);
651 unset($object->country_id);
652 unset($object->country_code);
653 unset($object->barcode_type);
654 unset($object->barcode_type_code);
655 unset($object->barcode_type_label);
656 unset($object->barcode_type_coder);
657 unset($object->total_ht);
658 unset($object->total_tva);
659 unset($object->total_localtax1);
660 unset($object->total_localtax2);
661 unset($object->total_ttc);
662 unset($object->fk_account);
663 unset($object->comments);
664 unset($object->note);
665 unset($object->mode_reglement_id);
666 unset($object->cond_reglement_id);
667 unset($object->cond_reglement);
668 unset($object->shipping_method_id);
669 unset($object->fk_incoterms);
670 unset($object->label_incoterms);
671 unset($object->location_incoterms);
672 */
673
674 // If object has lines, remove $db property
675 if (isset($object->lines) && is_array($object->lines) && count($object->lines) > 0) {
676 $nboflines = count($object->lines);
677 for ($i = 0; $i < $nboflines; $i++) {
678 $this->_cleanObjectDatas($object->lines[$i]);
679
680 unset($object->lines[$i]->lines);
681 unset($object->lines[$i]->note);
682 }
683 }
684
685 return $object;
686 }
687
697 private function _validate($data, $object)
698 {
699 if ($data === null) {
700 $data = array();
701 }
702 $result = array();
703 foreach ($object->fields as $field => $propfield) {
704 if (in_array($field, array('rowid', 'entity', 'date_creation', 'tms', 'fk_user_creat', 'ref')) || empty($propfield['notnull']) || $propfield['notnull'] != 1 || !empty($propfield['noteditable']) || isset($propfield['default'])) {
705 continue; // Not a mandatory field or auto-generated field or has default value
706 }
707 if (!isset($data[$field])) {
708 throw new RestException(400, "$field field missing");
709 }
710 $result[$field] = $data[$field];
711 }
712 return $result;
713 }
714}
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:47
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
Class for API REST v1.
Definition api.class.php:35
_filterObjectProperties($object, $properties)
Filter properties that will be returned on object.
_checkValForAPI($field, $value, $object)
Check and convert a string depending on its type/name.
static _checkAccessToResource($resource, $resource_id=0, $dbtablename='', $feature2='', $dbt_keyfield='fk_soc', $dbt_select='rowid', $parenttableforentity='')
Check access by user to a given resource.
Class for RecruitmentCandidature.
Class for RecruitmentJobPosition.
getJobPosition($id)
Get properties of a jobposition object.
indexJobPosition($sortfield="t.rowid", $sortorder='ASC', $limit=100, $page=0, $sqlfilters='', $properties='', $pagination_data=false)
List jobpositions.
deleteJobPosition($id)
Delete jobposition.
__construct()
Constructor.
putCandidature($id, $request_data=null)
Update candidature.
deleteCandidature($id)
Delete candidature.
postJobPosition($request_data=null)
Create jobposition object.
_cleanObjectDatas($object)
Clean sensible object datas @phpstan-template T.
putJobPosition($id, $request_data=null)
Update jobposition.
_validate($data, $object)
Validate fields before create or update object.
getCandidature($id)
Get properties of a candidature object.
indexCandidature($sortfield="t.rowid", $sortorder='ASC', $limit=100, $page=0, $sqlfilters='', $properties='', $pagination_data=false)
List candatures.
postCandidature($request_data=null)
Create candidature object.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
if(!function_exists( 'dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0, $forbiddenfields=array())
forgeSQLFromUniversalSearchCriteria
sanitizeVal($out='', $check='alphanohtml', $filter=null, $options=null)
Return a sanitized or empty value after checking value against a rule.