From b813144edb102c4c9b13407836042eae220761dd Mon Sep 17 00:00:00 2001 From: arnaud Date: Fri, 6 Jan 2017 16:44:09 +0100 Subject: [PATCH 1/6] NEW generation ODT --- htdocs/admin/user.php | 43 +++- .../core/class/commondocgenerator.class.php | 16 ++ htdocs/core/class/commonobject.class.php | 1 + htdocs/core/class/html.formfile.class.php | 29 ++- htdocs/core/lib/functions2.lib.php | 1 - .../modules/product/modules_product.class.php | 37 ++++ .../core/modules/user/modules_user.class.php | 62 ++++++ htdocs/product/admin/product.php | 192 ++++++++++++++++++ htdocs/product/card.php | 70 +++++++ htdocs/product/class/product.class.php | 35 ++++ htdocs/user/card.php | 35 ++++ 11 files changed, 516 insertions(+), 5 deletions(-) create mode 100644 htdocs/core/modules/user/modules_user.class.php diff --git a/htdocs/admin/user.php b/htdocs/admin/user.php index f76762c8334..fcb062457a9 100644 --- a/htdocs/admin/user.php +++ b/htdocs/admin/user.php @@ -40,11 +40,48 @@ if (! $user->admin) accessforbidden(); $extrafields = new ExtraFields($db); +$type='user'; /* * Action */ -if (preg_match('/set_(.*)/',$action,$reg)) + +// Activate a model +if ($action == 'set_default') +{ + $ret = addDocumentModel($value, $type, $label, $scandir); + $res = true; +} + +elseif ($action == 'del_default') +{ + $ret = delDocumentModel($value, $type); + if ($ret > 0) + { + if ($conf->global->USER_ADDON_PDF_ODT == "$value") dolibarr_del_const($db, 'USER_ADDON_PDF_ODT',$conf->entity); + } + $res = true; +} + +// Set default model +elseif ($action == 'setdoc') +{ + if (dolibarr_set_const($db, "USER_ADDON_PDF_ODT",$value,'chaine',0,'',$conf->entity)) + { + // La constante qui a ete lue en avant du nouveau set + // on passe donc par une variable pour avoir un affichage coherent + $conf->global->PRODUCT_ADDON_PDF_ODT = $value; + } + + // On active le modele + $ret = delDocumentModel($value, $type); + if ($ret > 0) + { + $ret = addDocumentModel($value, $type, $label, $scandir); + } + $res = true; +} +elseif (preg_match('/set_(.*)/',$action,$reg)) { $code=$reg[1]; if (dolibarr_set_const($db, $code, 1, 'chaine', 0, '', $conf->entity) > 0) @@ -58,7 +95,7 @@ if (preg_match('/set_(.*)/',$action,$reg)) } } -if (preg_match('/del_(.*)/',$action,$reg)) +elseif (preg_match('/del_(.*)/',$action,$reg)) { $code=$reg[1]; if (dolibarr_del_const($db, $code, $conf->entity) > 0) @@ -72,7 +109,7 @@ if (preg_match('/del_(.*)/',$action,$reg)) } } //Set hide closed customer into combox or select -if ($action == 'sethideinactiveuser') +elseif ($action == 'sethideinactiveuser') { $status = GETPOST('status','alpha'); diff --git a/htdocs/core/class/commondocgenerator.class.php b/htdocs/core/class/commondocgenerator.class.php index 9b233ef6b74..51977bcb356 100644 --- a/htdocs/core/class/commondocgenerator.class.php +++ b/htdocs/core/class/commondocgenerator.class.php @@ -317,6 +317,22 @@ abstract class CommonDocGenerator return $array_other; } + + + + function get_substitutionarray_each_var_object(&$object,$outputlangs,$recursive=true) { + foreach($object as $key => $value) { + if(!is_array($value) && !is_object($value)) { + $array_other['object_'.$key] = $value; + } + if(is_array($value) && $recursive){ + foreach($value as $key2 => $val) { + $array_other[$key][$key2] = $this->get_substitutionarray_each_var_object($val,$outputlangs,false); + } + } + } + return $array_other; + } diff --git a/htdocs/core/class/commonobject.class.php b/htdocs/core/class/commonobject.class.php index 3279d372a0e..f8ab4f34467 100644 --- a/htdocs/core/class/commonobject.class.php +++ b/htdocs/core/class/commonobject.class.php @@ -3589,6 +3589,7 @@ abstract class CommonObject $modele=$tmp[0]; $srctemplatepath=$tmp[1]; } + // Search template files $file=''; $classname=''; $filefound=0; diff --git a/htdocs/core/class/html.formfile.class.php b/htdocs/core/class/html.formfile.class.php index a973c2622cc..ded716c1766 100644 --- a/htdocs/core/class/html.formfile.class.php +++ b/htdocs/core/class/html.formfile.class.php @@ -299,7 +299,7 @@ class FormFile $headershown=0; $showempty=0; $i=0; - + $titletoshow=$langs->trans("Documents"); if (! empty($title)) $titletoshow=$title; @@ -402,6 +402,33 @@ class FormFile $modellist=ModelePDFProjects::liste_modeles($this->db); } } + elseif ($modulepart == 'product') + { + if (is_array($genallowed)) $modellist=$genallowed; + else + { + include_once DOL_DOCUMENT_ROOT.'/core/modules/product/modules_product.class.php'; + $modellist=ModelePDFProduct::liste_modeles($this->db); + } + } + elseif ($modulepart == 'user') + { + if (is_array($genallowed)) $modellist=$genallowed; + else + { + include_once DOL_DOCUMENT_ROOT.'/core/modules/user/modules_user.php'; + $modellist=ModelePDFProduct::liste_modeles($this->db); + } + } + elseif ($modulepart == 'group') + { + if (is_array($genallowed)) $modellist=$genallowed; + else + { + include_once DOL_DOCUMENT_ROOT.'/core/modules/product/modules_group.php'; + $modellist=ModelePDFProduct::liste_modeles($this->db); + } + } elseif ($modulepart == 'project_task') { if (is_array($genallowed)) $modellist=$genallowed; diff --git a/htdocs/core/lib/functions2.lib.php b/htdocs/core/lib/functions2.lib.php index 82449ac63cb..19279296ce9 100644 --- a/htdocs/core/lib/functions2.lib.php +++ b/htdocs/core/lib/functions2.lib.php @@ -1444,7 +1444,6 @@ function getListOfModels($db,$type,$maxfilenamelength=0) $const=$obj->description; $dirtoscan.=($dirtoscan?',':'').preg_replace('/[\r\n]+/',',',trim($conf->global->$const)); - $listoffiles=array(); // Now we add models found in directories scanned diff --git a/htdocs/core/modules/product/modules_product.class.php b/htdocs/core/modules/product/modules_product.class.php index e08ea074bbd..d7138af84a8 100644 --- a/htdocs/core/modules/product/modules_product.class.php +++ b/htdocs/core/modules/product/modules_product.class.php @@ -24,6 +24,43 @@ * \class ModeleProductCode * \brief Parent class for product code generators */ + +/** + * \file htdocs/core/modules/contract/modules_contract.php + * \ingroup contract + * \brief File with parent class for generating contracts to PDF and File of class to manage contract numbering + */ + + require_once DOL_DOCUMENT_ROOT.'/core/class/commondocgenerator.class.php'; + +/** + * Parent class to manage intervention document templates + */ +abstract class ModelePDFProduct extends CommonDocGenerator +{ + var $error=''; + + + /** + * Return list of active generation modules + * + * @param DoliDB $db Database handler + * @param integer $maxfilenamelength Max length of value to show + * @return array List of templates + */ + static function liste_modeles($db,$maxfilenamelength=0) + { + global $conf; + + $type='product'; + $liste=array(); + + include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; + $liste=getListOfModels($db,$type,$maxfilenamelength); + return $liste; + } +} + abstract class ModeleProductCode { var $error=''; diff --git a/htdocs/core/modules/user/modules_user.class.php b/htdocs/core/modules/user/modules_user.class.php new file mode 100644 index 00000000000..3e94ece9372 --- /dev/null +++ b/htdocs/core/modules/user/modules_user.class.php @@ -0,0 +1,62 @@ + + * Copyright (C) 2004-2010 Laurent Destailleur + * Copyright (C) 2004 Eric Seigne + * Copyright (C) 2005-2012 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 . + * or see http://www.gnu.org/ + */ + + +/** + * \class ModeleProductCode + * \brief Parent class for product code generators + */ + +/** + * \file htdocs/core/modules/contract/modules_contract.php + * \ingroup contract + * \brief File with parent class for generating contracts to PDF and File of class to manage contract numbering + */ + + require_once DOL_DOCUMENT_ROOT.'/core/class/commondocgenerator.class.php'; + +/** + * Parent class to manage intervention document templates + */ +abstract class ModelePDFUser extends CommonDocGenerator +{ + var $error=''; + + + /** + * Return list of active generation modules + * + * @param DoliDB $db Database handler + * @param integer $maxfilenamelength Max length of value to show + * @return array List of templates + */ + static function liste_modeles($db,$maxfilenamelength=0) + { + global $conf; + + $type='user'; + $liste=array(); + + include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; + $liste=getListOfModels($db,$type,$maxfilenamelength); + return $liste; + } +} \ No newline at end of file diff --git a/htdocs/product/admin/product.php b/htdocs/product/admin/product.php index 8a561c0e53e..10da8463058 100644 --- a/htdocs/product/admin/product.php +++ b/htdocs/product/admin/product.php @@ -43,6 +43,7 @@ if (! $user->admin || (empty($conf->product->enabled) && empty($conf->service->e $action = GETPOST('action','alpha'); $value = GETPOST('value','alpha'); +$type='product'; // Pricing Rules $select_pricing_rules=array( @@ -110,6 +111,42 @@ if ($action == 'setModuleOptions') } } +// Activate a model +if ($action == 'set_default') +{ + $ret = addDocumentModel($value, $type, $label, $scandir); + $res = true; +} + +if ($action == 'del_default') +{ + $ret = delDocumentModel($value, $type); + if ($ret > 0) + { + if ($conf->global->PRODUCT_ADDON_PDF_ODT == "$value") dolibarr_del_const($db, 'PRODUCT_ADDON_PDF_ODT',$conf->entity); + } + $res = true; +} + +// Set default model +if ($action == 'setdoc') +{ + if (dolibarr_set_const($db, "PRODUCT_ADDON_PDF_ODT",$value,'chaine',0,'',$conf->entity)) + { + // La constante qui a ete lue en avant du nouveau set + // on passe donc par une variable pour avoir un affichage coherent + $conf->global->PRODUCT_ADDON_PDF_ODT = $value; + } + + // On active le modele + $ret = delDocumentModel($value, $type); + if ($ret > 0) + { + $ret = addDocumentModel($value, $type, $label, $scandir); + } + $res = true; +} + if ($action == 'other' && GETPOST('value_PRODUIT_LIMIT_SIZE') >= 0) { $res = dolibarr_set_const($db, "PRODUIT_LIMIT_SIZE", GETPOST('value_PRODUIT_LIMIT_SIZE'),'chaine',0,'',$conf->entity); @@ -228,6 +265,8 @@ else if (empty($conf->service->enabled)) llxHeader('',$title); +$dirmodels=array_merge(array('/'),(array) $conf->modules_parts['models']); + $linkback=''.$langs->trans("BackToModuleList").''; print load_fiche_titre($title,$linkback,'title_setup'); @@ -317,6 +356,159 @@ foreach ($dirproduct as $dirroot) } print ''; + +// Defini tableau def des modeles +$def = array(); +$sql = "SELECT nom"; +$sql.= " FROM ".MAIN_DB_PREFIX."document_model"; +$sql.= " WHERE type = '".$type."'"; +$sql.= " AND entity = ".$conf->entity; +$resql=$db->query($sql); +if ($resql) +{ + $i = 0; + $num_rows=$db->num_rows($resql); + while ($i < $num_rows) + { + $array = $db->fetch_array($resql); + array_push($def, $array[0]); + $i++; + } +} +else +{ + dol_print_error($db); +} + +print ''; +print ''; +print ''; +print ''; +print '\n"; +print '\n"; +print ''; +print ''; +print "\n"; + +clearstatcache(); + +$var=true; +foreach ($dirmodels as $reldir) +{ + foreach (array('','/doc') as $valdir) + { + $dir = dol_buildpath($reldir."core/modules/product".$valdir); + if (is_dir($dir)) + { + $handle=opendir($dir); + if (is_resource($handle)) + { + while (($file = readdir($handle))!==false) + { + $filelist[]=$file; + } + closedir($handle); + arsort($filelist); + + foreach($filelist as $file) + { + if (preg_match('/\.modules\.php$/i',$file) && preg_match('/^(pdf_|doc_)/',$file)) + { + + if (file_exists($dir.'/'.$file)) + { + $name = substr($file, 4, dol_strlen($file) -16); + $classname = substr($file, 0, dol_strlen($file) -12); + + require_once $dir.'/'.$file; + $module = new $classname($db); + + $modulequalified=1; + if ($module->version == 'development' && $conf->global->MAIN_FEATURES_LEVEL < 2) $modulequalified=0; + if ($module->version == 'experimental' && $conf->global->MAIN_FEATURES_LEVEL < 1) $modulequalified=0; + + if ($modulequalified) + { + $var = !$var; + print ''; + + // Active + if (in_array($name, $def)) + { + print ''; + } + else + { + print '"; + } + + // Defaut + print ''; + + // Info + $htmltooltip = ''.$langs->trans("Name").': '.$module->name; + $htmltooltip.='
'.$langs->trans("Type").': '.($module->type?$module->type:$langs->trans("Unknown")); + if ($module->type == 'pdf') + { + $htmltooltip.='
'.$langs->trans("Width").'/'.$langs->trans("Height").': '.$module->page_largeur.'/'.$module->page_hauteur; + } + $htmltooltip.='

