Compare commits

...

72 Commits

Author SHA1 Message Date
jf-cbd
6105255d76 Improve logs readability 2025-03-06 11:46:00 +01:00
jf-cbd
f3909eb50a Improve tests readability 2025-03-06 10:41:52 +01:00
jf-cbd
7f2a1a6416 N°4459 and N°7640 - Workaround for sodium default encrypted key invalid 2025-03-06 10:41:26 +01:00
jf-cbd
32dbf1490c WIP 2025-03-05 17:24:28 +01:00
jf-cbd
2dc9f5dcc6 WIP 2025-03-05 16:49:51 +01:00
jf-cbd
200b148c86 WIP 2025-03-05 16:28:29 +01:00
jf-cbd
6fcbf68013 WIP 2025-03-05 15:44:22 +01:00
jf-cbd
2fedfa1b4c WIP 2025-03-05 14:35:51 +01:00
jf-cbd
755ac3a15c WIP 2025-03-03 18:28:15 +01:00
jf-cbd
01e2d3b317 WIP 2025-03-03 17:27:36 +01:00
jf-cbd
39fd258544 Fix tests 2025-03-03 15:43:10 +01:00
jf-cbd
f58a2be620 N°8231 - making rest api log more readable 2025-03-03 15:00:54 +01:00
jf-cbd
e8c622a15d Making JSON more readable 2025-02-28 16:35:59 +01:00
jf-cbd
783fa01fc9 Fix tests 2025-02-28 16:19:09 +01:00
jf-cbd
b1f49ae487 Clean test 2025-02-28 15:51:11 +01:00
jf-cbd
896069d64b WIP 2025-02-28 11:45:44 +01:00
jf-cbd
85434578d3 WIP 2025-02-28 11:00:24 +01:00
jf-cbd
e142fba0e6 WIP 2025-02-28 10:50:06 +01:00
jf-cbd
c9f32311b4 WIP 2025-02-27 17:56:46 +01:00
jf-cbd
a9e10742ec WIP 2025-02-27 17:26:14 +01:00
jf-cbd
cb2a093498 WIP 2025-02-27 16:00:58 +01:00
denis.flaven@combodo.com
944b1f557d Work in progress 2025-02-27 12:17:52 +01:00
denis.flaven@combodo.com
affed69999 Version number bump. 2025-02-07 10:09:48 +01:00
denis.flaven@combodo.com
d5754fc568 N°8135 - Bump datamodel version. 2025-01-31 17:04:56 +01:00
jf-cbd
44290db312 N°8134 - Portal user profile is broken, regression from 7776 2025-01-28 10:23:44 +01:00
jf-cbd
c49ceae75e Fix HandleForm call 2025-01-21 16:46:15 +01:00
jf-cbd
8980f627e9 Fix format 2025-01-21 12:09:06 +01:00
jf-cbd
160bfd714b N°7776 remove twig from ajax calls 2025-01-20 15:41:22 +01:00
jf-cbd
8d58372074 Update unattended installation script documentation 2025-01-16 17:13:26 +01:00
Eric Espie
37fc1a5723 N°7810 - security hardening 2024-12-27 09:04:28 +01:00
jf-cbd
95aa444ee6 Security hardening 2024-12-13 16:48:13 +01:00
jf-cbd
f5de808c7c Security hardening (#685)
* security hardening
2024-12-13 15:09:18 +01:00
Benjamin Dalsass
e03033ce52 N°7219 - Fatal error following dashboard modification when dashboard title contains an é 2024-11-27 09:40:22 +01:00
jf-cbd
374b35f78a 🚀 Fix GitHub action 2024-11-07 14:50:46 +01:00
jf-cbd
04bd8cc5ce 🚀 Update GitHub actions to improve PR classification 2024-10-22 16:07:47 +02:00
denis.flaven@combodo.com
8cb701bda3 🔖 Prepare 2.7.11 version 2024-09-26 16:53:24 +02:00
jf-cbd
1b29746806 Rename github token 2024-09-23 17:14:41 +02:00
jf-cbd
fb9c317256 Add an action in the workflow to automatically add pull requests to the Combodo PRs dashboard 2024-09-23 14:43:33 +02:00
Eric Espie
0904a21e3f Cleanup ItopTestCase 2024-06-24 11:50:37 +02:00
Molkobain
82d11eeb47 N°7127 - Upgrade handlebars.js to v4.7.8 2024-06-21 11:19:39 +02:00
Eric Espie
142d6c8993 N°7533 - Detect and warns on Galera clusters 2024-06-20 11:06:57 +02:00
Timothee
320922a13d N°7545 Correctly display error message 2024-06-17 16:49:33 +02:00
Eric Espie
f03d731b1d N°7533 - Prevent installation of iTop on Galera clusters 2024-06-12 16:14:23 +02:00
Eric Espie
8be7628668 N°7548 - Code hardening 2024-05-29 18:11:36 +02:00
Eric Espie
62caf16153 N°7364 - Code hardening 2024-05-21 14:20:30 +02:00
odain
163a3afc0f N°7426 - no session created - replace php_sapi_name() by PHP_SAPI in unattended 2024-05-16 15:31:08 +02:00
odain
f8b54be896 N°7426 - no session created - replace php_sapi_name() by PHP_SAPI 2024-05-16 14:10:54 +02:00
Romain Quetiez
53dc452d61 Avoid unnecessary custom test environment compilations (base compilation of file modification time) 2024-05-16 09:53:04 +02:00
Romain Quetiez
ccaf2dc5b7 Make the tests compatible with windows (and linux) 2024-05-16 09:53:04 +02:00
Molkobain
5d5df5ad1a N°7255 - Fix misc. stylesheets not working in portal since N°7047 2024-05-07 10:37:39 +02:00
Molkobain
32140b360f Cherry pick fixes from 59a955f4 2024-04-29 11:45:09 +02:00
jf-cbd
d85767a838 Update test to run only on commmunity builds 2024-04-24 11:14:40 +02:00
jf-cbd
eeec57536b Security hardening 2024-04-23 11:55:39 +02:00
Molkobain
16ff6341d0 N°7455 - Fix regression from 4c784886, wrong class tested 2024-04-19 09:14:53 +02:00
Molkobain
9dab8679d6 N°7448 - Update dictionary entry 2024-04-18 18:44:20 +02:00
Molkobain
4c78488644 N°7455 - Ensure form renderer class extends FormRenderer 2024-04-18 18:15:02 +02:00
odain
b65e931c4c N°7439 - setup wizard broken on essential targets after fresh install via unattended CLI 2024-04-15 09:57:54 +02:00
odain
6cb3519308 N°7407 - test readability 2024-04-12 16:59:56 +02:00
odain
cfb9fae648 N°7407 - fix previous commit 2024-04-12 16:49:11 +02:00
odain
f4e791734f N°7407 - remove phpunit annotation 2024-04-12 14:49:20 +02:00
odain
6653ab0668 N°7407 - fix missing extension installation via unattended from production-modules or extension folder 2024-04-12 13:25:40 +02:00
odain
7ab258ba03 N°7439 - setup wizard broken on essential targets 2024-04-12 11:32:04 +02:00
odain
b5af30a93f N°7407 - refactor unattended tests to make work anywhere 2024-04-12 11:32:04 +02:00
Molkobain
bbfa601ab1 N°7446 - Add temporary workaround to fix an issue due to DB views creation. 2024-04-12 09:59:14 +02:00
odain
172b1cb1ff N°7407 - add phpunit annotation to exclude tests on top of targets 2024-04-11 18:54:43 +02:00
Molkobain
ca356859a3 N°7446 - Add comment for better understanding 2024-04-11 16:55:48 +02:00
Molkobain
5efe294895 N°7446 - Fix custom datamodel test class not creating DB tables correctly 2024-04-11 16:50:27 +02:00
odain
e0170ccc7e N°7407 - InstallationFileServiceTest relies on local installation.xml to reduce maintenance 2024-04-10 14:05:39 +02:00
odain
3b78885f38 N°7407 - unattended test cleanup for PHP 8.x deprecations 2024-04-09 11:06:03 +02:00
odain
ed562c9f73 N°7407 - fix unattended test enhancement 2024-04-09 10:02:06 +02:00
odain-cbd
85c576a986 N°7407 - N°7306 - Ease iTop installation via unattended CLI by using installation.xml choices (#641)
* N°7306 - Use iTop configuration settings to run unattended installation (instead of XML file settings)

* 7306 - fix infinite loop with db_tls.ca null

* 7306 - complete fields to use from itop configuration instead of XML setup

* fix using default language from conf

* 6365 - temp work

* 6365 - add option to select modules from installation.xml

* 6365-select modules option in unattended install

* 6365 - pass env to service + debug failed test

* 6365 - debug ci again + separate process annotation

* 6365 - fix test + cleanup

* 6365 - ci using use_installation_xml mode

* 6365 - ci using use_installation_xml mode

* 6365 - pass selected_modules to unattended

* N°6365 - Compute selected modules based on selected extensions coming from XML setup

* switch constr parameters and fix call from unattended cli

* 6365 - use use_installation_xml for unattended install only when no selected modules already provided

* test ci XML setup including selected extensions but no modules

* test ci installing iTop without selected modules/extenesions: guess via installation.xml

* same but without even providing XML setup - comment it in ci_description.ini

* 7306 - cleanup requires

* use infra master

* N°6365 - make current unattended CLI work with any iTop version (CLIPage compatibility)

* N°6365 - log which modules will be installed during setup

* N°6365 - unattended documentation + bash helper

* 6365- fix warning due to copies index access

* 6365 - enhance traces feedback to understant which and how modules are computes

* 6365 - enhance bash CLI + doc

* 6365 - fix require clipage compatibility

* 6365 - add return for better cli ouput

* 6365 - enhance ouput messages

* Document the usage and harmonize argument names (still not perfect)

* 6365 - fix use of new param param-file

* 6365 - fix test + vardump cleanup

* N°6365 - use underscore for unattended install options as advices in the PR

* 6365 -enhance test by using PHP_BINARY

---------

Co-authored-by: Romain Quetiez <romain.quetiez@combodo.com>
2024-04-09 10:00:58 +02:00
odain-cbd
5a34c76cc4 N°5120 - Unattended install : use cty toolkit version (#624)
* N°5120 - Unattended install : use cty toolkit version

* N°5120 - CLI mode validation

* 5120 - bring CI enhancements

* 5120 - bring back saas use-itop-config option: to read db settings from conf directly

* 5120 - move unattended script in setup folder/unattended-install

* 5120 - use-itop-config option: take db_tls_enabled and db_tls_ca into account

* 5120 - move test

* 5120 - put ci enhancements back - logs and conf preservation with install mode

* 5120 - keep PR simple - remove saas use-itop-config option for now

* 5120 - remove ci enhancement to preserve configuration

* Update setup/unattended-install/README.md

Co-authored-by: Thomas Casteleyn <thomas.casteleyn@super-visions.com>

* 5120 - documentation

* 5120 - fix log level

* 5120 - fix test

* fix phpunit test comment

---------

Co-authored-by: Thomas Casteleyn <thomas.casteleyn@super-visions.com>
2024-03-29 11:09:27 +01:00
100 changed files with 3864 additions and 1137 deletions

43
.github/workflows/action.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: Add PRs to Combodo PRs Dashboard
on:
pull_request_target:
types:
- opened
jobs:
add-to-project:
name: Add PR to Combodo Project
runs-on: ubuntu-latest
steps:
- name: Check if author is a member of the organization
id: check-membership
run: |
ORG="Combodo"
AUTHOR=$(jq -r .pull_request.user.login "$GITHUB_EVENT_PATH")
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}" \
"https://api.github.com/orgs/$ORG/members/$AUTHOR")
if [ "$RESPONSE" == "404" ]; then
echo "project_url=https://github.com/orgs/Combodo/projects/5" >> $GITHUB_ENV
echo "is_member=false" >> $GITHUB_ENV
else
echo "project_url=https://github.com/orgs/Combodo/projects/4" >> $GITHUB_ENV
echo "is_member=true" >> $GITHUB_ENV
fi
- name: Add internal tag if member
if: env.is_member == 'true'
run: |
curl -X POST -H "Authorization: token ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/Combodo/iTop/issues/${{ github.event.pull_request.number }}/labels \
-d '{"labels":["internal"]}'
env:
is_member: ${{ env.is_member }}
- name: Add PR to the appropriate project
uses: actions/add-to-project@v1.0.2
with:
project-url: ${{ env.project_url }}
github-token: ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}

View File

@@ -27,7 +27,7 @@ $iTopFolder = __DIR__."/../../../";
require_once("$iTopFolder/approot.inc.php");
require_once(APPROOT."/application/utils.inc.php");
if (php_sapi_name() !== 'cli')
if (PHP_SAPI !== 'cli')
{
throw new \Exception('This script can only run from CLI');
}
@@ -48,4 +48,4 @@ if (!file_exists($sCssFile))
{
fwrite(STDERR, "Failed to compile $sCssFile, exiting.");
exit(1);
}
}

View File

@@ -26,7 +26,7 @@ $iTopFolder = __DIR__ . "/../../" ;
require_once ("$iTopFolder/approot.inc.php");
require_once (APPROOT."/setup/setuputils.class.inc.php");
if (php_sapi_name() !== 'cli')
if (PHP_SAPI !== 'cli')
{
throw new \Exception('This script can only run from CLI');
}
@@ -70,4 +70,4 @@ if (false === empty($aMissing)) {
echo "Some new tests dirs exists !\n"
.' They must be declared either in the allowed or denied list in '.iTopComposer::class." (see N°2651).\n"
.' List of dirs:'."\n".var_export($aMissing, true);
}
}

View File

@@ -1316,6 +1316,11 @@ interface iRestServiceProvider
public function ExecOperation($sVersion, $sVerb, $aParams);
}
interface iRestInputSanitizer
{
public function SanitizeJsonInput(string $sJsonInput): string;
}
/**
* Minimal REST response structure. Derive this structure to add response data and error codes.
*
@@ -1405,6 +1410,14 @@ class RestResult
* @api
*/
public $message;
/**
* Sanitize the content of this result to hide sensitive information
*/
public function SanitizeContent()
{
// The default implementation does nothing
}
}
/**

View File

@@ -1193,12 +1193,12 @@ EOF
$sOkButtonLabel = Dict::S('UI:Button:Save');
$sCancelButtonLabel = Dict::S('UI:Button:Cancel');
$sId = utils::HtmlEntities($this->sId);
$sLayoutClass = utils::HtmlEntities($this->sLayoutClass);
$sId = json_encode($this->sId);
$sLayoutClass = json_encode($this->sLayoutClass);
$sAutoReload = $this->bAutoReload ? 'true' : 'false';
$sAutoReloadSec = (string) $this->iAutoReloadSec;
$sTitle = utils::HtmlEntities($this->sTitle);
$sFile = utils::HtmlEntities($this->GetDefinitionFile());
$sTitle = json_encode($this->sTitle);
$sFile = json_encode($this->GetDefinitionFile());
$sUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php';
$sReloadURL = $this->GetReloadURL();
@@ -1250,15 +1250,15 @@ $('#dashboard_editor').dialog({
});
$('#dashboard_editor .ui-layout-center').runtimedashboard({
dashboard_id: '$sId',
layout_class: '$sLayoutClass',
title: '$sTitle',
dashboard_id: $sId,
layout_class: $sLayoutClass,
title: $sTitle,
auto_reload: $sAutoReload,
auto_reload_sec: $sAutoReloadSec,
submit_to: '$sUrl',
submit_parameters: {operation: 'save_dashboard', file: '$sFile', extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
submit_parameters: {operation: 'save_dashboard', file: $sFile, extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
render_to: '$sUrl',
render_parameters: {operation: 'render_dashboard', file: '$sFile', extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
render_parameters: {operation: 'render_dashboard', file: $sFile, extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
new_dashlet_parameters: {operation: 'new_dashlet'}
});

View File

@@ -199,16 +199,8 @@ class utils
public static function IsModeCLI()
{
$sSAPIName = php_sapi_name();
$sCleanName = strtolower(trim($sSAPIName));
if ($sCleanName == 'cli')
{
return true;
}
else
{
return false;
}
$sCleanName = strtolower(trim(PHP_SAPI));
return ($sCleanName === 'cli');
}
protected static $bPageMode = null;
@@ -316,13 +308,13 @@ class utils
}
return self::Sanitize($retValue, $defaultValue, $sSanitizationFilter);
}
public static function ReadPostedParam($sName, $defaultValue = '', $sSanitizationFilter = 'parameter')
{
$retValue = isset($_POST[$sName]) ? $_POST[$sName] : $defaultValue;
return self::Sanitize($retValue, $defaultValue, $sSanitizationFilter);
}
public static function Sanitize($value, $defaultValue, $sSanitizationFilter)
{
if ($value === $defaultValue)
@@ -338,7 +330,7 @@ class utils
$retValue = $defaultValue;
}
}
return $retValue;
return $retValue;
}
/**
@@ -481,11 +473,11 @@ class utils
$sMimeType = self::GetFileMimeType($sTmpName);
$oDocument = new ormDocument($doc_content, $sMimeType, $sName);
break;
case UPLOAD_ERR_NO_FILE:
// no file to load, it's a normal case, just return an empty document
break;
case UPLOAD_ERR_FORM_SIZE:
case UPLOAD_ERR_INI_SIZE:
throw new FileUploadException(Dict::Format('UI:Error:UploadedFileTooBig', ini_get('upload_max_filesize')));
@@ -494,7 +486,7 @@ class utils
case UPLOAD_ERR_PARTIAL:
throw new FileUploadException(Dict::S('UI:Error:UploadedFileTruncated.'));
break;
case UPLOAD_ERR_NO_TMP_DIR:
throw new FileUploadException(Dict::S('UI:Error:NoTmpDir'));
break;
@@ -507,7 +499,7 @@ class utils
$sName = is_null($sIndex) ? $aFileInfo['name'] : $aFileInfo['name'][$sIndex];
throw new FileUploadException(Dict::Format('UI:Error:UploadStoppedByExtension_FileName', $sName));
break;
default:
throw new FileUploadException(Dict::Format('UI:Error:UploadFailedUnknownCause_Code', $sError));
break;
@@ -615,17 +607,17 @@ class utils
return $aSelectedObj;
}
public static function GetNewTransactionId()
{
return privUITransaction::GetNewTransactionId();
}
public static function IsTransactionValid($sId, $bRemoveTransaction = true)
{
return privUITransaction::IsTransactionValid($sId, $bRemoveTransaction);
}
public static function RemoveTransaction($sId)
{
return privUITransaction::RemoveTransaction($sId);
@@ -810,9 +802,9 @@ class utils
$aDateTokens = array_keys($aSpec);
$aDateRegexps = array_values($aSpec);
}
$sDateRegexp = str_replace($aDateTokens, $aDateRegexps, $sFormat);
if (preg_match('!^(?<head>)'.$sDateRegexp.'(?<tail>)$!', $sDate, $aMatches))
{
$sYear = isset($aMatches['year']) ? $aMatches['year'] : 0;
@@ -829,7 +821,7 @@ class utils
}
// http://www.spaweditor.com/scripts/regex/index.php
}
/**
* Convert an old date/time format specification (using % placeholders)
* to a format compatible with DateTime::createFromFormat
@@ -1248,7 +1240,7 @@ class utils
{
$aArguments['param_file'] = $sParamFile;
}
$aArgs = array();
foreach($aArguments as $sName => $value)
{
@@ -1257,7 +1249,7 @@ class utils
$aArgs[] = "--$sName=".escapeshellarg($value);
}
$sArgs = implode(' ', $aArgs);
$sScript = realpath(APPROOT.$sScriptName);
if (!file_exists($sScript))
{
@@ -1348,7 +1340,7 @@ class utils
public static function GetPopupMenuItems($oPage, $iMenuId, $param, &$aActions, $sTableId = null, $sDataTableId = null)
{
// 1st - add standard built-in menu items
//
//
switch($iMenuId)
{
case iPopupMenuExtension::MENU_OBJLIST_TOOLKIT:
@@ -1373,7 +1365,7 @@ class utils
"mailto:?body=".urlencode($sUrl).' ' // Add an extra space to make it work in Outlook
);
}
if (UserRights::IsActionAllowed($param->GetFilter()->GetClass(), UR_ACTION_BULK_READ, $param) != UR_ALLOWED_NO)
{
// Bulk export actions
@@ -1387,7 +1379,7 @@ class utils
}
$aResult[] = new JSPopupMenuItem('UI:Menu:AddToDashboard', Dict::S('UI:Menu:AddToDashboard'), "DashletCreationDlg('$sOQL', '$sContext')");
$aResult[] = new JSPopupMenuItem('UI:Menu:ShortcutList', Dict::S('UI:Menu:ShortcutList'), "ShortcutListDlg('$sOQL', '$sDataTableId', '$sContext')");
break;
case iPopupMenuExtension::MENU_OBJDETAILS_ACTIONS:
@@ -1401,7 +1393,7 @@ class utils
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/tabularfieldsselector.js');
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.dragtable.js');
$oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/dragtable.css');
$aResult = array(
new SeparatorPopupMenuItem(),
// Static menus: Email this page & CSV Export
@@ -1465,7 +1457,7 @@ class utils
if (is_object($oMenuItem))
{
$aActions[$oMenuItem->GetUID()] = $oMenuItem->GetMenuItem();
foreach($oMenuItem->GetLinkedScripts() as $sLinkedScript)
{
$oPage->add_linked_script($sLinkedScript);
@@ -1602,7 +1594,7 @@ class utils
return $sProposed;
}
}
/**
* Some characters cause troubles with jQuery when used inside DOM IDs, so let's replace them by the safe _ (underscore)
* @param string $sId The ID to sanitize
@@ -1612,13 +1604,13 @@ class utils
{
return str_replace(array(':', '[', ']', '+', '-'), '_', $sId);
}
/**
* Helper to execute an HTTP POST request
* Source: http://netevil.org/blog/2006/nov/http-post-from-php-without-curl
* originaly named after do_post_request
* Does not require cUrl but requires openssl for performing https POSTs.
*
*
* @param string $sUrl The URL to POST the data to
* @param array $aData The data to POST as an array('param_name' => value)
* @param string $sOptionnalHeaders Additional HTTP headers as a string with newlines between headers
@@ -1629,11 +1621,11 @@ class utils
*
* @return string The result of the POST request
* @throws Exception with a specific error message depending on the cause
*/
*/
public static function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null, &$aResponseHeaders = null, $aCurlOptions = array())
{
// $sOptionnalHeaders is a string containing additional HTTP headers that you would like to send in your request.
if (function_exists('curl_init'))
{
// If cURL is available, let's use it, since it provides a greater control over the various HTTP/SSL options
@@ -1667,7 +1659,7 @@ class utils
CURLOPT_POSTFIELDS => http_build_query($aData),
CURLOPT_HTTPHEADER => $aHTTPHeaders,
);
$aAllOptions = $aCurlOptions + $aOptions;
$ch = curl_init($sUrl);
curl_setopt_array($ch, $aAllOptions);
@@ -1693,7 +1685,7 @@ class utils
else
{
// cURL is not available let's try with streams and fopen...
$sData = http_build_query($aData);
$aParams = array('http' => array(
'method' => 'POST',
@@ -1705,7 +1697,7 @@ class utils
$aParams['http']['header'] .= $sOptionnalHeaders;
}
$ctx = stream_context_create($aParams);
$fp = @fopen($sUrl, 'rb', false, $ctx);
if (!$fp)
{
@@ -1746,7 +1738,7 @@ class utils
/**
* Get a standard list of character sets
*
*
* @param array $aAdditionalEncodings Additional values
* @return array of iconv code => english label, sorted by label
*/
@@ -1776,8 +1768,8 @@ class utils
public static function HtmlEntities($sValue)
{
return htmlentities($sValue, ENT_QUOTES, 'UTF-8');
}
}
/**
* Helper to encapsulation iTop's html_entity_decode
* @param string $sValue
@@ -1806,7 +1798,7 @@ class utils
return $e->getMessage();
}
}
/**
* Convert (?) plain text to some HTML markup by replacing newlines by <br/> tags
* and escaping HTML entities
@@ -1819,7 +1811,7 @@ class utils
$sText = str_replace("\r", "\n", $sText);
return str_replace("\n", '<br/>', htmlentities($sText, ENT_QUOTES, 'UTF-8'));
}
/**
* Eventually compiles the SASS (.scss) file into the CSS (.css) file
*
@@ -1882,7 +1874,7 @@ class utils
return $sCss;
}
public static function GetImageSize($sImageData)
{
if (function_exists('getimagesizefromstring')) // PHP 5.4.0 or higher
@@ -1939,7 +1931,7 @@ class utils
case 'image/png':
$img = @imagecreatefromstring($oImage->GetData());
break;
default:
// Unsupported image type, return the image as-is
//throw new Exception("Unsupported image type: '".$oImage->GetMimeType()."'. Cannot resize the image, original image will be used.");
@@ -1953,14 +1945,14 @@ class utils
else
{
// Let's scale the image, preserving the transparency for GIFs and PNGs
$fScale = min($iMaxImageWidth / $iWidth, $iMaxImageHeight / $iHeight);
$iNewWidth = $iWidth * $fScale;
$iNewHeight = $iHeight * $fScale;
$new = imagecreatetruecolor($iNewWidth, $iNewHeight);
// Preserve transparency
if(($oImage->GetMimeType() == "image/gif") || ($oImage->GetMimeType() == "image/png"))
{
@@ -1968,38 +1960,38 @@ class utils
imagealphablending($new, false);
imagesavealpha($new, true);
}
imagecopyresampled($new, $img, 0, 0, 0, 0, $iNewWidth, $iNewHeight, $iWidth, $iHeight);
ob_start();
switch ($oImage->GetMimeType())
{
case 'image/gif':
imagegif($new); // send image to output buffer
break;
case 'image/jpeg':
imagejpeg($new, null, 80); // null = send image to output buffer, 80 = good quality
break;
case 'image/png':
imagepng($new, null, 5); // null = send image to output buffer, 5 = medium compression
break;
}
$oResampledImage = new ormDocument(ob_get_contents(), $oImage->GetMimeType(), $oImage->GetFileName());
@ob_end_clean();
imagedestroy($img);
imagedestroy($new);
return $oResampledImage;
}
}
/**
* Create a 128 bit UUID in the format: {########-####-####-####-############}
*
*
* Note: this method can be run from the command line as well as from the web server.
* Note2: this method is not cryptographically secure! If you need a cryptographically secure value
* consider using open_ssl or PHP 7 methods.
@@ -2037,7 +2029,7 @@ class utils
{
return ModuleService::GetInstance()->GetCurrentModuleName($iCallDepth + 1);
}
/**
* **Warning** : returned result can be invalid as we're using backtrace to find the module dir name
*
@@ -2068,7 +2060,7 @@ class utils
{
return ModuleService::GetInstance()->GetCurrentModuleUrl(1);
}
/**
* @param string $sProperty The name of the property to retrieve
* @param mixed $defaultvalue
@@ -2078,7 +2070,7 @@ class utils
{
return ModuleService::GetInstance()->GetCurrentModuleSetting($sProperty, $defaultvalue);
}
/**
* @param string $sModuleName
* @return string|NULL compiled version of a given module, as it was seen by the compiler
@@ -2087,7 +2079,7 @@ class utils
{
return ModuleService::GetInstance()->GetCompiledModuleVersion($sModuleName);
}
/**
* Check if the given path/url is an http(s) URL
* @param string $sPath
@@ -2102,7 +2094,7 @@ class utils
}
return $bRet;
}
/**
* Check if the given URL is a link to download a document/image on the CURRENT iTop
* In such a case we can read the content of the file directly in the database (if the users rights allow) and return the ormDocument
@@ -2151,7 +2143,7 @@ class utils
}
return $result;
}
/**
* Read the content of a file (and retrieve its MIME type) from either:
* - an URL pointing to a blob (image/document) on the current iTop server
@@ -2195,7 +2187,7 @@ class utils
'html' => 'text/html',
'exe' => 'application/octet-stream',
);
$sData = null;
$sMimeType = 'text/plain'; // Default MIME Type: treat the file as a bunch a characters...
$sFileName = 'uploaded-file'; // Default name for downloaded-files
@@ -2251,7 +2243,7 @@ class utils
}
$sExtension = strtolower(pathinfo($sPath, PATHINFO_EXTENSION));
$sFileName = basename($sPath);
if (array_key_exists($sExtension, $aKnownExtensions))
{
$sMimeType = $aKnownExtensions[$sExtension];
@@ -2265,7 +2257,7 @@ class utils
}
return $oUploadedDoc;
}
protected static function ParseHeaders($aHeaders)
{
$aCleanHeaders = array();
@@ -2290,7 +2282,7 @@ class utils
}
return $aCleanHeaders;
}
/**
* @return string a string based on compilation time or (if not available because the datamodel has not been loaded)
* the version of iTop. This string is useful to prevent browser side caching of content that may vary at each
@@ -2445,7 +2437,7 @@ class utils
{
return false;
}
return substr_compare($haystack, $needle, -strlen($needle)) === 0;
}

View File

@@ -14,7 +14,7 @@ define('APPCONF', APPROOT.'conf/');
* @used-by utils::GetItopVersionWikiSyntax()
* @used-by iTopModulesPhpVersionIntegrationTest
*/
define('ITOP_CORE_VERSION', '2.7.10');
define('ITOP_CORE_VERSION', '2.7.12');
require_once APPROOT.'bootstrap.inc.php';

View File

@@ -48,7 +48,7 @@ if (file_exists(MAINTENANCE_MODE_FILE) && !$bBypassMaintenance)
http_response_code(503);
// Display message depending on the request
include(APPROOT.'application/maintenancemsg.php');
$sSAPIName = strtoupper(trim(php_sapi_name()));
$sSAPIName = strtoupper(trim(PHP_SAPI));
switch (true)
{

View File

@@ -138,7 +138,7 @@ abstract class AttributeDefinition
protected $aCSSClasses;
public function GetType()
public function GetType()
{
return Dict::S('Core:'.get_class($this));
}
@@ -3775,7 +3775,7 @@ class AttributeFinalClass extends AttributeString
*/
class AttributePassword extends AttributeString implements iAttributeNoGroupBy
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
@@ -3851,7 +3851,7 @@ class AttributePassword extends AttributeString implements iAttributeNoGroupBy
*/
class AttributeEncryptedString extends AttributeString implements iAttributeNoGroupBy
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
static $sKey = null; // Encryption key used for all encrypted fields
static $sLibrary = null; // Encryption library used for all encrypted fields
@@ -9243,7 +9243,7 @@ class AttributeSubItem extends AttributeDefinition
*/
class AttributeOneWayPassword extends AttributeDefinition implements iAttributeNoGroupBy
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)

View File

@@ -95,7 +95,7 @@ class MySQLHasGoneAwayException extends MySQLException
{
return array(
2006,
2013
2013,
);
}
@@ -1296,8 +1296,8 @@ class CMDBSource
*/
public static function IsSameFieldTypes($sItopGeneratedFieldType, $sDbFieldType)
{
list($sItopFieldDataType, $sItopFieldTypeOptions, $sItopFieldOtherOptions) = static::GetFieldDataTypeAndOptions($sItopGeneratedFieldType);
list($sDbFieldDataType, $sDbFieldTypeOptions, $sDbFieldOtherOptions) = static::GetFieldDataTypeAndOptions($sDbFieldType);
[$sItopFieldDataType, $sItopFieldTypeOptions, $sItopFieldOtherOptions] = static::GetFieldDataTypeAndOptions($sItopGeneratedFieldType);
[$sDbFieldDataType, $sDbFieldTypeOptions, $sDbFieldOtherOptions] = static::GetFieldDataTypeAndOptions($sDbFieldType);
if (strcasecmp($sItopFieldDataType, $sDbFieldDataType) !== 0)
{
@@ -1734,8 +1734,20 @@ class CMDBSource
return false;
}
/**
* @return string query to upgrade database charset and collation if needed, null if not
public static function GetClusterNb()
{
$result = 0;
$sSql = "SHOW STATUS LIKE 'wsrep_cluster_size';";
$aRows = self::QueryToArray($sSql);
if (count($aRows) > 0)
{
$result = $aRows[0]['Value'];
}
return intval($result);
}
/**
* @return string query to upgrade database charset and collation if needed, null if not
* @throws \MySQLException
*
* @since 2.5.0 N°1001 switch to utf8mb4

View File

@@ -34,7 +34,9 @@
*/
class ObjectResult
{
public $code;
use SanitizeTrait;
public $code;
public $message;
public $class;
public $key;
@@ -122,6 +124,19 @@ class ObjectResult
{
$this->fields[$sAttCode] = $this->MakeResultValue($oObject, $sAttCode, $bExtendedOutput);
}
public function SanitizeContent()
{
foreach($this->fields as $sFieldAttCode => $fieldValue)
{
try {
$oAttDef = MetaModel::GetAttributeDef($this->class, $sFieldAttCode);
} catch (Exception $e) { // for special cases like ID
continue;
}
$this->SanitizeFieldIfSensitive($this->fields, $sFieldAttCode, $fieldValue, $oAttDef);
}
}
}
@@ -181,6 +196,16 @@ class RestResultWithObjects extends RestResult
$sObjKey = get_class($oObject).'::'.$oObject->GetKey();
$this->objects[$sObjKey] = $oObjRes;
}
public function SanitizeContent()
{
parent::SanitizeContent();
foreach($this->objects as $sObjKey => $oObjRes)
{
$oObjRes->SanitizeContent();
}
}
}
class RestResultWithRelations extends RestResultWithObjects
@@ -247,9 +272,10 @@ class RestDelete
*
* @package Core
*/
class CoreServices implements iRestServiceProvider
class CoreServices implements iRestServiceProvider, iRestInputSanitizer
{
/**
use SanitizeTrait;
/**
* Enumerate services delivered by this class
*
* @param string $sVersion The version (e.g. 1.0) supported by the services
@@ -663,6 +689,33 @@ class CoreServices implements iRestServiceProvider
}
return $oResult;
}
public function SanitizeJsonInput(string $sJsonInput): string
{
$sSanitizedJsonInput = $sJsonInput;
$aJsonData = json_decode($sSanitizedJsonInput, true);
$sOperation = $aJsonData['operation'];
switch ($sOperation) {
case 'core/check_credentials':
if (isset($aJsonData['password'])) {
$aJsonData['password'] = '*****';
}
break;
case 'core/update':
case 'core/create':
default :
$sClass = $aJsonData['class'];
if (isset($aJsonData['fields'])) {
foreach ($aJsonData['fields'] as $sFieldAttCode => $fieldValue) {
$oAttDef = MetaModel::GetAttributeDef($sClass, $sFieldAttCode);
$this->SanitizeFieldIfSensitive($aJsonData['fields'], $sFieldAttCode, $fieldValue, $oAttDef);
}
}
break;
}
return json_encode($aJsonData, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
/**
* Helper for object deletion
@@ -802,3 +855,50 @@ class CoreServices implements iRestServiceProvider
return $iLimit * max(0, $iPage - 1);
}
}
trait SanitizeTrait
{
/**
* Sanitize a field if it is sensitive.
*
* @param array $fields The fields array
* @param string $sFieldAttCode The attribute code
* @param mixed $oAttDef The attribute definition
* @throws Exception
*/
private function SanitizeFieldIfSensitive(array &$fields, string $sFieldAttCode, $fieldValue, $oAttDef): void
{
// for simple attribute
if ($oAttDef instanceof iAttributeNoGroupBy) // iAttributeNoGroupBy is equivalent to sensitive attribute
{
$fields[$sFieldAttCode] = '*****';
return;
}
// for 1-n / n-n relation
if ($oAttDef instanceof AttributeLinkedSet) {
foreach ($fieldValue as $i => $aLnkValues) {
foreach ($aLnkValues as $sLnkAttCode => $sLnkValue) {
$oLnkAttDef = MetaModel::GetAttributeDef($oAttDef->GetLinkedClass(), $sLnkAttCode);
if ($oLnkAttDef instanceof iAttributeNoGroupBy) { // 1-n relation
$fields[$sFieldAttCode][$i][$sLnkAttCode] = '*****';
}
elseif ($oAttDef instanceof AttributeLinkedSetIndirect && $oLnkAttDef instanceof AttributeExternalField) { // for n-n relation
$oExtKeyAttDef = MetaModel::GetAttributeDef($oLnkAttDef->GetTargetClass(), $oLnkAttDef->GetExtAttCode());
if ($oExtKeyAttDef instanceof iAttributeNoGroupBy) {
$fields[$sFieldAttCode][$i][$sLnkAttCode] = '*****';
}
}
}
}
return;
}
// for external attribute
if ($oAttDef instanceof AttributeExternalField) {
$oExtKeyAttDef = MetaModel::GetAttributeDef($oAttDef->GetTargetClass(), $oAttDef->GetExtAttCode());
if ($oExtKeyAttDef instanceof iAttributeNoGroupBy) {
$fields[$sFieldAttCode] = '*****';
}
}
}
}

View File

@@ -537,7 +537,7 @@ EOF
}
else
{
throw new Exception('graphviz not found (executable path: '.$sDotExecutable.')');
throw new Exception('graphviz not found');
}
return $sHtml;
}
@@ -592,7 +592,7 @@ EOF
}
else
{
throw new Exception('graphviz not found (executable path: '.$sDotExecutable.')');
throw new Exception('graphviz not found');
}
return $sHtml;
}

