From 80cbc2838585997c50731d647059e8a78be3a10b Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sat, 20 May 2017 15:52:36 +0200 Subject: [PATCH] NEW Add REST API to push a file. --- htdocs/accountancy/admin/categories_list.php | 2 +- htdocs/accountancy/admin/journals_list.php | 2 +- htdocs/admin/dict.php | 4 +- htdocs/admin/mails_templates.php | 2 +- htdocs/api/admin/explorer.php | 2 + ....php => api_dictionarycountries.class.php} | 5 +- ...lass.php => api_dictionarytowns.class.php} | 5 +- htdocs/api/class/api_documents.class.php | 127 +++++++++++++----- htdocs/api/class/api_login.class.php | 2 +- htdocs/core/class/html.form.class.php | 2 +- htdocs/core/lib/functions.lib.php | 4 +- htdocs/core/modules/modResource.class.php | 2 +- .../install/mysql/migration/4.0.0-5.0.0.sql | 2 +- .../mysql/tables/llx_user_employment.sql | 2 +- htdocs/variants/generator.php | 14 +- test/phpunit/RestAPIDocumentTest.php | 21 ++- 16 files changed, 126 insertions(+), 72 deletions(-) rename htdocs/api/class/{api_dictionnarycountries.class.php => api_dictionarycountries.class.php} (97%) rename htdocs/api/class/{api_dictionnarytowns.class.php => api_dictionarytowns.class.php} (95%) diff --git a/htdocs/accountancy/admin/categories_list.php b/htdocs/accountancy/admin/categories_list.php index 86be6aa52ab..c0fa895ae4c 100644 --- a/htdocs/accountancy/admin/categories_list.php +++ b/htdocs/accountancy/admin/categories_list.php @@ -929,7 +929,7 @@ if ($id) { print ''; if ($user->admin) print ''.img_delete().''; - //else print ''.img_delete().''; // Some dictionnary can be edited by other profile than admin + //else print ''.img_delete().''; // Some dictionary can be edited by other profile than admin print ''; } else print ' '; diff --git a/htdocs/accountancy/admin/journals_list.php b/htdocs/accountancy/admin/journals_list.php index facb507c530..1421d71be97 100644 --- a/htdocs/accountancy/admin/journals_list.php +++ b/htdocs/accountancy/admin/journals_list.php @@ -676,7 +676,7 @@ if ($id) { print ''; if ($user->admin) print ''.img_delete().''; - //else print ''.img_delete().''; // Some dictionnary can be edited by other profile than admin + //else print ''.img_delete().''; // Some dictionary can be edited by other profile than admin print ''; } else print ' '; diff --git a/htdocs/admin/dict.php b/htdocs/admin/dict.php index 8319ccd0450..28db9ef8b49 100644 --- a/htdocs/admin/dict.php +++ b/htdocs/admin/dict.php @@ -1100,7 +1100,7 @@ if ($id) $reshook=$hookmanager->executeHooks('createDictionaryFieldlist',$parameters, $obj, $tmpaction); // Note that $action and $object may have been modified by some hooks $error=$hookmanager->error; $errors=$hookmanager->errors; - if ($id == 3) unset($fieldlist[2]); // Remove field ??? if dictionnary Regions + if ($id == 3) unset($fieldlist[2]); // Remove field ??? if dictionary Regions if (empty($reshook)) { @@ -1555,7 +1555,7 @@ if ($id) { print ''; if ($user->admin) print ''.img_delete().''; - //else print ''.img_delete().''; // Some dictionnary can be edited by other profile than admin + //else print ''.img_delete().''; // Some dictionary can be edited by other profile than admin print ''; } else print ' '; diff --git a/htdocs/admin/mails_templates.php b/htdocs/admin/mails_templates.php index a403cf1f493..b9e5df738ad 100644 --- a/htdocs/admin/mails_templates.php +++ b/htdocs/admin/mails_templates.php @@ -767,7 +767,7 @@ if ($resql) { print ''; if ($user->admin) print ''.img_delete().''; - //else print ''.img_delete().''; // Some dictionnary can be edited by other profile than admin + //else print ''.img_delete().''; // Some dictionary can be edited by other profile than admin print ''; } else print ' '; diff --git a/htdocs/api/admin/explorer.php b/htdocs/api/admin/explorer.php index c83d759dcd6..b09acb6772a 100644 --- a/htdocs/api/admin/explorer.php +++ b/htdocs/api/admin/explorer.php @@ -14,6 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . + * + * @deprecated Old explorer. Not using Swagger. See instead explorer in htdocs/api/index.php. */ /** diff --git a/htdocs/api/class/api_dictionnarycountries.class.php b/htdocs/api/class/api_dictionarycountries.class.php similarity index 97% rename from htdocs/api/class/api_dictionnarycountries.class.php rename to htdocs/api/class/api_dictionarycountries.class.php index ddb3e2474a9..070be509a8a 100644 --- a/htdocs/api/class/api_dictionnarycountries.class.php +++ b/htdocs/api/class/api_dictionarycountries.class.php @@ -27,7 +27,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/ccountry.class.php'; * @access protected * @class DolibarrApiAccess {@requires user,external} */ -class DictionnaryCountries extends DolibarrApi +class DictionaryCountries extends DolibarrApi { private $translations = null; @@ -93,7 +93,8 @@ class DictionnaryCountries extends DolibarrApi if ($result) { $num = $this->db->num_rows($result); - for ($i = 0; $i < min($num, ($limit <= 0 ? $num : $limit)); $i++) { + $min = min($num, ($limit <= 0 ? $num : $limit)); + for ($i = 0; $i < $min; $i++) { $obj = $this->db->fetch_object($result); $country = new Ccountry($this->db); if ($country->fetch($obj->rowid) > 0) { diff --git a/htdocs/api/class/api_dictionnarytowns.class.php b/htdocs/api/class/api_dictionarytowns.class.php similarity index 95% rename from htdocs/api/class/api_dictionnarytowns.class.php rename to htdocs/api/class/api_dictionarytowns.class.php index da58c9109eb..0ebcfbe0b17 100644 --- a/htdocs/api/class/api_dictionnarytowns.class.php +++ b/htdocs/api/class/api_dictionarytowns.class.php @@ -27,7 +27,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/ccountry.class.php'; * @access protected * @class DolibarrApiAccess {@requires user,external} */ -class DictionnaryTowns extends DolibarrApi +class DictionaryTowns extends DolibarrApi { /** * Constructor @@ -88,7 +88,8 @@ class DictionnaryTowns extends DolibarrApi if ($result) { $num = $this->db->num_rows($result); - for ($i = 0; $i < min($num, ($limit <= 0 ? $num : $limit)); $i++) { + $min = min($num, ($limit <= 0 ? $num : $limit)); + for ($i = 0; $i < $min; $i++) { $list[] = $this->db->fetch_object($result); } } else { diff --git a/htdocs/api/class/api_documents.class.php b/htdocs/api/class/api_documents.class.php index ca10b2befce..6650fc45401 100644 --- a/htdocs/api/class/api_documents.class.php +++ b/htdocs/api/class/api_documents.class.php @@ -58,23 +58,33 @@ class Documents extends DolibarrApi * * @return array * @throws RestException - * */ + /* public function get($module_part, $filename) { - } + }*/ /** - * Receive file + * Push a file. + * Test sample: { "filename": "mynewfile.txt", "modulepart": "facture", "ref": "FA1701-001", "subdir": "", "filecontent": "content text", "fileencoding": "" } * - * @param array $request_data Request datas - * - * @return bool State of copy + * @param string $filename Name of file to create ('FA1705-0123') + * @param string $modulepart Module part ('facture', ...) + * @param string $ref Reference of object (This will define subdir automatically and store submited file into it) + * @param string $subdir Subdirectory (Only if refname not provided) + * @param string $filecontent File content (string with file content. An empty file will be created if this parameter is not provided) + * @param string $fileencoding File encoding (''=no encoding, 'base64'=Base 64) + * @return bool State of copy * @throws RestException */ - public function post($request_data) { - global $conf; + public function post($filename, $modulepart, $ref='', $subdir='', $filecontent='', $fileencoding='') { + global $db, $conf; + + /*var_dump($modulepart); + var_dump($filename); + var_dump($filecontent); + exit;*/ require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php'; @@ -82,46 +92,89 @@ class Documents extends DolibarrApi throw new RestException(401); } - // Suppression de la chaine de caractere ../ dans $original_file - $original_file = str_replace("../","/", $request_data['name']); - $refname = str_replace("../","/", $request_data['refname']); + $newfilecontent = ''; + if (empty($fileencoding)) $newfilecontent = $filecontent; + if ($fileencoding == 'base64') $newfilecontent = base64_decode($filecontent); - // find the subdirectory name as the reference - if (empty($request_data['refname'])) $refname=basename(dirname($original_file)."/"); + $original_file = dol_sanitizeFileName($filename); + // Define $uploadir + $object = null; + $entity = $user->entity; + if ($ref) + { + if ($modulepart == 'facture' || $modulepart == 'invoice') + { + $modulepart='facture'; + $object=new Facture($db); + $result = $object->fetch('', $ref); + if (! ($result > 0)) + { + throw new RestException(500, 'The object '.$modulepart." with ref '".$ref."' was not found."); + } + if (! empty($entity)) + { + $tmpreldir = get_exdir(0, 0, 0, 0, $object, $modulepart); + $upload_dir = $conf->{$modulepart}->multidir_output[$entity].'/'.$tmpreldir.$object->ref; + } + else + { + $tmpreldir = get_exdir(0, 0, 0, 0, $object, $modulepart); + $upload_dir = $conf->{$modulepart}->dir_output.'/'.$tmpreldir.$object->ref; + } + } + + if (empty($upload_dir) || $upload_dir == '/') + { + throw new RestException(500, 'This value of modulepart does not support yet usage of refname. Check modulepart parameter or try to use subdir parameter instead of ref.'); + } + } + else + { + if ($modulepart == 'invoice') $modulepart ='facture'; + if (empty($conf->{$modulepart}->dir_output)) + { + throw new RestException(500, 'This value of modulepart is not supported with refname not defined.'); + } + $upload_dir = $conf->{$modulepart}->multidir_output[$entity]; + + if (empty($upload_dir) || $upload_dir == '/') + { + throw new RestException(500, 'This value of modulepart is not yet supported.'); + } + } + $upload_dir = dol_sanitizePathName($upload_dir); + // Security: - // On interdit les remontees de repertoire ainsi que les pipe dans - // les noms de fichiers. - if (preg_match('/\.\./',$original_file) || preg_match('/[<>|]/',$original_file)) - { - throw new RestException(401,'Refused to deliver file '.$original_file); - } - if (preg_match('/\.\./',$refname) || preg_match('/[<>|]/',$refname)) - { - throw new RestException(401,'Refused to deliver file '.$refname); - } - - $modulepart = $request_data['modulepart']; + // TODO Use dol_check_secure_access_document // Check mandatory fields - $result = $this->_validate_file($request_data); + //$result = $this->_validate_file($request_data); - $upload_dir = DOL_DATA_ROOT . '/' .$modulepart.'/'.dol_sanitizeFileName($refname); - $destfile = $upload_dir . $original_file; + $destfile = $upload_dir . '/' . $original_file; - if (!is_dir($upload_dir)) { + if (!dol_is_dir($upload_dir)) { throw new RestException(401,'Directory not exists : '.$upload_dir); } - $file = $_FILES['file']; - $srcfile = $file['tmp_name']; - $res = dol_move($srcfile, $destfile, 0, 1); - - if (!$res) { - throw new RestException(500); + if (dol_is_file($destfile)) + { + throw new RestException(500, "File with name '".$original_file."' already exists."); } - - return $res; + + $fhandle = fopen($destfile, 'w'); + if ($fhandle) + { + $nbofbyteswrote = fwrite($fhandle, $newfilecontent); + fclose($fhandle); + @chmod($destfile, octdec($conf->global->MAIN_UMASK)); + } + else + { + throw new RestException(500, 'Failed to open file for write'); + } + + return true; } /** diff --git a/htdocs/api/class/api_login.class.php b/htdocs/api/class/api_login.class.php index 76a9befebc9..9b965e24c33 100644 --- a/htdocs/api/class/api_login.class.php +++ b/htdocs/api/class/api_login.class.php @@ -36,7 +36,7 @@ class Login * * Request the API token for a couple username / password. * Using method POST is recommanded for security reasons (method GET is often logged by default by web servers with parameters so with login and pass into server log file). - * Both method are provided for developer conveniance. Best is to not use at all the login API method and enter directly the "api_key" into field at the top right of page (Note: "api_key" can be found/set on the user page). + * Both methods are provided for developer conveniance. Best is to not use at all the login API method and enter directly the "api_key" into field at the top right of page (Note: "api_key" can be found/set on the user page). * * @param string $login User login * @param string $password User password diff --git a/htdocs/core/class/html.form.class.php b/htdocs/core/class/html.form.class.php index 26001e8e1f1..f58e96087b8 100644 --- a/htdocs/core/class/html.form.class.php +++ b/htdocs/core/class/html.form.class.php @@ -2502,7 +2502,7 @@ class Form } if ($objp->supplier_reputation) { - //TODO dictionnary + //TODO dictionary $reputations=array(''=>$langs->trans('Standard'),'FAVORITE'=>$langs->trans('Favorite'),'NOTTHGOOD'=>$langs->trans('NotTheGoodQualitySupplier'), 'DONOTORDER'=>$langs->trans('DoNotOrderThisProductToThisSupplier')); $opt .= " - ".$reputations[$objp->supplier_reputation]; diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 09856131da7..2d3b1420e81 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -4555,8 +4555,8 @@ function yn($yesno, $case=1, $color=0) /** * Return a path to have a directory according to object. - * New usage: $conf->module->multidir_output[$object->entity].'/'.get_exdir(0, 0, 0, 1, $object, 'modulepart') - * or: $conf->module->dir_output.'/'.get_exdir(0, 0, 0, 1, $object, 'modulepart') if multidir_output not defined. + * New usage: $conf->module->multidir_output[$object->entity].'/'.get_exdir(0, 0, 0, 1, $object, $modulepart) + * or: $conf->module->dir_output.'/'.get_exdir(0, 0, 0, 1, $object, $modulepart) if multidir_output not defined. * Old usage: '015' with level 3->"0/1/5/", '015' with level 1->"5/", 'ABC-1' with level 3 ->"0/0/1/" * * @param string $num Id of object (deprecated, $object will be used in future) diff --git a/htdocs/core/modules/modResource.class.php b/htdocs/core/modules/modResource.class.php index f6674bdcfd7..020fa92e9f3 100644 --- a/htdocs/core/modules/modResource.class.php +++ b/htdocs/core/modules/modResource.class.php @@ -305,7 +305,7 @@ class modResource extends DolibarrModules ); //$this->import_convertvalue_array[$r]=array('s.fk_soc'=>array('rule'=>'lastrowid',table='t'); $this->import_regex_array[$r]=array('s.datec'=>'^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]( [0-9][0-9]:[0-9][0-9]:[0-9][0-9])?$'); - $this->import_examplevalues_array[$r]=array('r.ref'=>"REF1",'r.fk_code_type_resource'=>"Code from dictionnary resource type",'r.datec'=>"2017-01-01 or 2017-01-01 12:30:00"); + $this->import_examplevalues_array[$r]=array('r.ref'=>"REF1",'r.fk_code_type_resource'=>"Code from dictionary resource type",'r.datec'=>"2017-01-01 or 2017-01-01 12:30:00"); $this->import_updatekeys_array[$r]=array('r.rf'=>'ResourceFormLabel_ref'); } diff --git a/htdocs/install/mysql/migration/4.0.0-5.0.0.sql b/htdocs/install/mysql/migration/4.0.0-5.0.0.sql index 7eb08612611..a750581ae49 100644 --- a/htdocs/install/mysql/migration/4.0.0-5.0.0.sql +++ b/htdocs/install/mysql/migration/4.0.0-5.0.0.sql @@ -218,7 +218,7 @@ create table llx_user_employment tms timestamp, fk_user_creat integer, fk_user_modif integer, - job varchar(128), -- job position. may be a dictionnary + job varchar(128), -- job position. may be a dictionary status integer NOT NULL, -- draft, active, closed salary double(24,8), -- last and current value stored into llx_user salaryextra double(24,8), -- last and current value stored into llx_user diff --git a/htdocs/install/mysql/tables/llx_user_employment.sql b/htdocs/install/mysql/tables/llx_user_employment.sql index 4dfa2548c2f..80520ce3dd8 100644 --- a/htdocs/install/mysql/tables/llx_user_employment.sql +++ b/htdocs/install/mysql/tables/llx_user_employment.sql @@ -28,7 +28,7 @@ create table llx_user_employment tms timestamp, fk_user_creat integer, fk_user_modif integer, - job varchar(128), -- job position. may be a dictionnary + job varchar(128), -- job position. may be a dictionary status integer NOT NULL, -- draft, active, closed salary double(24,8), -- last and current value stored into llx_user salaryextra double(24,8), -- last and current value stored into llx_user diff --git a/htdocs/variants/generator.php b/htdocs/variants/generator.php index 7b0358fd176..d4162899874 100644 --- a/htdocs/variants/generator.php +++ b/htdocs/variants/generator.php @@ -162,19 +162,19 @@ if (! empty($id) || ! empty($ref)) { print_fiche_titre($langs->trans('ProductCombinationGenerator')); - $dictionnary_attr = array(); + $dictionary_attr = array(); foreach ($prodattr->fetchAll() as $attr) { - $dictionnary_attr[$attr->id] = $attr; + $dictionary_attr[$attr->id] = $attr; foreach ($prodattrval->fetchAllByProductAttribute($attr->id) as $attrval) { - $dictionnary_attr[$attr->id]->values[$attrval->id] = $attrval; + $dictionary_attr[$attr->id]->values[$attrval->id] = $attrval; } } ?>