'.$langs->trans("FeaturesSupported").':'; + $htmltooltip.='
'.$langs->trans("Logo").': '.yn($module->option_logo,1,1); + $htmltooltip.='
'.$langs->trans("PaymentMode").': '.yn($module->option_modereg,1,1); + $htmltooltip.='
'.$langs->trans("PaymentConditions").': '.yn($module->option_condreg,1,1); + $htmltooltip.='
'.$langs->trans("MultiLanguage").': '.yn($module->option_multilang,1,1); + $htmltooltip.='
'.$langs->trans("WatermarkOnDraftOrders").': '.yn($module->option_draft_watermark,1,1); + + + print ''; + + // Preview + print ''; + + print "\n"; + } + } + } + } + } + } + } +} + +print '
'.$langs->trans("Name").''.$langs->trans("Description").''.$langs->trans("Status")."'.$langs->trans("Default")."'.$langs->trans("ShortInfo").''.$langs->trans("Preview").'
'; + print (empty($module->name)?$name:$module->name); + print "\n"; + if (method_exists($module,'info')) print $module->info($langs); + else print $module->description; + print ''."\n"; + print ''; + print img_picto($langs->trans("Enabled"),'switch_on'); + print ''; + print ''."\n"; + print 'scandir.'&label='.urlencode($module->name).'">'.img_picto($langs->trans("Disabled"),'switch_off').''; + print "'; + if ($conf->global->PRODUCT_ADDON_PDF == $name) + { + print img_picto($langs->trans("Default"),'on'); + } + else + { + print 'scandir.'&label='.urlencode($module->name).'" alt="'.$langs->trans("Default").'">'.img_picto($langs->trans("Disabled"),'off').''; + } + print ''; + print $form->textwithpicto('',$htmltooltip,1,0); + print ''; + if ($module->type == 'pdf') + { + print ''.img_object($langs->trans("Preview"),'contract').''; + } + else + { + print img_object($langs->trans("PreviewNotAvailable"),'generic'); + } + print '
'; +print "
"; + /* * Other conf */ diff --git a/htdocs/product/card.php b/htdocs/product/card.php index 59ccdb79a1f..7a0ebca0ace 100644 --- a/htdocs/product/card.php +++ b/htdocs/product/card.php @@ -38,6 +38,7 @@ require '../main.inc.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/canvas.class.php'; require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/genericobject.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php'; @@ -517,6 +518,40 @@ if (empty($reshook)) $action=''; } } + if ($action == 'builddoc' && $user->rights->produit->creer) { + /*if (GETPOST('model')) { + $object->setDocModel($user, GETPOST('model')); + }*/ + + // Define output language + $outputlangs = $langs; + if (! empty($conf->global->MAIN_MULTILANGS)) { + $outputlangs = new Translate("", $conf); + $newlang = (GETPOST('lang_id') ? GETPOST('lang_id') : $object->thirdparty->default_lang); + $outputlangs->setDefaultLang($newlang); + } + $ret = $object->fetch($id); // Reload to get new records + $result = $object->generateDocument($object->modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref); + if ($result <= 0) + { + setEventMessages($object->error, $object->errors, 'errors'); + $action=''; + } + } + + // Remove file in doc form + if ($action == 'remove_file' && $user->rights->contrat->creer) { + if ($object->id > 0) { + require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php'; + + $langs->load("other"); + $upload_dir = $conf->contrat->dir_output; + $file = $upload_dir . '/' . GETPOST('file'); + $ret = dol_delete_file($file, 0, 0, 0, $object); + if ($ret) setEventMessages($langs->trans("FileWasRemoved", GETPOST('file')), null, 'mesgs'); + else setEventMessages($langs->trans("ErrorFailToDeleteFile", GETPOST('file')), null, 'errors'); + } + } // Add product into object @@ -770,6 +805,7 @@ else $title = $langs->trans('ProductServiceCard'); llxHeader('', $title, $helpurl); $form = new Form($db); +$formfile = new FormFile($db); $formproduct = new FormProduct($db); @@ -1829,5 +1865,39 @@ if ($object->id && ($action == '' || $action == 'view') && $object->status) } +print '
'; + +/* + * Documents generes +*/ +$filename = dol_sanitizeFileName($object->ref); +$filedir = $conf->produit->dir_output . "/" . dol_sanitizeFileName($object->ref); +$urlsource = $_SERVER["PHP_SELF"] . "?id=" . $object->id; +$genallowed = $user->rights->produit->creer; +$delallowed = $user->rights->produit->supprimer; + +$var = true; + +$somethingshown = $formfile->show_documents('product', $filename, $filedir, $urlsource, $genallowed, $delallowed, $object->modelpdf, 1, 0, 0, 28, 0, '', 0, '', $soc->default_lang); + +// Linked object block +$somethingshown = $form->showLinkedObjectBlock($object); + +// Show links to link elements +$linktoelem = $form->showLinkToObjectBlock($object); +if ($linktoelem) print '
'.$linktoelem; + + +print '
'; + +// List of actions on element +include_once DOL_DOCUMENT_ROOT . '/core/class/html.formactions.class.php'; +$formactions = new FormActions($db); +$somethingshown = $formactions->showactions($object, 'product', $socid); + + +print '
'; + + llxFooter(); $db->close(); diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php index 1d8d17d6c8f..2c4eb128413 100644 --- a/htdocs/product/class/product.class.php +++ b/htdocs/product/class/product.class.php @@ -3191,6 +3191,41 @@ class Product extends CommonObject return $result; } + + /** + * Create a document onto disk according to template module. + * + * @param string $modele Force model to use ('' to not force) + * @param Translate $outputlangs Object langs to use for output + * @param int $hidedetails Hide details of lines + * @param int $hidedesc Hide description + * @param int $hideref Hide ref + * @return int 0 if KO, 1 if OK + */ + public function generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0) + { + global $conf,$user,$langs; + + $langs->load("products"); + + // Positionne le modele sur le nom du modele a utiliser + if (! dol_strlen($modele)) + { + if (! empty($conf->global->PRODUCT_ADDON_PDF)) + { + $modele = $conf->global->PRODUCT_ADDON_PDF; + } + else + { + $modele = 'strato'; + } + } + + $modelpath = "core/modules/product/doc/"; + + return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref); + } + /** * Return label of status of object * diff --git a/htdocs/user/card.php b/htdocs/user/card.php index b0425b182e0..ce6ef3f992f 100644 --- a/htdocs/user/card.php +++ b/htdocs/user/card.php @@ -35,6 +35,7 @@ require '../main.inc.php'; require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php'; require_once DOL_DOCUMENT_ROOT.'/user/class/usergroup.class.php'; require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/usergroups.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php'; @@ -589,6 +590,7 @@ if (empty($reshook)) { $form = new Form($db); $formother=new FormOther($db); $formcompany = new FormCompany($db); +$formfile = new FormFile($db); llxHeader('',$langs->trans("UserCard")); @@ -2275,8 +2277,41 @@ else print ''; } + print '
'; + /* + * Documents generes + */ + $filename = dol_sanitizeFileName($object->ref); + $filedir = $conf->user->dir_output . "/" . dol_sanitizeFileName($object->ref); + $urlsource = $_SERVER["PHP_SELF"] . "?id=" . $object->id; + $genallowed = $user->rights->user->creer; + $delallowed = $user->rights->user->supprimer; + + $var = true; + + $somethingshown = $formfile->show_documents('user', $filename, $filedir, $urlsource, $genallowed, $delallowed, $object->modelpdf, 1, 0, 0, 28, 0, '', 0, '', $soc->default_lang); + + // Linked object block + $somethingshown = $form->showLinkedObjectBlock($object); + + // Show links to link elements + $linktoelem = $form->showLinkToObjectBlock($object); + if ($linktoelem) print '
'.$linktoelem; + + + print '
'; + + // List of actions on element + include_once DOL_DOCUMENT_ROOT . '/core/class/html.formactions.class.php'; + $formactions = new FormActions($db); + $somethingshown = $formactions->showactions($object, 'user', $socid); + + + print '
'; + if (! empty($conf->ldap->enabled) && ! empty($object->ldap_sid)) $ldap->close; } + } if (! empty($conf->api->enabled) && ! empty($conf->use_javascript_ajax)) From abeba95de9740d1cecaed4f872ff799326e15c13 Mon Sep 17 00:00:00 2001 From: arnaud Date: Thu, 2 Mar 2017 15:13:42 +0100 Subject: [PATCH 2/6] New USER odt --- htdocs/admin/user.php | 194 +++++++++++++++++++++- htdocs/core/class/html.formfile.class.php | 4 +- htdocs/core/lib/admin.lib.php | 2 +- htdocs/user/card.php | 9 +- htdocs/user/class/user.class.php | 34 ++++ 5 files changed, 235 insertions(+), 8 deletions(-) diff --git a/htdocs/admin/user.php b/htdocs/admin/user.php index fcb062457a9..d237ca3e727 100644 --- a/htdocs/admin/user.php +++ b/htdocs/admin/user.php @@ -35,11 +35,12 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php'; $langs->load("admin"); $langs->load("members"); $langs->load("users"); - if (! $user->admin) accessforbidden(); $extrafields = new ExtraFields($db); +$action = GETPOST('action','alpha'); +$value = GETPOST('value','alpha'); $type='user'; /* @@ -47,7 +48,36 @@ $type='user'; */ // Activate a model -if ($action == 'set_default') + +// Define constants for submodules that contains parameters (forms with param1, param2, ... and value1, value2, ...) +if ($action == 'setModuleOptions') +{ + $post_size=count($_POST); + + $db->begin(); + + for($i=0;$i < $post_size;$i++) + { + if (array_key_exists('param'.$i,$_POST)) + { + $param=GETPOST("param".$i,'alpha'); + $value=GETPOST("value".$i,'alpha'); + if ($param) $res = dolibarr_set_const($db,$param,$value,'chaine',0,'',$conf->entity); + if (! $res > 0) $error++; + } + } + if (! $error) + { + $db->commit(); + setEventMessages($langs->trans("SetupSaved"), null, 'mesgs'); + } + else + { + $db->rollback(); + setEventMessages($langs->trans("Error"), null, 'errors'); + } +} +elseif ($action == 'set_default') { $ret = addDocumentModel($value, $type, $label, $scandir); $res = true; @@ -70,7 +100,7 @@ elseif ($action == 'setdoc') { // La constante qui a ete lue en avant du nouveau set // on passe donc par une variable pour avoir un affichage coherent - $conf->global->PRODUCT_ADDON_PDF_ODT = $value; + $conf->global->USER_ADDON_PDF_ODT = $value; } // On active le modele @@ -175,6 +205,164 @@ print ''; print ''; + + +$dirmodels=array_merge(array('/'),(array) $conf->modules_parts['models']); + +$form=new Form($db); + +// Defini tableau def des modeles +$def = array(); +$sql = "SELECT nom"; +$sql.= " FROM ".MAIN_DB_PREFIX."document_model"; +$sql.= " WHERE type = '".$type."'"; +$sql.= " AND entity = ".$conf->entity; +$resql=$db->query($sql); +if ($resql) +{ + $i = 0; + $num_rows=$db->num_rows($resql); + while ($i < $num_rows) + { + $array = $db->fetch_array($resql); + array_push($def, $array[0]); + $i++; + } +} +else +{ + dol_print_error($db); +} + +print ''; +print ''; +print ''; +print ''; +print '\n"; +print '\n"; +print ''; +print ''; +print "\n"; + +clearstatcache(); + +$var=true; +foreach ($dirmodels as $reldir) +{ + foreach (array('','/doc') as $valdir) + { + $dir = dol_buildpath($reldir."core/modules/user".$valdir); + if (is_dir($dir)) + { + $handle=opendir($dir); + if (is_resource($handle)) + { + while (($file = readdir($handle))!==false) + { + $filelist[]=$file; + } + closedir($handle); + arsort($filelist); + + foreach($filelist as $file) + { + if (preg_match('/\.modules\.php$/i',$file) && preg_match('/^(pdf_|doc_)/',$file)) + { + + if (file_exists($dir.'/'.$file)) + { + $name = substr($file, 4, dol_strlen($file) -16); + $classname = substr($file, 0, dol_strlen($file) -12); + + require_once $dir.'/'.$file; + $module = new $classname($db); + + $modulequalified=1; + if ($module->version == 'development' && $conf->global->MAIN_FEATURES_LEVEL < 2) $modulequalified=0; + if ($module->version == 'experimental' && $conf->global->MAIN_FEATURES_LEVEL < 1) $modulequalified=0; + + if ($modulequalified) + { + $var = !$var; + print ''; + + // Active + if (in_array($name, $def)) + { + print ''; + } + else + { + print '"; + } + + // Defaut + print ''; + + // Info + $htmltooltip = ''.$langs->trans("Name").': '.$module->name; + $htmltooltip.='
'.$langs->trans("Type").': '.($module->type?$module->type:$langs->trans("Unknown")); + if ($module->type == 'pdf') + { + $htmltooltip.='
'.$langs->trans("Width").'/'.$langs->trans("Height").': '.$module->page_largeur.'/'.$module->page_hauteur; + } + $htmltooltip.='

'.$langs->trans("FeaturesSupported").':'; + $htmltooltip.='
'.$langs->trans("Logo").': '.yn($module->option_logo,1,1); + $htmltooltip.='
'.$langs->trans("PaymentMode").': '.yn($module->option_modereg,1,1); + $htmltooltip.='
'.$langs->trans("PaymentConditions").': '.yn($module->option_condreg,1,1); + $htmltooltip.='
'.$langs->trans("MultiLanguage").': '.yn($module->option_multilang,1,1); + $htmltooltip.='
'.$langs->trans("WatermarkOnDraftOrders").': '.yn($module->option_draft_watermark,1,1); + + + print ''; + + // Preview + print ''; + + print "\n"; + } + } + } + } + } + } + } +} + +print '
'.$langs->trans("Name").''.$langs->trans("Description").''.$langs->trans("Status")."'.$langs->trans("Default")."'.$langs->trans("ShortInfo").''.$langs->trans("Preview").'
'; + print (empty($module->name)?$name:$module->name); + print "\n"; + if (method_exists($module,'info')) print $module->info($langs); + else print $module->description; + print ''."\n"; + print ''; + print img_picto($langs->trans("Enabled"),'switch_on'); + print ''; + print ''."\n"; + print 'scandir.'&label='.urlencode($module->name).'">'.img_picto($langs->trans("Disabled"),'switch_off').''; + print "'; + if ($conf->global->USER_ADDON_PDF == $name) + { + print img_picto($langs->trans("Default"),'on'); + } + else + { + print 'scandir.'&label='.urlencode($module->name).'" alt="'.$langs->trans("Default").'">'.img_picto($langs->trans("Disabled"),'off').''; + } + print ''; + print $form->textwithpicto('',$htmltooltip,1,0); + print ''; + if ($module->type == 'pdf') + { + print ''.img_object($langs->trans("Preview"),'contract').''; + } + else + { + print img_object($langs->trans("PreviewNotAvailable"),'generic'); + } + print '
'; +print "
"; + dol_fiche_end(); llxFooter(); diff --git a/htdocs/core/class/html.formfile.class.php b/htdocs/core/class/html.formfile.class.php index ded716c1766..f60faa84426 100644 --- a/htdocs/core/class/html.formfile.class.php +++ b/htdocs/core/class/html.formfile.class.php @@ -416,8 +416,8 @@ class FormFile if (is_array($genallowed)) $modellist=$genallowed; else { - include_once DOL_DOCUMENT_ROOT.'/core/modules/user/modules_user.php'; - $modellist=ModelePDFProduct::liste_modeles($this->db); + include_once DOL_DOCUMENT_ROOT.'/core/modules/user/modules_user.class.php'; + $modellist=ModelePDFUser::liste_modeles($this->db); } } elseif ($modulepart == 'group') diff --git a/htdocs/core/lib/admin.lib.php b/htdocs/core/lib/admin.lib.php index ffb09b2b3ab..56c48a33173 100644 --- a/htdocs/core/lib/admin.lib.php +++ b/htdocs/core/lib/admin.lib.php @@ -1270,7 +1270,7 @@ function addDocumentModel($name, $type, $label='', $description='') $sql.= ($label?"'".$db->escape($label)."'":'null').", "; $sql.= (! empty($description)?"'".$db->escape($description)."'":"null"); $sql.= ")"; - + dol_syslog("admin.lib::addDocumentModel", LOG_DEBUG); $resql=$db->query($sql); if ($resql) diff --git a/htdocs/user/card.php b/htdocs/user/card.php index ce6ef3f992f..53634de159c 100644 --- a/htdocs/user/card.php +++ b/htdocs/user/card.php @@ -580,6 +580,11 @@ if (empty($reshook)) { setEventMessages($ldap->error, $ldap->errors, 'errors'); } } + + // Actions to build doc + $upload_dir = $conf->user->dir_output; + $permissioncreate=$user->rights->user->user->creer; + include DOL_DOCUMENT_ROOT.'/core/actions_builddoc.inc.php'; } @@ -2284,8 +2289,8 @@ else $filename = dol_sanitizeFileName($object->ref); $filedir = $conf->user->dir_output . "/" . dol_sanitizeFileName($object->ref); $urlsource = $_SERVER["PHP_SELF"] . "?id=" . $object->id; - $genallowed = $user->rights->user->creer; - $delallowed = $user->rights->user->supprimer; + $genallowed = $user->rights->user->user->creer; + $delallowed = $user->rights->user->user->supprimer; $var = true; diff --git a/htdocs/user/class/user.class.php b/htdocs/user/class/user.class.php index 1ee07e0a7af..f77a7fbc9df 100644 --- a/htdocs/user/class/user.class.php +++ b/htdocs/user/class/user.class.php @@ -2622,5 +2622,39 @@ class User extends CommonObject } } + /** + * Create a document onto disk according to template module. + * + * @param string $modele Force model to use ('' to not force) + * @param Translate $outputlangs Object langs to use for output + * @param int $hidedetails Hide details of lines + * @param int $hidedesc Hide description + * @param int $hideref Hide ref + * @return int 0 if KO, 1 if OK + */ + public function generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0) + { + global $conf,$user,$langs; + + $langs->load("user"); + + // Positionne le modele sur le nom du modele a utiliser + if (! dol_strlen($modele)) + { + if (! empty($conf->global->USER_ADDON_PDF)) + { + $modele = $conf->global->USER_ADDON_PDF; + } + else + { + $modele = 'bluesky'; + } + } + + $modelpath = "core/modules/user/doc/"; + + return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref); + } + } From cff9fa0e6b4f4c13909f703f2cd646336cd48437 Mon Sep 17 00:00:00 2001 From: arnaud Date: Fri, 3 Mar 2017 12:01:58 +0100 Subject: [PATCH 3/6] FIX user odt --- .../core/class/commondocgenerator.class.php | 29 ++++++++++++++----- .../core/modules/user/modules_user.class.php | 28 ++++++++++++++++++ 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/htdocs/core/class/commondocgenerator.class.php b/htdocs/core/class/commondocgenerator.class.php index 51977bcb356..1d4cb508fb9 100644 --- a/htdocs/core/class/commondocgenerator.class.php +++ b/htdocs/core/class/commondocgenerator.class.php @@ -320,16 +320,29 @@ abstract class CommonDocGenerator + /** + * Define array with couple subtitution key => subtitution value + * + * @param Object $object Dolibarr Object + * @param Translate $outputlangs Language object for output + * @param boolean $recursive Want to fetch child array or child object + * @return array Array of substitution key->code + */ function get_substitutionarray_each_var_object(&$object,$outputlangs,$recursive=true) { - foreach($object as $key => $value) { - if(!is_array($value) && !is_object($value)) { - $array_other['object_'.$key] = $value; - } - if(is_array($value) && $recursive){ - foreach($value as $key2 => $val) { - $array_other[$key][$key2] = $this->get_substitutionarray_each_var_object($val,$outputlangs,false); + $array_other = array(); + if(!empty($object)) { + foreach($object as $key => $value) { + if(!is_array($value) && !is_object($value)) { + $array_other['object_'.$key] = $value; } - } + if(is_array($value) && $recursive){ + if(!empty($value)) { + foreach($value as $key2 => $val) { + $array_other[$key][$key2] = $this->get_substitutionarray_each_var_object($val,$outputlangs,false); + } + } + } + } } return $array_other; } diff --git a/htdocs/core/modules/user/modules_user.class.php b/htdocs/core/modules/user/modules_user.class.php index 3e94ece9372..43660da1adb 100644 --- a/htdocs/core/modules/user/modules_user.class.php +++ b/htdocs/core/modules/user/modules_user.class.php @@ -55,6 +55,34 @@ abstract class ModelePDFUser extends CommonDocGenerator $type='user'; $liste=array(); + include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; + $liste=getListOfModels($db,$type,$maxfilenamelength); + return $liste; + } +} + +/** + * Parent class to manage intervention document templates + */ +abstract class ModelePDFUserGroup extends CommonDocGenerator +{ + var $error=''; + + + /** + * Return list of active generation modules + * + * @param DoliDB $db Database handler + * @param integer $maxfilenamelength Max length of value to show + * @return array List of templates + */ + static function liste_modeles($db,$maxfilenamelength=0) + { + global $conf; + + $type='usergroup'; + $liste=array(); + include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; $liste=getListOfModels($db,$type,$maxfilenamelength); return $liste; From 4a7e3fb3872d0e85613227f5d1b454cae5c5a0a8 Mon Sep 17 00:00:00 2001 From: arnaud Date: Wed, 8 Mar 2017 11:34:21 +0100 Subject: [PATCH 4/6] NEW odt usergroup --- htdocs/admin/usergroup.php | 316 ++++++++++++++++++ .../core/class/commondocgenerator.class.php | 16 +- htdocs/core/class/conf.class.php | 5 + htdocs/core/class/html.formfile.class.php | 6 +- htdocs/core/lib/usergroups.lib.php | 5 + .../core/modules/user/modules_user.class.php | 28 -- .../usergroup/modules_usergroup.class.php | 62 ++++ htdocs/install/mysql/tables/llx_user.sql | 1 + htdocs/install/mysql/tables/llx_usergroup.sql | 5 +- htdocs/user/class/usergroup.class.php | 34 ++ htdocs/user/group/card.php | 40 ++- 11 files changed, 475 insertions(+), 43 deletions(-) create mode 100644 htdocs/admin/usergroup.php create mode 100644 htdocs/core/modules/usergroup/modules_usergroup.class.php diff --git a/htdocs/admin/usergroup.php b/htdocs/admin/usergroup.php new file mode 100644 index 00000000000..72365726d25 --- /dev/null +++ b/htdocs/admin/usergroup.php @@ -0,0 +1,316 @@ + + * Copyright (C) 2003 Jean-Louis Bergamo + * Copyright (C) 2004-2009 Laurent Destailleur + * Copyright (C) 2004 Sebastien Di Cintio + * Copyright (C) 2004 Benoit Mortier + * Copyright (C) 2005-2011 Regis Houssin + * Copyright (C) 2015 Juanjo Menent + * + * 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/admin/usergroup.php + * \ingroup core + * \brief Page to setup usergroup module + */ + +require '../main.inc.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/usergroups.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php'; + +$langs->load("admin"); +$langs->load("members"); +$langs->load("users"); +if (! $user->admin) accessforbidden(); + +$extrafields = new ExtraFields($db); + +$action = GETPOST('action','alpha'); +$value = GETPOST('value','alpha'); +$type='group'; + +/* + * Action + */ + +// Activate a model + +// Define constants for submodules that contains parameters (forms with param1, param2, ... and value1, value2, ...) +if ($action == 'setModuleOptions') +{ + $post_size=count($_POST); + + $db->begin(); + + for($i=0;$i < $post_size;$i++) + { + if (array_key_exists('param'.$i,$_POST)) + { + $param=GETPOST("param".$i,'alpha'); + $value=GETPOST("value".$i,'alpha'); + if ($param) $res = dolibarr_set_const($db,$param,$value,'chaine',0,'',$conf->entity); + if (! $res > 0) $error++; + } + } + if (! $error) + { + $db->commit(); + setEventMessages($langs->trans("SetupSaved"), null, 'mesgs'); + } + else + { + $db->rollback(); + setEventMessages($langs->trans("Error"), null, 'errors'); + } +} +elseif ($action == 'set_default') +{ + $ret = addDocumentModel($value, $type, $label, $scandir); + $res = true; +} + +elseif ($action == 'del_default') +{ + $ret = delDocumentModel($value, $type); + if ($ret > 0) + { + if ($conf->global->USERGROUP_ADDON_PDF_ODT == "$value") dolibarr_del_const($db, 'USERGROUP_ADDON_PDF_ODT',$conf->entity); + } + $res = true; +} + +// Set default model +elseif ($action == 'setdoc') +{ + if (dolibarr_set_const($db, "USERGROUP_ADDON_PDF_ODT",$value,'chaine',0,'',$conf->entity)) + { + // La constante qui a ete lue en avant du nouveau set + // on passe donc par une variable pour avoir un affichage coherent + $conf->global->USERGROUP_ADDON_PDF_ODT = $value; + } + + // On active le modele + $ret = delDocumentModel($value, $type); + if ($ret > 0) + { + $ret = addDocumentModel($value, $type, $label, $scandir); + } + $res = true; +} +elseif (preg_match('/set_(.*)/',$action,$reg)) +{ + $code=$reg[1]; + if (dolibarr_set_const($db, $code, 1, 'chaine', 0, '', $conf->entity) > 0) + { + header("Location: ".$_SERVER["PHP_SELF"]); + exit; + } + else + { + dol_print_error($db); + } +} + +elseif (preg_match('/del_(.*)/',$action,$reg)) +{ + $code=$reg[1]; + if (dolibarr_del_const($db, $code, $conf->entity) > 0) + { + header("Location: ".$_SERVER["PHP_SELF"]); + exit; + } + else + { + dol_print_error($db); + } +} +/* + * View + */ + +$help_url='EN:Module_Users|FR:Module_Utilisateurs|ES:Módulo_Usuarios'; +llxHeader('',$langs->trans("UsersSetup"),$help_url); + +$linkback=''.$langs->trans("BackToModuleList").''; +print load_fiche_titre($langs->trans("UsersSetup"),$linkback,'title_setup'); + + +$head=user_admin_prepare_head(); + +dol_fiche_head($head,'usergroupcard', $langs->trans("MenuUsersAndGroups"), 0, 'user'); + + +$dirmodels=array_merge(array('/'),(array) $conf->modules_parts['models']); + +$form=new Form($db); + +// Defini tableau def des modeles +$def = array(); +$sql = "SELECT nom"; +$sql.= " FROM ".MAIN_DB_PREFIX."document_model"; +$sql.= " WHERE type = '".$type."'"; +$sql.= " AND entity = ".$conf->entity; +$resql=$db->query($sql); +if ($resql) +{ + $i = 0; + $num_rows=$db->num_rows($resql); + while ($i < $num_rows) + { + $array = $db->fetch_array($resql); + array_push($def, $array[0]); + $i++; + } +} +else +{ + dol_print_error($db); +} + +print ''; +print ''; +print ''; +print ''; +print '\n"; +print '\n"; +print ''; +print ''; +print "\n"; + +clearstatcache(); + +$var=true; +foreach ($dirmodels as $reldir) +{ + foreach (array('','/doc') as $valdir) + { + $dir = dol_buildpath($reldir."core/modules/usergroup".$valdir); + if (is_dir($dir)) + { + $handle=opendir($dir); + if (is_resource($handle)) + { + while (($file = readdir($handle))!==false) + { + $filelist[]=$file; + } + closedir($handle); + arsort($filelist); + + foreach($filelist as $file) + { + if (preg_match('/\.modules\.php$/i',$file) && preg_match('/^(pdf_|doc_)/',$file)) + { + + if (file_exists($dir.'/'.$file)) + { + $name = substr($file, 4, dol_strlen($file) -16); + $classname = substr($file, 0, dol_strlen($file) -12); + + require_once $dir.'/'.$file; + $module = new $classname($db); + + $modulequalified=1; + if ($module->version == 'development' && $conf->global->MAIN_FEATURES_LEVEL < 2) $modulequalified=0; + if ($module->version == 'experimental' && $conf->global->MAIN_FEATURES_LEVEL < 1) $modulequalified=0; + + if ($modulequalified) + { + $var = !$var; + print ''; + + // Active + if (in_array($name, $def)) + { + print ''; + } + else + { + print '"; + } + + // Defaut + print ''; + + // Info + $htmltooltip = ''.$langs->trans("Name").': '.$module->name; + $htmltooltip.='
'.$langs->trans("Type").': '.($module->type?$module->type:$langs->trans("Unknown")); + if ($module->type == 'pdf') + { + $htmltooltip.='
'.$langs->trans("Width").'/'.$langs->trans("Height").': '.$module->page_largeur.'/'.$module->page_hauteur; + } + $htmltooltip.='