View File

@@ -17,7 +17,7 @@
*/
// Beware the version number MUST be enclosed with quotes otherwise v2.3.0 becomes v2 0.3 .0
$version: "v2.7.10";
$version: "v2.7.12";
$approot-relative: "../../../../../" !default; // relative to env-***/branding/themes/***/main.css
// Base colors

View File

@@ -5,7 +5,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'authent-cas/2.7.10',
'authent-cas/2.7.12',
array(
// Identification
//

View File

@@ -27,7 +27,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'authent-external/2.7.10',
'authent-external/2.7.12',
array(
// Identification
//

View File

@@ -9,7 +9,7 @@ if (function_exists('ldap_connect'))
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'authent-ldap/2.7.10',
'authent-ldap/2.7.12',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'authent-local/2.7.10',
'authent-local/2.7.12',
array(
// Identification
//

View File

@@ -24,7 +24,7 @@
/** @noinspection PhpUnhandledExceptionInspection */
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'combodo-db-tools/2.7.10',
'combodo-db-tools/2.7.12',
array(
// Identification
//

View File

@@ -19,7 +19,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-attachments/2.7.10',
'itop-attachments/2.7.12',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-backup/2.7.10',
'itop-backup/2.7.12',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-bridge-virtualization-storage/2.7.10',
'itop-bridge-virtualization-storage/2.7.12',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-change-mgmt-itil/2.7.10',
'itop-change-mgmt-itil/2.7.12',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-change-mgmt/2.7.10',
'itop-change-mgmt/2.7.12',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-config-mgmt/2.7.10',
'itop-config-mgmt/2.7.12',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-config/2.7.10',
'itop-config/2.7.12',
array(
// Identification
//

View File

@@ -24,7 +24,7 @@
/** @noinspection PhpUnhandledExceptionInspection */
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-core-update/2.7.10',
'itop-core-update/2.7.12',
array(
// Identification
//

View File

@@ -18,7 +18,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-datacenter-mgmt/2.7.10',
'itop-datacenter-mgmt/2.7.12',
array(
// Identification
//

View File

@@ -25,7 +25,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-endusers-devices/2.7.10',
'itop-endusers-devices/2.7.12',
array(
// Identification
//

View File

@@ -24,7 +24,7 @@
/** @noinspection PhpUnhandledExceptionInspection */
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-files-information/2.7.10',
'itop-files-information/2.7.12',
array(
// Identification
//

View File

@@ -6,7 +6,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-full-itil/2.7.10',
'itop-full-itil/2.7.12',
array(
// Identification
//

View File

@@ -0,0 +1,19 @@
<?php
class TokenValidation
{
// construct function
public function __construct()
{
}
public function isSetupTokenValid($sParamToken) : bool
{
if (!file_exists(APPROOT.'data/.setup')) {
return false;
}
$sSetupToken = trim(file_get_contents(APPROOT.'data/.setup'));
unlink(APPROOT.'data/.setup');
return $sParamToken === $sSetupToken;
}
}

View File

@@ -280,6 +280,7 @@ try
require_once ('hubconnectorpage.class.inc.php');
require_once (APPROOT.'/application/startup.inc.php');
require_once('TokenValidation.php');
$sTargetRoute = utils::ReadParam('target', ''); // ||browse_extensions|deploy_extensions|
@@ -299,11 +300,20 @@ try
case 'inform_after_setup':
// Hidden IFRAME at the end of the setup
require_once (APPROOT.'/application/ajaxwebpage.class.inc.php');
$oPage = new NiceWebPage('');
$aDataToPost = MakeDataToPost($sTargetRoute);
$oPage->add('<form id="hub_launch_form" action="'.$sHubUrlStateless.'" method="post">');
$oPage->add('<input type="hidden" name="json" value="'.htmlentities(json_encode($aDataToPost), ENT_QUOTES, 'UTF-8').'">');
$oPage->add_ready_script('$("#hub_launch_form").submit();');
$sParamToken = utils::ReadParam('setup_token');
$oTokenValidation = new TokenValidation();
$bIsTokenValid = $oTokenValidation->isSetupTokenValid($sParamToken);
if (UserRights::IsAdministrator() || $bIsTokenValid) {
$oPage = new NiceWebPage('');
$aDataToPost = MakeDataToPost($sTargetRoute);
$oPage->add('<form id="hub_launch_form" action="' . $sHubUrlStateless . '" method="post">');
$oPage->add('<input type="hidden" name="json" value="' . htmlentities(json_encode($aDataToPost), ENT_QUOTES, 'UTF-8') . '">');
$oPage->add_ready_script('$("#hub_launch_form").submit();');
} else {
IssueLog::Error('TokenValidation failed on inform_after_setup page');
throw new Exception("Not allowed");
}
break;
default:

View File

@@ -5,7 +5,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-hub-connector/2.7.10',
'itop-hub-connector/2.7.12',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-incident-mgmt-itil/2.7.10',
'itop-incident-mgmt-itil/2.7.12',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-knownerror-mgmt/2.7.10',
'itop-knownerror-mgmt/2.7.12',
array(
// Identification
//

View File

@@ -5,7 +5,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-oauth-client/2.7.10',
'itop-oauth-client/2.7.12',
array(
// Identification
//

View File

@@ -20,7 +20,7 @@
/** @noinspection PhpUnhandledExceptionInspection */
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-portal-base/2.7.10', array(
'itop-portal-base/2.7.12', array(
// Identification
'label' => 'Portal Development Library',
'category' => 'Portal',

View File

@@ -15,6 +15,11 @@
# You should have received a copy of the GNU Affero General Public License
# along with iTop. If not, see <http://www.gnu.org/licenses/>
p_user_profile_brick_edit_person:
path: '/user/edit_person'
defaults:
_controller: 'Combodo\iTop\Portal\Controller\UserProfileBrickController::EditPerson'
p_user_profile_brick:
path: '/user/{sBrickId}'
defaults:

File diff suppressed because one or more lines are too long

View File

@@ -1246,7 +1246,12 @@ class ObjectController extends BrickController
$bIgnoreSilos = $oScopeValidator->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass);
$aParams = array('objects_id' => $aObjectIds);
$oSearch = DBObjectSearch::FromOQL("SELECT $sObjectClass WHERE id IN (:objects_id)");
if ($bIgnoreSilos === true)
if (!$oScopeValidator->AddScopeToQuery($oSearch, $sObjectClass)
) {
IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to read ' . $sObjectClass . ' object.');
throw new HttpException(Response::HTTP_NOT_FOUND, Dict::S('UI:ObjectDoesNotExist'));
}
if ($bIgnoreSilos === true)
{
$oSearch->AllowAllData();
}

View File

@@ -35,7 +35,7 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
use UserRights;
use utils;
use Dict;
/**
* Class UserProfileBrickController
*
@@ -66,34 +66,9 @@ class UserProfileBrickController extends BrickController
$oRequestManipulator = $this->get('request_manipulator');
/** @var \Combodo\iTop\Portal\Helper\ObjectFormHandlerHelper $ObjectFormHandler */
$ObjectFormHandler = $this->get('object_form_handler');
/** @var \Combodo\iTop\Portal\Brick\BrickCollection $oBrickCollection */
$oBrickCollection = $this->get('brick_collection');
$oBrick = $this->GetBrick($sBrickId);
// If the brick id was not specified, we get the first one registered that is an instance of UserProfileBrick as default
if ($sBrickId === null)
{
/** @var \Combodo\iTop\Portal\Brick\PortalBrick $oTmpBrick */
foreach ($oBrickCollection->GetBricks() as $oTmpBrick)
{
if ($oTmpBrick instanceof UserProfileBrick)
{
$oBrick = $oTmpBrick;
}
}
// We make sure a UserProfileBrick was found
if (!isset($oBrick) || $oBrick === null)
{
$oBrick = new UserProfileBrick();
//throw new HttpException(Response::HTTP_INTERNAL_SERVER_ERROR, 'UserProfileBrick : Brick could not be loaded as there was no UserProfileBrick loaded in the application.');
}
}
else
{
$oBrick = $oBrickCollection->GetBrickById($sBrickId);
}
$aData = array();
$aData = array();
// Setting form mode regarding the demo mode parameter
$bDemoMode = MetaModel::GetConfig()->Get('demo_mode');
@@ -130,11 +105,12 @@ class UserProfileBrickController extends BrickController
$oCurContact = UserRights::GetContactObject();
$sCurContactClass = get_class($oCurContact);
$sCurContactId = $oCurContact->GetKey();
$aForm = $oBrick->GetForm();
$aForm['submit_endpoint'] = $this->generateUrl('p_user_profile_brick_edit_person', ['sBrickId' => $sBrickId]);
// Preparing forms
$aData['forms']['contact'] = $ObjectFormHandler->HandleForm($oRequest, $sFormMode, $sCurContactClass, $sCurContactId,
$oBrick->GetForm());
$aData['forms']['preferences'] = $this->HandlePreferencesForm($oRequest, $sFormMode);
$aData['forms']['contact'] = $ObjectFormHandler->HandleForm($oRequest, $sFormMode, $sCurContactClass, $sCurContactId,
$aForm);
$aData['forms']['preferences'] = $this->HandlePreferencesForm($oRequest, $sFormMode);
// - If user can change password, we display the form
$aData['forms']['password'] = (UserRights::CanChangePassword()) ? $this->HandlePasswordForm($oRequest, $sFormMode) : null;
@@ -150,6 +126,35 @@ class UserProfileBrickController extends BrickController
return $oResponse;
}
public function EditPerson(Request $oRequest)
{
/** @var \Combodo\iTop\Portal\Helper\ObjectFormHandlerHelper $oObjectFormHandler */
$oObjectFormHandler = $this->get('object_form_handler');
/** @var \Combodo\iTop\Portal\Helper\SecurityHelper $oSecurityHelper */
$oSecurityHelper = $this->get('security_helper');
$oCurContact = UserRights::GetContactObject();
$sObjectClass = get_class($oCurContact);
$sObjectId = $oCurContact->GetKey();
// Checking security layers
// Warning : This is a dirty quick fix to allow editing its own contact information
$bAllowWrite = ($sObjectClass === 'Person' && $sObjectId == UserRights::GetContactId());
if (!$oSecurityHelper->IsActionAllowed(UR_ACTION_MODIFY, $sObjectClass, $sObjectId) && !$bAllowWrite) {
IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to modify ' . $sObjectClass . '::' . $sObjectId . ' object.');
throw new HttpException(Response::HTTP_NOT_FOUND, Dict::S('UI:ObjectDoesNotExist'));
}
$aForm = $this->GetBrick()->GetForm();
$aForm['submit_endpoint'] = $this->generateUrl('p_user_profile_brick_edit_person');
$aData = ['sMode' => 'edit'];
$aData['form'] = $oObjectFormHandler->HandleForm($oRequest, $aData['sMode'], $sObjectClass, $sObjectId, $aForm);
return new JsonResponse($aData);
}
/**
* @param \Symfony\Component\HttpFoundation\Request $oRequest
* @param string $sFormMode
@@ -388,4 +393,34 @@ class UserProfileBrickController extends BrickController
return $aFormData;
}
/**
* @param $sBrickId
* @return \Combodo\iTop\Portal\Brick\PortalBrick|UserProfileBrick
* @throws \Combodo\iTop\Portal\Brick\BrickNotFoundException
*/
public function GetBrick($sBrickId = null)
{
/** @var \Combodo\iTop\Portal\Brick\BrickCollection $oBrickCollection */
$oBrickCollection = $this->get('brick_collection');
// If the brick id was not specified, we get the first one registered that is an instance of UserProfileBrick as default
if ($sBrickId === null) {
/** @var \Combodo\iTop\Portal\Brick\PortalBrick $oTmpBrick */
foreach ($oBrickCollection->GetBricks() as $oTmpBrick) {
if ($oTmpBrick instanceof UserProfileBrick) {
$oBrick = $oTmpBrick;
}
}
// We make sure a UserProfileBrick was found
if (!isset($oBrick) || $oBrick === null) {
$oBrick = new UserProfileBrick();
//throw new HttpException(Response::HTTP_INTERNAL_SERVER_ERROR, 'UserProfileBrick : Brick could not be loaded as there was no UserProfileBrick loaded in the application.');
}
} else {
$oBrick = $oBrickCollection->GetBrickById($sBrickId);
}
return $oBrick;
}
}

View File

@@ -132,10 +132,8 @@ class ObjectFormHandlerHelper
$bModal = ($oRequest->isXmlHttpRequest() && empty($sOperation));
// - Retrieve form properties
if ($aFormProperties === null)
{
$aFormProperties = ApplicationHelper::GetLoadedFormFromClass($this->aCombodoPortalInstanceConf['forms'], $sObjectClass, $sMode);
}
$aFormProperties = $aFormProperties ?? ApplicationHelper::GetLoadedFormFromClass($this->aCombodoPortalInstanceConf['forms'], $sObjectClass, $sMode);
// - Create and
if (empty($sOperation))
{
@@ -243,13 +241,17 @@ class ObjectFormHandlerHelper
case static::ENUM_MODE_CREATE:
case static::ENUM_MODE_EDIT:
case static::ENUM_MODE_VIEW:
$sFormEndpoint = $this->oUrlGenerator->generate(
'p_object_'.$sMode,
array(
'sObjectClass' => $sObjectClass,
'sObjectId' => $sObjectId,
)
);
if(array_key_exists('submit_endpoint', $aFormProperties)) {
$sFormEndpoint = $aFormProperties['submit_endpoint'];
} else {
$sFormEndpoint = $this->oUrlGenerator->generate(
'p_object_' . $sMode,
array(
'sObjectClass' => $sObjectClass,
'sObjectId' => $sObjectId,
)
);
}
break;
case static::ENUM_MODE_APPLY_STIMULUS:
@@ -282,7 +284,8 @@ class ObjectFormHandlerHelper
->SetActionRulesToken($sActionRulesToken)
->SetRenderer($oFormRenderer)
->SetFormProperties($aFormProperties);
$oFormManager->PrepareFormAndHTMLDocument();
$oFormManager->PrepareFields();
$oFormManager->Build();
$aFormData['hidden_fields'] = $oFormManager->GetHiddenFieldsId();
// Check the number of editable fields

View File

@@ -64,12 +64,12 @@
{% endif %}
{# Custom CSS that is supposed to do adjustments to the portal #}
{% if app['combodo.portal.instance.conf'].properties.themes.custom is defined %}
<link href="{{ app['combodo.portal.instance.conf'].properties.themes.custom|add_itop_version }}" rel="stylesheet">
<link href="{{ app['combodo.absolute_url'] ~ app['combodo.portal.instance.conf'].properties.themes.custom|add_itop_version }}" rel="stylesheet">
{% endif %}
{# Others CSS that will come after the theme/portal/custom, in an undefined order #}
{% if app['combodo.portal.instance.conf'].properties.themes.others is defined %}
{% for theme in app['combodo.portal.instance.conf'].properties.themes.others %}
<link href="{{ theme|add_itop_version }}" rel="stylesheet">
<link href="{{ app['combodo.absolute_url'] ~ theme|add_itop_version }}" rel="stylesheet">
{% endfor %}
{% endif %}
{% endblock %}
@@ -464,8 +464,8 @@
sBody = '{{ 'Error:XHR:Fail'|dict_format(constant('ITOP_APPLICATION_SHORT'))|escape('js') }}';
}
var oModalElem = $('#modal-for-alert');
oModalElem.find('.modal-content .modal-header .modal-title').html(sTitle);
oModalElem.find('.modal-content .modal-body .alert').addClass('alert-danger').html(sBody);
oModalElem.find('.modal-content .modal-header .modal-title').text(sTitle);
oModalElem.find('.modal-content .modal-body .alert').addClass('alert-danger').text(sBody);
oModalElem.modal('show');
};
{% endblock %}

View File

@@ -20,7 +20,7 @@
/** @noinspection PhpUnhandledExceptionInspection */
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-portal/2.7.10', array(
'itop-portal/2.7.12', array(
// Identification
'label' => 'Enhanced Customer Portal',
'category' => 'Portal',

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-problem-mgmt/2.7.10',
'itop-problem-mgmt/2.7.12',
array(
// Identification
//

View File

@@ -19,7 +19,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-profiles-itil/2.7.10',
'itop-profiles-itil/2.7.12',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-request-mgmt-itil/2.7.10',
'itop-request-mgmt-itil/2.7.12',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-request-mgmt/2.7.10',
'itop-request-mgmt/2.7.12',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-service-mgmt-provider/2.7.10',
'itop-service-mgmt-provider/2.7.12',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-service-mgmt/2.7.10',
'itop-service-mgmt/2.7.12',
array(
// Identification
//

View File

@@ -18,7 +18,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-sla-computation/2.7.10',
'itop-sla-computation/2.7.12',
array(
// Identification
//

View File

@@ -25,7 +25,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-storage-mgmt/2.7.10',
'itop-storage-mgmt/2.7.12',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__,
'itop-tickets/2.7.10',
'itop-tickets/2.7.12',
array(
// Identification
//

View File

@@ -16,7 +16,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-virtualization-mgmt/2.7.10',
'itop-virtualization-mgmt/2.7.12',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-welcome-itil/2.7.10',
'itop-welcome-itil/2.7.12',
array(
// Identification
//

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<information>
<version>2.7.10</version>
<version>2.7.12</version>
</information>

View File

@@ -81,7 +81,7 @@ function DisplayPreferences($oP)
$oP->add('<fieldset><legend>'.Dict::S('UI:FavoriteOtherSettings').'</legend>');
$oP->add('<form method="post" onsubmit="return ValidateOtherSettings()">');
$iDefaultPageSize = appUserPreferences::GetPref('default_page_size', MetaModel::GetConfig()->GetMinDisplayLimit());
$iDefaultPageSize = (int)appUserPreferences::GetPref('default_page_size', MetaModel::GetConfig()->GetMinDisplayLimit());
$oP->add('<p>'.Dict::Format('UI:Favorites:Default_X_ItemsPerPage', '<input id="default_page_size" name="default_page_size" type="text" size="3" value="'.$iDefaultPageSize.'"/><span id="v_default_page_size"></span>').'</p>');
$bShow = utils::IsArchiveMode() || appUserPreferences::GetPref('show_obsolete_data', MetaModel::GetConfig()->Get('obsolescence.show_obsolete_data'));

View File

@@ -3,7 +3,7 @@
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
@@ -25,7 +25,7 @@ require_once(APPROOT.'setup/backup.class.inc.php');
* The installation process is split into a sequence of unitary steps
* for performance reasons (i.e; timeout, memory usage) and also in order
* to provide some feedback about the progress of the installation.
*
*
* This class can be used for a step by step interactive installation
* while displaying a progress bar, or in an unattended manner
* (for example from the command line), to run all the steps
@@ -157,7 +157,7 @@ class ApplicationInstaller
}
}
while(($aRes['status'] != self::ERROR) && ($aRes['next-step'] != ''));
return ($iOverallStatus == self::OK);
}
@@ -225,7 +225,7 @@ class ApplicationInstaller
case 'copy':
$aPreinstall = $this->oParams->Get('preinstall');
$aCopies = $aPreinstall['copies'];
$aCopies = $aPreinstall['copies'] ?? [];
self::DoCopy($aCopies);
$sReport = "Copying...";
@@ -472,7 +472,7 @@ class ApplicationInstaller
{
$sSource = $aCopy['source'];
$sDestination = APPROOT.$aCopy['destination'];
SetupUtils::builddir($sDestination);
SetupUtils::tidydir($sDestination);
SetupUtils::copydir($sSource, $sDestination);
@@ -513,7 +513,7 @@ class ApplicationInstaller
$oBackup->CreateCompressedBackup($sTargetFile, $sSourceConfigFile);
}
protected static function DoCompile($aSelectedModules, $sSourceDir, $sExtensionDir, $sTargetDir, $sEnvironment, $bUseSymbolicLinks = false)
{
SetupPage::log_info("Compiling data model.");
@@ -525,7 +525,7 @@ class ApplicationInstaller
if (empty($sSourceDir) || empty($sTargetDir))
{
throw new Exception("missing parameter source_dir and/or target_dir");
}
}
$sSourcePath = APPROOT.$sSourceDir;
$aDirsToScan = array($sSourcePath);
@@ -582,10 +582,10 @@ class ApplicationInstaller
}
$oFactory = new ModelFactory($aDirsToScan);
$oDictModule = new MFDictModule('dictionaries', 'iTop Dictionaries', APPROOT.'dictionaries');
$oFactory->LoadModule($oDictModule);
$sDeltaFile = APPROOT.'core/datamodel.core.xml';
if (file_exists($sDeltaFile))
{
@@ -598,7 +598,7 @@ class ApplicationInstaller
$oApplicationModule = new MFCoreModule('application', 'Application Module', $sDeltaFile);
$oFactory->LoadModule($oApplicationModule);
}
$aModules = $oFactory->FindModules();
foreach($aModules as $oModule)
@@ -611,7 +611,7 @@ class ApplicationInstaller
}
// Dump the "reference" model, just before loading any actual delta
$oFactory->SaveToFile(APPROOT.'data/datamodel-'.$sEnvironment.'.xml');
$sDeltaFile = APPROOT.'data/'.$sEnvironment.'.delta.xml';
if (file_exists($sDeltaFile))
{
@@ -635,12 +635,12 @@ class ApplicationInstaller
if (file_exists($sFileToPatch))
{
$sContent = file_get_contents($sFileToPatch);
$sContent = str_replace("require_once(APPROOT.'modules/itop-welcome-itil/model.itop-welcome-itil.php');", "//\n// The line below is no longer needed in iTop 2.0 -- patched by the setup program\n// require_once(APPROOT.'modules/itop-welcome-itil/model.itop-welcome-itil.php');", $sContent);
file_put_contents($sFileToPatch, $sContent);
}
// Set an "Instance UUID" identifying this machine based on a file located in the data directory
$sInstanceUUIDFile = APPROOT.'data/instance.txt';
SetupUtils::builddir(APPROOT.'data');
@@ -699,7 +699,7 @@ class ApplicationInstaller
// Starting 2.0, all table names must be lowercase
if ($sMode != 'install')
{
SetupPage::log_info("Renaming '{$sDBPrefix}priv_internalUser' into '{$sDBPrefix}priv_internaluser' (lowercase)");
SetupPage::log_info("Renaming '{$sDBPrefix}priv_internalUser' into '{$sDBPrefix}priv_internaluser' (lowercase)");
// This command will have no effect under Windows...
// and it has been written in two steps so as to make it work under windows!
CMDBSource::SelectDB($sDBName);
@@ -710,18 +710,18 @@ class ApplicationInstaller
}
catch (Exception $e)
{
SetupPage::log_info("Renaming '{$sDBPrefix}priv_internalUser' failed (already done in a previous upgrade?)");
SetupPage::log_info("Renaming '{$sDBPrefix}priv_internalUser' failed (already done in a previous upgrade?)");
}
// let's remove the records in priv_change which have no counterpart in priv_changeop
SetupPage::log_info("Cleanup of '{$sDBPrefix}priv_change' to remove orphan records");
SetupPage::log_info("Cleanup of '{$sDBPrefix}priv_change' to remove orphan records");
CMDBSource::SelectDB($sDBName);
try
{
$sTotalCount = "SELECT COUNT(*) FROM `{$sDBPrefix}priv_change`";
$iTotalCount = (int)CMDBSource::QueryToScalar($sTotalCount);
SetupPage::log_info("There is a total of $iTotalCount records in {$sDBPrefix}priv_change.");
$sOrphanCount = "SELECT COUNT(c.id) FROM `{$sDBPrefix}priv_change` AS c left join `{$sDBPrefix}priv_changeop` AS o ON c.id = o.changeid WHERE o.id IS NULL";
$iOrphanCount = (int)CMDBSource::QueryToScalar($sOrphanCount);
SetupPage::log_info("There are $iOrphanCount useless records in {$sDBPrefix}priv_change (".sprintf('%.2f', ((100.0*$iOrphanCount)/$iTotalCount))."%)");
@@ -745,11 +745,11 @@ class ApplicationInstaller
}
catch (Exception $e)
{
SetupPage::log_info("Cleanup of orphan records in `{$sDBPrefix}priv_change` failed: ".$e->getMessage());
SetupPage::log_info("Cleanup of orphan records in `{$sDBPrefix}priv_change` failed: ".$e->getMessage());
}
}
// Module specific actions (migrate the data)
//
$aAvailableModules = $oProductionEnv->AnalyzeInstallation(MetaModel::GetConfig(), APPROOT.$sModulesDir);
@@ -757,9 +757,9 @@ class ApplicationInstaller
if(!$oProductionEnv->CreateDatabaseStructure(MetaModel::GetConfig(), $sMode))
{
throw new Exception("Failed to create/upgrade the database structure for environment '$sTargetEnvironment'");
throw new Exception("Failed to create/upgrade the database structure for environment '$sTargetEnvironment'");
}
// Set a DBProperty with a unique ID to identify this instance of iTop
$sUUID = DBProperty::GetProperty('database_uuid', '');
if ($sUUID === '')
@@ -767,10 +767,10 @@ class ApplicationInstaller
$sUUID = utils::CreateUUID('database');
DBProperty::SetProperty('database_uuid', $sUUID, 'Installation/upgrade of '.ITOP_APPLICATION, 'Unique ID of this '.ITOP_APPLICATION.' Database');
}
// priv_change now has an 'origin' field to distinguish between the various input sources
// Let's initialize the field with 'interactive' for all records were it's null
// Then check if some records should hold a different value, based on a pattern matching in the userinfo field
// Then check if some records should hold a different value, based on a pattern matching in the userinfo field
CMDBSource::SelectDB($sDBName);
try
{
@@ -778,21 +778,21 @@ class ApplicationInstaller
$iCount = (int)CMDBSource::QueryToScalar($sCount);
if ($iCount > 0)
{
SetupPage::log_info("Initializing '{$sDBPrefix}priv_change.origin' ($iCount records to update)");
SetupPage::log_info("Initializing '{$sDBPrefix}priv_change.origin' ($iCount records to update)");
// By default all uninitialized values are considered as 'interactive'
$sInit = "UPDATE `{$sDBPrefix}priv_change` SET `origin` = 'interactive' WHERE `origin` IS NULL";
CMDBSource::Query($sInit);
// CSV Import was identified by the comment at the end
$sInit = "UPDATE `{$sDBPrefix}priv_change` SET `origin` = 'csv-import.php' WHERE `userinfo` LIKE '%Web Service (CSV)'";
CMDBSource::Query($sInit);
// CSV Import was identified by the comment at the end
$sInit = "UPDATE `{$sDBPrefix}priv_change` SET `origin` = 'csv-interactive' WHERE `userinfo` LIKE '%(CSV)' AND origin = 'interactive'";
CMDBSource::Query($sInit);
// Syncho data sources were identified by the comment at the end
// Unfortunately the comment is localized, so we have to search for all possible patterns
$sCurrentLanguage = Dict::GetUserLanguage();
@@ -806,19 +806,19 @@ class ApplicationInstaller
Dict::SetUserLanguage($sCurrentLanguage);
$sCondition = "`userinfo` LIKE ".implode(" OR `userinfo` LIKE ", array_keys($aSuffixes));
$sInit = "UPDATE `{$sDBPrefix}priv_change` SET `origin` = 'synchro-data-source' WHERE ($sCondition)";
$sInit = "UPDATE `{$sDBPrefix}priv_change` SET `origin` = 'synchro-data-source' WHERE ($sCondition)";
CMDBSource::Query($sInit);
SetupPage::log_info("Initialization of '{$sDBPrefix}priv_change.origin' completed.");
SetupPage::log_info("Initialization of '{$sDBPrefix}priv_change.origin' completed.");
}
else
{
SetupPage::log_info("'{$sDBPrefix}priv_change.origin' already initialized, nothing to do.");
SetupPage::log_info("'{$sDBPrefix}priv_change.origin' already initialized, nothing to do.");
}
}
catch (Exception $e)
{
SetupPage::log_error("Initializing '{$sDBPrefix}priv_change.origin' failed: ".$e->getMessage());
SetupPage::log_error("Initializing '{$sDBPrefix}priv_change.origin' failed: ".$e->getMessage());
}
// priv_async_task now has a 'status' field to distinguish between the various statuses rather than just relying on the date columns
@@ -830,24 +830,24 @@ class ApplicationInstaller
$iCount = (int)CMDBSource::QueryToScalar($sCount);
if ($iCount > 0)
{
SetupPage::log_info("Initializing '{$sDBPrefix}priv_async_task.status' ($iCount records to update)");
SetupPage::log_info("Initializing '{$sDBPrefix}priv_async_task.status' ($iCount records to update)");
$sInit = "UPDATE `{$sDBPrefix}priv_async_task` SET `status` = 'planned' WHERE (`status` IS NULL) AND (`started` IS NULL)";
CMDBSource::Query($sInit);
$sInit = "UPDATE `{$sDBPrefix}priv_async_task` SET `status` = 'error' WHERE (`status` IS NULL) AND (`started` IS NOT NULL)";
CMDBSource::Query($sInit);
SetupPage::log_info("Initialization of '{$sDBPrefix}priv_async_task.status' completed.");
SetupPage::log_info("Initialization of '{$sDBPrefix}priv_async_task.status' completed.");
}
else
{
SetupPage::log_info("'{$sDBPrefix}priv_async_task.status' already initialized, nothing to do.");
SetupPage::log_info("'{$sDBPrefix}priv_async_task.status' already initialized, nothing to do.");
}
}
catch (Exception $e)
{
SetupPage::log_error("Initializing '{$sDBPrefix}priv_async_task.status' failed: ".$e->getMessage());
SetupPage::log_error("Initializing '{$sDBPrefix}priv_async_task.status' failed: ".$e->getMessage());
}
SetupPage::log_info("Database Schema Successfully Updated for environment '$sTargetEnvironment'.");
@@ -887,15 +887,15 @@ class ApplicationInstaller
$oProductionEnv = new RunTimeEnvironment($sTargetEnvironment);
$oProductionEnv->InitDataModel($oConfig, true); // load data model and connect to the database
$oContextTag = new ContextTag(ContextTag::TAG_SETUP);
self::$bMetaModelStarted = true; // No need to reload the final MetaModel in case the installer runs synchronously
self::$bMetaModelStarted = true; // No need to reload the final MetaModel in case the installer runs synchronously
// Perform here additional DB setup... profiles, etc...
//
$aAvailableModules = $oProductionEnv->AnalyzeInstallation(MetaModel::GetConfig(), APPROOT.$sModulesDir);
$oProductionEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDatabaseCreation');
$oProductionEnv->UpdatePredefinedObjects();
if($sMode == 'install')
{
if (!self::CreateAdminAccount(MetaModel::GetConfig(), $sAdminUser, $sAdminPwd, $sAdminLanguage))
@@ -907,20 +907,20 @@ class ApplicationInstaller
SetupPage::log_info("Administrator account '$sAdminUser' created.");
}
}
// Perform final setup tasks here
//
$oProductionEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDatabaseSetup');
}
/**
* Helper function to create and administrator account for iTop
* @return boolean true on success, false otherwise
* @return boolean true on success, false otherwise
*/
protected static function CreateAdminAccount(Config $oConfig, $sAdminUser, $sAdminPwd, $sLanguage)
{
SetupPage::log_info('CreateAdminAccount');
if (UserRights::CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage))
{
return true;
@@ -946,9 +946,9 @@ class ApplicationInstaller
'user rights' => 'addons/userrights/userrightsprofile.db.class.inc.php',
));
}
$oProductionEnv = new RunTimeEnvironment($sTargetEnvironment);
//Load the MetaModel if needed (asynchronous mode)
if (!self::$bMetaModelStarted)
{
@@ -956,8 +956,8 @@ class ApplicationInstaller
$oContextTag = new ContextTag(ContextTag::TAG_SETUP);
self::$bMetaModelStarted = true; // No need to reload the final MetaModel in case the installer runs synchronously
}
}
$aAvailableModules = $oProductionEnv->AnalyzeInstallation($oConfig, APPROOT.$sModulesDir);
$oProductionEnv->LoadData($aAvailableModules, $aSelectedModules, $bSampleData);
@@ -992,7 +992,7 @@ class ApplicationInstaller
$bPreserveModuleSettings = false;
if ($sMode == 'upgrade')
{
try
try
{
$oOldConfig = new Config($sPreviousConfigFile);
$oConfig = clone($oOldConfig);
@@ -1038,7 +1038,7 @@ class ApplicationInstaller
{
mkdir(APPCONF);
chmod(APPCONF, 0770); // RWX for owner and group, nothing for others
SetupPage::log_info("Created configuration directory: ".APPCONF);
SetupPage::log_info("Created configuration directory: ".APPCONF);
}
// Write the final configuration file
@@ -1048,7 +1048,7 @@ class ApplicationInstaller
@chmod($sConfigDir, 0770); // RWX for owner and group, nothing for others
$oConfig->WriteToFile($sConfigFile);
// try to make the final config file read-only
@chmod($sConfigFile, 0440); // Read-only for owner and group, nothing for others

View File

@@ -76,7 +76,7 @@ class ModuleDiscovery
'doc.manual_setup' => 'url',
'doc.more_information' => 'url',
);
// Cache the results and the source directories
protected static $m_aSearchDirs = null;
@@ -148,7 +148,7 @@ class ModuleDiscovery
self::$m_aModuleVersionByName[$sModuleName]['version'] = $sModuleVersion;
self::$m_aModuleVersionByName[$sModuleName]['id'] = $sId;
}
self::$m_aModules[$sId] = $aArgs;
// Now keep the relative paths, as provided
@@ -250,7 +250,7 @@ class ModuleDiscovery
if ($bAbortOnMissingDependency && count($aDependencies) > 0)
{
$aModulesInfo = array();
$aModuleDeps = array();
$aModuleDeps = array();
foreach($aDependencies as $sId => $aDeps)
{
$aModule = $aModules[$sId];
@@ -282,7 +282,7 @@ class ModuleDiscovery
// The de-duplication is now done directly by the AddModule method
return $aModules;
}
protected static function DependencyIsResolved($sDepString, $aOrderedModules, $aSelectedModules)
{
$bResult = false;
@@ -329,12 +329,12 @@ class ModuleDiscovery
if (version_compare($sCurrentVersion, $sExpectedVersion, $sOperator))
{
$aReplacements[$sModuleId] = '(true)'; // Add parentheses to protect against invalid condition causing
// a function call that results in a runtime fatal error
// a function call that results in a runtime fatal error
}
else
{
$aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing
// a function call that results in a runtime fatal error
// a function call that results in a runtime fatal error
}
}
else
@@ -393,20 +393,20 @@ class ModuleDiscovery
{
self::ResetCache();
}
if (is_null(self::$m_aSearchDirs))
{
self::$m_aSearchDirs = $aSearchDirs;
// Not in cache, let's scan the disk
foreach($aSearchDirs as $sSearchDir)
{
$sLookupDir = realpath($sSearchDir);
$sLookupDir = realpath($sSearchDir);
if ($sLookupDir == '')
{
throw new Exception("Invalid directory '$sSearchDir'");
}
clearstatcache();
self::ListModuleFiles(basename($sSearchDir), dirname($sSearchDir));
}
@@ -418,7 +418,7 @@ class ModuleDiscovery
return self::GetModules($bAbortOnMissingDependency, $aModulesToLoad);
}
}
public static function ResetCache()
{
self::$m_aSearchDirs = null;
@@ -430,7 +430,7 @@ class ModuleDiscovery
* Helper function to interpret the name of a module
* @param $sModuleId string Identifier of the module, in the form 'name/version'
* @return array(name, version)
*/
*/
public static function GetModuleName($sModuleId)
{
$aMatches = array();
@@ -459,7 +459,7 @@ class ModuleDiscovery
{
static $iDummyClassIndex = 0;
$sDirectory = $sRootDir.'/'.$sRelDir;
if ($hDir = opendir($sDirectory))
{
// This is the correct way to loop over the directory. (according to the documentation)
@@ -495,12 +495,12 @@ class ModuleDiscovery
$idx++;
}
$bRet = eval($sModuleFileContents);
if ($bRet === false)
{
SetupPage::log_warning("Eval of $sRelDir/$sFile returned false");
}
//echo "<p>Done.</p>\n";
}
catch(ParseError $e)
@@ -528,7 +528,7 @@ class ModuleDiscovery
/** Alias for backward compatibility with old module files in which
* the declaration of a module invokes SetupWebPage::AddModule()
* whereas the new form is ModuleDiscovery::AddModule()
*/
*/
class SetupWebPage extends ModuleDiscovery
{
// For backward compatibility with old modules...
@@ -555,9 +555,9 @@ class SetupWebPage extends ModuleDiscovery
public static function log($sText)
{
SetupPage::log($sText);
}
}
}
/** Ugly patch !!!
* In order to be able to analyse / load several times
* the same module file, we rename the class (to avoid duplicate class definitions)

View File

@@ -3,7 +3,7 @@
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
@@ -100,14 +100,14 @@ class RunTimeEnvironment
$this->log_info(sprintf('%.3fs - query: %s ', $fDuration, $sQuery));
$this->log_db_query($sQuery);
}
/**
* Helper function to initialize the ORM and load the data model
* from the given file
* @param $oConfig object The configuration (volatile, not necessarily already on disk)
* @param $bModelOnly boolean Whether or not to allow loading a data model with no corresponding DB
* @param $bModelOnly boolean Whether or not to allow loading a data model with no corresponding DB
* @return none
*/
*/
public function InitDataModel($oConfig, $bModelOnly = true, $bUseCache = false)
{
require_once APPROOT.'/setup/moduleinstallation.class.inc.php';
@@ -121,15 +121,15 @@ class RunTimeEnvironment
{
$this->log_info("MetaModel::Startup (ModelOnly = $bModelOnly)");
}
if (!$bUseCache)
{
// Reset the cache for the first use !
MetaModel::ResetCache(md5(APPROOT).'-'.$this->sTargetEnv);
}
MetaModel::Startup($oConfig, $bModelOnly, $bUseCache, false /* $bTraceSourceFiles */, $this->sTargetEnv);
if ($this->oExtensionsMap === null)
{
$this->oExtensionsMap = new iTopExtensionsMap($this->sTargetEnv);
@@ -139,7 +139,7 @@ class RunTimeEnvironment
/**
* Analyzes the current installation and the possibilities
*
* @param Config $oConfig Defines the target environment (DB)
* @param null|Config $oConfig Defines the target environment (DB)
* @param mixed $modulesPath Either a single string or an array of absolute paths
* @param bool $bAbortOnMissingDependency ...
* @param array $aModulesToLoad List of modules to search for, defaults to all if omitted
@@ -178,7 +178,7 @@ class RunTimeEnvironment
'name_code' => ITOP_APPLICATION,
)
);
$aDirs = is_array($modulesPath) ? $modulesPath : array($modulesPath);
$aModules = ModuleDiscovery::GetAvailableModules($aDirs, $bAbortOnMissingDependency, $aModulesToLoad);
foreach($aModules as $sModuleId => $aModuleInfo)
@@ -194,11 +194,11 @@ class RunTimeEnvironment
//throw new Exception("Missing version for the module: '$sModuleId'");
$sModuleVersion = '1.0.0';
}
$sModuleAppVersion = $aModuleInfo['itop_version'];
$aModuleInfo['version_db'] = '';
$aModuleInfo['version_code'] = $sModuleVersion;
if (!in_array($sModuleAppVersion, array('1.0.0', '1.0.1', '1.0.2')))
{
// This module is NOT compatible with the current version
@@ -223,18 +223,20 @@ class RunTimeEnvironment
}
$aRes[$sModuleName] = $aModuleInfo;
}
try
{
CMDBSource::InitFromConfig($oConfig);
$aSelectInstall = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->Get('db_subname')."priv_module_install");
$aSelectInstall = array();
if (! is_null($oConfig)) {
CMDBSource::InitFromConfig($oConfig);
$aSelectInstall = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->Get('db_subname')."priv_module_install");
}
}
catch (MySQLException $e)
{
// No database or erroneous information
$aSelectInstall = array();
}
// Build the list of installed module (get the latest installation)
//
$aInstallByModule = array(); // array of <module> => array ('installed' => timestamp, 'version' => <version>)
@@ -251,7 +253,7 @@ class RunTimeEnvironment
}
}
}
foreach ($aSelectInstall as $aInstall)
{
//$aInstall['comment']; // unsused
@@ -265,7 +267,7 @@ class RunTimeEnvironment
// as being installed
$sModuleVersion = '0.0.0';
}
if ($aInstall['parent_id'] == 0)
{
$sModuleName = ROOT_MODULE;
@@ -275,7 +277,7 @@ class RunTimeEnvironment
// Skip all modules belonging to previous installations
continue;
}
if (array_key_exists($sModuleName, $aInstallByModule))
{
if ($iInstalled < $aInstallByModule[$sModuleName]['installed'])
@@ -283,30 +285,30 @@ class RunTimeEnvironment
continue;
}
}
if ($aInstall['parent_id'] == 0)
{
$aRes[$sModuleName]['version_db'] = $sModuleVersion;
$aRes[$sModuleName]['name_db'] = $aInstall['name'];
}
$aInstallByModule[$sModuleName]['installed'] = $iInstalled;
$aInstallByModule[$sModuleName]['version'] = $sModuleVersion;
}
// Adjust the list of proposed modules
//
foreach ($aInstallByModule as $sModuleName => $aModuleDB)
{
if ($sModuleName == ROOT_MODULE) continue; // Skip the main module
if (!array_key_exists($sModuleName, $aRes))
{
// A module was installed, it is not proposed in the new build... skip
// A module was installed, it is not proposed in the new build... skip
continue;
}
$aRes[$sModuleName]['version_db'] = $aModuleDB['version'];
if ($aRes[$sModuleName]['install']['flag'] == MODULE_ACTION_MANDATORY)
{
$aRes[$sModuleName]['uninstall'] = array(
@@ -322,7 +324,7 @@ class RunTimeEnvironment
);
}
}
return $aRes;
}
@@ -336,9 +338,9 @@ class RunTimeEnvironment
{
self::MakeDirSafe(APPCONF);
self::MakeDirSafe(APPCONF.$this->sTargetEnv);
$sTargetConfigFile = APPCONF.$this->sTargetEnv.'/'.ITOP_CONFIG_FILE;
// Write the config file
@chmod($sTargetConfigFile, 0770); // In case it exists: RWX for owner and group, nothing for others
$oConfig->WriteToFile($sTargetConfigFile);
@@ -354,7 +356,7 @@ class RunTimeEnvironment
// Do nothing, overload this method if needed
return array();
}
/**
* Decide whether or not the given extension is selected for installation
* @param iTopExtension $oExtension
@@ -364,10 +366,10 @@ class RunTimeEnvironment
{
return ($oExtension->sSource == iTopExtension::SOURCE_REMOTE);
}
/**
* Get the installed modules (only the installed ones)
*/
* Get the installed modules (only the installed ones)
*/
protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir)
{
$sSourceDirFull = APPROOT.$sSourceDir;
@@ -388,7 +390,7 @@ class RunTimeEnvironment
$aExtraDirs = $this->GetExtraDirsToScan($aDirsToCompile);
$aDirsToCompile = array_merge($aDirsToCompile, $aExtraDirs);
$aRet = array();
// Determine the installed modules and extensions
@@ -396,7 +398,7 @@ class RunTimeEnvironment
$oSourceConfig = new Config(APPCONF.$sSourceEnv.'/'.ITOP_CONFIG_FILE);
$oSourceEnv = new RunTimeEnvironment($sSourceEnv);
$aAvailableModules = $oSourceEnv->AnalyzeInstallation($oSourceConfig, $aDirsToCompile);
// Actually read the modules available for the target environment,
// but get the selection from the source environment and finally
// mark as (automatically) chosen alll the "remote" modules present in the
@@ -416,7 +418,7 @@ class RunTimeEnvironment
//
$oDictModule = new MFDictModule('dictionaries', 'iTop Dictionaries', APPROOT.'dictionaries');
$aRet[$oDictModule->GetName()] = $oDictModule;
$oFactory = new ModelFactory($aDirsToCompile);
$sDeltaFile = APPROOT.'core/datamodel.core.xml';
if (file_exists($sDeltaFile))
@@ -430,14 +432,14 @@ class RunTimeEnvironment
$oApplicationModule = new MFCoreModule('application', 'Application Module', $sDeltaFile);
$aRet[$oApplicationModule->GetName()] = $oApplicationModule;
}
$aModules = $oFactory->FindModules();
foreach($aModules as $oModule)
{
$sModule = $oModule->GetName();
$sModuleRootDir = $oModule->GetRootDir();
$bIsExtra = $this->oExtensionsMap->ModuleIsChosenAsPartOfAnExtension($sModule, iTopExtension::SOURCE_REMOTE);
if (array_key_exists($sModule, $aAvailableModules))
if (array_key_exists($sModule, $aAvailableModules))
{
if (($aAvailableModules[$sModule]['version_db'] != '') || $bIsExtra && !$oModule->IsAutoSelect()) //Extra modules are always unless they are 'AutoSelect'
{
@@ -445,7 +447,7 @@ class RunTimeEnvironment
}
}
}
// Now process the 'AutoSelect' modules
do
{
@@ -514,7 +516,7 @@ class RunTimeEnvironment
}
$oFactory->LoadModule($oModule);
}
if ($oModule instanceof MFDeltaModule)
{
@@ -565,7 +567,7 @@ class RunTimeEnvironment
{
$this->log_info("Creating the structure in '".$oConfig->Get('db_name')."'.");
}
//MetaModel::CheckDefinitions();
if ($sMode == 'install')
{
@@ -597,7 +599,7 @@ class RunTimeEnvironment
MetaModel::DBCreate(array($this, 'LogQueryCallback'));
$this->log_ok("Database structure successfully updated.");
// Check (and update only if it seems needed) the hierarchical keys
if (MFCompiler::SkipRebuildHKeys()) {
$this->log_ok("Hierchical keys are NOT rebuilt due to the presence of the \"data/.setup-rebuild-hkeys-never\" file");
@@ -657,7 +659,7 @@ class RunTimeEnvironment
if ($aPredefinedObjects != null)
{
$this->log_info("$sClass::GetPredefinedObjects() returned " . count($aPredefinedObjects) . " elements.");
// Create/Delete/Update objects of this class,
// according to the given constant values
//
@@ -699,7 +701,7 @@ class RunTimeEnvironment
// Restore the previous access mode
$oConfig->Set('access_mode', $iPrevAccessMode);
}
public function RecordInstallation(Config $oConfig, $sDataModelVersion, $aSelectedModuleCodes, $aSelectedExtensionCodes, $sShortComment = null)
{
// Have it work fine even if the DB has been set in read-only mode for the users
@@ -708,7 +710,7 @@ class RunTimeEnvironment
//$oConfig->Set('access_mode', ACCESS_FULL);
if (CMDBSource::DBName() == '')
{
{
// In case this has not yet been done
CMDBSource::InitFromConfig($oConfig);
}
@@ -718,7 +720,7 @@ class RunTimeEnvironment
$sShortComment = 'Done by the setup program';
}
$sMainComment = $sShortComment."\nBuilt on ".ITOP_BUILD_DATE;
// Record datamodel version
$aData = array(
'source_dir' => $oConfig->Get('source_dir'),
@@ -731,7 +733,7 @@ class RunTimeEnvironment
$oInstallRec->Set('parent_id', 0); // root module
$oInstallRec->Set('installed', $iInstallationTime);
$iMainItopRecord = $oInstallRec->DBInsertNoReload();
// Record main installation
$oInstallRec = new ModuleInstallation();
$oInstallRec->Set('name', ITOP_APPLICATION);
@@ -740,8 +742,8 @@ class RunTimeEnvironment
$oInstallRec->Set('parent_id', 0); // root module
$oInstallRec->Set('installed', $iInstallationTime);
$iMainItopRecord = $oInstallRec->DBInsertNoReload();
// Record installed modules and extensions
//
$aAvailableExtensions = array();
@@ -775,7 +777,7 @@ class RunTimeEnvironment
$aComments[] = "Depends on module: $sDependOn";
}
$sComment = implode("\n", $aComments);
$oInstallRec = new ModuleInstallation();
$oInstallRec->Set('name', $sName);
$oInstallRec->Set('version', $sVersion);
@@ -784,7 +786,7 @@ class RunTimeEnvironment
$oInstallRec->Set('installed', $iInstallationTime);
$oInstallRec->DBInsertNoReload();
}
if ($this->oExtensionsMap)
{
// Mark as chosen the selected extensions code passed to us
@@ -796,7 +798,7 @@ class RunTimeEnvironment
$this->oExtensionsMap->MarkAsChosen($oExtension->sCode);
}
}
foreach($this->oExtensionsMap->GetChoices() as $oExtension)
{
$oInstallRec = new ExtensionInstallation();
@@ -813,9 +815,9 @@ class RunTimeEnvironment
MetaModel::GetConfig()->Set('access_mode', $iPrevAccessMode);
// Database is created, installation has been tracked into it
return true;
return true;
}
public function GetApplicationVersion(Config $oConfig)
{
$aResult = false;
@@ -832,7 +834,7 @@ class RunTimeEnvironment
$this->log_error('Exception '.$e->getMessage());
return false;
}
// Scan the list of installed modules to get the version of the 'ROOT' module which holds the main application version
foreach ($aSelectInstall as $aInstall)
{
@@ -844,7 +846,7 @@ class RunTimeEnvironment
// as being installed
$sModuleVersion = '0.0.0';
}
if ($aInstall['parent_id'] == 0)
{
if ($aInstall['name'] == DATAMODEL_MODULE)
@@ -870,7 +872,7 @@ class RunTimeEnvironment
$aResult['datamodel_version'] = $aResult['product_version'];
}
$this->log_info("GetApplicationVersion returns: product_name: ".$aResult['product_name'].', product_version: '.$aResult['product_version']);
return $aResult;
return $aResult;
}
public static function MakeDirSafe($sDir)
@@ -886,8 +888,8 @@ class RunTimeEnvironment
}
/**
* Wrappers for logging into the setup log files
*/
* Wrappers for logging into the setup log files
*/
protected function log_error($sText)
{
SetupPage::log_error($sText);
@@ -923,7 +925,7 @@ class RunTimeEnvironment
fclose($hSetupQueriesFile);
}
}
public function GetCurrentDataModelVersion()
{
$oSearch = DBObjectSearch::FromOQL("SELECT ModuleInstallation WHERE name='".DATAMODEL_MODULE."'");
@@ -1116,7 +1118,7 @@ class RunTimeEnvironment
}
}
}
/**
* Load data from XML files for the selected modules (structural data and/or sample data)
* @param array[] $aAvailableModules All available modules and their definition
@@ -1126,13 +1128,13 @@ class RunTimeEnvironment
public function LoadData($aAvailableModules, $aSelectedModules, $bSampleData)
{
$oDataLoader = new XMLDataLoader();
CMDBObject::SetTrackInfo("Initialization");
$oMyChange = CMDBObject::GetCurrentChange();
SetupPage::log_info("starting data load session");
$oDataLoader->StartSession($oMyChange);
$aFiles = array();
$aPreviouslyLoadedFiles = array();
foreach($aAvailableModules as $sModuleId => $aModule)
@@ -1173,7 +1175,7 @@ class RunTimeEnvironment
}
}
}
// Simulate the load of the previously loaded files, in order to initialize
// the mapping between the identifiers in the XML and the actual identifiers
// in the current database
@@ -1185,12 +1187,12 @@ class RunTimeEnvironment
{
throw(new Exception("File $sFileName does not exist"));
}
$oDataLoader->LoadFile($sFileName, true);
$sResult = sprintf("loading of %s done.", basename($sFileName));
SetupPage::log_info($sResult);
}
foreach($aFiles as $sFileRelativePath)
{
$sFileName = APPROOT.$sFileRelativePath;
@@ -1199,16 +1201,16 @@ class RunTimeEnvironment
{
throw(new Exception("File $sFileName does not exist"));
}
$oDataLoader->LoadFile($sFileName);
$sResult = sprintf("loading of %s done.", basename($sFileName));
SetupPage::log_info($sResult);
}
$oDataLoader->EndSession();
SetupPage::log_info("ending data load session");
}
/**
* Merge two arrays of file names, adding the relative path to the files provided in the array to merge
* @param string[] $aSourceArray
@@ -1225,7 +1227,7 @@ class RunTimeEnvironment
}
return array_merge($aSourceArray, $aToMerge);
}
/**
* Check the MetaModel for some common pitfall (class name too long, classes requiring too many joins...)
* The check takes about 900 ms for 200 classes
@@ -1265,7 +1267,7 @@ class RunTimeEnvironment
$iCount++;
}
$fDuration = microtime(true) - $fStart;
return sprintf("Checked %d classes in %.1f ms. No error found.\n", $iCount, $fDuration*1000.0);
}
} // End of class

View File

@@ -1230,6 +1230,12 @@ EOF
$aResult['checks'][] = new CheckResult(CheckResult::INFO, "MySQL server's max_connections is set to $iMaxConnections.");
}
$iClusters = $oDBSource->GetClusterNb();
if ($iClusters > 0) {
SetupLog::Warning('Warning - Using Galera will cause malfunctions and data corruptions. Combodo does not support this type of infrastructure.');
$aResult['checks'][] = new CheckResult(CheckResult::WARNING, 'Using Galera will cause malfunctions and data corruptions. Combodo does not support this type of infrastructure.');
}
try {
$aResult['databases'] = $oDBSource->ListDB();
}

View File

@@ -0,0 +1,13 @@
# Apache 2.4
<ifModule mod_authz_core.c>
Require all denied
</ifModule>
# Apache 2.2
<ifModule !mod_authz_core.c>
deny from all
Satisfy All
</ifModule>
# Apache 2.2 and 2.4
IndexIgnore *

View File

@@ -0,0 +1,365 @@
<?php
require_once(APPROOT.'/application/utils.inc.php');
require_once(APPROOT.'/setup/setuppage.class.inc.php');
require_once(APPROOT.'/setup/wizardcontroller.class.inc.php');
require_once(APPROOT.'/setup/wizardsteps.class.inc.php');
if (version_compare(ITOP_DESIGN_LATEST_VERSION, '2.7', '<=')) {
require_once(APPROOT.'/core/config.class.inc.php');
require_once(APPROOT.'/core/log.class.inc.php');
require_once(APPROOT.'/core/kpi.class.inc.php');
require_once(APPROOT.'/core/cmdbsource.class.inc.php');
require_once(APPROOT.'/application/clipage.class.inc.php');
}
class InstallationFileService {
/** @var \RunTimeEnvironment $oProductionEnv */
private $oProductionEnv;
/** @var \ItopExtensionsMap $oProductionEnv */
private $oItopExtensionsMap;
private $sTargetEnvironment;
private $sInstallationPath;
private $aSelectedModules;
private $aSelectedExtensions;
private $aAfterComputationSelectedExtensions;
private $aUnSelectedModules;
private $aAutoSelectModules;
private $bInstallationOptionalChoicesChecked;
/**
* @param string $sInstallationPath
* @param string $sTargetEnvironment
* @param array $aSelectedExtensions
* @param bool $bInstallationOptionalChoicesChecked : this option is used only when no extensions are selected (ie empty
* $aSelectedExtensions)
*/
public function __construct(string $sInstallationPath, string $sTargetEnvironment='production', array $aSelectedExtensions = [], bool $bInstallationOptionalChoicesChecked=true) {
$this->sInstallationPath = $sInstallationPath;
$this->aSelectedModules = [];
$this->aUnSelectedModules = [];
$this->sTargetEnvironment = $sTargetEnvironment;
$this->aSelectedExtensions = $aSelectedExtensions;
$this->aAfterComputationSelectedExtensions = (count($aSelectedExtensions)==0) ? [] : $aSelectedExtensions;
$this->bInstallationOptionalChoicesChecked = $bInstallationOptionalChoicesChecked;
}
public function Init(): void {
clearstatcache();
$this->ProcessDefaultModules();
$this->ProcessInstallationChoices();
$this->ProcessExtensionModulesNotSpecifiedInChoices();
$this->ProcessAutoSelectModules();
}
public function GetProductionEnv(): RunTimeEnvironment {
if (is_null($this->oProductionEnv)){
$this->oProductionEnv = new RunTimeEnvironment();
}
return $this->oProductionEnv;
}
public function SetProductionEnv(RunTimeEnvironment $oProductionEnv): void {
$this->oProductionEnv = $oProductionEnv;
}
public function GetAfterComputationSelectedExtensions(): array {
return $this->aAfterComputationSelectedExtensions;
}
public function SetItopExtensionsMap(ItopExtensionsMap $oItopExtensionsMap): void {
$this->oItopExtensionsMap = $oItopExtensionsMap;
}
public function GetItopExtensionsMap(): ItopExtensionsMap {
if (is_null($this->oItopExtensionsMap)){
$this->oItopExtensionsMap = new iTopExtensionsMap($this->sTargetEnvironment, true);
}
return $this->oItopExtensionsMap;
}
public function GetAutoSelectModules(): array {
return $this->aAutoSelectModules;
}
public function GetSelectedModules(): array {
return $this->aSelectedModules;
}
public function GetUnSelectedModules(): array {
return $this->aUnSelectedModules;
}
public function ProcessInstallationChoices(): void {
$oXMLParameters = new XMLParameters($this->sInstallationPath);
$aSteps = $oXMLParameters->Get('steps', []);
if (! is_array($aSteps)) {
return;
}
foreach ($aSteps as $aStepInfo) {
$aOptions = $aStepInfo["options"] ?? null;
if (! is_null($aOptions) && is_array($aOptions)) {
foreach ($aOptions as $aChoiceInfo) {
$this->ProcessSelectedChoice($aChoiceInfo, $this->bInstallationOptionalChoicesChecked);
}
}
$aOptions = $aStepInfo["alternatives"] ?? null;
if (! is_null($aOptions) && is_array($aOptions)) {
foreach ($aOptions as $aChoiceInfo) {
$this->ProcessSelectedChoice($aChoiceInfo, false);
}
}
}
foreach ($this->aSelectedModules as $sModuleId => $sVal){
if (array_key_exists($sModuleId, $this->aUnSelectedModules)){
unset($this->aUnSelectedModules[$sModuleId]);
}
}
}
private function ProcessUnSelectedChoice($aChoiceInfo) {
if (!is_array($aChoiceInfo)) {
return;
}
$aCurrentModules = $aChoiceInfo["modules"] ?? [];
foreach ($aCurrentModules as $sModuleId){
$this->aUnSelectedModules[$sModuleId] = true;
}
$aAlternatives = $aChoiceInfo["alternatives"] ?? null;
if (!is_null($aAlternatives) && is_array($aAlternatives)) {
foreach ($aAlternatives as $aSubChoiceInfo) {
$this->ProcessUnSelectedChoice($aSubChoiceInfo);
}
}
if (array_key_exists('sub_options', $aChoiceInfo)) {
if (array_key_exists('options', $aChoiceInfo['sub_options'])) {
$aSubOptions = $aChoiceInfo['sub_options']['options'];
if (!is_null($aSubOptions) && is_array($aSubOptions)) {
foreach ($aSubOptions as $aSubChoiceInfo) {
$this->ProcessUnSelectedChoice($aSubChoiceInfo);
}
}
}
if (array_key_exists('alternatives', $aChoiceInfo['sub_options'])) {
$aSubAlternatives = $aChoiceInfo['sub_options']['alternatives'];
if (!is_null($aSubAlternatives) && is_array($aSubAlternatives)) {
foreach ($aSubAlternatives as $aSubChoiceInfo) {
$this->ProcessUnSelectedChoice($aSubChoiceInfo);
}
}
}
}
}
private function ProcessSelectedChoice($aChoiceInfo, bool $bAllChecked) {
if (!is_array($aChoiceInfo)) {
return;
}
$sDefault = $aChoiceInfo["default"] ?? "false";
$sMandatory = $aChoiceInfo["mandatory"] ?? "false";
$aCurrentModules = $aChoiceInfo["modules"] ?? [];
$sExtensionCode = $aChoiceInfo["extension_code"] ?? null;
if (0 === count($this->aSelectedExtensions)){
$bSelected = $bAllChecked || $sDefault === "true" || $sMandatory === "true";
if ($bSelected){
$this->aAfterComputationSelectedExtensions[]= $sExtensionCode;
}
} else {
$bSelected = $sMandatory === "true" ||
(null !== $sExtensionCode && in_array($sExtensionCode, $this->aSelectedExtensions));
}
foreach ($aCurrentModules as $sModuleId){
if ($bSelected) {
$this->aSelectedModules[$sModuleId] = true;
} else {
$this->aUnSelectedModules[$sModuleId] = true;
}
}
$aAlternatives = $aChoiceInfo["alternatives"] ?? null;
if (!is_null($aAlternatives) && is_array($aAlternatives)) {
foreach ($aAlternatives as $aSubChoiceInfo) {
if ($bSelected) {
$this->ProcessSelectedChoice($aSubChoiceInfo, $bAllChecked);
} else {
$this->ProcessUnSelectedChoice($aSubChoiceInfo);
}
}
}
if (array_key_exists('sub_options', $aChoiceInfo)) {
if (array_key_exists('options', $aChoiceInfo['sub_options'])) {
$aSubOptions = $aChoiceInfo['sub_options']['options'];
if (!is_null($aSubOptions) && is_array($aSubOptions)) {
foreach ($aSubOptions as $aSubChoiceInfo) {
if ($bSelected) {
$this->ProcessSelectedChoice($aSubChoiceInfo, $bAllChecked);
} else {
$this->ProcessUnSelectedChoice($aSubChoiceInfo);
}
}
}
}
if (array_key_exists('alternatives', $aChoiceInfo['sub_options'])) {
$aSubAlternatives = $aChoiceInfo['sub_options']['alternatives'];
if (!is_null($aSubAlternatives) && is_array($aSubAlternatives)) {
foreach ($aSubAlternatives as $aSubChoiceInfo) {
if ($bSelected) {
$this->ProcessSelectedChoice($aSubChoiceInfo, false);
} else {
$this->ProcessUnSelectedChoice($aSubChoiceInfo);
}
}
}
}
}
}
private function GetExtraDirs() : array {
$aSearchDirs = [];
$aDirs = [
'/datamodels/1.x',
'/datamodels/2.x',
'data/' . $this->sTargetEnvironment . '-modules',
'extensions',
];
foreach ($aDirs as $sRelativeDir){
$sDirPath = APPROOT.$sRelativeDir;
if (is_dir($sDirPath))
{
$aSearchDirs[] = $sDirPath;
}
}
return $aSearchDirs;
}
public function ProcessDefaultModules() : void {
$sProductionModuleDir = APPROOT.'data/' . $this->sTargetEnvironment . '-modules/';
$aAvailableModules = $this->GetProductionEnv()->AnalyzeInstallation(MetaModel::GetConfig(), $this->GetExtraDirs(), false, null);
$this->aAutoSelectModules = [];
foreach ($aAvailableModules as $sModuleId => $aModule) {
if (($sModuleId != ROOT_MODULE)) {
if (isset($aModule['auto_select'])) {
$this->aAutoSelectModules[$sModuleId] = $aModule;
continue;
}
if (($aModule['category'] == 'authentication') || (!$aModule['visible'])) {
$this->aSelectedModules[$sModuleId] = true;
continue;
}
$bIsExtra = (array_key_exists('root_dir', $aModule) && (strpos($aModule['root_dir'],
$sProductionModuleDir) !== false)); // Some modules (root, datamodel) have no 'root_dir'
if ($bIsExtra) {
// Modules in data/production-modules/ are considered as mandatory and always installed
$this->aSelectedModules[$sModuleId] = true;
}
}
}
}
public function ProcessAutoSelectModules() : void {
foreach($this->GetAutoSelectModules() as $sModuleId => $aModule)
{
try {
$bSelected = false;
SetupInfo::SetSelectedModules($this->aSelectedModules);
eval('$bSelected = ('.$aModule['auto_select'].');');
if ($bSelected)
{
// Modules in data/production-modules/ are considered as mandatory and always installed
$this->aSelectedModules[$sModuleId] = true;
}
}
catch (Exception $e) {
}
}
}
public function CanChooseUnpackageExtension(iTopExtension $oExtension) : bool {
if ($oExtension->sSource === iTopExtension::SOURCE_REMOTE){
SetupLog::Info("Data Extension can be selected", null, ['extension' => $oExtension->sCode]);
return true;
}
$bSelectable = $this->bInstallationOptionalChoicesChecked && ($oExtension->sSource === iTopExtension::SOURCE_MANUAL);
if ($bSelectable){
SetupLog::Info("Manual Extension can be selected", null, ['extension' => $oExtension->sCode]);
} else {
SetupLog::Debug("Manual Extension can NOT be selected", null, ['extension' => $oExtension->sCode]);
}
return $bSelectable;
}
public function ProcessExtensionModulesNotSpecifiedInChoices() {
/** @var \iTopExtension $oExtension */
foreach($this->GetItopExtensionsMap()->GetAllExtensions() as $oExtension) {
if (in_array($oExtension->sCode, $this->aAfterComputationSelectedExtensions)){
//extension already processed in installation.xml
SetupLog::Info("Extension already processed via installation choices", null,
[
'extension' => $oExtension->sCode,
]) ;
continue;
}
if ($this->CanChooseUnpackageExtension($oExtension)){
if (($oExtension->bVisible) && (count($oExtension->aMissingDependencies) === 0)) {
$aCurrentModules = [];
$aUnselectableModules = [];
$bIsExtensionSelectable = true;
foreach ($oExtension->aModules as $sModuleId) {
if (array_key_exists($sModuleId, $this->aSelectedModules)) {
//already selected
continue;
}
if (array_key_exists($sModuleId, $this->aUnSelectedModules)) {
$aUnselectableModules[] = $sModuleId;
//already unselected
$bIsExtensionSelectable = false;
} else {
$aCurrentModules[$sModuleId] = true;
}
}
if ($bIsExtensionSelectable) {
SetupLog::Debug("Add modules from unpackaged extension", null,
[
'extension' => $oExtension->sCode,
'source' => $oExtension->sSource,
'modules to add' => array_keys($aCurrentModules),
]);
$this->aSelectedModules = array_merge($this->aSelectedModules, $aCurrentModules);
$this->aAfterComputationSelectedExtensions[] = $oExtension->sCode;
} else {
SetupLog::Warning("Unpackaged extension can not be selected due to modules incompatible with installation choices",
null,
[
'extension' => $oExtension->sCode,
'source' => $oExtension->sSource,
'modules' => array_keys($aCurrentModules),
'unselectable modules' => $aUnselectableModules,
]);
}
}
}
}
}
}

View File

@@ -0,0 +1,73 @@
# Unattended-install
This script allows to install and update iTop via CLI.
For more information, see the official Wiki : [Automated installation [iTop Documentation]](https://www.itophub.io/wiki/page?id=latest:advancedtopics:automatic_install)
# unattended-install.php
## Usage
Execution of the unattended installation
> Note:
> Because the installation runs from the command line, make sure that the current user has enough rights to access the different locations and that the web server will be able to access the files and directories created during the scripted installation. In order to exactly emulate the behavior of
the interactive installation it may be a good practice to run this installation from the user account used for running the web server process.
Launch the script with the following command: ```bash php unattended_install.php --param-file=fresh-install.xml ```
Where: `fresh-install.xml` is the response file containing your desired settings for the installation (there are 4 models available in the folder `xml_setup`: fresh-install.xml, itil-fresh-install.xml, itil-upgrade.xml, upgrade.xml)
Fresh installation parameters
> Important:
> In the case of a fresh installation (<mode>install</mode>), do not forget to complete below mandatory parameters before:
```xml
<database>
<server></server>
<user></user>
<pwd></pwd>
<name></name>
<db_tls_enabled></db_tls_enabled>
<db_tls_ca></db_tls_ca>
<prefix></prefix>
</database>
<url>
</url>
<graphviz_path>/usr/bin/dot</graphviz_path>
<admin_account>
<user></user>
<pwd></pwd>
<language></language>
</admin_account>
<language></language>
```
## Options
To get all available options of the script, you can perform the following command :
```php unattended-install.php --help```
# install-itop.sh
## Usage
You can install your iTop by only using config-itop.php settings and run either
- a non-ITIL iTop fresh installation (use itil-fresh-install.xml to have ITIL modules instead)
```
./install-itop.sh ./xml_setup/fresh-install.xml
```
- a non-ITIL iTop upgrade (use itil-upgrade.xml to have ITIL modules instead)
```
./install-itop.sh ./xml_setup/upgrade.xml
```
- a specific iTop installation by providing both xml setup file
in below example file provided is the one generated by iTop during last setup.
```
./install-itop.sh ../../log/install-2024-04-03.xml
```

View File

@@ -0,0 +1,50 @@
#! /bin/bash
CLI_NAME=$(basename $0)
DIR=$(dirname $0)
ITOP_DIR="$DIR/../.."
HELP="Syntax: $CLI_NAME XML_SETUP [INSTALLATION_XML]"
function HELP {
echo $HELP
exit 1
}
if [ $# -lt 1 ]
then
echo "Missing parameters passed."
HELP
fi
if [ $# -gt 2 ]
then
echo "Too much parameters passed ($#) : $*."
HELP
fi
XML_SETUP=$1
if [ ! -f $XML_SETUP ]
then
echo "XML_SETUP file ($XML_SETUP) not found."
HELP
fi
if [ $# -eq 2 ]
then
INSTALLATION_XML=$2
if [ ! -f $INSTALLATION_XML ]
then
echo "INSTALLATION_XML file ($INSTALLATION_XML) not found."
HELP
fi
else
INSTALLATION_XML="$ITOP_DIR/datamodels/2.x/installation.xml"
fi
echo "$CLI_NAME: Using XML_SETUP ($XML_SETUP) and INSTALLATION_XML ($INSTALLATION_XML) files during unattended itop installation."
rm -rf $ITOP_DIR/data/.maintenance;
echo php $DIR/unattended-install.php --use_itop_config --installation_xml="$INSTALLATION_XML" --param-file="$XML_SETUP"
php $DIR/unattended-install.php --use_itop_config --installation_xml="$INSTALLATION_XML" --param-file="$XML_SETUP"

View File

@@ -0,0 +1,406 @@
<?php
require_once(dirname(__FILE__, 3) . '/approot.inc.php');
require_once(__DIR__ . '/InstallationFileService.php');
function PrintUsageAndExit()
{
echo <<<EOF
Usage: php unattended-install.php --param-file=<path_to_response_file> [--installation_xml=<path_to_installation_xml>] [--use_itop_config]
Options:
--param-file=<path_to_response_file> Path to the file (XML) to use for the unattended installation. That file (generated by the setup into log directory) must contain the following sections:
- target_env: the target environment (production, test, dev)
- database: the database settings (server, user, pwd, name, prefix)
- selected_modules: the list of modules to install
--response_file DEPRECATED: use `--param-file` instead
--installation_xml=<path_to_installation_xml> Use an installation.xml file to compute the modules to install depending on the selected extensions listed in the param file
--use_itop_config Use the iTop configuration file to get the database settings, otherwise use the database settings from the parameters file
Advanced options:
--check-consistency=1 Check the data model consistency after the installation (default: 0)
--clean=1 In case of a first installation, cleanup the environment before proceeding: delete the configuration file, the cache directory, the target directory, the database (default: 0)
--install=0 Set to 0 to perform a dry-run (default: 1)
EOF;
exit(-1);
}
/////////////////////////////////////////////////
$sCleanName = strtolower(trim(PHP_SAPI));
if ($sCleanName !== 'cli')
{
echo "Mode CLI only";
exit(-1);
}
if (in_array('--help', $argv)) {
PrintUsageAndExit();
}
$sParamFile = utils::ReadParam('param-file', null, true /* CLI allowed */, 'raw_data') ?? utils::ReadParam('response_file', null, true /* CLI allowed */, 'raw_data');
if (is_null($sParamFile)) {
echo "Missing mandatory argument `--param-file`.\n";
PrintUsageAndExit();
}
$bCheckConsistency = (utils::ReadParam('check-consistency', '0', true /* CLI allowed */) == '1');
if (false === file_exists($sParamFile)) {
echo "Param file `$sParamFile` doesn't exist! Exiting...\n";
exit(-1);
}
$oParams = new XMLParameters($sParamFile);
$sMode = $oParams->Get('mode');
$sTargetEnvironment = $oParams->Get('target_env', '');
if ($sTargetEnvironment == '')
{
$sTargetEnvironment = 'production';
}
$sXmlSetupBaseName = basename($sParamFile);
$sInstallationXmlPath = utils::ReadParam('installation_xml', null, true /* CLI allowed */, 'raw_data');
if (! is_null($sInstallationXmlPath) && is_file($sInstallationXmlPath)) {
$sInstallationBaseName = basename($sInstallationXmlPath);
$aSelectedExtensionsFromXmlSetup = $oParams->Get('selected_extensions', []);
$bInstallationChoicesProvided = count($aSelectedExtensionsFromXmlSetup) !== 0;
if ($bInstallationChoicesProvided) {
$sMsg = "Modules to install computed based on $sInstallationBaseName file and installation choices (listed in section `selected_extensions` of $sXmlSetupBaseName file)";
echo "$sMsg:\n".implode(',', $aSelectedExtensionsFromXmlSetup)."\n\n";
SetupLog::Info($sMsg, null, $aSelectedExtensionsFromXmlSetup);
} else {
$sMsg = "Modules to install computed based on default installation choices inside $sInstallationBaseName (no choice specified in section `selected_extensions` of $sXmlSetupBaseName file).";
echo "$sMsg\n\n";
SetupLog::Info($sMsg);
}
$oInstallationFileService = new InstallationFileService($sInstallationXmlPath, $sTargetEnvironment, $aSelectedExtensionsFromXmlSetup);
$oInstallationFileService->Init();
$aComputedExtensions = $oInstallationFileService->GetAfterComputationSelectedExtensions();
if (! $bInstallationChoicesProvided) {
$sMsg = "Computed installation choices";
echo "$sMsg:\n".implode(',', $aComputedExtensions)."\n\n";
SetupLog::Info($sMsg, null, $aComputedExtensions);
$oParams->Set('selected_extensions', $aComputedExtensions);
}
$aComputedModules = $oInstallationFileService->GetSelectedModules();
$aSelectedModules = array_keys($aComputedModules);
$oParams->Set('selected_modules', $aSelectedModules);
$sMsg = "Computed modules to install";
} else {
$aSelectedModules = $oParams->Get('selected_modules', []);
$sMsg = "Modules to install listed in $sXmlSetupBaseName (selected_modules section)";
}
sort($aSelectedModules);
echo "$sMsg:\n".implode(',', $aSelectedModules)."\n\n";
SetupLog::Info($sMsg, null, $aSelectedModules);
// Configuration file
$sConfigFile = APPCONF.$sTargetEnvironment.'/'.ITOP_CONFIG_FILE;
$bUseItopConfig = in_array('--use_itop_config', $argv);
if ($bUseItopConfig && file_exists($sConfigFile)){
//unattended run based on db settings coming from itop configuration
copy($sConfigFile, "$sConfigFile.backup");
$oConfig = new Config($sConfigFile);
$aDBXmlSettings = $oParams->Get('database', array());
$aDBXmlSettings ['server'] = $oConfig->Get('db_host');
$aDBXmlSettings ['user'] = $oConfig->Get('db_user');
$aDBXmlSettings ['pwd'] = $oConfig->Get('db_pwd');
$aDBXmlSettings ['name'] = $oConfig->Get('db_name');
$aDBXmlSettings ['prefix'] = $oConfig->Get('db_subname');
$aDBXmlSettings ['db_tls_enabled'] = $oConfig->Get('db_tls.enabled');
//cannot be null or infinite loop triggered!
$aDBXmlSettings ['db_tls_ca'] = $oConfig->Get('db_tls.ca') ?? "";
$oParams->Set('database', $aDBXmlSettings);
$aFields = [
'url' => 'app_root_url',
'source_dir' => 'source_dir',
'graphviz_path' => 'graphviz_path',
];
foreach($aFields as $sSetupField => $sConfField){
$oParams->Set($sSetupField, $oConfig->Get($sConfField));
}
$oParams->Set('mysql_bindir', $oConfig->GetModuleSetting('itop-backup', 'mysql_bindir', ""));
$oParams->Set('language', $oConfig->GetDefaultLanguage());
} else {
//unattended run based on db settings coming from response_file (XML file)
$aDBXmlSettings = $oParams->Get('database', array());
}
$sDBServer = $aDBXmlSettings['server'];
$sDBUser = $aDBXmlSettings['user'];
$sDBPwd = $aDBXmlSettings['pwd'];
$sDBName = $aDBXmlSettings['name'];
$sDBPrefix = $aDBXmlSettings['prefix'];
if ($sMode == 'install')
{
echo "Installation mode detected.\n";
$bClean = utils::ReadParam('clean', false, true /* CLI allowed */);
if ($bClean)
{
echo "Cleanup mode detected.\n";
if (file_exists($sConfigFile))
{
echo "Trying to delete the configuration file: '$sConfigFile'.\n";
@chmod($sConfigFile, 0770); // RWX for owner and group, nothing for others
unlink($sConfigFile);
}
else
{
echo "No config file to delete ($sConfigFile does not exist).\n";
}
// Starting with iTop 2.7.0, a failed setup leaves some lock files, let's remove them
$aLockFiles = array(
'data/.readonly' => 'read-only lock file',
'data/.maintenance' => 'maintenance mode lock file',
);
foreach($aLockFiles as $sFile => $sDescription)
{
$sLockFile = APPROOT.$sFile;
if (file_exists($sLockFile))
{
echo "Trying to delete the $sDescription: '$sLockFile'.\n";
unlink($sLockFile);
}
}
// Starting with iTop 2.6.0, let's remove the cache directory as well
// Can cause some strange issues in the setup (apparently due to the Dict class being automatically loaded ??)
$sCacheDir = APPROOT.'data/cache-'.$sTargetEnvironment;
if (file_exists($sCacheDir))
{
if (is_dir($sCacheDir))
{
echo "Emptying the cache directory '$sCacheDir'.\n";
SetupUtils::tidydir($sCacheDir);
}
else
{
die("ERROR the cache directory '$sCacheDir' exists, but is NOT a directory !!!\nExiting.\n");
}
}
// env-xxx directory
$sTargetDir = APPROOT.'env-'.$sTargetEnvironment;
if (file_exists($sTargetDir))
{
if (is_dir($sTargetDir))
{
echo "Emptying the target directory '$sTargetDir'.\n";
SetupUtils::tidydir($sTargetDir);
}
else
{
die("ERROR the target dir '$sTargetDir' exists, but is NOT a directory !!!\nExiting.\n");
}
}
else
{
echo "No target directory to delete ($sTargetDir does not exist).\n";
}
if ($sDBPrefix != '')
{
die("Cleanup not implemented for a partial database (prefix= '$sDBPrefix')\nExiting.");
}
$oMysqli = new mysqli($sDBServer, $sDBUser, $sDBPwd);
if ($oMysqli->connect_errno)
{
die("Cannot connect to the MySQL server (".$oMysqli->connect_errno . ") ".$oMysqli->connect_error."\nExiting");
}
else
{
if ($oMysqli->select_db($sDBName))
{
echo "Deleting database '$sDBName'\n";
$oMysqli->query("DROP DATABASE `$sDBName`");
}
else
{
echo "The database '$sDBName' does not seem to exist. Nothing to cleanup.\n";
}
}
}
}
else
{
//use settings from itop conf
$sTargetEnvironment = $oParams->Get('target_env', '');
if ($sTargetEnvironment == '')
{
$sTargetEnvironment = 'production';
}
$sTargetDir = APPROOT.'env-'.$sTargetEnvironment;
}
$bHasErrors = false;
$aChecks = SetupUtils::CheckBackupPrerequisites(APPROOT.'data'); // mmm should be the backup destination dir
$aSelectedModules = $oParams->Get('selected_modules');
$sSourceDir = $oParams->Get('source_dir', 'datamodels/latest');
$sExtensionDir = $oParams->Get('extensions_dir', 'extensions');
$aChecks = array_merge($aChecks, SetupUtils::CheckSelectedModules($sSourceDir, $sExtensionDir, $aSelectedModules));
foreach($aChecks as $oCheckResult)
{
switch ($oCheckResult->iSeverity)
{
case CheckResult::ERROR:
$bHasErrors = true;
$sHeader = "Error";
break;
case CheckResult::WARNING:
$sHeader = "Warning";
break;
case 3: // CheckResult::TRACE added in iTop 3.0.0
// does nothing : those are old debug traces, see N°2214
$sHeader = 'Trace';
break;
case CheckResult::INFO:
default:
$sHeader = "Info";
break;
}
echo $sHeader.": ".$oCheckResult->sLabel;
if (strlen($oCheckResult->sDescription))
{
echo ' - '.$oCheckResult->sDescription;
}
echo "\n";
}
if ($bHasErrors)
{
echo "Encountered stopper issues. Aborting...\n";
$sLogMsg = "Encountered stopper issues. Aborting...";
echo "$sLogMsg\n";
SetupLog::Error($sLogMsg);
exit(-1);
}
$bFoundIssues = false;
$bInstall = utils::ReadParam('install', true, true /* CLI allowed */);
if ($bInstall)
{
echo "Starting the unattended installation...\n";
$oWizard = new ApplicationInstaller($oParams);
$bRes = $oWizard->ExecuteAllSteps();
if (!$bRes)
{
echo "\nencountered installation issues!";
$bFoundIssues = true;
}
else
{
$oMysqli = new mysqli($sDBServer, $sDBUser, $sDBPwd);
if (!$oMysqli->connect_errno)
{
if ($oMysqli->select_db($sDBName))
{
// Check the presence of a table to record information about the MTP (from the Designer)
$sDesignerUpdatesTable = $sDBPrefix.'priv_designer_update';
$sSQL = "SELECT id FROM `$sDesignerUpdatesTable`";
if ($oMysqli->query($sSQL) !== false)
{
// Record the Designer Udpates in the priv_designer_update table
$sDeltaFile = APPROOT.'data/'.$sTargetEnvironment.'.delta.xml';
if (is_readable($sDeltaFile))
{
// Retrieve the revision
$oDoc = new DOMDocument();
$oDoc->load($sDeltaFile);
$iRevision = 0;
$iRevision = $oDoc->firstChild->getAttribute('revision_id');
if ($iRevision > 0) // Safety net, just in case...
{
$sDate = date('Y-m-d H:i:s');
$sSQL = "INSERT INTO `$sDesignerUpdatesTable` (revision_id, compilation_date, comment) VALUES ($iRevision, '$sDate', 'Deployed using unattended.php.')";
if ($oMysqli->query($sSQL) !== false)
{
echo "\nDesigner update (MTP at revision $iRevision) successfully recorded.\n";
}
else
{
echo "\nFailed to record designer updates(".$oMysqli->error.").\n";
}
}
else
{
echo "\nFailed to read the revision from $sDeltaFile file. No designer update information will be recorded.\n";
}
}
else
{
echo "\nNo $sDeltaFile file (or the file is not accessible). No designer update information to record.\n";
}
}
}
}
}
}
else
{
echo "No installation requested.\n";
}
if (!$bFoundIssues && $bCheckConsistency)
{
echo "Checking data model consistency.\n";
ob_start();
$sCheckRes = '';
try
{
MetaModel::CheckDefinitions(false);
$sCheckRes = ob_get_clean();
}
catch(Exception $e)
{
$sCheckRes = ob_get_clean()."\nException: ".$e->getMessage();
}
if (strlen($sCheckRes) > 0)
{
echo $sCheckRes;
echo "\nfound consistency issues!";
$bFoundIssues = true;
}
}
if (! $bFoundIssues)
{
// last line: used to check the install
// the only way to track issues in case of Fatal error or even parsing error!
$sLogMsg = "installed!";
if ($bUseItopConfig && is_file("$sConfigFile.backup"))
{
echo "\nuse config file provided by backup in $sConfigFile.";
copy("$sConfigFile.backup", $sConfigFile);
}
SetupLog::Info($sLogMsg);
echo "\n$sLogMsg";
exit(0);
}
$sLogMsg = "installation failed!";
SetupLog::Error($sLogMsg);
echo "\n$sLogMsg\n";
exit(-1);

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<security>
<requestFiltering>
<fileExtensions applyToWebDAV="false" allowUnlisted="false"></fileExtensions>
</requestFiltering>
<authorization>
<deny users="*" /> <!-- Denies all users -->
</authorization>
</security>
</system.webServer>
</configuration>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<installation>
<mode>install</mode>
<preinstall>
</preinstall>
<source_dir>datamodels/2.x/</source_dir>
<datamodel_version>2.7.0</datamodel_version>
<previous_configuration_file>/var/www/html/iTop/conf/production/config-itop.php</previous_configuration_file>
<extensions_dir>extensions</extensions_dir>
<target_env>production</target_env>
<workspace_dir></workspace_dir>
<database>
<server></server>
<user></user>
<pwd></pwd>
<name></name>
<db_tls_enabled></db_tls_enabled>
<db_tls_ca></db_tls_ca>
<prefix></prefix>
</database>
<url></url>
<graphviz_path>/usr/bin/dot</graphviz_path>
<admin_account>
<user></user>
<pwd></pwd>
<language></language>
</admin_account>
<language></language>
<selected_modules type="array">
</selected_modules>
<selected_extensions type="array">
</selected_extensions>
<sample_data>1</sample_data>
<old_addon></old_addon>
<options type="array"/>
<mysql_bindir></mysql_bindir>
<selected_modules type="array">
</selected_modules>
<selected_extensions type="array">
</selected_extensions>
</installation>

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<installation>
<mode>install</mode>
<preinstall>
</preinstall>
<source_dir>datamodels/2.x/</source_dir>
<datamodel_version>2.7.0</datamodel_version>
<previous_configuration_file>/var/www/html/iTop/conf/production/config-itop.php</previous_configuration_file>
<extensions_dir>extensions</extensions_dir>
<target_env>production</target_env>
<workspace_dir></workspace_dir>
<database>
<server></server>
<user></user>
<pwd></pwd>
<name></name>
<db_tls_enabled></db_tls_enabled>
<db_tls_ca></db_tls_ca>
<prefix></prefix>
</database>
<url></url>
<graphviz_path>/usr/bin/dot</graphviz_path>
<admin_account>
<user></user>
<pwd></pwd>
<language></language>
</admin_account>
<language></language>
<selected_modules type="array">
</selected_modules>
<selected_extensions type="array">
</selected_extensions>
<sample_data>1</sample_data>
<old_addon></old_addon>
<options type="array"/>
<mysql_bindir></mysql_bindir>
<selected_modules type="array">
</selected_modules>
<selected_extensions type="array">
<item>itop-config-mgmt-datacenter</item>
<item>itop-config-mgmt-end-user</item>
<item>itop-config-mgmt-storage</item>
<item>itop-config-mgmt-virtualization</item>
<item>itop-service-mgmt-enterprise</item>
<item>itop-ticket-mgmt-itil</item>
<item>itop-ticket-mgmt-itil-user-request</item>
<item>itop-ticket-mgmt-itil-incident</item>
<item>itop-ticket-mgmt-itil-enhanced-portal</item>
<item>itop-change-mgmt-itil</item>
<item>itop-config-mgmt-core</item>
<item>itop-kown-error-mgmt</item>
</selected_extensions>
</installation>

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<installation>
<mode>upgrade</mode>
<preinstall>
</preinstall>
<source_dir>datamodels/2.x/</source_dir>
<datamodel_version>2.7.0</datamodel_version>
<previous_configuration_file>/var/www/html/iTop/conf/production/config-itop.php</previous_configuration_file>
<extensions_dir>extensions</extensions_dir>
<target_env>production</target_env>
<workspace_dir></workspace_dir>
<database>
<server></server>
<user></user>
<pwd></pwd>
<name></name>
<db_tls_enabled></db_tls_enabled>
<db_tls_ca></db_tls_ca>
<prefix></prefix>
</database>
<url></url>
<graphviz_path>/usr/bin/dot</graphviz_path>
<admin_account>
<user></user>
<pwd></pwd>
<language></language>
</admin_account>
<language></language>
<selected_modules type="array">
</selected_modules>
<selected_extensions type="array">
</selected_extensions>
<sample_data>1</sample_data>
<old_addon></old_addon>
<options type="array"/>
<mysql_bindir></mysql_bindir>
<selected_modules type="array">
</selected_modules>
<selected_extensions type="array">
<item>itop-config-mgmt-datacenter</item>
<item>itop-config-mgmt-end-user</item>
<item>itop-config-mgmt-storage</item>
<item>itop-config-mgmt-virtualization</item>
<item>itop-service-mgmt-enterprise</item>
<item>itop-ticket-mgmt-itil</item>
<item>itop-ticket-mgmt-itil-user-request</item>
<item>itop-ticket-mgmt-itil-incident</item>
<item>itop-ticket-mgmt-itil-enhanced-portal</item>
<item>itop-change-mgmt-itil</item>
<item>itop-config-mgmt-core</item>
<item>itop-kown-error-mgmt</item>
</selected_extensions>
</installation>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<installation>
<mode>upgrade</mode>
<preinstall>
</preinstall>
<source_dir>datamodels/2.x/</source_dir>
<datamodel_version>2.7.0</datamodel_version>
<previous_configuration_file>/var/www/html/iTop/conf/production/config-itop.php</previous_configuration_file>
<extensions_dir>extensions</extensions_dir>
<target_env>production</target_env>
<workspace_dir></workspace_dir>
<database>
<server></server>
<user></user>
<pwd></pwd>
<name></name>
<db_tls_enabled></db_tls_enabled>
<db_tls_ca></db_tls_ca>
<prefix></prefix>
</database>
<url></url>
<graphviz_path>/usr/bin/dot</graphviz_path>
<admin_account>
<user></user>
<pwd></pwd>
<language></language>
</admin_account>
<language></language>
<selected_modules type="array">
</selected_modules>
<selected_extensions type="array">
</selected_extensions>
<sample_data>1</sample_data>
<old_addon></old_addon>
<options type="array"/>
<mysql_bindir></mysql_bindir>
<selected_modules type="array">
</selected_modules>
<selected_extensions type="array">
</selected_extensions>
</installation>

View File

@@ -2607,6 +2607,11 @@ class WizStepDone extends WizardStep
$oProductionEnv->InitDataModel($oConfig, true);
$sIframeUrl = $oConfig->GetModuleSetting('itop-hub-connector', 'setup_url', '');
$sSetupTokenFile = APPROOT.'data/.setup';
$sSetupToken = bin2hex(random_bytes(12));
file_put_contents($sSetupTokenFile, $sSetupToken);
$sIframeUrl.= "&setup_token=$sSetupToken";
if ($sIframeUrl != '')
{
$oPage->add('<iframe id="fresh_content" style="border:0; width:100%; display:none;" src="'.$sIframeUrl.'"></iframe>');

View File

@@ -21,6 +21,7 @@
namespace Combodo\iTop\Form;
use Combodo\iTop\Renderer\FormRenderer;
use CoreException;
/**
* Description of formmanager
@@ -59,6 +60,12 @@ abstract class FormManager
$oFormManager = new static();
$sFormRendererClass = $aJson['formrenderer_class'];
// N°7455 - Ensure form renderer class extends FormRenderer
if (false === is_a($sFormRendererClass, FormRenderer::class, true))
{
throw new CoreException('Form renderer class must extend '.FormRenderer::class);
}
/** @var \Combodo\iTop\Renderer\FormRenderer $oFormRenderer */
$oFormRenderer = new $sFormRendererClass();
$oFormRenderer->SetEndpoint($aJson['formrenderer_endpoint']);

View File

@@ -7,7 +7,7 @@
<div id="login-content">
<h1>{{ 'UI:ResetPwd-Title'|dict_s }}</h1>
{% if bNoUser %}
<p>{{ 'UI:ResetPwd-Error-WrongLogin'|dict_format(sAuthUser) }}</p>
<p>{{ 'UI:ResetPwd-EmailSent'|dict_s }}</p>
{% elseif bBadToken %}
<p>{{ 'UI:ResetPwd-Error-InvalidToken'|dict_s }}</p>
{% else %}

View File

@@ -8,7 +8,7 @@
<div id="login-title">
<h1>{{ 'UI:ResetPwd-Title'|dict_s }}</h1>
{% if bNoUser and sErrorMessage is null %}
<p>{{ 'UI:ResetPwd-Error-WrongLogin'|dict_format(sAuthUser) }}</p>
<p>{{ 'UI:ResetPwd-EmailSent'|dict_s }}</p>
{% elseif bBadToken and sErrorMessage is null %}
<p>{{ 'UI:ResetPwd-Error-InvalidToken'|dict_s }}</p>
{% else %}

View File

@@ -5,7 +5,7 @@ php_version=7.2-apache
db_version=5.7
[itop]
itop_setup=tests/setup_params/default-params.xml
;itop_setup=tests/setup_params/default-params.xml
itop_backup=tests/backups/backup-itop.tar.gz
[phpunit]

View File

@@ -4,6 +4,9 @@
"sempro/phpunit-pretty-print": "^1.4"
},
"autoload": {
"classmap": [
"unitary-tests/"
],
"psr-4": {
"Combodo\\iTop\\Test\\UnitTest\\": "src/BaseTestCase/",
"Combodo\\iTop\\Test\\UnitTest\\Hook\\": "src/Hook/",

View File

@@ -19,10 +19,6 @@
printerClass="Sempro\PHPUnitPrettyPrinter\PrettyPrinter"
>
<extensions>
<extension class="Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook" />
</extensions>
<php>
<ini name="error_reporting" value="E_ALL"/>
<ini name="display_errors" value="On"/>

View File

@@ -19,10 +19,6 @@
printerClass="Sempro\PHPUnitPrettyPrinter\PrettyPrinter"
>
<extensions>
<extension class="Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook" />
</extensions>
<php>
<ini name="error_reporting" value="E_ALL"/>
<ini name="display_errors" value="On"/>
@@ -45,7 +41,9 @@
<testsuite name="Setup">
<directory>unitary-tests/setup</directory>
</testsuite>
<!-- Note: The unitary-tests/sources/application/TwigBase is omitted for now as the test is not working -->
<testsuite name="SourcesApplicationTwigBase">
<directory>unitary-tests/sources/application/TwigBase</directory>
</testsuite>
<testsuite name="SourcesApplicationSearch">
<directory>unitary-tests/sources/application/search</directory>
</testsuite>

View File

@@ -19,10 +19,6 @@
printerClass="Sempro\PHPUnitPrettyPrinter\PrettyPrinter"
>
<extensions>
<extension class="Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook" />
</extensions>
<php>
<ini name="error_reporting" value="E_ALL"/>
<ini name="display_errors" value="On"/>

View File

@@ -7,11 +7,9 @@
namespace Combodo\iTop\Test\UnitTest;
use CMDBSource;
use Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook;
use Combodo\iTop\Test\UnitTest\Service\UnitTestRunTimeEnvironment;
use Config;
use Exception;
use IssueLog;
use MetaModel;
use SetupUtils;
use utils;
@@ -31,9 +29,9 @@ use utils;
abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
{
/**
* @var bool[]
*/
protected static $aReadyCustomEnvironments = [];
* @var UnitTestRunTimeEnvironment
*/
protected $oEnvironment = null;
/**
* @inheritDoc
@@ -51,11 +49,19 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
$this->setRunClassInSeparateProcess(true);
}
/**
/**
* @return string Abs path to the XML delta to use for the tests of that class
*/
abstract public function GetDatamodelDeltaAbsPath(): string;
protected function setUp(): void
{
static::LoadRequiredItopFiles();
$this->oEnvironment = new UnitTestRunTimeEnvironment('production', $this->GetTestEnvironment());
parent::setUp();
}
/**
* @inheritDoc
*/
@@ -93,40 +99,16 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
}
/**
* Mark {@see \Combodo\iTop\Test\UnitTest\ItopDataTestCase::GetTestEnvironment()} as ready (compiled)
*
* @return void
*/
private function MarkEnvironmentReady(): void
{
if (false === $this->IsEnvironmentReady()) {
touch(static::GetTestEnvironmentFolderAbsPath());
}
}
/**
* @return bool True if the {@see \Combodo\iTop\Test\UnitTest\ItopDataTestCase::GetTestEnvironment()} is ready (compiled, but not started)
*
* @details Having the environment ready means that it has been compiled for this global tests run, not that it is a relic from a previous global tests run
* @return bool True if the {@see \Combodo\iTop\Test\UnitTest\ItopDataTestCase::GetTestEnvironment()} is ready (compiled, up-to-date, but not necessarily started)
*/
final protected function IsEnvironmentReady(): bool
{
// As these test cases run in separate processes, the best way we found to let know a process if its environment was already prepared for **this run** was to compare the modification times of:
// - its own env-<ENV> folder
// - a file generated at the beginning of the global test run {@see \Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook}
$sRunStartedFilePath = TestsRunStartHook::GetRunStartedFileAbsPath();
$sEnvFolderPath = static::GetTestEnvironmentFolderAbsPath();
clearstatcache();
if (false === file_exists($sRunStartedFilePath) || false === file_exists($sEnvFolderPath)) {
if (false === file_exists($this->GetTestEnvironmentFolderAbsPath())) {
return false;
}
$iRunStartedFileModificationTime = filemtime($sRunStartedFilePath);
$iEnvFolderModificationTime = filemtime($sEnvFolderPath);
return $iEnvFolderModificationTime >= $iRunStartedFileModificationTime;
}
return $this->oEnvironment->IsUpToDate();
}
/**
* @inheritDoc
@@ -141,6 +123,12 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
// Note: To improve performances, we compile all XML deltas from test cases derived from this class and make a single environment where everything will be ran at once.
// This requires XML deltas to be compatible, but it is a known and accepted trade-off. See PR #457
if (false === $this->IsEnvironmentReady()) {
$this->debug("Preparing custom environment '$sTestEnv' with the following datamodel files:");
foreach ($this->oEnvironment->GetCustomDatamodelFiles() as $sCustomDatamodelFile) {
$this->debug(" - $sCustomDatamodelFile");
}
//----------------------------------------------------
// Clear any previous "$sTestEnv" environment
//----------------------------------------------------
@@ -153,14 +141,6 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
SetupUtils::tidydir($sConfFolder);
}
// - Datamodel delta files
// - Cache folder
// - Compiled folder
// We don't need to clean them as they are already by the compilation
// - Drop database
// We don't do that now, it will be done before re-creating the DB, once the metamodel is started
//----------------------------------------------------
// Prepare "$sTestEnv" environment
//----------------------------------------------------
@@ -179,7 +159,7 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
$oTestConfig->Set('db_name', $oTestConfig->Get('db_name').'_'.$sTestEnvSanitizedForDBName);
// - Compile env. based on the existing 'production' env.
$oEnvironment = new UnitTestRunTimeEnvironment($sTestEnv);
$oEnvironment = new UnitTestRunTimeEnvironment($sSourceEnv, $sTestEnv);
$oEnvironment->WriteConfigFileSafe($oTestConfig);
$oEnvironment->CompileFrom($sSourceEnv, false);
@@ -190,9 +170,11 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
}
CMDBSource::CreateDB($oTestConfig->Get('db_name'));
MetaModel::Startup($sConfFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sTestEnv);
// N°7446 For some reason we need to create the DB schema before starting the MM, then only we can create the tables.
// In 2.7, we can't call MetaModel::DBCreate() directly as the views creation will fail
$this->InvokeNonPublicStaticMethod(MetaModel::class, 'DBCreateTables', []);
$this->MarkEnvironmentReady();
$this->debug('Preparation of custom environment "'.$sTestEnv.'" done.');
$this->debug("Custom environment '$sTestEnv' is ready!");
}
parent::PrepareEnvironment();

View File

@@ -28,20 +28,8 @@ abstract class ItopTestCase extends TestCase
protected static $aBackupStaticProperties = [];
/** @noinspection UsingInclusionOnceReturnValueInspection avoid errors for approot includes */
protected function setUp(): void
{
$sAppRootRelPath = 'approot.inc.php';
$sDepthSeparator = '../';
for ($iDepth = 0; $iDepth < 8; $iDepth++) {
if (file_exists($sAppRootRelPath)) {
require_once $sAppRootRelPath;
break;
}
$sAppRootRelPath = $sDepthSeparator.$sAppRootRelPath;
}
$this->LoadRequiredItopFiles();
$this->LoadRequiredTestFiles();
}
@@ -68,8 +56,9 @@ abstract class ItopTestCase extends TestCase
*/
protected function LoadRequiredItopFiles(): void
{
// Empty until we actually need to require some files in the class
}
// At least make sure that the autoloader will be loaded, and that the APPROOT constant is defined
require_once __DIR__.'/../../../../approot.inc.php';
}
/**
* Overload this method to require necessary files through {@see \Combodo\iTop\Test\UnitTest\ItopTestCase::RequireOnceUnitTestFile()}
@@ -96,23 +85,6 @@ abstract class ItopTestCase extends TestCase
require_once APPROOT . $sFileRelPath;
}
/**
* Helper to load a module file. The caller test must be in that module !
* Will browse dir up to find a module.*.php
*
* @param string $sFileRelPath for example 'portal/src/Helper/ApplicationHelper.php'
* @since 2.7.10 3.1.1 3.2.0 N°6709 method creation
*/
protected function RequireOnceCurrentModuleFile(string $sFileRelPath): void
{
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
$sCallerFileFullPath = $aStack[0]['file'];
$sCallerDir = dirname($sCallerFileFullPath);
$sModuleRootPath = static::GetFirstDirUpContainingFile($sCallerDir, 'module.*.php');
require_once $sModuleRootPath . $sFileRelPath;
}
/**
* Require once a unit test file (eg. a mock class) from its relative path from the *current* dir.
* This ensure that required files don't crash when unit tests dir is moved in the iTop structure (see N°5608)
@@ -130,26 +102,6 @@ abstract class ItopTestCase extends TestCase
require_once $sCallerDirAbsPath . DIRECTORY_SEPARATOR . $sFileRelPath;
}
private static function GetFirstDirUpContainingFile(string $sSearchPath, string $sFileToFindGlobPattern): ?string
{
for ($iDepth = 0; $iDepth < 8; $iDepth++) {
$aGlobFiles = glob($sSearchPath . '/' . $sFileToFindGlobPattern);
if (is_array($aGlobFiles) && (count($aGlobFiles) > 0)) {
return $sSearchPath . '/';
}
$iOffsetSep = strrpos($sSearchPath, '/');
if ($iOffsetSep === false) {
$iOffsetSep = strrpos($sSearchPath, '\\');
if ($iOffsetSep === false) {
// Do not throw an exception here as PHPUnit will not show it clearly when determing the list of test to perform
return 'Could not find the approot file in ' . $sSearchPath;
}
}
$sSearchPath = substr($sSearchPath, 0, $iOffsetSep);
}
return null;
}
protected function debug($sMsg)
{
if (DEBUG_UNIT_TEST) {
@@ -164,7 +116,7 @@ abstract class ItopTestCase extends TestCase
public function GetMicroTime()
{
list($uSec, $sec) = explode(" ", microtime());
[$uSec, $sec] = explode(" ", microtime());
return ((float)$uSec + (float)$sec);
}
@@ -206,7 +158,7 @@ abstract class ItopTestCase extends TestCase
/**
* @param string $sObjectClass for example DBObject::class
* @param string $sMethodName
* @param object $oObject
* @param object|null $oObject
* @param array $aArgs
*
* @return mixed method result
@@ -249,7 +201,7 @@ abstract class ItopTestCase extends TestCase
* @throws \ReflectionException
* @since 2.7.8 3.0.3 3.1.0
*/
public function GetNonPublicProperty(object $oObject, string $sProperty)
public function GetNonPublicProperty($oObject, string $sProperty)
{
$oProperty = $this->GetProperty(get_class($oObject), $sProperty);
@@ -318,7 +270,7 @@ abstract class ItopTestCase extends TestCase
* @throws \ReflectionException
* @since 2.7.8 3.0.3 3.1.0
*/
public function SetNonPublicProperty(object $oObject, string $sProperty, $value)
public function SetNonPublicProperty($oObject, string $sProperty, $value)
{
$oProperty = $this->GetProperty(get_class($oObject), $sProperty);
$oProperty->setValue($oObject, $value);

View File

@@ -1,63 +0,0 @@
<?php
declare(strict_types=1);
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\Hook;
require_once __DIR__ . '/../../../../approot.inc.php';
use PHPUnit\Runner\AfterLastTestHook;
use PHPUnit\Runner\BeforeFirstTestHook;
use utils;
/**
* Class TestsRunStartHook
*
* IMPORTANT: This will no longer work in PHPUnit 10.0 and there is no alternative for now, so we will have to migrate it when the time comes
* @link https://localheinz.com/articles/2023/02/14/extending-phpunit-with-its-new-event-system/#content-hooks-event-system
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @package Combodo\iTop\Test\UnitTest\Hook
* @since N°6097 2.7.10 3.0.4 3.1.1
*/
class TestsRunStartHook implements BeforeFirstTestHook, AfterLastTestHook
{
/**
* Use the modification time on this file to check whereas it is newer than the requirements in a test case
*
* @return string Abs. path to a file generated when the global tests run starts.
*/
public static function GetRunStartedFileAbsPath(): string
{
// Note: This can't be put in the cache-<ENV> folder as we have multiple <ENV> running across the test cases
// We also don't want to put it in the unit tests folder as it is not supposed to be writable
return APPROOT.'data/.php-unit-tests-run-started';
}
/**
* @inheritDoc
*/
public function executeBeforeFirstTest(): void
{
// Create / change modification timestamp of file marking the beginning of the tests run
touch(static::GetRunStartedFileAbsPath());
}
/**
* @inheritDoc
*/
public function executeAfterLastTest(): void
{
// Cleanup of file marking the beginning of the tests run
if (file_exists(static::GetRunStartedFileAbsPath())) {
unlink(static::GetRunStartedFileAbsPath());
}
}
}

View File

@@ -9,9 +9,13 @@ namespace Combodo\iTop\Test\UnitTest\Service;
use Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase;
use IssueLog;
use LogChannels;
use MFCoreModule;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use ReflectionClass;
use RunTimeEnvironment;
use utils;
/**
@@ -25,62 +29,140 @@ use RunTimeEnvironment;
class UnitTestRunTimeEnvironment extends RunTimeEnvironment
{
/**
* @var string[]
*/
protected $aCustomDatamodelFiles = null;
/**
* @var string
*/
protected $sSourceEnv;
public function __construct($sSourceEnv, $sTargetEnv)
{
parent::__construct($sTargetEnv);
$this->sSourceEnv = $sSourceEnv;
}
public function GetEnvironment(): string
{
return $this->sFinalEnv;
}
public function IsUpToDate()
{
clearstatcache();
$fLastCompilationTime = filemtime(APPROOT.'env-'.$this->sFinalEnv);
$aModifiedFiles = [];
$this->FindFilesModifiedAfter($fLastCompilationTime, APPROOT.'datamodels/2.x', $aModifiedFiles);
$this->FindFilesModifiedAfter($fLastCompilationTime, APPROOT.'extensions', $aModifiedFiles);
$this->FindFilesModifiedAfter($fLastCompilationTime, APPROOT.'data/production-modules', $aModifiedFiles);
foreach ($this->GetCustomDatamodelFiles() as $sCustomDatamodelFile) {
if (filemtime($sCustomDatamodelFile) > $fLastCompilationTime) {
$aModifiedFiles[] = $sCustomDatamodelFile;
}
}
if (count($aModifiedFiles) > 0) {
echo "The following files have been modified after the last compilation:\n";
foreach ($aModifiedFiles as $sFile) {
echo " - $sFile\n";
}
}
return (count($aModifiedFiles) === 0);
}
/**
* @inheritDoc
*/
protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir)
{
$aRet = parent::GetMFModulesToCompile($sSourceEnv, $sSourceDir);
/** @var string[] $aDeltaFiles Referential of loaded deltas. Mostly to avoid duplicates. */
$aDeltaFiles = [];
foreach (get_declared_classes() as $sClass) {
// Filter on classes derived from this \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCaseItopCustomDatamodelTestCase
if (false === is_a($sClass, ItopCustomDatamodelTestCase::class, true)) {
continue;
}
$oReflectionClass = new ReflectionClass($sClass);
$oReflectionMethod = $oReflectionClass->getMethod('GetDatamodelDeltaAbsPath');
// Filter on classes with an actual XML delta (eg. not \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase and maybe some other deriving from a class with a delta)
if ($oReflectionMethod->isAbstract()) {
continue;
}
/** @var \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase $oTestClassInstance */
$oTestClassInstance = new $sClass();
// Check test class is for desired environment
if ($oTestClassInstance->GetTestEnvironment() !== $this->sFinalEnv) {
continue;
}
// Check XML delta actually exists
$sDeltaFile = $oTestClassInstance->GetDatamodelDeltaAbsPath();
if (false === is_file($sDeltaFile)) {
$this->fail("Could not prepare '$this->sFinalEnv' as the XML delta file '$sDeltaFile' (used in $sClass) does not seem to exist");
}
// Avoid duplicates
if (in_array($sDeltaFile, $aDeltaFiles)) {
continue;
}
// Prepare fake module name for delta
$sDeltaName = preg_replace('/[^\d\w]/', '', $sDeltaFile);
// Note: We can't use \MFDeltaModule as we can't specify the ID which leads to only 1 delta being applied... In the future we might introduce a new MFXXXModule, but in the meantime it feels alright (GLA / RQU)
$oDelta = new MFCoreModule($sDeltaName, $sDeltaName, $sDeltaFile);
IssueLog::Debug('XML delta found for unit tests', static::class, [
'Unit test class' => $sClass,
'Delta file path' => $sDeltaFile,
]);
$aDeltaFiles[] = $sDeltaFile;
$aRet[$sDeltaName] = $oDelta;
foreach ($this->GetCustomDatamodelFiles() as $sDeltaFile) {
$sDeltaId = preg_replace('/[^\d\w]/', '', $sDeltaFile);
$sDeltaName = basename($sDeltaFile);
$sDeltaDir = dirname($sDeltaFile);
$oDelta = new MFCoreModule($sDeltaName, "$sDeltaDir/$sDeltaName", $sDeltaFile);
$aRet[$sDeltaId] = $oDelta;
}
return $aRet;
}
public function GetCustomDatamodelFiles()
{
if (!is_null($this->aCustomDatamodelFiles)) {
return $this->aCustomDatamodelFiles;
}
$this->aCustomDatamodelFiles = [];
// Search for the PHP files implementing the method GetDatamodelDeltaAbsPath
// and extract the delta file path from the method
foreach(['unitary-tests', 'integration-tests'] as $sTestDir) {
// Iterate on all PHP files in subdirectories
// Note: grep is not available on Windows, so we will use the PHP Reflection API
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__."/../../$sTestDir")) as $oFile) {
if ($oFile->isDir()){
continue;
}
if (pathinfo($oFile->getFilename(), PATHINFO_EXTENSION) !== 'php') {
continue;
}
$sFile = $oFile->getPathname();
$sContent = file_get_contents($sFile);
if (strpos($sContent, 'GetDatamodelDeltaAbsPath') === false) {
continue;
}
$sClass = '';
$aMatches = [];
if (preg_match('/namespace\s+([^;]+);/', $sContent, $aMatches)) {
$sNamespace = $aMatches[1];
$sClass = $sNamespace.'\\'.basename($sFile, '.php');
}
if (preg_match('/\s+class\s+([^ ]+)\s+/', $sContent, $aMatches)) {
$sClass = $sNamespace.'\\'.$aMatches[1];
}
if ($sClass === '') {
continue;
}
require_once $sFile;
$oReflectionClass = new ReflectionClass($sClass);
if ($oReflectionClass->isAbstract()) {
continue;
}
// Check if the class extends ItopCustomDatamodelTestCase
if (!$oReflectionClass->isSubclassOf(ItopCustomDatamodelTestCase::class)) {
continue;
}
/** @var \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase $oTestClassInstance */
$oTestClassInstance = new $sClass();
if ($oTestClassInstance->GetTestEnvironment() !== $this->sFinalEnv) {
continue;
}
$sDeltaFile = $oTestClassInstance->GetDatamodelDeltaAbsPath();
if (!is_file($sDeltaFile)) {
throw new \Exception("Unknown delta file: $sDeltaFile, from test class '$sClass'");
}
if (!in_array($sDeltaFile, $this->aCustomDatamodelFiles)) {
$this->aCustomDatamodelFiles[] = $sDeltaFile;
}
}
}
return $this->aCustomDatamodelFiles;
}
private function FindFilesModifiedAfter(float $fReferenceTimestamp, string $sPathToScan, array &$aModifiedFiles)
{
if (!is_dir($sPathToScan)) {
return;
}
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($sPathToScan)) as $oFile) {
if ($oFile->isDir()) {
continue;
}
if (filemtime($oFile->getPathname()) > $fReferenceTimestamp) {
$aModifiedFiles[] = $oFile->getPathname();
}
}
}
}

View File

@@ -140,7 +140,7 @@ class ExampleFor_iQueryModifier implements \iQueryModifier
public function GetFieldExpression(QueryBuilderContext &$oBuild, $sClass, $sAttCode, $sColId, Expression $oFieldSQLExp, SQLQuery &$oSelect)
{
// Do nothing, we just need the class to exists for the unit test
return $oFieldSQLExp;
}
}
]]></content>

