From 9fd5ddf807604f699da73a27dcf0bdc5b7725a56 Mon Sep 17 00:00:00 2001
From: Laurent Destailleur <eldy@destailleur.fr>
Date: Wed, 9 Nov 2016 22:54:51 +0100
Subject: [PATCH] NEW Add REST API for projects

---
 htdocs/api/class/api.class.php             |   6 +-
 htdocs/api/index.php                       |  12 +-
 htdocs/commande/class/api_orders.class.php |   2 +-
 htdocs/langs/en_US/admin.lang              |   2 +-
 htdocs/projet/class/api_projects.class.php | 568 +++++++++++++++++++++
 htdocs/projet/class/project.class.php      |  33 +-
 htdocs/projet/class/task.class.php         |  11 +-
 7 files changed, 615 insertions(+), 19 deletions(-)
 create mode 100644 htdocs/projet/class/api_projects.class.php

diff --git a/htdocs/api/class/api.class.php b/htdocs/api/class/api.class.php
index 45f2be3e4da..e3081d10400 100644
--- a/htdocs/api/class/api.class.php
+++ b/htdocs/api/class/api.class.php
@@ -23,8 +23,7 @@ use Luracast\Restler\Defaults;
 require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php';
 
 /**
- * Class for API
- *
+ * Class for API REST v1
  */
 class DolibarrApi
 {
@@ -55,6 +54,8 @@ class DolibarrApi
         $this->db = $db;
         $production_mode = ( empty($conf->global->API_PRODUCTION_MODE) ? false : true );
         $this->r = new Restler($production_mode);
+        
+        $this->r->setAPIVersion(1);
     }
 
     /**
@@ -97,6 +98,7 @@ class DolibarrApi
         unset($object->statuts);
         unset($object->statuts_short);
         unset($object->statuts_logo);
+        unset($object->statuts_long);
         
         // Remove the $oldcopy property because it is not supported by the JSON
         // encoder. The following error is generated when trying to serialize
diff --git a/htdocs/api/index.php b/htdocs/api/index.php
index 80cfb5485df..7fe3e6437ac 100644
--- a/htdocs/api/index.php
+++ b/htdocs/api/index.php
@@ -106,7 +106,10 @@ foreach ($modulesdir as $dir)
                 elseif ($module == 'facture') {
                     $moduledirforclass = 'compta/facture';
                 }
-
+                elseif ($module == 'project') {
+                    $moduledirforclass = 'projet';
+                }
+                
                 // Defined if module is enabled
                 $enabled=true;
                 if (empty($conf->$moduleforperm->enabled)) $enabled=false;
@@ -135,7 +138,7 @@ foreach ($modulesdir as $dir)
                                 require_once $dir_part.$file_searched;
                                 if (class_exists($classname))
                                 {
-                                    dol_syslog("Found deprecated API classname=".$classname." into ".$dir);
+                                    dol_syslog("Found deprecated API by index.php classname=".$classname." into ".$dir);
                                     $api->r->addAPIClass($classname, '/');
                                 }
                             }
@@ -145,7 +148,7 @@ foreach ($modulesdir as $dir)
                                 require_once $dir_part.$file_searched;
                                 if (class_exists($classname))
                                 {
-                                    dol_syslog("Found API classname=".$classname." into ".$dir);
+                                    dol_syslog("Found API by index.php classname=".$classname." into ".$dir);
                                     $listofapis[] = $classname;
                                 }
                             }
@@ -161,13 +164,14 @@ foreach ($modulesdir as $dir)
 // shows the classes in the order they are added and it's a mess if they are
 // not sorted.
 sort($listofapis);
+//var_dump($listofapis);
 foreach ($listofapis as $classname)
 {
     $api->r->addAPIClass($classname);
 }
 
 // TODO If not found, redirect to explorer
-
+//var_dump($api);
 
 // Call API (we suppose we found it)
 $api->r->handle();
diff --git a/htdocs/commande/class/api_orders.class.php b/htdocs/commande/class/api_orders.class.php
index f000e4ce201..c9931ac6f2a 100644
--- a/htdocs/commande/class/api_orders.class.php
+++ b/htdocs/commande/class/api_orders.class.php
@@ -371,7 +371,7 @@ class Orders extends DolibarrApi
       }
 			$request_data = (object) $request_data;
       $updateRes = $this->commande->deleteline(DolibarrApiAccess::$user,$lineid);
-      if ($updateRes == 1) {
+      if ($updateRes > 0) {
         return $this->get($id);
       }
       return false;
diff --git a/htdocs/langs/en_US/admin.lang b/htdocs/langs/en_US/admin.lang
index 27d86696b82..f57dcfdc69d 100644
--- a/htdocs/langs/en_US/admin.lang
+++ b/htdocs/langs/en_US/admin.lang
@@ -1626,7 +1626,7 @@ AddOtherPagesOrServices=Add other pages or services
 AddModels=Add document or numbering templates
 AddSubstitutions=Add keys substitutions
 DetectionNotPossible=Detection not possible
-UrlToGetKeyToUseAPIs=Url to get token to use API (once token has been received it is saved on database user table and will be checked on each future access) 
+UrlToGetKeyToUseAPIs=Url to get token to use API (once token has been received it is saved on database user table and must be provided on each API call) 
 ListOfAvailableAPIs=List of available APIs
 activateModuleDependNotSatisfied=Module "%s" depends on module "%s" that is missing, so module "%1$s" may not work correclty. Please install module "%2$s" or disable module "%1$s" if you want to be safe from any surprise
 CommandIsNotInsideAllowedCommands=The command you try to run is not inside list of allowed commands defined into parameter <strong>$dolibarr_main_restrict_os_commands</strong> into <strong>conf.php</strong> file.
diff --git a/htdocs/projet/class/api_projects.class.php b/htdocs/projet/class/api_projects.class.php
new file mode 100644
index 00000000000..74650407f87
--- /dev/null
+++ b/htdocs/projet/class/api_projects.class.php
@@ -0,0 +1,568 @@
+<?php
+/* Copyright (C) 2015   Jean-François Ferry     <jfefe@aternatik.fr>
+ * Copyright (C) 2016	Laurent Destailleur		<eldy@users.sourceforge.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+ use Luracast\Restler\RestException;
+
+ require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
+ 
+/**
+ * API class for projects
+ *
+ * @access protected
+ * @class  DolibarrApiAccess {@requires user,external}
+ */
+class Projects extends DolibarrApi
+{
+
+    /**
+     * @var array   $FIELDS     Mandatory fields, checked when create and update object
+     */
+    static $FIELDS = array(
+        'ref',
+        'title'
+    );
+
+    /**
+     * @var Project $project {@type Project}
+     */
+    public $project;
+
+    /**
+     * Constructor
+     */
+    function __construct()
+    {
+		global $db, $conf;
+		$this->db = $db;
+        $this->project = new Project($this->db);
+    }
+
+    /**
+     * Get properties of a project object
+     *
+     * Return an array with project informations
+     *
+     * @param       int         $id         ID of project
+     * @return 	array|mixed data without useless information
+	 *
+     * @throws 	RestException
+     */
+    function get($id)
+    {
+		if(! DolibarrApiAccess::$user->rights->projet->lire) {
+			throw new RestException(401);
+		}
+
+        $result = $this->project->fetch($id);
+        if( ! $result ) {
+            throw new RestException(404, 'Project not found');
+        }
+
+		if( ! DolibarrApi::_checkAccessToResource('project',$this->project->id)) {
+			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+		}
+
+        $this->project->fetchObjectLinked();
+		return $this->_cleanObjectDatas($this->project);
+    }
+
+    
+   
+    /**
+     * List projects
+     *
+     * Get a list of projects
+     *
+     * @param string	       $sortfield	        Sort field
+     * @param string	       $sortorder	        Sort order
+     * @param int		       $limit		        Limit for list
+     * @param int		       $page		        Page number
+     * @param string   	       $thirdparty_ids	    Thirdparty ids to filter projects of. {@example '1' or '1,2,3'} {@pattern /^[0-9,]*$/i}
+     * @param string           $sqlfilters          Other criteria to filter answers separated by a comma. Syntax example "(t.ref:like:'SO-%') and (t.date_creation:<:'20160101')"
+     * @return  array                               Array of project objects
+     */
+    function index($sortfield = "t.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $thirdparty_ids = '', $sqlfilters = '') {
+        global $db, $conf;
+
+        $obj_ret = array();
+        // case of external user, $thirdpartyid param is ignored and replaced by user's socid
+        $socids = DolibarrApiAccess::$user->societe_id ? DolibarrApiAccess::$user->societe_id : $thirdparty_ids;
+
+        // If the internal user must only see his customers, force searching by him
+        if (! DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) $search_sale = DolibarrApiAccess::$user->id;
+
+        $sql = "SELECT t.rowid";
+        if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) $sql .= ", sc.fk_soc, sc.fk_user"; // We need these fields in order to filter by sale (including the case where the user can only see his prospects)
+        $sql.= " FROM ".MAIN_DB_PREFIX."projet as t";
+
+        if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; // We need this table joined to the select in order to filter by sale
+
+        $sql.= ' WHERE t.entity IN ('.getEntity('project', 1).')';
+        if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) $sql.= " AND t.fk_soc = sc.fk_soc";
+        if ($socids) $sql.= " AND t.fk_soc IN (".$socids.")";
+        if ($search_sale > 0) $sql.= " AND t.rowid = sc.fk_soc";		// Join for the needed table to filter by sale
+        // Insert sale filter
+        if ($search_sale > 0)
+        {
+            $sql .= " AND sc.fk_user = ".$search_sale;
+        }
+        // Add sql filters
+        if ($sqlfilters) 
+        {
+            if (! DolibarrApi::_checkFilters($sqlfilters))
+            {
+                throw new RestException(503, 'Error when validating parameter sqlfilters '.$sqlfilters);
+            }
+	        $regexstring='\(([^:\'\(\)]+:[^:\'\(\)]+:[^:\(\)]+)\)';
+            $sql.=" AND (".preg_replace_callback('/'.$regexstring.'/', 'DolibarrApi::_forge_criteria_callback', $sqlfilters).")";
+        }
+        
+        $sql.= $db->order($sortfield, $sortorder);
+        if ($limit)	{
+            if ($page < 0)
+            {
+                $page = 0;
+            }
+            $offset = $limit * $page;
+
+            $sql.= $db->plimit($limit + 1, $offset);
+        }
+
+        dol_syslog("API Rest request");
+        $result = $db->query($sql);
+
+        if ($result)
+        {
+            $num = $db->num_rows($result);
+            while ($i < min($num, ($limit <= 0 ? $num : $limit)))
+            {
+                $obj = $db->fetch_object($result);
+                $project_static = new Project($db);
+                if($project_static->fetch($obj->rowid)) {
+                    $obj_ret[] = parent::_cleanObjectDatas($project_static);
+                }
+                $i++;
+            }
+        }
+        else {
+            throw new RestException(503, 'Error when retrieve project list');
+        }
+        if( ! count($obj_ret)) {
+            throw new RestException(404, 'No project found');
+        }
+		return $obj_ret;
+    }
+
+    /**
+     * Create project object
+     *
+     * @param   array   $request_data   Request data
+     * @return  int     ID of project
+     */
+    function post($request_data = NULL)
+    {
+      if(! DolibarrApiAccess::$user->rights->projet->creer) {
+			  throw new RestException(401, "Insuffisant rights");
+		  }
+        // Check mandatory fields
+        $result = $this->_validate($request_data);
+
+        foreach($request_data as $field => $value) {
+            $this->project->$field = $value;
+        }
+        /*if (isset($request_data["lines"])) {
+          $lines = array();
+          foreach ($request_data["lines"] as $line) {
+            array_push($lines, (object) $line);
+          }
+          $this->project->lines = $lines;
+        }*/
+        if ($this->project->create(DolibarrApiAccess::$user) <= 0) {
+            $errormsg = $this->project->error;
+            throw new RestException(500, $errormsg ? $errormsg : "Error while creating project");
+        }
+
+        return $this->project->id;
+    }
+
+    /**
+     * Get tasks of a project
+     *
+     * @param int   $id             Id of project
+     *
+     * @url	GET {id}/tasks
+     *
+     * @return int
+     */
+    function getLines($id) {
+      if(! DolibarrApiAccess::$user->rights->projet->lire) {
+		  	throw new RestException(401);
+		  }
+
+      $result = $this->project->fetch($id);
+      if( ! $result ) {
+         throw new RestException(404, 'Project not found');
+      }
+
+		  if( ! DolibarrApi::_checkAccessToResource('project',$this->project->id)) {
+			  throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+      }
+      $this->project->getLinesArray(DolibarrApiAccess::$user);
+      $result = array();
+      foreach ($this->project->lines as $line) {
+        array_push($result,$this->_cleanObjectDatas($line));
+      }
+      return $result;
+    }
+
+    
+    /**
+     * Get users and roles assigned to a project
+     *
+     * @param int   $id             Id of project
+     *
+     * @url	GET {id}/roles
+     *
+     * @return int
+     */
+    function getRoles($id) {
+        if(! DolibarrApiAccess::$user->rights->projet->lire) {
+            throw new RestException(401);
+        }
+    
+        $result = $this->project->fetch($id);
+        if( ! $result ) {
+            throw new RestException(404, 'Project not found');
+        }
+    
+        if( ! DolibarrApi::_checkAccessToResource('project',$this->project->id)) {
+            throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+        }
+        
+        require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
+        $taskstatic=new Task($this->db);
+        $this->project->roles = $taskstatic->getUserRolesForProjectsOrTasks(DolibarrApiAccess::$user, 0, $id, 0);
+        $result = array();
+        foreach ($this->project->roles as $line) {
+            array_push($result,$this->_cleanObjectDatas($line));
+        }
+        return $result;
+    }
+    
+    
+    /**
+     * Add a task to given project
+     *
+     * @param int   $id             Id of project to update
+     * @param array $request_data   Projectline data
+     *
+     * @url	POST {id}/tasks
+     *
+     * @return int
+     */
+    /*
+    function postLine($id, $request_data = NULL) {
+      if(! DolibarrApiAccess::$user->rights->projet->creer) {
+		  	throw new RestException(401);
+		  }
+
+      $result = $this->project->fetch($id);
+      if( ! $result ) {
+         throw new RestException(404, 'Project not found');
+      }
+
+		  if( ! DolibarrApi::_checkAccessToResource('project',$this->project->id)) {
+			  throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+      }
+			$request_data = (object) $request_data;
+      $updateRes = $this->project->addline(
+                        $request_data->desc,
+                        $request_data->subprice,
+                        $request_data->qty,
+                        $request_data->tva_tx,
+                        $request_data->localtax1_tx,
+                        $request_data->localtax2_tx,
+                        $request_data->fk_product,
+                        $request_data->remise_percent,
+                        $request_data->info_bits,
+                        $request_data->fk_remise_except,
+                        'HT',
+                        0,
+                        $request_data->date_start,
+                        $request_data->date_end,
+                        $request_data->product_type,
+                        $request_data->rang,
+                        $request_data->special_code,
+                        $fk_parent_line,
+                        $request_data->fk_fournprice,
+                        $request_data->pa_ht,
+                        $request_data->label,
+                        $request_data->array_options,
+                        $request_data->fk_unit,
+                        $this->element,
+                        $request_data->id
+      );
+
+      if ($updateRes > 0) {
+        return $this->get($id)->line->rowid;
+
+      }
+      return false;
+    }
+    */
+    
+    /**
+     * Update a task to given project
+     *
+     * @param int   $id             Id of project to update
+     * @param int   $lineid         Id of line to update
+     * @param array $request_data   Projectline data
+     *
+     * @url	PUT {id}/tasks/{lineid}
+     *
+     * @return object
+     */
+    /*
+    function putLine($id, $lineid, $request_data = NULL) {
+      if(! DolibarrApiAccess::$user->rights->projet->creer) {
+		  	throw new RestException(401);
+		  }
+
+      $result = $this->project->fetch($id);
+      if( ! $result ) {
+         throw new RestException(404, 'Project not found');
+      }
+
+		  if( ! DolibarrApi::_checkAccessToResource('project',$this->project->id)) {
+			  throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+      }
+			$request_data = (object) $request_data;
+      $updateRes = $this->project->updateline(
+                        $lineid,
+                        $request_data->desc,
+                        $request_data->subprice,
+                        $request_data->qty,
+                        $request_data->remise_percent,
+                        $request_data->tva_tx,
+                        $request_data->localtax1_tx,
+                        $request_data->localtax2_tx,
+                        'HT',
+                        $request_data->info_bits,
+                        $request_data->date_start,
+                        $request_data->date_end,
+                        $request_data->product_type,
+                        $request_data->fk_parent_line,
+                        0,
+                        $request_data->fk_fournprice,
+                        $request_data->pa_ht,
+                        $request_data->label,
+                        $request_data->special_code,
+                        $request_data->array_options,
+                        $request_data->fk_unit
+      );
+
+      if ($updateRes > 0) {
+        $result = $this->get($id);
+        unset($result->line);
+        return $this->_cleanObjectDatas($result);
+      }
+      return false;
+    }*/
+    
+
+    /**
+     * Delete a tasks of given project
+     *
+     *
+     * @param int   $id             Id of project to update
+     * @param int   $taskid         Id of task to delete
+     *
+     * @url	DELETE {id}/tasks/{taskid}
+     *
+     * @return int
+     */
+    function delLine($id, $taskid) {
+      if(! DolibarrApiAccess::$user->rights->projet->creer) {
+		  	throw new RestException(401);
+		  }
+
+      $result = $this->project->fetch($id);
+      if( ! ($result > 0) ) {
+         throw new RestException(404, 'Project not found');
+      }
+
+		  if( ! DolibarrApi::_checkAccessToResource('project',$this->project->id)) {
+			  throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+      }
+    
+      require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
+      $taskstatic=new Task($this->db);
+      $result = $taskstatic->fetch($taskid);
+      if( ! ($result > 0) ) {
+          throw new RestException(404, 'Task not found');
+      }
+      
+      $deleteRes = $taskstatic->delete(DolibarrApiAccess::$user);
+      
+      if( ! ($deleteRes > 0)) {
+          throw new RestException(500, 'Error when delete tasks : '.$taskstatic->error);
+      }
+      
+      return array(
+          'success' => array(
+              'code' => 200,
+              'message' => 'Task deleted'
+          )
+      );
+    }
+
+    
+    /**
+     * Update project general fields (won't touch lines of project)
+     *
+     * @param int   $id             Id of project to update
+     * @param array $request_data   Datas
+     *
+     * @return int
+     */
+    function put($id, $request_data = NULL) {
+      if(! DolibarrApiAccess::$user->rights->projet->creer) {
+		  	throw new RestException(401);
+		  }
+
+        $result = $this->project->fetch($id);
+        if( ! $result ) {
+            throw new RestException(404, 'Project not found');
+        }
+
+		if( ! DolibarrApi::_checkAccessToResource('project',$this->project->id)) {
+			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+		}
+        foreach($request_data as $field => $value) {
+            $this->project->$field = $value;
+        }
+
+        if($this->project->update(DolibarrApiAccess::$user, 0))
+            return $this->get($id);
+
+        return false;
+    }
+
+    /**
+     * Delete project
+     *
+     * @param   int     $id         Project ID
+     *
+     * @return  array
+     */
+    function delete($id)
+    {
+        if(! DolibarrApiAccess::$user->rights->projet->supprimer) {
+			throw new RestException(401);
+		}
+        $result = $this->project->fetch($id);
+        if( ! $result ) {
+            throw new RestException(404, 'Project not found');
+        }
+
+		if( ! DolibarrApi::_checkAccessToResource('project',$this->project->id)) {
+			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+		}
+
+        if( ! $this->project->delete(DolibarrApiAccess::$user)) {
+            throw new RestException(500, 'Error when delete project : '.$this->project->error);
+        }
+
+        return array(
+            'success' => array(
+                'code' => 200,
+                'message' => 'Project deleted'
+            )
+        );
+
+    }
+
+    /**
+     * Validate a project
+     *
+     * @param   int $id             Project ID
+     * @param   int $notrigger      1=Does not execute triggers, 0= execute triggers
+     *
+     * @url POST    {id}/validate
+     *
+     * @return  array
+     * FIXME An error 403 is returned if the request has an empty body.
+     * Error message: "Forbidden: Content type `text/plain` is not supported."
+     * Workaround: send this in the body
+     * {
+     *   "notrigger": 0
+     * }
+     */
+    function validate($id, $notrigger=0)
+    {
+        if(! DolibarrApiAccess::$user->rights->projet->creer) {
+			throw new RestException(401);
+		}
+        $result = $this->project->fetch($id);
+        if( ! $result ) {
+            throw new RestException(404, 'Project not found');
+        }
+
+		if( ! DolibarrApi::_checkAccessToResource('project',$this->project->id)) {
+			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+		}
+
+		$result = $this->project->setValid(DolibarrApiAccess::$user, $notrigger);
+		if ($result == 0) {
+		    throw new RestException(500, 'Error nothing done. May be object is already validated');
+		}
+		if ($result < 0) {
+		    throw new RestException(500, 'Error when validating Project: '.$this->project->error);
+		}
+
+        return array(
+            'success' => array(
+                'code' => 200,
+                'message' => 'Project validated'
+            )
+        );
+    }
+
+    /**
+     * Validate fields before create or update object
+     *
+     * @param   array           $data   Array with data to verify
+     * @return  array
+     * @throws  RestException
+     */
+    function _validate($data)
+    {
+        $object = array();
+        foreach (self::$FIELDS as $field) {
+            if (!isset($data[$field]))
+                throw new RestException(400, "$field field missing");
+            $object[$field] = $data[$field];
+
+        }
+        return $object;
+    }
+    
+    
+    // TODO
+    // getSummaryOfTimeSpent
+}
diff --git a/htdocs/projet/class/project.class.php b/htdocs/projet/class/project.class.php
index 4ac568182de..3efb1a4cfcc 100644
--- a/htdocs/projet/class/project.class.php
+++ b/htdocs/projet/class/project.class.php
@@ -695,10 +695,11 @@ class Project extends CommonObject
     /**
      * 		Validate a project
      *
-     * 		@param		User	$user		User that validate
-     * 		@return		int					<0 if KO, >0 if OK
+     * 		@param		User	$user		   User that validate
+     *      @param      int     $notrigger     1=Disable triggers
+     * 		@return		int					   <0 if KO, >0 if OK
      */