'.$langs->trans("FeaturesSupported").':'; + $htmltooltip.='
'.$langs->trans("Logo").': '.yn($module->option_logo,1,1); + $htmltooltip.='
'.$langs->trans("PaymentMode").': '.yn($module->option_modereg,1,1); + $htmltooltip.='
'.$langs->trans("PaymentConditions").': '.yn($module->option_condreg,1,1); + $htmltooltip.='
'.$langs->trans("MultiLanguage").': '.yn($module->option_multilang,1,1); + $htmltooltip.='
'.$langs->trans("WatermarkOnDraftOrders").': '.yn($module->option_draft_watermark,1,1); + + + print ''; + + // Preview + print ''; + + print "\n"; + } + } + } + } + } + } + } +} + +print '
'.$langs->trans("Name").''.$langs->trans("Description").''.$langs->trans("Status")."'.$langs->trans("Default")."'.$langs->trans("ShortInfo").''.$langs->trans("Preview").'
'; + print (empty($module->name)?$name:$module->name); + print "\n"; + if (method_exists($module,'info')) print $module->info($langs); + else print $module->description; + print ''."\n"; + print ''; + print img_picto($langs->trans("Enabled"),'switch_on'); + print ''; + print ''."\n"; + print 'scandir.'&label='.urlencode($module->name).'">'.img_picto($langs->trans("Disabled"),'switch_off').''; + print "'; + if ($conf->global->USERGROUP_ADDON_PDF == $name) + { + print img_picto($langs->trans("Default"),'on'); + } + else + { + print 'scandir.'&label='.urlencode($module->name).'" alt="'.$langs->trans("Default").'">'.img_picto($langs->trans("Disabled"),'off').''; + } + print ''; + print $form->textwithpicto('',$htmltooltip,1,0); + print ''; + if ($module->type == 'pdf') + { + print ''.img_object($langs->trans("Preview"),'contract').''; + } + else + { + print img_object($langs->trans("PreviewNotAvailable"),'generic'); + } + print '
'; +print "
"; + +dol_fiche_end(); + +llxFooter(); +$db->close(); diff --git a/htdocs/core/class/commondocgenerator.class.php b/htdocs/core/class/commondocgenerator.class.php index 1d4cb508fb9..9c3bc5b131e 100644 --- a/htdocs/core/class/commondocgenerator.class.php +++ b/htdocs/core/class/commondocgenerator.class.php @@ -332,19 +332,17 @@ abstract class CommonDocGenerator $array_other = array(); if(!empty($object)) { foreach($object as $key => $value) { - if(!is_array($value) && !is_object($value)) { - $array_other['object_'.$key] = $value; - } - if(is_array($value) && $recursive){ - if(!empty($value)) { - foreach($value as $key2 => $val) { - $array_other[$key][$key2] = $this->get_substitutionarray_each_var_object($val,$outputlangs,false); - } + if(!empty($value)) { + if(!is_array($value) && !is_object($value)) { + $array_other['object_'.$key] = $value; + } + if(is_array($value) && $recursive){ + $array_other['object_'.$key] = $this->get_substitutionarray_each_var_object($value,$outputlangs,false); } } } } - return $array_other; + return $array_other; } diff --git a/htdocs/core/class/conf.class.php b/htdocs/core/class/conf.class.php index 0bd28092bff..8a5172c182b 100644 --- a/htdocs/core/class/conf.class.php +++ b/htdocs/core/class/conf.class.php @@ -106,6 +106,7 @@ class Conf $this->propal = new stdClass(); $this->facture = new stdClass(); $this->contrat = new stdClass(); + $this->usergroup = new stdClass(); $this->adherent = new stdClass(); $this->bank = new stdClass(); $this->notification = new stdClass(); @@ -309,6 +310,10 @@ class Conf // For backward compatibility $this->user->dir_output=$rootforuser."/users"; $this->user->dir_temp=$rootforuser."/users/temp"; + + // UserGroup + $this->usergroup->dir_output=$rootforuser."/usergroups"; + $this->usergroup->dir_temp=$rootforuser."/usergroups/temp"; // For propal storage $this->propal->dir_output=$rootfordata."/propale"; diff --git a/htdocs/core/class/html.formfile.class.php b/htdocs/core/class/html.formfile.class.php index f60faa84426..2dbbd9545db 100644 --- a/htdocs/core/class/html.formfile.class.php +++ b/htdocs/core/class/html.formfile.class.php @@ -420,13 +420,13 @@ class FormFile $modellist=ModelePDFUser::liste_modeles($this->db); } } - elseif ($modulepart == 'group') + elseif ($modulepart == 'usergroup') { if (is_array($genallowed)) $modellist=$genallowed; else { - include_once DOL_DOCUMENT_ROOT.'/core/modules/product/modules_group.php'; - $modellist=ModelePDFProduct::liste_modeles($this->db); + include_once DOL_DOCUMENT_ROOT.'/core/modules/usergroup/modules_usergroup.class.php'; + $modellist=ModelePDFUserGroup::liste_modeles($this->db); } } elseif ($modulepart == 'project_task') diff --git a/htdocs/core/lib/usergroups.lib.php b/htdocs/core/lib/usergroups.lib.php index ed95bdc4023..117f7a1e86e 100644 --- a/htdocs/core/lib/usergroups.lib.php +++ b/htdocs/core/lib/usergroups.lib.php @@ -207,6 +207,11 @@ function user_admin_prepare_head() $head[$h][2] = 'card'; $h++; + $head[$h][0] = DOL_URL_ROOT.'/admin/usergroup.php'; + $head[$h][1] = $langs->trans("Group"); + $head[$h][2] = 'usergroupcard'; + $h++; + $head[$h][0] = DOL_URL_ROOT.'/user/admin/user_extrafields.php'; $head[$h][1] = $langs->trans("ExtraFields"); $head[$h][2] = 'attributes'; diff --git a/htdocs/core/modules/user/modules_user.class.php b/htdocs/core/modules/user/modules_user.class.php index 43660da1adb..3e94ece9372 100644 --- a/htdocs/core/modules/user/modules_user.class.php +++ b/htdocs/core/modules/user/modules_user.class.php @@ -55,34 +55,6 @@ abstract class ModelePDFUser extends CommonDocGenerator $type='user'; $liste=array(); - include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; - $liste=getListOfModels($db,$type,$maxfilenamelength); - return $liste; - } -} - -/** - * Parent class to manage intervention document templates - */ -abstract class ModelePDFUserGroup extends CommonDocGenerator -{ - var $error=''; - - - /** - * Return list of active generation modules - * - * @param DoliDB $db Database handler - * @param integer $maxfilenamelength Max length of value to show - * @return array List of templates - */ - static function liste_modeles($db,$maxfilenamelength=0) - { - global $conf; - - $type='usergroup'; - $liste=array(); - include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; $liste=getListOfModels($db,$type,$maxfilenamelength); return $liste; diff --git a/htdocs/core/modules/usergroup/modules_usergroup.class.php b/htdocs/core/modules/usergroup/modules_usergroup.class.php new file mode 100644 index 00000000000..f7d4778efe1 --- /dev/null +++ b/htdocs/core/modules/usergroup/modules_usergroup.class.php @@ -0,0 +1,62 @@ + + * Copyright (C) 2004-2010 Laurent Destailleur + * Copyright (C) 2004 Eric Seigne + * Copyright (C) 2005-2012 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 . + * or see http://www.gnu.org/ + */ + + +/** + * \class ModeleProductCode + * \brief Parent class for product code generators + */ + +/** + * \file htdocs/core/modules/contract/modules_contract.php + * \ingroup contract + * \brief File with parent class for generating contracts to PDF and File of class to manage contract numbering + */ + + require_once DOL_DOCUMENT_ROOT.'/core/class/commondocgenerator.class.php'; + +/** + * Parent class to manage intervention document templates + */ +abstract class ModelePDFUserGroup extends CommonDocGenerator +{ + var $error=''; + + + /** + * Return list of active generation modules + * + * @param DoliDB $db Database handler + * @param integer $maxfilenamelength Max length of value to show + * @return array List of templates + */ + static function liste_modeles($db,$maxfilenamelength=0) + { + global $conf; + + $type='usergroup'; + $liste=array(); + + include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; + $liste=getListOfModels($db,$type,$maxfilenamelength); + return $liste; + } +} \ No newline at end of file diff --git a/htdocs/install/mysql/tables/llx_user.sql b/htdocs/install/mysql/tables/llx_user.sql index e334ae1f802..5530622ac1d 100644 --- a/htdocs/install/mysql/tables/llx_user.sql +++ b/htdocs/install/mysql/tables/llx_user.sql @@ -62,6 +62,7 @@ create table llx_user fk_member integer, fk_user integer, -- Hierarchic parent note text DEFAULT NULL, + model_pdf varchar(255) DEFAULT NULL, datelastlogin datetime, datepreviouslogin datetime, egroupware_id integer, diff --git a/htdocs/install/mysql/tables/llx_usergroup.sql b/htdocs/install/mysql/tables/llx_usergroup.sql index 156c6eda364..9afe72f3b24 100644 --- a/htdocs/install/mysql/tables/llx_usergroup.sql +++ b/htdocs/install/mysql/tables/llx_usergroup.sql @@ -25,7 +25,8 @@ create table llx_usergroup entity integer DEFAULT 1 NOT NULL, -- multi company id datec datetime, tms timestamp, - note text + note text, + model_pdf varchar(255) DEFAULT NULL, )ENGINE=innodb; -- @@ -35,4 +36,4 @@ create table llx_usergroup -- 1 : first company group -- 2 : second company group -- 3 : etc... --- \ No newline at end of file +-- diff --git a/htdocs/user/class/usergroup.class.php b/htdocs/user/class/usergroup.class.php index 50237b0ef61..96f2d3aa7bc 100644 --- a/htdocs/user/class/usergroup.class.php +++ b/htdocs/user/class/usergroup.class.php @@ -818,5 +818,39 @@ class UserGroup extends CommonObject $this->datem=time(); $this->members=array($user->id); // Members of this group is just me } + + /** + * Create a document onto disk according to template module. + * + * @param string $modele Force model to use ('' to not force) + * @param Translate $outputlangs Object langs to use for output + * @param int $hidedetails Hide details of lines + * @param int $hidedesc Hide description + * @param int $hideref Hide ref + * @return int 0 if KO, 1 if OK + */ + public function generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0) + { + global $conf,$user,$langs; + + $langs->load("user"); + + // Positionne le modele sur le nom du modele a utiliser + if (! dol_strlen($modele)) + { + if (! empty($conf->global->USERGROUP_ADDON_PDF)) + { + $modele = $conf->global->USERGROUP_ADDON_PDF; + } + else + { + $modele = 'grass'; + } + } + + $modelpath = "core/modules/usergroup/doc/"; + + return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref); + } } diff --git a/htdocs/user/group/card.php b/htdocs/user/group/card.php index 716db97b911..cc496152f4f 100644 --- a/htdocs/user/group/card.php +++ b/htdocs/user/group/card.php @@ -28,6 +28,7 @@ require '../../main.inc.php'; require_once DOL_DOCUMENT_ROOT.'/user/class/usergroup.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/usergroups.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php'; if(! empty($conf->multicompany->enabled)) dol_include_once('/multicompany/class/actions_multicompany.class.php'); // Defini si peux lire/modifier utilisateurs et permisssions @@ -220,6 +221,8 @@ llxHeader('',$langs->trans("GroupCard")); $form = new Form($db); $fuserstatic = new User($db); +$form = new Form($db); +$formfile = new FormFile($db); if ($action == 'create') { @@ -495,6 +498,42 @@ else } print ""; print "
"; + + // Actions to build doc + $upload_dir = $conf->usergroup->dir_output; + $permissioncreate=$user->rights->user->user->creer; + include DOL_DOCUMENT_ROOT.'/core/actions_builddoc.inc.php'; + + /* + * Documents generes + */ + $filename = dol_sanitizeFileName($object->ref); + $filedir = $conf->usergroup->dir_output . "/" . dol_sanitizeFileName($object->ref); + $urlsource = $_SERVER["PHP_SELF"] . "?id=" . $object->id; + $genallowed = $user->rights->user->user->creer; + $delallowed = $user->rights->user->user->supprimer; + + $var = true; + + $somethingshown = $formfile->show_documents('usergroup', $filename, $filedir, $urlsource, $genallowed, $delallowed, $object->modelpdf, 1, 0, 0, 28, 0, '', 0, '', $soc->default_lang); + + // Linked object block + $somethingshown = $form->showLinkedObjectBlock($object); + + // Show links to link elements + $linktoelem = $form->showLinkToObjectBlock($object); + if ($linktoelem) print '
'.$linktoelem; + + + print '
'; + + // List of actions on element + include_once DOL_DOCUMENT_ROOT . '/core/class/html.formactions.class.php'; + $formactions = new FormActions($db); + $somethingshown = $formactions->showactions($object, 'usergroup', $socid); + + + print '
'; } /* @@ -551,7 +590,6 @@ else print ''; } - } } From 192a825de8548ed7cbbbafbb4cc0f841c60527f7 Mon Sep 17 00:00:00 2001 From: arnaud Date: Tue, 14 Mar 2017 09:25:29 +0100 Subject: [PATCH 5/6] NEW ODT docs for USER USERGROUP CONTRACT and PRODUCT class --- htdocs/core/actions_builddoc.inc.php | 3 +- htdocs/core/class/html.formfile.class.php | 29 +- htdocs/core/lib/functions2.lib.php | 2 +- .../doc/doc_generic_contract_odt.modules.php | 495 +++++++++++++++++ .../doc/doc_generic_product_odt.modules.php | 499 +++++++++++++++++ .../user/doc/doc_generic_user_odt.modules.php | 433 +++++++++++++++ .../doc/doc_generic_usergroup_odt.modules.php | 501 ++++++++++++++++++ .../usergroup/modules_usergroup.class.php | 2 +- .../install/doctemplates/contracts/index.html | 0 .../contracts/template_contract.odt | Bin 0 -> 25866 bytes .../install/doctemplates/products/index.html | 0 .../products/template_product.odt | Bin 0 -> 17591 bytes .../doctemplates/usergroups/index.html | 0 .../usergroups/template_usergroup.odt | Bin 0 -> 17537 bytes htdocs/install/doctemplates/users/index.html | 0 .../doctemplates/users/template_user.odt | Bin 0 -> 13431 bytes .../install/mysql/migration/5.0.0-6.0.0.sql | 8 + htdocs/install/step1.php | 12 +- htdocs/product/admin/product.php | 264 ++++----- htdocs/product/card.php | 24 +- htdocs/user/group/card.php | 10 +- 21 files changed, 2141 insertions(+), 141 deletions(-) create mode 100644 htdocs/core/modules/contract/doc/doc_generic_contract_odt.modules.php create mode 100644 htdocs/core/modules/product/doc/doc_generic_product_odt.modules.php create mode 100644 htdocs/core/modules/user/doc/doc_generic_user_odt.modules.php create mode 100644 htdocs/core/modules/usergroup/doc/doc_generic_usergroup_odt.modules.php create mode 100644 htdocs/install/doctemplates/contracts/index.html create mode 100755 htdocs/install/doctemplates/contracts/template_contract.odt create mode 100644 htdocs/install/doctemplates/products/index.html create mode 100644 htdocs/install/doctemplates/products/template_product.odt create mode 100644 htdocs/install/doctemplates/usergroups/index.html create mode 100755 htdocs/install/doctemplates/usergroups/template_usergroup.odt create mode 100644 htdocs/install/doctemplates/users/index.html create mode 100755 htdocs/install/doctemplates/users/template_user.odt diff --git a/htdocs/core/actions_builddoc.inc.php b/htdocs/core/actions_builddoc.inc.php index adbc17a4161..94f4229e0db 100644 --- a/htdocs/core/actions_builddoc.inc.php +++ b/htdocs/core/actions_builddoc.inc.php @@ -33,13 +33,14 @@ // Build doc if ($action == 'builddoc' && $permissioncreate) { + if (is_numeric(GETPOST('model'))) { $error=$langs->trans("ErrorFieldRequired",$langs->transnoentities("Model")); } else { - // Reload to get all modified line records and be ready for hooks + // Reload to get all modified line records and be ready for hooks $ret = $object->fetch($id); $ret = $object->fetch_thirdparty(); /*if (empty($object->id) || ! $object->id > 0) diff --git a/htdocs/core/class/html.formfile.class.php b/htdocs/core/class/html.formfile.class.php index 44b6a8adcd7..5aa699c055c 100644 --- a/htdocs/core/class/html.formfile.class.php +++ b/htdocs/core/class/html.formfile.class.php @@ -333,7 +333,7 @@ class FormFile $titletoshow=$langs->trans("Documents"); if (! empty($title)) $titletoshow=$title; - + // Show table if ($genallowed) { @@ -439,6 +439,15 @@ class FormFile $modellist=ModelePDFTask::liste_modeles($this->db); } } + elseif ($modulepart == 'product') + { + if (is_array($genallowed)) $modellist=$genallowed; + else + { + include_once DOL_DOCUMENT_ROOT.'/core/modules/product/modules_product.class.php'; + $modellist=ModelePDFProduct::liste_modeles($this->db); + } + } elseif ($modulepart == 'export') { if (is_array($genallowed)) $modellist=$genallowed; @@ -524,6 +533,24 @@ class FormFile { $modellist=''; } + elseif ($modulepart == 'user') + { + if (is_array($genallowed)) $modellist=$genallowed; + else + { + include_once DOL_DOCUMENT_ROOT.'/core/modules/user/modules_user.class.php'; + $modellist=ModelePDFUser::liste_modeles($this->db); + } + } + elseif ($modulepart == 'usergroup') + { + if (is_array($genallowed)) $modellist=$genallowed; + else + { + include_once DOL_DOCUMENT_ROOT.'/core/modules/usergroup/modules_usergroup.class.php'; + $modellist=ModelePDFUserGroup::liste_modeles($this->db); + } + } else //if ($modulepart != 'agenda') { // For normalized standard modules diff --git a/htdocs/core/lib/functions2.lib.php b/htdocs/core/lib/functions2.lib.php index 0a4eb1b0fe1..dc0541e39ef 100644 --- a/htdocs/core/lib/functions2.lib.php +++ b/htdocs/core/lib/functions2.lib.php @@ -1509,7 +1509,7 @@ function getListOfModels($db,$type,$maxfilenamelength=0) $sql.= " WHERE type = '".$type."'"; $sql.= " AND entity IN (0,".(! empty($conf->multicompany->enabled) && ! empty($conf->multicompany->transverse_mode)?"1,":"").$conf->entity.")"; $sql.= " ORDER BY description DESC"; - + dol_syslog('/core/lib/function2.lib.php::getListOfModels', LOG_DEBUG); $resql = $db->query($sql); if ($resql) diff --git a/htdocs/core/modules/contract/doc/doc_generic_contract_odt.modules.php b/htdocs/core/modules/contract/doc/doc_generic_contract_odt.modules.php new file mode 100644 index 00000000000..a58d00455e8 --- /dev/null +++ b/htdocs/core/modules/contract/doc/doc_generic_contract_odt.modules.php @@ -0,0 +1,495 @@ + + * Copyright (C) 2012 Juanjo Menent +* +* 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 . +* or see http://www.gnu.org/ +*/ + +/** + * \file htdocs/core/modules/contract/doc/doc_generic_contract_odt.modules.php + * \ingroup societe + * \brief File of class to build ODT documents for third parties + */ + +require_once DOL_DOCUMENT_ROOT.'/core/modules/contract/modules_contract.php'; +require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/doc.lib.php'; + + +/** + * Class to build documents using ODF templates generator + */ +class doc_generic_contract_odt extends ModelePDFContract +{ + var $emetteur; // Objet societe qui emet + + var $phpmin = array(5,2,0); // Minimum version of PHP required by module + var $version = 'dolibarr'; + + + /** + * Constructor + * + * @param DoliDB $db Database handler + */ + function __construct($db) + { + global $conf,$langs,$mysoc; + + $langs->load("main"); + $langs->load("companies"); + + $this->db = $db; + $this->name = "ODT templates"; + $this->description = $langs->trans("DocumentModelOdt"); + $this->scandir = 'CONTRACT_ADDON_PDF_ODT_PATH'; // Name of constant that is used to save list of directories to scan + + // Dimension page pour format A4 + $this->type = 'odt'; + $this->page_largeur = 0; + $this->page_hauteur = 0; + $this->format = array($this->page_largeur,$this->page_hauteur); + $this->marge_gauche=0; + $this->marge_droite=0; + $this->marge_haute=0; + $this->marge_basse=0; + + $this->option_logo = 1; // Affiche logo + $this->option_tva = 0; // Gere option tva CONTRACT_TVAOPTION + $this->option_modereg = 0; // Affiche mode reglement + $this->option_condreg = 0; // Affiche conditions reglement + $this->option_codeproduitservice = 0; // Affiche code produit-service + $this->option_multilang = 1; // Dispo en plusieurs langues + $this->option_escompte = 0; // Affiche si il y a eu escompte + $this->option_credit_note = 0; // Support credit notes + $this->option_freetext = 1; // Support add of a personalised text + $this->option_draft_watermark = 0; // Support add of a watermark on drafts + + // Recupere emetteur + $this->emetteur=$mysoc; + if (! $this->emetteur->country_code) $this->emetteur->country_code=substr($langs->defaultlang,-2); // By default if not defined + } + + + /** + * Return description of a module + * + * @param Translate $langs Lang object to use for output + * @return string Description + */ + function info($langs) + { + global $conf,$langs; + + $langs->load("companies"); + $langs->load("errors"); + + $form = new Form($this->db); + + $texte = $this->description.".
\n"; + $texte.= '
'; + $texte.= ''; + $texte.= ''; + $texte.= ''; + if ($conf->global->MAIN_PROPAL_CHOOSE_ODT_DOCUMENT > 0) + { + $texte.= ''; + $texte.= ''; + $texte.= ''; + } + $texte.= ''; + + // List of directories area + $texte.= ''; + + $texte.= ''; + $texte.= ''; + + $texte.= '
'; + $texttitle=$langs->trans("ListOfDirectories"); + $listofdir=explode(',',preg_replace('/[\r\n]+/',',',trim($conf->global->CONTRACT_ADDON_PDF_ODT_PATH))); + $listoffiles=array(); + foreach($listofdir as $key=>$tmpdir) + { + $tmpdir=trim($tmpdir); + $tmpdir=preg_replace('/DOL_DATA_ROOT/',DOL_DATA_ROOT,$tmpdir); + if (! $tmpdir) { + unset($listofdir[$key]); continue; + } + if (! is_dir($tmpdir)) $texttitle.=img_warning($langs->trans("ErrorDirNotFound",$tmpdir),0); + else + { + $tmpfiles=dol_dir_list($tmpdir,'files',0,'\.(ods|odt)'); + if (count($tmpfiles)) $listoffiles=array_merge($listoffiles,$tmpfiles); + } + } + $texthelp=$langs->trans("ListOfDirectoriesForModelGenODT"); + // Add list of substitution keys + $texthelp.='
'.$langs->trans("FollowingSubstitutionKeysCanBeUsed").'
'; + $texthelp.=$langs->transnoentitiesnoconv("FullListOnOnlineDocumentation"); // This contains an url, we don't modify it + + $texte.= $form->textwithpicto($texttitle,$texthelp,1,'help','',1); + $texte.= '
'; + $texte.= ''; + $texte.= '
'; + $texte.= ''; + $texte.= '
'; + + // Scan directories + if (count($listofdir)) + { + $texte.=$langs->trans("NumberOfModelFilesFound").': '.count($listoffiles).''; + + if ($conf->global->MAIN_PROPAL_CHOOSE_ODT_DOCUMENT > 0) + { + // Model for creation + $liste=ModelePDFContract::liste_modeles($this->db); + $texte.= ''; + $texte.= ''; + $texte.= ''; + $texte.= '"; + + $texte.= ''; + $texte.= ''; + $texte.= '"; + $texte.= ''; + + $texte.= ''; + $texte.= '"; + $texte.= '
'.$langs->trans("DefaultModelPropalCreate").''; + $texte.= $form->selectarray('value2',$liste,$conf->global->CONTRACT_ADDON_PDF_ODT_DEFAULT); + $texte.= "
'.$langs->trans("DefaultModelPropalToBill").''; + $texte.= $form->selectarray('value3',$liste,$conf->global->CONTRACT_ADDON_PDF_ODT_TOBILL); + $texte.= "
'.$langs->trans("DefaultModelPropalClosed").''; + $texte.= $form->selectarray('value4',$liste,$conf->global->CONTRACT_ADDON_PDF_ODT_CLOSED); + $texte.= "
'; + } + } + + $texte.= '
'; + $texte.= $langs->trans("ExampleOfDirectoriesForModelGen"); + $texte.= '
'; + $texte.= '
'; + + return $texte; + } + + /** + * Function to build a document on disk using the generic odt module. + * + * @param Contract $object Object source to build document + * @param Translate $outputlangs Lang output object + * @param string $srctemplatepath Full path of source filename for generator using a template file + * @param int $hidedetails Do not show line details + * @param int $hidedesc Do not show desc + * @param int $hideref Do not show ref + * @return int 1 if OK, <=0 if KO + */ + function write_file($object,$outputlangs,$srctemplatepath,$hidedetails=0,$hidedesc=0,$hideref=0) + { + global $user,$langs,$conf,$mysoc,$hookmanager; + + if (empty($srctemplatepath)) + { + dol_syslog("doc_generic_odt::write_file parameter srctemplatepath empty", LOG_WARNING); + return -1; + } + + // Add odtgeneration hook + if (! is_object($hookmanager)) + { + include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php'; + $hookmanager=new HookManager($this->db); + } + $hookmanager->initHooks(array('odtgeneration')); + global $action; + + if (! is_object($outputlangs)) $outputlangs=$langs; + $sav_charset_output=$outputlangs->charset_output; + $outputlangs->charset_output='UTF-8'; + + $outputlangs->load("main"); + $outputlangs->load("dict"); + $outputlangs->load("companies"); + $outputlangs->load("bills"); + + if ($conf->contrat->dir_output) + { + // If $object is id instead of object + if (! is_object($object)) + { + $id = $object; + $object = new Contract($this->db); + $result=$object->fetch($id); + if ($result < 0) + { + dol_print_error($this->db,$object->error); + return -1; + } + } + + $dir = $conf->contrat->dir_output; + $objectref = dol_sanitizeFileName($object->ref); + if (! preg_match('/specimen/i',$objectref)) $dir.= "/" . $objectref; + $file = $dir . "/" . $objectref . ".odt"; + + if (! file_exists($dir)) + { + if (dol_mkdir($dir) < 0) + { + $this->error=$langs->transnoentities("ErrorCanNotCreateDir",$dir); + return -1; + } + } + + if (file_exists($dir)) + { + //print "srctemplatepath=".$srctemplatepath; // Src filename + $newfile=basename($srctemplatepath); + $newfiletmp=preg_replace('/\.od(t|s)/i','',$newfile); + $newfiletmp=preg_replace('/template_/i','',$newfiletmp); + $newfiletmp=preg_replace('/modele_/i','',$newfiletmp); + + $newfiletmp=$objectref.'_'.$newfiletmp; + + // Get extension (ods or odt) + $newfileformat=substr($newfile, strrpos($newfile, '.')+1); + if ( ! empty($conf->global->MAIN_DOC_USE_TIMING)) + { + $format=$conf->global->MAIN_DOC_USE_TIMING; + if ($format == '1') $format='%Y%m%d%H%M%S'; + $filename=$newfiletmp.'-'.dol_print_date(dol_now(),$format).'.'.$newfileformat; + } + else + { + $filename=$newfiletmp.'.'.$newfileformat; + } + $file=$dir.'/'.$filename; + //print "newdir=".$dir; + //print "newfile=".$newfile; + //print "file=".$file; + //print "conf->contrat->dir_temp=".$conf->contrat->dir_temp; + + dol_mkdir($conf->contrat->dir_temp); + + + // If CUSTOMER contact defined on contract, we use it + $usecontact=false; + $arrayidcontact=$object->getIdContact('external','CUSTOMER'); + if (count($arrayidcontact) > 0) + { + $usecontact=true; + $result=$object->fetch_contact($arrayidcontact[0]); + } + + // Recipient name + if (! empty($usecontact)) + { + // On peut utiliser le nom de la societe du contact + if (! empty($conf->global->MAIN_USE_COMPANY_NAME_OF_CONTACT)) $socobject = $object->contact; + else { + $socobject = $object->client; + // if we have a CUSTOMER contact and we dont use it as recipient we store the contact object for later use + $contactobject = $object->contact; + } + } + else + { + $socobject=$object->client; + } + // Make substitution + $substitutionarray=array( + '__FROM_NAME__' => $this->emetteur->name, + '__FROM_EMAIL__' => $this->emetteur->email, + '__TOTAL_TTC__' => $object->total_ttc, + '__TOTAL_HT__' => $object->total_ht, + '__TOTAL_VAT__' => $object->total_vat + ); + complete_substitutions_array($substitutionarray, $langs, $object); + // Call the ODTSubstitution hook + $parameters=array('file'=>$file,'object'=>$object,'outputlangs'=>$outputlangs,'substitutionarray'=>&$substitutionarray); + $reshook=$hookmanager->executeHooks('ODTSubstitution',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks + + // Line of free text + $newfreetext=''; + $paramfreetext='contract_FREE_TEXT'; + if (! empty($conf->global->$paramfreetext)) + { + $newfreetext=make_substitutions($conf->global->$paramfreetext,$substitutionarray); + } + + // Open and load template + require_once ODTPHP_PATH.'odf.php'; + try { + $odfHandler = new odf( + $srctemplatepath, + array( + 'PATH_TO_TMP' => $conf->contrat->dir_temp, + 'ZIP_PROXY' => 'PclZipProxy', // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy. + 'DELIMITER_LEFT' => '{', + 'DELIMITER_RIGHT' => '}' + ) + ); + } + catch(Exception $e) + { + $this->error=$e->getMessage(); + return -1; + } + // After construction $odfHandler->contentXml contains content and + // [!-- BEGIN row.lines --]*[!-- END row.lines --] has been replaced by + // [!-- BEGIN lines --]*[!-- END lines --] + //print html_entity_decode($odfHandler->__toString()); + //print exit; + + + // Make substitutions into odt of freetext + try { + $odfHandler->setVars('free_text', $newfreetext, true, 'UTF-8'); + } + catch(OdfException $e) + { + } + + // Make substitutions into odt + $array_contract=$this->get_substitutionarray_each_var_object($object, $outputlangs); + $array_user=$this->get_substitutionarray_user($user,$outputlangs); + $array_soc=$this->get_substitutionarray_mysoc($mysoc,$outputlangs); + $array_thirdparty=$this->get_substitutionarray_thirdparty($socobject,$outputlangs); + $array_objet=$this->get_substitutionarray_object($object,$outputlangs); + $array_other=$this->get_substitutionarray_other($outputlangs); + // retrieve contact information for use in contract as contact_xxx tags + $array_thirdparty_contact = array(); + if ($usecontact) + $array_thirdparty_contact=$this->get_substitutionarray_contact($contactobject,$outputlangs,'contact'); + + $tmparray = array_merge($array_contract,$array_user,$array_soc,$array_thirdparty,$array_objet,$array_other,$array_thirdparty_contact); + complete_substitutions_array($tmparray, $outputlangs, $object); + $object->fetch_optionals(); + // Call the ODTSubstitution hook + $parameters=array('file'=>$file,'object'=>$object,'outputlangs'=>$outputlangs,'substitutionarray'=>&$tmparray); + $reshook=$hookmanager->executeHooks('ODTSubstitution',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks + foreach($tmparray as $key=>$value) + { + try { + if (preg_match('/logo$/',$key)) // Image + { + if (file_exists($value)) $odfHandler->setImage($key, $value); + else $odfHandler->setVars($key, 'ErrorFileNotFound', true, 'UTF-8'); + } + else // Text + { + $odfHandler->setVars($key, $value, true, 'UTF-8'); + } + } + catch(OdfException $e) + { + } + } + // Replace tags of lines + try + { + $listlines = $odfHandler->setSegment('lines'); + foreach ($object->lines as $line) + { + $tmparray=$this->get_substitutionarray_lines($line,$outputlangs); + complete_substitutions_array($tmparray, $outputlangs, $object, $line, "completesubstitutionarray_lines"); + // Call the ODTSubstitutionLine hook + $parameters=array('odfHandler'=>&$odfHandler,'file'=>$file,'object'=>$object,'outputlangs'=>$outputlangs,'substitutionarray'=>&$tmparray,'line'=>$line); + $reshook=$hookmanager->executeHooks('ODTSubstitutionLine',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks + foreach($tmparray as $key => $val) + { + try + { + $listlines->setVars($key, $val, true, 'UTF-8'); + } + catch(OdfException $e) + { + } + catch(SegmentException $e) + { + } + } + $listlines->merge(); + } + $odfHandler->mergeSegment($listlines); + } + catch(OdfException $e) + { + $this->error=$e->getMessage(); + dol_syslog($this->error, LOG_WARNING); + return -1; + } + + // Replace labels translated + $tmparray=$outputlangs->get_translations_for_substitutions(); + foreach($tmparray as $key=>$value) + { + try { + $odfHandler->setVars($key, $value, true, 'UTF-8'); + } + catch(OdfException $e) + { + } + } + + // Call the beforeODTSave hook + $parameters=array('odfHandler'=>&$odfHandler,'file'=>$file,'object'=>$object,'outputlangs'=>$outputlangs); + $reshook=$hookmanager->executeHooks('beforeODTSave',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks + + // Write new file + if (!empty($conf->global->MAIN_ODT_AS_PDF)) { + try { + $odfHandler->exportAsAttachedPDF($file); + }catch (Exception $e){ + $this->error=$e->getMessage(); + return -1; + } + } + else { + try { + $odfHandler->saveToDisk($file); + }catch (Exception $e){ + $this->error=$e->getMessage(); + return -1; + } + } + + $reshook=$hookmanager->executeHooks('afterODTCreation',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks + + if (! empty($conf->global->MAIN_UMASK)) + @chmod($file, octdec($conf->global->MAIN_UMASK)); + + $odfHandler=null; // Destroy object + + return 1; // Success + } + else + { + $this->error=$langs->transnoentities("ErrorCanNotCreateDir",$dir); + return -1; + } + } + + return -1; + } + +} + diff --git a/htdocs/core/modules/product/doc/doc_generic_product_odt.modules.php b/htdocs/core/modules/product/doc/doc_generic_product_odt.modules.php new file mode 100644 index 00000000000..3341ad20f08 --- /dev/null +++ b/htdocs/core/modules/product/doc/doc_generic_product_odt.modules.php @@ -0,0 +1,499 @@ + + * Copyright (C) 2012 Juanjo Menent +* +* 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 . +* or see http://www.gnu.org/ +*/ + +/** + * \file htdocs/core/modules/product/doc/doc_generic_product_odt.modules.php + * \ingroup societe + * \brief File of class to build ODT documents for third parties + */ + +require_once DOL_DOCUMENT_ROOT.'/core/modules/product/modules_product.class.php'; +require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/doc.lib.php'; + + +/** + * Class to build documents using ODF templates generator + */ +class doc_generic_product_odt extends ModelePDFProduct +{ + var $emetteur; // Objet societe qui emet + + var $phpmin = array(5,2,0); // Minimum version of PHP required by module + var $version = 'dolibarr'; + + + /** + * Constructor + * + * @param DoliDB $db Database handler + */ + function __construct($db) + { + global $conf,$langs,$mysoc; + + $langs->load("main"); + $langs->load("companies"); + + $this->db = $db; + $this->name = "ODT templates"; + $this->description = $langs->trans("DocumentModelOdt"); + $this->scandir = 'PRODUCT_ADDON_PDF_ODT_PATH'; // Name of constant that is used to save list of directories to scan + + // Dimension page pour format A4 + $this->type = 'odt'; + $this->page_largeur = 0; + $this->page_hauteur = 0; + $this->format = array($this->page_largeur,$this->page_hauteur); + $this->marge_gauche=0; + $this->marge_droite=0; + $this->marge_haute=0; + $this->marge_basse=0; + + $this->option_logo = 1; // Affiche logo + $this->option_tva = 0; // Gere option tva PRODUCT_TVAOPTION + $this->option_modereg = 0; // Affiche mode reglement + $this->option_condreg = 0; // Affiche conditions reglement + $this->option_codeproduitservice = 0; // Affiche code produit-service + $this->option_multilang = 1; // Dispo en plusieurs langues + $this->option_escompte = 0; // Affiche si il y a eu escompte + $this->option_credit_note = 0; // Support credit notes + $this->option_freetext = 1; // Support add of a personalised text + $this->option_draft_watermark = 0; // Support add of a watermark on drafts + + // Recupere emetteur + $this->emetteur=$mysoc; + if (! $this->emetteur->country_code) $this->emetteur->country_code=substr($langs->defaultlang,-2); // By default if not defined + } + + + /** + * Return description of a module + * + * @param Translate $langs Lang object to use for output + * @return string Description + */ + function info($langs) + { + global $conf,$langs; + + $langs->load("companies"); + $langs->load("errors"); + + $form = new Form($this->db); + + $texte = $this->description.".
\n"; + $texte.= '
'; + $texte.= ''; + $texte.= ''; + $texte.= ''; + if ($conf->global->MAIN_PROPAL_CHOOSE_ODT_DOCUMENT > 0) + { + $texte.= ''; + $texte.= ''; + $texte.= ''; + } + $texte.= ''; + + // List of directories area + $texte.= ''; + + $texte.= ''; + $texte.= ''; + + $texte.= '
'; + $texttitle=$langs->trans("ListOfDirectories"); + $listofdir=explode(',',preg_replace('/[\r\n]+/',',',trim($conf->global->PRODUCT_ADDON_PDF_ODT_PATH))); + $listoffiles=array(); + foreach($listofdir as $key=>$tmpdir) + { + $tmpdir=trim($tmpdir); + $tmpdir=preg_replace('/DOL_DATA_ROOT/',DOL_DATA_ROOT,$tmpdir); + if (! $tmpdir) { + unset($listofdir[$key]); continue; + } + if (! is_dir($tmpdir)) $texttitle.=img_warning($langs->trans("ErrorDirNotFound",$tmpdir),0); + else + { + $tmpfiles=dol_dir_list($tmpdir,'files',0,'\.(ods|odt)'); + if (count($tmpfiles)) $listoffiles=array_merge($listoffiles,$tmpfiles); + } + } + $texthelp=$langs->trans("ListOfDirectoriesForModelGenODT"); + // Add list of substitution keys + $texthelp.='
'.$langs->trans("FollowingSubstitutionKeysCanBeUsed").'
'; + $texthelp.=$langs->transnoentitiesnoconv("FullListOnOnlineDocumentation"); // This contains an url, we don't modify it + + $texte.= $form->textwithpicto($texttitle,$texthelp,1,'help','',1); + $texte.= '
'; + $texte.= ''; + $texte.= '
'; + $texte.= ''; + $texte.= '
'; + + // Scan directories + if (count($listofdir)) + { + $texte.=$langs->trans("NumberOfModelFilesFound").': '.count($listoffiles).''; + + /*if ($conf->global->MAIN_PRODUCT_CHOOSE_ODT_DOCUMENT > 0) + { + // Model for creation + $liste=ModelePDFProduct::liste_modeles($this->db); + $texte.= ''; + $texte.= ''; + $texte.= ''; + $texte.= '"; + + $texte.= ''; + $texte.= ''; + $texte.= '"; + $texte.= ''; + + $texte.= ''; + $texte.= '"; + $texte.= '
'.$langs->trans("DefaultModelPropalCreate").''; + $texte.= $form->selectarray('value2',$liste,$conf->global->PRODUCT_ADDON_PDF_ODT_DEFAULT); + $texte.= "
'.$langs->trans("DefaultModelPropalToBill").''; + $texte.= $form->selectarray('value3',$liste,$conf->global->PRODUCT_ADDON_PDF_ODT_TOBILL); + $texte.= "
'.$langs->trans("DefaultModelPropalClosed").''; + $texte.= $form->selectarray('value4',$liste,$conf->global->PRODUCT_ADDON_PDF_ODT_CLOSED); + $texte.= "
'; + }*/ + } + + $texte.= '
'; + $texte.= $langs->trans("ExampleOfDirectoriesForModelGen"); + $texte.= '
'; + $texte.= '
'; + + return $texte; + } + + /** + * Function to build a document on disk using the generic odt module. + * + * @param Product $object Object source to build document + * @param Translate $outputlangs Lang output object + * @param string $srctemplatepath Full path of source filename for generator using a template file + * @param int $hidedetails Do not show line details + * @param int $hidedesc Do not show desc + * @param int $hideref Do not show ref + * @return int 1 if OK, <=0 if KO + */ + function write_file($object,$outputlangs,$srctemplatepath,$hidedetails=0,$hidedesc=0,$hideref=0) + { + global $product,$langs,$conf,$mysoc,$hookmanager,$user; + + if (empty($srctemplatepath)) + { + dol_syslog("doc_generic_odt::write_file parameter srctemplatepath empty", LOG_WARNING); + return -1; + } + + // Add odtgeneration hook + if (! is_object($hookmanager)) + { + include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php'; + $hookmanager=new HookManager($this->db); + } + $hookmanager->initHooks(array('odtgeneration')); + global $action; + + if (! is_object($outputlangs)) $outputlangs=$langs; + $sav_charset_output=$outputlangs->charset_output; + $outputlangs->charset_output='UTF-8'; + + $outputlangs->load("main"); + $outputlangs->load("dict"); + $outputlangs->load("companies"); + $outputlangs->load("bills"); + if ($conf->produit->dir_output) + { + // If $object is id instead of object + if (! is_object($object)) + { + $id = $object; + $object = new Product($this->db); + $result=$object->fetch($id); + if ($result < 0) + { + dol_print_error($this->db,$object->error); + return -1; + } + } + $productFournisseur = new ProductFournisseur($this->db); + $supplierprices = $productFournisseur->list_product_fournisseur_price($object->id); + $object->supplierprices = $supplierprices; + + $dir = $conf->produit->dir_output; + $objectref = dol_sanitizeFileName($object->ref); + if (! preg_match('/specimen/i',$objectref)) $dir.= "/" . $objectref; + $file = $dir . "/" . $objectref . ".odt"; + + if (! file_exists($dir)) + { + if (dol_mkdir($dir) < 0) + { + $this->error=$langs->transnoentities("ErrorCanNotCreateDir",$dir); + return -1; + } + } + + if (file_exists($dir)) + { + //print "srctemplatepath=".$srctemplatepath; // Src filename + $newfile=basename($srctemplatepath); + $newfiletmp=preg_replace('/\.od(t|s)/i','',$newfile); + $newfiletmp=preg_replace('/template_/i','',$newfiletmp); + $newfiletmp=preg_replace('/modele_/i','',$newfiletmp); + + $newfiletmp=$objectref.'_'.$newfiletmp; + + // Get extension (ods or odt) + $newfileformat=substr($newfile, strrpos($newfile, '.')+1); + if ( ! empty($conf->global->MAIN_DOC_USE_TIMING)) + { + $format=$conf->global->MAIN_DOC_USE_TIMING; + if ($format == '1') $format='%Y%m%d%H%M%S'; + $filename=$newfiletmp.'-'.dol_print_date(dol_now(),$format).'.'.$newfileformat; + } + else + { + $filename=$newfiletmp.'.'.$newfileformat; + } + $file=$dir.'/'.$filename; + //print "newdir=".$dir; + //print "newfile=".$newfile; + //print "file=".$file; + //print "conf->produit->dir_temp=".$conf->produit->dir_temp; + + dol_mkdir($conf->produit->dir_temp); + + + // If CUSTOMER contact defined on product, we use it + $usecontact=false; + $arrayidcontact=$object->getIdContact('external','CUSTOMER'); + if (count($arrayidcontact) > 0) + { + $usecontact=true; + $result=$object->fetch_contact($arrayidcontact[0]); + } + + // Recipient name + if (! empty($usecontact)) + { + // On peut utiliser le nom de la societe du contact + if (! empty($conf->global->MAIN_USE_COMPANY_NAME_OF_CONTACT)) $socobject = $object->contact; + else { + $socobject = $object->client; + // if we have a CUSTOMER contact and we dont use it as recipient we store the contact object for later use + $contactobject = $object->contact; + } + } + else + { + $socobject=$object->client; + } + // Make substitution + $substitutionarray=array( + '__FROM_NAME__' => $this->emetteur->name, + '__FROM_EMAIL__' => $this->emetteur->email, + '__TOTAL_TTC__' => $object->total_ttc, + '__TOTAL_HT__' => $object->total_ht, + '__TOTAL_VAT__' => $object->total_vat + ); + complete_substitutions_array($substitutionarray, $langs, $object); + // Call the ODTSubstitution hook + $parameters=array('file'=>$file,'object'=>$object,'outputlangs'=>$outputlangs,'substitutionarray'=>&$substitutionarray); + $reshook=$hookmanager->executeHooks('ODTSubstitution',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks + + // Line of free text + $newfreetext=''; + $paramfreetext='product_FREE_TEXT'; + if (! empty($conf->global->$paramfreetext)) + { + $newfreetext=make_substitutions($conf->global->$paramfreetext,$substitutionarray); + } + + // Open and load template + require_once ODTPHP_PATH.'odf.php'; + try { + $odfHandler = new odf( + $srctemplatepath, + array( + 'PATH_TO_TMP' => $conf->produit->dir_temp, + 'ZIP_PROXY' => 'PclZipProxy', // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy. + 'DELIMITER_LEFT' => '{', + 'DELIMITER_RIGHT' => '}' + ) + ); + } + catch(Exception $e) + { + $this->error=$e->getMessage(); + return -1; + } + // After construction $odfHandler->contentXml contains content and + // [!-- BEGIN row.lines --]*[!-- END row.lines --] has been replaced by + // [!-- BEGIN lines --]*[!-- END lines --] + //print html_entity_decode($odfHandler->__toString()); + //print exit; + + + // Make substitutions into odt of freetext + try { + $odfHandler->setVars('free_text', $newfreetext, true, 'UTF-8'); + } + catch(OdfException $e) + { + } + + // Make substitutions into odt + $array_global = $this->get_substitutionarray_each_var_object($object, $outputlangs); + $array_user=$this->get_substitutionarray_user($user,$outputlangs); + $array_soc=$this->get_substitutionarray_mysoc($mysoc,$outputlangs); + $array_thirdparty=$this->get_substitutionarray_thirdparty($socobject,$outputlangs); + //$array_objet=$this->get_substitutionarray_object($object,$outputlangs); + $array_other=$this->get_substitutionarray_other($outputlangs); + // retrieve contact information for use in product as contact_xxx tags + $array_thirdparty_contact = array(); + if ($usecontact) + $array_thirdparty_contact=$this->get_substitutionarray_contact($contactobject,$outputlangs,'contact'); + + $tmparray = array_merge($array_global,$array_user,$array_soc,$array_thirdparty,$array_other,$array_thirdparty_contact); + complete_substitutions_array($tmparray, $outputlangs, $object); + $object->fetch_optionals(); + // Call the ODTSubstitution hook + $parameters=array('file'=>$file,'object'=>$object,'outputlangs'=>$outputlangs,'substitutionarray'=>&$tmparray); + $reshook=$hookmanager->executeHooks('ODTSubstitution',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks + foreach($tmparray as $key=>$value) + { + try { + if (preg_match('/logo$/',$key)) // Image + { + if (file_exists($value)) $odfHandler->setImage($key, $value); + else $odfHandler->setVars($key, 'ErrorFileNotFound', true, 'UTF-8'); + } + else // Text + { + $odfHandler->setVars($key, $value, true, 'UTF-8'); + } + } + catch(OdfException $e) + { + } + } + // Replace tags of lines + try + { + $listlines = $odfHandler->setSegment('supplierprices'); + if(!empty($object->supplierprices)){ + foreach ($object->supplierprices as $supplierprice) + { + $array_lines = $this->get_substitutionarray_each_var_object($supplierprice, $outputlangs); + complete_substitutions_array($array_lines, $outputlangs, $object, $supplierprice, "completesubstitutionarray_lines"); + // Call the ODTSubstitutionLine hook + $parameters=array('odfHandler'=>&$odfHandler,'file'=>$file,'object'=>$object,'outputlangs'=>$outputlangs,'substitutionarray'=>&$array_lines,'line'=>$supplierprice); + $reshook=$hookmanager->executeHooks('ODTSubstitutionLine',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks + foreach($array_lines as $key => $val) + { + try + { + $listlines->setVars($key, $val, true, 'UTF-8'); + } + catch(OdfException $e) + { + } + catch(SegmentException $e) + { + } + } + $listlines->merge(); + } + } + $odfHandler->mergeSegment($listlines); + } + catch(OdfException $e) + { + $this->error=$e->getMessage(); + dol_syslog($this->error, LOG_WARNING); + return -1; + } + + // Replace labels translated + $tmparray=$outputlangs->get_translations_for_substitutions(); + foreach($tmparray as $key=>$value) + { + try { + $odfHandler->setVars($key, $value, true, 'UTF-8'); + } + catch(OdfException $e) + { + } + } + + // Call the beforeODTSave hook + $parameters=array('odfHandler'=>&$odfHandler,'file'=>$file,'object'=>$object,'outputlangs'=>$outputlangs); + $reshook=$hookmanager->executeHooks('beforeODTSave',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks + + // Write new file + if (!empty($conf->global->MAIN_ODT_AS_PDF)) { + try { + $odfHandler->exportAsAttachedPDF($file); + }catch (Exception $e){ + $this->error=$e->getMessage(); + return -1; + } + } + else { + try { + $odfHandler->saveToDisk($file); + }catch (Exception $e){ + $this->error=$e->getMessage(); + return -1; + } + } + + $reshook=$hookmanager->executeHooks('afterODTCreation',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks + + if (! empty($conf->global->MAIN_UMASK)) + @chmod($file, octdec($conf->global->MAIN_UMASK)); + + $odfHandler=null; // Destroy object + + return 1; // Success + } + else + { + $this->error=$langs->transnoentities("ErrorCanNotCreateDir",$dir); + return -1; + } + } + + return -1; + } + +} + diff --git a/htdocs/core/modules/user/doc/doc_generic_user_odt.modules.php b/htdocs/core/modules/user/doc/doc_generic_user_odt.modules.php new file mode 100644 index 00000000000..b7079dde30e --- /dev/null +++ b/htdocs/core/modules/user/doc/doc_generic_user_odt.modules.php @@ -0,0 +1,433 @@ + + * Copyright (C) 2012 Juanjo Menent +* +* 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 . +* or see http://www.gnu.org/ +*/ + +/** + * \file htdocs/core/modules/user/doc/doc_generic_user_odt.modules.php + * \ingroup societe + * \brief File of class to build ODT documents for third parties + */ + +require_once DOL_DOCUMENT_ROOT.'/core/modules/user/modules_user.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/doc.lib.php'; + + +/** + * Class to build documents using ODF templates generator + */ +class doc_generic_user_odt extends ModelePDFUser +{ + var $emetteur; // Objet societe qui emet + + var $phpmin = array(5,2,0); // Minimum version of PHP required by module + var $version = 'dolibarr'; + + + /** + * Constructor + * + * @param DoliDB $db Database handler + */ + function __construct($db) + { + global $conf,$langs,$mysoc; + + $langs->load("main"); + $langs->load("companies"); + + $this->db = $db; + $this->name = "ODT templates"; + $this->description = $langs->trans("DocumentModelOdt"); + $this->scandir = 'USER_ADDON_PDF_ODT_PATH'; // Name of constant that is used to save list of directories to scan + + // Dimension page pour format A4 + $this->type = 'odt'; + $this->page_largeur = 0; + $this->page_hauteur = 0; + $this->format = array($this->page_largeur,$this->page_hauteur); + $this->marge_gauche=0; + $this->marge_droite=0; + $this->marge_haute=0; + $this->marge_basse=0; + + $this->option_logo = 1; // Affiche logo + $this->option_tva = 0; // Gere option tva USER_TVAOPTION + $this->option_modereg = 0; // Affiche mode reglement + $this->option_condreg = 0; // Affiche conditions reglement + $this->option_codeproduitservice = 0; // Affiche code produit-service + $this->option_multilang = 1; // Dispo en plusieurs langues + $this->option_escompte = 0; // Affiche si il y a eu escompte + $this->option_credit_note = 0; // Support credit notes + $this->option_freetext = 1; // Support add of a personalised text + $this->option_draft_watermark = 0; // Support add of a watermark on drafts + + // Recupere emetteur + $this->emetteur=$mysoc; + if (! $this->emetteur->country_code) $this->emetteur->country_code=substr($langs->defaultlang,-2); // By default if not defined + } + + + /** + * Return description of a module + * + * @param Translate $langs Lang object to use for output + * @return string Description + */ + function info($langs) + { + global $conf,$langs; + + $langs->load("companies"); + $langs->load("errors"); + + $form = new Form($this->db); + + $texte = $this->description.".
\n"; + $texte.= '
'; + $texte.= ''; + $texte.= ''; + $texte.= ''; + if ($conf->global->MAIN_PROPAL_CHOOSE_ODT_DOCUMENT > 0) + { + $texte.= ''; + $texte.= ''; + $texte.= ''; + } + $texte.= ''; + + // List of directories area + $texte.= ''; + + $texte.= ''; + $texte.= ''; + + $texte.= '
'; + $texttitle=$langs->trans("ListOfDirectories"); + $listofdir=explode(',',preg_replace('/[\r\n]+/',',',trim($conf->global->USER_ADDON_PDF_ODT_PATH))); + $listoffiles=array(); + foreach($listofdir as $key=>$tmpdir) + { + $tmpdir=trim($tmpdir); + $tmpdir=preg_replace('/DOL_DATA_ROOT/',DOL_DATA_ROOT,$tmpdir); + if (! $tmpdir) { + unset($listofdir[$key]); continue; + } + if (! is_dir($tmpdir)) $texttitle.=img_warning($langs->trans("ErrorDirNotFound",$tmpdir),0); + else + { + $tmpfiles=dol_dir_list($tmpdir,'files',0,'\.(ods|odt)'); + if (count($tmpfiles)) $listoffiles=array_merge($listoffiles,$tmpfiles); + } + } + $texthelp=$langs->trans("ListOfDirectoriesForModelGenODT"); + // Add list of substitution keys + $texthelp.='
'.$langs->trans("FollowingSubstitutionKeysCanBeUsed").'
'; + $texthelp.=$langs->transnoentitiesnoconv("FullListOnOnlineDocumentation"); // This contains an url, we don't modify it + + $texte.= $form->textwithpicto($texttitle,$texthelp,1,'help','',1); + $texte.= '
'; + $texte.= ''; + $texte.= '
'; + $texte.= ''; + $texte.= '
'; + + // Scan directories + if (count($listofdir)) + { + $texte.=$langs->trans("NumberOfModelFilesFound").': '.count($listoffiles).''; + + if ($conf->global->MAIN_PROPAL_CHOOSE_ODT_DOCUMENT > 0) + { + // Model for creation + $liste=ModelePDFUser::liste_modeles($this->db); + $texte.= ''; + $texte.= ''; + $texte.= ''; + $texte.= '"; + + $texte.= ''; + $texte.= ''; + $texte.= '"; + $texte.= ''; + + $texte.= ''; + $texte.= '"; + $texte.= '
'.$langs->trans("DefaultModelPropalCreate").''; + $texte.= $form->selectarray('value2',$liste,$conf->global->USER_ADDON_PDF_ODT_DEFAULT); + $texte.= "
'.$langs->trans("DefaultModelPropalToBill").''; + $texte.= $form->selectarray('value3',$liste,$conf->global->USER_ADDON_PDF_ODT_TOBILL); + $texte.= "
'.$langs->trans("DefaultModelPropalClosed").''; + $texte.= $form->selectarray('value4',$liste,$conf->global->USER_ADDON_PDF_ODT_CLOSED); + $texte.= "
'; + } + } + + $texte.= '
'; + $texte.= $langs->trans("ExampleOfDirectoriesForModelGen"); + $texte.= '
'; + $texte.= '
'; + + return $texte; + } + + /** + * Function to build a document on disk using the generic odt module. + * + * @param User $object Object source to build document + * @param Translate $outputlangs Lang output object + * @param string $srctemplatepath Full path of source filename for generator using a template file + * @param int $hidedetails Do not show line details + * @param int $hidedesc Do not show desc + * @param int $hideref Do not show ref + * @return int 1 if OK, <=0 if KO + */ + function write_file($object,$outputlangs,$srctemplatepath,$hidedetails=0,$hidedesc=0,$hideref=0) + { + global $user,$langs,$conf,$mysoc,$hookmanager; + + if (empty($srctemplatepath)) + { + dol_syslog("doc_generic_odt::write_file parameter srctemplatepath empty", LOG_WARNING); + return -1; + } + + // Add odtgeneration hook + if (! is_object($hookmanager)) + { + include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php'; + $hookmanager=new HookManager($this->db); + } + $hookmanager->initHooks(array('odtgeneration')); + global $action; + + if (! is_object($outputlangs)) $outputlangs=$langs; + $sav_charset_output=$outputlangs->charset_output; + $outputlangs->charset_output='UTF-8'; + + $outputlangs->load("main"); + $outputlangs->load("dict"); + $outputlangs->load("companies"); + $outputlangs->load("bills"); + + if ($conf->user->dir_output) + { + // If $object is id instead of object + if (! is_object($object)) + { + $id = $object; + $object = new User($this->db); + $result=$object->fetch($id); + if ($result < 0) + { + dol_print_error($this->db,$object->error); + return -1; + } + } + + $dir = $conf->user->dir_output; + $objectref = dol_sanitizeFileName($object->ref); + if (! preg_match('/specimen/i',$objectref)) $dir.= "/" . $objectref; + $file = $dir . "/" . $objectref . ".odt"; + + if (! file_exists($dir)) + { + if (dol_mkdir($dir) < 0) + { + $this->error=$langs->transnoentities("ErrorCanNotCreateDir",$dir); + return -1; + } + } + + if (file_exists($dir)) + { + //print "srctemplatepath=".$srctemplatepath; // Src filename + $newfile=basename($srctemplatepath); + $newfiletmp=preg_replace('/\.od(t|s)/i','',$newfile); + $newfiletmp=preg_replace('/template_/i','',$newfiletmp); + $newfiletmp=preg_replace('/modele_/i','',$newfiletmp); + + $newfiletmp=$objectref.'_'.$newfiletmp; + + // Get extension (ods or odt) + $newfileformat=substr($newfile, strrpos($newfile, '.')+1); + if ( ! empty($conf->global->MAIN_DOC_USE_TIMING)) + { + $format=$conf->global->MAIN_DOC_USE_TIMING; + if ($format == '1') $format='%Y%m%d%H%M%S'; + $filename=$newfiletmp.'-'.dol_print_date(dol_now(),$format).'.'.$newfileformat; + } + else + { + $filename=$newfiletmp.'.'.$newfileformat; + } + $file=$dir.'/'.$filename; + //print "newdir=".$dir; + //print "newfile=".$newfile; + //print "file=".$file; + //print "conf->user->dir_temp=".$conf->user->dir_temp; + + dol_mkdir($conf->user->dir_temp); + + + // If CUSTOMER contact defined on user, we use it + $usecontact=false; + $arrayidcontact=$object->getIdContact('external','CUSTOMER'); + if (count($arrayidcontact) > 0) + { + $usecontact=true; + $result=$object->fetch_contact($arrayidcontact[0]); + } + + // Recipient name + if (! empty($usecontact)) + { + // On peut utiliser le nom de la societe du contact + if (! empty($conf->global->MAIN_USE_COMPANY_NAME_OF_CONTACT)) $socobject = $object->contact; + else { + $socobject = $object->client; + // if we have a CUSTOMER contact and we dont use it as recipient we store the contact object for later use + $contactobject = $object->contact; + } + } + else + { + $socobject=$object->client; + } + + // Open and load template + require_once ODTPHP_PATH.'odf.php'; + try { + $odfHandler = new odf( + $srctemplatepath, + array( + 'PATH_TO_TMP' => $conf->user->dir_temp, + 'ZIP_PROXY' => 'PclZipProxy', // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy. + 'DELIMITER_LEFT' => '{', + 'DELIMITER_RIGHT' => '}' + ) + ); + } + catch(Exception $e) + { + $this->error=$e->getMessage(); + return -1; + } + + // Make substitutions into odt + $array_user=$this->get_substitutionarray_user($object,$outputlangs); + $array_soc=$this->get_substitutionarray_mysoc($mysoc,$outputlangs); + $array_thirdparty=$this->get_substitutionarray_thirdparty($socobject,$outputlangs); + $array_other=$this->get_substitutionarray_other($outputlangs); + // retrieve contact information for use in user as contact_xxx tags + $array_thirdparty_contact = array(); + if ($usecontact) + $array_thirdparty_contact=$this->get_substitutionarray_contact($contactobject,$outputlangs,'contact'); + + $tmparray = array_merge($array_user,$array_soc,$array_thirdparty,$array_other,$array_thirdparty_contact); + complete_substitutions_array($tmparray, $outputlangs, $object); + $object->fetch_optionals(); + // Call the ODTSubstitution hook + $parameters=array('file'=>$file,'object'=>$object,'outputlangs'=>$outputlangs,'substitutionarray'=>&$tmparray); + $reshook=$hookmanager->executeHooks('ODTSubstitution',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks + foreach($tmparray as $key=>$value) + { + try { + if (preg_match('/logo$/',$key)) // Image + { + if (file_exists($value)) $odfHandler->setImage($key, $value); + else $odfHandler->setVars($key, 'ErrorFileNotFound', true, 'UTF-8'); + } + else // Text + { + $odfHandler->setVars($key, $value, true, 'UTF-8'); + } + } + catch(OdfException $e) + { + } + } + + // Replace labels translated + $tmparray=$outputlangs->get_translations_for_substitutions(); + foreach($tmparray as $key=>$value) + { + try { + $odfHandler->setVars($key, $value, true, 'UTF-8'); + } + catch(OdfException $e) + { + } + } + + // Call the beforeODTSave hook + $parameters=array('odfHandler'=>&$odfHandler,'file'=>$file,'object'=>$object,'outputlangs'=>$outputlangs); + $reshook=$hookmanager->executeHooks('beforeODTSave',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks + + // Write new file + if (!empty($conf->global->MAIN_ODT_AS_PDF)) { + try { + $odfHandler->exportAsAttachedPDF($file); + }catch (Exception $e){ + $this->error=$e->getMessage(); + return -1; + } + } + else { + try { + $odfHandler->saveToDisk($file); + }catch (Exception $e){ + $this->error=$e->getMessage(); + return -1; + } + } + + $reshook=$hookmanager->executeHooks('afterODTCreation',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks + + if (! empty($conf->global->MAIN_UMASK)) + @chmod($file, octdec($conf->global->MAIN_UMASK)); + + $odfHandler=null; // Destroy object + + return 1; // Success + } + else + { + $this->error=$langs->transnoentities("ErrorCanNotCreateDir",$dir); + return -1; + } + } + + return -1; + } + + function get_substitutionarray_object($object,$outputlangs) { + foreach($object as $key => $value) { + if(!is_array($value) && !is_object($value)) { + $array_other['object_'.$key] = $value; + } + } + return $array_other; + } + +} + diff --git a/htdocs/core/modules/usergroup/doc/doc_generic_usergroup_odt.modules.php b/htdocs/core/modules/usergroup/doc/doc_generic_usergroup_odt.modules.php new file mode 100644 index 00000000000..35367052796 --- /dev/null +++ b/htdocs/core/modules/usergroup/doc/doc_generic_usergroup_odt.modules.php @@ -0,0 +1,501 @@ + + * Copyright (C) 2012 Juanjo Menent +* +* 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 . +* or see http://www.gnu.org/ +*/ + +/** + * \file htdocs/core/modules/user/doc/doc_generic_user_odt.modules.php + * \ingroup societe + * \brief File of class to build ODT documents for third parties + */ + +require_once DOL_DOCUMENT_ROOT.'/core/modules/usergroup/modules_usergroup.class.php'; +require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php'; +require_once DOL_DOCUMENT_ROOT.'/user/class/usergroup.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/doc.lib.php'; + + +/** + * Class to build documents using ODF templates generator + */ +class doc_generic_usergroup_odt extends ModelePDFUserGroup +{ + var $emetteur; // Objet societe qui emet + + var $phpmin = array(5,2,0); // Minimum version of PHP required by module + var $version = 'dolibarr'; + + + /** + * Constructor + * + * @param DoliDB $db Database handler + */ + function __construct($db) + { + global $conf,$langs,$mysoc; + + $langs->load("main"); + $langs->load("companies"); + + $this->db = $db; + $this->name = "ODT templates"; + $this->description = $langs->trans("DocumentModelOdt"); + $this->scandir = 'USERGROUP_ADDON_PDF_ODT_PATH'; // Name of constant that is used to save list of directories to scan + + // Dimension page pour format A4 + $this->type = 'odt'; + $this->page_largeur = 0; + $this->page_hauteur = 0; + $this->format = array($this->page_largeur,$this->page_hauteur); + $this->marge_gauche=0; + $this->marge_droite=0; + $this->marge_haute=0; + $this->marge_basse=0; + + $this->option_logo = 1; // Affiche logo + $this->option_tva = 0; // Gere option tva USERGROUP_TVAOPTION + $this->option_modereg = 0; // Affiche mode reglement + $this->option_condreg = 0; // Affiche conditions reglement + $this->option_codeproduitservice = 0; // Affiche code produit-service + $this->option_multilang = 1; // Dispo en plusieurs langues + $this->option_escompte = 0; // Affiche si il y a eu escompte + $this->option_credit_note = 0; // Support credit notes + $this->option_freetext = 1; // Support add of a personalised text + $this->option_draft_watermark = 0; // Support add of a watermark on drafts + + // Recupere emetteur + $this->emetteur=$mysoc; + if (! $this->emetteur->country_code) $this->emetteur->country_code=substr($langs->defaultlang,-2); // By default if not defined + } + + + /** + * Return description of a module + * + * @param Translate $langs Lang object to use for output + * @return string Description + */ + function info($langs) + { + global $conf,$langs; + + $langs->load("companies"); + $langs->load("errors"); + + $form = new Form($this->db); + + $texte = $this->description.".
\n"; + $texte.= '
'; + $texte.= ''; + $texte.= ''; + $texte.= ''; + if ($conf->global->MAIN_PROPAL_CHOOSE_ODT_DOCUMENT > 0) + { + $texte.= ''; + $texte.= ''; + $texte.= ''; + } + $texte.= ''; + + // List of directories area + $texte.= ''; + + $texte.= ''; + $texte.= ''; + + $texte.= '
'; + $texttitle=$langs->trans("ListOfDirectories"); + $listofdir=explode(',',preg_replace('/[\r\n]+/',',',trim($conf->global->USERGROUP_ADDON_PDF_ODT_PATH))); + $listoffiles=array(); + foreach($listofdir as $key=>$tmpdir) + { + $tmpdir=trim($tmpdir); + $tmpdir=preg_replace('/DOL_DATA_ROOT/',DOL_DATA_ROOT,$tmpdir); + if (! $tmpdir) { + unset($listofdir[$key]); continue; + } + if (! is_dir($tmpdir)) $texttitle.=img_warning($langs->trans("ErrorDirNotFound",$tmpdir),0); + else + { + $tmpfiles=dol_dir_list($tmpdir,'files',0,'\.(ods|odt)'); + if (count($tmpfiles)) $listoffiles=array_merge($listoffiles,$tmpfiles); + } + } + $texthelp=$langs->trans("ListOfDirectoriesForModelGenODT"); + // Add list of substitution keys + $texthelp.='
'.$langs->trans("FollowingSubstitutionKeysCanBeUsed").'
'; + $texthelp.=$langs->transnoentitiesnoconv("FullListOnOnlineDocumentation"); // This contains an url, we don't modify it + + $texte.= $form->textwithpicto($texttitle,$texthelp,1,'help','',1); + $texte.= '
'; + $texte.= ''; + $texte.= '
'; + $texte.= ''; + $texte.= '
'; + + // Scan directories + if (count($listofdir)) + { + $texte.=$langs->trans("NumberOfModelFilesFound").': '.count($listoffiles).''; + + if ($conf->global->MAIN_PROPAL_CHOOSE_ODT_DOCUMENT > 0) + { + // Model for creation + $liste=ModelePDFUserGroup::liste_modeles($this->db); + $texte.= ''; + $texte.= ''; + $texte.= ''; + $texte.= '"; + + $texte.= ''; + $texte.= ''; + $texte.= '"; + $texte.= ''; + + $texte.= ''; + $texte.= '"; + $texte.= '
'.$langs->trans("DefaultModelPropalCreate").''; + $texte.= $form->selectarray('value2',$liste,$conf->global->USERGROUP_ADDON_PDF_ODT_DEFAULT); + $texte.= "
'.$langs->trans("DefaultModelPropalToBill").''; + $texte.= $form->selectarray('value3',$liste,$conf->global->USERGROUP_ADDON_PDF_ODT_TOBILL); + $texte.= "
'.$langs->trans("DefaultModelPropalClosed").''; + $texte.= $form->selectarray('value4',$liste,$conf->global->USERGROUP_ADDON_PDF_ODT_CLOSED); + $texte.= "
'; + } + } + + $texte.= '
'; + $texte.= $langs->trans("ExampleOfDirectoriesForModelGen"); + $texte.= '
'; + $texte.= '
'; + + return $texte; + } + + /** + * Function to build a document on disk using the generic odt module. + * + * @param UserGroup $object Object source to build document + * @param Translate $outputlangs Lang output object + * @param string $srctemplatepath Full path of source filename for generator using a template file + * @param int $hidedetails Do not show line details + * @param int $hidedesc Do not show desc + * @param int $hideref Do not show ref + * @return int 1 if OK, <=0 if KO + */ + function write_file($object,$outputlangs,$srctemplatepath,$hidedetails=0,$hidedesc=0,$hideref=0) + { + global $user,$langs,$conf,$mysoc,$hookmanager; + + if (empty($srctemplatepath)) + { + dol_syslog("doc_generic_odt::write_file parameter srctemplatepath empty", LOG_WARNING); + return -1; + } + + // Add odtgeneration hook + if (! is_object($hookmanager)) + { + include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php'; + $hookmanager=new HookManager($this->db); + } + $hookmanager->initHooks(array('odtgeneration')); + global $action; + + if (! is_object($outputlangs)) $outputlangs=$langs; + $sav_charset_output=$outputlangs->charset_output; + $outputlangs->charset_output='UTF-8'; + + $outputlangs->load("main"); + $outputlangs->load("dict"); + $outputlangs->load("companies"); + $outputlangs->load("bills"); + + if ($conf->user->dir_output) + { + // If $object is id instead of object + if (! is_object($object)) + { + $id = $object; + $object = new UserGroup($this->db); + $result=$object->fetch($id); + if ($result < 0) + { + dol_print_error($this->db,$object->error); + return -1; + } + } + + $dir = $conf->usergroup->dir_output; + $objectref = dol_sanitizeFileName($object->ref); + if (! preg_match('/specimen/i',$objectref)) $dir.= "/" . $objectref; + $file = $dir . "/" . $objectref . ".odt"; + + if (! file_exists($dir)) + { + if (dol_mkdir($dir) < 0) + { + $this->error=$langs->transnoentities("ErrorCanNotCreateDir",$dir); + return -1; + } + } + + if (file_exists($dir)) + { + //print "srctemplatepath=".$srctemplatepath; // Src filename + $newfile=basename($srctemplatepath); + $newfiletmp=preg_replace('/\.od(t|s)/i','',$newfile); + $newfiletmp=preg_replace('/template_/i','',$newfiletmp); + $newfiletmp=preg_replace('/modele_/i','',$newfiletmp); + + $newfiletmp=$objectref.'_'.$newfiletmp; + + // Get extension (ods or odt) + $newfileformat=substr($newfile, strrpos($newfile, '.')+1); + if ( ! empty($conf->global->MAIN_DOC_USE_TIMING)) + { + $format=$conf->global->MAIN_DOC_USE_TIMING; + if ($format == '1') $format='%Y%m%d%H%M%S'; + $filename=$newfiletmp.'-'.dol_print_date(dol_now(),$format).'.'.$newfileformat; + } + else + { + $filename=$newfiletmp.'.'.$newfileformat; + } + $file=$dir.'/'.$filename; + //print "newdir=".$dir; + //print "newfile=".$newfile; + //print "file=".$file; + //print "conf->user->dir_temp=".$conf->user->dir_temp; + + dol_mkdir($conf->user->dir_temp); + + + // If CUSTOMER contact defined on user, we use it + $usecontact=false; + $arrayidcontact=$object->getIdContact('external','CUSTOMER'); + if (count($arrayidcontact) > 0) + { + $usecontact=true; + $result=$object->fetch_contact($arrayidcontact[0]); + } + + // Recipient name + if (! empty($usecontact)) + { + // On peut utiliser le nom de la societe du contact + if (! empty($conf->global->MAIN_USE_COMPANY_NAME_OF_CONTACT)) $socobject = $object->contact; + else { + $socobject = $object->client; + // if we have a CUSTOMER contact and we dont use it as recipient we store the contact object for later use + $contactobject = $object->contact; + } + } + else + { + $socobject=$object->client; + } + // Make substitution + $substitutionarray=array( + '__FROM_NAME__' => $this->emetteur->name, + '__FROM_EMAIL__' => $this->emetteur->email, + '__TOTAL_TTC__' => $object->total_ttc, + '__TOTAL_HT__' => $object->total_ht, + '__TOTAL_VAT__' => $object->total_vat + ); + complete_substitutions_array($substitutionarray, $langs, $object); + // Call the ODTSubstitution hook + $parameters=array('file'=>$file,'object'=>$object,'outputlangs'=>$outputlangs,'substitutionarray'=>&$substitutionarray); + $reshook=$hookmanager->executeHooks('ODTSubstitution',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks + + // Line of free text + $newfreetext=''; + $paramfreetext='user_FREE_TEXT'; + if (! empty($conf->global->$paramfreetext)) + { + $newfreetext=make_substitutions($conf->global->$paramfreetext,$substitutionarray); + } + + // Open and load template + require_once ODTPHP_PATH.'odf.php'; + try { + $odfHandler = new odf( + $srctemplatepath, + array( + 'PATH_TO_TMP' => $conf->user->dir_temp, + 'ZIP_PROXY' => 'PclZipProxy', // PhpZipProxy or PclZipProxy. Got "bad compression method" error when using PhpZipProxy. + 'DELIMITER_LEFT' => '{', + 'DELIMITER_RIGHT' => '}' + ) + ); + } + catch(Exception $e) + { + $this->error=$e->getMessage(); + return -1; + } + // After construction $odfHandler->contentXml contains content and + // [!-- BEGIN row.lines --]*[!-- END row.lines --] has been replaced by + // [!-- BEGIN lines --]*[!-- END lines --] + //print html_entity_decode($odfHandler->__toString()); + //print exit; + + + // Make substitutions into odt of freetext + try { + $odfHandler->setVars('free_text', $newfreetext, true, 'UTF-8'); + } + catch(OdfException $e) + { + } + + // Make substitutions into odt + $array_user=$this->get_substitutionarray_user($user,$outputlangs); + $array_global=$this->get_substitutionarray_each_var_object($object,$outputlangs); + $array_soc=$this->get_substitutionarray_mysoc($mysoc,$outputlangs); + $array_thirdparty=$this->get_substitutionarray_thirdparty($socobject,$outputlangs); + $array_objet=$this->get_substitutionarray_each_var_object($object,$outputlangs); + $array_other=$this->get_substitutionarray_other($outputlangs); + // retrieve contact information for use in user as contact_xxx tags + $array_thirdparty_contact = array(); + if ($usecontact) + $array_thirdparty_contact=$this->get_substitutionarray_contact($contactobject,$outputlangs,'contact'); + + $tmparray = array_merge($array_global,$array_user,$array_soc,$array_thirdparty,$array_objet,$array_other,$array_thirdparty_contact); + complete_substitutions_array($tmparray, $outputlangs, $object); + $object->fetch_optionals(); + // Call the ODTSubstitution hook + $parameters=array('file'=>$file,'object'=>$object,'outputlangs'=>$outputlangs,'substitutionarray'=>&$tmparray); + $reshook=$hookmanager->executeHooks('ODTSubstitution',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks + foreach($tmparray as $key=>$value) + { + try + { + if (preg_match('/logo$/',$key)) // Image + { + if (file_exists($value)) $odfHandler->setImage($key, $value); + else $odfHandler->setVars($key, 'ErrorFileNotFound', true, 'UTF-8'); + } + else // Text + { + $odfHandler->setVars($key, $value, true, 'UTF-8'); + } + } + catch(OdfException $e) + { + } + } + // Replace tags of lines + try + { + $listlines = $odfHandler->setSegment('lines'); + foreach ($object->members as $u) + { + $tmparray=$this->get_substitutionarray_each_var_object($u,$outputlangs); + unset($tmparray['object_pass']); + unset($tmparray['object_pass_indatabase']); + complete_substitutions_array($tmparray, $outputlangs, $object, $user, "completesubstitutionarray_users"); + // Call the ODTSubstitutionLine hook + $parameters=array('odfHandler'=>&$odfHandler,'file'=>$file,'object'=>$object,'outputlangs'=>$outputlangs,'substitutionarray'=>&$tmparray,'line'=>$u); + $reshook=$hookmanager->executeHooks('ODTSubstitutionLine',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks + foreach($tmparray as $key => $val) + { + try + { + if(!is_array($val)) { + $listlines->setVars($key, $val, true, 'UTF-8'); + } + } + catch(OdfException $e) + { + } + catch(SegmentException $e) + { + } + } + $listlines->merge(); + } + $odfHandler->mergeSegment($listlines); + } + catch(OdfException $e) + { + $this->error=$e->getMessage(); + dol_syslog($this->error, LOG_WARNING); + return -1; + } + + // Replace labels translated + $tmparray=$outputlangs->get_translations_for_substitutions(); + foreach($tmparray as $key=>$value) + { + try { + $odfHandler->setVars($key, $value, true, 'UTF-8'); + } + catch(OdfException $e) + { + } + } + + // Call the beforeODTSave hook + $parameters=array('odfHandler'=>&$odfHandler,'file'=>$file,'object'=>$object,'outputlangs'=>$outputlangs); + $reshook=$hookmanager->executeHooks('beforeODTSave',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks + + // Write new file + if (!empty($conf->global->MAIN_ODT_AS_PDF)) { + try { + $odfHandler->exportAsAttachedPDF($file); + }catch (Exception $e){ + $this->error=$e->getMessage(); + return -1; + } + } + else { + try { + $odfHandler->saveToDisk($file); + }catch (Exception $e){ + $this->error=$e->getMessage(); + return -1; + } + } + + $reshook=$hookmanager->executeHooks('afterODTCreation',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks + + if (! empty($conf->global->MAIN_UMASK)) + @chmod($file, octdec($conf->global->MAIN_UMASK)); + + $odfHandler=null; // Destroy object + + return 1; // Success + } + else + { + $this->error=$langs->transnoentities("ErrorCanNotCreateDir",$dir); + return -1; + } + } + + return -1; + } + +} + diff --git a/htdocs/core/modules/usergroup/modules_usergroup.class.php b/htdocs/core/modules/usergroup/modules_usergroup.class.php index f7d4778efe1..26edb08d57c 100644 --- a/htdocs/core/modules/usergroup/modules_usergroup.class.php +++ b/htdocs/core/modules/usergroup/modules_usergroup.class.php @@ -52,7 +52,7 @@ abstract class ModelePDFUserGroup extends CommonDocGenerator { global $conf; - $type='usergroup'; + $type='group'; $liste=array(); include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; diff --git a/htdocs/install/doctemplates/contracts/index.html b/htdocs/install/doctemplates/contracts/index.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/htdocs/install/doctemplates/contracts/template_contract.odt b/htdocs/install/doctemplates/contracts/template_contract.odt new file mode 100755 index 0000000000000000000000000000000000000000..8ece83c989c9ff688337dbb0edf6febd47eb0dcf GIT binary patch literal 25866 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 jUx%REKSA)Nd85A==G7F@&=G4A!~e?R literal 0 HcmV?d00001 diff --git a/htdocs/install/doctemplates/products/index.html b/htdocs/install/doctemplates/products/index.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/htdocs/install/doctemplates/products/template_product.odt b/htdocs/install/doctemplates/products/template_product.odt new file mode 100644 index 0000000000000000000000000000000000000000..344fcfa391aefbfef2b4ae85c5436e6c707adaa2 GIT binary patch literal 17591 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 literal 0 HcmV?d00001 diff --git a/htdocs/install/doctemplates/users/index.html b/htdocs/install/doctemplates/users/index.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/htdocs/install/doctemplates/users/template_user.odt b/htdocs/install/doctemplates/users/template_user.odt new file mode 100755 index 0000000000000000000000000000000000000000..f1f1fcc2c5af013e85cb5dcdf394b8053b10e749 GIT binary patch literal 13431 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 WNr*Q`EeHtG+sEdOe=g{M9sM79yJa*0 literal 0 HcmV?d00001 diff --git a/htdocs/install/mysql/migration/5.0.0-6.0.0.sql b/htdocs/install/mysql/migration/5.0.0-6.0.0.sql index 0dfc5f36925..0f8bfb4591e 100644 --- a/htdocs/install/mysql/migration/5.0.0-6.0.0.sql +++ b/htdocs/install/mysql/migration/5.0.0-6.0.0.sql @@ -114,3 +114,11 @@ UPDATE llx_const set value='moono-lisa' where value = 'moono' AND name = 'FCKEDI ALTER TABLE llx_product_price ADD COLUMN default_vat_code varchar(10) after tva_tx; ALTER TABLE llx_product_fournisseur_price ADD COLUMN default_vat_code varchar(10) after tva_tx; +ALTER TABLE llx_user ADD COLUMN model_pdf varchar(255); +ALTER TABLE llx_usergroup ADD COLUMN model_pdf varchar(255); + +INSERT INTO `llx_const` (`name`, `entity`, `value`, `type`, `visible`, `note`, `tms`) VALUES +('PRODUCT_ADDON_PDF_ODT_PATH', 1, 'DOL_DATA_ROOT/doctemplates/products', 'chaine', 0, '', '2017-03-13 16:54:30'), +('CONTRACT_ADDON_PDF_ODT_PATH', 1, 'DOL_DATA_ROOT/doctemplates/contracts', 'chaine', 0, '', '2017-03-13 13:07:27'), +('USERGROUP_ADDON_PDF_ODT_PATH', 1, 'DOL_DATA_ROOT/doctemplates/usergroups', 'chaine', 0, '', '2017-03-10 15:25:06'), +('USER_ADDON_PDF_ODT_PATH', 1, 'DOL_DATA_ROOT/doctemplates/users', 'chaine', 0, '', '2017-03-10 15:14:14') diff --git a/htdocs/install/step1.php b/htdocs/install/step1.php index b323c970a9d..b1a9fabb20d 100644 --- a/htdocs/install/step1.php +++ b/htdocs/install/step1.php @@ -457,7 +457,17 @@ if (! $error && $db->connected && $action == "set") require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; $srcroot=$main_dir.'/install/doctemplates'; $destroot=$main_data_dir.'/doctemplates'; - $docs=array('thirdparties' => 'thirdparty', 'proposals' => 'proposal', 'orders' => 'order', 'invoices' => 'invoice', 'projects' => 'project', 'tasks' => 'task_summary'); + $docs=array('contracts' => 'contract' + , 'thirdparties' => 'thirdparty' + , 'products' => 'product' + , 'proposals' => 'proposal' + , 'orders' => 'order' + , 'invoices' => 'invoice' + , 'projects' => 'project' + , 'tasks' => 'task_summary' + , 'users' => 'user' + , 'usergroups' => 'usergroups' + ); foreach($docs as $cursordir => $cursorfile) { $src=$srcroot.'/'.$cursordir.'/template_'.$cursorfile.'.odt'; diff --git a/htdocs/product/admin/product.php b/htdocs/product/admin/product.php index c42c3981494..c879ab54059 100644 --- a/htdocs/product/admin/product.php +++ b/htdocs/product/admin/product.php @@ -44,9 +44,9 @@ if (! $user->admin || (empty($conf->product->enabled) && empty($conf->service->e $action = GETPOST('action','alpha'); $value = GETPOST('value','alpha'); -$type = GETPOST('type','alpha'); $label = GETPOST('label','alpha'); $scandir = GETPOST('scandir','alpha'); +$type='product'; // Pricing Rules $select_pricing_rules=array( @@ -325,6 +325,7 @@ $form=new Form($db); * Module to manage product / services code */ $dirproduct=array('/core/modules/product/'); +$dirmodels=array_merge(array('/'),(array) $conf->modules_parts['models']); print load_fiche_titre($langs->trans("ProductCodeChecker"), '', ''); @@ -402,146 +403,157 @@ foreach ($dirproduct as $dirroot) } print ''; - -// Load array def with activated templates +// Defini tableau def des modeles $def = array(); $sql = "SELECT nom"; $sql.= " FROM ".MAIN_DB_PREFIX."document_model"; -$sql.= " WHERE type = 'product'"; +$sql.= " WHERE type = '".$type."'"; $sql.= " AND entity = ".$conf->entity; $resql=$db->query($sql); if ($resql) { $i = 0; $num_rows=$db->num_rows($resql); - if ($num_rows > 0) + while ($i < $num_rows) { - print '
'; - print load_fiche_titre($langs->trans("ModelModulesProduct"), '', ''); - - while ($i < $num_rows) - { - $array = $db->fetch_array($resql); - array_push($def, $array[0]); - $i++; - } - - print ''; - print ''; - print ''; - print ''; - print ''; - print ''; - print ''; - print "\n"; - - $var=true; - foreach ($dirproduct as $dirroot) - { - $dir = dol_buildpath($dirroot.'core/modules/product/doc/',0); - $handle=@opendir($dir); - if (is_resource($handle)) - { - while (($file = readdir($handle))!==false) - { - if (preg_match('/\.modules\.php$/i',$file)) - { - $name = substr($file, 4, dol_strlen($file) -16); - $classname = substr($file, 0, dol_strlen($file) -12); - - try { - dol_include_once($dirroot.'core/modules/product/doc/'.$file); - } - catch(Exception $e) - { - dol_syslog($e->getMessage(), LOG_ERR); - } - - $module = new $classname($db); - - $modulequalified=1; - if (! empty($module->version)) { - if ($module->version == 'development' && $conf->global->MAIN_FEATURES_LEVEL < 2) $modulequalified=0; - else if ($module->version == 'experimental' && $conf->global->MAIN_FEATURES_LEVEL < 1) $modulequalified=0; - } - - if ($modulequalified) - { - $var = !$var; - print ''; - - // Activate / Disable - if (in_array($name, $def)) - { - print ""; - } - else - { - if (versioncompare($module->phpmin,versionphparray()) > 0) - { - print ""; - } - else - { - print ""; - } - } - - // Info - $htmltooltip = ''.$langs->trans("Name").': '.$module->name; - $htmltooltip.='
'.$langs->trans("Type").': '.($module->type?$module->type:$langs->trans("Unknown")); - if ($module->type == 'pdf') - { - $htmltooltip.='
'.$langs->trans("Height").'/'.$langs->trans("Width").': '.$module->page_hauteur.'/'.$module->page_largeur; - } - $htmltooltip.='

'.$langs->trans("FeaturesSupported").':'; - $htmltooltip.='
'.$langs->trans("WatermarkOnDraft").': '.yn((! empty($module->option_draft_watermark)?$module->option_draft_watermark:''), 1, 1); - - print ''; - - // Preview - print ''; - - print "\n"; - } - } - } - closedir($handle); - } - } - print '
'.$langs->trans("Name").''.$langs->trans("Description").''.$langs->trans("Status").''.$langs->trans("ShortInfo").''.$langs->trans("Preview").'
'; - print $module->name; - print "\n"; - if (method_exists($module,'info')) print $module->info($langs); - else print $module->description; - print '\n"; - print 'scandir.'&label='.urlencode($module->name).'">'; - print img_picto($langs->trans("Enabled"),'switch_on'); - print ''; - print "\n"; - print img_picto(dol_escape_htmltag($langs->trans("ErrorModuleRequirePHPVersion",join('.',$module->phpmin))),'switch_off'); - print "\n"; - print 'scandir.'&label='.urlencode($module->name).'">'.img_picto($langs->trans("Disabled"),'switch_off').''; - print "'; - print $form->textwithpicto('',$htmltooltip,1,0); - print ''; - if ($module->type == 'pdf') - { - $linkspec=''.img_object($langs->trans("Preview"),'bill').''; - } - else - { - $linkspec=img_object($langs->trans("PreviewNotAvailable"),'generic'); - } - print $linkspec; - print '
'; + $array = $db->fetch_array($resql); + array_push($def, $array[0]); + $i++; } } else { - dol_print_error($db); + dol_print_error($db); } - + +print ''; +print ''; +print ''; +print ''; +print '\n"; +print '\n"; +print ''; +print ''; +print "\n"; + +clearstatcache(); + +$var=true; +foreach ($dirmodels as $reldir) +{ + foreach (array('','/doc') as $valdir) + { + $dir = dol_buildpath($reldir."core/modules/product".$valdir); + if (is_dir($dir)) + { + $handle=opendir($dir); + if (is_resource($handle)) + { + while (($file = readdir($handle))!==false) + { + $filelist[]=$file; + } + closedir($handle); + arsort($filelist); + + foreach($filelist as $file) + { + if (preg_match('/\.modules\.php$/i',$file) && preg_match('/^(pdf_|doc_)/',$file)) + { + + if (file_exists($dir.'/'.$file)) + { + $name = substr($file, 4, dol_strlen($file) -16); + $classname = substr($file, 0, dol_strlen($file) -12); + + require_once $dir.'/'.$file; + $module = new $classname($db); + + $modulequalified=1; + if ($module->version == 'development' && $conf->global->MAIN_FEATURES_LEVEL < 2) $modulequalified=0; + if ($module->version == 'experimental' && $conf->global->MAIN_FEATURES_LEVEL < 1) $modulequalified=0; + + if ($modulequalified) + { + $var = !$var; + print ''; + + // Active + if (in_array($name, $def)) + { + print ''; + } + else + { + print '"; + } + + // Defaut + print ''; + + // Info + $htmltooltip = ''.$langs->trans("Name").': '.$module->name; + $htmltooltip.='
'.$langs->trans("Type").': '.($module->type?$module->type:$langs->trans("Unknown")); + if ($module->type == 'pdf') + { + $htmltooltip.='
'.$langs->trans("Width").'/'.$langs->trans("Height").': '.$module->page_largeur.'/'.$module->page_hauteur; + } + $htmltooltip.='

'.$langs->trans("FeaturesSupported").':'; + $htmltooltip.='
'.$langs->trans("Logo").': '.yn($module->option_logo,1,1); + $htmltooltip.='
'.$langs->trans("PaymentMode").': '.yn($module->option_modereg,1,1); + $htmltooltip.='
'.$langs->trans("PaymentConditions").': '.yn($module->option_condreg,1,1); + $htmltooltip.='
'.$langs->trans("MultiLanguage").': '.yn($module->option_multilang,1,1); + $htmltooltip.='
'.$langs->trans("WatermarkOnDraftOrders").': '.yn($module->option_draft_watermark,1,1); + + + print ''; + + // Preview + print ''; + + print "\n"; + } + } + } + } + } + } + } +} + +print '
'.$langs->trans("Name").''.$langs->trans("Description").''.$langs->trans("Status")."'.$langs->trans("Default")."'.$langs->trans("ShortInfo").''.$langs->trans("Preview").'
'; + print (empty($module->name)?$name:$module->name); + print "\n"; + if (method_exists($module,'info')) print $module->info($langs); + else print $module->description; + print ''."\n"; + print ''; + print img_picto($langs->trans("Enabled"),'switch_on'); + print ''; + print ''."\n"; + print 'scandir.'&label='.urlencode($module->name).'">'.img_picto($langs->trans("Disabled"),'switch_off').''; + print "'; + if ($conf->global->PRODUCT_ADDON_PDF == $name) + { + print img_picto($langs->trans("Default"),'on'); + } + else + { + print 'scandir.'&label='.urlencode($module->name).'" alt="'.$langs->trans("Default").'">'.img_picto($langs->trans("Disabled"),'off').''; + } + print ''; + print $form->textwithpicto('',$htmltooltip,1,0); + print ''; + if ($module->type == 'pdf') + { + print ''.img_object($langs->trans("Preview"),'contract').''; + } + else + { + print img_object($langs->trans("PreviewNotAvailable"),'generic'); + } + print '
'; +print "
"; /* * Other conf diff --git a/htdocs/product/card.php b/htdocs/product/card.php index b27cef6d985..4df8b45041f 100644 --- a/htdocs/product/card.php +++ b/htdocs/product/card.php @@ -147,7 +147,6 @@ if (empty($reshook)) { // Save last template used to generate document if (GETPOST('model')) $object->setDocModel($user, GETPOST('model','alpha')); - // Define output language $outputlangs = $langs; $newlang=''; @@ -158,11 +157,12 @@ if (empty($reshook)) $outputlangs = new Translate("",$conf); $outputlangs->setDefaultLang($newlang); } - $result=product_create($db, $object, GETPOST('model','alpha'), $outputlangs); + $ret = $object->fetch($id); // Reload to get new records + $result = $object->generateDocument($object->modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref); if ($result <= 0) { - dol_print_error($db,$result); - exit; + setEventMessages($object->error, $object->errors, 'errors'); + $action=''; } } @@ -197,6 +197,20 @@ if (empty($reshook)) setEventMessages($errors, null, 'errors'); } } + + // Remove file in doc form + if ($action == 'remove_file' && $user->rights->produit->creer) { + if ($object->id > 0) { + require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php'; + + $langs->load("other"); + $upload_dir = $conf->product->dir_output; + $file = $upload_dir . '/' . GETPOST('file'); + $ret = dol_delete_file($file, 0, 0, 0, $object); + if ($ret) setEventMessages($langs->trans("FileWasRemoved", GETPOST('file')), null, 'mesgs'); + else setEventMessages($langs->trans("ErrorFailToDeleteFile", GETPOST('file')), null, 'errors'); + } + } // Add a product or service if ($action == 'add' && ($user->rights->produit->creer || $user->rights->service->creer)) @@ -2001,7 +2015,7 @@ if (! empty($conf->global->PRODUCT_ADD_FORM_ADD_TO) && $object->id && ($action = /* * Documents generes */ -if ($action == '' || $action == 'view') +if ($action != 'edit' && $action != 'delete') { print '
'; print ''; // ancre diff --git a/htdocs/user/group/card.php b/htdocs/user/group/card.php index 5cc173da6ad..192d681ca81 100644 --- a/htdocs/user/group/card.php +++ b/htdocs/user/group/card.php @@ -210,6 +210,11 @@ if ($action == 'update') setEventMessages($langs->trans('ErrorForbidden'), null, 'mesgs'); } } + +// Actions to build doc +$upload_dir = $conf->usergroup->dir_output; +$permissioncreate=$user->rights->user->user->creer; +include DOL_DOCUMENT_ROOT.'/core/actions_builddoc.inc.php'; @@ -498,11 +503,6 @@ else } print ""; print "
"; - - // Actions to build doc - $upload_dir = $conf->usergroup->dir_output; - $permissioncreate=$user->rights->user->user->creer; - include DOL_DOCUMENT_ROOT.'/core/actions_builddoc.inc.php'; /* * Documents generes From 3ab7bac511ddf748356e27e058a79657ad5bb955 Mon Sep 17 00:00:00 2001 From: arnaud Date: Tue, 14 Mar 2017 09:37:51 +0100 Subject: [PATCH 6/6] FIX fresh install const --- htdocs/install/mysql/data/llx_const.sql | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/htdocs/install/mysql/data/llx_const.sql b/htdocs/install/mysql/data/llx_const.sql index 42ecad2b927..b34e0cac513 100644 --- a/htdocs/install/mysql/data/llx_const.sql +++ b/htdocs/install/mysql/data/llx_const.sql @@ -85,3 +85,12 @@ insert into llx_const (name, value, type, note, visible) values ('MAIN_DELAY_EXP -- insert into llx_const (name, value, type, note, visible) values ('MAIN_FIX_FOR_BUGGED_MTA','1','chaine','Set constant to fix email ending from PHP with some linux ike system',1); insert into llx_const (name, value, type, note, visible) values ('MAILING_EMAIL_FROM','dolibarr@domain.com','chaine','EMail emmetteur pour les envois d emailings',0); + + +-- +-- ODT Path +--- +insert into `llx_const` (`name`, `entity`, `value`, `type`, `visible`) VALUES ('PRODUCT_ADDON_PDF_ODT_PATH', 1, 'DOL_DATA_ROOT/doctemplates/products', 'chaine', 0); +insert into `llx_const` (`name`, `entity`, `value`, `type`, `visible`) VALUES ('CONTRACT_ADDON_PDF_ODT_PATH', 1, 'DOL_DATA_ROOT/doctemplates/contracts', 'chaine', 0); +insert into `llx_const` (`name`, `entity`, `value`, `type`, `visible`) VALUES ('USERGROUP_ADDON_PDF_ODT_PATH', 1, 'DOL_DATA_ROOT/doctemplates/usergroups', 'chaine', 0); +insert into `llx_const` (`name`, `entity`, `value`, `type`, `visible`) VALUES ('USER_ADDON_PDF_ODT_PATH', 1, 'DOL_DATA_ROOT/doctemplates/users', 'chaine', 0);