View File

@@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.7">
<classes>
<class id="TestServer" _delta="define">
<parent>cmdbAbstractObject</parent>
<properties>
<category>bizmodel</category>
<abstract>false</abstract>
<key_type>autoincrement</key_type>
<db_table>test_server</db_table>
<db_key_field>id</db_key_field>
</properties>
<presentation/>
<methods/>
<fields>
<field id="contact_list" xsi:type="AttributeLinkedSetIndirect">
<linked_class>lnkContactTestToServer</linked_class>
<ext_key_to_me>test_server_id</ext_key_to_me>
<ext_key_to_remote>contact_test_id</ext_key_to_remote>
<is_null_allowed>true</is_null_allowed>
</field>
<field id="password_list" xsi:type="AttributeLinkedSet">
<linked_class>PasswordTest</linked_class>
<ext_key_to_me>server_test_id</ext_key_to_me>
<is_null_allowed>true</is_null_allowed>
</field>
<field id="name" xsi:type="AttributeString">
<sql>name</sql>
<default_value/>
<is_null_allowed>false</is_null_allowed>
</field>
</fields>
</class>
<class id="ContactTest" _delta="define">
<parent>cmdbAbstractObject</parent>
<properties>
<category>bizmodel</category>
<abstract>false</abstract>
<key_type>autoincrement</key_type>
<db_table>contact_test</db_table>
<db_key_field>id</db_key_field>
</properties>
<presentation/>
<methods/>
<fields>
<field id="password" xsi:type="AttributeEncryptedString">
<sql>password</sql>
</field>
<field id="server_test_list" xsi:type="AttributeLinkedSetIndirect">
<linked_class>lnkContactTestToServer</linked_class>
<ext_key_to_me>contact_test_id</ext_key_to_me>
<ext_key_to_remote>test_server_id</ext_key_to_remote>
<is_null_allowed>true</is_null_allowed>
</field>
</fields>
</class>
<class id="lnkContactTestToServer" _delta="define">
<parent>cmdbAbstractObject</parent>
<properties>
<category>bizmodel</category>
<abstract>false</abstract>
<key_type>autoincrement</key_type>
<db_table>lnk_contact_server_test</db_table>
<db_key_field>id</db_key_field>
</properties>
<presentation/>
<methods/>
<fields>
<field id="contact_test_password" xsi:type="AttributeExternalField" _delta="define">
<extkey_attcode>contact_test_id</extkey_attcode>
<target_attcode>password</target_attcode>
</field>
<field id="test_server_id" xsi:type="AttributeExternalKey" _delta="define">
<target_class>TestServer</target_class>
<on_target_delete>DEL_MANUAL</on_target_delete>
<sql>test_server</sql>
<is_null_allowed>false</is_null_allowed>
</field>
<field id="contact_test_id" xsi:type="AttributeExternalKey" _delta="define">
<target_class>ContactTest</target_class>
<on_target_delete>DEL_MANUAL</on_target_delete>
<sql>contact_test</sql>
<is_null_allowed>false</is_null_allowed>
</field>
</fields>
</class>
<class id="PasswordTest" _delta="define">
<parent>cmdbAbstractObject</parent>
<properties>
<category>bizmodel</category>
<abstract>false</abstract>
<key_type>autoincrement</key_type>
<db_table>password_test</db_table>
<db_key_field>id</db_key_field>
</properties>
<presentation/>
<methods/>
<fields>
<field id="server_test_id" xsi:type="AttributeExternalKey" _delta="define">
<target_class>TestServer</target_class>
<sql>server_test_id</sql>
<on_target_delete>DEL_MANUAL</on_target_delete>
</field>
<field id="password" xsi:type="AttributeEncryptedString" _delta="define">
<sql>password</sql>
</field>
</fields>
</class>
</classes>
</itop_design>

