diff --git a/ChangeLog b/ChangeLog index 814d48d278b..70144f80733 100644 --- a/ChangeLog +++ b/ChangeLog @@ -16,6 +16,7 @@ Following changes may create regressions for some external modules, but were nec * Properties ->libelle_incoterms were renamed into ->label_incoterms + ***** ChangeLog for 10.0.0 compared to 9.0.0 ***** For Users: NEW: Module "Ticket" is available as a stable module. @@ -195,6 +196,55 @@ Following changes may create regressions for some external modules, but were nec called $dolibarr_main_instance_unique_id is now generated at each installation. It will be used by some future features. +***** ChangeLog for 9.0.4 compared to 9.0.3 ***** +FIX: #5249 +FIX: #11025 +FIX: #11032 +FIX: #11097 +FIX: #11169 +FIX: #11202 +FIX: #11244 +FIX: #11296 +FIX: #11316 +FIX: #11335 +FIX: Add missing end date of subscription in export +FIX: A user may read holiday and expense report without permissions +FIX: better syntax +FIX: condition +FIX: confirmation of mass email sending + option MAILING_NO_USING_PHPMAIL +FIX: crabe pdf: bad detailed VAT for situation invoices, in situations S2 and above +FIX: default value for duration of validity can be set from generic +FIX: do not include tpl from disabled modules +FIX: Error management when MAILING_NO_USING_PHPMAIL is set +FIX: Even with permission, can't validate leave once validator defined. +FIX: extrafield list search: SQL error when field is multiselect +FIX: if last char of customercode is accent making the truncate of first +FIX: Import of chart of account +FIX: in edit mode, dictionary inputs do not escape the string inside the 'value' attribute, causing errors if there are any double quotes +FIX: invalid link on user.fk_user +FIX: invoice class: bad SQL request if product type not set +FIX: javascript error when ckeditor module not enabled +FIX: mail presend: can overwrite a file previously uploaded (Issue #11056) +FIX: mass send mail +FIX: missing compatibility with multicompany transverse mode +FIX: missing llx_const encrypt +FIX: modulebuilder: hardcoded llx_ +FIX: Not showing Contract and Project columns on ficheinter list +FIX: only profid1 to 4 were editable for pdf option to show. Not 5 and 6. +FIX: productaccount buylist with pages +FIX: remove isolated transaction commit +FIX: security (a user can read leave or holiday of other without perm. +FIX: situation invoices: bad detailed VAT in situations following the first one +FIX: situation invoices: block progress percentage change for discount lines +FIX: syntax error +FIX: the id was not loaded in fetch of accounting system +FIX: try to use WHERE EXISTS instead of DISTINCT +FIX: use dol_sanitizeFileName() function to remove double spaces in filenames, as well as done on document.php when we want to download pdf +FIX: Use of cron with multicompany +FIX: var name +FIX: we need to fetch fourn invoice with ref in current entity +FIX: Wrong stock movement on supplier credit notes +FIX: Import of record in ledger ***** ChangeLog for 9.0.3 compared to 9.0.2 ***** FIX: #11013 diff --git a/build/generate_filelist_xml.php b/build/generate_filelist_xml.php index 4b1d4b55c65..d5af14f9171 100755 --- a/build/generate_filelist_xml.php +++ b/build/generate_filelist_xml.php @@ -106,6 +106,7 @@ if (empty($includecustom)) { } print "Release : ".$release."\n"; +print "Working on files into : ".DOL_DOCUMENT_ROOT."\n"; print "Include custom in signature : ".$includecustom."\n"; print "Include constants in signature : "; foreach ($includeconstants as $countrycode => $tmp) { diff --git a/htdocs/core/class/utils.class.php b/htdocs/core/class/utils.class.php index 9e0063ac414..f86d6570e5c 100644 --- a/htdocs/core/class/utils.class.php +++ b/htdocs/core/class/utils.class.php @@ -50,33 +50,36 @@ class Utils * Purge files into directory of data files. * CAN BE A CRON TASK * - * @param string $choice Choice of purge mode ('tempfiles', '' or 'tempfilesold' to purge temp older than 24h, 'allfiles', 'logfile') - * @return int 0 if OK, < 0 if KO (this function is used also by cron so only 0 is OK) + * @param string $choice Choice of purge mode ('tempfiles', '' or 'tempfilesold' to purge temp older than $nbsecondsold seconds, 'allfiles', 'logfile') + * @param int $nbsecondsold Nb of seconds old to accept deletion of a directory if $choice is 'tempfilesold' + * @return int 0 if OK, < 0 if KO (this function is used also by cron so only 0 is OK) */ - public function purgeFiles($choice = 'tempfilesold') + public function purgeFiles($choice = 'tempfilesold', $nbsecondsold = 86400) { global $conf, $langs, $dolibarr_main_data_root; $langs->load("admin"); - dol_syslog("Utils::purgeFiles choice=".$choice, LOG_DEBUG); require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; $filesarray=array(); if (empty($choice)) $choice='tempfilesold'; + dol_syslog("Utils::purgeFiles choice=".$choice, LOG_DEBUG); + if ($choice=='tempfiles' || $choice=='tempfilesold') { // Delete temporary files if ($dolibarr_main_data_root) { - $filesarray=dol_dir_list($dolibarr_main_data_root, "directories", 1, '^temp$', '', 'name', SORT_ASC, 2, 0, '', 1); // Do not follow symlinks - if ($choice == 'tempfilesold') + $filesarray=dol_dir_list($dolibarr_main_data_root, "directories", 1, '^temp$', '', 'name', SORT_ASC, 2, 0, '', 1); // Do not follow symlinks + + if ($choice == 'tempfilesold') { $now = dol_now(); foreach($filesarray as $key => $val) { - if ($val['date'] > ($now - (24 * 3600))) unset($filesarray[$key]); // Discard files not older than 24h + if ($val['date'] > ($now - ($nbsecondsold))) unset($filesarray[$key]); // Discard temp dir not older than $nbsecondsold } } } @@ -119,13 +122,14 @@ class Utils $counterror=0; if (count($filesarray)) { - foreach($filesarray as $key => $value) + foreach($filesarray as $key => $value) { //print "x ".$filesarray[$key]['fullname']."-".$filesarray[$key]['type']."
\n"; - if ($filesarray[$key]['type'] == 'dir') + if ($filesarray[$key]['type'] == 'dir') { $startcount=0; $tmpcountdeleted=0; + $result=dol_delete_dir_recursive($filesarray[$key]['fullname'], $startcount, 1, 0, $tmpcountdeleted); $count+=$result; $countdeleted+=$tmpcountdeleted; @@ -165,6 +169,13 @@ class Utils } else $this->output=$langs->trans("PurgeNothingToDelete").($choice == 'tempfilesold' ? ' (older than 24h)':''); + // Recreate temp dir that are not automatically recreated by core code for performance purpose, we need them + if (! empty($conf->api->enabled)) + { + dol_mkdir($conf->api->dir_temp); + } + dol_mkdir($conf->user->dir_temp); + //return $count; return 0; // This function can be called by cron so must return 0 if OK } diff --git a/htdocs/core/modules/import/import_csv.modules.php b/htdocs/core/modules/import/import_csv.modules.php index f75e5035670..1311264f7fb 100644 --- a/htdocs/core/modules/import/import_csv.modules.php +++ b/htdocs/core/modules/import/import_csv.modules.php @@ -669,18 +669,30 @@ class ImportCsv extends ModeleImports if (! preg_match('/^'.preg_quote($alias).'\./', $key)) continue; // Not a field of current table if ($val == 'user->id') { - $listfields[] = preg_replace('/^'.preg_quote($alias).'\./', '', $key); + $listfields[] = preg_replace('/^'.preg_quote($alias, '/').'\./', '', $key); $listvalues[] = $user->id; } elseif (preg_match('/^lastrowid-/', $val)) { $tmp=explode('-', $val); $lastinsertid=(isset($last_insert_id_array[$tmp[1]]))?$last_insert_id_array[$tmp[1]]:0; - $keyfield = preg_replace('/^'.preg_quote($alias).'\./', '', $key); + $keyfield = preg_replace('/^'.preg_quote($alias, '/').'\./', '', $key); $listfields[] = $keyfield; $listvalues[] = $lastinsertid; //print $key."-".$val."-".$listfields."-".$listvalues."
";exit; } + elseif (preg_match('/^const-/', $val)) + { + $tmp=explode('-', $val, 2); + $listfields[] = preg_replace('/^'.preg_quote($alias, '/').'\./', '', $key); + $listvalues[] = "'".$tmp[1]."'"; + } + else + { + $this->errors[$error]['lib']='Bad value of profile setup '.$val.' for array_import_fieldshidden'; + $this->errors[$error]['type']='Import profile setup'; + $error++; + } } } //print 'listfields='.$listfields.'
listvalues='.$listvalues.'
'; diff --git a/htdocs/core/modules/import/import_xlsx.modules.php b/htdocs/core/modules/import/import_xlsx.modules.php index ae0290401cc..00db9096bca 100644 --- a/htdocs/core/modules/import/import_xlsx.modules.php +++ b/htdocs/core/modules/import/import_xlsx.modules.php @@ -688,21 +688,33 @@ class ImportXlsx extends ModeleImports // Loop on each hidden fields to add them into listfields/listvalues foreach($objimport->array_import_fieldshidden[0] as $key => $val) { - if (! preg_match('/^'.preg_quote($alias).'\./', $key)) continue; // Not a field of current table + if (! preg_match('/^'.preg_quote($alias, '/').'\./', $key)) continue; // Not a field of current table if ($val == 'user->id') { - $listfields[] = preg_replace('/^'.preg_quote($alias).'\./', '', $key); + $listfields[] = preg_replace('/^'.preg_quote($alias, '/').'\./', '', $key); $listvalues[] = $user->id; } elseif (preg_match('/^lastrowid-/', $val)) { $tmp=explode('-', $val); $lastinsertid=(isset($last_insert_id_array[$tmp[1]]))?$last_insert_id_array[$tmp[1]]:0; - $keyfield = preg_replace('/^'.preg_quote($alias).'\./', '', $key); + $keyfield = preg_replace('/^'.preg_quote($alias, '/').'\./', '', $key); $listfields[] = $keyfield; $listvalues[] = $lastinsertid; //print $key."-".$val."-".$listfields."-".$listvalues."
";exit; } + elseif (preg_match('/^const-/', $val)) + { + $tmp=explode('-', $val, 2); + $listfields[] = preg_replace('/^'.preg_quote($alias, '/').'\./', '', $key); + $listvalues[] = "'".$tmp[1]."'"; + } + else + { + $this->errors[$error]['lib']='Bad value of profile setup '.$val.' for array_import_fieldshidden'; + $this->errors[$error]['type']='Import profile setup'; + $error++; + } } } //print 'listfields='.$listfields.'
listvalues='.$listvalues.'
'; diff --git a/htdocs/core/modules/modAccounting.class.php b/htdocs/core/modules/modAccounting.class.php index c58ba79a8c4..12f95053dc0 100644 --- a/htdocs/core/modules/modAccounting.class.php +++ b/htdocs/core/modules/modAccounting.class.php @@ -288,32 +288,36 @@ class modAccounting extends DolibarrModules $this->import_entities_array[$r]=array(); // We define here only fields that use another icon that the one defined into import_icon $this->import_tables_array[$r]=array('b'=>MAIN_DB_PREFIX.'accounting_bookkeeping'); // List of tables to insert into (insert done in same order) $this->import_fields_array[$r]=array( - 'b.doc_date'=>"Docdate", - 'b.piece_num'=>"TransactionNumShort", + 'b.piece_num'=>"TransactionNumShort", + 'b.doc_date'=>"Docdate", + //'b.doc_type'=>'Doctype', + 'b.doc_ref'=>'Piece', 'b.code_journal'=>'Codejournal', - 'b.journal_label'=>'JournalLabel', + //'b.journal_label'=>'JournalLabel', 'b.numero_compte'=>'AccountAccounting', - 'b.label_compte'=>'LabelAccount', + //'b.label_compte'=>'LabelAccount', 'b.subledger_account'=>'SubledgerAccount', 'b.subledger_label'=>'SubledgerAccountLabel', 'b.label_operation'=>'LabelOperation', 'b.debit'=>"Debit", 'b.credit'=>"Credit" ); - $this->import_fieldshidden_array[$r]=array('b.fk_user_author'=>'user->id'); // aliastable.field => ('user->id' or 'lastrowid-'.tableparent) + $this->import_fieldshidden_array[$r]=array('b.doc_type'=>'const-import_from_external', 'b.fk_doc'=>'const-0', 'b.fk_docdet'=>'const-0', 'b.fk_user_author'=>'user->id', 'b.date_creation'=>'const-'.dol_print_date(dol_now(), 'standard')); // aliastable.field => ('user->id' or 'lastrowid-'.tableparent) $this->import_regex_array[$r]=array('b.doc_date'=>'^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$'); $this->import_examplevalues_array[$r]=array( - 'b.doc_date'=>'formatted as \'.dol_print_date(dol_now(),\'%Y-%m-%d\')', - 'b.piece_num'=>'1', + 'b.piece_num'=>'123 (!!! use next value not already used)', + 'b.doc_date'=>dol_print_date(dol_now(), "%Y-%m-%d"), + //'b.doc_type'=>'import', + 'b.doc_ref'=>'My document ABC', 'b.code_journal'=>"VTE", - 'b.journal_label'=>"Journal des ventes", + //'b.journal_label'=>"Sale journal", 'b.numero_compte'=>"707", - 'b.label_compte'=>'Ventes', + //'b.label_compte'=>'Product account 707', 'b.subledger_account'=>'', 'b.subledger_label'=>'', - 'b.label_operation'=>"Ventes services", - 'b.debit'=>"0,00", - 'b.credit'=>"100,00" + 'b.label_operation'=>"Sale of ABC", + 'b.debit'=>"0", + 'b.credit'=>"100" ); // Chart of accounts diff --git a/htdocs/fourn/class/fournisseur.facture.class.php b/htdocs/fourn/class/fournisseur.facture.class.php index 1e282f62776..6bedfcb5c1f 100644 --- a/htdocs/fourn/class/fournisseur.facture.class.php +++ b/htdocs/fourn/class/fournisseur.facture.class.php @@ -343,7 +343,7 @@ class FactureFournisseur extends CommonInvoice $sql.= ", '".$this->db->escape($this->ref_supplier)."'"; $sql.= ", ".$conf->entity; $sql.= ", '".$this->db->escape($this->type)."'"; - $sql.= ", '".$this->db->escape($this->libelle)."'"; + $sql.= ", '".$this->db->escape($this->label?$this->label:$this->libelle)."'"; $sql.= ", ".$this->socid; $sql.= ", '".$this->db->idate($now)."'"; $sql.= ", '".$this->db->idate($this->date)."'"; @@ -484,7 +484,7 @@ class FactureFournisseur extends CommonInvoice { $idligne = $this->db->last_insert_id(MAIN_DB_PREFIX.'facture_fourn_det'); - $this->updateline( + $this->updateline( $idligne, $line->description, $line->pu_ht, @@ -511,8 +511,6 @@ class FactureFournisseur extends CommonInvoice $result=$this->update_price(); if ($result > 0) { - $action='create'; - // Actions on extra fields if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) // For avoid conflicts if trigger used { @@ -587,7 +585,7 @@ class FactureFournisseur extends CommonInvoice $sql.= " t.datec,"; $sql.= " t.datef,"; $sql.= " t.tms,"; - $sql.= " t.libelle,"; + $sql.= " t.libelle as label,"; $sql.= " t.paye,"; $sql.= " t.amount,"; $sql.= " t.remise,"; @@ -646,8 +644,8 @@ class FactureFournisseur extends CommonInvoice $this->date = $this->db->jdate($obj->datef); $this->datep = $this->db->jdate($obj->datef); $this->tms = $this->db->jdate($obj->tms); - $this->libelle = $obj->libelle; // deprecated - $this->label = $obj->libelle; + $this->libelle = $obj->label; // deprecated + $this->label = $obj->label; $this->paye = $obj->paye; $this->amount = $obj->amount; $this->remise = $obj->remise; @@ -2847,7 +2845,7 @@ class SupplierInvoiceLine extends CommonObjectLine */ public function fetch($rowid) { - $sql = 'SELECT f.rowid, f.ref as ref_supplier, f.libelle as label, f.description, f.date_start, f.date_end, f.pu_ht, f.pu_ttc, f.qty, f.remise_percent, f.tva_tx'; + $sql = 'SELECT f.rowid, f.ref as ref_supplier, f.description, f.date_start, f.date_end, f.pu_ht, f.pu_ttc, f.qty, f.remise_percent, f.tva_tx'; $sql.= ', f.localtax1_type, f.localtax2_type, f.localtax1_tx, f.localtax2_tx, f.total_localtax1, f.total_localtax2 '; $sql.= ', f.total_ht, f.tva as total_tva, f.total_ttc, f.fk_facture_fourn, f.fk_product, f.product_type, f.info_bits, f.rang, f.special_code, f.fk_parent_line, f.fk_unit'; $sql.= ', p.rowid as product_id, p.ref as product_ref, p.label as product_label, p.description as product_desc'; diff --git a/htdocs/install/mysql/tables/llx_accounting_bookkeeping.sql b/htdocs/install/mysql/tables/llx_accounting_bookkeeping.sql index 5317254d79d..88aa6d4e129 100644 --- a/htdocs/install/mysql/tables/llx_accounting_bookkeeping.sql +++ b/htdocs/install/mysql/tables/llx_accounting_bookkeeping.sql @@ -21,8 +21,9 @@ CREATE TABLE llx_accounting_bookkeeping ( rowid integer NOT NULL AUTO_INCREMENT PRIMARY KEY, entity integer DEFAULT 1 NOT NULL, -- | multi company id - doc_date date NOT NULL, -- FEC:PieceDate - doc_type varchar(30) NOT NULL, -- | facture_client/reglement_client/facture_fournisseur/reglement_fournisseur + piece_num integer NOT NULL, -- FEC:EcritureNum | accounting transaction id + doc_date date NOT NULL, -- FEC:PieceDate | date of source document + doc_type varchar(30) NOT NULL, -- | facture_client/reglement_client/facture_fournisseur/reglement_fournisseur/import doc_ref varchar(300) NOT NULL, -- FEC:PieceRef | facture_client/reglement_client/... reference number fk_doc integer NOT NULL, -- | facture_client/reglement_client/... rowid fk_docdet integer NOT NULL, -- | facture_client/reglement_client/... line rowid @@ -48,7 +49,6 @@ CREATE TABLE llx_accounting_bookkeeping fk_user integer NULL, -- The id of user that validate the accounting source document code_journal varchar(32) NOT NULL, -- FEC:JournalCode journal_label varchar(255), -- FEC:JournalLib - piece_num integer NOT NULL, -- FEC:EcritureNum | accounting source document date_validated datetime, -- FEC:ValidDate | if empty: movement not validated / if not empty: movement validated (No deleting / No modification) date_export datetime DEFAULT NULL, -- import_key varchar(14), diff --git a/htdocs/langs/en_US/admin.lang b/htdocs/langs/en_US/admin.lang index b8cab5ce713..d4c88dfa32e 100644 --- a/htdocs/langs/en_US/admin.lang +++ b/htdocs/langs/en_US/admin.lang @@ -149,7 +149,7 @@ SystemToolsAreaDesc=This area provides administration functions. Use the menu to Purge=Purge PurgeAreaDesc=This page allows you to delete all files generated or stored by Dolibarr (temporary files or all files in %s directory). Using this feature is not normally necessary. It is provided as a workaround for users whose Dolibarr is hosted by a provider that does not offer permissions to delete files generated by the web server. PurgeDeleteLogFile=Delete log files, including %s defined for Syslog module (no risk of losing data) -PurgeDeleteTemporaryFiles=Delete all temporary files (no risk of losing data) +PurgeDeleteTemporaryFiles=Delete all temporary files (no risk of losing data). Note: Deletion is done only if the temp directory was created 24 hours ago. PurgeDeleteTemporaryFilesShort=Delete temporary files PurgeDeleteAllFilesInDocumentsDir=Delete all files in directory: %s.
This will delete all generated documents related to elements (third parties, invoices etc...), files uploaded into the ECM module, database backup dumps and temporary files. PurgeRunNow=Purge now @@ -1110,7 +1110,7 @@ AreaForAdminOnly=Setup parameters can be set by administrator users only. SystemInfoDesc=System information is miscellaneous technical information you get in read only mode and visible for administrators only. SystemAreaForAdminOnly=This area is available to administrator users only. Dolibarr user permissions cannot change this restriction. CompanyFundationDesc=Edit the information of the company/entity. Click on "%s" or "%s" button at the bottom of the page. -AccountantDesc=Edit the details of your accountant/bookkeeper +AccountantDesc=If you have an external accountant/bookkeeper, you can edit here its information. AccountantFileNumber=Accountant code DisplayDesc=Parameters affecting the look and behaviour of Dolibarr can be modified here. AvailableModules=Available app/modules diff --git a/htdocs/public/demo/index.php b/htdocs/public/demo/index.php index 7228778144f..0f6e21f0984 100644 --- a/htdocs/public/demo/index.php +++ b/htdocs/public/demo/index.php @@ -106,13 +106,13 @@ if (empty($reshook)) // Visible $alwayscheckedmodules=array('barcode','bookmark','categorie','externalrss','fckeditor','geoipmaxmind','gravatar','memcached','syslog','user','webservices'); // Technical module we always want - $alwaysuncheckedmodules=array('dynamicprices','incoterm','loan','multicurrency','paybox','paypal','stripe','google','printing','scanner','skype','workflow'); // Module we dont want by default + $alwaysuncheckedmodules=array('dav','dynamicprices','incoterm','loan','multicurrency','paybox','paypal','stripe','google','printing','scanner','skype','takepos','workflow','website'); // Module we dont want by default // Not visible $alwayshiddencheckedmodules=array('accounting','api','barcode','blockedlog','bookmark','clicktodial','comptabilite','cron','document','domain','externalrss','externalsite','fckeditor','geoipmaxmind','gravatar','label','ldap', 'mailmanspip','notification','oauth','syslog','user','webservices', // Extended modules 'memcached','numberwords','zipautofillfr'); - $alwayshiddenuncheckedmodules=array('ftp','hrm','webservicesclient','websites', + $alwayshiddenuncheckedmodules=array('debugbar','emailcollector','ftp','hrm','modulebuilder','webservicesclient','websites', // Extended modules 'awstats','bittorrent','bootstrap','cabinetmed','cmcic','concatpdf','customfield','deplacement','dolicloud','filemanager','lightbox','mantis','monitoring','moretemplates','multicompany','nltechno','numberingpack','openstreetmap', 'ovh','phenix','phpsysinfo','pibarcode','postnuke','selectbank','skincoloreditor','submiteverywhere','survey','thomsonphonebook','topten','tvacerfa','voyage','webcalendar','webmail'); diff --git a/test/phpunit/FactureFournisseurTest.php b/test/phpunit/FactureFournisseurTest.php index 2d6218471aa..33d503825ac 100644 --- a/test/phpunit/FactureFournisseurTest.php +++ b/test/phpunit/FactureFournisseurTest.php @@ -135,7 +135,7 @@ class FactureFournisseurTest extends PHPUnit_Framework_TestCase $localobject->initAsSpecimen(); $result=$localobject->create($user); - $this->assertLessThan($result, 0); + $this->assertLessThan($result, 0, $localobject->errorsToString()); print __METHOD__." result=".$result."\n"; return $result; } @@ -160,7 +160,7 @@ class FactureFournisseurTest extends PHPUnit_Framework_TestCase $localobject=new FactureFournisseur($this->savdb); $result=$localobject->fetch($id); - $this->assertLessThan($result, 0); + $this->assertLessThan($result, 0, $localobject->errorsToString()); print __METHOD__." id=".$id." result=".$result."\n"; return $localobject; } @@ -186,7 +186,7 @@ class FactureFournisseurTest extends PHPUnit_Framework_TestCase $result=$localobject->update($user); print __METHOD__." id=".$localobject->id." result=".$result."\n"; - $this->assertLessThan($result, 0); + $this->assertLessThan($result, 0, $localobject->errorsToString()); return $localobject; } @@ -210,7 +210,7 @@ class FactureFournisseurTest extends PHPUnit_Framework_TestCase $result=$localobject->validate($user); print __METHOD__." id=".$localobject->id." result=".$result."\n"; - $this->assertLessThan($result, 0); + $this->assertLessThan($result, 0, $localobject->errorsToString()); return $localobject; } @@ -265,7 +265,7 @@ class FactureFournisseurTest extends PHPUnit_Framework_TestCase $result=$localobject->delete($user); print __METHOD__." id=".$id." result=".$result."\n"; - $this->assertLessThan($result, 0); + $this->assertLessThan($result, 0, $localobject->errorsToString()); return $result; } }