dolibarr 23.0.3
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');
24
25
26
40{
44 public $jobposition;
48 public $candidature;
49
50
56 public function __construct()
57 {
58 global $db;
59 $this->db = $db;
60 $this->jobposition = new RecruitmentJobPosition($this->db);
61 $this->candidature = new RecruitmentCandidature($this->db);
62 }
63
64
78 public function getJobPosition($id)
79 {
80 if (!DolibarrApiAccess::$user->hasRight('recruitment', 'recruitmentjobposition', 'read')) {
81 throw new RestException(403);
82 }
83
84 $result = $this->jobposition->fetch($id);
85 if (!$result) {
86 throw new RestException(404, 'JobPosition not found');
87 }
88
89 if (!DolibarrApi::_checkAccessToResource('recruitment', $this->jobposition->id, 'recruitment_recruitmentjobposition')) {
90 throw new RestException(403, 'Access to instance id='.$this->jobposition->id.' of object not allowed for login '.DolibarrApiAccess::$user->login);
91 }
92
93 return $this->_cleanObjectDatas($this->jobposition);
94 }
95
109 public function getCandidature($id)
110 {
111 if (!DolibarrApiAccess::$user->hasRight('recruitment', 'recruitmentjobposition', 'read')) {
112 throw new RestException(403);
113 }
114
115 $result = $this->candidature->fetch($id);
116 if (!$result) {
117 throw new RestException(404, 'Candidature not found');
118 }
119
120 if (!DolibarrApi::_checkAccessToResource('recruitment', $this->candidature->id, 'recruitment_recruitmentcandidature')) {
121 throw new RestException(403, 'Access to instance id='.$this->candidature->id.' of object not allowed for login '.DolibarrApiAccess::$user->login);
122 }
123
124 return $this->_cleanObjectDatas($this->candidature);
125 }
126
147 public function indexJobPosition($sortfield = "t.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $sqlfilters = '', $properties = '', $pagination_data = false)
148 {
149 $obj_ret = array();
150 $tmpobject = new RecruitmentJobPosition($this->db);
151
152 if (!DolibarrApiAccess::$user->hasRight('recruitment', 'recruitmentjobposition', 'read')) {
153 throw new RestException(403);
154 }
155
156 $socid = DolibarrApiAccess::$user->socid ?: 0;
157
158 $restrictonsocid = 0; // Set to 1 if there is a field socid in table of object
159
160 // If the internal user must only see his customers, force searching by him
161 $search_sale = 0;
162 if ($restrictonsocid && !DolibarrApiAccess::$user->hasRight('societe', 'client', 'voir') && !$socid) {
163 $search_sale = DolibarrApiAccess::$user->id;
164 }
165
166 $sql = "SELECT t.rowid";
167 $sql .= " FROM ".MAIN_DB_PREFIX.$tmpobject->table_element." AS t";
168 $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
169 $sql .= " WHERE 1 = 1";
170 if ($tmpobject->ismultientitymanaged) {
171 $sql .= ' AND t.entity IN ('.getEntity($tmpobject->element).')';
172 }
173 if ($restrictonsocid && $socid) {
174 $sql .= " AND t.fk_soc = ".((int) $socid);
175 }
176 // Search on sale representative
177 if ($search_sale && $search_sale != '-1') {
178 if ($search_sale == -2) {
179 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = t.fk_soc)";
180 } elseif ($search_sale > 0) {
181 $sql .= " AND EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = t.fk_soc AND sc.fk_user = ".((int) $search_sale).")";
182 }
183 }
184 if ($sqlfilters) {
185 $errormessage = '';
186 $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage);
187 if ($errormessage) {
188 throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage);
189 }
190 }
191
192 //this query will return total orders with the filters given
193 $sqlTotals = str_replace('SELECT t.rowid', 'SELECT count(t.rowid) as total', $sql);
194
195 $sql .= $this->db->order($sortfield, $sortorder);
196 if ($limit) {
197 if ($page < 0) {
198 $page = 0;
199 }
200 $offset = $limit * $page;
201
202 $sql .= $this->db->plimit($limit + 1, $offset);
203 }
204
205 $result = $this->db->query($sql);
206 $i = 0;
207 if ($result) {
208 $num = $this->db->num_rows($result);
209 $min = min($num, ($limit <= 0 ? $num : $limit));
210 while ($i < $min) {
211 $obj = $this->db->fetch_object($result);
212 $tmp_object = new RecruitmentJobPosition($this->db);
213 if ($tmp_object->fetch($obj->rowid)) {
214 $obj_ret[] = $this->_filterObjectProperties($this->_cleanObjectDatas($tmp_object), $properties);
215 }
216 $i++;
217 }
218 } else {
219 throw new RestException(503, 'Error when retrieving jobposition list: '.$this->db->lasterror());
220 }
221
222 //if $pagination_data is true the response will contain element data with all values and element pagination with pagination data(total,page,limit)
223 if ($pagination_data) {
224 $totalsResult = $this->db->query($sqlTotals);
225 $total = $this->db->fetch_object($totalsResult)->total;
226
227 $tmp = $obj_ret;
228 $obj_ret = [];
229
230 $obj_ret['data'] = $tmp;
231 $obj_ret['pagination'] = [
232 'total' => (int) $total,
233 'page' => $page, //count starts from 0
234 'page_count' => ceil((int) $total / $limit),
235 'limit' => $limit
236 ];
237 }
238
239 return $obj_ret;
240 }
241
262 public function indexCandidature($sortfield = "t.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $sqlfilters = '', $properties = '', $pagination_data = false)
263 {
264 global $db, $conf;
265
266 $obj_ret = array();
267 $tmpobject = new RecruitmentCandidature($this->db);
268
269 if (!DolibarrApiAccess::$user->hasRight('recruitment', 'recruitmentjobposition', 'read')) {
270 throw new RestException(403);
271 }
272
273 $socid = DolibarrApiAccess::$user->socid ?: 0;
274
275 $restrictonsocid = 0; // Set to 1 if there is a field socid in table of object
276
277 // If the internal user must only see his customers, force searching by him
278 $search_sale = 0;
279 if ($restrictonsocid && !DolibarrApiAccess::$user->hasRight('societe', 'client', 'voir') && !$socid) {
280 $search_sale = DolibarrApiAccess::$user->id;
281 }
282
283 $sql = "SELECT t.rowid";
284 $sql .= " FROM ".MAIN_DB_PREFIX.$tmpobject->table_element." AS t";
285 $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
286 $sql .= " WHERE 1 = 1";
287 if ($tmpobject->ismultientitymanaged) {
288 $sql .= ' AND t.entity IN ('.getEntity($tmpobject->element).')';
289 }
290 if ($restrictonsocid && $socid) {
291 $sql .= " AND t.fk_soc = ".((int) $socid);
292 }
293 // Search on sale representative
294 if ($search_sale && $search_sale != '-1') {
295 if ($search_sale == -2) {
296 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = t.fk_soc)";
297 } elseif ($search_sale > 0) {
298 $sql .= " AND EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = t.fk_soc AND sc.fk_user = ".((int) $search_sale).")";
299 }
300 }
301 if ($sqlfilters) {
302 $errormessage = '';
303 $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage);
304 if ($errormessage) {
305 throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage);
306 }
307 }
308
309 //this query will return total orders with the filters given
310 $sqlTotals = str_replace('SELECT t.rowid', 'SELECT count(t.rowid) as total', $sql);
311
312 $sql .= $this->db->order($sortfield, $sortorder);
313 if ($limit) {
314 if ($page < 0) {
315 $page = 0;
316 }
317 $offset = $limit * $page;
318
319 $sql .= $this->db->plimit($limit + 1, $offset);
320 }
321
322 $result = $this->db->query($sql);
323 $i = 0;
324 if ($result) {
325 $num = $this->db->num_rows($result);
326 $min = min($num, ($limit <= 0 ? $num : $limit));
327 while ($i < $min) {
328 $obj = $this->db->fetch_object($result);
329 $tmp_object = new RecruitmentCandidature($this->db);
330 if ($tmp_object->fetch($obj->rowid)) {
331 $obj_ret[] = $this->_filterObjectProperties($this->_cleanObjectDatas($tmp_object), $properties);
332 }
333 $i++;
334 }
335 } else {
336 throw new RestException(503, 'Error when retrieving candidature list: '.$this->db->lasterror());
337 }
338
339 //if $pagination_data is true the response will contain element data with all values and element pagination with pagination data(total,page,limit)
340 if ($pagination_data) {
341 $totalsResult = $this->db->query($sqlTotals);
342 $total = $this->db->fetch_object($totalsResult)->total;
343
344 $tmp = $obj_ret;
345 $obj_ret = [];
346
347 $obj_ret['data'] = $tmp;
348 $obj_ret['pagination'] = [
349 'total' => (int) $total,
350 'page' => $page, //count starts from 0
351 'page_count' => ceil((int) $total / $limit),
352 'limit' => $limit
353 ];
354 }
355
356 return $obj_ret;
357 }
358
371 public function postJobPosition($request_data = null)
372 {
373 if (!DolibarrApiAccess::$user->hasRight('recruitment', 'recruitmentjobposition', 'write')) {
374 throw new RestException(403);
375 }
376
377 // Check mandatory fields
378 $result = $this->_validate($request_data, $this->jobposition);
379
380 foreach ($request_data as $field => $value) {
381 if ($field === 'caller') {
382 // 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
383 $this->jobposition->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
384 continue;
385 }
386
387 $this->jobposition->$field = $this->_checkValForAPI($field, $value, $this->jobposition);
388 }
389
390 // Clean data
391 // $this->jobposition->abc = sanitizeVal($this->jobposition->abc, 'alphanohtml');
392
393 if ($this->jobposition->create(DolibarrApiAccess::$user) < 0) {
394 throw new RestException(500, "Error creating jobposition", array_merge(array($this->jobposition->error), $this->jobposition->errors));
395 }
396 return $this->jobposition->id;
397 }
398
411 public function postCandidature($request_data = null)
412 {
413 if (!DolibarrApiAccess::$user->hasRight('recruitment', 'recruitmentjobposition', 'write')) {
414 throw new RestException(403);
415 }
416
417 // Check mandatory fields. Validate against the candidature model so the API
418 // rejects with the actual missing candidature fields (e.g. email) instead of
419 // the unrelated job position fields (see issue #38429).
420 $result = $this->_validate($request_data, $this->candidature);
421
422 foreach ($request_data as $field => $value) {
423 if ($field === 'caller') {
424 // 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
425 $this->candidature->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
426 continue;
427 }
428
429 $this->candidature->$field = $this->_checkValForAPI($field, $value, $this->candidature);
430 }
431
432 // Clean data
433 // $this->jobposition->abc = sanitizeVal($this->jobposition->abc, 'alphanohtml');
434
435 if ($this->candidature->create(DolibarrApiAccess::$user) < 0) {
436 throw new RestException(500, "Error creating candidature", array_merge(array($this->candidature->error), $this->candidature->errors));
437 }
438 return $this->candidature->id;
439 }
440
454 public function putJobPosition($id, $request_data = null)
455 {
456 if (!DolibarrApiAccess::$user->hasRight('recruitment', 'recruitmentjobposition', 'write')) {
457 throw new RestException(403);
458 }
459
460 $result = $this->jobposition->fetch($id);
461 if (!$result) {
462 throw new RestException(404, 'jobposition not found');
463 }
464
465 if (!DolibarrApi::_checkAccessToResource('recruitment', $this->jobposition->id, 'recruitment_recruitmentjobposition')) {
466 throw new RestException(403, 'Access to instance id='.$this->jobposition->id.' of object not allowed for login '.DolibarrApiAccess::$user->login);
467 }
468
469 foreach ($request_data as $field => $value) {
470 if ($field == 'id') {
471 continue;
472 }
473 if ($field === 'caller') {
474 // 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
475 $this->jobposition->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
476 continue;
477 }
478
479 $this->jobposition->$field = $this->_checkValForAPI($field, $value, $this->jobposition);
480 }
481
482 // Clean data
483 // $this->jobposition->abc = sanitizeVal($this->jobposition->abc, 'alphanohtml');
484
485 if ($this->jobposition->update(DolibarrApiAccess::$user, 0) > 0) {
486 return $this->getJobPosition($id);
487 } else {
488 throw new RestException(500, $this->jobposition->error);
489 }
490 }
491
505 public function putCandidature($id, $request_data = null)
506 {
507 if (!DolibarrApiAccess::$user->hasRight('recruitment', 'recruitmentjobposition', 'write')) {
508 throw new RestException(403);
509 }
510
511 $result = $this->candidature->fetch($id);
512 if (!$result) {
513 throw new RestException(404, 'candidature not found');
514 }
515
516 if (!DolibarrApi::_checkAccessToResource('recruitment', $this->candidature->id, 'recruitment_recruitmentcandidature')) {
517 throw new RestException(403, 'Access to instance id='.$this->candidature->id.' of object not allowed for login '.DolibarrApiAccess::$user->login);
518 }
519
520 foreach ($request_data as $field => $value) {
521 if ($field == 'id') {
522 continue;
523 }
524 if ($field === 'caller') {
525 // 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
526 $this->candidature->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
527 continue;
528 }
529
530 $this->candidature->$field = $this->_checkValForAPI($field, $value, $this->candidature);
531 }
532
533 // Clean data
534 // $this->jobposition->abc = sanitizeVal($this->jobposition->abc, 'alphanohtml');
535
536 if ($this->candidature->update(DolibarrApiAccess::$user, 0) > 0) {
537 return $this->getCandidature($id);
538 } else {
539 throw new RestException(500, $this->candidature->error);
540 }
541 }
542
543
556 public function deleteJobPosition($id)
557 {
558 if (!DolibarrApiAccess::$user->hasRight('recruitment', 'recruitmentjobposition', 'delete')) {
559 throw new RestException(403);
560 }
561 $result = $this->jobposition->fetch($id);
562 if (!$result) {
563 throw new RestException(404, 'jobposition not found');
564 }
565
566 if (!DolibarrApi::_checkAccessToResource('recruitment', $this->jobposition->id, 'recruitment_recruitmentjobposition')) {
567 throw new RestException(403, 'Access to instance id='.$this->jobposition->id.' of object not allowed for login '.DolibarrApiAccess::$user->login);
568 }
569
570 if (!$this->jobposition->delete(DolibarrApiAccess::$user)) {
571 throw new RestException(500, 'Error when deleting jobposition : '.$this->jobposition->error);
572 }
573
574 return array(
575 'success' => array(
576 'code' => 200,
577 'message' => 'jobposition deleted'
578 )
579 );
580 }
581
594 public function deleteCandidature($id)
595 {
596 if (!DolibarrApiAccess::$user->hasRight('recruitment', 'recruitmentjobposition', 'delete')) {
597 throw new RestException(403);
598 }
599 $result = $this->candidature->fetch($id);
600 if (!$result) {
601 throw new RestException(404, 'candidature not found');
602 }
603
604 if (!DolibarrApi::_checkAccessToResource('recruitment', $this->candidature->id, 'recruitment_recruitmentcandidature')) {
605 throw new RestException(403, 'Access to instance id='.$this->candidature->id.' of object not allowed for login '.DolibarrApiAccess::$user->login);
606 }
607
608 if (!$this->candidature->delete(DolibarrApiAccess::$user)) {
609 throw new RestException(500, 'Error when deleting candidature : '.$this->candidature->error);
610 }
611
612 return array(
613 'success' => array(
614 'code' => 200,
615 'message' => 'candidature deleted'
616 )
617 );
618 }
619
620
621 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
631 protected function _cleanObjectDatas($object)
632 {
633 // phpcs:enable
634 $object = parent::_cleanObjectDatas($object);
635
636 unset($object->rowid);
637 unset($object->canvas);
638
639 /*unset($object->name);
640 unset($object->lastname);
641 unset($object->firstname);
642 unset($object->civility_id);
643 unset($object->statut);
644 unset($object->state);
645 unset($object->state_id);
646 unset($object->state_code);
647 unset($object->region);
648 unset($object->region_code);
649 unset($object->country);
650 unset($object->country_id);
651 unset($object->country_code);
652 unset($object->barcode_type);
653 unset($object->barcode_type_code);
654 unset($object->barcode_type_label);
655 unset($object->barcode_type_coder);
656 unset($object->total_ht);
657 unset($object->total_tva);
658 unset($object->total_localtax1);
659 unset($object->total_localtax2);
660 unset($object->total_ttc);
661 unset($object->fk_account);
662 unset($object->comments);
663 unset($object->note);
664 unset($object->mode_reglement_id);
665 unset($object->cond_reglement_id);
666 unset($object->cond_reglement);
667 unset($object->shipping_method_id);
668 unset($object->fk_incoterms);
669 unset($object->label_incoterms);
670 unset($object->location_incoterms);
671 */
672
673 // If object has lines, remove $db property
674 if (isset($object->lines) && is_array($object->lines) && count($object->lines) > 0) {
675 $nboflines = count($object->lines);
676 for ($i = 0; $i < $nboflines; $i++) {
677 $this->_cleanObjectDatas($object->lines[$i]);
678
679 unset($object->lines[$i]->lines);
680 unset($object->lines[$i]->note);
681 }
682 }
683
684 return $object;
685 }
686
696 private function _validate($data, $object)
697 {
698 if ($data === null) {
699 $data = array();
700 }
701 $result = array();
702 foreach ($object->fields as $field => $propfield) {
703 if (in_array($field, array('rowid', 'entity', 'date_creation', 'tms', 'fk_user_creat')) || empty($propfield['notnull']) || $propfield['notnull'] != 1) {
704 continue; // Not a mandatory field
705 }
706 if (!isset($data[$field])) {
707 throw new RestException(400, "$field field missing");
708 }
709 $result[$field] = $data[$field];
710 }
711 return $result;
712 }
713}
$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:33
_filterObjectProperties($object, $properties)
Filter properties that will be returned on object.
static _checkAccessToResource($resource, $resource_id=0, $dbtablename='', $feature2='', $dbt_keyfield='fk_soc', $dbt_select='rowid')
Check access by user to a given resource.
_checkValForAPI($field, $value, $object)
Check and convert a string depending on its type/name.
Definition api.class.php:98
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.
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0)
forgeSQLFromUniversalSearchCriteria
if(!function_exists( 'dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
sanitizeVal($out='', $check='alphanohtml', $filter=null, $options=null)
Return a sanitized or empty value after checking value against a rule.