* FIX Drag and drop of a file reports a wrong error, or none at all
The error handler of dragAndDropFileUpload() had three defects that all end on
the user believing the file was attached when it was not.
- The first assignment of the message was dead code, there was no return after
the test on the http code 403, so a refusal was reported as a generic error.
- The key ErrorUploadPermissionDenied it used exists in no language file, so
the raw key was shown.
- JSON.parse() was called with no try/catch on an answer that is not always a
json, a fatal error of the endpoint or a request over post_max_size for
example. The exception left the user on a page with no message at all.
- An empty list of files was treated as a success, while it means the endpoint
stored nothing.
The value of PHP_SELF is also escaped before it is written into the 6 generated
javascript strings. It holds the path info of the request on a server that
accepts it, so it is a user input. dol_escape_js() is called with the mode that
escapes a double quote by a double quote, the strings being delimited by double
quotes, and the sequence '</' is escaped too because the function does not do it
and the path info could otherwise close the script tag and open one of its own.
Adds the key ErrorOnAtLeastOneFileUpload to en_US, used when some files of a
batch failed and some did not.
* FIX getMultidirOutput returns a directory the Documents tab does not read
Four defects of the same function, all ending on a file stored where the user
will never see it, or written outside the documents directory.
- The ref of the project of a task was only passed through dol_sanitizePathName(),
which keeps a slash, a colon and the accented chars, while projet/tasks/document.php
sanitizes it with dol_sanitizeFileName(). A project ref holding a slash even
created an extra level of directory. Measured on real databases: 304 projects
over 330 hold a slash on one of them, carrying 701 tasks over 824.
- The same case calls $object->fetchProject() with no guard, while the signature
of the function accepts an object that is not a CommonObject, and even a null
when a module is given. Such a caller gets a fatal error where it expects the
error string. No core caller is in that case today, an external module or a
hook can be.
- The entity of the object may have no declared directory, an object shared by
another entity for example. The undefined index returned a relative path, so
the caller read or wrote under the web root. The current entity is used
instead, and the fallback is logged because the directory is then not the one
of the entity of the object, which matters for a caller that deletes files.
- When the current entity has no declared directory either, the fallback
returned the sub directory alone, again a relative path. The same error than
for a module that declares no directory at all is now returned.
Adds the sub directory of a partnership and of a stock transfer, which their own
document tabs already read.
* FIX getElementProperties answers wrong properties for 9 elements
A customer payment, a supplier payment, a various payment, a stock transfer and
the 4 objects of the hrm module had no properties at all, or wrong ones, so any
caller that resolves a class, a table or a document directory from an element
failed on them.
- payment, payment_supplier and payment_various had no branch. The branch of a
customer payment tests $elementType and not $element, because the rule on the
elements named myobject_mysubobject rewrites $element to 'payment' for
'payment_salary' too, which is stored somewhere else.
- job, position, skill and evaluation answered a wrong table (hrm_job_user for
a position, and so on) and no sub directory, while their document tabs read
one named after the element.
- stocktransfer answered an empty classname, because it is not the ucfirst() of
the element, so a caller doing new $classname($db) ended on a fatal error.
- The sub directory was concatenated even when the module is disabled and the
directory is empty, which answered a path at the root of the file system.
- A contact and a conference are stored into a sub directory their tab reads.
isModEnabled('invoice') is tested for a customer payment: there is no module
named 'compta', so testing it was always false, while $conf->compta->payment is
set unconditionally by Conf::setValues() and could not be used as a proxy.
* FIX Access refused to everyone on 11 objects of the core
restrictedArea() and checkUserAccessToObject() refuse the access to objects that
no permission and no rule can match, whatever the user, an administrator
included.
- The hrm module declares no permission at its first level, only 'all', and the
stocktransfer module only 'stocktransfer'. A check on the module itself
therefore tests a permission that does not exist. The mapping is the same one
as into User::hasRight().
- The module of an event organization declares no permission at all, its whole
permission block being commented out on purpose, and its cards check the
parent project instead. The feature is mapped onto that project, with the two
guards the card has: an external user is refused, and so is a conference with
no parent project, whose id of 0 would otherwise grant an access with no check
on the record at all.
- The default rule of checkUserAccessToObject() builds its sql on the columns
entity and fk_soc of the table. llx_asset, llx_paiement, llx_paiementfourn and
llx_workstation_workstation have no fk_soc, and llx_hrm_job, llx_hrm_job_user
and llx_hrm_skill have neither. The sql failed, so the access was refused to
every user this rule applies to. These tables are now checked on their entity
only, which is what the $check rule already does for the same class of tables,
and the 3 tables of hrm can be checked on nothing at all. The rule is selected
on the table and not on the element, because $object is an id and not an
object for most of the callers, the cards of an asset and of a workstation
included, which are broken today for any user without the permission to see
all third parties.
- An external user is refused explicitly on those tables: none of these objects
is linked to a third party, so the default rule refused him through a link
that does not exist, and the rules that replace it do not look at the third
party of the user at all.
Measured on a vanilla instance with 5 profiles, an administrator, an internal
user with every right, one without the permission to see all third parties, one
that is not a sales representative of the third party of the object, and an
external user: the 11 objects go from refused to granted for the internal users
and stay refused for the external one, and the 26 other elements answer exactly
the same for the 5 profiles.
* FIX A file dropped on a card is lost, or reported as refused when it was stored
FileUpload stores the file into a directory that the "Attached files" tab of the
object never reads, so the user attaches a file that no screen will ever show,
and nothing is indexed in database to find it back. Measured on real databases:
216487 thirdparties over 216887 and 157852 products over 280319 are in that case
on the cards that already enable the drag and drop.
- The directory of the object is now forged with get_exdir(), the way the tabs
do: it always uses the id for a thirdparty, whose ref is a company name and is
not unique, and it falls back on the id when the ref is empty. The sub
directory of the module is read with getMultidirOutput(), which knows the
elements that store their documents into one. That function does not return
an empty string when it fails but a string starting with 'error-', so only an
absolute path is accepted: writing into that string would create the files
under the web root.
- fetchObjectByElement() returns an object even when fetch() returned 0. The
object was then not loaded, and the file was stored at the root of the
directory of the module, out of any object. The constructor now throws, and
the endpoint answers the error with the same json contract than a successful
call so that the caller can show it, instead of a fatal error and an http 500.
- An attachment of the same name was silently overwritten, dol_move_uploaded_file()
being called with $allowoverwrite = 1 while the name was checked before the
ref of the object was added as a prefix. The check is done again on the final
name, and on the .noexe suffixed name too, which that function appends to an
executable file.
- An executable file was renamed with that .noexe suffix and then reported as an
error, while it was correctly stored.
- The endpoint called restrictedArea() with an empty feature when the element is
unknown, and the loop of that function then takes no branch at all and grants
the access with no check. It refuses before, with the same http code and the
same message than a refusal, so that a user cannot tell an object that exists
but is not allowed from an object that does not exist. The refusal of an
external user on an object of another third party answered a message of its
own, which allowed the same enumeration.
Comes with the tests of the path resolution, of the fallback of get_exdir(), of
the rejection of the error string of getMultidirOutput(), and of the file name
deduplication.
* FIX Remove the drop area from 13 cards where dropping a file is harmful
Two distinct groups, both of them removing a drop area that only produces a
result the user does not want.
7 pages have no "Attached files" tab at all, so a file dropped on them can be
reached by no screen: a fiscal year and its info page, an accounting model, a
webhook trigger history, an intracomm report, a bookcal calendar and its booking
list. Measured: 4 of them accepted the upload and wrote an orphan file, the
accounting model wrote it at the root of the directory of the module, out of any
object, and the 2 bookcal ones answered an http 500.
6 cards print their tabs inside their edit form, so the drop area covered that
form: a product, an expense report, a VAT payment, a social contribution, a loan
and a salary. Dropping a file there reloads the page, which discards what the
user is typing. A product and an expense report have a second call for the read
only view, so the drop area is only removed from the call of the edit branch;
the 4 others share a single call between both views, so the parameter is now
conditional.
* NEW Drag and drop a file on the 23 remaining cards
Every card of the core that owns an "Attached files" tab can now receive a file
by drag and drop, which was the case of 32 of them only. The 23 added here are
an asset, an event, a various payment, a customer payment, a contact, a donation,
a conference or booth, a shipment, a supplier payment, a leave request, an
evaluation, a job, a position, a skill, a knowledge record, a manufacturing
order, a partnership, a lot, a stock transfer, a task, a reception, a resource
and a workstation.
The tab bar of a leave request and of a resource is printed inside their edit
form, so the drop area is not enabled there: dropping a file reloads the page,
which would discard what the user is typing. The card of a supplier payment
prints its tab bar even when the object was not loaded, so the drop area is only
enabled when it is.
* FIX getMultidirOutput refuses an entity with no directory instead of falling back [skip-claudemd]
The previous revision of this PR fell back on the directory of the current
entity when the entity of the object had none, with a LOG_WARNING. On a
multicompany install that made a caller read, write and above all delete
files in the directory of another entity. Refuse instead: the function
already answers 'error-diroutput-not-defined-for-this-object' when the
module declares no directory at all, so the caller has one behaviour to
handle, not two.
The entity is cast to int, which is what the array index needs and what
silences the four PhanTypeMismatchDimFetchNullable this function reported.
---------
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2716 lines
102 KiB
PHP
2716 lines
102 KiB
PHP
<?php
|
||
/* Copyright (C) 2010-2014 Laurent Destailleur <eldy@users.sourceforge.net>
|
||
* Copyright (C) 2015 Juanjo Menent <jmenent@2byte.es>
|
||
* Copyright (C) 2023 Alexandre Janniaux <alexandre.janniaux@gmail.com>
|
||
* Copyright (C) 2025 MDW <mdeweerd@users.noreply.github.com>
|
||
* Copyright (C) 2025-2026 Frédéric France <frederic.france@free.fr>
|
||
*
|
||
* 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 <https://www.gnu.org/licenses/>.
|
||
* or see https://www.gnu.org/
|
||
*/
|
||
|
||
/**
|
||
* \file test/phpunit/FunctionsLibTest.php
|
||
* \ingroup test
|
||
* \brief PHPUnit test
|
||
* \remarks To run this script as CLI: phpunit filename.php
|
||
*/
|
||
|
||
global $conf,$user,$langs,$db,$mysoc;
|
||
//define('TEST_DB_FORCE_TYPE','mysql'); // This is to force using mysql driver
|
||
//require_once 'PHPUnit/Autoload.php';
|
||
require_once dirname(__FILE__).'/../../htdocs/master.inc.php';
|
||
require_once dirname(__FILE__).'/../../htdocs/core/lib/date.lib.php';
|
||
require_once dirname(__FILE__).'/../../htdocs/product/class/product.class.php';
|
||
require_once dirname(__FILE__).'/../../htdocs/societe/class/societe.class.php';
|
||
require_once dirname(__FILE__).'/../../htdocs/contact/class/contact.class.php';
|
||
require_once dirname(__FILE__).'/CommonClassTest.class.php';
|
||
|
||
if (! defined('NOREQUIREUSER')) {
|
||
define('NOREQUIREUSER', '1');
|
||
}
|
||
if (! defined('NOREQUIREDB')) {
|
||
define('NOREQUIREDB', '1');
|
||
}
|
||
if (! defined('NOREQUIRESOC')) {
|
||
define('NOREQUIRESOC', '1');
|
||
}
|
||
if (! defined('NOREQUIRETRAN')) {
|
||
define('NOREQUIRETRAN', '1');
|
||
}
|
||
if (! defined('NOCSRFCHECK')) {
|
||
define('NOCSRFCHECK', '1');
|
||
}
|
||
if (! defined('NOTOKENRENEWAL')) {
|
||
define('NOTOKENRENEWAL', '1');
|
||
}
|
||
if (! defined('NOREQUIREMENU')) {
|
||
define('NOREQUIREMENU', '1'); // If there is no menu to show
|
||
}
|
||
if (! defined('NOREQUIREHTML')) {
|
||
define('NOREQUIREHTML', '1'); // If we don't need to load the html.form.class.php
|
||
}
|
||
if (! defined('NOREQUIREAJAX')) {
|
||
define('NOREQUIREAJAX', '1');
|
||
}
|
||
if (! defined("NOLOGIN")) {
|
||
define("NOLOGIN", '1'); // If this page is public (can be called outside logged session)
|
||
}
|
||
|
||
print "\n".$langs->trans("CurrentTimeZone").' : '.getServerTimeZoneString();
|
||
print "\n".$langs->trans("CurrentHour").' : '.dol_print_date(dol_now('gmt'), 'dayhour', 'tzserver');
|
||
print "\n";
|
||
|
||
|
||
/**
|
||
* Class for PHPUnit tests
|
||
*
|
||
* @backupGlobals disabled
|
||
* @backupStaticAttributes enabled
|
||
* @remarks backupGlobals must be disabled to have db,conf,user and lang not erased.
|
||
*/
|
||
class FunctionsLibTest extends CommonClassTest
|
||
{
|
||
/**
|
||
* setUpBeforeClass
|
||
*
|
||
* @return void
|
||
*/
|
||
public static function setUpBeforeClass(): void
|
||
{
|
||
global $conf,$user,$langs,$db;
|
||
//$db->begin(); // This is to have all actions inside a transaction even if test launched without suite.
|
||
|
||
if (! function_exists('mb_substr')) {
|
||
print "\n".__METHOD__." function mb_substr must be enabled.\n";
|
||
die(1);
|
||
}
|
||
|
||
if (getDolGlobalInt('MAIN_MAX_DECIMALS_UNIT') != 5) {
|
||
print "\n".__METHOD__." bad setup for number of digits for unit amount. Must be 5 for this test.\n";
|
||
die(1);
|
||
}
|
||
|
||
if (getDolGlobalInt('MAIN_MAX_DECIMALS_TOT') != 2) {
|
||
print "\n".__METHOD__." bad setup for number of digits for unit amount. Must be 2 for this test.\n";
|
||
die(1);
|
||
}
|
||
|
||
print __METHOD__."\n";
|
||
}
|
||
|
||
|
||
/**
|
||
* testGetExdirForObject
|
||
*
|
||
* get_exdir() with $level = 0 and $withoutslash = 1 is the reference implementation used to forge the
|
||
* directory where the documents of an object are stored. FileUpload (the drag and drop of a file on a
|
||
* card) relies on it to store the file into the directory read by the "Attached files" tab.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testGetExdirForObject()
|
||
{
|
||
global $conf, $db;
|
||
|
||
// PRODUCT_USE_OLD_PATH_FOR_PHOTO makes get_exdir() answer a two level path built on the id for a
|
||
// product, instead of the ref. The assertions below are on the default path, so the option is forced
|
||
// off here: it is a global, another test of the suite may have left it on, and the whole suite runs in
|
||
// a single process. The option gets its own coverage at the end of this test.
|
||
$savoldpath = getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO');
|
||
$conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO = 0;
|
||
|
||
// The ref is used when it is defined
|
||
$object = new Product($db);
|
||
$object->id = 42;
|
||
$object->ref = 'MYREF';
|
||
$this->assertSame('MYREF', get_exdir(0, 0, 0, 1, $object, 'product'), 'The ref must be used when it is defined');
|
||
|
||
// The trailing slash is added when $withoutslash is 0. FileUpload appends its own '/' to the
|
||
// result, so it must call get_exdir() with $withoutslash = 1 to avoid a double slash in the path.
|
||
$this->assertSame('MYREF/', get_exdir(0, 0, 0, 0, $object, 'product'), 'A trailing slash is expected when $withoutslash is 0');
|
||
|
||
// The modulepart is deduced from the object when it is not given
|
||
$this->assertSame('MYREF', get_exdir(0, 0, 0, 1, $object), 'The modulepart must be deduced from the object');
|
||
|
||
// The ref is a user input: it must never introduce a directory traversal nor a sub directory
|
||
$object->ref = '../../etc';
|
||
$this->assertStringNotContainsString('..', get_exdir(0, 0, 0, 1, $object, 'product'), 'A ref must never allow a directory traversal');
|
||
$object->ref = 'A/B';
|
||
$this->assertStringNotContainsString('/', get_exdir(0, 0, 0, 1, $object, 'product'), 'A ref must never introduce a sub directory');
|
||
|
||
// The id is used as a fallback when the ref is empty
|
||
$object->ref = '';
|
||
$this->assertSame('42', get_exdir(0, 0, 0, 1, $object, 'product'), 'The id must be used when the ref is empty');
|
||
$object->ref = null;
|
||
$this->assertSame('42', get_exdir(0, 0, 0, 1, $object, 'product'), 'The id must be used when the ref is null');
|
||
$object->ref = '0';
|
||
$this->assertSame('42', get_exdir(0, 0, 0, 1, $object, 'product'), 'A ref "0" is empty for php, so the id is used');
|
||
|
||
// An object with neither a ref nor an id falls back on the directory '0', shared by every unsaved
|
||
// object. A caller must never forge a path from an object it did not load.
|
||
$empty = new Product($db);
|
||
$empty->id = 0;
|
||
$empty->ref = '';
|
||
$this->assertSame('0', get_exdir(0, 0, 0, 1, $empty, 'product'), 'An object with no id and no ref falls back on the directory "0"');
|
||
|
||
// The id is always used for a thirdparty, because its ref is a company name, so it is not unique
|
||
$thirdparty = new Societe($db);
|
||
$thirdparty->id = 7;
|
||
$thirdparty->ref = 'My company';
|
||
$this->assertSame('7', get_exdir(0, 0, 0, 1, $thirdparty, 'societe'), 'The id must be used for a thirdparty');
|
||
$this->assertSame('7', get_exdir(0, 0, 0, 1, $thirdparty, 'thirdparty'), 'The id must be used for a thirdparty');
|
||
|
||
// The rule is on the class, not only on the modulepart: a contact belongs to the module 'societe'
|
||
// but it is not a Societe, so it keeps its ref
|
||
$contact = new Contact($db);
|
||
$contact->id = 8;
|
||
$contact->ref = 'DOE';
|
||
$this->assertSame('DOE', get_exdir(0, 0, 0, 1, $contact, 'contact'), 'A contact is not a thirdparty, its ref is used');
|
||
|
||
// A module storing its documents on several levels returns the level directories only, not the object
|
||
$object->ref = 'MYREF';
|
||
$this->assertSame('2/4', get_exdir(0, 0, 0, 1, $object, 'invoice_supplier'), 'Two levels of directories are expected');
|
||
$this->assertSame('2/4', get_exdir(0, 0, 0, 1, $object, 'supplier_invoice'), 'The two aliases must answer the same directory');
|
||
$this->assertSame('2/4/', get_exdir(0, 0, 0, 0, $object, 'invoice_supplier'), 'A trailing slash is expected when $withoutslash is 0');
|
||
|
||
// The levels are built from the id, not from the ref
|
||
$object->id = 1234;
|
||
$this->assertSame('4/3', get_exdir(0, 0, 0, 1, $object, 'invoice_supplier'), 'The levels must be built from the id');
|
||
|
||
// With PRODUCT_USE_OLD_PATH_FOR_PHOTO a product joins the modules storing on two levels, so its
|
||
// directory is built on the id and the ref is ignored. This is the path the "Attached files" tab of a
|
||
// product reads on such an instance, so FileUpload must forge the same one.
|
||
$conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO = 1;
|
||
$object->id = 42;
|
||
$object->ref = 'MYREF';
|
||
$this->assertSame('2/4', get_exdir(0, 0, 0, 1, $object, 'product'), 'With the old path option a product uses two levels built on its id');
|
||
|
||
$conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO = $savoldpath;
|
||
}
|
||
|
||
/**
|
||
* testGetElementPropertiesDirOutput
|
||
*
|
||
* The 'dir_output' returned for an element must be the directory read by the "Attached files" tab of
|
||
* this element, otherwise a file uploaded by drag and drop is stored but never shown to the user.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testGetElementPropertiesDirOutput()
|
||
{
|
||
global $conf, $db;
|
||
|
||
// A contact is stored into a sub directory of the thirdparty module, see contact/document.php.
|
||
// The exact value is asserted and not only the suffix: an assertion on the suffix alone would also
|
||
// pass on the value '/contact' returned when the module is disabled, which is the bug guarded here.
|
||
if (!isModEnabled('societe')) {
|
||
$this->markTestSkipped('The module societe must be enabled to check the directory of a contact');
|
||
}
|
||
$prop = getElementProperties('contact');
|
||
$this->assertSame('societe', $prop['module']);
|
||
$this->assertSame($conf->societe->multidir_output[$conf->entity].'/contact', $prop['dir_output'], 'A contact is stored into a /contact sub directory');
|
||
$this->assertSame($conf->societe->multidir_temp[$conf->entity].'/contact', $prop['dir_temp'], 'The sub directory applies to the temporary directory too');
|
||
|
||
// The elements added or fixed here must answer the class that is really able to load them, and the
|
||
// table restrictedArea() builds its sql on.
|
||
$expected = array(
|
||
// element => array(module, classname, classpath, classfile, table_element)
|
||
'payment' => array('facture', 'Paiement', 'compta/paiement/class', 'paiement', 'paiement'),
|
||
'payment_supplier' => array('fournisseur', 'PaiementFourn', 'fourn/class', 'paiementfourn', 'paiementfourn'),
|
||
'payment_various' => array('bank', 'PaymentVarious', 'compta/bank/class', 'paymentvarious', 'payment_various'),
|
||
'stocktransfer' => array('stocktransfer', 'StockTransfer', 'product/stock/stocktransfer/class', 'stocktransfer', 'stocktransfer_stocktransfer'),
|
||
'job' => array('hrm', 'Job', 'hrm/class', 'job', 'hrm_job'),
|
||
'position' => array('hrm', 'Position', 'hrm/class', 'position', 'hrm_job_user'),
|
||
'skill' => array('hrm', 'Skill', 'hrm/class', 'skill', 'hrm_skill'),
|
||
'evaluation' => array('hrm', 'Evaluation', 'hrm/class', 'evaluation', 'hrm_evaluation'),
|
||
);
|
||
foreach ($expected as $element => $values) {
|
||
$prop = getElementProperties($element);
|
||
|
||
$this->assertSame($element, $prop['element'], 'The element '.$element.' must not be truncated by the myobject_mysubobject rule');
|
||
$this->assertSame($values[0], $prop['module'], 'Wrong module for the element '.$element);
|
||
$this->assertSame($values[1], $prop['classname'], 'Wrong classname for the element '.$element);
|
||
$this->assertSame($values[2], $prop['classpath'], 'Wrong classpath for the element '.$element);
|
||
$this->assertSame($values[3], $prop['classfile'], 'Wrong classfile for the element '.$element);
|
||
$this->assertSame($values[4], $prop['table_element'], 'Wrong table for the element '.$element);
|
||
|
||
// The class must really exist, otherwise fetchObjectByElement() ends on a fatal error
|
||
$file = DOL_DOCUMENT_ROOT.'/'.$prop['classpath'].'/'.$prop['classfile'].'.class.php';
|
||
$this->assertFileExists($file, 'The class file of the element '.$element.' does not exist');
|
||
require_once $file;
|
||
$this->assertTrue(class_exists($prop['classname']), 'The class '.$prop['classname'].' of the element '.$element.' does not exist');
|
||
|
||
// And the table must exist too, restrictedArea() builds its sql on it.
|
||
// The table of an optional module is only created when the module is enabled, so the check is
|
||
// skipped otherwise: the mapping asserted above does not depend on the module being enabled.
|
||
if (isModEnabled($values[0])) {
|
||
$sql = "SELECT COUNT(*) as nb FROM ".$db->prefix().$db->escape($values[4]);
|
||
$resql = $db->query($sql);
|
||
$this->assertNotFalse($resql, 'The table '.$values[4].' of the element '.$element.' is not readable');
|
||
if ($resql) {
|
||
$db->free($resql);
|
||
}
|
||
}
|
||
}
|
||
|
||
// The elements of the hrm module are all stored into a sub directory named after the element
|
||
if (isModEnabled('hrm')) {
|
||
foreach (array('job', 'position', 'skill', 'evaluation') as $element) {
|
||
$prop = getElementProperties($element);
|
||
$this->assertSame($conf->hrm->dir_output.'/'.$element, $prop['dir_output'], 'The element '.$element.' is stored into a /'.$element.' sub directory');
|
||
$this->assertSame($conf->hrm->dir_temp.'/'.$element, $prop['dir_temp'], 'The sub directory applies to the temporary directory of '.$element.' too');
|
||
}
|
||
}
|
||
|
||
// The sub directory must not be appended when the module is disabled, otherwise we would return a
|
||
// path at the root of the file system (for example '/contact') instead of an empty string. This is
|
||
// only observable with the module really off, so it is simulated here.
|
||
$elements = array('contact' => 'societe', 'job' => 'hrm', 'position' => 'hrm', 'skill' => 'hrm',
|
||
'evaluation' => 'hrm', 'conferenceorbooth' => 'eventorganization');
|
||
foreach ($elements as $element => $module) {
|
||
$savconfmodule = isset($conf->$module) ? $conf->$module : null;
|
||
$savmodules = $conf->modules;
|
||
|
||
unset($conf->$module);
|
||
unset($conf->modules[$module]);
|
||
|
||
try {
|
||
$prop = getElementProperties($element);
|
||
$this->assertSame('', $prop['dir_output'], 'The dir_output of the element '.$element.' must be empty when the module '.$module.' is disabled, not the sub directory alone');
|
||
$this->assertSame('', $prop['dir_temp'], 'The dir_temp of the element '.$element.' must be empty when the module '.$module.' is disabled');
|
||
} finally {
|
||
if ($savconfmodule !== null) {
|
||
$conf->$module = $savconfmodule;
|
||
}
|
||
$conf->modules = $savmodules;
|
||
}
|
||
}
|
||
|
||
// So the directory of an element is either empty, or a directory of the data directory of Dolibarr,
|
||
// and never the sentinel string returned by getMultidirOutput() when it fails.
|
||
$allelements = array('contact', 'job', 'position', 'skill', 'evaluation', 'conferenceorbooth',
|
||
'partnership', 'stocktransfer', 'payment', 'payment_supplier', 'payment_various', 'product',
|
||
'societe', 'action', 'expedition', 'reception', 'don', 'expensereport', 'holiday', 'mo',
|
||
'productlot', 'resource', 'workstation', 'knowledgerecord', 'asset', 'salary', 'chargesociales');
|
||
foreach ($allelements as $element) {
|
||
$prop = getElementProperties($element);
|
||
foreach (array('dir_output', 'dir_temp') as $key) {
|
||
$dir = (string) $prop[$key];
|
||
$this->assertStringNotContainsString('error-diroutput-not-defined-for-this-object', $dir, 'The '.$key.' of the element '.$element.' must never be the sentinel of getMultidirOutput');
|
||
$this->assertTrue(
|
||
$dir === '' || strpos($dir, DOL_DATA_ROOT) === 0,
|
||
'The '.$key.' of the element '.$element.' must be empty or inside DOL_DATA_ROOT, got "'.$dir.'"'
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* testDolCheckFilters
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function testDolCheckFilters()
|
||
{
|
||
global $conf, $langs, $db;
|
||
|
||
// A sql with global parenthesis at level 2
|
||
$error = '';
|
||
$parenthesislevel = 0;
|
||
$sql = '(( ... (a:=:1) .éééé. (b:=:1) ... ))';
|
||
$result = dolCheckFilters($sql, $error, $parenthesislevel);
|
||
$this->assertEquals(2, $parenthesislevel);
|
||
$this->assertTrue($result);
|
||
|
||
// A sql with global parenthesis at level 2
|
||
$error = '';
|
||
$parenthesislevel = 0;
|
||
$sql = '(((((a:=:1) ... ) .éééé.. (b:=:1) ..) ... ))';
|
||
$result = dolCheckFilters($sql, $error, $parenthesislevel);
|
||
$this->assertEquals(2, $parenthesislevel);
|
||
$this->assertTrue($result);
|
||
|
||
// A sql with global parenthesis at level 2
|
||
$error = '';
|
||
$parenthesislevel = 0;
|
||
$sql = '((... (((a:=:1) ... ( .éééé.. (b:=:1) ..)))))';
|
||
$result = dolCheckFilters($sql, $error, $parenthesislevel);
|
||
$this->assertEquals(2, $parenthesislevel);
|
||
$this->assertTrue($result);
|
||
|
||
// A sql with global parenthesis at level 0
|
||
$error = '';
|
||
$parenthesislevel = 0;
|
||
$sql = '(a:=:1) ... (b:=:1) éééé ...';
|
||
$result = dolCheckFilters($sql, $error, $parenthesislevel);
|
||
$this->assertEquals(0, $parenthesislevel);
|
||
$this->assertTrue($result);
|
||
|
||
// A sql with bad balance
|
||
$error = '';
|
||
$parenthesislevel = 0;
|
||
$sql = '((((a:=:1) ... (b:=:1) éééé ..))';
|
||
$result = dolCheckFilters($sql, $error, $parenthesislevel);
|
||
$this->assertEquals(0, $parenthesislevel);
|
||
$this->assertFalse($result);
|
||
|
||
// A sql with bad balance
|
||
$error = '';
|
||
$parenthesislevel = 0;
|
||
$sql = '(((a:=:1) ... (b:=:1) éééé ..)))';
|
||
$result = dolCheckFilters($sql, $error, $parenthesislevel);
|
||
$this->assertEquals(0, $parenthesislevel);
|
||
$this->assertFalse($result);
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
/**
|
||
* testDolForgeExplodeAnd
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function testDolForgeExplodeAnd()
|
||
{
|
||
$tmp = dolForgeExplodeAnd('');
|
||
$this->assertEquals(0, count($tmp));
|
||
|
||
$tmp = dolForgeExplodeAnd('(a:=:1)');
|
||
$this->assertEquals('(a:=:1)', $tmp[0]);
|
||
|
||
$tmp = dolForgeExplodeAnd('(a:=:1) AND (b:=:2)');
|
||
$this->assertEquals('(a:=:1)', $tmp[0]);
|
||
$this->assertEquals('(b:=:2)', $tmp[1]);
|
||
|
||
$tmp = dolForgeExplodeAnd('(a:=:1) AND ((b:=:2) OR (c:=:3))');
|
||
$this->assertEquals('(a:=:1)', $tmp[0]);
|
||
$this->assertEquals('((b:=:2) OR (c:=:3))', $tmp[1]);
|
||
|
||
$tmp = dolForgeExplodeAnd('(a:=:1) AND (b:=:2) OR (c:=:3)');
|
||
$this->assertEquals('(a:=:1)', $tmp[0]);
|
||
$this->assertEquals('(b:=:2) OR (c:=:3)', $tmp[1]);
|
||
|
||
$tmp = dolForgeExplodeAnd('(a:=:1) OR (b:=:2) AND (c:=:3)');
|
||
$this->assertEquals('(a:=:1) OR (b:=:2)', $tmp[0]);
|
||
$this->assertEquals('(c:=:3)', $tmp[1]);
|
||
|
||
$tmp = dolForgeExplodeAnd('(a:=:1) OR ((b:=:2) AND (c:=:3))');
|
||
$this->assertEquals('(a:=:1) OR ((b:=:2) AND (c:=:3))', $tmp[0]);
|
||
|
||
$tmp = dolForgeExplodeAnd('((y:=:1) AND (p:=:8))');
|
||
$this->assertEquals('(y:=:1)', $tmp[0]);
|
||
$this->assertEquals('(p:=:8)', $tmp[1]);
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* testDolForgeSQLCriteriaCallback
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function testDolForgeSQLCriteriaCallback()
|
||
{
|
||
global $conf, $langs, $db;
|
||
|
||
// Test using like
|
||
$filter = "(lastname:like:'%aaa%') OR (firstname:like:'%bbb%')";
|
||
$sql = forgeSQLFromUniversalSearchCriteria($filter);
|
||
$this->assertEquals(" AND ((lastname LIKE '%aaa%') OR (firstname LIKE '%bbb%'))", $sql);
|
||
|
||
// Test on NOW
|
||
$filter = "(client:!=:8) AND (datefin:>=:'__NOW__')";
|
||
$sql = forgeSQLFromUniversalSearchCriteria($filter);
|
||
$this->assertStringContainsStringIgnoringCase(" AND ((client <> 8) AND (datefin >= '", $sql);
|
||
|
||
// An attempt for SQL injection
|
||
$filter = 'if(now()=sysdate()%2Csleep(6)%2C0)';
|
||
$sql = forgeSQLFromUniversalSearchCriteria($filter);
|
||
$this->assertEquals('Filter error - Bad syntax of the search string', $sql);
|
||
|
||
// A real search string
|
||
$filter = '(((statut:=:1) or (entity:in:__AAA__)) and (abc:<:2.0) and (abc:!=:1.23))';
|
||
$sql = forgeSQLFromUniversalSearchCriteria($filter);
|
||
$this->assertEquals(' AND ((((statut = 1) or (entity IN (__AAA__))) and (abc < 2) and (abc <> 1.23)))', $sql);
|
||
|
||
// A real search string
|
||
$filter = "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.date_creation:<:'2016-01-01 12:30:00') or (t.nature:is:NULL)";
|
||
$sql = forgeSQLFromUniversalSearchCriteria($filter);
|
||
$this->assertEquals(" AND ((t.ref LIKE 'SO-%') or (t.date_creation < '20160101') or (t.date_creation < '2016-01-01 12:30:00') or (t.nature IS NULL))", $sql);
|
||
|
||
// A real search string
|
||
$filter = "(t.fieldstring:=:'aaa ttt')";
|
||
$sql = forgeSQLFromUniversalSearchCriteria($filter);
|
||
$this->assertEquals(" AND ((t.fieldstring = 'aaa ttt'))", $sql);
|
||
|
||
// Check that parenthesis are NOT allowed inside the last operand. Very important.
|
||
$filter = "(t.fieldint:=:(1,2))";
|
||
$sql = forgeSQLFromUniversalSearchCriteria($filter);
|
||
$this->assertEquals("Filter error - Bad syntax of the search string", $sql);
|
||
|
||
// Check that ' is escaped into the last operand
|
||
$filter = "(t.fieldstring:=:'aaa'ttt')";
|
||
$sql = forgeSQLFromUniversalSearchCriteria($filter);
|
||
|
||
if ($db->type == 'mysqli') {
|
||
$this->assertEquals(" AND ((t.fieldstring = 'aaa\'ttt'))", $sql); // with mysql
|
||
} else {
|
||
$this->assertEquals(" AND ((t.fieldstring = 'aaa''ttt'))", $sql); // with pgsql
|
||
}
|
||
|
||
$filter = "(t.fk_soc:IN:1,2)";
|
||
$sql = forgeSQLFromUniversalSearchCriteria($filter);
|
||
$this->assertEquals(" AND ((t.fk_soc IN (1,2)))", $sql);
|
||
|
||
$filter = "(t.fk_soc:IN:'1','2=b')";
|
||
$sql = forgeSQLFromUniversalSearchCriteria($filter);
|
||
$this->assertEquals(" AND ((t.fk_soc IN ('1','2=b')))", $sql);
|
||
|
||
// Test use of forbiddenstring
|
||
$errorstr = '';
|
||
$filter = "(t.fk_soc:IN:'1','2=b')";
|
||
$sql = forgeSQLFromUniversalSearchCriteria($filter, $errorstr, 0, 0, 0, array('aaa'));
|
||
$this->assertEquals(" AND ((t.fk_soc IN ('1','2=b')))", $sql);
|
||
|
||
$filter = "(t.api_key:IN:'1','2=b')";
|
||
$sql = forgeSQLFromUniversalSearchCriteria($filter, $errorstr, 0, 0, 0, array('aaa'));
|
||
$this->assertEquals(" AND (1=1)", $sql);
|
||
|
||
$filter = "(t.fk_soc:IN:'1','2=b')";
|
||
$sql = forgeSQLFromUniversalSearchCriteria($filter, $errorstr, 0, 0, 0, array('fk_soc'));
|
||
$this->assertEquals(" AND (1=1)", $sql);
|
||
|
||
|
||
global $dolibarr_allow_unsecured_select_in_extrafields_filter;
|
||
|
||
// If $dolibarr_allow_unsecured_select_in_extrafields_filter is set
|
||
$dolibarr_allow_unsecured_select_in_extrafields_filter = 1;
|
||
|
||
$filter = "(t.fk_soc:IN:SELECT rowid FROM llx_societe WHERE fournisseur = 1)";
|
||
$sql = forgeSQLFromUniversalSearchCriteria($filter);
|
||
$this->assertEquals(" AND ((t.fk_soc IN (SELECT rowid FROM llx_societe WHERE fournisseur = 1)))", $sql);
|
||
|
||
// If $dolibarr_allow_unsecured_select_in_extrafields_filter is unset (default)
|
||
$dolibarr_allow_unsecured_select_in_extrafields_filter = 0;
|
||
|
||
$filter = "(t.fk_soc:IN:SELECT rowid FROM llx_societe WHERE fournisseur = 1)";
|
||
$sql = forgeSQLFromUniversalSearchCriteria($filter);
|
||
$this->assertEquals(" AND ((t.fk_soc IN (SELECTrowidFROMllx_societeWHEREfournisseur1)))", $sql);
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
/**
|
||
* testDolClone
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testDolClone()
|
||
{
|
||
global $db;
|
||
|
||
$newproduct1 = new Product($db);
|
||
$newproduct1->initAsSpecimen();
|
||
|
||
print __METHOD__." this->savdb has type ".(is_resource($db->db) ? get_resource_type($db->db) : (is_object($db->db) ? 'object' : 'unknown'))."\n";
|
||
print __METHOD__." newproduct1->db->db has type ".(is_resource($newproduct1->db->db) ? get_resource_type($newproduct1->db->db) : (is_object($newproduct1->db->db) ? 'object' : 'unknown'))."\n";
|
||
$this->assertEquals($db->connected, 1, 'Savdb is connected');
|
||
$this->assertNotNull($newproduct1->db->db, 'newproduct1->db is not null');
|
||
|
||
$newproductcloned1 = dol_clone($newproduct1);
|
||
|
||
print __METHOD__." this->savdb has type ".(is_resource($db->db) ? get_resource_type($db->db) : (is_object($db->db) ? 'object' : 'unknown'))."\n";
|
||
print __METHOD__." newproduct1->db->db has type ".(is_resource($newproduct1->db->db) ? get_resource_type($newproduct1->db->db) : (is_object($newproduct1->db->db) ? 'object' : 'unknown'))."\n";
|
||
$this->assertEquals($db->connected, 1, 'Savdb is connected');
|
||
$this->assertNotNull($newproduct1->db->db, 'newproduct1->db is not null');
|
||
}
|
||
|
||
/**
|
||
* testDolCloneInArray
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testDolCloneInArray()
|
||
{
|
||
global $db;
|
||
|
||
$newproduct1 = new Product($db);
|
||
$newproduct1->initAsSpecimen();
|
||
|
||
$newproductclonedinarray1 = dol_clone_in_array($newproduct1);
|
||
|
||
print __METHOD__." newproductclonedinarray1[db] must be null\n";
|
||
$this->assertNull((empty($newproductclonedinarray1['db']) ? null : 'defined'), 'newproductclonedinarray1[db] is null');
|
||
$this->assertNotNull($newproduct1->db->db, 'newproduct1->db is not null');
|
||
}
|
||
|
||
/**
|
||
* testNum2Alpha
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testNum2Alpha()
|
||
{
|
||
$result = num2Alpha(0);
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals($result, 'A', 'Check num2Alpha 0');
|
||
|
||
$result = num2Alpha(5);
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals($result, 'F', 'Check num2Alpha 5');
|
||
|
||
$result = num2Alpha(26);
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals($result, 'AA', 'Check num2Alpha 26');
|
||
}
|
||
|
||
/**
|
||
* testIsValidEmail
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testIsValidEmail()
|
||
{
|
||
// Nb of line is same than entry text
|
||
|
||
$input = "bidon@bademail";
|
||
$result = isValidEmail($input);
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertFalse($result, 'Check isValidEmail '.$input);
|
||
|
||
$input = "test@yahoo.com";
|
||
$result = isValidEmail($input);
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertTrue($result, 'Check isValidEmail '.$input);
|
||
|
||
$input = "The name of sender <test@yahoo.com>";
|
||
$result = isValidEmail($input);
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertFalse($result, 'Check isValidEmail '.$input);
|
||
|
||
$input = "1234.abcdefg@domainame.com.br";
|
||
$result = isValidEmail($input);
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertTrue($result, 'Check isValidEmail '.$input);
|
||
|
||
$input = "1234.abcdefg@domainame.entreprises";
|
||
$result = isValidEmail($input);
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertTrue($result, 'Check isValidEmail '.$input);
|
||
}
|
||
|
||
/**
|
||
* testIsValidMXRecord
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testIsValidMXRecord()
|
||
{
|
||
// Nb of line is same than entry text
|
||
|
||
$input = "yahoo.com";
|
||
$result = isValidMXRecord($input);
|
||
print __METHOD__." ".$input." result=".$result."\n";
|
||
$this->assertEquals(1, $result);
|
||
|
||
$input = "yhaoo.com";
|
||
$result = isValidMXRecord($input);
|
||
print __METHOD__." ".$input." result=".$result."\n";
|
||
$this->assertEquals(0, $result);
|
||
|
||
$input = "dolibarr.fr";
|
||
$result = isValidMXRecord($input);
|
||
print __METHOD__." ".$input." result=".$result."\n";
|
||
$this->assertEquals(0, $result);
|
||
|
||
// Note: intentionally not a .mil domain (some CI network environments filter/block .mil DNS
|
||
// resolution intermittently, which made this assertion flaky without any actual code issue).
|
||
$input = "microsoft.com";
|
||
$result = isValidMXRecord($input);
|
||
print __METHOD__." ".$input." result=".$result."\n";
|
||
$this->assertEquals(1, $result);
|
||
}
|
||
|
||
/**
|
||
* testDolGetFirstLineOfText
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testDolGetFirstLineOfText()
|
||
{
|
||
// Nb of line is same than entry text
|
||
|
||
$input = "aaaa";
|
||
$result = dolGetFirstLineOfText($input);
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals("aaaa", $result);
|
||
|
||
$input = "aaaa\nbbbbbbbbbbbb\n";
|
||
$result = dolGetFirstLineOfText($input, 2);
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals("aaaa\nbbbbbbbbbbbb", $result);
|
||
|
||
$input = "aaaa<br>bbbbbbbbbbbb<br>";
|
||
$result = dolGetFirstLineOfText($input, 2);
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals("aaaa<br>\nbbbbbbbbbbbb", $result);
|
||
|
||
// Nb of line is lower
|
||
|
||
$input = "aaaa\nbbbbbbbbbbbb\ncccccc\n";
|
||
$result = dolGetFirstLineOfText($input);
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals("aaaa...", $result);
|
||
|
||
$input = "aaaa<br>bbbbbbbbbbbb<br>cccccc<br>";
|
||
$result = dolGetFirstLineOfText($input);
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals("aaaa...", $result);
|
||
|
||
$input = "aaaa\nbbbbbbbbbbbb\ncccccc\n";
|
||
$result = dolGetFirstLineOfText($input, 2);
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals("aaaa\nbbbbbbbbbbbb...", $result);
|
||
|
||
$input = "aaaa<br>bbbbbbbbbbbb<br>cccccc<br>";
|
||
$result = dolGetFirstLineOfText($input, 2);
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals("aaaa<br>\nbbbbbbbbbbbb...", $result);
|
||
|
||
// Nb of line is higher
|
||
|
||
$input = "aaaa<br>bbbbbbbbbbbb<br>cccccc";
|
||
$result = dolGetFirstLineOfText($input, 100);
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals("aaaa<br>\nbbbbbbbbbbbb<br>\ncccccc", $result, 'dolGetFirstLineOfText with nb 100 a');
|
||
|
||
$input = "aaaa<br>bbbbbbbbbbbb<br>cccccc<br>";
|
||
$result = dolGetFirstLineOfText($input, 100);
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals("aaaa<br>\nbbbbbbbbbbbb<br>\ncccccc", $result, 'dolGetFirstLineOfText with nb 100 b');
|
||
|
||
$input = "aaaa<br>bbbbbbbbbbbb<br>cccccc<br>\n";
|
||
$result = dolGetFirstLineOfText($input, 100);
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals("aaaa<br>\nbbbbbbbbbbbb<br>\ncccccc", $result, 'dolGetFirstLineOfText with nb 100 c');
|
||
}
|
||
|
||
|
||
/**
|
||
* testDolBuildPath
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testDolBuildPath()
|
||
{
|
||
/*$tmp=dol_buildpath('/google/oauth2callback.php', 0);
|
||
var_dump($tmp);
|
||
*/
|
||
|
||
/*$tmp=dol_buildpath('/google/oauth2callback.php', 1);
|
||
var_dump($tmp);
|
||
*/
|
||
|
||
$result = dol_buildpath('/google/oauth2callback.php', 2);
|
||
print __METHOD__." dol_buildpath result=".$result."\n";
|
||
$this->assertStringStartsWith('http', $result);
|
||
|
||
$result = dol_buildpath('/google/oauth2callback.php', 3);
|
||
print __METHOD__." dol_buildpath result=".$result."\n";
|
||
$this->assertStringStartsWith('http', $result);
|
||
}
|
||
|
||
|
||
/**
|
||
* testGetBrowserInfo
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testGetBrowserInfo()
|
||
{
|
||
// MSIE 5.0
|
||
$user_agent = 'Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt; KITV4 Wanadoo; KITV5 Wanadoo)';
|
||
$tmp = getBrowserInfo($user_agent);
|
||
$this->assertEquals('ie', $tmp['browsername']);
|
||
$this->assertEquals('5.0', $tmp['browserversion']);
|
||
$this->assertEmpty($tmp['phone']);
|
||
$this->assertFalse($tmp['tablet']);
|
||
$this->assertEquals('classic', $tmp['layout']);
|
||
|
||
// Firefox 0.9.1
|
||
$user_agent = 'Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.5a) Gecko/20030728 Mozilla Firefox/0.9.1';
|
||
$tmp = getBrowserInfo($user_agent);
|
||
$this->assertEquals('firefox', $tmp['browsername']);
|
||
$this->assertEquals('0.9.1', $tmp['browserversion']);
|
||
$this->assertEmpty($tmp['phone']);
|
||
$this->assertFalse($tmp['tablet']);
|
||
$this->assertEquals('classic', $tmp['layout']);
|
||
|
||
$user_agent = 'Mozilla/3.0 (Windows 98; U) Opera 6.03 [en]';
|
||
$tmp = getBrowserInfo($user_agent);
|
||
$this->assertEquals('opera', $tmp['browsername']);
|
||
$this->assertEquals('6.03', $tmp['browserversion']);
|
||
$this->assertEmpty($tmp['phone']);
|
||
$this->assertFalse($tmp['tablet']);
|
||
$this->assertEquals('classic', $tmp['layout']);
|
||
|
||
$user_agent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.21 (KHTML, like Gecko) Chrome/19.0.1042.0 Safari/535.21';
|
||
$tmp = getBrowserInfo($user_agent);
|
||
$this->assertEquals('chrome', $tmp['browsername']);
|
||
$this->assertEquals('19.0.1042.0', $tmp['browserversion']);
|
||
$this->assertEmpty($tmp['phone']);
|
||
$this->assertFalse($tmp['tablet']);
|
||
$this->assertEquals('classic', $tmp['layout']);
|
||
|
||
$user_agent = 'chrome (Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11)';
|
||
$tmp = getBrowserInfo($user_agent);
|
||
$this->assertEquals('chrome', $tmp['browsername']);
|
||
$this->assertEquals('17.0.963.56', $tmp['browserversion']);
|
||
$this->assertEmpty($tmp['phone']);
|
||
$this->assertFalse($tmp['tablet']);
|
||
$this->assertEquals('classic', $tmp['layout']);
|
||
|
||
$user_agent = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1';
|
||
$tmp = getBrowserInfo($user_agent);
|
||
$this->assertEquals('safari', $tmp['browsername']);
|
||
$this->assertEquals('533.21.1', $tmp['browserversion']);
|
||
$this->assertEmpty($tmp['phone']);
|
||
$this->assertFalse($tmp['tablet']);
|
||
$this->assertEquals('classic', $tmp['layout']);
|
||
|
||
// Internet Explorer 11
|
||
$user_agent = 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko';
|
||
$tmp = getBrowserInfo($user_agent);
|
||
$this->assertEquals('ie', $tmp['browsername']);
|
||
$this->assertEquals('11.0', $tmp['browserversion']);
|
||
$this->assertEmpty($tmp['phone']);
|
||
$this->assertFalse($tmp['tablet']);
|
||
$this->assertEquals('classic', $tmp['layout']);
|
||
|
||
// Internet Explorer 11 bis
|
||
$user_agent = 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; NP06; rv:11.0) like Gecko';
|
||
$tmp = getBrowserInfo($user_agent);
|
||
$this->assertEquals('ie', $tmp['browsername']);
|
||
$this->assertEquals('11.0', $tmp['browserversion']);
|
||
$this->assertEmpty($tmp['phone']);
|
||
$this->assertFalse($tmp['tablet']);
|
||
$this->assertEquals('classic', $tmp['layout']);
|
||
|
||
// iPad
|
||
$user_agent = 'Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25';
|
||
$tmp = getBrowserInfo($user_agent);
|
||
$this->assertEquals('safari', $tmp['browsername']);
|
||
$this->assertEquals('8536.25', $tmp['browserversion']);
|
||
$this->assertEquals('ios', $tmp['browseros']);
|
||
$this->assertEquals('tablet', $tmp['layout']);
|
||
$this->assertEquals('iphone', $tmp['phone']);
|
||
|
||
// Lynx
|
||
$user_agent = 'Lynx/2.8.8dev.3 libwww‑FM/2.14 SSL‑MM/1.4.1';
|
||
$tmp = getBrowserInfo($user_agent);
|
||
$this->assertEquals('textbrowser', $tmp['browsername']);
|
||
$this->assertEquals('2.8.8', $tmp['browserversion']);
|
||
$this->assertEquals('unknown', $tmp['browseros']);
|
||
$this->assertEquals('classic', $tmp['layout']);
|
||
|
||
// W3M
|
||
$user_agent = 'w3m/1.2.3-git123456';
|
||
$tmp = getBrowserInfo($user_agent);
|
||
$this->assertEquals('textbrowser', $tmp['browsername']);
|
||
$this->assertEquals('1.2.3', $tmp['browserversion']);
|
||
$this->assertEquals('unknown', $tmp['browseros']);
|
||
$this->assertEquals('classic', $tmp['layout']);
|
||
}
|
||
|
||
|
||
/**
|
||
* testGetLanguageCodeFromCountryCode
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testGetLanguageCodeFromCountryCode()
|
||
{
|
||
global $mysoc;
|
||
|
||
$language = getLanguageCodeFromCountryCode('US');
|
||
$this->assertEquals('en_US', $language, 'US');
|
||
|
||
$language = getLanguageCodeFromCountryCode('ES');
|
||
$this->assertEquals('es_ES', $language, 'ES');
|
||
|
||
$language = getLanguageCodeFromCountryCode('CL');
|
||
$this->assertEquals('es_CL', $language, 'CL');
|
||
|
||
$language = getLanguageCodeFromCountryCode('CA');
|
||
$this->assertEquals('en_CA', $language, 'CA');
|
||
|
||
$language = getLanguageCodeFromCountryCode('MQ');
|
||
$this->assertEquals('fr_CA', $language);
|
||
|
||
$language = getLanguageCodeFromCountryCode('FR');
|
||
$this->assertEquals('fr_FR', $language);
|
||
|
||
$language = getLanguageCodeFromCountryCode('BE');
|
||
$this->assertEquals('fr_BE', $language);
|
||
|
||
$mysoc->country_code = 'FR';
|
||
$language = getLanguageCodeFromCountryCode('CH');
|
||
$this->assertEquals('fr_CH', $language);
|
||
|
||
$mysoc->country_code = 'DE';
|
||
$language = getLanguageCodeFromCountryCode('CH');
|
||
$this->assertEquals('de_CH', $language);
|
||
|
||
$language = getLanguageCodeFromCountryCode('DE');
|
||
$this->assertEquals('de_DE', $language);
|
||
|
||
$language = getLanguageCodeFromCountryCode('SA');
|
||
$this->assertEquals('ar_SA', $language);
|
||
|
||
$language = getLanguageCodeFromCountryCode('SE');
|
||
$this->assertEquals('sv_SE', $language);
|
||
|
||
$language = getLanguageCodeFromCountryCode('DK');
|
||
$this->assertEquals('da_DK', $language);
|
||
}
|
||
|
||
/**
|
||
* testDolTextIsHtml
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testDolTextIsHtml()
|
||
{
|
||
// True
|
||
$input = '<html>xxx</html>';
|
||
$after = dol_textishtml($input);
|
||
$this->assertTrue($after, 'Test with html tag');
|
||
$input = '<body>xxx</body>';
|
||
$after = dol_textishtml($input);
|
||
$this->assertTrue($after, 'Test with body tag');
|
||
$input = 'xxx <b>yyy</b> zzz';
|
||
$after = dol_textishtml($input);
|
||
$this->assertTrue($after, 'Test with b tag');
|
||
$input = 'xxx <u>yyy</u> zzz';
|
||
$after = dol_textishtml($input);
|
||
$this->assertTrue($after, 'Test with u tag');
|
||
$input = 'text with <div>some div</div>';
|
||
$after = dol_textishtml($input);
|
||
$this->assertTrue($after, 'Test with div tag');
|
||
$input = 'text with HTML entities';
|
||
$after = dol_textishtml($input);
|
||
$this->assertTrue($after, 'Test with entities tag');
|
||
$input = 'xxx<br>';
|
||
$after = dol_textishtml($input);
|
||
$this->assertTrue($after, 'Test with entities br');
|
||
$input = 'xxx<br >';
|
||
$after = dol_textishtml($input);
|
||
$this->assertTrue($after, 'Test with entities br');
|
||
$input = 'xxx<br style="eee">';
|
||
$after = dol_textishtml($input);
|
||
$this->assertTrue($after, 'Test with entities br and attributes');
|
||
$input = 'xxx<br style="eee" >';
|
||
$after = dol_textishtml($input);
|
||
$this->assertTrue($after, 'Test with entities br and attributes bis');
|
||
$input = '<h2>abc</h2>';
|
||
$after = dol_textishtml($input);
|
||
$this->assertTrue($after, 'Test with entities h2');
|
||
$input = '<img id="abc" src="https://xxx.com/aaa/image.png" />';
|
||
$after = dol_textishtml($input);
|
||
$this->assertTrue($after, 'Test with img tag');
|
||
$input = '<a class="azerty" href="https://xxx.com/aaa/image.png" />';
|
||
$after = dol_textishtml($input);
|
||
$this->assertTrue($after, 'Test with a tag');
|
||
$input = 'This is a text with html spaces';
|
||
$after = dol_textishtml($input);
|
||
$this->assertTrue($after, 'Test with a ');
|
||
$input = 'This is a text with accent é';
|
||
$after = dol_textishtml($input);
|
||
$this->assertTrue($after, 'Test with a é');
|
||
$input = '<i class="abc">xxx</i>';
|
||
$after = dol_textishtml($input);
|
||
$this->assertTrue($after, 'Test with i tag and class;');
|
||
|
||
// False
|
||
$input = 'xxx < br>';
|
||
$after = dol_textishtml($input);
|
||
$this->assertFalse($after);
|
||
$input = 'xxx <email@email.com>'; // <em> is html, <em... is not
|
||
$after = dol_textishtml($input);
|
||
$this->assertFalse($after);
|
||
$input = 'xxx <brstyle="ee">';
|
||
$after = dol_textishtml($input);
|
||
$this->assertFalse($after);
|
||
$input = 'This is a text with html comments <!-- comment -->'; // we suppose this is not enough to be html content
|
||
$after = dol_textishtml($input);
|
||
$this->assertFalse($after);
|
||
|
||
$input = "A text\nwith a link https://aaa?param=abc&param2=def";
|
||
$after = dol_textishtml($input);
|
||
$this->assertFalse($after);
|
||
}
|
||
|
||
|
||
/**
|
||
* testDolHtmlCleanLastBr
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function testDolHtmlCleanLastBr()
|
||
{
|
||
$input = "A string\n";
|
||
$after = dol_htmlcleanlastbr($input);
|
||
$this->assertEquals("A string", $after);
|
||
|
||
$input = "A string first\nA string second\n";
|
||
$after = dol_htmlcleanlastbr($input);
|
||
$this->assertEquals("A string first\nA string second", $after);
|
||
|
||
$input = "A string\n\n\n";
|
||
$after = dol_htmlcleanlastbr($input);
|
||
$this->assertEquals("A string", $after);
|
||
|
||
$input = "A string<br>";
|
||
$after = dol_htmlcleanlastbr($input);
|
||
$this->assertEquals("A string", $after);
|
||
|
||
$input = "A string first<br>\nA string second<br>";
|
||
$after = dol_htmlcleanlastbr($input);
|
||
$this->assertEquals("A string first<br>\nA string second", $after);
|
||
|
||
$input = "A string\n<br type=\"_moz\" />\n";
|
||
$after = dol_htmlcleanlastbr($input);
|
||
$this->assertEquals("A string", $after);
|
||
|
||
$input = "A string\n<br><br />\n\n";
|
||
$after = dol_htmlcleanlastbr($input);
|
||
$this->assertEquals("A string", $after);
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* testDolConcat
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function testDolConcat()
|
||
{
|
||
$text1 = "A string 1";
|
||
$text2 = "A string 2"; // text 1 and 2 are text, concat need only \n
|
||
$after = dol_concatdesc($text1, $text2);
|
||
$this->assertEquals("A string 1\nA string 2", $after);
|
||
|
||
$text1 = "A<br>string 1";
|
||
$text2 = "A string 2"; // text 1 is html, concat need <br>\n
|
||
$after = dol_concatdesc($text1, $text2);
|
||
$this->assertEquals("A<br>string 1<br>\nA string 2", $after);
|
||
|
||
$text1 = "A string 1";
|
||
$text2 = "A <b>string</b> 2"; // text 2 is html, concat need <br>\n
|
||
$after = dol_concatdesc($text1, $text2);
|
||
$this->assertEquals("A string 1<br>\nA <b>string</b> 2", $after);
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
/**
|
||
* testDolStringNoSpecial
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function testDolStringNoSpecial()
|
||
{
|
||
include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
|
||
|
||
$text = "A string with space and special char like ' or ° and more...\n";
|
||
$after = dol_string_nospecial($text, '_', '', '', 0);
|
||
$this->assertEquals("A_string_with_space_and_special_char_like___or___and_more...\n", $after, "testDolStringNoSpecial 1");
|
||
|
||
$text = "A string with space and special char like ' or ° and more...\n";
|
||
$after = dol_string_nospecial($text, '_', '', '', 1);
|
||
$this->assertEquals("A string with space and special char like _ or _ and more...\n", $after, "testDolStringNoSpecial 2");
|
||
|
||
$text = 'Bahnhofstraße';
|
||
$after = dolEscapeXML(dol_trunc(dol_string_nospecial(dol_string_unaccent($text), ' '), 70, 'right', 'UTF-8', 1));
|
||
$this->assertEquals("Bahnhofstraße", $after, "testDolStringNoSpecial with german char");
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* testDolStringNohtmltag
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function testDolStringNohtmltag()
|
||
{
|
||
$text = "A\nstring\n\nand more\n";
|
||
$after = dol_string_nohtmltag($text, 0);
|
||
$this->assertEquals("A\nstring\n\nand more", $after, "test1a");
|
||
|
||
$text = "A <b>string<b><br>\n<br>\n\nwith html tag<br>\n";
|
||
$after = dol_string_nohtmltag($text, 0);
|
||
$this->assertEquals("A string\n\n\n\n\nwith html tag", $after, 'test2a 2 br and 3 \n give 5 \n');
|
||
|
||
$text = "A <b>string<b><br>\n<br>\n\nwith html tag<br>\n";
|
||
$after = dol_string_nohtmltag($text, 1);
|
||
$this->assertEquals("A string with html tag", $after, 'test2b 2 br and 3 \n give 1 space');
|
||
|
||
$text = "A <b>string<b><br>\n<br>\n\nwith html tag<br>\n";
|
||
$after = dol_string_nohtmltag($text, 2);
|
||
$this->assertEquals("A string\n\nwith html tag", $after, 'test2c 2 br and 3 \n give 2 \n');
|
||
|
||
$text = "A <b>string<b><br>\r\n<br>\r\n\r\nwith html tag<br>\n";
|
||
$after = dol_string_nohtmltag($text, 2);
|
||
$this->assertEquals("A string\n\nwith html tag", $after, 'test2c 2 br and 3 \r\n give 2 \n');
|
||
|
||
$text = "A string<br>Another string";
|
||
$after = dol_string_nohtmltag($text, 0);
|
||
$this->assertEquals("A string\nAnother string", $after, "test4");
|
||
|
||
$text = "A string<br>Another string";
|
||
$after = dol_string_nohtmltag($text, 1);
|
||
$this->assertEquals("A string Another string", $after, "test5");
|
||
|
||
$text = '<a href="/myurl" title="<u>Afficher projet</u>">ABC</a>';
|
||
$after = dol_string_nohtmltag($text, 1);
|
||
$this->assertEquals("ABC", $after, "test6");
|
||
|
||
$text = '<a href="/myurl" title="<u>Afficher projet</u>">DEF</a>';
|
||
$after = dol_string_nohtmltag($text, 1);
|
||
$this->assertEquals("DEF", $after, "test7");
|
||
|
||
$text = '<a href="/myurl" title="<u>A title</u>">HIJ</a>';
|
||
$after = dol_string_nohtmltag($text, 0);
|
||
$this->assertEquals("HIJ", $after, "test8");
|
||
|
||
$text = "A <b>string<b>\n\nwith html tag and '<' chars<br>\n";
|
||
$after = dol_string_nohtmltag($text, 0);
|
||
$this->assertEquals("A string\n\nwith html tag and '<' chars", $after, "test9");
|
||
|
||
$text = "A <b>string<b>\n\nwith tag with < chars<br>\n";
|
||
$after = dol_string_nohtmltag($text, 1);
|
||
$this->assertEquals("A string with tag with < chars", $after, "test10");
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* testDolHtmlEntitiesBr
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function testDolHtmlEntitiesBr()
|
||
{
|
||
// Text not already HTML
|
||
|
||
$input = "A string\nwith a é, &, < and >.";
|
||
$after = dol_htmlentitiesbr($input, 0); // Add <br> before \n
|
||
$this->assertEquals("A string<br>\nwith a é, &, < and >.", $after);
|
||
|
||
$input = "A string\nwith a é, &, < and >.";
|
||
$after = dol_htmlentitiesbr($input, 1); // Replace \n with <br>
|
||
$this->assertEquals("A string<br>with a é, &, < and >.", $after);
|
||
|
||
$input = "A string\nwith a é, &, < and >.\n\n"; // With some \n at end that should be cleaned
|
||
$after = dol_htmlentitiesbr($input, 0); // Add <br> before \n
|
||
$this->assertEquals("A string<br>\nwith a é, &, < and >.", $after);
|
||
|
||
$input = "A string\nwith a é, &, < and >.\n\n"; // With some \n at end that should be cleaned
|
||
$after = dol_htmlentitiesbr($input, 1); // Replace \n with <br>
|
||
$this->assertEquals("A string<br>with a é, &, < and >.", $after);
|
||
|
||
// Text already HTML, so &,<,> should not be converted
|
||
|
||
$input = "A string<br>\nwith a é, &, < and >.";
|
||
$after = dol_htmlentitiesbr($input);
|
||
$this->assertEquals("A string<br>\nwith a é, &, < and >.", $after);
|
||
|
||
$input = "<li>\nA string with a é, &, < and >.</li>\nAnother string";
|
||
$after = dol_htmlentitiesbr($input);
|
||
$this->assertEquals("<li>\nA string with a é, &, < and >.</li>\nAnother string", $after);
|
||
|
||
$input = "A string<br>\nwith a é, &, < and >.<br>"; // With some <br> at end that should be cleaned
|
||
$after = dol_htmlentitiesbr($input);
|
||
$this->assertEquals("A string<br>\nwith a é, &, < and >.", $after);
|
||
|
||
$input = "<li>\nA string with a é, &, < and >.</li>\nAnother string<br>"; // With some <br> at end that should be cleaned
|
||
$after = dol_htmlentitiesbr($input);
|
||
$this->assertEquals("<li>\nA string with a é, &, < and >.</li>\nAnother string", $after);
|
||
|
||
// TODO Add test with param $removelasteolbr = 0
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
/**
|
||
* testDolNbOfLinesBis
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function testDolNbOfLinesBis()
|
||
{
|
||
// This is not a html string so nb of lines depends on \n
|
||
$input = "A string\nwith a é, &, < and > and bold tag.\nThird line";
|
||
$after = dol_nboflines_bis($input, 0);
|
||
$this->assertEquals($after, 3);
|
||
|
||
// This is a html string so nb of lines depends on <br>
|
||
$input = "A string\nwith a é, &, < and > and <b>bold</b> tag.\nThird line";
|
||
$after = dol_nboflines_bis($input, 0);
|
||
$this->assertEquals($after, 1);
|
||
|
||
// This is a html string so nb of lines depends on <br>
|
||
$input = "A string<br>with a é, &, < and > and <b>bold</b> tag.<br>Third line";
|
||
$after = dol_nboflines_bis($input, 0);
|
||
$this->assertEquals($after, 3);
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
/**
|
||
* testDolUnaccent
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function testDolUnaccent()
|
||
{
|
||
// Text not already HTML
|
||
|
||
$input = "A string\nwith a à ä é è ë ï ü ö ÿ É É, &, < and >."; // the second É is different than the first one
|
||
$after = dol_string_unaccent($input);
|
||
$this->assertEquals("A string\nwith a a a e e e i u o y E E, &, < and >.", $after);
|
||
}
|
||
|
||
|
||
/**
|
||
* testDolUtf8Check
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testDolUtf8Check()
|
||
{
|
||
// True
|
||
$result = utf8_check('azerty');
|
||
$this->assertTrue($result);
|
||
|
||
$file = dirname(__FILE__).'/textutf8.txt';
|
||
$filecontent = file_get_contents($file);
|
||
$result = utf8_check($filecontent);
|
||
$this->assertTrue($result);
|
||
|
||
$file = dirname(__FILE__).'/textiso.txt';
|
||
$filecontent = file_get_contents($file);
|
||
$result = utf8_check($filecontent);
|
||
$this->assertFalse($result);
|
||
}
|
||
|
||
/**
|
||
* testDolAsciiCheck
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testDolAsciiCheck()
|
||
{
|
||
// True
|
||
$result = ascii_check('azerty');
|
||
$this->assertTrue($result);
|
||
|
||
$result = ascii_check('é');
|
||
$this->assertFalse($result);
|
||
|
||
$file = dirname(__FILE__).'/textutf8.txt';
|
||
$filecontent = file_get_contents($file);
|
||
$result = ascii_check($filecontent);
|
||
$this->assertFalse($result);
|
||
}
|
||
|
||
/**
|
||
* testDolTrunc
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function testDolTrunc()
|
||
{
|
||
// Default trunc (will add … if truncation truncation or keep last char if only one char)
|
||
$input = "éeéeéeàa";
|
||
$after = dol_trunc($input, 3);
|
||
$this->assertEquals("éeé…", $after, 'Test A1');
|
||
$after = dol_trunc($input, 2);
|
||
$this->assertEquals("ée…", $after, 'Test A2');
|
||
$after = dol_trunc($input, 1);
|
||
$this->assertEquals("é…", $after, 'Test A3');
|
||
$input = "éeée";
|
||
$after = dol_trunc($input, 3);
|
||
$this->assertEquals("éeée", $after, 'Test B1');
|
||
$after = dol_trunc($input, 2);
|
||
$this->assertEquals("ée…", $after, 'Test B2');
|
||
$after = dol_trunc($input, 1);
|
||
$this->assertEquals("é…", $after, 'Test B3');
|
||
$input = "éeée";
|
||
$after = dol_trunc($input, 3);
|
||
$this->assertEquals("éeée", $after, 'Test C1');
|
||
$after = dol_trunc($input, 2);
|
||
$this->assertEquals("ée…", $after, 'Test C2');
|
||
$after = dol_trunc($input, 1);
|
||
$this->assertEquals("é…", $after, 'Test C3');
|
||
$input = "éeé";
|
||
$after = dol_trunc($input, 3);
|
||
$this->assertEquals("éeé", $after, 'Test C');
|
||
$after = dol_trunc($input, 2);
|
||
$this->assertEquals("éeé", $after, 'Test D');
|
||
$after = dol_trunc($input, 1);
|
||
$this->assertEquals("é…", $after, 'Test E');
|
||
// Trunc with no …
|
||
$input = "éeéeéeàa";
|
||
$after = dol_trunc($input, 3, 'right', 'UTF-8', 1);
|
||
$this->assertEquals("éeé", $after, 'Test F');
|
||
$after = dol_trunc($input, 2, 'right', 'UTF-8', 1);
|
||
$this->assertEquals("ée", $after, 'Test G');
|
||
$input = "éeé";
|
||
$after = dol_trunc($input, 3, 'right', 'UTF-8', 1);
|
||
$this->assertEquals("éeé", $after, 'Test H');
|
||
$after = dol_trunc($input, 2, 'right', 'UTF-8', 1);
|
||
$this->assertEquals("ée", $after, 'Test I');
|
||
$after = dol_trunc($input, 1, 'right', 'UTF-8', 1);
|
||
$this->assertEquals("é", $after, 'Test J');
|
||
$input = "éeéeéeàa";
|
||
$after = dol_trunc($input, 4, 'middle');
|
||
$this->assertEquals("ée…àa", $after, 'Test K');
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* testDolMkTime
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testDolMkTime()
|
||
{
|
||
global $conf;
|
||
|
||
$savtz = date_default_timezone_get();
|
||
|
||
// Some test for UTC TZ
|
||
date_default_timezone_set('UTC');
|
||
|
||
// Check bad hours
|
||
$result = dol_mktime(25, 0, 0, 1, 1, 1970, 1, 1); // Error (25 hours)
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals('', $result);
|
||
$result = dol_mktime(2, 61, 0, 1, 1, 1970, 1, 1); // Error (61 minutes)
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals('', $result);
|
||
$result = dol_mktime(2, 1, 61, 1, 1, 1970, 1, 1); // Error (61 seconds)
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals('', $result);
|
||
$result = dol_mktime(2, 1, 1, 1, 32, 1970, 1, 1); // Error (day 32)
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals('', $result);
|
||
$result = dol_mktime(2, 1, 1, 13, 1, 1970, 1, 1); // Error (month 13)
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals('', $result);
|
||
|
||
$result = dol_mktime(2, 1, 1, 1, 1, 1970, 1); // 1970-01-01 02:01:01 in GMT area -> 7261
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals(7261, $result);
|
||
|
||
$result = dol_mktime(2, 0, 0, 1, 1, 1970, 0); // 1970-01-01 02:00:00 = 7200 in local area Europe/Paris = 3600 GMT
|
||
print __METHOD__." result=".$result."\n";
|
||
$tz = getServerTimeZoneInt('winter'); // +1 in Europe/Paris at this time (this time is winter)
|
||
$this->assertEquals(7200 - ($tz * 3600), $result); // 7200 if we are at greenwich winter, 7200-($tz*3600) at local winter
|
||
|
||
// Some test for local TZ Europe/Paris
|
||
date_default_timezone_set('Europe/Paris');
|
||
|
||
// Check that tz for paris in winter is used
|
||
$result = dol_mktime(2, 0, 0, 1, 1, 1970, 'server'); // 1970-01-01 02:00:00 = 7200 in local area Europe/Paris = 3600 GMT
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals(3600, $result); // 7200 if we are at greenwich winter, 3600 at Europe/Paris
|
||
|
||
// Check that daylight saving time is used
|
||
$result = dol_mktime(2, 0, 0, 6, 1, 2014, 0); // 2014-06-01 02:00:00 = 1401588000-3600(location)-3600(daylight) in local area Europe/Paris = 1401588000 GMT
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals(1401588000 - 3600 - 3600, $result); // 1401588000 are at greenwich summer, 1401588000-3600(location)-3600(daylight) at Europe/Paris summer
|
||
|
||
date_default_timezone_set($savtz);
|
||
}
|
||
|
||
|
||
/**
|
||
* testDolEscapeJs
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testDolEscapeJs()
|
||
{
|
||
$input = "x&<b>#</b>,\"'"; // " will be converted into '
|
||
$result = dol_escape_js($input);
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals("x&<b>#</b>,\'\'", $result, "Test mode=0");
|
||
|
||
$result = dol_escape_js($input, 1);
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals("x&<b>#</b>,\"\'", $result, "Test mode=1");
|
||
|
||
$result = dol_escape_js($input, 2);
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals("x&<b>#</b>,\\\"'", $result, "Test mode=2");
|
||
}
|
||
|
||
|
||
/**
|
||
* testDolEscapeHtmlTag
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testDolEscapeHtmlTag()
|
||
{
|
||
print __METHOD__."\n";
|
||
|
||
$input = 'x&<b>#</b>,"'; // & and " are converted into html entities, <b> are removed
|
||
$result = dol_escape_htmltag($input);
|
||
$this->assertEquals('x&#,"', $result);
|
||
|
||
$input = 'x&<b>#</b>,"'; // & and " are converted into html entities, <b> are not removed
|
||
$result = dol_escape_htmltag($input, 1);
|
||
$this->assertEquals('x&<b>#</b>,"', $result);
|
||
|
||
$input = '<img alt="" src="https://github.githubassets.com/assets/GitHub%20Mark-ea2971cee799.png">'; // & and " are converted into html entities, <b> are not removed
|
||
$result = dol_escape_htmltag($input, 1, 1, 'common', 0, 1);
|
||
$this->assertEquals('<img alt="" src="https://github.githubassets.com/assets/GitHub%20Mark-ea2971cee799.png">', $result);
|
||
|
||
$input = '<img src="data:image/png;base64, 123/456+789==" style="height: 123px; width:456px">'; // & and " are converted into html entities, <b> are not removed
|
||
$result = dol_escape_htmltag($input, 1, 1, 'common');
|
||
$this->assertEquals('<img src="data:image/png;base64, 123/456+789==" style="height: 123px; width:456px">', $result);
|
||
|
||
$input = '<img src="data:image/png;base64, 123/456+789==" style="height: 123px; width:456px">'; // & and " are converted into html entities, <b> are not removed
|
||
$result = dol_escape_htmltag($input, 1);
|
||
$this->assertEquals('<img src="data:image/png;base64, 123/456+789==" style="height: 123px; width:456px">', $result);
|
||
|
||
$input = '<img alt="" src="https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png">'; // & and " are converted into html entities, <b> are not removed
|
||
$result = dol_escape_htmltag($input, 1, 1, 'common', 0, 1);
|
||
$this->assertEquals('<img alt="" src="https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png">', $result);
|
||
|
||
|
||
$input = '<div style="float:left; margin-left:0px; margin-right:5px">
|
||
<img id="sigPhoto" src="https://www.domain.com/aaa.png" style="height:65px; width:65px" />
|
||
</div>
|
||
<div style="margin-left:74px"><strong>A text here</strong> and more<br>
|
||
<a href="mailto:abc+def@domain.com" id="sigEmail" style="color:#428BCA;">abc+def@domain.com</a><br>
|
||
<a href="https://www.another-domain.com" id="sigWebsite" style="color:#428BCA;">https://www.another-domain.com</a><br>
|
||
</div>';
|
||
$result = dol_escape_htmltag($input, 1, 1, 'common');
|
||
$this->assertEquals($input, $result);
|
||
|
||
// The same plus the tag 'code'
|
||
$input = '<div style="float:left; margin-left:0px; margin-right:5px">
|
||
<img id="sigPhoto" src="https://www.domain.com/aaa.png" style="height:65px; width:65px" />
|
||
</div>
|
||
<div style="margin-left:74px"><strong>A text here</strong> and more<br>
|
||
<a href="mailto:abc+def@domain.com" id="sigEmail" style="color:#428BCA;">abc+def@domain.com</a><br>
|
||
<a href="https://www.another-domain.com" id="sigWebsite" style="color:#428BCA;">https://www.another-domain.com</a><br>
|
||
<code>abc</code>
|
||
</div>';
|
||
$result = dol_escape_htmltag($input, 1, 1, 'common,code');
|
||
$this->assertEquals($input, $result);
|
||
}
|
||
|
||
|
||
/**
|
||
* testDolFormatAddress
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testDolFormatAddress()
|
||
{
|
||
global $conf,$user,$langs,$db;
|
||
|
||
$conf = $this->savconf;
|
||
$user = $this->savuser;
|
||
$langs = $this->savlangs;
|
||
$db = $this->savdb;
|
||
|
||
print __METHOD__."\n";
|
||
|
||
$object = new Societe($db);
|
||
$object->initAsSpecimen();
|
||
|
||
$object->country_code = 'FR';
|
||
$address = dol_format_address($object);
|
||
$this->assertEquals("21 jump street\n99999 MyTown", $address);
|
||
|
||
$object->country_code = 'GB';
|
||
$address = dol_format_address($object);
|
||
$this->assertEquals("21 jump street\nMyTown, MyState\n99999", $address);
|
||
|
||
$object->country_code = 'US';
|
||
$address = dol_format_address($object);
|
||
$this->assertEquals("21 jump street\nMyTown, MyState, 99999", $address);
|
||
|
||
$object->country_code = 'AU';
|
||
$address = dol_format_address($object);
|
||
$this->assertEquals("21 jump street\nMyTown, MyState, 99999", $address);
|
||
|
||
$object->country_code = 'JP';
|
||
$address = dol_format_address($object);
|
||
$this->assertEquals("21 jump street\nMyState, MyTown 99999", $address);
|
||
}
|
||
|
||
|
||
/**
|
||
* testDolPrintPhone
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testDolPrintPhone()
|
||
{
|
||
global $conf,$user,$langs,$db;
|
||
$conf = $this->savconf;
|
||
$user = $this->savuser;
|
||
$langs = $this->savlangs;
|
||
$db = $this->savdb;
|
||
|
||
print __METHOD__."\n";
|
||
|
||
$object = new Societe($db);
|
||
$object->initAsSpecimen();
|
||
|
||
$object->country_code = 'FR';
|
||
$phone = dol_print_phone('1234567890', $object->country_code);
|
||
$this->assertEquals('<span class="paddingright">12 34 56 78 90</span>', $phone, 'Phone for FR 1');
|
||
|
||
$object->country_code = 'FR';
|
||
$phone = dol_print_phone('1234567890', $object->country_code, 0, 0, 0, '');
|
||
$this->assertEquals('<span class="paddingright">1234567890</span>', $phone, 'Phone for FR 2');
|
||
|
||
$object->country_code = 'FR';
|
||
$phone = dol_print_phone('1234567890', $object->country_code, 0, 0, 0, ' ');
|
||
$this->assertEquals('<span class="paddingright">12 34 56 78 90</span>', $phone, 'Phone for FR 3');
|
||
|
||
$object->country_code = 'CA';
|
||
$phone = dol_print_phone('1234567890', $object->country_code, 0, 0, 0, ' ');
|
||
$this->assertEquals('<span class="paddingright">(123) 456-7890</span>', $phone, 'Phone for CA 1');
|
||
|
||
// Every digit must appear exactly once, in order, whatever the country format
|
||
$object->country_code = 'JO';
|
||
$phone = dol_print_phone('+96212345678', $object->country_code, 0, 0, 0, ' ');
|
||
$this->assertEquals('<span class="paddingright">+962 1 234 56 78</span>', $phone, 'Phone for JO 1');
|
||
|
||
$object->country_code = 'PE';
|
||
$phone = dol_print_phone('987654321', $object->country_code, 0, 0, 0, ' ');
|
||
$this->assertEquals('<span class="paddingright">987 654 321</span>', $phone, 'Phone for PE 1');
|
||
|
||
$object->country_code = 'PE';
|
||
$phone = dol_print_phone('+5111234567', $object->country_code, 0, 0, 0, ' ');
|
||
$this->assertEquals('<span class="paddingright">+511 123 4567</span>', $phone, 'Phone for PE 2');
|
||
|
||
$object->country_code = 'PE';
|
||
$phone = dol_print_phone('+51987654321', $object->country_code, 0, 0, 0, ' ');
|
||
$this->assertEquals('<span class="paddingright">+51 987 654 321</span>', $phone, 'Phone for PE 3');
|
||
}
|
||
|
||
|
||
/**
|
||
* testImgPicto
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testImgPicto()
|
||
{
|
||
$s = img_picto('title', 'user');
|
||
print __METHOD__." s=".$s."\n";
|
||
$this->assertStringContainsStringIgnoringCase('fa-user', $s, 'testImgPicto1');
|
||
|
||
$s = img_picto('title', 'img.png', 'style="float: right"', 0);
|
||
print __METHOD__." s=".$s."\n";
|
||
$this->assertStringContainsStringIgnoringCase('theme', $s, 'testImgPicto2');
|
||
$this->assertStringContainsStringIgnoringCase('style="float: right"', $s, 'testImgPicto2');
|
||
|
||
$s = img_picto('title', '/fullpath/img.png', '', 1);
|
||
print __METHOD__." s=".$s."\n";
|
||
$this->assertEquals('<img src="/fullpath/img.png" alt="" title="title" class="inline-block">', $s, 'testImgPicto3');
|
||
|
||
$s = img_picto('title', '/fullpath/img.png', '', true);
|
||
print __METHOD__." s=".$s."\n";
|
||
$this->assertEquals('<img src="/fullpath/img.png" alt="" title="title" class="inline-block">', $s, 'testImgPicto4');
|
||
|
||
$s = img_picto('title', 'delete', '', 0, 1);
|
||
print __METHOD__." s=".$s."\n";
|
||
$this->assertEquals(DOL_URL_ROOT.'/theme/eldy/img/delete.png', $s, 'testImgPicto5');
|
||
}
|
||
|
||
/**
|
||
* testDolNow
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testDolNow()
|
||
{
|
||
$now = dol_now('gmt');
|
||
$nowtzserver = dol_now('tzserver');
|
||
print __METHOD__." getServerTimeZoneInt=".(getServerTimeZoneInt('now') * 3600)."\n";
|
||
$this->assertEquals(getServerTimeZoneInt('now') * 3600, ($nowtzserver - $now));
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* Data provider for testVerifCond
|
||
*
|
||
* @return array<string,array{0:string,1:bool}>
|
||
*/
|
||
public function verifCondDataProvider(): array
|
||
{
|
||
return [
|
||
'Test a true comparison' => ['1==1', true,],
|
||
'Test a false comparison' => ['1==2', false,],
|
||
'Test that the conf property of a module reports true when enabled' => ['isModEnabled("facture")', true,],
|
||
'Test that the conf property of a module reports false when disabled' => ['isModEnabled("moduledummy")', false,],
|
||
'Test that verifConf(0) returns false' => [0, false,],
|
||
'Test that verifConf("0") returns false' => ["0", false,],
|
||
'Test that verifConf("") returns false (special case)' => ['', true,],
|
||
];
|
||
}
|
||
|
||
/**
|
||
* testVerifCond
|
||
*
|
||
* @dataProvider verifCondDataProvider
|
||
*
|
||
* @param string $cond Condition to test using verifCond
|
||
* @param string $expected Expected outcome of verifCond
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testVerifCond($cond, $expected)
|
||
{
|
||
if ($expected) {
|
||
$this->assertTrue(verifCond($cond));
|
||
} else {
|
||
$this->assertFalse(verifCond($cond));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* testVerifCondOnMethods
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testVerifCondOnMethods()
|
||
{
|
||
$a = verifCond('$user->hasRight("facture", "read")');
|
||
$this->assertTrue($a);
|
||
|
||
$a = verifCond('$user->hasMethodKo("facture", "read")');
|
||
$this->assertFalse($a);
|
||
}
|
||
|
||
|
||
/**
|
||
* testGetDefaultTva
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testGetDefaultTva()
|
||
{
|
||
global $conf,$user,$langs,$db;
|
||
$this->savconf = $conf;
|
||
$this->savuser = $user;
|
||
$this->savlangs = $langs;
|
||
$this->savdb = $db;
|
||
|
||
// Sellers
|
||
$companyfrnovat = new Societe($db);
|
||
$companyfrnovat->country_code = 'FR';
|
||
$companyfrnovat->tva_assuj = 0;
|
||
|
||
$companyfr = new Societe($db);
|
||
$companyfr->country_code = 'FR';
|
||
$companyfr->tva_assuj = 1;
|
||
$companyfr->tva_intra = 'FR9999';
|
||
|
||
// Buyers
|
||
$companymc = new Societe($db);
|
||
$companymc->country_code = 'MC';
|
||
$companymc->tva_assuj = 1;
|
||
$companyfr->tva_intra = 'MC9999';
|
||
|
||
$companyit = new Societe($db);
|
||
$companyit->country_code = 'IT';
|
||
$companyit->tva_assuj = 1;
|
||
$companyit->tva_intra = 'IT99999';
|
||
|
||
$companyde = new Societe($db);
|
||
$companyde->country_code = 'DE';
|
||
$companyde->tva_assuj = 1;
|
||
$companyde->tva_intra = 'DE99999';
|
||
|
||
$notcompanyde = new Societe($db);
|
||
$notcompanyde->country_code = 'DE';
|
||
$notcompanyde->tva_assuj = 0;
|
||
$notcompanyde->tva_intra = '';
|
||
$notcompanyde->typent_code = 'TE_PRIVATE';
|
||
|
||
$companyus = new Societe($db);
|
||
$companyus->country_code = 'US';
|
||
$companyus->tva_assuj = 1;
|
||
$companyus->tva_intra = '';
|
||
|
||
|
||
// Test RULE 0 (FR-DE)
|
||
// Not tested
|
||
|
||
// Test RULE 1
|
||
print __METHOD__." rule=RULE 1\n";
|
||
$vat = get_default_tva($companyfrnovat, $companymc, 0);
|
||
$this->assertEquals(0, $vat, 'RULE 1');
|
||
|
||
// Test RULE 2 (FR-FR)
|
||
print __METHOD__." rule=RULE 2 FR-FR\n";
|
||
$vat = get_default_tva($companyfr, $companyfr, 0);
|
||
$this->assertEquals(20, $vat, 'RULE 2 FR-FR - Check that the dictionary is set to have default VAT to 20');
|
||
|
||
// Test RULE 2 (FR-MC)
|
||
print __METHOD__." rule=RULE 2 FR-MC\n";
|
||
$vat = get_default_tva($companyfr, $companymc, 0);
|
||
$this->assertEquals(20, $vat, 'RULE 2 FR-MC');
|
||
|
||
// Test RULE 3 (FR-DE company)
|
||
print __METHOD__." rule=RULE 3 FR-DE\n";
|
||
$vat = get_default_tva($companyfr, $companyit, 0);
|
||
$this->assertEquals(0, $vat, 'RULE 3');
|
||
|
||
// Test RULE 4 (FR-DE not a company)
|
||
print __METHOD__." rule=RULE 4 FR-DE\n";
|
||
$vat = get_default_tva($companyfr, $notcompanyde, 0);
|
||
$this->assertEquals(20, $vat, 'RULE 4');
|
||
|
||
// Test RULE 5 (FR-US)
|
||
print __METHOD__." rule=RULE 5 FR-US\n";
|
||
$vat = get_default_tva($companyfr, $companyus, 0);
|
||
$this->assertEquals(0, $vat, 'RULE 5');
|
||
|
||
|
||
// We do same tests but with option SERVICE_ARE_ECOMMERCE_200238EC on.
|
||
$conf->global->SERVICE_ARE_ECOMMERCE_200238EC = 1;
|
||
|
||
// Test RULE 1 (FR-US)
|
||
print __METHOD__." rule=RULE 1 ECOMMERCE_200238EC FR-US\n";
|
||
$vat = get_default_tva($companyfr, $companyus, 0);
|
||
$this->assertEquals(0, $vat, 'RULE 1 ECOMMERCE_200238EC');
|
||
|
||
// Test RULE 2 (FR-FR)
|
||
print __METHOD__." rule=RULE 2 ECOMMERCE_200238EC FR-FR\n";
|
||
$vat = get_default_tva($companyfr, $companyfr, 0);
|
||
$this->assertEquals(20, $vat, 'RULE 2 ECOMMERCE_200238EC');
|
||
|
||
// Test RULE 3 (FR-DE company)
|
||
print __METHOD__." rule=RULE 3 ECOMMERCE_200238EC FR-DE company\n";
|
||
$vat = get_default_tva($companyfr, $companyde, 0);
|
||
$this->assertEquals(0, $vat, 'RULE 3 ECOMMERCE_200238EC');
|
||
|
||
// Test RULE 4 (FR-DE not a company)
|
||
print __METHOD__." rule=RULE 4 ECOMMERCE_200238EC FR-DE not company\n";
|
||
$vat = get_default_tva($companyfr, $notcompanyde, 0);
|
||
$this->assertEquals(19, $vat, 'RULE 4 ECOMMERCE_200238EC');
|
||
|
||
// Test RULE 5 (FR-US)
|
||
print __METHOD__." rule=RULE 5 ECOMMERCE_200238EC FR-US\n";
|
||
$vat = get_default_tva($companyfr, $companyus, 0);
|
||
$this->assertEquals(0, $vat, 'RULE 5 ECOMMERCE_200238EC');
|
||
}
|
||
|
||
/**
|
||
* testGetDefaultTvaForBuyerState
|
||
*
|
||
* Covers VATRULE 2: when the buyer department (state/province) has a VAT rule in the
|
||
* dictionary, it becomes the default VAT rate. Also checks that only active rates of the
|
||
* current entity are considered (an inactive rate must never be selected).
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testGetDefaultTvaForBuyerState()
|
||
{
|
||
global $conf,$user,$langs,$db;
|
||
$this->savconf = $conf;
|
||
$this->savuser = $user;
|
||
$this->savlangs = $langs;
|
||
$this->savdb = $db;
|
||
|
||
// Make sure the ecommerce directive left on by a previous test does not interfere with VATRULE 2
|
||
unset($conf->global->SERVICE_ARE_ECOMMERCE_200238EC);
|
||
|
||
// Seller subject to VAT in France
|
||
$companyfr = new Societe($db);
|
||
$companyfr->country_code = 'FR';
|
||
$companyfr->tva_assuj = 1;
|
||
$companyfr->tva_intra = 'FR9999';
|
||
|
||
// Find an existing department (state/province) to attach a VAT rule to, and the France country id
|
||
$stateid = 0;
|
||
$sql = "SELECT rowid FROM ".$db->prefix()."c_departements WHERE code_departement = '75'";
|
||
$resql = $db->query($sql);
|
||
if ($resql && $db->num_rows($resql)) {
|
||
$objdep = $db->fetch_object($resql);
|
||
$stateid = (int) $objdep->rowid;
|
||
}
|
||
if (empty($stateid)) {
|
||
$this->markTestSkipped('No department found in c_departements to run the VATRULE 2 test.');
|
||
return;
|
||
}
|
||
|
||
$frpaysid = 0;
|
||
$sql = "SELECT rowid FROM ".$db->prefix()."c_country WHERE code = 'FR'";
|
||
$resql = $db->query($sql);
|
||
if ($resql && $db->num_rows($resql)) {
|
||
$objpays = $db->fetch_object($resql);
|
||
$frpaysid = (int) $objpays->rowid;
|
||
}
|
||
|
||
// Insert two temporary VAT rules attached to that department:
|
||
// - an ACTIVE one at 12 that must be selected
|
||
// - an INACTIVE one at 25 (higher, so it would win the taux DESC sort) that must be ignored
|
||
$db->query("DELETE FROM ".$db->prefix()."c_tva WHERE code IN ('TESTVATACT', 'TESTVATINACT')");
|
||
$db->query("INSERT INTO ".$db->prefix()."c_tva (entity, fk_pays, fk_department_buyer, code, type_vat, taux, use_default, recuperableonly, active) VALUES (1, ".$frpaysid.", ".$stateid.", 'TESTVATACT', 0, 12, 0, 0, 1)");
|
||
$db->query("INSERT INTO ".$db->prefix()."c_tva (entity, fk_pays, fk_department_buyer, code, type_vat, taux, use_default, recuperableonly, active) VALUES (1, ".$frpaysid.", ".$stateid.", 'TESTVATINACT', 0, 25, 0, 0, 0)");
|
||
|
||
// Buyer located in that department
|
||
$buyer = new Societe($db);
|
||
$buyer->country_code = 'FR';
|
||
$buyer->tva_assuj = 1;
|
||
$buyer->state_id = $stateid;
|
||
|
||
// Case 1: an active department rate exists -> VATRULE 2 returns it, ignoring the inactive (higher) one
|
||
$vatactive = get_default_tva($companyfr, $buyer, 0);
|
||
|
||
// Case 2: no active department rate remains -> VATRULE 2 must not fire on the inactive rows
|
||
$db->query("UPDATE ".$db->prefix()."c_tva SET active = 0 WHERE code = 'TESTVATACT'");
|
||
$vatinactive = get_default_tva($companyfr, $buyer, 0);
|
||
|
||
// Cleanup fixtures before asserting so a failed assertion never leaves test data behind
|
||
$db->query("DELETE FROM ".$db->prefix()."c_tva WHERE code IN ('TESTVATACT', 'TESTVATINACT')");
|
||
|
||
// The active department rate is selected...
|
||
$this->assertStringContainsString('TESTVATACT', $vatactive, 'VATRULE 2 must select the active department VAT rate');
|
||
// ...and the inactive one is never selected (this is the fix)
|
||
$this->assertStringNotContainsString('TESTVATINACT', $vatactive, 'An inactive department VAT rate must not be selected by VATRULE 2');
|
||
// When no active department rate remains, VATRULE 2 does not select the (now inactive) rate either
|
||
$this->assertStringNotContainsString('TESTVATACT', $vatinactive, 'An inactive department VAT rate must not be selected by VATRULE 2');
|
||
}
|
||
|
||
/**
|
||
* testGetDefaultLocalTax
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testGetDefaultLocalTax()
|
||
{
|
||
global $conf,$user,$langs,$db;
|
||
$this->savconf = $conf;
|
||
$this->savuser = $user;
|
||
$this->savlangs = $langs;
|
||
$this->savdb = $db;
|
||
|
||
$companyfrnovat = new Societe($db);
|
||
$companyfrnovat->country_code = 'FR';
|
||
$companyfrnovat->tva_assuj = 0;
|
||
$companyfrnovat->localtax1_assuj = 0;
|
||
$companyfrnovat->localtax2_assuj = 0;
|
||
|
||
$companyes = new Societe($db);
|
||
$companyes->country_code = 'ES';
|
||
$companyes->tva_assuj = 1;
|
||
$companyes->localtax1_assuj = 1;
|
||
$companyes->localtax2_assuj = 1;
|
||
|
||
$companymc = new Societe($db);
|
||
$companymc->country_code = 'MC';
|
||
$companymc->tva_assuj = 1;
|
||
$companymc->localtax1_assuj = 0;
|
||
$companymc->localtax2_assuj = 0;
|
||
|
||
$companyit = new Societe($db);
|
||
$companyit->country_code = 'IT';
|
||
$companyit->tva_assuj = 1;
|
||
$companyit->tva_intra = 'IT99999';
|
||
$companyit->localtax1_assuj = 0;
|
||
$companyit->localtax2_assuj = 0;
|
||
|
||
$notcompanyit = new Societe($db);
|
||
$notcompanyit->country_code = 'IT';
|
||
$notcompanyit->tva_assuj = 1;
|
||
$notcompanyit->tva_intra = '';
|
||
$notcompanyit->typent_code = 'TE_PRIVATE';
|
||
$notcompanyit->localtax1_assuj = 0;
|
||
$notcompanyit->localtax2_assuj = 0;
|
||
|
||
$companyus = new Societe($db);
|
||
$companyus->country_code = 'US';
|
||
$companyus->tva_assuj = 1;
|
||
$companyus->tva_intra = '';
|
||
$companyus->localtax1_assuj = 0;
|
||
$companyus->localtax2_assuj = 0;
|
||
|
||
// Test RULE FR-MC
|
||
print __METHOD__." rule=FR-MC\n";
|
||
$vat1 = get_default_localtax($companyfrnovat, $companymc, 1, 0);
|
||
$vat2 = get_default_localtax($companyfrnovat, $companymc, 2, 0);
|
||
$this->assertEquals(0, $vat1);
|
||
$this->assertEquals(0, $vat2);
|
||
|
||
// Test RULE ES-ES
|
||
print __METHOD__." rule=ES-ES\n";
|
||
$vat1 = get_default_localtax($companyes, $companyes, 1, 0);
|
||
$vat2 = get_default_localtax($companyes, $companyes, 2, 0);
|
||
$this->assertEquals($vat1, 5.2);
|
||
$this->assertStringStartsWith((string) $vat2, '-19:-15:-9'); // Can be -19 (old version) or '-19:-15:-9' (new setup)
|
||
|
||
// Test RULE ES-IT
|
||
print __METHOD__." rule=ES-IT company\n";
|
||
$vat1 = get_default_localtax($companyes, $companyit, 1, 0);
|
||
$vat2 = get_default_localtax($companyes, $companyit, 2, 0);
|
||
$this->assertEquals(0, $vat1);
|
||
$this->assertEquals(0, $vat2);
|
||
|
||
// Test RULE ES-not IT
|
||
print __METHOD__." rule=ES-IT not company\n";
|
||
$vat1 = get_default_localtax($companyes, $notcompanyit, 1, 0);
|
||
$vat2 = get_default_localtax($companyes, $notcompanyit, 2, 0);
|
||
$this->assertEquals(0, $vat1);
|
||
$this->assertEquals(0, $vat2);
|
||
|
||
// Test RULE FR-IT
|
||
// Not tested
|
||
|
||
// Test RULE ES-US
|
||
print __METHOD__." rule=ES-US\n";
|
||
$vat1 = get_default_localtax($companyes, $companyus, 1, 0);
|
||
$vat2 = get_default_localtax($companyes, $companyus, 2, 0);
|
||
$this->assertEquals(0, $vat1);
|
||
$this->assertEquals(0, $vat2);
|
||
}
|
||
|
||
|
||
/**
|
||
* testGetLocalTaxByThird
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testGetLocalTaxByThird()
|
||
{
|
||
global $mysoc;
|
||
|
||
$mysoc->country_code = 'ES';
|
||
|
||
$result = get_localtax_by_third(1);
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals('5.2', $result);
|
||
|
||
$result = get_localtax_by_third(2);
|
||
print __METHOD__." result=".$result."\n";
|
||
$this->assertEquals('-19:-15:-9', $result);
|
||
}
|
||
|
||
|
||
/**
|
||
* testDolExplodeIntoArray
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testDolExplodeIntoArray()
|
||
{
|
||
$stringtoexplode = 'AA=B/B.CC=.EE=FF.HH=GG;.';
|
||
$tmp = dolExplodeIntoArray($stringtoexplode, '.', '=');
|
||
|
||
print __METHOD__." tmp=".json_encode($tmp)."\n";
|
||
$this->assertEquals('{"AA":"B\/B","CC":"","EE":"FF","HH":"GG;"}', json_encode($tmp));
|
||
|
||
$stringtoexplode = "AA=B/B;CC=\n\rEE=FF\nHH=GG;;;\nII=JJ\n";
|
||
$tmp = dolExplodeIntoArray($stringtoexplode, "(\r\n|\n|\r|;)", '=');
|
||
|
||
print __METHOD__." tmp=".json_encode($tmp)."\n";
|
||
$this->assertEquals('{"AA":"B\/B","CC":"","EE":"FF","HH":"GG","II":"JJ"}', json_encode($tmp));
|
||
}
|
||
|
||
/**
|
||
* dol_nl2br
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testDolNl2Br()
|
||
{
|
||
|
||
//String to encode
|
||
$string = "a\na";
|
||
|
||
$this->assertEquals(dol_nl2br($string), "a<br>\na");
|
||
|
||
//With $forxml parameter
|
||
$this->assertEquals(dol_nl2br($string, 0, 1), "a<br />\na");
|
||
|
||
//Replacing \n by br
|
||
$this->assertEquals(dol_nl2br($string, 1), "a<br>a");
|
||
|
||
//With $forxml parameter
|
||
$this->assertEquals(dol_nl2br($string, 1, 1), "a<br />a");
|
||
}
|
||
|
||
/**
|
||
* testDolPrice2Num
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function testDolPrice2Num()
|
||
{
|
||
global $langs, $conf;
|
||
|
||
$oldlangs = $langs;
|
||
|
||
// For US language SeparatorThousand=, and SeparatorDecimal=.
|
||
$newlangs = new Translate('', $conf);
|
||
$newlangs->setDefaultLang('en_US');
|
||
$newlangs->load("main");
|
||
$langs = $newlangs;
|
||
|
||
$this->assertEquals(150, price2num('(SELECT/**/CASE/**/WHEN/**/(0<1)/**/THEN/**/SLEEP(5)/**/ELSE/**/SLEEP(0)/**/END)'));
|
||
|
||
$this->assertEquals(1000, price2num('1 000.0'));
|
||
$this->assertEquals(1000, price2num('1 000', 'MT'));
|
||
$this->assertEquals(1000, price2num('1 000', 'MU'));
|
||
|
||
$this->assertEquals(1000.123456, price2num('1 000.123456'), 'Test 1 000.123456 give 1000.123456 with us language');
|
||
$this->assertEquals(1000.123456, price2num('1,000.123456'), 'Test 1,000.123456 give 1000.123456 with us language');
|
||
$this->assertEquals(1000.123, price2num('1 000.123'), 'Test 1 000.123 give 1000.123 with us language');
|
||
$this->assertEquals(1000.123, price2num('1,000.123'), 'Test 1,000.123 give 1000.123 with us language');
|
||
|
||
// Round down
|
||
$this->assertEquals(1000.12, price2num('1 000.123452', 'MT'), 'Error in round down with MT');
|
||
$this->assertEquals(1000.12345, price2num('1 000.123452', 'MU'), "Test MU");
|
||
|
||
// Round up
|
||
$this->assertEquals(1000.13, price2num('1 000.125456', 'MT'));
|
||
$this->assertEquals(1000.12546, price2num('1 000.125456', 'MU'), "Test MU");
|
||
|
||
$this->assertEquals(1, price2num('1.000'), 'Test 1.000 give 1 with english language');
|
||
|
||
// Text can't be converted
|
||
$this->assertEquals('12.4', price2num('12.4$'));
|
||
$this->assertEquals('12.4', price2num('12r.4$'));
|
||
|
||
$this->assertEquals('1.023210.00', price2num('1.023,210.00'), 'Test invalid 1.023,210.00 with en_US');
|
||
$this->assertEquals('1023.21000', price2num('1,023.210,00'), 'Test invalid 1,023.210,00 with en_US');
|
||
|
||
// For spanish language SeparatorThousand=. and SeparatorDecimal=,
|
||
$newlangs2 = new Translate('', $conf);
|
||
$newlangs2->setDefaultLang('es_ES');
|
||
$newlangs2->load("main");
|
||
$langs = $newlangs2;
|
||
|
||
// Test with 3 chars after . or ,
|
||
// If a . is used and there is 3 digits after, it is a thousand separator
|
||
$this->assertEquals(1234, price2num('1.234', '', 2), 'Test 1.234 give 1234 with spanish language if user input');
|
||
$this->assertEquals(1.234, price2num('1,234', '', 2), 'Test 1,234 give 1.234 with spanish language if user input');
|
||
$this->assertEquals(1234, price2num('1 234', '', 2), 'Test 1 234 give 1234 with spanish language if user input');
|
||
$this->assertEquals(-1.234, price2num('-1.234'), 'Test -1.234 give 1.234 with spanish language and not user input (this differs when user input)');
|
||
$this->assertEquals(-1.234, price2num('-1,234'), 'Test -1,234 give 1234 with spanish language and not user input');
|
||
$this->assertEquals(-1234, price2num('-1 234'), 'Test -1 234 give 1234 with spanish language and not user input');
|
||
$this->assertEquals(1111.234, price2num('1.111,234'), 'Test 1.111,234 give 1111.234 with spanish language and not user input');
|
||
$this->assertEquals(21500123, price2num('21.500.123'), 'Test 21.500.123 give 21500123 with spanish language and not user input');
|
||
$this->assertEquals(21500123, price2num('21500.123', 0, 2), 'Test 21500.123 give 21500123 with spanish language if user input');
|
||
$this->assertEquals(21500123, price2num('21 500.123', 0, 2), 'Test 21 500.123 give 21500123 with spanish language if user input');
|
||
$this->assertEquals(21500.123, price2num('21500.123'), 'Test 21500.123 give 21500123 with spanish language and not user input');
|
||
$this->assertEquals(21500.123, price2num('21500,123'), 'Test 21500,123 give 21500.123 with spanish language and not user input');
|
||
// Test with 2 digits (we can guess that , is not a thousand separator)
|
||
$this->assertEquals(21500.12, price2num('21500.12'), 'Test 21500.12 give 21500.12 with spanish language and not user input');
|
||
$this->assertEquals(21500.12, price2num('21500,12'), 'Test 21500,12 give 21500.12 with spanish language and not user input');
|
||
// Test with 3 digits
|
||
$this->assertEquals(12123, price2num('12.123', '', 2), 'Test 12.123 give 12123 with spanish language if user input');
|
||
$this->assertEquals(12.123, price2num('12,123', '', 2), 'Test 12,123 give 12.123 with spanish language if user input');
|
||
$this->assertEquals(1012.123, price2num('1.012,123', '', 2), 'Test 1.012,123 give 1012.123 with spanish language if user input');
|
||
$this->assertEquals(1012.123, price2num('1 012,123', '', 2), 'Test 1 012,123 give 1012.123 with spanish language if user input');
|
||
$this->assertEquals(12.123, price2num('12.123'), 'Test 12.123 give 12.123 with spanish language and not user input');
|
||
$this->assertEquals(12.123, price2num('12,123'), 'Test 12,123 give 12.123 with spanish language and not user input');
|
||
|
||
// For french language SeparatorThousand=Space and SeparatorDecimal=,
|
||
$newlangs3 = new Translate('', $conf);
|
||
$newlangs3->setDefaultLang('fr_FR');
|
||
$newlangs3->load("main");
|
||
$langs = $newlangs3;
|
||
|
||
$this->assertEquals(1, price2num('1.000', '', 2), 'Test 1.000 give 1 with french language if user input');
|
||
$this->assertEquals(1, price2num('1.000'), 'Test 1.000 give 1 with french language');
|
||
$this->assertEquals(1000, price2num('1 000'), 'Test 1.000 give 1 with french language');
|
||
$this->assertEquals(1.234, price2num('1.234', '', 2), 'Test 1.234 give 1.234 with french language if user input');
|
||
$this->assertEquals(1.234, price2num('1.234'), 'Test 1.234 give 1.234 with french language');
|
||
$this->assertEquals(1.234, price2num('1,234', '', 2), 'Test 1,234 give 1.234 with french language if user input');
|
||
$this->assertEquals(1.234, price2num('1,234'), 'Test 1,234 give 1.234 with french language');
|
||
$this->assertEquals(1111.234, price2num('1 111,234'), 'Test 1 111,234 give 1111.234 with french language');
|
||
$this->assertEquals(21500000, price2num('21500 000'), 'Test 21500 000 give 21500000 with french language');
|
||
$this->assertEquals(21500000, price2num('21 500 000'), 'Test 21 500 000 give 21500000 with french language');
|
||
$this->assertEquals(21500, price2num('21500.00'), 'Test 21500.00 give 21500 with french language');
|
||
$this->assertEquals(21500, price2num('21500,00'), 'Test 21500,00 give 21500 with french language');
|
||
|
||
$this->assertEquals(21500, price2num('21.500,00'), 'Test 21.500,00 give 21500 with french language');
|
||
$this->assertEquals('1.023.210.00', price2num('1.023,210.00'), 'Test invalid 1.023,210.00 with french language');
|
||
$this->assertEquals('1.023.210.00', price2num('1,023.210,00'), 'Test invalid 1,023.210,00 with french language');
|
||
|
||
$langs = $oldlangs;
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* testDolGetDate
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function testDolGetDate()
|
||
{
|
||
global $conf;
|
||
|
||
$conf->global->MAIN_START_WEEK = 0;
|
||
|
||
$tmp = dol_getdate(24 * 60 * 60 + 1, false, 'UTC'); // 2/1/1970 and 1 second = friday
|
||
$this->assertEquals(5, $tmp['wday'], 'Bad value of day in week');
|
||
|
||
$conf->global->MAIN_START_WEEK = 1;
|
||
|
||
$tmp = dol_getdate(1, false, 'UTC'); // 1/1/1970 and 1 second = thirday
|
||
$this->assertEquals(4, $tmp['wday'], 'Bad value of day in week');
|
||
|
||
$tmp = dol_getdate(24 * 60 * 60 + 1, false, 'UTC'); // 2/1/1970 and 1 second = friday
|
||
$this->assertEquals(5, $tmp['wday'], 'Bad value of day in week');
|
||
|
||
$tmp = dol_getdate(1, false, "Europe/Paris"); // 1/1/1970 and 1 second = thirday
|
||
$this->assertEquals(1970, $tmp['year']);
|
||
$this->assertEquals(1, $tmp['mon']);
|
||
$this->assertEquals(1, $tmp['mday']);
|
||
$this->assertEquals(4, $tmp['wday']);
|
||
$this->assertEquals(0, $tmp['yday']);
|
||
$this->assertEquals(1, $tmp['hours']); // We are winter, so we are GMT+1 even during summer
|
||
$this->assertEquals(0, $tmp['minutes']);
|
||
$this->assertEquals(1, $tmp['seconds']);
|
||
|
||
$tmp = dol_getdate(15638401, false, "Europe/Paris"); // 1/7/1970 and 1 second = wednesday
|
||
$this->assertEquals(1970, $tmp['year']);
|
||
$this->assertEquals(7, $tmp['mon']);
|
||
$this->assertEquals(1, $tmp['mday']);
|
||
$this->assertEquals(3, $tmp['wday']);
|
||
$this->assertEquals(181, $tmp['yday']);
|
||
$this->assertEquals(1, $tmp['hours']); // There is no daylight in 1970, so we are GMT+1 even during summer
|
||
$this->assertEquals(0, $tmp['minutes']);
|
||
$this->assertEquals(1, $tmp['seconds']);
|
||
|
||
$tmp = dol_getdate(1593561601, false, "Europe/Paris"); // 1/7/2020 and 1 second = wednesday
|
||
$this->assertEquals(2020, $tmp['year']);
|
||
$this->assertEquals(7, $tmp['mon']);
|
||
$this->assertEquals(1, $tmp['mday']);
|
||
$this->assertEquals(3, $tmp['wday']);
|
||
$this->assertEquals(182, $tmp['yday']); // 182 and not 181, due to the 29th february
|
||
$this->assertEquals(2, $tmp['hours']); // There is a daylight, so we are GMT+2
|
||
$this->assertEquals(0, $tmp['minutes']);
|
||
$this->assertEquals(1, $tmp['seconds']);
|
||
|
||
$tmp = dol_getdate(1, false, 'UTC'); // 1/1/1970 and 1 second = thirday
|
||
$this->assertEquals(1970, $tmp['year']);
|
||
$this->assertEquals(1, $tmp['mon']);
|
||
$this->assertEquals(1, $tmp['mday']);
|
||
$this->assertEquals(4, $tmp['wday']);
|
||
$this->assertEquals(0, $tmp['yday']);
|
||
// We must disable this because on CI, timezone is may be UTC or something else
|
||
//$this->assertEquals(1, $tmp['hours']); // We are winter, so we are GMT+1 even during summer
|
||
$this->assertEquals(0, $tmp['minutes']);
|
||
$this->assertEquals(1, $tmp['seconds']);
|
||
|
||
$tmp = dol_getdate(15638401, false, 'UTC'); // 1/7/1970 and 1 second = wednesday
|
||
$this->assertEquals(1970, $tmp['year']);
|
||
$this->assertEquals(7, $tmp['mon']);
|
||
$this->assertEquals(1, $tmp['mday']);
|
||
$this->assertEquals(3, $tmp['wday']);
|
||
$this->assertEquals(181, $tmp['yday']);
|
||
// We must disable this because on CI, timezone is may be UTC or something else
|
||
//$this->assertEquals(1, $tmp['hours']); // There is no daylight in 1970, so we are GMT+1 even during summer
|
||
$this->assertEquals(0, $tmp['minutes']);
|
||
$this->assertEquals(1, $tmp['seconds']);
|
||
|
||
$tmp = dol_getdate(1593561601, false, 'UTC'); // 1/7/2020 and 1 second = wednesday
|
||
$this->assertEquals(2020, $tmp['year']);
|
||
$this->assertEquals(7, $tmp['mon']);
|
||
$this->assertEquals(1, $tmp['mday']);
|
||
$this->assertEquals(3, $tmp['wday']);
|
||
$this->assertEquals(182, $tmp['yday']); // 182 and not 181, due to the 29th february
|
||
// We must disable this because on CI, timezone is may be UTC or something else
|
||
//$this->assertEquals(2, $tmp['hours']); // There is a daylight, so we are GMT+2
|
||
$this->assertEquals(0, $tmp['minutes']);
|
||
$this->assertEquals(1, $tmp['seconds']);
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
/**
|
||
* testMakeSubstitutions
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function testMakeSubstitutions()
|
||
{
|
||
global $conf, $langs, $mysoc;
|
||
$langs->load("main");
|
||
|
||
// Try simple replacement
|
||
$substit = array("__AAA__" => 'Not used', "__BBB__" => 'Not used', "__CCC__" => "C instead", "DDD" => "D instead");
|
||
$substit += getCommonSubstitutionArray($langs);
|
||
|
||
$chaine = 'This is a string with theme constant __[MAIN_THEME]__ and __(DIRECTION)__ and __CCC__ and DDD and __MYCOMPANY_NAME__ and __YEAR__';
|
||
$newstring = make_substitutions($chaine, $substit);
|
||
print __METHOD__." ".$newstring."\n";
|
||
$this->assertEquals($newstring, 'This is a string with theme constant eldy and ltr and C instead and D instead and '.$mysoc->name.' and '.dol_print_date(dol_now(), '%Y', 'gmt'));
|
||
|
||
// Try mix HTML not HTML, no change on initial text
|
||
$substit = array("__NOHTML__" => 'No html', "__HTML__" => '<b>HTML</b>');
|
||
|
||
$chaine = "This is a text with\nNew line\nThen\n__NOHTML__\nThen\n__HTML__";
|
||
$newstring = make_substitutions($chaine, $substit, $langs);
|
||
print __METHOD__." ".$newstring."\n";
|
||
$this->assertEquals($newstring, "This is a text with\nNew line\nThen\nNo html\nThen\n<b>HTML</b>", 'Test on make_substitutions with conversion of inserted values only');
|
||
|
||
// Try mix HTML not HTML, accept to change initial text
|
||
$substit = array("__NOHTML__" => 'No html', "__HTML__" => '<b>HTML</b>');
|
||
|
||
$chaine = "This is a text with\nNew line\nThen\n__NOHTML__\nThen\n__HTML__";
|
||
$newstring = make_substitutions($chaine, $substit, $langs, 1);
|
||
print __METHOD__." ".$newstring."\n";
|
||
$this->assertEquals($newstring, "This is a text with<br>\nNew line<br>\nThen<br>\nNo html<br>\nThen<br>\n<b>HTML</b>", 'Test on make_substitutions with full conversion of text accepted');
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* testDolStringIsGoodIso
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function testDolStringIsGoodIso()
|
||
{
|
||
global $conf, $langs;
|
||
|
||
$chaine = 'This is an ISO string';
|
||
$result = dol_string_is_good_iso($chaine);
|
||
$this->assertEquals($result, 1);
|
||
|
||
$chaine = 'This is a not ISO string '.chr(0);
|
||
$result = dol_string_is_good_iso($chaine);
|
||
$this->assertEquals($result, 0);
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* testUtf8Check
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function testUtf8Check()
|
||
{
|
||
global $conf, $langs;
|
||
|
||
$chaine = 'This is an UTF8 string with a é.';
|
||
$result = utf8_check($chaine);
|
||
$this->assertEquals(true, $result);
|
||
|
||
$chaine = mb_convert_encoding('This is an UTF8 with a é.', 'ISO-8859-1', 'UTF-8');
|
||
$result = utf8_check($chaine);
|
||
$this->assertEquals(false, $result);
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* testUtf8Valid
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function testUtf8Valid()
|
||
{
|
||
global $conf, $langs;
|
||
|
||
$chaine = 'This is an UTF8 string with a é.';
|
||
$result = utf8_valid($chaine);
|
||
$this->assertEquals(true, $result);
|
||
|
||
$chaine = mb_convert_encoding('This is an UTF8 with a é.', 'ISO-8859-1', 'UTF-8');
|
||
$result = utf8_valid($chaine);
|
||
$this->assertEquals(false, $result);
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* testGetUserRemoteIP
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function testGetUserRemoteIP()
|
||
{
|
||
global $conf, $langs;
|
||
|
||
$_SERVER['HTTP_X_FORWARDED_FOR'] = '1.2.3.4';
|
||
$_SERVER['HTTP_CLIENT_IP'] = '5.6.7.8';
|
||
$result = getUserRemoteIP();
|
||
$this->assertEquals($result, '1.2.3.4');
|
||
|
||
$_SERVER['HTTP_X_FORWARDED_FOR'] = '1.2.3.4<corrupted>';
|
||
$_SERVER['HTTP_CLIENT_IP'] = '5.6.7.8';
|
||
$result = getUserRemoteIP();
|
||
$this->assertEquals($result, '5.6.7.8');
|
||
|
||
$_SERVER['HTTP_X_FORWARDED_FOR'] = '[1:2:3:4]';
|
||
$_SERVER['HTTP_CLIENT_IP'] = '5.6.7.8';
|
||
$result = getUserRemoteIP();
|
||
$this->assertEquals($result, '[1:2:3:4]');
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* testFetchObjectByElement
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function testFetchObjectByElement()
|
||
{
|
||
global $conf, $langs;
|
||
|
||
$result = fetchObjectByElement(0, 'product');
|
||
|
||
$this->assertTrue(is_object($result));
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* testRoundUpToNextMultiple
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testRoundUpToNextMultiple()
|
||
{
|
||
$this->assertEquals(roundUpToNextMultiple(39.5), 40);
|
||
$this->assertEquals(roundUpToNextMultiple(40), 40);
|
||
$this->assertEquals(roundUpToNextMultiple(40.4), 45);
|
||
$this->assertEquals(roundUpToNextMultiple(40.5), 45);
|
||
$this->assertEquals(roundUpToNextMultiple(44.5), 45);
|
||
|
||
$this->assertEquals(roundUpToNextMultiple(39.5, 10), 40);
|
||
$this->assertEquals(roundUpToNextMultiple(40, 10), 40);
|
||
$this->assertEquals(roundUpToNextMultiple(40.5, 10), 50);
|
||
$this->assertEquals(roundUpToNextMultiple(44.5, 10), 50);
|
||
|
||
$this->assertEquals(roundUpToNextMultiple(39.5, 6), 42);
|
||
$this->assertEquals(roundUpToNextMultiple(40, 6), 42);
|
||
$this->assertEquals(roundUpToNextMultiple(40.5, 6), 42);
|
||
$this->assertEquals(roundUpToNextMultiple(44.5, 6), 48);
|
||
}
|
||
|
||
/**
|
||
* testNaturalSearch
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testNaturalSearch()
|
||
{
|
||
global $db;
|
||
|
||
$s = natural_search("t.field", "abc def");
|
||
$this->assertEquals(" AND (t.field LIKE '%abc%' AND t.field LIKE '%def%')", $s);
|
||
|
||
$s = natural_search("t.field", "'abc def' ghi");
|
||
$this->assertEquals(" AND (t.field LIKE '%abc def%' AND t.field LIKE '%ghi%')", $s);
|
||
|
||
$s = natural_search("t.field", "abc def,ghi", 3); // mode 3 is to provide a list of string separated with coma
|
||
$this->assertEquals(" AND (t.field IN ('abc def','ghi'))", $s);
|
||
|
||
$s = natural_search("t.field", "'ab\'c' def','ghi', 'jkl'", 3); // mode 3 is to provide a list of string separated with coma
|
||
$this->assertEquals(" AND (t.field IN ('abc def','ghi','jkl'))", $s);
|
||
|
||
$s = natural_search("t.field", "a,b", 3); // mode 3 is to provide a list of string separated with coma
|
||
$this->assertEquals(" AND (t.field IN ('a','b'))", $s);
|
||
|
||
$s = natural_search("t.field", "A'@%B", 3); // mode 3 is to provide a list of string separated with coma
|
||
$this->assertEquals(" AND (t.field IN ('AB'))", $s);
|
||
|
||
/*
|
||
$s = $db->sanitize("a,b", 1);
|
||
var_dump($s);
|
||
$s = $db->sanitize("'a',b", 1);
|
||
var_dump($s);
|
||
$s = $db->sanitize("'a'b',c", 1);
|
||
var_dump($s);
|
||
*/
|
||
|
||
$s = natural_search("t.field", "KØB", 3); // mode 3 is to provide a list of string separated with coma
|
||
$this->assertEquals(" AND (t.field IN ('KØB'))", $s);
|
||
}
|
||
|
||
/**
|
||
* testDolExplodeKeepIfQuotes
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testDolExplodeKeepIfQuotes()
|
||
{
|
||
global $db;
|
||
|
||
$result = dolExplodeKeepIfQuotes("a b");
|
||
$this->assertEquals("a", $result[0]);
|
||
$this->assertEquals("b", $result[1]);
|
||
|
||
$result = dolExplodeKeepIfQuotes("'a' 'b'");
|
||
$this->assertEquals("a", $result[0]);
|
||
$this->assertEquals("b", $result[1]);
|
||
|
||
$result = dolExplodeKeepIfQuotes("a 'b' c");
|
||
$this->assertEquals("a", $result[0]);
|
||
$this->assertEquals("b", $result[1]);
|
||
$this->assertEquals("c", $result[2]);
|
||
|
||
$result = dolExplodeKeepIfQuotes("a b'c");
|
||
$this->assertEquals("a", $result[0]);
|
||
$this->assertEquals("b'c", $result[1]);
|
||
|
||
$result = dolExplodeKeepIfQuotes("a 'b'c");
|
||
$this->assertEquals("a", $result[0]);
|
||
$this->assertEquals("b", $result[1]);
|
||
$this->assertEquals("c", $result[2]);
|
||
|
||
$result = dolExplodeKeepIfQuotes("a 'b c'");
|
||
$this->assertEquals("a", $result[0]);
|
||
$this->assertEquals("b c", $result[1]);
|
||
|
||
$result = dolExplodeKeepIfQuotes("1 0");
|
||
$this->assertEquals("1", $result[0]);
|
||
$this->assertEquals("0", $result[1]);
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* testDolSanitizePathName
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testDolSanitizePathName()
|
||
{
|
||
global $conf,$user,$langs,$db;
|
||
$conf = $this->savconf;
|
||
$user = $this->savuser;
|
||
$langs = $this->savlangs;
|
||
$db = $this->savdb;
|
||
|
||
$s = '../aéa/bbb ccc/ddd';
|
||
$result = dol_sanitizePathName($s);
|
||
$this->assertEquals('_/aéa/bbb ccc/ddd', $result);
|
||
|
||
$s = '../aéa/bbb ccc/ddd';
|
||
$result = dol_sanitizePathName($s, '_', 1);
|
||
$this->assertEquals('_/aea/bbb ccc/ddd', $result);
|
||
|
||
$s = 'C:\ccc/d\'d"d$';
|
||
$result = dol_sanitizePathName($s);
|
||
$this->assertEquals('C:\ccc/d\'d_d_', $result);
|
||
|
||
$s = 'C:\ccc/d\'d"d$';
|
||
$result = dol_sanitizePathName($s);
|
||
$this->assertEquals('C:\ccc/d\'d_d_', $result);
|
||
|
||
$s = '/aaa/bbb -a -b';
|
||
$result = dol_sanitizePathName($s);
|
||
$this->assertEquals('/aaa/bbb _a _b', $result);
|
||
|
||
$s = '/aaa/bbb -a -b';
|
||
$result = dol_sanitizePathName($s, '_', 0, 1);
|
||
$this->assertEquals('/aaa/bbb -a -b', $result);
|
||
}
|
||
|
||
/**
|
||
* testPrice
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testPrice()
|
||
{
|
||
global $conf;
|
||
|
||
// English formatting: SeparatorDecimal=. and SeparatorThousand=,
|
||
$langsus = new Translate('', $conf);
|
||
$langsus->setDefaultLang('en_US');
|
||
$langsus->load('main');
|
||
|
||
// Default rounding is min(MAIN_MAX_DECIMALS_UNIT, MAIN_MAX_DECIMALS_TOT) = min(5, 2) = 2 (checked in setUpBeforeClass)
|
||
$this->assertEquals('1,000.00', price(1000, 0, $langsus));
|
||
$this->assertEquals('0.00', price(0, 0, $langsus));
|
||
$this->assertEquals('-1,000.00', price(-1000, 0, $langsus));
|
||
$this->assertEquals('1,234.50', price(1234.5, 0, $langsus));
|
||
// More decimals than the default rounding are kept, to not lose information
|
||
$this->assertEquals('1,234.567', price(1234.567, 0, $langsus));
|
||
|
||
// French formatting: SeparatorDecimal=, and SeparatorThousand=Space
|
||
$langsfr = new Translate('', $conf);
|
||
$langsfr->setDefaultLang('fr_FR');
|
||
$langsfr->load('main');
|
||
|
||
$this->assertEquals('1 234,50', price(1234.5, 0, $langsfr));
|
||
|
||
// HTML mode replaces spaces with
|
||
$this->assertEquals('1 234,50', price(1234.5, 1, $langsfr));
|
||
|
||
// International separators when $outlangs = 'none'
|
||
$this->assertEquals('1234.50', price(1234.5, 0, 'none'));
|
||
|
||
// forcerounding forces the exact number of decimals shown (0 forces rounding to unit)
|
||
$this->assertEquals('1,235', price(1234.5, 0, $langsus, 1, -1, 0));
|
||
// 'MU' forces MAIN_MAX_DECIMALS_UNIT (5, checked in setUpBeforeClass)
|
||
$this->assertEquals('1,234.56789', price(1234.56789, 0, $langsus, 1, -1, 'MU'));
|
||
|
||
// Currency symbol placement: USD is shown before the amount, EUR after
|
||
$this->assertEquals('$1,000.00', price(1000, 0, $langsus, 1, -1, -1, 'USD'));
|
||
$this->assertEquals('1,000.00 €', price(1000, 0, $langsus, 1, -1, -1, 'EUR'));
|
||
}
|
||
|
||
/**
|
||
* testDolPrintDate
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testDolPrintDate()
|
||
{
|
||
// Timestamp for 2020-07-01 00:00:01 UTC (same value already used and verified in testDolGetDate)
|
||
$timestamp = 1593561601;
|
||
|
||
$this->assertEquals('', dol_print_date('', 'standard', true), 'Empty input must return empty string');
|
||
$this->assertEquals('1970-01-01 00:00:00', dol_print_date(0, 'standard', true), 'Timestamp 0 is a valid date (1970-01-01)');
|
||
|
||
// Format shortcuts that are not language-sensitive
|
||
$this->assertEquals('2020-07-01', dol_print_date($timestamp, 'dayrfc', true));
|
||
$this->assertEquals('2020-07-01 00:00:01', dol_print_date($timestamp, 'standard', true));
|
||
$this->assertEquals('2020-07-01T00:00:01Z', dol_print_date($timestamp, 'dayhourrfc', true));
|
||
$this->assertEquals('20200701000001', dol_print_date($timestamp, 'dayhourlog', true));
|
||
|
||
// A literal (non-shortcut) strftime-style format string
|
||
$this->assertEquals('01/07/2020 00:00', dol_print_date($timestamp, '%d/%m/%Y %H:%M', true));
|
||
|
||
// tzoutput=false uses the PHP server timezone instead of GMT: forced to UTC here so both must match
|
||
$savtz = date_default_timezone_get();
|
||
date_default_timezone_set('UTC');
|
||
$this->assertEquals('2020-07-01 00:00:01', dol_print_date($timestamp, 'standard', false));
|
||
date_default_timezone_set($savtz);
|
||
}
|
||
|
||
/**
|
||
* testDolPrintSize
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testDolPrintSize()
|
||
{
|
||
global $conf, $langs;
|
||
|
||
$oldlangs = $langs;
|
||
$newlangs = new Translate('', $conf);
|
||
$newlangs->setDefaultLang('en_US');
|
||
$newlangs->load('main');
|
||
$langs = $newlangs;
|
||
|
||
// Below the 10240 threshold, size is always kept in bytes regardless of $shortvalue
|
||
$this->assertEquals('500 Bytes', dol_print_size(500));
|
||
$this->assertEquals('500 Bytes', dol_print_size(500, 1));
|
||
|
||
// Above the threshold, $shortvalue=1 converts to Kilobytes (rounded)
|
||
$this->assertEquals('20 Kilobytes', dol_print_size(20000, 1));
|
||
$this->assertEquals('20 Kb', dol_print_size(20000, 1, 1), '$shortunit=1 uses the short unit label');
|
||
|
||
// $shortvalue=0 (default) never converts, even for a large size
|
||
$this->assertEquals('20000 Bytes', dol_print_size(20000, 0));
|
||
$this->assertEquals('20000 b.', dol_print_size(20000, 0, 1));
|
||
|
||
$langs = $oldlangs;
|
||
}
|
||
|
||
/**
|
||
* testDolGetFirstLastname
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testDolGetFirstLastname()
|
||
{
|
||
$this->assertEquals('John Smith', dolGetFirstLastname('John', 'Smith', 1), 'nameorder=1: firstname then lastname');
|
||
$this->assertEquals('Smith John', dolGetFirstLastname('John', 'Smith', 0), 'nameorder=0: lastname then firstname');
|
||
$this->assertEquals('John', dolGetFirstLastname('John', 'Smith', 2), 'nameorder=2: firstname only');
|
||
$this->assertEquals('Smith', dolGetFirstLastname('', 'Smith', 3), 'nameorder=3: firstname if defined else lastname (here empty firstname)');
|
||
$this->assertEquals('John', dolGetFirstLastname('John', '', 3), 'nameorder=3: firstname if defined');
|
||
$this->assertEquals('Smith', dolGetFirstLastname('John', 'Smith', 4), 'nameorder=4: lastname only');
|
||
$this->assertEquals('John', dolGetFirstLastname('John', '', 5), 'nameorder=5: lastname if defined else firstname (here empty lastname)');
|
||
$this->assertEquals('Smith', dolGetFirstLastname('', 'Smith', 5), 'nameorder=5: lastname if defined');
|
||
// nameorder=-1 (auto) falls back to MAIN_FIRSTNAME_NAME_POSITION, default is firstname+lastname
|
||
$this->assertEquals('John Smith', dolGetFirstLastname('John', 'Smith', -1));
|
||
}
|
||
|
||
/**
|
||
* testJsonOrUnserialize
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testJsonOrUnserialize()
|
||
{
|
||
// Valid JSON is decoded as-is (object and array forms)
|
||
$this->assertEquals(array('a' => 1, 'b' => 'x'), jsonOrUnserialize('{"a":1,"b":"x"}'));
|
||
$this->assertEquals(array(1, 2, 3), jsonOrUnserialize('[1,2,3]'));
|
||
|
||
// A legacy PHP-serialized string (not valid JSON) falls back to unserialize()
|
||
$this->assertEquals(array('a' => 1), jsonOrUnserialize('a:1:{s:1:"a";i:1;}'));
|
||
|
||
// A string that is neither valid JSON nor a valid serialized value returns false
|
||
$this->assertFalse(@jsonOrUnserialize('not valid at all'));
|
||
}
|
||
|
||
/**
|
||
* testPictoFromLangcode
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testPictoFromLangcode()
|
||
{
|
||
$this->assertEquals('', picto_from_langcode(''));
|
||
$this->assertEquals('<span class="fa fa-language"></span>', picto_from_langcode('auto'));
|
||
|
||
// 'fr_CA' is one of the special-cased codes using a specific flag (Martinique)
|
||
$this->assertEquals('<span class="flag-sprite mq" title="fr_CA"></span>', picto_from_langcode('fr_CA'));
|
||
|
||
// Generic 'xx_YY' code: flag is derived from the country part (YY)
|
||
$this->assertEquals('<span class="flag-sprite us" title="en_US"></span>', picto_from_langcode('en_US'));
|
||
|
||
// A bare 2-letter country code (no underscore) is used as-is
|
||
$this->assertEquals('<span class="flag-sprite fr" title="FR"></span>', picto_from_langcode('FR'));
|
||
|
||
// $notitlealt=1 removes the title attribute
|
||
$this->assertEquals('<span class="flag-sprite us"></span>', picto_from_langcode('en_US', '', 1));
|
||
|
||
// A 'class="..."' in $moreatt is merged into the span's own class instead of being added as a separate attribute
|
||
$this->assertEquals('<span class="flag-sprite us saturatemedium" title="en_US"></span>', picto_from_langcode('en_US', 'class="saturatemedium"'));
|
||
|
||
// Any other attribute in $moreatt is kept as a separate attribute
|
||
$this->assertEquals('<span class="flag-sprite us" style="float: right" title="en_US"></span>', picto_from_langcode('en_US', 'style="float: right"'));
|
||
}
|
||
|
||
/**
|
||
* testGetElementProperties
|
||
*
|
||
* @return void
|
||
*/
|
||
public function testGetElementProperties()
|
||
{
|
||
// Generic path: no '@'/'_' parsing, no special case branch, so classfile/classname are completed
|
||
// from subelement (classfile=strtolower(subelement), classname=ucfirst(subelement))
|
||
$properties = getElementProperties('project');
|
||
$this->assertEquals('project', $properties['element']);
|
||
$this->assertEquals('projet', $properties['module']);
|
||
$this->assertEquals('project', $properties['subelement']);
|
||
$this->assertEquals('projet', $properties['table_element']);
|
||
$this->assertEquals('projet/class', $properties['classpath']);
|
||
$this->assertEquals('project', $properties['classfile']);
|
||
$this->assertEquals('Project', $properties['classname']);
|
||
|
||
// 'myobject@mymodule' syntax to ask a resource from an external module: note that table_element
|
||
// keeps the full original string (it is never cleaned of the '@mymodule' part)
|
||
$properties = getElementProperties('myobject@mymodule');
|
||
$this->assertEquals('myobject', $properties['element']);
|
||
$this->assertEquals('mymodule', $properties['module']);
|
||
$this->assertEquals('myobject', $properties['subelement']);
|
||
$this->assertEquals('myobject@mymodule', $properties['table_element']);
|
||
$this->assertEquals('mymodule/class', $properties['classpath']);
|
||
$this->assertEquals('myobject', $properties['classfile']);
|
||
$this->assertEquals('Myobject', $properties['classname']);
|
||
|
||
// 'myobject_mysubobject' syntax: element/module resolved from the string, but the specific
|
||
// 'project_task' case branch then overrides module (projet, not project) and table_element
|
||
$properties = getElementProperties('project_task');
|
||
$this->assertEquals('project', $properties['element']);
|
||
$this->assertEquals('projet', $properties['module']);
|
||
$this->assertEquals('task', $properties['subelement']);
|
||
$this->assertEquals('projet_task', $properties['table_element']);
|
||
$this->assertEquals('projet/class', $properties['classpath']);
|
||
$this->assertEquals('task', $properties['classfile']);
|
||
$this->assertEquals('Task', $properties['classname']);
|
||
|
||
// Generic '...det' fallback (no dedicated case branch for this fictional module): module and
|
||
// subelement are stripped of the 'det' suffix, but element is not, and classname keeps the raw
|
||
// (non-capitalized) result of the suffix replacement since it is not empty afterwards
|
||
$properties = getElementProperties('myobjectdet');
|
||
$this->assertEquals('myobjectdet', $properties['element']);
|
||
$this->assertEquals('myobject', $properties['module']);
|
||
$this->assertEquals('myobject', $properties['subelement']);
|
||
$this->assertEquals('myobjectdet', $properties['table_element']);
|
||
$this->assertEquals('myobject/class', $properties['classpath']);
|
||
$this->assertEquals('myobject', $properties['classfile']);
|
||
$this->assertEquals('myobjectLine', $properties['classname']);
|
||
|
||
// 'contratdet': the generic '...det' fallback runs first (module=contrat is in the list of
|
||
// modules using "Ligne" instead of "Line", so classname=contratLigne), then the dedicated
|
||
// 'contratdet' case branch only adds parent_element on top, it does not touch classname/classfile
|
||
$properties = getElementProperties('contratdet');
|
||
$this->assertEquals('contrat', $properties['module']);
|
||
$this->assertEquals('contrat', $properties['subelement']);
|
||
$this->assertEquals('contratdet', $properties['table_element']);
|
||
$this->assertEquals('contrat', $properties['parent_element']);
|
||
$this->assertEquals('contrat/class', $properties['classpath']);
|
||
$this->assertEquals('contrat', $properties['classfile']);
|
||
$this->assertEquals('contratLigne', $properties['classname']);
|
||
|
||
// 'facturedet': same generic fallback runs first, but this time the dedicated 'facturedet' case
|
||
// branch overrides classpath (fuller 'compta/facture/class' path) and classname (capitalized
|
||
// 'FactureLigne' instead of the generic fallback's lowercase 'factureLigne')
|
||
$properties = getElementProperties('facturedet');
|
||
$this->assertEquals('facture', $properties['module']);
|
||
$this->assertEquals('facturedet', $properties['table_element']);
|
||
$this->assertEquals('facture', $properties['parent_element']);
|
||
$this->assertEquals('compta/facture/class', $properties['classpath']);
|
||
$this->assertEquals('facture', $properties['classfile']);
|
||
$this->assertEquals('FactureLigne', $properties['classname']);
|
||
// facture is a core module, always configured with an output directory
|
||
$this->assertNotEmpty($properties['dir_output']);
|
||
|
||
// 'action'/'actioncomm' special case: note how table_element is corrected to 'actioncomm' even
|
||
// though the input 'action' is kept as-is in element, and subelement is capitalized 'Actioncomm'
|
||
$properties = getElementProperties('action');
|
||
$this->assertEquals('action', $properties['element']);
|
||
$this->assertEquals('agenda', $properties['module']);
|
||
$this->assertEquals('Actioncomm', $properties['subelement']);
|
||
$this->assertEquals('actioncomm', $properties['table_element']);
|
||
$this->assertEquals('comm/action/class', $properties['classpath']);
|
||
$this->assertEquals('actioncomm', $properties['classfile']);
|
||
$this->assertEquals('Actioncomm', $properties['classname']);
|
||
}
|
||
}
|