diff --git a/composer.json b/composer.json
index f0de86ce7ea..ed75d3ca61d 100644
--- a/composer.json
+++ b/composer.json
@@ -13,7 +13,8 @@
"require": {
"php": ">=5.3.0",
"ext-gd": "*",
- "ext-curl": "*"
+ "ext-curl": "*",
+ "restler/framework": "3.0.*"
},
"suggest": {
"ext-mysqli": "*",
diff --git a/dev/skeletons/build_api_class.php b/dev/skeletons/build_api_class.php
new file mode 100755
index 00000000000..4ed122c95d8
--- /dev/null
+++ b/dev/skeletons/build_api_class.php
@@ -0,0 +1,123 @@
+#!/usr/bin/php
+
+ *
+ * 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 .
+ */
+
+/**
+ * \file dev/skeletons/build_api_class.php
+ * \ingroup core
+ * \brief Create a complete API class file from existant class file
+ */
+
+$sapi_type = php_sapi_name();
+$script_file = basename(__FILE__);
+$path=dirname(__FILE__).'/';
+
+// Test if batch mode
+if (substr($sapi_type, 0, 3) == 'cgi') {
+ echo "Error: You are using PHP for CGI. To execute ".$script_file." from command line, you must use PHP for CLI mode.\n";
+ exit;
+}
+
+// Include Dolibarr environment
+require_once($path."../../htdocs/master.inc.php");
+// After this $db is a defined handler to database.
+
+// Main
+$version='1';
+@set_time_limit(0);
+$error=0;
+
+$langs->load("main");
+
+
+print "***** $script_file ($version) *****\n";
+
+
+// -------------------- START OF BUILD_API_FROM_CLASS --------------------
+
+// Check parameters
+if (! isset($argv[1]) && ! isset($argv[2]))
+{
+ print "Usage: $script_file phpClassFile phpClassName\n";
+ exit;
+}
+// Show parameters
+print 'Classfile='.$argv[1]."\n";
+print 'Classname='.$argv[2]."\n";
+
+$classfile=$argv[1];
+$classname=$argv[2];
+$classmin=strtolower($classname);
+$classnameApi = $classname.'Api';
+$property=array();
+$targetcontent='';
+
+// Load the class and read properties
+require_once($classfile);
+
+$property=array();
+$class = new $classname($db);
+$values=get_class_vars($classname);
+
+unset($values['db']);
+unset($values['error']);
+unset($values['errors']);
+unset($values['element']);
+unset($values['table_element']);
+unset($values['table_element_line']);
+unset($values['fk_element']);
+unset($values['ismultientitymanaged']);
+
+// Read skeleton_api_class.class.php file
+$skeletonfile=$path.'skeleton_api_class.class.php';
+$sourcecontent=file_get_contents($skeletonfile);
+if (! $sourcecontent)
+{
+ print "\n";
+ print "Error: Failed to read skeleton sample '".$skeletonfile."'\n";
+ print "Try to run script from skeletons directory.\n";
+ exit;
+}
+
+// Define output variables
+$outfile='out.api_'.$classmin.'.class.php';
+$targetcontent=$sourcecontent;
+
+// Substitute class name
+$targetcontent=preg_replace('/skeleton_api_class\.class\.php/', 'api_'.$classmin.'.class.php', $targetcontent);
+$targetcontent=preg_replace('/skeleton/', $classmin, $targetcontent);
+//$targetcontent=preg_replace('/\$table_element=\'skeleton\'/', '\$table_element=\''.$tablenoprefix.'\'', $targetcontent);
+$targetcontent=preg_replace('/SkeletonApi/', $classnameApi, $targetcontent);
+$targetcontent=preg_replace('/Skeleton/', $classname, $targetcontent);
+
+// Build file
+$fp=fopen($outfile,"w");
+if ($fp)
+{
+ fputs($fp, $targetcontent);
+ fclose($fp);
+ print "\n";
+ print "File '".$outfile."' has been built in current directory.\n";
+}
+else $error++;
+
+
+
+// -------------------- END OF BUILD_CLASS_FROM_TABLE SCRIPT --------------------
+
+print "You can now rename generated files by removing the 'out.' prefix in their name and store them into directory /module/class.\n";
+return $error;
diff --git a/dev/skeletons/skeleton_api_class.class.php b/dev/skeletons/skeleton_api_class.class.php
new file mode 100644
index 00000000000..9cc8de2bc5e
--- /dev/null
+++ b/dev/skeletons/skeleton_api_class.class.php
@@ -0,0 +1,288 @@
+
+ *
+ * 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 .
+ */
+
+ use Luracast\Restler\RestException;
+
+
+/**
+ * API class for skeleton object
+ *
+ * @smart-auto-routing false
+ * @access protected
+ * @class DolibarrApiAccess {@requires user,external}
+ *
+ *
+ */
+class SkeletonApi extends DolibarrApi
+{
+ /**
+ * @var array $FIELDS Mandatory fields, checked when create and update object
+ */
+ static $FIELDS = array(
+ 'name'
+ );
+
+ /**
+ * @var Skeleton $skeleton {@type Skeleton}
+ */
+ public $skeleton;
+
+ /**
+ * Constructor
+ *
+ * @url GET skeleton/
+ *
+ */
+ function __construct()
+ {
+ global $db, $conf;
+ $this->db = $db;
+ $this->skeleton = new Skeleton($this->db);
+ }
+
+ /**
+ * Get properties of a skeleton object
+ *
+ * Return an array with skeleton informations
+ *
+ * @param int $id ID of skeleton
+ * @return array|mixed data without useless information
+ *
+ * @url GET skeleton/{id}
+ * @throws RestException
+ */
+ function get($id)
+ {
+ if(! DolibarrApiAccess::$user->rights->skeleton->read) {
+ throw new RestException(401);
+ }
+
+ $result = $this->skeleton->fetch($id);
+ if( ! $result ) {
+ throw new RestException(404, 'Skeleton not found');
+ }
+
+ if( ! DolibarrApi::_checkAccessToResource('skeleton',$this->skeleton->id)) {
+ throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+ }
+
+ return $this->_cleanObjectDatas($this->skeleton);
+ }
+
+ /**
+ * List skeletons
+ *
+ * Get a list of skeletons
+ *
+ * @param int $mode Use this param to filter list
+ * @param string $sortfield Sort field
+ * @param string $sortorder Sort order
+ * @param int $limit Limit for list
+ * @param int $page Page number
+ *
+ * @return array Array of skeleton objects
+ *
+ * @url GET /skeletons/
+ */
+ function getList($mode, $sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0) {
+ global $db, $conf;
+
+ $obj_ret = array();
+
+ $socid = DolibarrApiAccess::$user->societe_id ? DolibarrApiAccess::$user->societe_id : '';
+
+ // If the internal user must only see his customers, force searching by him
+ if (! DolibarrApiAccess::$user->rights->societe->client->voir && !$socid) $search_sale = DolibarrApiAccess::$user->id;
+
+ $sql = "SELECT s.rowid";
+ if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socid) || $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."skeleton as s";
+
+ if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socid) || $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.= ", ".MAIN_DB_PREFIX."c_stcomm as st";
+ $sql.= " WHERE s.fk_stcomm = st.id";
+
+ // Example of use $mode
+ //if ($mode == 1) $sql.= " AND s.client IN (1, 3)";
+ //if ($mode == 2) $sql.= " AND s.client IN (2, 3)";
+
+ $sql.= ' AND s.entity IN ('.getEntity('skeleton', 1).')';
+ if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socid) || $search_sale > 0) $sql.= " AND s.fk_soc = sc.fk_soc";
+ if ($socid) $sql.= " AND s.fk_soc = ".$socid;
+ if ($search_sale > 0) $sql.= " AND s.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;
+ }
+
+ $nbtotalofrecords = 0;
+ if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST))
+ {
+ $result = $db->query($sql);
+ $nbtotalofrecords = $db->num_rows($result);
+ }
+
+ $sql.= $db->order($sortfield, $sortorder);
+ if ($limit) {
+ if ($page < 0)
+ {
+ $page = 0;
+ }
+ $offset = $limit * $page;
+
+ $sql.= $db->plimit($limit + 1, $offset);
+ }
+
+ $result = $db->query($sql);
+ if ($result)
+ {
+ $num = $db->num_rows($result);
+ while ($i < $num)
+ {
+ $obj = $db->fetch_object($result);
+ $skeleton_static = new Skeleton($db);
+ if($skeleton_static->fetch($obj->rowid)) {
+ $obj_ret[] = parent::_cleanObjectDatas($skeleton_static);
+ }
+ $i++;
+ }
+ }
+ else {
+ throw new RestException(503, 'Error when retrieve skeleton list');
+ }
+ if( ! count($obj_ret)) {
+ throw new RestException(404, 'No skeleton found');
+ }
+ return $obj_ret;
+ }
+
+ /**
+ * Create skeleton object
+ *
+ * @param array $request_data Request datas
+ * @return int ID of skeleton
+ *
+ * @url POST skeleton/
+ */
+ function post($request_data = NULL)
+ {
+ if(! DolibarrApiAccess::$user->rights->skeleton->create) {
+ throw new RestException(401);
+ }
+ // Check mandatory fields
+ $result = $this->_validate($request_data);
+
+ foreach($request_data as $field => $value) {
+ $this->skeleton->$field = $value;
+ }
+ if( ! $this->skeleton->create(DolibarrApiAccess::$user)) {
+ throw new RestException(500);
+ }
+ return $this->skeleton->id;
+ }
+
+ /**
+ * Update skeleton
+ *
+ * @param int $id Id of skeleton to update
+ * @param array $request_data Datas
+ * @return int
+ *
+ * @url PUT skeleton/{id}
+ */
+ function put($id, $request_data = NULL)
+ {
+ if(! DolibarrApiAccess::$user->rights->skeleton->create) {
+ throw new RestException(401);
+ }
+
+ $result = $this->skeleton->fetch($id);
+ if( ! $result ) {
+ throw new RestException(404, 'Skeleton not found');
+ }
+
+ if( ! DolibarrApi::_checkAccessToResource('skeleton',$this->skeleton->id)) {
+ throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+ }
+
+ foreach($request_data as $field => $value) {
+ $this->skeleton->$field = $value;
+ }
+
+ if($this->skeleton->update($id, DolibarrApiAccess::$user))
+ return $this->get ($id);
+
+ return false;
+ }
+
+ /**
+ * Delete skeleton
+ *
+ * @param int $id Skeleton ID
+ * @return array
+ *
+ * @url DELETE skeleton/{id}
+ */
+ function delete($id)
+ {
+ if(! DolibarrApiAccess::$user->rights->skeleton->supprimer) {
+ throw new RestException(401);
+ }
+ $result = $this->skeleton->fetch($id);
+ if( ! $result ) {
+ throw new RestException(404, 'Skeleton not found');
+ }
+
+ if( ! DolibarrApi::_checkAccessToResource('skeleton',$this->skeleton->id)) {
+ throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+ }
+
+ if( !$this->skeleton->delete($id))
+ {
+ throw new RestException(500);
+ }
+
+ return array(
+ 'success' => array(
+ 'code' => 200,
+ 'message' => 'Skeleton deleted'
+ )
+ );
+
+ }
+
+ /**
+ * Validate fields before create or update object
+ *
+ * @param array $data Data to validate
+ * @return array
+ *
+ * @throws RestException
+ */
+ function _validate($data)
+ {
+ $skeleton = array();
+ foreach (SkeletonApi::$FIELDS as $field) {
+ if (!isset($data[$field]))
+ throw new RestException(400, "$field field missing");
+ $skeleton[$field] = $data[$field];
+ }
+ return $skeleton;
+ }
+}
diff --git a/htdocs/adherents/card_subscriptions.php b/htdocs/adherents/card_subscriptions.php
index 369027b1e14..3aedb6fe50c 100644
--- a/htdocs/adherents/card_subscriptions.php
+++ b/htdocs/adherents/card_subscriptions.php
@@ -873,7 +873,7 @@ if ($rowid)
}
});
';
- if (GETPOST('paymentsave')) print '$("#'.GETPOST('paymentsave').'").attr("checked",true);';
+ if (GETPOST('paymentsave')) print '$("#'.GETPOST('paymentsave').'").prop("checked",true);';
print '});';
print ''."\n";
}
diff --git a/htdocs/admin/const.php b/htdocs/admin/const.php
index 6b63ba74c6b..55434d4de2e 100644
--- a/htdocs/admin/const.php
+++ b/htdocs/admin/const.php
@@ -165,7 +165,7 @@ jQuery(document).ready(function() {
var row_num = field_id.split("_");
jQuery("#updateconst").show();
jQuery("#action").val('update');
- jQuery("#check_" + row_num[1]).attr("checked",true);
+ jQuery("#check_" + row_num[1]).prop("checked",true);
});
});
diff --git a/htdocs/admin/mails.php b/htdocs/admin/mails.php
index 159c03589b1..2b50008d58a 100644
--- a/htdocs/admin/mails.php
+++ b/htdocs/admin/mails.php
@@ -271,12 +271,12 @@ if ($action == 'edit')
{
jQuery(".drag").hide();
jQuery("#MAIN_MAIL_EMAIL_TLS").val(0);
- jQuery("#MAIN_MAIL_EMAIL_TLS").attr(\'disabled\', \'disabled\');
+ jQuery("#MAIN_MAIL_EMAIL_TLS").prop("disabled", true);
';
if ($linuxlike)
{
- print ' jQuery("#MAIN_MAIL_SMTP_SERVER").attr(\'disabled\', \'disabled\');';
- print ' jQuery("#MAIN_MAIL_SMTP_PORT").attr(\'disabled\', \'disabled\');';
+ print ' jQuery("#MAIN_MAIL_SMTP_SERVER").prop("disabled", true);';
+ print ' jQuery("#MAIN_MAIL_SMTP_PORT").prop("disabled", true);';
}
print '
}
@@ -284,9 +284,9 @@ if ($action == 'edit')
{
jQuery(".drag").show();
jQuery("#MAIN_MAIL_EMAIL_TLS").val('.$conf->global->MAIN_MAIL_EMAIL_TLS.');
- jQuery("#MAIN_MAIL_EMAIL_TLS").removeAttr(\'disabled\');
- jQuery("#MAIN_MAIL_SMTP_SERVER").removeAttr(\'disabled\');
- jQuery("#MAIN_MAIL_SMTP_PORT").removeAttr(\'disabled\');
+ jQuery("#MAIN_MAIL_EMAIL_TLS").removeAttr("disabled");
+ jQuery("#MAIN_MAIL_SMTP_SERVER").removeAttr("disabled");
+ jQuery("#MAIN_MAIL_SMTP_PORT").removeAttr("disabled");
}
}
initfields();
diff --git a/htdocs/admin/menus/edit.php b/htdocs/admin/menus/edit.php
index 9ded9ae1f34..4ab41c7991e 100644
--- a/htdocs/admin/menus/edit.php
+++ b/htdocs/admin/menus/edit.php
@@ -255,12 +255,12 @@ if ($action == 'create')
{
if (jQuery("#topleft").val() == \'top\')
{
- jQuery("#menuId").attr(\'disabled\',\'disabled\');
+ jQuery("#menuId").prop("disabled", true);
jQuery("#menuId").val(\'\');
}
else
{
- jQuery("#menuId").removeAttr(\'disabled\');
+ jQuery("#menuId").removeAttr("disabled");
}
}
init_topleft();
diff --git a/htdocs/admin/system/modules.php b/htdocs/admin/system/modules.php
index 47a386c1a03..1014beba184 100644
--- a/htdocs/admin/system/modules.php
+++ b/htdocs/admin/system/modules.php
@@ -118,7 +118,7 @@ foreach($sortorder as $numero=>$name)
$idperms="";
$var=!$var;
// Module
- print "
";
+ print "
';
$alt=$name.' - '.$modules_files[$numero];
if (! empty($picto[$numero]))
{
diff --git a/htdocs/admin/tools/dolibarr_export.php b/htdocs/admin/tools/dolibarr_export.php
index 842efb3a0ac..671a9bf9eba 100644
--- a/htdocs/admin/tools/dolibarr_export.php
+++ b/htdocs/admin/tools/dolibarr_export.php
@@ -96,7 +96,7 @@ jQuery(document).ready(function() {
jQuery("#select_sql_compat").click(function() {
if (jQuery("#select_sql_compat").val() == 'POSTGRESQL')
{
- jQuery("#checkbox_dump_disable-add-locks").attr('checked',true);
+ jQuery("#checkbox_dump_disable-add-locks").prop('checked',true);
}
});
diff --git a/htdocs/api/README.md b/htdocs/api/README.md
new file mode 100644
index 00000000000..30f90a7e13e
--- /dev/null
+++ b/htdocs/api/README.md
@@ -0,0 +1,50 @@
+API howto
+=========
+
+Explore the api
+---------------
+
+You can explore API method by using web interface : https://**yourdolibarr.tld**/htdocs/public/api/explorer/index.html (replace **yourdolibarr.tld** by real hostname of your Dolibarr installation)
+
+Access to the API
+---------------
+
+> **Warning : access to the API should (or better : must!) be secured with SSL connection**
+
+To access to the API you need a token to identify. When you access the API for the first time, you need to log in with user name and password to get a token. **Only** this token will allow to access API with.
+
+To log in with the API, use this uri : https://**yourdolibarr.tld**/htdocs/public/api/login?login=**username**&password=**password** (replace bold strings with real values)
+
+The token will be saved by Dolibarr for next user accesses to the API and it **must** be put into request uri as **api_key** parameter.
+
+Develop the API
+--------------
+
+The API uses Lucarast Restler framework. Please check documentation https://www.luracast.com/products/restler and examples http://help.luracast.com/restler/examples/
+Github contains also usefull informations : https://github.com/Luracast/Restler
+
+To implement it into Dolibarr, we need to create a specific class for object we want to use. A skeleton file is available into /dev directory : *skeleton_api_class.class.php*
+The API class file must be put into object class directory, with specific file name. By example, API class file for '*myobject*' must be put as : /htdocs/*myobject*/class/api_*myobject*.class.php. Class must be named **MyobjectApi**.
+
+If a module provide several object, use a different name for '*myobject*' and put the file into the same directory.
+
+**Define url for methods**
+
+It is possible to specify url for API methods by simply use the PHPDoc tag **@url**. See examples :
+
+ /**
+ * List contacts
+ *
+ * Get a list of contacts
+ *
+ * @url GET /contact/list
+ * @url GET /contact/list/{socid}
+ * @url GET /thirdparty/{socid}/contacts
+ * [...]
+
+**Other Annotations**
+Other annotations are used, you are encouraged to read them : https://github.com/Luracast/Restler/blob/master/ANNOTATIONS.md
+
+PHPDoc tags can also be used to specify variables informations for API. Again, rtfm : https://github.com/Luracast/Restler/blob/master/PARAM.md
+
+
diff --git a/htdocs/api/admin/api.php b/htdocs/api/admin/api.php
new file mode 100644
index 00000000000..0e1673715d2
--- /dev/null
+++ b/htdocs/api/admin/api.php
@@ -0,0 +1,138 @@
+
+ * Copyright (C) 2005-2010 Laurent Destailleur
+ * Copyright (C) 2011 Juanjo Menent
+ * Copyright (C) 2012 Regis Houssin
+ * Copyright (C) 2015 Regis Houssin
+ *
+ * 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 .
+ */
+
+/**
+ * \file htdocs/api/admin/api.php
+ * \ingroup api
+ * \brief Page to setup api module
+ */
+
+require '../../main.inc.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
+
+$langs->load("admin");
+
+if (! $user->admin)
+ accessforbidden();
+
+$actionsave=GETPOST("save");
+
+// Sauvegardes parametres
+if ($actionsave)
+{
+ $i=0;
+
+ $db->begin();
+
+ $i+=dolibarr_set_const($db,'API_KEY',trim(GETPOST("API_KEY")),'chaine',0,'',$conf->entity);
+
+ if ($i >= 1)
+ {
+ $db->commit();
+ setEventMessage($langs->trans("SetupSaved"));
+ }
+ else
+ {
+ $db->rollback();
+ setEventMessage($langs->trans("Error"), 'errors');
+ }
+}
+
+
+/*
+ * View
+ */
+
+llxHeader();
+
+$linkback=''.$langs->trans("BackToModuleList").'';
+print_fiche_titre($langs->trans("ApiSetup"),$linkback,'title_setup');
+
+print $langs->trans("ApiDesc")." \n";
+print " \n";
+
+print '';
+
+print '
';
+
+// API endpoint
+print ''.$langs->trans("ApiEndPointIs").': ';
+$url=DOL_MAIN_URL_ROOT.'/public/api/';
+print img_picto('','object_globe.png').' '.$url." \n";
+$url=DOL_MAIN_URL_ROOT.'/public/api/.json';
+print img_picto('','object_globe.png').' '.$url." \n";
+
+// Explorer
+print ''.$langs->trans("ApiExporerIs").': ';
+$url=DOL_MAIN_URL_ROOT.'/public/api/explorer/index.html';
+print img_picto('','object_globe.png').' '.$url." \n";
+
+print ' ';
+
+
+print ' ';
+print $langs->trans("OnlyActiveElementsAreExposed", DOL_URL_ROOT.'/admin/modules.php');
+
+if (! empty($conf->use_javascript_ajax))
+{
+ print "\n".'';
+}
+
+
+llxFooter();
+$db->close();
diff --git a/htdocs/api/class/api.class.php b/htdocs/api/class/api.class.php
new file mode 100644
index 00000000000..9f03ae84acf
--- /dev/null
+++ b/htdocs/api/class/api.class.php
@@ -0,0 +1,215 @@
+
+ *
+ * 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 .
+ */
+
+use Luracast\Restler\Restler;
+use Luracast\Restler\RestException;
+
+require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php';
+
+/**
+ * Class for API
+ *
+ */
+class DolibarrApi
+{
+
+ /**
+ * @var DoliDb $db Database object
+ */
+ static protected $db;
+
+ /**
+ * @var Restler $r Restler object
+ */
+ var $r;
+
+ /**
+ * Constructor
+ *
+ * @param DoliDb $db Database handler
+ */
+ function __construct($db) {
+ $this->db = $db;
+ $this->r = new Restler();
+ }
+
+ /**
+ * Executed method when API is called without parameter
+ *
+ * Display a short message an return a http code 200
+ *
+ * @return array
+ */
+ function index()
+ {
+ return array(
+ 'success' => array(
+ 'code' => 200,
+ 'message' => __class__.' is up and running!'
+ )
+ );
+ }
+
+
+ /**
+ * Clean sensible object datas
+ *
+ * @param object $object Object to clean
+ * @return array Array of cleaned object properties
+ *
+ * @todo use an array for properties to clean
+ *
+ */
+ function _cleanObjectDatas($object) {
+
+ // Remove $db object property for object
+ unset($object->db);
+
+ // If object has lines, remove $db property
+ if(isset($object->lines) && count($object->lines) > 0) {
+ for($i=0; $i < count($object->lines); $i++) {
+ $this->_cleanObjectDatas($object->lines[$i]);
+ }
+ }
+
+ // If object has linked objects, remove $db property
+ if(isset($object->linkedObjects) && count($object->linkedObjects) > 0) {
+ foreach($object->linkedObjects as $type_object => $linked_object) {
+ foreach($linked_object as $object2clean) {
+ $this->_cleanObjectDatas($object2clean);
+ }
+ }
+ }
+ return $object;
+ }
+
+ /**
+ * Check user access to a resource
+ *
+ * Check access by user to a given resource
+ *
+ * @param string $resource element to check
+ * @param int $resource_id Object ID if we want to check a particular record (optional) is linked to a owned thirdparty (optional).
+ * @param type $dbtablename 'TableName&SharedElement' with Tablename is table where object is stored. SharedElement is an optional key to define where to check entity. Not used if objectid is null (optional)
+ * @param string $feature2 Feature to check, second level of permission (optional). Can be or check with 'level1|level2'.
+ * @param string $dbt_keyfield Field name for socid foreign key if not fk_soc. Not used if objectid is null (optional)
+ * @param string $dbt_select Field name for select if not rowid. Not used if objectid is null (optional)
+ * @throws RestException
+ */
+ static function _checkAccessToResource($resource, $resource_id=0, $dbtablename='', $feature2='', $dbt_keyfield='fk_soc', $dbt_select='rowid') {
+
+ // Features/modules to check
+ $featuresarray = array($resource);
+ if (preg_match('/&/', $resource)) {
+ $featuresarray = explode("&", $resource);
+ }
+ else if (preg_match('/\|/', $resource)) {
+ $featuresarray = explode("|", $resource);
+ }
+
+ // More subfeatures to check
+ if (! empty($feature2)) {
+ $feature2 = explode("|", $feature2);
+ }
+
+ return checkUserAccessToObject(DolibarrApiAccess::$user, $featuresarray,$resource_id,$dbtablename,$feature2,$dbt_keyfield,$dbt_select);
+ }
+}
+
+/**
+ * API init
+ *
+ */
+class DolibarrApiInit extends DolibarrApi
+{
+
+ function __construct() {
+ global $db;
+ $this->db = $db;
+ }
+
+ /**
+ * Login
+ *
+ * Log user with username and password
+ *
+ * @param string $login Username
+ * @param string $password User password
+ * @param int $entity User entity
+ * @return array Response status and user token
+ *
+ * @throws RestException
+ */
+ public function login($login, $password, $entity = 0) {
+
+ // Authentication mode
+ if (empty($dolibarr_main_authentication))
+ $dolibarr_main_authentication = 'http,dolibarr';
+ // Authentication mode: forceuser
+ if ($dolibarr_main_authentication == 'forceuser' && empty($dolibarr_auto_user))
+ $dolibarr_auto_user = 'auto';
+ // Set authmode
+ $authmode = explode(',', $dolibarr_main_authentication);
+
+ include_once DOL_DOCUMENT_ROOT . '/core/lib/security2.lib.php';
+ $login = checkLoginPassEntity($login, $password, $entity, $authmode);
+ if (empty($login))
+ {
+ throw new RestException(403, 'Access denied');
+ }
+
+ // Generate token for user
+ $token = dol_hash($login.uniqid().$conf->global->MAIN_API_KEY,1);
+
+ // We store API token into database
+ $sql = "UPDATE ".MAIN_DB_PREFIX."user";
+ $sql.= " SET api_key = '".$this->db->escape($token)."'";
+ $sql.= " WHERE login = '".$this->db->escape($login)."'";
+
+ dol_syslog(get_class($this)."::login", LOG_DEBUG); // No log
+ $result = $this->db->query($sql);
+ if (!$result)
+ {
+ throw new RestException(500, 'Error when updating user :'.$this->db->error_msg);
+ }
+
+ //return token
+ return array(
+ 'success' => array(
+ 'code' => 200,
+ 'token' => $token,
+ 'message' => 'Welcome ' . $login
+ )
+ );
+ }
+
+ /**
+ * Get status (Dolibarr version)
+ *
+ * @access protected
+ * @class DolibarrApiAccess {@requires admin}
+ */
+ function status() {
+ require_once DOL_DOCUMENT_ROOT . '/core/lib/functions.lib.php';
+ return array(
+ 'success' => array(
+ 'code' => 200,
+ 'dolibarr_version' => DOL_VERSION
+ )
+ );
+ }
+}
diff --git a/htdocs/api/class/api_access.class.php b/htdocs/api/class/api_access.class.php
new file mode 100644
index 00000000000..1e3d43ed9db
--- /dev/null
+++ b/htdocs/api/class/api_access.class.php
@@ -0,0 +1,144 @@
+
+ *
+ * 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 .
+ */
+
+use \Luracast\Restler\iAuthenticate;
+use \Luracast\Restler\Resources;
+use \Luracast\Restler\Defaults;
+use Luracast\Restler\RestException;
+
+
+/**
+ * Dolibarr API access class
+ *
+ */
+class DolibarrApiAccess implements iAuthenticate
+{
+ const REALM = 'Restricted Dolibarr API';
+
+ /**
+ * @var array $requires role required by API method user / external / admin
+ */
+ public static $requires = array('user','external','admin');
+
+ /**
+ * @var string $role user role
+ */
+ public static $role = 'user';
+
+ /**
+ * @var User $user Loggued user
+ */
+ public static $user = '';
+
+ // @codingStandardsIgnoreStart
+
+ /**
+ * @return string string to be used with WWW-Authenticate header
+ * @example Basic
+ * @example Digest
+ * @example OAuth
+ */
+ public function __getWWWAuthenticateString();
+
+ /**
+ * Check access
+ *
+ * @return boolean
+ */
+ public function _isAllowed()
+ {
+ // @codingStandardsIgnoreEnd
+ global $db;
+
+ $stored_key = '';
+
+ $userClass = Defaults::$userIdentifierClass;
+
+ if (isset($_GET['api_key'])) {
+ $sql = "SELECT u.login, u.datec, u.api_key, ";
+ $sql.= " u.tms as date_modification, u.entity";
+ $sql.= " FROM ".MAIN_DB_PREFIX."user as u";
+ $sql.= " WHERE u.api_key = '".$db->escape($_GET['api_key'])."'";
+
+ if ($db->query($sql))
+ {
+ if ($db->num_rows($result))
+ {
+ $obj = $db->fetch_object($result);
+ $login = $obj->login;
+ $stored_key = $obj->api_key;
+ }
+ }
+ else {
+ throw new RestException(503, 'Error when fetching user api_key :'.$db->error_msg);
+ }
+
+ if ( $stored_key != $_GET['api_key']) {
+ $userClass::setCacheIdentifier($_GET['api_key']);
+ return false;
+ }
+
+ $fuser = new User($db);
+ if(! $fuser->fetch('',$login)) {
+ throw new RestException(503, 'Error when fetching user :'.$fuser->error);
+ }
+ $fuser->getrights();
+ static::$user = $fuser;
+
+ if($fuser->societe_id)
+ static::$role = 'external';
+
+ if($fuser->admin)
+ static::$role = 'admin';
+ }
+ else
+ {
+ return false;
+ }
+
+ $userClass::setCacheIdentifier(static::$role);
+ Resources::$accessControlFunction = 'DolibarrApiAccess::verifyAccess';
+ return in_array(static::$role, (array) static::$requires) || static::$role == 'admin';
+ }
+
+ // @codingStandardsIgnoreStart
+ public function __getWWWAuthenticateString()
+ {
+ return '';
+ }
+ // @codingStandardsIgnoreEnd
+
+ /**
+ * Verify access
+ *
+ * @param array $m Properties of method
+ *
+ * @access private
+ */
+ public static function verifyAccess(array $m)
+ {
+ $requires = isset($m['class']['DolibarrApiAccess']['properties']['requires'])
+ ? $m['class']['DolibarrApiAccess']['properties']['requires']
+ : false;
+
+
+ return $requires
+ ? static::$role == 'admin' || in_array(static::$role, (array) $requires)
+ : true;
+
+ }
+}
diff --git a/htdocs/api/index.php b/htdocs/api/index.php
new file mode 100644
index 00000000000..3ac00faa65f
--- /dev/null
+++ b/htdocs/api/index.php
@@ -0,0 +1,8 @@
+clicktodial->enabled)) {
@@ -64,13 +65,15 @@ if ($resql)
if ($obj)
{
$found = $obj->name;
+ } else {
+ $found = $notfound;
}
$db->free($resql);
}
else
{
dol_print_error($db,'Error');
+ $found = $error;
}
echo $found;
-
diff --git a/htdocs/barcode/printsheet.php b/htdocs/barcode/printsheet.php
index 1a518b1859c..36d9117eefe 100644
--- a/htdocs/barcode/printsheet.php
+++ b/htdocs/barcode/printsheet.php
@@ -313,28 +313,28 @@ jQuery(document).ready(function() {
{
if (jQuery("#fillmanually:checked").val() == "fillmanually")
{
- jQuery("#submitproduct").attr(\'disabled\',\'disabled\');
- jQuery("#submitthirdparty").attr(\'disabled\',\'disabled\');
- jQuery("#search_productid").attr(\'disabled\',\'disabled\');
- jQuery("#socid").attr(\'disabled\',\'disabled\');
+ jQuery("#submitproduct").prop("disabled", true);
+ jQuery("#submitthirdparty").prop("disabled", true);
+ jQuery("#search_productid").prop("disabled", true);
+ jQuery("#socid").prop("disabled", true);
jQuery(".showforproductselector").hide();
jQuery(".showforthirdpartyselector").hide();
}
if (jQuery("#fillfromproduct:checked").val() == "fillfromproduct")
{
- jQuery("#submitproduct").removeAttr(\'disabled\');
- jQuery("#submitthirdparty").attr(\'disabled\',\'disabled\');
- jQuery("#search_productid").removeAttr(\'disabled\');
- jQuery("#socid").attr(\'disabled\',\'disabled\');
+ jQuery("#submitproduct").removeAttr("disabled");
+ jQuery("#submitthirdparty").prop("disabled", true);
+ jQuery("#search_productid").removeAttr("disabled");
+ jQuery("#socid").prop("disabled", true);
jQuery(".showforproductselector").show();
jQuery(".showforthirdpartyselector").hide();
}
if (jQuery("#fillfromthirdparty:checked").val() == "fillfromthirdparty")
{
- jQuery("#submitproduct").attr(\'disabled\',\'disabled\');
- jQuery("#submitthirdparty").removeAttr(\'disabled\');
- jQuery("#search_productid").attr(\'disabled\',\'disabled\');
- jQuery("#socid").removeAttr(\'disabled\');
+ jQuery("#submitproduct").prop("disabled", true);
+ jQuery("#submitthirdparty").removeAttr("disabled");
+ jQuery("#search_productid").prop("disabled", true);
+ jQuery("#socid").removeAttr("disabled");
jQuery(".showforproductselector").hide();
jQuery(".showforthirdpartyselector").show();
}
@@ -348,11 +348,11 @@ jQuery(document).ready(function() {
{
if (jQuery("#select_fk_barcode_type").val() > 0 && jQuery("#forbarcode").val())
{
- jQuery("#submitformbarcodegen").removeAttr(\'disabled\');
+ jQuery("#submitformbarcodegen").removeAttr("disabled");
}
else
{
- jQuery("#submitformbarcodegen").attr(\'disabled\',\'disabled\');
+ jQuery("#submitformbarcodegen").prop("disabled", true);
}
}
init_gendoc_button();
diff --git a/htdocs/categories/class/api_category.class.php b/htdocs/categories/class/api_category.class.php
new file mode 100644
index 00000000000..364147d097d
--- /dev/null
+++ b/htdocs/categories/class/api_category.class.php
@@ -0,0 +1,355 @@
+
+ *
+ * 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 .
+ */
+
+ use Luracast\Restler\RestException;
+
+ require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
+
+/**
+ * API class for category object
+ *
+ * @smart-auto-routing false
+ * @access protected
+ * @class DolibarrApiAccess {@requires user,external}
+ *
+ *
+ */
+class CategoryApi extends DolibarrApi
+{
+ /**
+ * @var array $FIELDS Mandatory fields, checked when create and update object
+ */
+ static $FIELDS = array(
+ 'label',
+ 'type'
+ );
+
+ static $TYPES = array(
+ 0 => 'product',
+ 1 => 'supplier',
+ 2 => 'customer',
+ 3 => 'member',
+ 4 => 'contact',
+ );
+
+ /**
+ * @var Categorie $category {@type Categorie}
+ */
+ public $category;
+
+ /**
+ * Constructor
+ *
+ * @url GET category/
+ *
+ */
+ function __construct()
+ {
+ global $db, $conf;
+ $this->db = $db;
+ $this->category = new Categorie($this->db);
+
+ }
+
+ /**
+ * Get properties of a category object
+ *
+ * Return an array with category informations
+ *
+ * @param int $id ID of category
+ * @return array|mixed data without useless information
+ *
+ * @url GET category/{id}
+ * @throws RestException
+ */
+ function get($id)
+ {
+ if(! DolibarrApiAccess::$user->rights->categorie->lire) {
+ throw new RestException(401);
+ }
+
+ $result = $this->category->fetch($id);
+ if( ! $result ) {
+ throw new RestException(404, 'category not found');
+ }
+
+ if( ! DolibarrApi::_checkAccessToResource('category',$this->category->id)) {
+ throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+ }
+
+ return $this->_cleanObjectDatas($this->category);
+ }
+
+ /**
+ * List categories
+ *
+ * Get a list of categories
+ *
+ * @param string $type Type of category ('member', 'customer', 'supplier', 'product', 'contact')
+ * @param string $sortfield Sort field
+ * @param string $sortorder Sort order
+ * @param int $limit Limit for list
+ * @param int $page Page number
+ * @return array Array of category objects
+ *
+ * @url GET /category/list
+ */
+ function getList($type='product', $sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0) {
+ global $db, $conf;
+
+ $obj_ret = array();
+
+ if(! DolibarrApiAccess::$user->rights->categorie->lire) {
+ throw new RestException(401);
+ }
+
+ $sql = "SELECT s.rowid";
+ $sql.= " FROM ".MAIN_DB_PREFIX."categorie as s";
+ $sql.= ' WHERE s.entity IN ('.getEntity('categorie', 1).')';
+ $sql.= ' AND s.type='.array_search($type,CategoryApi::$TYPES);
+
+ $nbtotalofrecords = 0;
+ if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST))
+ {
+ $result = $db->query($sql);
+ $nbtotalofrecords = $db->num_rows($result);
+ }
+
+ $sql.= $db->order($sortfield, $sortorder);
+ if ($limit) {
+ if ($page < 0)
+ {
+ $page = 0;
+ }
+ $offset = $limit * $page;
+
+ $sql.= $db->plimit($limit + 1, $offset);
+ }
+
+ $result = $db->query($sql);
+ if ($result)
+ {
+ $num = $db->num_rows($result);
+ while ($i < $num)
+ {
+ $obj = $db->fetch_object($result);
+ $category_static = new Categorie($db);
+ if($category_static->fetch($obj->rowid)) {
+ $obj_ret[] = parent::_cleanObjectDatas($category_static);
+ }
+ $i++;
+ }
+ }
+ else {
+ throw new RestException(503, 'Error when retrieve category list : '.$category_static->error);
+ }
+ if( ! count($obj_ret)) {
+ throw new RestException(404, 'No category found');
+ }
+ return $obj_ret;
+ }
+
+ /**
+ * Get member categories list
+ *
+ * @param string $sortfield Sort field
+ * @param string $sortorder Sort order
+ * @param int $limit Limit for list
+ * @param int $page Page number
+ * @return mixed
+ *
+ * @url GET /category/list/member
+ */
+ function getListCategoryMember($sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0) {
+ return $this->getList('member', $sortfield, $sortorder, $limit, $page);
+ }
+
+ /**
+ * Get customer categories list
+ *
+ * @param string $sortfield Sort field
+ * @param string $sortorder Sort order
+ * @param int $limit Limit for list
+ * @param int $page Page number
+ *
+ * @return mixed
+ *
+ * @url GET /category/list/customer
+ */
+ function getListCategoryCustomer($sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0) {
+ return $this->getList('customer', $sortfield, $sortorder, $limit, $page);
+ }
+
+ /**
+ * Get supplier categories list
+ *
+ * @param string $sortfield Sort field
+ * @param string $sortorder Sort order
+ * @param int $limit Limit for list
+ * @param int $page Page number
+ *
+ * @return mixed
+ *
+ * @url GET /category/list/supplier
+ */
+ function getListCategorySupplier($sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0) {
+ return $this->getList('supplier', $sortfield, $sortorder, $limit, $page);
+ }
+
+ /**
+ * Get product categories list
+ *
+ * @param string $sortfield Sort field
+ * @param string $sortorder Sort order
+ * @param int $limit Limit for list
+ * @param int $page Page number
+ *
+ * @return mixed
+ *
+ * @url GET /category/list/product
+ */
+ function getListCategoryProduct($sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0) {
+ return $this->getList('product', $sortfield, $sortorder, $limit, $page);
+ }
+
+ /**
+ * Get contact categories list
+ *
+ * @param string $sortfield Sort field
+ * @param string $sortorder Sort order
+ * @param int $limit Limit for list
+ * @param int $page Page number
+ * @return mixed
+ *
+ * @url GET /category/list/contact
+ */
+ function getListCategoryContact($sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0) {
+ return $this->getList('contact', $sortfield, $sortorder, $limit, $page);
+ }
+
+ /**
+ * Create category object
+ *
+ * @param array $request_data Request data
+ * @return int ID of category
+ *
+ * @url POST category/
+ */
+ function post($request_data = NULL)
+ {
+ if(! DolibarrApiAccess::$user->rights->categorie->creer) {
+ throw new RestException(401);
+ }
+ // Check mandatory fields
+ $result = $this->_validate($request_data);
+
+ foreach($request_data as $field => $value) {
+ $this->category->$field = $value;
+ }
+ if($this->category->create(DolibarrApiAccess::$user) < 0) {
+ throw new RestException(503, 'Error when create category : '.$this->category->error);
+ }
+ return $this->category->id;
+ }
+
+ /**
+ * Update category
+ *
+ * @param int $id Id of category to update
+ * @param array $request_data Datas
+ * @return int
+ *
+ * @url PUT category/{id}
+ */
+ function put($id, $request_data = NULL)
+ {
+ if(! DolibarrApiAccess::$user->rights->categorie->creer) {
+ throw new RestException(401);
+ }
+
+ $result = $this->category->fetch($id);
+ if( ! $result ) {
+ throw new RestException(404, 'category not found');
+ }
+
+ if( ! DolibarrApi::_checkAccessToResource('category',$this->category->id)) {
+ throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+ }
+
+ foreach($request_data as $field => $value) {
+ $this->category->$field = $value;
+ }
+
+ if($this->category->update(DolibarrApiAccess::$user))
+ return $this->get ($id);
+
+ return false;
+ }
+
+ /**
+ * Delete category
+ *
+ * @param int $id Category ID
+ * @return array
+ *
+ * @url DELETE category/{id}
+ */
+ function delete($id)
+ {
+ if(! DolibarrApiAccess::$user->rights->categorie->supprimer) {
+ throw new RestException(401);
+ }
+ $result = $this->category->fetch($id);
+ if( ! $result ) {
+ throw new RestException(404, 'category not found');
+ }
+
+ if( ! DolibarrApi::_checkAccessToResource('category',$this->category->id)) {
+ throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+ }
+
+ if (! $this->category->delete(DolibarrApiAccess::$user)) {
+ throw new RestException(401,'error when delete category');
+ }
+
+ return array(
+ 'success' => array(
+ 'code' => 200,
+ 'message' => 'Category deleted'
+ )
+ );
+ }
+
+ /**
+ * Validate fields before create or update object
+ *
+ * @param array $data Data to validate
+ * @return array
+ *
+ * @throws RestException
+ */
+ function _validate($data)
+ {
+ $category = array();
+ foreach (CategoryApi::$FIELDS as $field) {
+ if (!isset($data[$field]))
+ throw new RestException(400, "$field field missing");
+ $category[$field] = $data[$field];
+ }
+ return $category;
+ }
+}
diff --git a/htdocs/categories/viewcat.php b/htdocs/categories/viewcat.php
index a1c544f338b..927474a5e44 100644
--- a/htdocs/categories/viewcat.php
+++ b/htdocs/categories/viewcat.php
@@ -254,7 +254,7 @@ else
{
$var=!$var;
print "\t
';
}
}
else
@@ -791,7 +791,7 @@ if ($id > 0 || ! empty($ref))
// Transaction reconciliated or edit link
if ($objp->rappro && $object->canBeConciliated() > 0) // If line not conciliated and account can be conciliated
{
- print '
';
+ print '
';
print '';
print img_edit();
print '';
@@ -844,7 +844,7 @@ if ($id > 0 || ! empty($ref))
if ($sep > 0) print ' '; // If we had at least one line in future
else print $langs->trans("CurrentBalance");
print ' '.$object->currency_code.'
';
// We can also use bcadd to avoid pb with floating points
@@ -428,7 +428,7 @@ if ($object->id > 0)
//$resteapayer=bcadd($resteapayer,$totalavoir,$conf->global->MAIN_MAX_DECIMALS_TOT);
$resteapayer = price2num($object->total_ttc - $totalpaye - $totalcreditnotes - $totaldeposits,'MT');
- print '