View File

@@ -0,0 +1,164 @@
<?php
declare(strict_types=1);
namespace Combodo\iTop\Test\UnitTest\Core;
use ArchivedObjectException;
use AttributeEncryptedString;
use Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase;
use CoreException;
use CoreUnexpectedValue;
use Exception;
use MetaModel;
use ormLinkSet;
use PasswordTest;
use RestResultWithObjects;
/**
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @backupGlobals disabled
*/
class RestServicesSanitizeOutputTest extends ItopCustomDatamodelTestCase
{
private const SIMPLE_PASSWORD = '123456';
/**
* @throws Exception
*/
protected function setUp(): void
{
parent::setUp();
// Workaround to cope with inconsistent settings in itop-config files from the CI
AttributeEncryptedString::$sKey = '6eb9d9afa3ee0fbcebe622a33bf57aaeafb7c37998fd24c403c2522c2d60117f';
}
/**
* @return void
* @throws CoreException
*/
public function testSanitizeAttributeOnRequestedObject()
{
$oContactTest = MetaModel::NewObject('ContactTest', [
'password' => self::SIMPLE_PASSWORD]
);
$oRestResultWithObject = new RestResultWithObjects();
$oRestResultWithObject->AddObject(0, 'ok', $oContactTest, ['ContactTest' => ['password']]);
$oRestResultWithObject->SanitizeContent();
static::assertEquals(
'{"objects":{"ContactTest::-1":{"code":0,"message":"ok","class":"ContactTest","key":-1,"fields":{"password":"*****"}}},"code":0,"message":null}',
json_encode($oRestResultWithObject));
}
/**
* @return void
* @throws Exception
*/
public function testSanitizeAttributeExternalFieldOnLink()
{
$oContactTest = $this->createObject('ContactTest', [
'password' => self::SIMPLE_PASSWORD]
);
$oTestServer = $this->createObject('TestServer', [
'name' => 'test_server',
]);
// create lnkContactTestToServer
$oLnkContactTestToServer = $this->createObject('lnkContactTestToServer', [
'contact_test_id' => $oContactTest->GetKey(),
'test_server_id' => $oTestServer->GetKey()
]);
$oRestResultWithObject = new RestResultWithObjects();
$oRestResultWithObject->AddObject(0, 'ok', $oLnkContactTestToServer,
['lnkContactTestToServer' => ['contact_test_password']]);
$oRestResultWithObject->SanitizeContent();
static::assertContains(
'*****',
json_encode($oRestResultWithObject));
static::assertNotContains(
self::SIMPLE_PASSWORD,
json_encode($oRestResultWithObject));
}
/**
* @throws Exception
*/
public function testSanitizeAttributeOnObjectRelatedThroughNNRelation()
{
$oContactTest = $this->createObject('ContactTest', [
'password' => self::SIMPLE_PASSWORD]);
$oTestServer = $this->createObject('TestServer', [
'name' => 'test_server',
]);
// create lnkContactTestToServer
$this->createObject('lnkContactTestToServer', [
'contact_test_id' => $oContactTest->GetKey(),
'test_server_id' => $oTestServer->GetKey()
]);
$oRestResultWithObject = new RestResultWithObjects();
$oRestResultWithObject->AddObject(0, 'ok', $oTestServer,
['TestServer' => ['contact_list']]);
$oRestResultWithObject->SanitizeContent();
static::assertContains(
'*****',
json_encode($oRestResultWithObject));
static::assertNotContains(
self::SIMPLE_PASSWORD,
json_encode($oRestResultWithObject));
}
/**
* @throws CoreException
* @throws CoreUnexpectedValue
* @throws ArchivedObjectException
* @throws Exception
*/
public function testSanitizeOnObjectRelatedThrough1NRelation()
{
$oTestServer = $this->createObject('TestServer', [
'name' => 'my_server',
]);
$oPassword = new PasswordTest();
$oPassword->Set('password', self::SIMPLE_PASSWORD);
$oPassword->Set('server_test_id', $oTestServer->GetKey());
/** @var ormLinkSet $oContactList */
$oContactList = $oTestServer->Get('password_list');
$oContactList->AddItem($oPassword);
$oTestServer->Set('password_list', $oContactList);
$oRestResultWithObject = new RestResultWithObjects();
$oRestResultWithObject->AddObject(0, 'ok', $oTestServer, ['TestServer' => ['id', 'password_list']]);
$oRestResultWithObject->SanitizeContent();
static::assertContains(
'*****',
json_encode($oRestResultWithObject));
static::assertNotContains(
self::SIMPLE_PASSWORD,
json_encode($oRestResultWithObject));
}
/**
* @return string Abs path to the XML delta to use for the tests of that class
*/
public function GetDatamodelDeltaAbsPath(): string
{
return __DIR__ . '/Delta/delta_test_sanitize_output.xml';
}
}