-    function setValid($user)
+    function setValid($user, $notrigger=0)
     {
         global $langs, $conf;
 
@@ -725,10 +726,13 @@ class Project extends CommonObject
             if ($resql)
             {
                 // Call trigger
-                $result=$this->call_trigger('PROJECT_VALIDATE',$user);
-                if ($result < 0) { $error++; }
-                // End call triggers
-
+                if (empty($notrigger))
+                {
+                    $result=$this->call_trigger('PROJECT_VALIDATE',$user);
+                    if ($result < 0) { $error++; }
+                    // End call triggers
+                }
+                
                 if (!$error)
                 {
                 	$this->statut=1;
@@ -1866,5 +1870,20 @@ class Project extends CommonObject
 		return 1;
 	}
 
+	
+	/**
+	 * 	Create an array of tasks of current project
+	 * 
+	 *  @param  User   $user       Object user we want project allowed to
+	 * 	@return int		           >0 if OK, <0 if KO
+	 */
+	function getLinesArray($user)
+	{
+	    require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
+	    $taskstatic = new Task($this->db);
+
+	    $this->lines = $taskstatic->getTasksArray(0, $user, $this->id, 0, 0);
+	}
+	
 }
 
diff --git a/htdocs/projet/class/task.class.php b/htdocs/projet/class/task.class.php
index ad2f5faecbe..2baccfb3382 100644
--- a/htdocs/projet/class/task.class.php
+++ b/htdocs/projet/class/task.class.php
@@ -176,7 +176,7 @@ class Task extends CommonObject
      *
      *  @param	int		$id			Id object
      *  @param	int		$ref		ref object
-     *  @return int 		        <0 if KO, >0 if OK
+     *  @return int 		        <0 if KO, 0 if not found, >0 if OK
      */
     function fetch($id,$ref='')
     {
@@ -214,7 +214,9 @@ class Task extends CommonObject
         $resql=$this->db->query($sql);
         if ($resql)
         {
-            if ($this->db->num_rows($resql))
+            $num_rows = $this->db->num_rows($resql);
+            
+            if ($num_rows)
             {
                 $obj = $this->db->fetch_object($resql);
 
@@ -241,7 +243,8 @@ class Task extends CommonObject
 
             $this->db->free($resql);
 
-            return 1;
+            if ($num_rows) return 1;
+            else return 0;
         }
         else
         {
@@ -754,7 +757,7 @@ class Task extends CommonObject
      * Return list of roles for a user for each projects or each tasks (or a particular project or a particular task).
      *
      * @param	User	$userp			      Return roles on project for this internal user. If set, usert and taskid must not be defined.
-     * @param	User	$usert			      Return roles on task for this internal user. If set userp must not be defined.
+     * @param	User	$usert			      Return roles on task for this internal user. If set userp must not be defined. -1 means no filter.
      * @param 	int		$projectid		      Project id list separated with , to filter on project
      * @param 	int		$taskid			      Task id to filter on a task
      * @param	string	$filteronprojstatus	  Filter on project status if userp is set. Not used if userp not defined.
-- 
GitLab