View File

@@ -0,0 +1,125 @@
<?php
declare(strict_types=1);
namespace Combodo\iTop\Test\UnitTest\Core;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use CoreException;
use CoreServices;
use CoreUnexpectedValue;
use RestResultWithObjects;
use UserLocal;
/**
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @backupGlobals disabled
*/
class RestServicesTest extends ItopDataTestCase
{
/**
* @return void
* @dataProvider providerTestSanitizeJsonInput
*/
public function testSanitizeJsonInput($sJsonData, $sExpectedJsonDataSanitized)
{
$oRS = new CoreServices();
$sOutputJson = $oRS->SanitizeJsonInput($sJsonData);
static::assertEquals($sExpectedJsonDataSanitized, $sOutputJson);
}
/**
* @return array[]
*/
public function providerTestSanitizeJsonInput(): array
{
return [
'core/check_credentials' => [
'{"operation": "core/check_credentials", "user": "admin", "password": "admin"}',
'{
"operation": "core/check_credentials",
"user": "admin",
"password": "*****"
}'
],
'core/update' => [
'{"operation": "core/update", "comment": "Update user", "class": "UserLocal", "key": {"id":1}, "output_fields": "first_name, password", "fields": {"password" : "123456"}}',
'{
"operation": "core/update",
"comment": "Update user",
"class": "UserLocal",
"key": {
"id": 1
},
"output_fields": "first_name, password",
"fields": {
"password": "*****"
}
}'
],
'core/create' => [
'{"operation": "core/create", "comment": "Create user", "class": "UserLocal", "fields": {"first_name": "John", "last_name": "Doe", "email": "jd@example/com", "password" : "123456"}}',
'{
"operation": "core/create",
"comment": "Create user",
"class": "UserLocal",
"fields": {
"first_name": "John",
"last_name": "Doe",
"email": "jd@example/com",
"password": "*****"
}
}'
],
];
}
/**
* @param $sOperation
* @param $aJsonData
* @param $sExpectedJsonDataSanitized
* @return void
* @throws CoreException
* @throws CoreUnexpectedValue
* @dataProvider providerTestSanitizeJsonOutput
*/
public function testSanitizeJsonOutput($sOperation, $aJsonData, $sExpectedJsonDataSanitized)
{
$oUser = new UserLocal();
$oUser->Set('password', '123456');
$oRestResultWithObject = new RestResultWithObjects();
$oRestResultWithObject->AddObject(0, 'ok', $oUser, ['UserLocal' => ['login', 'password']]);
$oRestResultWithObject->SanitizeContent();
static::assertEquals($sExpectedJsonDataSanitized, json_encode($oRestResultWithObject));
}
/**
* @return array[]
*/
public function providerTestSanitizeJsonOutput(): array
{
return [
'core/update' => [
'core/update',
['comment' => 'Update user', 'class' => 'UserLocal', 'key' => ['login' => 'my_example'], 'output_fields' => 'password', 'fields' => ['password' => 'opkB!req57']],
'{"objects":{"UserLocal::-1":{"code":0,"message":"ok","class":"UserLocal","key":-1,"fields":{"login":"","password":"*****"}}},"code":0,"message":null}'
],
'core/create' => [
'core/create',
['comment' => 'Create user', 'class' => 'UserLocal', 'fields' => ['password' => 'Azertyuiiop*12', 'login' => 'toto', 'profile_list' => [1]]],
'{"objects":{"UserLocal::-1":{"code":0,"message":"ok","class":"UserLocal","key":-1,"fields":{"login":"","password":"*****"}}},"code":0,"message":null}'
],
'core/get' => [
'core/get',
['comment' => 'Get user', 'class' => 'UserLocal', 'key' => ['login' => 'my_example'], 'output_fields' => 'first_name, password'],
'{"objects":{"UserLocal::-1":{"code":0,"message":"ok","class":"UserLocal","key":-1,"fields":{"login":"","password":"*****"}}},"code":0,"message":null}'
],
'core/check_credentials' => [
'core/check_credentials',
['user' => 'admin', 'password' => 'admin'],
'{"objects":{"UserLocal::-1":{"code":0,"message":"ok","class":"UserLocal","key":-1,"fields":{"login":"","password":"*****"}}},"code":0,"message":null}'
],
];
}
}

View File

@@ -21,7 +21,7 @@ class iTopConfigParserTest extends ItopTestCase
clearstatcache();
$this->sConfigPath = utils::GetConfigFilePath();
$this->tmpSavePath = tempnam('/tmp/', 'config-itop');
$this->tmpSavePath = tempnam(sys_get_temp_dir(), 'config-itop');
$this->conf_exists = is_file($this->sConfigPath);
if ($this->conf_exists)
@@ -156,8 +156,8 @@ class iTopConfigParserTest extends ItopTestCase
*/
public function testConfigWriteToFile()
{
$tmpConfigFileBeforePath = tempnam( '/tmp/', 'config-itop');
$tmpConfigFileAfterPath = tempnam( '/tmp/', 'config-itop');
$tmpConfigFileBeforePath = tempnam( sys_get_temp_dir(), 'config-itop');
$tmpConfigFileAfterPath = tempnam( sys_get_temp_dir(), 'config-itop');
//create new config file
$sConfigFile = utils::GetConfig()->GetLoadedFile();

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Combodo\iTop\Test\UnitTest\Module\LaunchTest;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use TokenValidation;
class TokenValidationTest extends ItopDataTestCase
{
/**
* @param string $sSetupToken
*
* @return string
*/
public function createSetupTokenFile(string $sSetupToken): string
{
$sSetupTokenFile = APPROOT . 'data/.setup';
file_put_contents($sSetupTokenFile, $sSetupToken);
return $sSetupTokenFile;
}
/**
* @group itop-community
* @return void
*/
public function testLaunch()
{
$this->RequireOnceItopFile('datamodels/2.x/itop-hub-connector/TokenValidation.php');
$oTokenValidation = new TokenValidation();
$sSetupToken = bin2hex(random_bytes(12));
$this->assertFalse($oTokenValidation->isSetupTokenValid('lol'));
$this->assertFalse($oTokenValidation->isSetupTokenValid(''));
$this->assertFalse($oTokenValidation->isSetupTokenValid($sSetupToken));
$this->createSetupTokenFile($sSetupToken);
$this->assertFalse($oTokenValidation->isSetupTokenValid('lol'));
$this->createSetupTokenFile($sSetupToken);
$this->assertFalse($oTokenValidation->isSetupTokenValid(''));
$this->createSetupTokenFile($sSetupToken);
$this->assertTrue($oTokenValidation->isSetupTokenValid($sSetupToken));
}
}

View File

@@ -0,0 +1,744 @@
<?php
namespace Combodo\iTop\Test\UnitTest\Setup\UnattendedInstall;
use Combodo\iTop\Test\UnitTest\ItopTestCase;
use ItopExtensionsMap;
use iTopExtension;
use RunTimeEnvironment;
use InstallationFileService;
use ModuleDiscovery;
class InstallationFileServiceTest extends ItopTestCase {
protected function setUp(): void {
parent::setUp();
require_once(dirname(__FILE__, 6) . '/setup/unattended-install/InstallationFileService.php');
ModuleDiscovery::ResetCache();
}
protected function tearDown(): void {
parent::tearDown();
}
private function GetInstallationPath() : string {
return realpath(__DIR__ . '/resources/installation.xml');
}
private function GetModuleData($sCategory, bool $bIsVisible, bool $bIsAutoSelect, bool $bProductionModulesInRootDir=false) : array {
$sRootDir = $bProductionModulesInRootDir ? APPROOT.'data/production-modules/' : '';
$aModuleData = [
'category' => $sCategory,
'visible' => $bIsVisible,
'root_dir' => $sRootDir,
];
if ($bIsAutoSelect){
$aModuleData['auto_select'] = true;
}
return $aModuleData;
}
public function ProcessDefaultModulesProvider() {
parent::setUp();
return [
'root module' => [
'aAllFoundModules' => [
'_Root_' => $this->GetModuleData('authentication', false, false, true),
],
'aExpectedSelectedModules' => [],
'aExpectedAutoSelectModules' => [],
],
'auto-select root module' => [
'aAllFoundModules' => [
'_Root_' => $this->GetModuleData('authentication', false, true, true),
],
'aExpectedSelectedModules' => [],
'aExpectedAutoSelectModules' => [],
],
'autoselect module only' => [
'aAllFoundModules' => [
'autoselect-only' => $this->GetModuleData('mycategory', true, true),
],
'aExpectedSelectedModules' => [],
'aExpectedAutoSelectModules' => ['autoselect-only'],
],
'autoselect/invisible module' => [
'aAllFoundModules' => [
'autoselect-only' => $this->GetModuleData('mycategory', false, true),
],
'aExpectedSelectedModules' => [],
'aExpectedAutoSelectModules' => ['autoselect-only'],
],
'autoselect/invisible/in-root-dir module' => [
'aAllFoundModules' => [
'autoselect-only' => $this->GetModuleData('mycategory', false, true , true),
],
'aExpectedSelectedModules' => [],
'aExpectedAutoSelectModules' => ['autoselect-only'],
],
'visible/authent module' => [
'aAllFoundModules' => [
'authent-module' => $this->GetModuleData('authentication', true, false , false),
],
'aExpectedSelectedModules' => ['authent-module'],
'aExpectedAutoSelectModules' => [],
],
'invisible module' => [
'aAllFoundModules' => [
'visible-module' => $this->GetModuleData('mycategory', false, false , false),
],
'aExpectedSelectedModules' => ['visible-module'],
'aExpectedAutoSelectModules' => [],
],
'in-root-dir module' => [
'aAllFoundModules' => [
'in-root-dir-module' => $this->GetModuleData('mycategory', true, false , true),
],
'aExpectedSelectedModules' => ['in-root-dir-module'],
'aExpectedAutoSelectModules' => [],
],
];
}
/**
* @dataProvider ProcessDefaultModulesProvider
*/
public function testProcessDefaultModules(array $aAllFoundModules, array $aExpectedSelectedModules, array $aExpectedAutoSelectModules) {
$oInstallationFileService = new InstallationFileService('', 'production', [], true);
$oProductionEnv = $this->createMock(RunTimeEnvironment::class);
$oProductionEnv->expects($this->once())
->method('AnalyzeInstallation')
->willReturn($aAllFoundModules);
$oInstallationFileService->SetProductionEnv($oProductionEnv);
$oInstallationFileService->ProcessDefaultModules();
sort($aExpectedSelectedModules);
$aModules = array_keys($oInstallationFileService->GetSelectedModules());
sort($aModules);
$this->assertEquals($aExpectedSelectedModules, $aModules);
$aAutoSelectModules = array_keys($oInstallationFileService->GetAutoSelectModules());
sort($aAutoSelectModules);
$this->assertEquals($aExpectedAutoSelectModules, $aAutoSelectModules);
}
public function ProcessInstallationChoicesProvider() {
return [
'all checked' => [ true ],
'only defaut + mandatory' => [ false ],
];
}
/**
* @dataProvider ProcessInstallationChoicesProvider
*/
public function testProcessInstallationChoices($bInstallationOptionalChoicesChecked) {
$sPath = $this->GetInstallationPath();
$oInstallationFileService = new InstallationFileService($sPath, 'production', [], $bInstallationOptionalChoicesChecked);
$oProductionEnv = $this->createMock(RunTimeEnvironment::class);
$oProductionEnv->expects($this->never())
->method('AnalyzeInstallation');
$oInstallationFileService->SetProductionEnv($oProductionEnv);
$oInstallationFileService->ProcessInstallationChoices();
$aExpectedModules = [
"itop-config-mgmt",
"itop-attachments",
"itop-profiles-itil",
"itop-welcome-itil",
"itop-tickets",
"itop-files-information",
"combodo-db-tools",
"itop-core-update",
"itop-hub-connector",
"itop-oauth-client",
"itop-datacenter-mgmt",
"itop-endusers-devices",
"itop-storage-mgmt",
"itop-virtualization-mgmt",
"itop-service-mgmt",
"itop-request-mgmt",
"itop-portal",
"itop-portal-base",
"itop-change-mgmt",
];
$aExpectedUnselectedModules = [
'itop-change-mgmt-itil',
'itop-incident-mgmt-itil',
'itop-request-mgmt-itil',
'itop-service-mgmt-provider',
];
if ($bInstallationOptionalChoicesChecked){
$aExpectedModules []= "itop-problem-mgmt";
$aExpectedModules []= "itop-knownerror-mgmt";
} else {
$aExpectedUnselectedModules []= "itop-problem-mgmt";
$aExpectedUnselectedModules []= "itop-knownerror-mgmt";
}
sort($aExpectedModules);
$aModules = array_keys($oInstallationFileService->GetSelectedModules());
sort($aModules);
$this->assertEquals($aExpectedModules, $aModules);
$aUnselectedModules = array_keys($oInstallationFileService->GetUnSelectedModules());
sort($aExpectedUnselectedModules);
sort($aUnselectedModules);
$this->assertEquals($aExpectedUnselectedModules, $aUnselectedModules);
$aGetAfterComputationSelectedExtensions = $oInstallationFileService->GetAfterComputationSelectedExtensions();
sort($aGetAfterComputationSelectedExtensions);
$aExpectedExtensions = [
'itop-change-mgmt-simple',
'itop-config-mgmt-core',
'itop-config-mgmt-datacenter',
'itop-config-mgmt-end-user',
'itop-config-mgmt-storage',
'itop-config-mgmt-virtualization',
'itop-service-mgmt-enterprise',
'itop-ticket-mgmt-simple-ticket',
'itop-ticket-mgmt-simple-ticket-enhanced-portal',
];
if ($bInstallationOptionalChoicesChecked){
$aExpectedExtensions []= "itop-problem-mgmt";
$aExpectedExtensions []= 'itop-kown-error-mgmt';
}
sort($aExpectedExtensions);
$this->assertEquals($aExpectedExtensions, $aGetAfterComputationSelectedExtensions);
$this->ValidateNonItilExtensionComputation($oInstallationFileService, $bInstallationOptionalChoicesChecked);
}
/**
* @dataProvider ItilExtensionProvider
*/
public function testProcessInstallationChoicesWithItilChoices(array $aSelectedExtensions, bool $bKnownMgtSelected, bool $bCoreMgtSelected) {
$sPath = $this->GetInstallationPath();
$oInstallationFileService = new InstallationFileService($sPath, 'production', $aSelectedExtensions, false);
$oProductionEnv = $this->createMock(RunTimeEnvironment::class);
$oProductionEnv->expects($this->never())
->method('AnalyzeInstallation');
$oInstallationFileService->SetProductionEnv($oProductionEnv);
$oInstallationFileService->ProcessInstallationChoices();
$aExpectedInstallationModules = [
"itop-config-mgmt",
"itop-attachments",
"itop-profiles-itil",
"itop-welcome-itil",
"itop-tickets",
"itop-files-information",
"combodo-db-tools",
"itop-core-update",
"itop-hub-connector",
"itop-oauth-client",
"itop-datacenter-mgmt",
"itop-endusers-devices",
"itop-storage-mgmt",
"itop-virtualization-mgmt",
"itop-service-mgmt",
"itop-request-mgmt-itil",
"itop-incident-mgmt-itil",
"itop-portal",
"itop-portal-base",
"itop-change-mgmt-itil",
];
if ($bKnownMgtSelected){
$aExpectedInstallationModules []= "itop-knownerror-mgmt";
}
sort($aExpectedInstallationModules);
$aModules = array_keys($oInstallationFileService->GetSelectedModules());
sort($aModules);
$this->assertEquals($aExpectedInstallationModules, $aModules);
$aExpectedUnselectedModules = [
'itop-change-mgmt',
'itop-problem-mgmt',
'itop-request-mgmt',
'itop-service-mgmt-provider',
];
if (!$bKnownMgtSelected){
$aExpectedUnselectedModules[]='itop-knownerror-mgmt';
}
$aUnselectedModules = array_keys($oInstallationFileService->GetUnSelectedModules());
sort($aExpectedUnselectedModules);
sort($aUnselectedModules);
$this->assertEquals($aExpectedUnselectedModules, $aUnselectedModules);
$this->ValidateItilExtensionComputation($oInstallationFileService, $bKnownMgtSelected, $bCoreMgtSelected);
}
public function GetDefaultModulesProvider() {
return [
'check all possible modules' => [true],
'only minimum defaul/mandatory from installation.xml' => [false],
];
}
private function GetMockListOfFoundModules() : array {
$sJsonContent = file_get_contents(realpath(__DIR__ . '/resources/AnalyzeInstallation.json'));
$sJsonContent = str_replace('ROOTDIR_TOREPLACE', addslashes(APPROOT), $sJsonContent);
return json_decode($sJsonContent, true);
}
/**
* @dataProvider GetDefaultModulesProvider
*/
public function testGetAllSelectedModules($bInstallationOptionalChoicesChecked=false) {
$sPath = $this->GetInstallationPath();
$oInstallationFileService = new InstallationFileService($sPath, 'production', [], $bInstallationOptionalChoicesChecked);
$oProductionEnv = $this->createMock(RunTimeEnvironment::class);
$oProductionEnv->expects($this->once())
->method('AnalyzeInstallation')
->willReturn($this->GetMockListOfFoundModules());
$oInstallationFileService->SetProductionEnv($oProductionEnv);
$oItopExtensionsMap = $this->createMock(ItopExtensionsMap::class);
$oItopExtensionsMap->expects($this->once())
->method('GetAllExtensions')
->willReturn([]);
$oInstallationFileService->SetItopExtensionsMap($oItopExtensionsMap);
$oInstallationFileService->Init();
$aExpectedInstallationModules = [
"itop-config-mgmt",
"itop-attachments",
"itop-profiles-itil",
"itop-welcome-itil",
"itop-tickets",
"itop-files-information",
"combodo-db-tools",
"itop-core-update",
"itop-hub-connector",
"itop-oauth-client",
"itop-datacenter-mgmt",
"itop-endusers-devices",
"itop-storage-mgmt",
"itop-virtualization-mgmt",
"itop-service-mgmt",
"itop-request-mgmt",
"itop-portal",
"itop-portal-base",
"itop-change-mgmt",
'authent-cas',
'authent-external',
'authent-ldap',
'authent-local',
'itop-backup',
'itop-config',
'itop-sla-computation',
'itop-bridge-virtualization-storage',
];
if ($bInstallationOptionalChoicesChecked){
$aExpectedInstallationModules []= "itop-problem-mgmt";
$aExpectedInstallationModules []= "itop-knownerror-mgmt";
}
sort($aExpectedInstallationModules);
$aSelectedModules = array_keys($oInstallationFileService->GetSelectedModules());
sort($aSelectedModules);
$this->assertEquals($aExpectedInstallationModules, $aSelectedModules);
$this->ValidateNonItilExtensionComputation($oInstallationFileService, $bInstallationOptionalChoicesChecked);
}
private function ValidateNonItilExtensionComputation($oInstallationFileService, bool $bInstallationOptionalChoicesChecked, array $aAdditionalExtensions=[]) {
$aGetAfterComputationSelectedExtensions = $oInstallationFileService->GetAfterComputationSelectedExtensions();
sort($aGetAfterComputationSelectedExtensions);
$aExpectedExtensions = array_merge($aAdditionalExtensions, [
'itop-change-mgmt-simple',
'itop-config-mgmt-core',
'itop-config-mgmt-datacenter',
'itop-config-mgmt-end-user',
'itop-config-mgmt-storage',
'itop-config-mgmt-virtualization',
'itop-service-mgmt-enterprise',
'itop-ticket-mgmt-simple-ticket',
'itop-ticket-mgmt-simple-ticket-enhanced-portal',
]);
if ($bInstallationOptionalChoicesChecked){
$aExpectedExtensions []= "itop-problem-mgmt";
$aExpectedExtensions []= 'itop-kown-error-mgmt';
}
sort($aExpectedExtensions);
$this->assertEquals($aExpectedExtensions, $aGetAfterComputationSelectedExtensions);
}
private function ValidateItilExtensionComputation($oInstallationFileService, bool $bKnownMgtSelected, bool $bCoreMgtSelected) {
$aGetAfterComputationSelectedExtensions = $oInstallationFileService->GetAfterComputationSelectedExtensions();
sort($aGetAfterComputationSelectedExtensions);
$aExpectedExtensions = [
'itop-change-mgmt-itil',
'itop-config-mgmt-datacenter',
'itop-config-mgmt-end-user',
'itop-config-mgmt-storage',
'itop-config-mgmt-virtualization',
'itop-service-mgmt-enterprise',
'itop-ticket-mgmt-itil',
'itop-ticket-mgmt-itil-enhanced-portal',
'itop-ticket-mgmt-itil-incident',
'itop-ticket-mgmt-itil-user-request',
];
if ($bCoreMgtSelected){
$aExpectedExtensions []= 'itop-config-mgmt-core';
}
if ($bKnownMgtSelected){
$aExpectedExtensions []= 'itop-kown-error-mgmt';
}
sort($aExpectedExtensions);
$this->assertEquals($aExpectedExtensions, $aGetAfterComputationSelectedExtensions);
}
private function GetSelectedItilExtensions(bool $coreExtensionIncluded, bool $bKnownMgtIncluded) : array {
$aExtensions = [
'itop-config-mgmt-datacenter',
'itop-config-mgmt-end-user',
'itop-config-mgmt-storage',
'itop-config-mgmt-virtualization',
'itop-service-mgmt-enterprise',
'itop-ticket-mgmt-itil',
'itop-ticket-mgmt-itil-user-request',
'itop-ticket-mgmt-itil-incident',
'itop-ticket-mgmt-itil-enhanced-portal',
'itop-change-mgmt-itil',
];
if ($coreExtensionIncluded){
$aExtensions[]= 'itop-config-mgmt-core';
}
if ($bKnownMgtIncluded){
$aExtensions[]= 'itop-kown-error-mgmt';
}
return $aExtensions;
}
public function ItilExtensionProvider() {
return [
'all itil extensions + INCLUDING known-error-mgt' => [
'aSelectedExtensions' => $this->GetSelectedItilExtensions(true, true),
'bKnownMgtSelected' => true,
'bCoreMgtSelected' => true,
],
'all itil extensions WITHOUT known-error-mgt' => [
'aSelectedExtensions' => $this->GetSelectedItilExtensions(true, false),
'bKnownMgtSelected' => false,
'bCoreMgtSelected' => true,
],
'all itil extensions WITHOUT core mandatory ones + INCLUDING known-error-mgt' => [
'aSelectedExtensions' => $this->GetSelectedItilExtensions(false, true),
'bKnownMgtSelected' => true,
'bCoreMgtSelected' => false,
],
'all itil extensions WITHOUT core mandatory ones and WITHOUT known-error-mgt' => [
'aSelectedExtensions' => $this->GetSelectedItilExtensions(false, false),
'bKnownMgtSelected' => false,
'bCoreMgtSelected' => false,
],
];
}
/**
* @dataProvider ItilExtensionProvider
*/
public function testGetAllSelectedModules_withItilExtensions(array $aSelectedExtensions, bool $bKnownMgtSelected, bool $bCoreMgtSelected) {
$sPath = $this->GetInstallationPath();
$oInstallationFileService = new InstallationFileService($sPath, 'production', $aSelectedExtensions);
$oProductionEnv = $this->createMock(RunTimeEnvironment::class);
$oProductionEnv->expects($this->once())
->method('AnalyzeInstallation')
->willReturn($this->GetMockListOfFoundModules());
$oInstallationFileService->SetProductionEnv($oProductionEnv);
$oItopExtensionsMap = $this->createMock(ItopExtensionsMap::class);
$oItopExtensionsMap->expects($this->once())
->method('GetAllExtensions')
->willReturn([]);
$oInstallationFileService->SetItopExtensionsMap($oItopExtensionsMap);
$oInstallationFileService->Init();
$aSelectedModules = array_keys($oInstallationFileService->GetSelectedModules());
$aExpectedInstallationModules = [
"itop-config-mgmt",
"itop-attachments",
"itop-profiles-itil",
"itop-welcome-itil",
"itop-tickets",
"itop-files-information",
"combodo-db-tools",
"itop-core-update",
"itop-hub-connector",
"itop-oauth-client",
"itop-datacenter-mgmt",
"itop-endusers-devices",
"itop-storage-mgmt",
"itop-virtualization-mgmt",
"itop-service-mgmt",
"itop-request-mgmt-itil",
"itop-incident-mgmt-itil",
"itop-portal",
"itop-portal-base",
"itop-change-mgmt-itil",
"itop-full-itil",
'authent-cas',
'authent-external',
'authent-ldap',
'authent-local',
'itop-backup',
'itop-config',
'itop-sla-computation',
'itop-bridge-virtualization-storage',
];
if ($bKnownMgtSelected){
$aExpectedInstallationModules []= "itop-knownerror-mgmt";
}
sort($aExpectedInstallationModules);
sort($aSelectedModules);
$this->assertEquals($aExpectedInstallationModules, $aSelectedModules);
$this->ValidateItilExtensionComputation($oInstallationFileService, $bKnownMgtSelected, $bCoreMgtSelected);
}
private function CreateItopExtension(string $sSource, string $sCode, array $aModules, array $aMissingDependencies, bool $bIsVisible) : iTopExtension{
$oExtension = new iTopExtension();
$oExtension->sCode = $sCode;
$oExtension->sSource = $sSource;
$oExtension->aModules = $aModules;
$oExtension->aMissingDependencies = $aMissingDependencies;
$oExtension->bVisible = $bIsVisible;
return $oExtension;
}
public function CanChooseUnpackageExtensionProvider() {
return [
'extension in SOURCE_REMOTE' => [
'sCode' => "extension-from-designer",
'bInstallationOptionalChoicesChecked' => false,
'sSource' => 'data',
'bExpectedRes' => true
],
'extension in SOURCE_WIZARD' => [
'sCode' => 'extension-from-package',
'bInstallationOptionalChoicesChecked' => true,
'sSource' => 'datamodels',
'bExpectedRes' => false
],
'extension in SOURCE_MANUAL + optional OK' => [
'sCode' => 'extension-from-package',
'bInstallationOptionalChoicesChecked' => true,
'sSource' => 'extensions',
'bExpectedRes' => true
],
'extension in SOURCE_MANUAL + optional NOT OK' => [
'sCode' => 'extension-from-package',
'bInstallationOptionalChoicesChecked' => false,
'sSource' => 'extensions',
'bExpectedRes' => false
],
];
}
/**
* @dataProvider CanChooseUnpackageExtensionProvider
*/
public function testCanChooseUnpackageExtension(string $sCode, bool $bInstallationOptionalChoicesChecked, string $sSource, bool $bExpectedRes) {
$sPath = $this->GetInstallationPath();
$oInstallationFileService = new InstallationFileService($sPath, 'production', [], $bInstallationOptionalChoicesChecked);
$oItopExtension = $this->CreateItopExtension($sSource, $sCode, [], [], true);
$this->assertEquals($bExpectedRes, $oInstallationFileService->CanChooseUnpackageExtension($oItopExtension));
}
public function ProcessExtensionModulesNotSpecifiedInChoicesProvider() {
return [
'extensions to install OK' => [
'aExtensionData' => [
'extension1' => [
//'itop-request-mgmt-itil', //unselected
'combodo-monitoring',
'itop-config-mgmt', //already selected
],
'extension2' => [
//'itop-incident-mgmt-itil', //unselected
'combodo-monitoring2',
'itop-attachments', //already selected
]
],
'bExtensionCanBeChoosen' => true,
'aMissingDependencies' => [],
'bIsVisible' => true,
'bExpectedAdditionalExtensions' => [
'extension1', 'extension2'
],
'bExpectedAdditionalModules' => [
'combodo-monitoring', 'combodo-monitoring2'
]
],
'extensions to install cannot be choose,' => [
'aExtensionData' => [
'extension1' => [
'combodo-monitoring',
],
'extension2' => [
'combodo-monitoring2',
]
],
'bExtensionCanBeChoosen' => false,
'aMissingDependencies' => [],
'bIsVisible' => true,
'bExpectedAdditionalExtensions' => [],
'bExpectedAdditionalModules' => []
],
'extensions to install not visible' => [
'aExtensionData' => [
'extension1' => [
'combodo-monitoring',
],
'extension2' => [
'combodo-monitoring2',
]
],
'bExtensionCanBeChoosen' => true,
'aMissingDependencies' => [],
'bIsVisible' => false,
'bExpectedAdditionalExtensions' => [],
'bExpectedAdditionalModules' => []
],
'extensions to install with missing dependencies' => [
'aExtensionData' => [
'extension1' => [
'combodo-monitoring',
],
'extension2' => [
'combodo-monitoring2',
]
],
'bExtensionCanBeChoosen' => true,
'aMissingDependencies' => ['missing-module'],
'bIsVisible' => true,
'bExpectedAdditionalExtensions' => [],
'bExpectedAdditionalModules' => []
],
'extensions to install with unselectable ITIL module' => [
'aExtensionData' => [
'extension1' => [
'itop-request-mgmt-itil', //unselected
'combodo-monitoring',
],
'extension2' => [
'itop-incident-mgmt-itil', //unselected
'combodo-monitoring2',
]
],
'bExtensionCanBeChoosen' => true,
'aMissingDependencies' => [],
'bIsVisible' => true,
'bExpectedAdditionalExtensions' => [],
'bExpectedAdditionalModules' => []
],
'extensions already processed' => [
'aExtensionData' => [
'itop-config-mgmt-core' => [
'itop-config-mgmt', //already selected
],
],
'bExtensionCanBeChoosen' => true,
'aMissingDependencies' => [],
'bIsVisible' => true,
'bExpectedAdditionalExtensions' => [
],
'bExpectedAdditionalModules' => [
]
],
];
}
/**
* @dataProvider ProcessExtensionModulesNotSpecifiedInChoicesProvider
*/
public function testProcessExtensionModulesNotSpecifiedInChoices(array $aExtensionData, bool $bExtensionCanBeChoosen,
array $aMissingDependencies, bool $bIsVisible, array $bExpectedAdditionalExtensions, array $bExpectedAdditionalModules) {
$sPath = $this->GetInstallationPath();
$oInstallationFileService = new InstallationFileService($sPath, 'production', [], true);
$oProductionEnv = $this->createMock(RunTimeEnvironment::class);
$oProductionEnv->expects($this->once())
->method('AnalyzeInstallation')
->willReturn($this->GetMockListOfFoundModules());
$oInstallationFileService->SetProductionEnv($oProductionEnv);
$oItopExtensionsMap = $this->createMock(ItopExtensionsMap::class);
$aItopExtensionMap = [];
$sSource = $bExtensionCanBeChoosen ? iTopExtension::SOURCE_REMOTE : iTopExtension::SOURCE_WIZARD;
foreach ($aExtensionData as $sExtensionCode => $aModules){
$aItopExtensionMap[]= $this->CreateItopExtension($sSource, $sExtensionCode, $aModules, $aMissingDependencies, $bIsVisible);
}
$oItopExtensionsMap->expects($this->once())
->method('GetAllExtensions')
->willReturn($aItopExtensionMap);
$oInstallationFileService->SetItopExtensionsMap($oItopExtensionsMap);
$oInstallationFileService->Init();
$aSelectedModules = array_keys($oInstallationFileService->GetSelectedModules());
sort($aSelectedModules);
$aExpectedInstallationModules = array_merge($bExpectedAdditionalModules, [
"itop-config-mgmt",
"itop-attachments",
"itop-profiles-itil",
"itop-welcome-itil",
"itop-tickets",
"itop-files-information",
"combodo-db-tools",
"itop-core-update",
"itop-hub-connector",
"itop-oauth-client",
"itop-datacenter-mgmt",
"itop-endusers-devices",
"itop-storage-mgmt",
"itop-virtualization-mgmt",
"itop-service-mgmt",
"itop-request-mgmt",
"itop-portal",
"itop-portal-base",
"itop-change-mgmt",
"itop-problem-mgmt",
"itop-knownerror-mgmt",
'authent-cas',
'authent-external',
'authent-ldap',
'authent-local',
'itop-backup',
'itop-config',
'itop-sla-computation',
'itop-bridge-virtualization-storage',
]);
sort($aExpectedInstallationModules);
$this->assertEquals($aExpectedInstallationModules, $aSelectedModules);
$this->ValidateNonItilExtensionComputation($oInstallationFileService, true, $bExpectedAdditionalExtensions);
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace Combodo\iTop\Test\UnitTest\Setup\UnattendedInstall;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
class UnattendedInstallTest extends ItopDataTestCase
{
protected function setUp(): void
{
parent::setUp();
}
protected function tearDown(): void
{
parent::tearDown();
$aFiles = [
'web.config',
'.htaccess',
];
foreach ($aFiles as $sFile){
$sPath = APPROOT."setup/unattended-install/$sFile";
if (is_file("$sPath.back")){
rename("$sPath.back", $sPath);
}
}
}
private function callUnattendedFromHttp() : string {
$ch = curl_init();
$sUrl = \MetaModel::GetConfig()->Get('app_root_url');
curl_setopt($ch, CURLOPT_URL, "$sUrl/setup/unattended-install/unattended-install.php");
curl_setopt($ch, CURLOPT_POST, 1);// set post data to true
curl_setopt($ch, CURLOPT_POSTFIELDS, []);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// Force disable of certificate check as most of dev / test env have a self-signed certificate
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
$sJson = curl_exec($ch);
curl_close ($ch);
return $sJson;
}
public function testCallUnattendedInstallFromHttp(){
$sJson = $this->callUnattendedFromHttp();
if (false !== strpos($sJson, "403 Forbidden")){
//.htaccess / webconfig effect
$aFiles = [
'web.config',
'.htaccess',
];
foreach ($aFiles as $sFile){
$sPath = APPROOT."setup/unattended-install/$sFile";
if (is_file("$sPath")) {
rename($sPath, "$sPath.back");
}
}
$sJson = $this->callUnattendedFromHttp();
}
$this->assertEquals("Mode CLI only", $sJson, "even without HTTP protection, script should NOT be called directly by HTTP");
}
public function testCallUnattendedInstallFromCLI() {
$sCliPath = realpath(APPROOT."/setup/unattended-install/unattended-install.php");
exec(sprintf("%s %s", PHP_BINARY, $sCliPath), $aOutput, $iCode);
$sOutput = implode('\n', $aOutput);
var_dump($sOutput);
$this->assertStringContainsString("Missing mandatory argument `--param-file`", $sOutput);
if (DIRECTORY_SEPARATOR === '\\') {
// Windows
$this->assertEquals(-1, $iCode);
} else {
// Linux
$this->assertEquals(255, $iCode);
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,216 @@
<?xml version="1.0" encoding="UTF-8"?>
<installation>
<steps type="array">
<step>
<title>Configuration Management options</title>
<description><![CDATA[<h2>The options below allow you to configure the type of elements that are to be managed inside iTop.</h2>]]></description>
<banner>/images/modules.png</banner>
<options type="array">
<choice>
<extension_code>itop-config-mgmt-core</extension_code>
<title>Configuration Management Core</title>
<description>All the base objects that are mandatory in the iTop CMDB: Organizations, Locations, Teams, Persons, etc.</description>
<modules type="array">
<module>itop-config-mgmt</module>
<module>itop-attachments</module>
<module>itop-profiles-itil</module>
<module>itop-welcome-itil</module>
<module>itop-tickets</module>
<module>itop-files-information</module>
<module>combodo-db-tools</module>
<module>itop-core-update</module>
<module>itop-hub-connector</module>
<module>itop-oauth-client</module>
</modules>
<mandatory>true</mandatory>
</choice>
<choice>
<extension_code>itop-config-mgmt-datacenter</extension_code>
<title>Data Center Devices</title>
<description>Manage Data Center devices such as Racks, Enclosures, PDUs, etc.</description>
<modules type="array">
<module>itop-datacenter-mgmt</module>
</modules>
<default>true</default>
</choice>
<choice>
<extension_code>itop-config-mgmt-end-user</extension_code>
<title>End-User Devices</title>
<description>Manage devices related to end-users: PCs, Phones, Tablets, etc.</description>
<modules type="array">
<module>itop-endusers-devices</module>
</modules>
<default>true</default>
</choice>
<choice>
<extension_code>itop-config-mgmt-storage</extension_code>
<title>Storage Devices</title>
<description>Manage storage devices such as NAS, SAN Switches, Tape Libraries and Tapes, etc.</description>
<modules type="array">
<module>itop-storage-mgmt</module>
</modules>
<default>true</default>
</choice>
<choice>
<extension_code>itop-config-mgmt-virtualization</extension_code>
<title>Virtualization</title>
<description>Manage Hypervisors, Virtual Machines and Farms.</description>
<modules type="array">
<module>itop-virtualization-mgmt</module>
</modules>
<default>true</default>
</choice>
</options>
</step>
<step>
<title>Service Management options</title>
<description><![CDATA[<h2>Select the choice that best describes the relationships between the services and the IT infrastructure in your IT environment.</h2>]]></description>
<banner>./wizard-icons/service.png</banner>
<alternatives type="array">
<choice>
<extension_code>itop-service-mgmt-enterprise</extension_code>
<title>Service Management for Enterprises</title>
<description>Select this option if the IT delivers services based on a shared infrastructure. For example if different organizations within your company subscribe to services (like Mail and Print services) delivered by a single shared backend.</description>
<modules type="array">
<module>itop-service-mgmt</module>
</modules>
<default>true</default>
</choice>
<choice>
<extension_code>itop-service-mgmt-service-provider</extension_code>
<title>Service Management for Service Providers</title>
<description>Select this option if the IT manages the infrastructure of independent customers. This is the most flexible model, since the services can be delivered with a mix of shared and customer specific infrastructure devices.</description>
<modules type="array">
<module>itop-service-mgmt-provider</module>
</modules>
</choice>
</alternatives>
</step>
<step>
<title>Tickets Management options</title>
<description><![CDATA[<h2>Select the type of tickets you want to use in order to respond to user requests and incidents.</h2>]]></description>
<banner>./itop-incident-mgmt-itil/images/incident-escalated.png</banner>
<alternatives type="array">
<choice>
<extension_code>itop-ticket-mgmt-simple-ticket</extension_code>
<title>Simple Ticket Management</title>
<description>Select this option to use one single type of tickets for all kind of requests.</description>
<modules type="array">
<module>itop-request-mgmt</module>
</modules>
<default>true</default>
<sub_options>
<options type="array">
<choice>
<extension_code>itop-ticket-mgmt-simple-ticket-enhanced-portal</extension_code>
<title>Customer Portal</title>
<description><![CDATA[Modern & responsive portal for the end-users]]></description>
<modules type="array">
<module>itop-portal</module>
<module>itop-portal-base</module>
</modules>
<default>true</default>
</choice>
</options>
</sub_options>
</choice>
<choice>
<extension_code>itop-ticket-mgmt-itil</extension_code>
<title>ITIL Compliant Tickets Management</title>
<description>Select this option to have different types of ticket for managing user requests and incidents. Each type of ticket has a specific life cycle and specific fields</description>
<sub_options>
<options type="array">
<choice>
<extension_code>itop-ticket-mgmt-itil-user-request</extension_code>
<title>User Request Management</title>
<description>Manage User Request tickets in iTop</description>
<modules type="array">
<module>itop-request-mgmt-itil</module>
</modules>
</choice>
<choice>
<extension_code>itop-ticket-mgmt-itil-incident</extension_code>
<title>Incident Management</title>
<description>Manage Incidents tickets in iTop</description>
<modules type="array">
<module>itop-incident-mgmt-itil</module>
</modules>
</choice>
<choice>
<extension_code>itop-ticket-mgmt-itil-enhanced-portal</extension_code>
<title>Customer Portal</title>
<description><![CDATA[Modern & responsive portal for the end-users]]></description>
<modules type="array">
<module>itop-portal</module>
<module>itop-portal-base</module>
</modules>
<default>true</default>
</choice>
</options>
</sub_options>
</choice>
<choice>
<extension_code>itop-ticket-mgmt-none</extension_code>
<title>No Tickets Management</title>
<description>Don't manage incidents or user requests in iTop</description>
<modules type="array">
</modules>
</choice>
</alternatives>
</step>
<step>
<title>Change Management options</title>
<description><![CDATA[<h2>Select the type of tickets you want to use in order to manage changes to the IT infrastructure.</h2>]]></description>
<banner>./itop-change-mgmt/images/change.png</banner>
<alternatives type="array">
<choice>
<extension_code>itop-change-mgmt-simple</extension_code>
<title>Simple Change Management</title>
<description>Select this option to use one type of ticket for all kind of changes.</description>
<modules type="array">
<module>itop-change-mgmt</module>
</modules>
<default>true</default>
</choice>
<choice>
<extension_code>itop-change-mgmt-itil</extension_code>
<title>ITIL Change Management</title>
<description>Select this option to use Normal/Routine/Emergency change tickets.</description>
<modules type="array">
<module>itop-change-mgmt-itil</module>
</modules>
</choice>
<choice>
<extension_code>itop-change-mgmt-none</extension_code>
<title>No Change Management</title>
<description>Don't manage changes in iTop</description>
<modules type="array">
</modules>
</choice>
</alternatives>
</step>
<step>
<title>Additional ITIL tickets</title>
<description><![CDATA[<h2>Pick from the list below the additional ITIL processes that are to be implemented in iTop.</h2>]]></description>
<banner>./itop-knownerror-mgmt/images/known-error.png</banner>
<options type="array">
<choice>
<extension_code>itop-kown-error-mgmt</extension_code>
<title>Known Errors Management</title>
<description>Select this option to track "Known Errors" and FAQs in iTop.</description>
<modules type="array">
<module>itop-knownerror-mgmt</module>
</modules>
</choice>
<choice>
<extension_code>itop-problem-mgmt</extension_code>
<title>Problem Management</title>
<description>Select this option track "Problems" in iTop.</description>
<modules type="array">
<module>itop-problem-mgmt</module>
</modules>
</choice>
</options>
</step>
</steps>
</installation>

View File

@@ -1,58 +1,44 @@
<?php
namespace Combodo\iTop\Test\UnitTest;
namespace Combodo\iTop\Test\UnitTest\Application\TwigBase;
use Combodo\iTop\Portal\Twig\AppExtension;
use Twig_Environment;
use Twig_Loader_Array;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
/**
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @backupGlobals disabled
*/
class TwigTest extends ItopDataTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->RequireOnceItopFile('core/config.class.inc.php');
}
protected function setUp(): void
{
parent::setUp();
$this->RequireOnceItopFile('core/config.class.inc.php');
}
/**
* Test the fix for ticket N°4384
*
* @dataProvider TemplateProvider
*
*/
public function testTemplate($sFileName, $sExpected)
{
$sId = 'TestTwig';
$oAppExtension = new AppExtension();
/**
* @covers N°4384 N°7810
*
*/
public function testTemplate()
{
// Creating sandbox twig env. to load and test the custom form template
$oTwig = new Environment(new FilesystemLoader(__DIR__.'/'));
// Creating sandbox twig env. to load and test the custom form template
$oTwig = new Twig_Environment(new Twig_Loader_Array([$sId => $sFileName]));
// Manually registering filters and functions as we didn't find how to do it automatically
$oAppExtension = new AppExtension();
$aFilters = $oAppExtension->getFilters();
foreach ($aFilters as $oFilter)
{
$oTwig->addFilter($oFilter);
}
$aFunctions = $oAppExtension->getFunctions();
foreach ($aFunctions as $oFunction)
{
$oTwig->addFunction($oFunction);
}
// Manually registering filters and functions as we didn't find how to do it automatically
$aFilters = $oAppExtension->getFilters();
foreach ($aFilters as $oFilter)
{
$oTwig->addFilter($oFilter);
}
$aFunctions = $oAppExtension->getFunctions();
foreach ($aFunctions as $oFunction)
{
$oTwig->addFunction($oFunction);
}
}
$sOutput = $oTwig->render('test.html.twig');
public static function testTemplateProvider()
{
$aReturn = array();
$aReturn['filter_system'] = [
'sFileName' => 'test.html',
'expected' =>file_get_contents(dirname(__FILE__).'/test.html'),
];
return $aReturn;
}
$this->assertEquals(file_get_contents(__DIR__.'/test.html'), $sOutput);
}
}

View File

@@ -42,5 +42,8 @@ Smith, Dupond
<div>['echo',1]|sort('system')|join</div>
echo1
<div>[['id','']|sort('system')</div>
id
POST /subscribe?0=cat+/etc/passwd HTTP/1.1
email=""@attacker.tld

View File

@@ -47,5 +47,8 @@
<div>['echo',1]|sort('system')|join</div>
{{ ['echo',1]|sort('system')|join }}
<div>[['id','']|sort('system')</div>
{{['id','']|sort('system')|join}}
POST /subscribe?0=cat+/etc/passwd HTTP/1.1
email="{{ app.request.query.filter(0,0,1024,{'options':'system'}) }}"@attacker.tld

View File

@@ -29,40 +29,8 @@
</admin_account>
<language>EN US</language>
<selected_modules type="array">
<item>authent-cas</item>
<item>authent-external</item>
<item>authent-local</item>
<item>itop-backup</item>
<item>itop-config</item>
<item>itop-profiles-itil</item>
<item>itop-sla-computation</item>
<item>itop-tickets</item>
<item>itop-welcome-itil</item>
<item>itop-config-mgmt</item>
<item>itop-attachments</item>
<item>itop-datacenter-mgmt</item>
<item>itop-endusers-devices</item>
<item>itop-storage-mgmt</item>
<item>itop-virtualization-mgmt</item>
<item>itop-bridge-virtualization-storage</item>
<item>itop-service-mgmt</item>
<item>itop-request-mgmt</item>
<item>itop-portal</item>
<item>itop-portal-base</item>
<item>itop-change-mgmt</item>
<item>itop-knownerror-mgmt</item>
</selected_modules>
<selected_extensions type="array">
<item>itop-config-mgmt-core</item>
<item>itop-config-mgmt-datacenter</item>
<item>itop-config-mgmt-end-user</item>
<item>itop-config-mgmt-storage</item>
<item>itop-config-mgmt-virtualization</item>
<item>itop-service-mgmt-enterprise</item>
<item>itop-ticket-mgmt-simple-ticket</item>
<item>itop-ticket-mgmt-simple-ticket-enhanced-portal</item>
<item>itop-change-mgmt-simple</item>
<item>itop-kown-error-mgmt</item>
</selected_extensions>
<sample_data>1</sample_data>
<old_addon></old_addon>

View File

@@ -209,6 +209,13 @@ try
/** @var iRestServiceProvider $oRS */
$oRS = $aOpToRestService[$sOperation]['service_provider'];
$sProvider = get_class($oRS);
if ($oRS instanceof iRestInputSanitizer) {
$sSanitizedJsonInput = $oRS->SanitizeJsonInput($sJsonString);
}
else {
$sSanitizedJsonInput = $sJsonString;
}
CMDBObject::SetTrackOrigin('webservice-rest');
$oResult = $oRS->ExecOperation($sVersion, $sOperation, $aJsonData);
@@ -234,6 +241,7 @@ catch(Exception $e)
//
$sResponse = json_encode($oResult);
if ($sResponse === false)
{
$oJsonIssue = new RestResult();
@@ -267,7 +275,7 @@ if (MetaModel::GetConfig()->Get('log_rest_service'))
$oLog->SetTrim('userinfo', UserRights::GetUser());
$oLog->Set('version', $sVersion);
$oLog->Set('operation', $sOperation);
$oLog->SetTrim('json_input', $sJsonString);
$oLog->SetTrim('json_input', $sSanitizedJsonInput);
$oLog->Set('provider', $sProvider);
$sMessage = $oResult->message;
@@ -277,7 +285,8 @@ if (MetaModel::GetConfig()->Get('log_rest_service'))
}
$oLog->SetTrim('message', $sMessage);
$oLog->Set('code', $oResult->code);
$oLog->SetTrim('json_output', $sResponse);
$oResult->SanitizeContent();
$oLog->SetTrim('json_output', json_encode($oResult, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
$oLog->DBInsertNoReload();
$oKPI->ComputeAndReport('Log inserted');