Compare commits

..

92 Commits
1.0.8 ... 2.7.4

Author SHA1 Message Date
odain
d229e08f02 prepare iTop release 2.7.4 2021-03-16 09:00:06 +01:00
odain
b5adb2e82b N°3671: fix and make test more lisible 2021-03-15 19:01:09 +01:00
odain
386c90c601 N°3668 - URL direct error: renamed trust_proxies<-behind_reverse_proxy 2021-03-15 14:56:16 +01:00
odain
5d0c61178b N°3671 : persist absolute URL when setup context (force trustproxy enabled) 2021-03-15 14:34:56 +01:00
odain
3bcae734e5 N°3671 : persist absolute URL when setup context (force trustproxy enabled) 2021-03-15 12:27:05 +01:00
odain
842e8f9e01 php doc 2021-03-10 15:32:44 +01:00
odain
52cd4f7c5e N°3788 - timeout/excessive duration during MTP - fix PostDbCreation 2021-03-09 11:44:25 +01:00
odain
995619af9b N°3788 - timeout/excessive duration during MTP - increase timeout from 5 to 30s 2021-03-09 08:27:33 +01:00
odain
c842162fe2 N°3788 - timeout/excessive duration during MTP 2021-03-09 08:13:57 +01:00
odain
83f99642e0 N°3793 - Cleanup of orphan CMDBChange can hang the setup 2021-03-08 11:38:20 +01:00
bruno-ds
ae6a264d6d N°3671 - fix typo in HTTP header name 2021-03-05 16:57:03 +01:00
bruno-ds
a06bf6ea7c coding convention (thanks @molkobain) 2021-03-05 09:20:04 +01:00
bruno-ds
bb8d4a92cb fix an indentation problem (thanks @Hipska) 2021-03-04 09:56:05 +01:00
bruno-ds
1429792690 N°3668 - fix an improper redirection to the homepage when iTop is behind a reverse proxy 2021-03-04 09:39:48 +01:00
bruno-ds
1f26b59d90 N°3671 - add an API endpoint (it will be used by N°3668 and N°3760) + some code cleanup asked by @molkobain 2021-03-04 09:32:13 +01:00
bruno-ds
7b093a6bba N°3671 - app_root_url: handle reverse proxies during the setup and preserve existing configuration during an upgrade. 2021-03-03 11:55:18 +01:00
odain
d4607ee815 N°3065 - Failed enum comparison when values contains parenthesis : add a warning 2021-03-02 07:33:36 +01:00
odain
5c0e92d51a N°3065 - Failed enum comparison when values contains parenthesis 2021-03-01 17:06:49 +01:00
bruno-ds
cd4b3fdaab N°3764 - fix CI 2021-03-01 16:27:40 +01:00
bruno-ds
0030d5c2b8 N°3764 - add transactions_gc_threshold in order to tune CSRF token GC load 2021-03-01 15:30:40 +01:00
bruno-ds
95a0efedcf N°3728 - security hardening 2021-03-01 15:28:34 +01:00
bruno-ds
13a1d32f56 N°3453 - portal export header fields are now localized 2021-02-26 11:47:34 +01:00
Pierre Goiffon
35155e4b7a 💡 N°3065 comments modifications 2021-02-26 10:06:29 +01:00
Eric
77710f1613 Revert "#1946 Fix Twig templates logging too much"
This reverts commit 2763b991
2021-02-25 17:57:39 +01:00
Eric
2763b99142 #1946 Fix Twig templates logging too much 2021-02-25 14:44:16 +01:00
bruno-ds
db13c105ad N°3473 - PHPdoc
as requested by @piRGoif
2021-02-24 17:38:54 +01:00
bruno-ds
2276539f24 N°3430 - code cleanup 2021-02-24 16:50:14 +01:00
bruno-ds
9b7cd20d47 N°3473 - security hardening 2021-02-24 16:50:13 +01:00
bruno-ds
e1d644c33b Merge remote-tracking branch 'origin/support/2.7' into support/2.7 2021-02-24 12:06:40 +01:00
bruno-ds
c601082a5e 3548 - disable core update if a file integrity problem is detected 2021-02-24 12:05:11 +01:00
Molkobain
5836be7131 Fix unit test 2021-02-24 09:49:16 +01:00
Molkobain
6f40bb4c35 Change check level to "warning" in order to keep consistency with the others 2021-02-24 09:29:42 +01:00
bruno-ds
241bd1cdeb N°3430 - code cleanup
- during the code review @dflaven preferred the reference rather than the return alternative
 - typo
2021-02-22 09:43:42 +01:00
Molkobain
71c5f47cd8 PHPDoc 2021-02-19 09:37:53 +01:00
odain
74246a8278 N°3065 - Failed enum comparison when values contains parenthesis - enhance db model parsing used during setup comparison with expected one to generate SQL migration queries 2021-02-18 18:24:09 +01:00
bruno-ds
c450c9426c Merge remote-tracking branch 'origin/support/2.7' into support/2.7 2021-02-18 16:29:44 +01:00
odain
46f9fe743c fix ci: adapt test to make sure config date_and_time is set properly before 2021-02-18 16:27:38 +01:00
odain
c31df5fff3 fix ci: adapt test to make sure config date_and_time is set properly before 2021-02-18 16:07:37 +01:00
Pierre Goiffon
6e0af1a3b7 💡 Add variable typing 2021-02-18 13:21:06 +01:00
bruno-ds
e9e18513be N°3430 - fix preference page's warning and add missing token generation
- fix the warning (ajax call interrupted) if preference form ajax call is way faster than the one of the 2 other by adding a new timeout_duration option before the redirect.
2021-02-18 12:18:38 +01:00
Molkobain
9d2fc883b8 Fix test name 2021-02-17 10:31:59 +01:00
odain
913ea0cef2 N°3412 - Command Injection vulnerability in the Setup Wizard - renaming 2021-02-17 10:22:21 +01:00
odain
82ba7f25b0 N°3412 - Command Injection vulnerability in the Setup Wizard - do not use escapeshellcmd before execution in Windows envt 2021-02-17 10:18:28 +01:00
odain
bb877a244b N°3412 - Command Injection vulnerability in the Setup Wizard - do not use escapeshellcmd before execution in Windows envt 2021-02-17 10:09:39 +01:00
odain
a12959d60e N°3412 - Command Injection vulnerability in the Setup Wizard - handle empty path 2021-02-17 07:50:18 +01:00
bruno-ds
83434b5506 N°3430 - add translations 2021-02-16 17:42:11 +01:00
bruno-ds
dcd4abe72b N°3430 - security hardening 2021-02-16 17:33:49 +01:00
odain
571520815a N°3412 - Command Injection vulnerability in the Setup Wizard - include test to CI 2021-02-16 17:25:45 +01:00
odain
e9cff0920b N°3412 - Command Injection vulnerability in the Setup Wizard - fix test and code 2021-02-16 17:12:41 +01:00
odain
905ee19519 N°3412 - Command Injection vulnerability in the Setup Wizard 2021-02-16 15:57:12 +01:00
bruno-ds
0b95220d1b N°3466 - Add (missing) translations 2021-02-16 09:46:04 +01:00
bruno-ds
e1b2a767f5 N°3142 - fix typos 2021-02-15 17:49:25 +01:00
bruno-ds
3058b2eb00 N°3142 - Add (missing) translations 2021-02-15 17:08:47 +01:00
Molkobain
38bc2d9d58 🔧 Change max line length in .editorConfig 2021-02-15 13:49:39 +01:00
Eric
c8e8778d7b N°3468 - Fix extension.xml preventing extensions installation
(cherry picked from commit 92c8af1b19)
2021-02-11 17:48:29 +01:00
bruno-ds
656fa3208a N°3721 - revert the feature (will only be available on the 3.0) 2021-02-10 15:33:01 +01:00
bruno-ds
f647ce61c2 N°3721 - toolkit's "update iTop" with the "Create symbolic links" option checked now empty the compiled directory as expected 2021-02-10 14:34:21 +01:00
Eric
6b76e5a853 N°3618 - Count on union with different conditions fails (php doc) 2021-02-08 09:39:24 +01:00
Eric
dbb6e43751 N°3618 - Count on union with different conditions fails (Fix unit tests) 2021-02-05 11:28:40 +01:00
Eric
f07f0ba1c7 N°3618 - Count on union with different conditions fails (Fix multi-column attributes sql generation) 2021-02-05 10:15:14 +01:00
Pierre Goiffon
a5894c1a4c Rename \Combodo\iTop\Test\UnitTest\ItopTestCase::InvokeInvisible* to InvokeNonPublic* 2021-02-04 09:41:56 +01:00
Eric
e06996a2e4 N°3660 - Fix JOIN without condition on child joined table is ignored (check done in optimizer) 2021-02-03 15:58:54 +01:00
Eric
2f0e7c6d29 N°3586 - Fix login window not correctly displayed 2021-02-03 09:15:56 +01:00
Eric
7115a6ae7d N°3660 - Fix JOIN without condition on child joined table is ignored (remove unnecessary check) 2021-02-03 09:07:59 +01:00
Pierre Goiffon
765560d1f5 ItopTestCase : helpers to call invisble methods 2021-02-02 17:57:40 +01:00
Eric
bc024d9ed0 N°3660 - Fix JOIN without condition on child joined table is ignored 2021-02-02 17:30:03 +01:00
jbostoen
37a4a3eb47 🌐 Fix typo in Dutch translations (#189)
Co-authored-by: jbostoen <->
2021-02-01 16:21:10 +01:00
Pierre Goiffon
54e9bd5c8e Merge branch 'support/2.6' into support/2.7
# Conflicts:
#	.editorconfig
2021-01-25 09:13:43 +01:00
Pierre Goiffon
066a6d8b36 🔧 Use same .editorconfig in all supported branches 2021-01-25 09:12:38 +01:00
jbostoen
4123c6213d 🌐 NL : distinguish between approval (goedkeuring) <=> acceptance (acceptatie) (#182)
Co-authored-by: jbostoen <->
2020-12-21 14:08:29 +01:00
Pierre Goiffon
8265b9b034 N°3416 fix PHPDoc 2020-12-10 18:10:45 +01:00
Pierre Goiffon
c4756e8cec Upgrade version n° 2020-12-08 18:47:24 +01:00
odain
37351d6b3e N°3464: fix ci 2020-12-07 16:23:17 +01:00
odain
57a085eec1 N°3464: move fix in itop-fence + fix/enhance rest api test 2020-12-07 15:56:35 +01:00
odain
0019595923 N°3464: fix ci 2020-12-07 00:44:39 +01:00
odain
4d61c14f80 N°3464 add test in phpunit.xml.dit to validate the fix 2020-12-07 00:12:31 +01:00
odain
cf1b613923 N°3464 REST comment field not working anymore 2020-12-06 23:54:27 +01:00
Pierre Goiffon
1304e2eb2d N°3416 Updates after code review v2 :) 2020-12-04 08:51:07 +01:00
Pierre Goiffon
3cf16627c1 Merge remote-tracking branch 'origin/support/2.6' into support/2.7
# Conflicts:
#	pages/ajax.render.php
2020-12-03 18:18:36 +01:00
Pierre Goiffon
4aaa237bf9 🔖 Prepare 2.7.3 version 2020-12-03 18:15:58 +01:00
Pierre Goiffon
cece15d10c N°3416 Updates after code review
Many thanks @bruno-ds !
* add comments to explain intentions
* fix indentations
2020-12-03 17:45:44 +01:00
Pierre Goiffon
aa15e009cb 🔖 Prepare 2.7.2-2 version 2020-12-03 10:05:37 +01:00
Pierre Goiffon
b9ca2ac13d N°3416 Fix DocumentFile preview not working anymore
Was caused by X-Frame-Options http header added with N°3317

(cherry picked from commit 35d77ff642)

# Conflicts:
#	pages/ajax.render.php
2020-12-03 08:20:51 +01:00
Pierre Goiffon
80e1e0e61a N°3426 Fix no navigation menu on User object creation
Caused by a typo in js/forms-json-utils.js
Thanks @Molkobain !
2020-12-02 18:02:00 +01:00
Pierre Goiffon
ecebe4ecd5 N°3416 XFrame and cache headers optimizations
* Remove XFrame header set in \WebPage::no_cache : not this method responsability, was confusing :/
* Remove no_cache() calls when already set in page constructor (ajax_page mainly)
* Also calls everywhere the \WebPage::no_cache method instead of setting headers manually
2020-12-02 17:19:05 +01:00
Pierre Goiffon
8bfcb14d0c N°3416 XFrame-Options header is now set using a config parameter, defaults to SAMEORIGIN
Also adds an indirection (\WebPage::add_xframe_options) to set header
2020-12-02 17:17:11 +01:00
Molkobain
1cf1473d6b N°3469 - Fix variable declaration (let => var) 🤭 2020-12-02 17:01:00 +01:00
Molkobain
aa43425df3 N°3469 - Portal: Fix modal created without an ID 2020-12-02 16:59:39 +01:00
Pierre Goiffon
35d77ff642 N°3416 Fix DocumentFile preview not working anymore
Was caused by X-Frame-Options http header added with N°3317
2020-12-02 15:44:58 +01:00
acognet
539fa43503 N°3461 - Setup Broken with Chrome v87 2020-11-30 18:27:25 +01:00
acognet
eb537f45f4 N°3421 - Attributes of class Person are not accessible from :current_contact in portal anymore. Only attributes of class Contact are. 2020-11-30 09:24:35 +01:00
acognet
a2a4cd4e7a N°3426 - Wrong tab is displayed when a creation or modification form is invalidated 2020-11-27 15:20:20 +01:00
112 changed files with 1879 additions and 499 deletions

View File

@@ -6,19 +6,20 @@ end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = false
max_line_length = 140
max_line_length = 300
tab_width = 4
ij_continuation_indent_size = 8
ij_formatter_off_tag = @formatter:off
ij_formatter_on_tag = @formatter:on
ij_formatter_tags_enabled = false
ij_smart_tabs = false
ij_visual_guides = 80,120
ij_visual_guides = 300
ij_wrap_on_typing = true
[*.css]
indent_style = tab
ij_smart_tabs = true
ij_visual_guides = none
ij_css_align_closing_brace_with_properties = false
ij_css_blank_lines_around_nested_selector = 1
ij_css_blank_lines_between_blocks = 1
@@ -38,7 +39,9 @@ ij_css_use_double_quotes = true
ij_css_value_alignment = do_not_align
[*.scss]
indent_style = tab
indent_size = 2
tab_width = 2
ij_visual_guides = none
ij_scss_align_closing_brace_with_properties = false
ij_scss_blank_lines_around_nested_selector = 1
ij_scss_blank_lines_between_blocks = 1
@@ -58,8 +61,8 @@ ij_scss_use_double_quotes = true
ij_scss_value_alignment = 0
[*.twig]
indent_style = tab
ij_smart_tabs = true
ij_visual_guides = none
ij_wrap_on_typing = false
ij_twig_keep_indents_on_empty_lines = false
ij_twig_spaces_inside_comments_delimiters = true
@@ -67,6 +70,7 @@ ij_twig_spaces_inside_delimiters = true
ij_twig_spaces_inside_variable_delimiters = true
[.editorconfig]
ij_visual_guides = none
ij_editorconfig_align_group_field_declarations = false
ij_editorconfig_space_after_colon = false
ij_editorconfig_space_after_comma = true
@@ -74,10 +78,12 @@ ij_editorconfig_space_before_colon = false
ij_editorconfig_space_before_comma = false
ij_editorconfig_spaces_around_assignment_operators = true
[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.rng,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul,phpunit.xml.dist}]
[{*.ant, *.fxml, *.jhm, *.jnlp, *.jrxml, *.rng, *.tld, *.wsdl, *.xml, *.xsd, *.xsl, *.xslt, *.xul, phpunit.xml.dist}]
indent_size = 2
tab_width = 2
ij_smart_tabs = true
ij_visual_guides = none
ij_wrap_on_typing = false
ij_xml_align_attributes = true
ij_xml_align_text = false
ij_xml_attribute_wrap = normal
@@ -93,11 +99,12 @@ ij_xml_line_comment_at_first_column = true
ij_xml_space_after_tag_name = false
ij_xml_space_around_equals_in_attribute = false
ij_xml_space_inside_empty_tag = false
ij_xml_text_wrap = normal
ij_xml_text_wrap = off
[{*.bash,*.sh,*.zsh}]
indent_size = 2
tab_width = 2
ij_visual_guides = none
ij_shell_binary_ops_start_line = false
ij_shell_keep_column_alignment_padding = false
ij_shell_minify_program = false
@@ -108,6 +115,7 @@ ij_shell_switch_cases_indented = false
indent_style = tab
ij_continuation_indent_size = 4
ij_smart_tabs = true
ij_visual_guides = none
ij_javascript_align_imports = false
ij_javascript_align_multiline_array_initializer_expression = false
ij_javascript_align_multiline_binary_operation = false
@@ -141,7 +149,7 @@ ij_javascript_chained_call_dot_on_new_line = true
ij_javascript_class_brace_style = end_of_line
ij_javascript_comma_on_new_line = false
ij_javascript_do_while_brace_force = always
ij_javascript_else_on_new_line = true
ij_javascript_else_on_new_line = false
ij_javascript_enforce_trailing_comma = keep
ij_javascript_extends_keyword_wrap = off
ij_javascript_extends_list_wrap = off
@@ -297,6 +305,7 @@ ij_php_array_initializer_new_line_after_left_brace = true
ij_php_array_initializer_right_brace_on_new_line = true
ij_php_array_initializer_wrap = on_every_item
ij_php_assignment_wrap = off
ij_php_attributes_wrap = off
ij_php_author_weight = 8
ij_php_binary_operation_sign_on_next_line = false
ij_php_binary_operation_wrap = off
@@ -385,6 +394,7 @@ ij_php_new_line_after_php_opening_tag = false
ij_php_null_type_position = in_the_end
ij_php_package_weight = 28
ij_php_param_weight = 5
ij_php_parameters_attributes_wrap = off
ij_php_parentheses_expression_new_line_after_left_paren = false
ij_php_parentheses_expression_right_paren_on_new_line = false
ij_php_phpdoc_blank_line_before_tags = true
@@ -406,6 +416,7 @@ ij_php_see_weight = 3
ij_php_since_weight = 28
ij_php_sort_phpdoc_elements = true
ij_php_space_after_colon = true
ij_php_space_after_colon_in_named_argument = true
ij_php_space_after_colon_in_return_type = true
ij_php_space_after_comma = true
ij_php_space_after_for_semicolon = true
@@ -419,6 +430,7 @@ ij_php_space_before_catch_parentheses = true
ij_php_space_before_class_left_brace = true
ij_php_space_before_closure_left_parenthesis = true
ij_php_space_before_colon = true
ij_php_space_before_colon_in_named_argument = false
ij_php_space_before_colon_in_return_type = false
ij_php_space_before_comma = false
ij_php_space_before_do_left_brace = true
@@ -486,6 +498,7 @@ ij_php_while_on_new_line = false
[{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.stylelintrc,bowerrc,composer.lock,jest.config}]
indent_size = 2
ij_visual_guides = none
ij_json_keep_blank_lines_in_code = 0
ij_json_keep_indents_on_empty_lines = false
ij_json_keep_line_breaks = true
@@ -500,7 +513,8 @@ ij_json_wrap_long_lines = false
[{*.htm,*.html,*.sht,*.shtm,*.shtml}]
indent_style = tab
ij_smart_tabs = true
ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3
ij_visual_guides = none
ij_html_add_new_line_before_tags = body, div, p, form, h1, h2, h3
ij_html_align_attributes = true
ij_html_align_text = false
ij_html_attribute_wrap = normal
@@ -527,10 +541,16 @@ ij_html_space_inside_empty_tag = false
ij_html_text_wrap = normal
ij_html_uniform_ident = false
[{*.yaml,*.yml}]
[{*.yaml, *.yml}]
indent_size = 2
ij_visual_guides = none
ij_yaml_align_values_properties = do_not_align
ij_yaml_autoinsert_sequence_marker = true
ij_yaml_block_mapping_on_new_line = false
ij_yaml_indent_sequence_value = true
ij_yaml_keep_indents_on_empty_lines = false
ij_yaml_keep_line_breaks = true
ij_yaml_space_before_colon = true
ij_yaml_sequence_on_new_line = false
ij_yaml_space_before_colon = false
ij_yaml_spaces_within_braces = true
ij_yaml_spaces_within_brackets = true

View File

@@ -41,10 +41,8 @@ class ajax_page extends WebPage implements iTabbedPage
parent::__construct($s_title, $bPrintable);
$this->m_sReadyScript = "";
//$this->add_header("Content-type: text/html; charset=utf-8");
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
$this->no_cache();
$this->add_xframe_options();
$this->m_oTabs = new TabManager();
$this->sContentType = 'text/html';
$this->sContentDisposition = 'inline';

View File

@@ -3256,16 +3256,15 @@ EOF
*/
public function DisplayDocumentInline(WebPage $oPage, $sAttCode)
{
/** @var \ormDocument $oDoc */
$oDoc = $this->Get($sAttCode);
$sClass = get_class($this);
$Id = $this->GetKey();
switch ($oDoc->GetMainMimeType())
{
switch ($oDoc->GetMainMimeType()) {
case 'text':
case 'html':
$data = $oDoc->GetData();
switch ($oDoc->GetMimeType())
{
switch ($oDoc->GetMimeType()) {
case 'text/xml':
$oPage->add("<iframe id='preview_$sAttCode' src=\"".utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=display_document&class=$sClass&id=$Id&field=$sAttCode\" width=\"100%\" height=\"400\">Loading...</iframe>\n");
break;

View File

@@ -32,10 +32,8 @@ class CSVPage extends WebPage
function __construct($s_title) {
parent::__construct($s_title);
$this->add_header("Content-type: text/plain; charset=".self::PAGES_CHARSET);
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
$this->no_cache();
$this->add_xframe_options();
//$this->add_header("Content-Transfer-Encoding: binary");
}

View File

@@ -459,17 +459,21 @@ EOF
$sAttType = $aTargetAttCodes[$sTargetAttCode];
$sExtFieldAttCode = $sTargetAttCode;
}
if (is_a($sAttType, 'AttributeLinkedSet', true))
{
continue;
}
if (is_a($sAttType, 'AttributeFriendlyName', true))
{
continue;
}
if (is_a($sAttType, 'AttributeOneWayPassword', true))
{
continue;
$aForbidenAttType = [
'AttributeLinkedSet',
'AttributeFriendlyName',
'iAttributeNoGroupBy', //we cannot only use iAttributeNoGroupBy since this method is also used by the designer who do not have access to the classes' PHP reflection API. So the known classes has to be listed altogether
'AttributeOneWayPassword',
'AttributeEncryptedString',
'AttributePassword',
];
foreach ($aForbidenAttType as $sForbidenAttType) {
if (is_a($sAttType, $sForbidenAttType, true))
{
continue 2;
}
}
$sLabel = $this->oModelReflection->GetLabel($sClass, $sAttCode);

View File

@@ -446,8 +446,21 @@ class DisplayBlock
$this->m_oSet = new CMDBObjectSet($this->m_oFilter, $aOrderBy, $aQueryParams);
}
$this->m_oSet->SetShowObsoleteData($this->m_bShowObsoleteData);
switch($this->m_sStyle)
{
switch($this->m_sStyle) {
case 'list_search':
case 'list':
break;
default:
// N°3473: except for 'list_search' and 'list' (which have more granularity, see the other switch below),
// refuse to render if the user is not allowed to see the class.
if (! UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) {
$sHtml .= $oPage->GetP(Dict::Format('UI:Error:ReadNotAllowedOn_Class', $this->m_oSet->GetClass()));
return $sHtml;
}
}
switch ($this->m_sStyle) {
case 'count':
if (isset($aExtraParams['group_by']))
{

View File

@@ -60,8 +60,7 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
// Create a breadcrumb entry for the current page, but get its title as late as possible (page title could be changed later)
$this->bBreadCrumbEnabled = true;
}
else
{
else {
$this->bBreadCrumbEnabled = false;
}
@@ -71,10 +70,8 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
$this->m_aMessages = array();
$this->SetRootUrl(utils::GetAbsoluteUrlAppRoot());
$this->add_header("Content-type: text/html; charset=".self::PAGES_CHARSET);
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
$this->no_cache();
$this->add_xframe_options();
$this->add_linked_stylesheet("../css/jquery.treeview.css");
$this->add_linked_stylesheet("../css/jquery.autocomplete.css");
$this->add_linked_stylesheet("../css/jquery-ui-timepicker-addon.css");

View File

@@ -84,10 +84,8 @@ class LoginWebPage extends NiceWebPage
parent::__construct($sTitle);
$this->SetStyleSheet();
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
$this->no_cache();
$this->add_xframe_options();
}
public function SetStyleSheet()
@@ -687,7 +685,7 @@ class LoginWebPage extends NiceWebPage
public static function HTTPReload()
{
$sOriginURL = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
$sOriginURL = utils::GetCurrentAbsoluteUrl();
if (!utils::StartsWith($sOriginURL, utils::GetAbsoluteUrlAppRoot()))
{
// If the found URL does not start with the configured AppRoot URL

View File

@@ -297,11 +297,19 @@ class privUITransactionFile
* Cleanup old transactions which have been pending since more than 24 hours
* Use filemtime instead of filectime since filectime may be affected by operations on the directory (like changing the access rights)
*/
protected static function CleanupOldTransactions()
protected static function CleanupOldTransactions($sTransactionDir = null)
{
$iLimit = time() - 24*3600;
$iThreshold = (int) MetaModel::GetConfig()->Get('transactions_gc_threshold');
$iThreshold = min(100, $iThreshold);
$iThreshold = max(1, $iThreshold);
if ((100 != $iThreshold) && (rand(1, 100) > $iThreshold)) {
return;
}
clearstatcache();
$aTransactions = glob(APPROOT.'data/transactions/*-*');
$iLimit = time() - 24*3600;
$sPattern = $sTransactionDir ? "$sTransactionDir/*" : APPROOT.'data/transactions/*';
$aTransactions = glob($sPattern);
foreach($aTransactions as $sFileName)
{
if (filemtime($sFileName) < $iLimit)

View File

@@ -782,22 +782,42 @@ class utils
}
}
/**
* @return bool The boolean value of the conf. "behind_reverse_proxy" (except if there is no REMOTE_ADDR int his case, it return false)
*
* @since 2.7.4
*/
public static function IsProxyTrusted()
{
if (empty($_SERVER['REMOTE_ADDR'])) {
return false;
}
$bTrustProxies = (bool) self::GetConfig()->Get('behind_reverse_proxy');
return $bTrustProxies;
}
/**
* Returns the absolute URL to the application root path
*
* @param bool $bForceTrustProxy
*
* @return string The absolute URL to the application root, without the first slash
*
* @throws \Exception
*
* @since 2.7.4 $bForceTrustProxy param added
*/
public static function GetAbsoluteUrlAppRoot()
public static function GetAbsoluteUrlAppRoot($bForceTrustProxy = false)
{
static $sUrl = null;
if ($sUrl === null)
if ($sUrl === null || $bForceTrustProxy)
{
$sUrl = self::GetConfig()->Get('app_root_url');
if ($sUrl == '')
{
$sUrl = self::GetDefaultUrlAppRoot();
$sUrl = self::GetDefaultUrlAppRoot($bForceTrustProxy);
}
elseif (strpos($sUrl, SERVER_NAME_PLACEHOLDER) > -1)
{
@@ -821,31 +841,116 @@ class utils
* For most usages, when an root url is needed, use utils::GetAbsoluteUrlAppRoot() instead as uses this only as a fallback when the
* app_root_url conf parameter is not defined.
*
* @param bool $bForceTrustProxy
*
* @return string
*
* @throws \Exception
*/
public static function GetDefaultUrlAppRoot()
*
* @since 2.7.4 $bForceTrustProxy param added
*/
public static function GetDefaultUrlAppRoot($bForceTrustProxy = false)
{
$sAbsoluteUrl = self::GetCurrentAbsoluteUrl($bForceTrustProxy, true);
$sCurrentScript = realpath($_SERVER['SCRIPT_FILENAME']);
$sAppRoot = realpath(APPROOT);
return self::GetAppRootUrl($sCurrentScript, $sAppRoot, $sAbsoluteUrl);
}
/**
* Build the current absolute URL from the server's variables.
*
* For almost every usage, you should use the more secure utils::GetAbsoluteUrlAppRoot() : instead of reading the current uri, it provide you the configured application's root URL (this is done during the setup and chn be changed in the configuration file)
*
* @see utils::GetAbsoluteUrlAppRoot
*
* @param bool $bForceTrustProxy
* @param bool $bTrimQueryString
*
* @return string
*
* @since 2.7.4
*/
public static function GetCurrentAbsoluteUrl($bForceTrustProxy = false, $bTrimQueryString = false)
{
// Build an absolute URL to this page on this server/port
$sServerName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';
$sProtocol = self::IsConnectionSecure() ? 'https' : 'http';
$iPort = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80;
if ($sProtocol == 'http')
{
$sServerName = self::GetServerName($bForceTrustProxy);
$bIsSecure = self::IsConnectionSecure($bForceTrustProxy);
$sProtocol = $bIsSecure ? 'https' : 'http';
$iPort = self::GetServerPort($bForceTrustProxy);
if ($bIsSecure) {
$sPort = ($iPort == 443) ? '' : ':'.$iPort;
} else {
$sPort = ($iPort == 80) ? '' : ':'.$iPort;
}
else
{
$sPort = ($iPort == 443) ? '' : ':'.$iPort;
$sPath = self::GetRequestUri($bForceTrustProxy);
if ($bTrimQueryString) {
// remove all the parameters from the query string
$iQuestionMarkPos = strpos($sPath, '?');
if ($iQuestionMarkPos !== false) {
$sPath = substr($sPath, 0, $iQuestionMarkPos);
}
}
$sAbsoluteUrl = "$sProtocol://{$sServerName}{$sPort}{$sPath}";
return $sAbsoluteUrl;
}
/**
* @param bool $bForceTrustProxy
*
* @return string
*
* @since 2.7.4
*/
public static function GetServerName($bForceTrustProxy = false)
{
$bTrustProxy = $bForceTrustProxy || self::IsProxyTrusted();
$sServerName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';
if ($bTrustProxy) {
$sServerName = isset($_SERVER['HTTP_X_FORWARDED_HOST']) ? $_SERVER['HTTP_X_FORWARDED_HOST'] : $sServerName;
}
return $sServerName;
}
/**
* @param bool $bForceTrustProxy
*
* @return int|mixed
* @since 2.7.4
*/
public static function GetServerPort($bForceTrustProxy = false)
{
$bTrustProxy = $bForceTrustProxy || self::IsProxyTrusted();
$sServerPort = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80;
if ($bTrustProxy) {
$sServerPort = isset($_SERVER['HTTP_X_FORWARDED_PORT']) ? $_SERVER['HTTP_X_FORWARDED_PORT'] : $sServerPort;
}
return $sServerPort;
}
/**
* @return string
*
* @since 2.7.4
*/
public static function GetRequestUri()
{
// $_SERVER['REQUEST_URI'] is empty when running on IIS
// Let's use Ivan Tcholakov's fix (found on www.dokeos.com)
if (!empty($_SERVER['REQUEST_URI']))
{
$sPath = $_SERVER['REQUEST_URI'];
}
else
if (empty($_SERVER['REQUEST_URI']))
{
$sPath = $_SERVER['SCRIPT_NAME'];
if (!empty($_SERVER['QUERY_STRING']))
@@ -856,18 +961,7 @@ class utils
}
$sPath = $_SERVER['REQUEST_URI'];
// remove all the parameters from the query string
$iQuestionMarkPos = strpos($sPath, '?');
if ($iQuestionMarkPos !== false)
{
$sPath = substr($sPath, 0, $iQuestionMarkPos);
}
$sAbsoluteUrl = "$sProtocol://{$sServerName}{$sPort}{$sPath}";
$sCurrentScript = realpath($_SERVER['SCRIPT_FILENAME']);
$sAppRoot = realpath(APPROOT);
return self::GetAppRootUrl($sCurrentScript, $sAppRoot, $sAbsoluteUrl);
return $sPath;
}
/**
@@ -911,19 +1005,36 @@ class utils
/**
* Helper to handle the variety of HTTP servers
* See N°286 (fixed in [896]), and N°634 (this fix)
*
*
* Though the official specs says 'a non empty string', some servers like IIS do set it to 'off' !
* nginx set it to an empty string
* Others might leave it unset (no array entry)
*/
public static function IsConnectionSecure()
* Others might leave it unset (no array entry)
*
* @param bool $bForceTrustProxy
*
* @return bool
*
* @since 2.7.4 reverse proxies handling
*/
public static function IsConnectionSecure($bForceTrustProxy = false)
{
$bSecured = false;
if (!empty($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS']) != 'off'))
$bTrustProxy = $bForceTrustProxy || self::IsProxyTrusted();
if ($bTrustProxy && !empty($_SERVER['HTTP_X_FORWARDED_PROTO']))
{
$bSecured = ($_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https');
}
elseif ($bTrustProxy && !empty($_SERVER['HTTP_X_FORWARDED_PROTOCOL']))
{
$bSecured = ($_SERVER['HTTP_X_FORWARDED_PROTOCOL'] === 'https');
}
elseif ((!empty($_SERVER['HTTPS'])) && (strtolower($_SERVER['HTTPS']) != 'off'))
{
$bSecured = true;
}
return $bSecured;
}
@@ -2337,4 +2448,13 @@ class utils
$e = new CoreException($sMessage, null, '', $oException);
throw $e;
}
/**
* @return bool : indicate whether we run under a windows environnement or not
* @since 2.7.4 : N°3412
*/
public static function IsWindowsEnvironment(){
return (substr(PHP_OS,0,3) === 'WIN');
}
}

View File

@@ -482,6 +482,23 @@ class WebPage implements Page
$this->a_headers[] = $s_header;
}
/**
* @param string|null $sHeaderValue for example `SAMESITE`. If null will set the header using the config parameter value.
*
* @since 2.7.3 3.0.0 N°3416
* @uses security_header_xframe config parameter
* @uses \utils::GetConfig()
* @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
*/
public function add_xframe_options($sHeaderValue = null)
{
if (is_null($sHeaderValue)) {
$sHeaderValue = utils::GetConfig()->Get('security_header_xframe');
}
$this->add_header('X-Frame-Options: '.$sHeaderValue);
}
/**
* Add needed headers to the page so that it will no be cached
*/
@@ -490,7 +507,6 @@ class WebPage implements Page
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
}
/**

View File

@@ -43,10 +43,8 @@ class XMLPage extends WebPage
$this->m_bPassThrough = $bPassThrough;
$this->m_bHeaderSent = false;
$this->add_header("Content-type: text/xml; charset=".self::PAGES_CHARSET);
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
$this->no_cache();
$this->add_xframe_options();
$this->add_header("Content-location: export.xml");
}

View File

@@ -103,6 +103,14 @@ define('LINKSET_EDITMODE_ACTIONS', 2); // Show the usual 'Actions' popup menu
define('LINKSET_EDITMODE_INPLACE', 3); // The "linked" objects can be created/modified/deleted in place
define('LINKSET_EDITMODE_ADDREMOVE', 4); // The "linked" objects can be added/removed in place
/**
* Attributes implementing this interface won't be accepted as `group by` field
* @since 2.7.4 N°3473
*/
interface iAttributeNoGroupBy
{
//no method, just a contract on implement
}
/**
* Attribute definition API, implemented in and many flavours (Int, String, Enum, etc.)
@@ -3761,7 +3769,7 @@ class AttributeFinalClass extends AttributeString
*
* @package iTopORM
*/
class AttributePassword extends AttributeString
class AttributePassword extends AttributeString implements iAttributeNoGroupBy
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
@@ -3837,7 +3845,7 @@ class AttributePassword extends AttributeString
*
* @package iTopORM
*/
class AttributeEncryptedString extends AttributeString
class AttributeEncryptedString extends AttributeString implements iAttributeNoGroupBy
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
@@ -9217,7 +9225,7 @@ class AttributeSubItem extends AttributeDefinition
/**
* One way encrypted (hashed) password
*/
class AttributeOneWayPassword extends AttributeDefinition
class AttributeOneWayPassword extends AttributeDefinition implements iAttributeNoGroupBy
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;

View File

@@ -1181,17 +1181,24 @@ class CMDBSource
}
/**
* There may have some differences between DB : for example in MySQL 5.7 we have "INT", while in MariaDB >= 10.2 you get "int DEFAULT 'NULL'"
* There may have some differences between DB ! For example in :
* * MySQL 5.7 we have `INT`
* * MariaDB >= 10.2 you get `int DEFAULT 'NULL'`
*
* We still do a case sensitive comparison for enum values !
* We still need to do a case sensitive comparison for enum values !
*
* A better solution would be to generate SQL field definitions ({@link GetFieldSpec} method) based on the DB used... But for
* now (N°2490 / SF #1756 / PR #91) we did implement this simpler solution
*
* @param string $sItopGeneratedFieldType
* @see GetFieldDataTypeAndOptions extracts all info from the SQL field definition
*
* @param string $sDbFieldType
*
* @param string $sItopGeneratedFieldType
*
* @return bool true if same type and options (case sensitive comparison only for type options), false otherwise
*
* @throws \CoreException
* @since 2.7.0 N°2490
*/
public static function IsSameFieldTypes($sItopGeneratedFieldType, $sDbFieldType)
@@ -1239,24 +1246,68 @@ class CMDBSource
}
/**
* @param string $sCompleteFieldType sql field type, for example 'VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0'
* @see \self::GetEnumOptions() specific processing for ENUM fields
*
* @param string $sCompleteFieldType sql field type, for example `VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0`
*
* @return string[] consisting of 3 items :
* 1. data type : for example 'VARCHAR'
* 2. type value : for example '255'
* 3. other options : for example ' CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0'
* 1. data type : for example `VARCHAR`
* 2. type value : for example `255`
* 3. other options : for example `CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0`
*
* @throws \CoreException
*/
private static function GetFieldDataTypeAndOptions($sCompleteFieldType)
{
preg_match('/^([a-zA-Z]+)(\(([^\)]+)\))?( .+)?/', $sCompleteFieldType, $aMatches);
$sDataType = isset($aMatches[1]) ? $aMatches[1] : '';
if (strcasecmp($sDataType, 'ENUM') === 0){
try{
return self::GetEnumOptions($sDataType, $sCompleteFieldType);
}catch(CoreException $e){
//do nothing ; especially do not block setup.
IssueLog::Warning("enum was not parsed properly: $sCompleteFieldType. it should not happen during setup.");
}
}
$sTypeOptions = isset($aMatches[2]) ? $aMatches[3] : '';
$sOtherOptions = isset($aMatches[4]) ? $aMatches[4] : '';
return array($sDataType, $sTypeOptions, $sOtherOptions);
}
/**
* @param string $sDataType for example `ENUM`
* @param string $sCompleteFieldType Example:
* `ENUM('CSP A','CSP (aaaa) M','NA','OEM(ROC)','OPEN(VL)','RETAIL (Boite)') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`
*
* @return string[] consisting of 3 items :
* 1. data type : ENUM or enum here
* 2. type value : in-between EUM parenthesis
* 3. other options : for example ' CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0'
* @throws \CoreException
* @since 2.7.4 N°3065 specific processing for enum fields : fix no alter table when enum values containing parenthesis
* Handle ENUM options
*/
private static function GetEnumOptions($sDataType, $sCompleteFieldType)
{
$iFirstOpeningParenthesis = strpos($sCompleteFieldType, '(');
$iLastEndingParenthesis = strrpos($sCompleteFieldType, ')');
if ($iFirstOpeningParenthesis === false || $iLastEndingParenthesis === false ){
//should never happen as GetFieldDataTypeAndOptions regexp matched.
//except if regexp is modiied/broken somehow one day...
throw new CoreException("GetEnumOptions issue with $sDataType parsing : " . $sCompleteFieldType);
}
$sTypeOptions = substr($sCompleteFieldType, $iFirstOpeningParenthesis + 1, $iLastEndingParenthesis - 1);
$sOtherOptions = substr($sCompleteFieldType, $iLastEndingParenthesis + 1);
return array($sDataType, $sTypeOptions, $sOtherOptions);
}
/**
* @param string $sTable
* @param string $sField

View File

@@ -1065,6 +1065,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'transactions_gc_threshold' => array(
'type' => 'integer',
'description' => 'probability in percent for the garbage collector to be triggered (100 mean always)',
'default' => 10, // added in itop 2.7.4, before the GC was always called
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'log_transactions' => array(
'type' => 'bool',
'description' => 'Whether or not to enable the debug log for the transactions.',
@@ -1249,6 +1257,22 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'security_header_xframe' => [
'type' => 'string',
'description' => 'Value of the X-Frame-Options HTTP header sent by iTop. Possible values : DENY, SAMEORIGIN, or empty string.',
'default' => 'SAMEORIGIN',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'behind_reverse_proxy' => [
'type' => 'bool',
'description' => 'If true, then proxies custom header (X-Forwarded-*) are taken into account. Use only if the webserver is not publicly accessible (reachable only by the reverse proxy)',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => true,
],
);

View File

@@ -1186,6 +1186,30 @@ abstract class DBSearch
}
}
}
if (is_array($aGroupByExpr))
{
foreach($aGroupByExpr as $sAlias => $oGroupByExp)
{
/** @var \Expression $oGroupByExp */
$aFields = $oGroupByExp->ListRequiredFields();
foreach($aFields as $sFieldAlias)
{
$aMatches = array();
if (preg_match('/^([^.]+)\\.([^.]+)$/', $sFieldAlias, $aMatches))
{
$sFieldClass = $this->GetClassName($aMatches[1]);
$oAttDef = MetaModel::GetAttributeDef($sFieldClass, $aMatches[2]);
if ( $oAttDef instanceof iAttributeNoGroupBy)
{
throw new Exception("Grouping on '$sFieldClass' fields is not supported.");
}
}
}
}
}
$oSQLQuery = $oSearch->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr, null, $aSelectExpr);
$oSQLQuery->SetSourceOQL($oSearch->ToOQL());

View File

@@ -2602,5 +2602,18 @@ class DBObjectSearch extends DBSearch
return $oExpression;
}
/**
* @param array $aAttCodes array of attCodes to search into
* @param string $sNeedle one word to be searched
*
* @throws \CoreException
*/
public function AddCondition_FullTextOnAttributes(array $aAttCodes, $sNeedle)
{
}
public function ListParameters()
{
return $this->GetCriteria()->ListParameters();
}
}

View File

@@ -4166,11 +4166,7 @@ abstract class MetaModel
}
if (count($aCurrentUser) > 0)
{
$oSearch = DBObjectSearch::FromOQL("SELECT User WHERE id = :id");
$oSearch->AllowAllData();
$oSet = new DBObjectSet($oSearch, array(), array('id' => UserRights::GetUserId()));
$oSet->OptimizeColumnLoad($aCurrentUser);
$oUser = $oSet->fetch();
$oUser = MetaModel::GetObject("User", UserRights::GetUserId(),true,true);
$aPlaceholders['current_user->object()'] = $oUser;
foreach ($aCurrentUser as $sField)
{
@@ -4179,10 +4175,7 @@ abstract class MetaModel
}
if (count($aCurrentContact) > 0)
{
$oSearch = DBObjectSearch::FromOQL("SELECT Contact WHERE id = :id");
$oSet = new DBObjectSet($oSearch, array(), array('id' => UserRights::GetContactId()));
$oSet->OptimizeColumnLoad(['Contact' => $aCurrentContact]);
$oUser = $oSet->fetch();
$oUser = MetaModel::GetObject("Person", UserRights::GetContactId(),true,true);
foreach ($aCurrentContact as $sField)
{
$aPlaceholders['current_contact->'.$sField] = $oUser->Get($sField);
@@ -5552,15 +5545,10 @@ abstract class MetaModel
// The field already exists, does it have the relevant properties?
//
$bToBeChanged = false;
$sActualFieldSpec = CMDBSource::GetFieldSpec($sTable, $sField);
if (!CMDBSource::IsSameFieldTypes($sDBFieldSpec, $sActualFieldSpec))
{
$bToBeChanged = true;
$aErrors[$sClass][$sAttCode][] = "field '$sField' in table '$sTable' has a wrong type: found <code>$sActualFieldSpec</code> while expecting <code>$sDBFieldSpec</code>";
}
if ($bToBeChanged)
{
$aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` CHANGE `$sField` $sFieldDefinition";
$aAlterTableItems[$sTable][$sField] = "CHANGE `$sField` $sFieldDefinition";
}

View File

@@ -1672,6 +1672,39 @@ class FieldExpression extends UnaryExpression
// Has been resolved into an SQL expression
class FieldExpressionResolved extends FieldExpression
{
protected $m_aAdditionalExpressions;
public function __construct($mExpression, $sParent = '')
{
$this->m_aAdditionalExpressions = array();
if (is_array($mExpression))
{
foreach ($mExpression as $sSuffix => $sExpression)
{
if ($sSuffix == '')
{
$sName = $sExpression;
}
$this->m_aAdditionalExpressions[$sSuffix] = new FieldExpressionResolved($sExpression, $sParent);
}
}
else
{
$sName = $mExpression;
}
parent::__construct($sName, $sParent);
}
/**
* @return array of additional expressions for muti-column attributes
* @since 2.7.4
*/
public function AdditionalExpressions()
{
return $this->m_aAdditionalExpressions;
}
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
}

View File

@@ -331,4 +331,12 @@ class OQLJoin
return $this->sRightField;
}
/**
* @return string
*/
public function GetLeftField()
{
return $this->sLeftField;
}
}

View File

@@ -50,8 +50,15 @@ class OQLClassTreeOptimizer
{
if ($oJoin->IsOutbound())
{
// The join is not used, remove from tree
$oCurrentClassNode->RemoveJoin($sLeftKey, $index);
// If joined class in not the same class than the external key target class
// then the join cannot be removed because it is used to filter the request
$sJoinedClass = $oJoin->GetOOQLClassNode()->GetNodeClass();
$sExtKeyAttCode = $oJoin->GetLeftField();
$oExtKeyAttDef = MetaModel::GetAttributeDef($oCurrentClassNode->GetNodeClass(), $sExtKeyAttCode);
if ($sJoinedClass == $oExtKeyAttDef->GetTargetClass()) {
// The join is not used, remove from tree
$oCurrentClassNode->RemoveJoin($sLeftKey, $index);
}
}
else
{

View File

@@ -178,6 +178,14 @@ class QueryBuilderExpressions
foreach ($this->m_aSelectExpr as $sColAlias => $oExpr)
{
$this->m_aSelectExpr[$sColAlias] = $oExpr->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
if ($this->m_aSelectExpr[$sColAlias] instanceof FieldExpressionResolved)
{
// Split the field with the relevant alias
foreach ($this->m_aSelectExpr[$sColAlias]->AdditionalExpressions() as $sSuffix => $oAdditionalExpr)
{
$this->m_aSelectExpr[$sColAlias.$sSuffix] = $oAdditionalExpr->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
}
}
}
if ($this->m_aGroupByExpr)
{

View File

@@ -199,8 +199,8 @@ EOF
// Integration within MS-Excel web queries + HTTPS + IIS:
// MS-IIS set these header values with no-cache... while Excel fails to do the job if using HTTPS
// Then the fix is to force the reset of header values Pragma and Cache-control
$oPage->add_header("Pragma:", true);
$oPage->add_header("Cache-control:", true);
$oPage->add_header("Pragma:");
$oPage->add_header("Cache-control:");
}
public function GetHeader()

View File

@@ -239,24 +239,16 @@ class SQLObjectQueryBuilder
continue;
}
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
foreach ($oAttDef->GetSQLExpressions() as $sColId => $sSQLExpr)
$oFieldSQLExp = new FieldExpressionResolved($oAttDef->GetSQLExpressions(), $sClassAlias);
/**
* @var string $sPluginClass
* @var iQueryModifier $oQueryModifier
*/
foreach (MetaModel::EnumPlugins('iQueryModifier') as $sPluginClass => $oQueryModifier)
{
if (!empty($sColId))
{
// Multi column attributes
$oBuild->m_oQBExpressions->AddSelect($sSelectedClassAlias.$sAttCode.$sColId, new FieldExpression($sAttCode.$sColId, $sClassAlias));
}
$oFieldSQLExp = new FieldExpressionResolved($sSQLExpr, $sClassAlias);
/**
* @var string $sPluginClass
* @var iQueryModifier $oQueryModifier
*/
foreach (MetaModel::EnumPlugins('iQueryModifier') as $sPluginClass => $oQueryModifier)
{
$oFieldSQLExp = $oQueryModifier->GetFieldExpression($oBuild, $sClass, $sAttCode, $sColId, $oFieldSQLExp, $oBaseSQLQuery);
}
$aTranslation[$sClassAlias][$sAttCode.$sColId] = $oFieldSQLExp;
$oFieldSQLExp = $oQueryModifier->GetFieldExpression($oBuild, $sClass, $sAttCode, '', $oFieldSQLExp, $oBaseSQLQuery);
}
$aTranslation[$sClassAlias][$sAttCode] = $oFieldSQLExp;
}
// Translate the selected columns

View File

@@ -17,17 +17,17 @@
*/
// Beware the version number MUST be enclosed with quotes otherwise v2.3.0 becomes v2 0.3 .0
$version: "v2.7.2";
$version: "v2.7.4";
$approot-relative: "../../../../../" !default; // relative to env-***/branding/themes/***/main.css
// Base colors
$gray-base: #000 !default;
$gray-darker: lighten($gray-base, 13.5%) !default; // #222
$gray-dark: #444 !default;
$gray: #777 !default;
$gray-light: #808080 !default;
$gray-lighter: #ddd !default;
$gray-extra-light: #F1F1F1 !default;
$gray-base: #000 !default;
$gray-darker: lighten($gray-base, 13.5%) !default; // #222
$gray-dark: #444 !default;
$gray: #777 !default;
$gray-light: #808080 !default;
$gray-lighter: #ddd !default;
$gray-extra-light: #F1F1F1 !default;
$white: #FFFFFF !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
'authent-cas/2.7.0',
'authent-cas/2.7.4',
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.0',
'authent-external/2.7.4',
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.0',
'authent-ldap/2.7.4',
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.1',
'authent-local/2.7.4',
array(
// Identification
//

View File

@@ -212,9 +212,9 @@ function DisplayInconsistenciesReport($aResults)
header('Content-Description: File Transfer');
header('Content-Type: multipart/x-zip');
header('Content-Disposition: inline; filename="'.basename($sZipReport).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Expires: 0');
header('Content-Length: '.filesize($sZipReport));
readfile($sZipReport);
unlink($sZipReport);

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.1',
'combodo-db-tools/2.7.4',
array(
// Identification
//

View File

@@ -61,7 +61,6 @@ try
LoginWebPage::DoLoginEx(null /* any portal */, false);
$oPage = new ajax_page("");
$oPage->no_cache();
$sOperation = utils::ReadParam('operation', '');

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.1',
'itop-attachments/2.7.4',
array(
// Identification
//
@@ -80,6 +80,31 @@ if (!class_exists('AttachmentInstaller'))
return $oConfiguration;
}
/**
* @since 2.7.4 N°3788
* @param string $sTableName
* @param int $iBulkSize
*
* @return array
* @throws \CoreException
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
*/
public static function GetOrphanAttachmentIds($sTableName, $iBulkSize){
$sSqlQuery = <<<SQL
SELECT id as attachment_id FROM `$sTableName` WHERE (`item_id`='' OR `item_id` IS NULL) LIMIT {$iBulkSize};
SQL;
/** @var \mysqli_result $oQueryResult */
$oQueryResult = CMDBSource::Query($sSqlQuery);
$aIds = [];
while($aRow = $oQueryResult->fetch_array()){
$aIds[] = $aRow['attachment_id'];
}
return $aIds;
}
/**
* Handler called before creating or upgrading the database schema
* @param $oConfiguration Config The new configuration of the application
@@ -97,10 +122,32 @@ if (!class_exists('AttachmentInstaller'))
$iCount = CMDBSource::QueryToScalar($sCountQuery);
if ($iCount > 0)
{
SetupPage::log_info("Cleanup of orphan attachments that cannot be migrated to the new ObjKey model: $iCount record(s) must be deleted.");
$sRepairQuery = "DELETE FROM `$sTableName` WHERE (`item_id`='' OR `item_id` IS NULL)";
$iRet = CMDBSource::Query($sRepairQuery); // Throws an exception in case of error
SetupPage::log_info("Cleanup of orphan attachments successfully completed.");
SetupPage::log_info("Cleanup of orphan attachments that cannot be migrated to the new ObjKey model: $iCount record(s) must be deleted.");
$iBulkSize = 100;
$iMaxDuration = 30;
$iDeletedCount = 0;
$fStartTime = microtime(true);
$aIds = self::GetOrphanAttachmentIds($sTableName, $iBulkSize);
while (count($aIds) !== 0) {
$sCleanupQuery = sprintf("DELETE FROM `$sTableName` WHERE `id` IN (%s)", implode(",", $aIds));
CMDBSource::Query($sCleanupQuery); // Throws an exception in case of error
$iDeletedCount += count($aIds);
$fElapsed = microtime(true) - $fStartTime;
if ($fElapsed > $iMaxDuration){
SetupPage::log_info(sprintf("Cleanup of orphan attachments interrupted after %.3f s. $iDeletedCount records were deleted among $iCount.", $fElapsed));
break;
}
$aIds = self::GetOrphanAttachmentIds($sTableName, $iBulkSize);
}
if (count($aIds) === 0){
SetupPage::log_info("Cleanup of orphan attachments successfully completed.");
}
}
else
{
@@ -108,7 +155,7 @@ if (!class_exists('AttachmentInstaller'))
}
}
}
/**
* Handler called after the creation/update of the database schema
* @param $oConfiguration Config The new configuration of the application
@@ -123,6 +170,7 @@ if (!class_exists('AttachmentInstaller'))
// Prerequisite: change null into 0 (workaround to the fact that we cannot use IS NULL in OQL)
SetupPage::log_info("Initializing attachment/item_org_id - null to zero");
$sTableName = MetaModel::DBGetTable('Attachment');
$sRepair = "UPDATE `$sTableName` SET `item_org_id` = 0 WHERE `item_org_id` IS NULL";
CMDBSource::Query($sRepair);
@@ -132,7 +180,13 @@ if (!class_exists('AttachmentInstaller'))
$iUpdated = 0;
while ($oAttachment = $oSet->Fetch())
{
if (empty($oAttachment->Get('item_class'))) {
//do not treat orphan attachment
continue;
}
$oContainer = MetaModel::GetObject($oAttachment->Get('item_class'), $oAttachment->Get('item_id'), false /* must be found */, true /* allow all data */);
if ($oContainer)
{
$oAttachment->SetItem($oContainer, true /*updateonchange*/);

View File

@@ -51,7 +51,6 @@ function DisplayErrorAndDie($oPage, $sHtmlErrorMessage, $exitCode = null)
$sOperation = utils::ReadParam('operation', '');
$oPage = new ajax_page('');
$oPage->no_cache();
$oPage->SetContentType('text/html');

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.1',
'itop-backup/2.7.4',
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.0',
'itop-bridge-virtualization-storage/2.7.4',
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.0',
'itop-change-mgmt-itil/2.7.4',
array(
// Identification
//

View File

@@ -237,9 +237,9 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
'Class:NormalChange' => 'Normale Change',
'Class:NormalChange+' => '',
'Class:NormalChange/Attribute:acceptance_date' => 'Datum goedkeuring',
'Class:NormalChange/Attribute:acceptance_date' => 'Datum acceptatie',
'Class:NormalChange/Attribute:acceptance_date+' => '',
'Class:NormalChange/Attribute:acceptance_comment' => 'Commentaar goedkeuring',
'Class:NormalChange/Attribute:acceptance_comment' => 'Commentaar acceptatie',
'Class:NormalChange/Attribute:acceptance_comment+' => '',
'Class:NormalChange/Stimulus:ev_validate' => 'Valideer',
'Class:NormalChange/Stimulus:ev_validate+' => '',

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.0',
'itop-change-mgmt/2.7.4',
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.1',
'itop-config-mgmt/2.7.4',
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.0',
'itop-config/2.7.4',
array(
// Identification
//

View File

@@ -75,6 +75,7 @@ Dict::Add('EN US', 'English', 'English', array(
'iTopUpdate:UI:CanCoreUpdate:Yes' => 'Application can be updated',
'iTopUpdate:UI:CanCoreUpdate:No' => 'Application cannot be updated: %1$s',
'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s',
'iTopUpdate:UI:CannotUpdateUseSetup' => 'You must use the <a href="%1$s">setup</a> to update the application.<br />Some modified files were detected, a partial update cannot be executed.',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => 'Ready to start',

View File

@@ -75,6 +75,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'iTopUpdate:UI:CanCoreUpdate:Yes' => 'L\'application peut être mise à jour',
'iTopUpdate:UI:CanCoreUpdate:No' => 'L\'application ne peut pas être mise à jour : %1$s',
'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Attention : la mise à jour de l\'application peut échouer : %1$s',
'iTopUpdate:UI:CannotUpdateUseSetup' => 'Vous devez utiliser la page <a href="%1$s">d\'installation</a> pour mettre à jour l\'application.<br />Des fichiers modifiés ont été détectés, une mise à jour partielle ne peut pas être effectuée.',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => 'Prêt pour l\\installation',

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.1',
'itop-core-update/2.7.4',
array(
// Identification
//

View File

@@ -37,7 +37,8 @@ class AjaxController extends Controller
}
else
{
$aParams['sMessage'] = Dict::Format("iTopUpdate:UI:CanCoreUpdate:{$sCanUpdateCore}", $sMessage);
$sLink = utils::GetAbsoluteUrlAppRoot().'setup/';
$aParams['sMessage'] = Dict::Format('iTopUpdate:UI:CannotUpdateUseSetup', $sLink);
}
} catch (FileNotExistException $e)
{

View File

@@ -54,7 +54,7 @@
</fieldset>
<fieldset>
<fieldset id="form-update-outer">
<legend>{{ 'iTopUpdate:UI:SelectUpdateFile'|dict_s }}</legend>
<div class="details">

View File

@@ -22,6 +22,9 @@ $.ajax({
}
else
{
$("#check-update").prop("disabled", true);
$("#file").prop("disabled", true);
$('#form-update-outer').slideUp(600);
oRequirements.addClass("message_error");
}
}

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.0',
'itop-datacenter-mgmt/2.7.4',
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.0',
'itop-endusers-devices/2.7.4',
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.1',
'itop-files-information/2.7.4',
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.0',
'itop-full-itil/2.7.4',
array(
// Identification
//

View File

@@ -111,7 +111,6 @@ function DoBackup($sTargetFile)
function ReportStatus($sMessage, $bSuccess, $iErrorCode = 0, $aMoreFields = array())
{
$oPage = new ajax_page("");
$oPage->no_cache();
$oPage->SetContentType('application/json');
$aResult = array(
'code' => $iErrorCode,

View File

@@ -4,20 +4,18 @@ class HubConnectorPage extends NiceWebPage
{
public function __construct($sTitle)
{
parent::__construct($sTitle);
parent::__construct($sTitle);
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
$this->no_cache();
$this->add_xframe_options();
$sImagesDir = utils::GetAbsoluteUrlAppRoot().'images';
$sModuleImagesDir = utils::GetAbsoluteUrlModulesRoot().'itop-hub-connector/images';
$sUserPrefs = appUserPreferences::GetAsJSON();
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/utils.js');
$this->add_script(
<<<EOF
$sImagesDir = utils::GetAbsoluteUrlAppRoot().'images';
$sModuleImagesDir = utils::GetAbsoluteUrlModulesRoot().'itop-hub-connector/images';
$sUserPrefs = appUserPreferences::GetAsJSON();
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/utils.js');
$this->add_script(
<<<EOF
var oUserPreferences = $sUserPrefs;
EOF
);

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.0',
'itop-hub-connector/2.7.4',
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.0',
'itop-incident-mgmt-itil/2.7.4',
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.0',
'itop-knownerror-mgmt/2.7.4',
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.1', array(
'itop-portal-base/2.7.4', array(
// Identification
'label' => 'Portal Development Library',
'category' => 'Portal',

View File

@@ -33,6 +33,7 @@ $(function()
category: 'redirect',
url: null,
modal: false,
timeout_duration: 400,
},
cancel_rule: {
category: 'close',
@@ -56,7 +57,7 @@ $(function()
this.options.submit_rule.url = this.options.submit_url;
if((this.options.cancel_url !== null) && (this.options.cancel_url !== ''))
this.options.cancel_rule.url = this.options.cancel_url;
this._super();
},
@@ -143,12 +144,14 @@ $(function()
// Determine where we go in case validation is successful
var sRuleType = me.options.submit_rule.category;
var bRedirectInModal = me.options.submit_rule.modal;
var iRedirectTimeout = me.options.submit_rule.timeout_duration;
var sRedirectUrl = me.options.submit_rule.url;
// - The validation might want us to be redirect elsewhere
if(oValidation.valid)
{
// Checking if we have to redirect to another page
// Typically this happens when applying a stimulus, we redirect to the transition form
// This code let the ajax response override the initial parameters
if(oValidation.redirection !== undefined)
{
var oRedirection = oValidation.redirection;
@@ -160,6 +163,10 @@ $(function()
{
sRedirectUrl = oRedirection.url;
}
if(oRedirection.timeout_duration !== undefined)
{
iRedirectTimeout = oRedirection.timeout_duration;
}
sRuleType = 'redirect';
}
}
@@ -241,7 +248,7 @@ $(function()
// Checking if we have to redirect to another page
if(sRuleType === 'redirect')
{
me._applyRedirectRule(sRedirectUrl, bRedirectInModal);
me._applyRedirectRule(sRedirectUrl, bRedirectInModal, iRedirectTimeout);
}
// Close rule only needs to be applied to non modal forms (modal is always closed on submit)
else if(sRuleType === 'close')
@@ -360,10 +367,13 @@ $(function()
{
$('#page_overlay').fadeOut(200);
},
_applyRedirectRule: function(sRedirectUrl, bRedirectInModal)
_applyRedirectRule: function(sRedirectUrl, bRedirectInModal, iRedirectTimeout)
{
var me = this;
//optional argument
iRedirectTimeout = (typeof iRedirectTimeout !== 'undefined' && iRedirectTimeout != null ) ? iRedirectTimeout : 400;
// Always close current modal
if(this.options.is_modal)
{
@@ -391,8 +401,8 @@ $(function()
// Showing loader while redirecting, otherwise user tend to click somewhere in the page.
// Note: We use a timeout because .always() is called right after here and will hide the loader
setTimeout(function(){ me._disableFormBeforeLoading(); }, 50);
// Redirecting after a few ms so the user can see what happend
setTimeout(function() { location.href = sRedirectUrl; }, 400);
// Redirecting after a few ms so the user can see what happened
setTimeout(function() { location.href = sRedirectUrl; }, iRedirectTimeout);
}
}
},

View File

@@ -106,7 +106,11 @@ var CombodoPortalToolbox = {
if (oOptions.base_modal.usage === 'clone')
{
oModalElem = oSelectorElem.clone();
oModalElem.attr('id', oOptions.id)
// Force modal to have an HTML ID, otherwise it can lead to complications, especially with the portal_leave_handle.js
// See N°3469
var sModalID = (oOptions.id !== null) ? oOptions.id : 'modal-with-generated-id-'+Date.now();
oModalElem.attr('id', sModalID)
.appendTo('body');
}
// - Get an existing modal in the DOM

View File

@@ -252,6 +252,7 @@ class ManageBrickController extends BrickController
$oExporter->SetObjectList($oSearch);
$oExporter->SetFormat($sFormat);
$oExporter->SetChunkSize(EXPORTER_DEFAULT_CHUNK_SIZE);
$oExporter->SetLocalizeOutput(true);
$oExporter->SetFields($sFields);
$aData = array(

View File

@@ -211,6 +211,7 @@ class UserProfileBrickController extends BrickController
{
$aFormData['validation']['redirection'] = array(
'url' => $oUrlGenerator->generate('p_user_profile_brick'),
'timeout_duration' => 1000, //since there are several ajax request, we use a longer timeout in hope that they will all be finished in time. A promise would have been more reliable, but since this change is made in a minor version, this approach is less error prone.
);
}
}

View File

@@ -1048,6 +1048,26 @@ class ObjectFormManager extends FormManager
$this->CancelAttachments();
}
/**
* @inheritDoc
*/
public function CheckTransaction(&$aData)
{
$isTransactionValid = \utils::IsTransactionValid($this->oForm->GetTransactionId(), false); //The transaction token is kept in order to preserve BC with ajax forms (the second call would fail if the token is deleted). (The GC will take care of cleaning the token for us later on)
if (!$isTransactionValid) {
if ($this->oObject->IsNew()) {
$sError = Dict::S('UI:Error:ObjectAlreadyCreated');
} else {
$sError = Dict::S('UI:Error:ObjectAlreadyUpdated');
}
$aData['messages']['error'] += [
'_main' => [$sError]
];
$aData['valid'] = false;
}
}
/**
* Validates the form and returns an array with the validation status and the messages.
* If the form is valid, creates/updates the object.
@@ -1070,124 +1090,116 @@ class ObjectFormManager extends FormManager
*/
public function OnSubmit($aArgs = null)
{
$aData = array(
'valid' => true,
'messages' => array(
'success' => array(),
'warnings' => array(), // Not used as of today, just to show that the structure is ready for change like this.
'error' => array(),
),
);
$aData = parent::OnSubmit($aArgs);
if (! $aData['valid']) {
return $aData;
}
// Update object and form
$this->OnUpdate($aArgs);
// Check if form valid
if ($this->oForm->Validate())
{
// The try catch is essentially to start a MySQL transaction in order to ensure that all or none objects are persisted when creating an object with links
try
{
$sObjectClass = get_class($this->oObject);
// Starting transaction
CMDBSource::Query('START TRANSACTION');
// Forcing allowed writing on the object if necessary. This is used in some particular cases.
$bAllowWrite = ($sObjectClass === 'Person' && $this->oObject->GetKey() == UserRights::GetContactId());
if ($bAllowWrite)
{
$this->oObject->AllowWrite(true);
}
// Writing object to DB
$bActivateTriggers = (!$this->oObject->IsNew() && $this->oObject->IsModified());
$bWasModified = $this->oObject->IsModified();
try
{
$this->oObject->DBWrite();
}
catch (CoreCannotSaveObjectException $e)
{
throw new Exception($e->getHtmlMessage());
}
// Finalizing images link to object, otherwise it will be cleaned by the GC
InlineImage::FinalizeInlineImages($this->oObject);
// Finalizing attachments link to object
// TODO : This has to be refactored when the function from itop-attachments has been migrated into the core
if (isset($aArgs['attachmentIds']))
{
$this->FinalizeAttachments($aArgs['attachmentIds']);
}
// Ending transaction with a commit as everything was fine
CMDBSource::Query('COMMIT');
// Checking if we have to apply a stimulus
if (isset($aArgs['applyStimulus']))
{
$this->oObject->ApplyStimulus($aArgs['applyStimulus']['code']);
}
// Activating triggers only on update
if ($bActivateTriggers)
{
$sTriggersQuery = $this->oContainer->getParameter('combodo.portal.instance.conf')['properties']['triggers_query'];
if ($sTriggersQuery !== null)
{
$aParentClasses = MetaModel::EnumParentClasses($sObjectClass, ENUM_PARENT_CLASSES_ALL);
$oTriggerSet = new DBObjectSet(DBObjectSearch::FromOQL($sTriggersQuery), array(),
array('parent_classes' => $aParentClasses));
/** @var \Trigger $oTrigger */
while ($oTrigger = $oTriggerSet->Fetch())
{
try
{
$oTrigger->DoActivate($this->oObject->ToArgs('this'));
}
catch(Exception $e)
{
utils::EnrichRaisedException($oTrigger, $e);
}
}
}
}
// Resetting caselog fields value, otherwise the value will stay in it after submit.
$this->oForm->ResetCaseLogFields();
if ($bWasModified)
{
//=if (isNew) because $bActivateTriggers = (!$this->oObject->IsNew() && $this->oObject->IsModified())
if(!$bActivateTriggers)
{
$aData['messages']['success'] += array( '_main' => array(Dict::Format('UI:Title:Object_Of_Class_Created', $this->oObject->GetName(),MetaModel::GetName(get_class($this->oObject)))));
}
else
{
$aData['messages']['success'] += array('_main' => array(Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($this->oObject)), $this->oObject->GetName())));
}
}
}
catch (Exception $e)
{
// End transaction with a rollback as something failed
CMDBSource::Query('ROLLBACK');
$aData['valid'] = false;
$aData['messages']['error'] += array('_main' => array($e->getMessage()));
IssueLog::Error(__METHOD__.' at line '.__LINE__.' : Rollback during submit ('.$e->getMessage().')');
}
finally
{
// Removing transaction id
utils::RemoveTransaction($this->oForm->GetTransactionId());
}
}
else
if (! $this->oForm->Validate())
{
// Handle errors
$aData['valid'] = false;
$aData['messages']['error'] += $this->oForm->GetErrorMessages();
return $aData;
}
// The try catch is essentially to start a MySQL transaction in order to ensure that all or none objects are persisted when creating an object with links
try
{
$sObjectClass = get_class($this->oObject);
// Starting transaction
CMDBSource::Query('START TRANSACTION');
// Forcing allowed writing on the object if necessary. This is used in some particular cases.
$bAllowWrite = ($sObjectClass === 'Person' && $this->oObject->GetKey() == UserRights::GetContactId());
if ($bAllowWrite)
{
$this->oObject->AllowWrite(true);
}
// Writing object to DB
$bActivateTriggers = (!$this->oObject->IsNew() && $this->oObject->IsModified());
$bWasModified = $this->oObject->IsModified();
try
{
$this->oObject->DBWrite();
}
catch (CoreCannotSaveObjectException $e)
{
throw new Exception($e->getHtmlMessage());
}
// Finalizing images link to object, otherwise it will be cleaned by the GC
InlineImage::FinalizeInlineImages($this->oObject);
// Finalizing attachments link to object
// TODO : This has to be refactored when the function from itop-attachments has been migrated into the core
if (isset($aArgs['attachmentIds']))
{
$this->FinalizeAttachments($aArgs['attachmentIds']);
}
// Ending transaction with a commit as everything was fine
CMDBSource::Query('COMMIT');
// Checking if we have to apply a stimulus
if (isset($aArgs['applyStimulus']))
{
$this->oObject->ApplyStimulus($aArgs['applyStimulus']['code']);
}
// Activating triggers only on update
if ($bActivateTriggers)
{
$sTriggersQuery = $this->oContainer->getParameter('combodo.portal.instance.conf')['properties']['triggers_query'];
if ($sTriggersQuery !== null)
{
$aParentClasses = MetaModel::EnumParentClasses($sObjectClass, ENUM_PARENT_CLASSES_ALL);
$oTriggerSet = new DBObjectSet(DBObjectSearch::FromOQL($sTriggersQuery), array(),
array('parent_classes' => $aParentClasses));
/** @var \Trigger $oTrigger */
while ($oTrigger = $oTriggerSet->Fetch())
{
try
{
$oTrigger->DoActivate($this->oObject->ToArgs('this'));
}
catch(Exception $e)
{
utils::EnrichRaisedException($oTrigger, $e);
}
}
}
}
// Resetting caselog fields value, otherwise the value will stay in it after submit.
$this->oForm->ResetCaseLogFields();
if ($bWasModified)
{
//=if (isNew) because $bActivateTriggers = (!$this->oObject->IsNew() && $this->oObject->IsModified())
if(!$bActivateTriggers)
{
$aData['messages']['success'] += array( '_main' => array(Dict::Format('UI:Title:Object_Of_Class_Created', $this->oObject->GetName(),MetaModel::GetName(get_class($this->oObject)))));
}
else
{
$aData['messages']['success'] += array('_main' => array(Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($this->oObject)), $this->oObject->GetName())));
}
}
}
catch (Exception $e)
{
// End transaction with a rollback as something failed
CMDBSource::Query('ROLLBACK');
$aData['valid'] = false;
$aData['messages']['error'] += array('_main' => array($e->getMessage()));
IssueLog::Error(__METHOD__.' at line '.__LINE__.' : Rollback during submit ('.$e->getMessage().')');
}
return $aData;
}

View File

@@ -48,6 +48,8 @@ class PasswordFormManager extends FormManager
// Building the form
$oForm = new Form('change_password');
$oForm->SetTransactionId(\utils::GetNewTransactionId());
// Adding hidden field with form type
$oField = new HiddenField('form_type');
$oField->SetCurrentValue('change_password');
@@ -93,14 +95,11 @@ class PasswordFormManager extends FormManager
*/
public function OnSubmit($aArgs = null)
{
$aData = array(
'valid' => true,
'messages' => array(
'success' => array(),
'warnings' => array(), // Not used as of today, just to show that the structure is ready for change like this.
'error' => array(),
),
);
$aData = parent::OnSubmit($aArgs);
if (! $aData['valid']) {
return $aData;
}
// Update object and form
$this->OnUpdate($aArgs);

View File

@@ -49,6 +49,8 @@ class PreferencesFormManager extends FormManager
// Building the form
$oForm = new Form('preferences');
$oForm->SetTransactionId(\utils::GetNewTransactionId());
// Adding hidden field with form type
$oField = new HiddenField('form_type');
$oField->SetCurrentValue('preferences');
@@ -97,14 +99,11 @@ class PreferencesFormManager extends FormManager
*/
public function OnSubmit($aArgs = null)
{
$aData = array(
'valid' => true,
'messages' => array(
'success' => array(),
'warnings' => array(), // Not used as of today, just to show that the structure is ready for change like this.
'error' => array(),
),
);
$aData = parent::OnSubmit($aArgs);
if (! $aData['valid']) {
return $aData;
}
// Update object and form
$this->OnUpdate($aArgs);

View File

@@ -227,16 +227,10 @@
$('#user-profile-wrapper .form_field .help-block > p').remove();
// Submiting contact form through AJAX
//if($('#{{ oContactForm.id }} .field_set').field_set('hasTouchedFields'))
//{
$('#{{ oContactForm.id }}').portal_form_handler('submit', oEvent);
//}
$('#{{ oContactForm.id }}').portal_form_handler('submit', oEvent);
// Submiting preferences form through AJAX
//if($('#{{ oPreferencesForm.id }} .field_set').field_set('hasTouchedFields'))
//{
$('#{{ oPreferencesForm.id }}').portal_form_handler('submit', oEvent);
//}
$('#{{ oPreferencesForm.id }}').portal_form_handler('submit', oEvent);
{% if oPasswordForm is not null %}
// Submiting password form through AJAX

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.0', array(
'itop-portal/2.7.4', 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.0',
'itop-problem-mgmt/2.7.4',
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.0',
'itop-profiles-itil/2.7.4',
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.0',
'itop-request-mgmt-itil/2.7.4',
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.0',
'itop-request-mgmt/2.7.4',
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.1',
'itop-service-mgmt-provider/2.7.4',
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.1',
'itop-service-mgmt/2.7.4',
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.0',
'itop-sla-computation/2.7.4',
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.0',
'itop-storage-mgmt/2.7.4',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__,
'itop-tickets/2.7.0',
'itop-tickets/2.7.4',
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.0',
'itop-virtualization-mgmt/2.7.4',
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.1',
'itop-welcome-itil/2.7.4',
array(
// Identification
//

View File

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

View File

@@ -1045,4 +1045,14 @@ Dict::Add('EN US', 'English', 'English', array(
'Class:AsyncTask/Attribute:event_id+' => '',
'Class:AsyncTask/Attribute:finalclass' => 'Final class',
'Class:AsyncTask/Attribute:finalclass+' => '',
'Class:AsyncTask/Attribute:status' => 'Status',
'Class:AsyncTask/Attribute:status+' => '',
'Class:AsyncTask/Attribute:remaining_retries' => 'Remaining retries',
'Class:AsyncTask/Attribute:remaining_retries+' => '',
'Class:AsyncTask/Attribute:last_error_code' => 'Last error code',
'Class:AsyncTask/Attribute:last_error_code+' => '',
'Class:AsyncTask/Attribute:last_error' => 'Last error',
'Class:AsyncTask/Attribute:last_error+' => '',
'Class:AsyncTask/Attribute:last_attempt' => 'Last attempt',
'Class:AsyncTask/Attribute:last_attempt+' => '',
));

View File

@@ -457,6 +457,7 @@ Dict::Add('EN US', 'English', 'English', array(
'UI:Error:ObjectsAlreadyDeleted' => 'Error: objects have already been deleted!',
'UI:Error:BulkDeleteNotAllowedOn_Class' => 'You are not allowed to perform a bulk delete of objects of class %1$s',
'UI:Error:DeleteNotAllowedOn_Class' => 'You are not allowed to delete objects of class %1$s',
'UI:Error:ReadNotAllowedOn_Class' => 'You are not allowed to view objects of class %1$s',
'UI:Error:BulkModifyNotAllowedOn_Class' => 'You are not allowed to perform a bulk update of objects of class %1$s',
'UI:Error:ObjectAlreadyCloned' => 'Error: the object has already been cloned!',
'UI:Error:ObjectAlreadyCreated' => 'Error: the object has already been created!',
@@ -465,6 +466,7 @@ Dict::Add('EN US', 'English', 'English', array(
'UI:Error:InvalidDashboard' => 'Error: invalid dashboard',
'UI:Error:MaintenanceMode' => 'Application is currently in maintenance',
'UI:Error:MaintenanceTitle' => 'Maintenance',
'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)',
'UI:GroupBy:Count' => 'Count',
'UI:GroupBy:Count+' => 'Number of elements',
@@ -1559,6 +1561,8 @@ When associated with a trigger, each action is given an "order" number, specifyi
'UI:Search:Criteria:Raw:Filtered' => 'Filtered',
'UI:Search:Criteria:Raw:FilteredOn' => 'Filtered on %1$s',
'UI:StateChanged' => 'State changed',
));
//

View File

@@ -1043,6 +1043,18 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Class:AsyncTask/Attribute:event_id+' => '',
'Class:AsyncTask/Attribute:finalclass' => 'Sous-classe de tâche asynchrone',
'Class:AsyncTask/Attribute:finalclass+' => '',
'Class:AsyncTask/Attribute:status' => 'Statut',
'Class:AsyncTask/Attribute:status+' => '',
'Class:AsyncTask/Attribute:remaining_retries' => 'Essais restants',
'Class:AsyncTask/Attribute:remaining_retries+' => '',
'Class:AsyncTask/Attribute:last_error_code' => 'Dernier code d\'erreur',
'Class:AsyncTask/Attribute:last_error_code+' => '',
'Class:AsyncTask/Attribute:last_error' => 'Dernière erreur',
'Class:AsyncTask/Attribute:last_error+' => '',
'Class:AsyncTask/Attribute:last_attempt' => 'Dernière tentative',
'Class:AsyncTask/Attribute:last_attempt+' => '',
));
// Additional language entries not present in English dict

View File

@@ -439,7 +439,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
'UI:Error:ObjectCannotBeUpdated' => 'Erreur: l\'objet ne peut pas être mis à jour.',
'UI:Error:ObjectsAlreadyDeleted' => 'Erreur: les objets ont déjà été supprimés !',
'UI:Error:BulkDeleteNotAllowedOn_Class' => 'Vous n\'êtes pas autorisé à faire une suppression massive sur les objets de type %1$s',
'UI:Error:DeleteNotAllowedOn_Class' => 'Vous n\'êtes pas autorisé supprimer des objets de type %1$s',
'UI:Error:DeleteNotAllowedOn_Class' => 'Vous n\'êtes pas autorisé à supprimer des objets de type %1$s',
'UI:Error:ReadNotAllowedOn_Class' => 'Vous n\'êtes pas autorisé à voir des objets de type %1$s',
'UI:Error:BulkModifyNotAllowedOn_Class' => 'Vous n\'êtes pas autorisé à faire une modification massive sur les objets de type %1$s',
'UI:Error:ObjectAlreadyCloned' => 'Erreur: l\'objet a déjà été dupliqué !',
'UI:Error:ObjectAlreadyCreated' => 'Erreur: l\'objet a déjà été créé !',
@@ -448,6 +449,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'UI:Error:InvalidDashboard' => 'Erreur: Le tableau de bord est invalide',
'UI:Error:MaintenanceMode' => 'L\'application est en maintenance',
'UI:Error:MaintenanceTitle' => 'Maintenance',
'UI:Error:InvalidToken' => 'Erreur: l\'opération a déjà été effectuée (CSRF token not found)',
'UI:GroupBy:Count' => 'Nombre',
'UI:GroupBy:Count+' => 'Nombre d\'éléments',
@@ -1539,6 +1541,8 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
'UI:Search:Criteria:Raw:Filtered' => 'Filtré',
'UI:Search:Criteria:Raw:FilteredOn' => 'Filtré sur %1$s',
'UI:StateChanged' => 'Etat modifié',
));
//

View File

@@ -813,7 +813,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
'UI:Delete:WillBeDeletedAutomatically' => 'Zal automatisch verwijderd worden',
'UI:Delete:MustBeDeletedManually' => 'Moet handmatig verwijderd worden',
'UI:Delete:CannotUpdateBecause_Issue' => 'Zou automatisch moeten geüpdatet worden, maar: %1$s',
'UI:Delete:WillAutomaticallyUpdate_Fields' => 'Zal automatisch aangeapst worden (reset: %1$s)',
'UI:Delete:WillAutomaticallyUpdate_Fields' => 'Zal automatisch aangepast worden (reset: %1$s)',
'UI:Delete:Count_Objects/LinksReferencing_Object' => '%1$d objecten/links verwijzen naar %2$s',
'UI:Delete:Count_Objects/LinksReferencingTheObjects' => '%1$d objecten/links verwijzen naar sommige objecten die verwijderd worden',
'UI:Delete:ReferencesMustBeDeletedToEnsureIntegrity' => 'Elke verdere verwijzing moet verwijderd worden om de integriteit van de database te verzekeren',

View File

@@ -192,7 +192,7 @@ function activateFirstTabWithError(sFormId) {
if ($fieldsWithError.length > 0)
{
$tabsContainer.tabs("option", "active", index);
return;
return false;
}
});
}

View File

@@ -1210,7 +1210,7 @@ HTML
$sOrigState = utils::ReadPostedParam('obj_state_orig', '');
if ($sTargetState != $sOrigState)
{
$aWarnings[] = 'State changed';
$aWarnings[] = Dict::S('UI:StateChanged');
}
$oObj->Set($sStateAttCode, $sTargetState);
}

View File

@@ -242,7 +242,6 @@ try
{
case 'parser_preview':
$oPage = new ajax_page("");
$oPage->no_cache();
$oPage->SetContentType('text/html');
$sSeparator = utils::ReadParam('separator', ',', false, 'raw_data');
if ($sSeparator == 'tab') $sSeparator = "\t";

View File

@@ -38,7 +38,6 @@ try
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
$oPage = new ajax_page("");
$oPage->no_cache();
$operation = utils::ReadParam('operation', '');
$sClass = utils::ReadParam('class', 'MissingAjaxParam', false, 'class');
@@ -62,9 +61,14 @@ try
ormDocument::DownloadDocument($oPage, $sClass, $id, $sField, 'attachment');
if ($iCacheSec > 0)
{
$oPage->add_header("Expires: "); // Reset the value set in ajax_page
$oPage->add_header("Cache-Control: no-transform,public,max-age=$iCacheSec,s-maxage=$iCacheSec");
$oPage->add_header("Pragma: cache"); // Reset the value set .... where ?
$oPage->add_header("Expires: "); // Reset the value set in ajax_page
// X-Frame http header : set in page constructor, but we need to allow frame integration for this specific page
// so we're resetting its value ! (see N°3416)
$oPage->add_xframe_options('');
$oPage->add_header("Last-Modified: Wed, 15 Jun 2015 13:21:15 GMT"); // An arbitrary date in the past is ok
}
}
@@ -76,12 +80,16 @@ try
$id = utils::ReadParam('id', '');
$sSecret = utils::ReadParam('s', '');
$iCacheSec = 31556926; // One year ahead: an inline image cannot change
if (!empty($id) && !empty($sSecret))
{
if (!empty($id) && !empty($sSecret)) {
ormDocument::DownloadDocument($oPage, 'InlineImage', $id, 'contents', 'inline', 'secret', $sSecret);
$oPage->add_header("Expires: "); // Reset the value set in ajax_page
$oPage->add_header("Cache-Control: no-transform,public,max-age=$iCacheSec,s-maxage=$iCacheSec");
$oPage->add_header("Pragma: cache"); // Reset the value set .... where ?
$oPage->add_header("Expires: "); // Reset the value set in ajax_page
// X-Frame http header : set in page constructor, but we need to allow frame integration for this specific page
// so we're resetting its value ! (see N°3416)
$oPage->add_xframe_options('');
$oPage->add_header("Last-Modified: Wed, 15 Jun 2016 13:21:15 GMT"); // An arbitrary date in the past is ok
}
break;
@@ -92,6 +100,11 @@ try
$oPage->SetContentType('text/javascript');
$oPage->add_header('Cache-control: public, max-age=86400'); // Cache for 24 hours
$oPage->add_header("Pragma: cache"); // Reset the value set .... where ?
// X-Frame http header : set in page constructor, but we need to allow frame integration for this specific page
// so we're resetting its value ! (see N°3416)
$oPage->add_xframe_options('');
$oPage->add(file_get_contents(Utils::GetCachePath().$sSignature.'.js'));
break;

View File

@@ -68,8 +68,6 @@ try
LoginWebPage::DoLoginEx($sRequestedPortalId, false);
$oPage = new ajax_page("");
$oPage->no_cache();
$sFilter = utils::ReadParam('filter', '', false, 'raw_data');
$sEncoding = utils::ReadParam('encoding', 'serialize');
@@ -889,13 +887,12 @@ try
case 'chart':
// Workaround for IE8 + IIS + HTTPS
// See TRAC #363, fix described here: http://forums.codecharge.com/posts.php?post_id=97771
$oPage->add_header("Expires: Fri, 17 Jul 1970 05:00:00 GMT");
$oPage->add_header("Cache-Control: cache, must-revalidate");
$oPage->add_header("Pragma: public");
$oPage->add_header("Expires: Fri, 17 Jul 1970 05:00:00 GMT");
$aParams = utils::ReadParam('params', array(), false, 'raw_data');
if ($sFilter != '')
{
if ($sFilter != '') {
$oFilter = DBSearch::unserialize($sFilter);
$oKPI = new ExecutionKPI();
$oDisplayBlock = new DisplayBlock($oFilter, 'chart_ajax', false);
@@ -961,6 +958,11 @@ try
if (!empty($sClass) && ($sClass != 'InlineImage') && !empty($id) && !empty($sField))
{
$oKPI = new ExecutionKPI();
// X-Frame http header : set in page constructor, but we need to allow frame integration for this specific page
// so we're resetting its value ! (see N°3416)
$oPage->add_xframe_options('');
ormDocument::DownloadDocument($oPage, $sClass, $id, $sField, 'inline');
$oKPI->ComputeAndReport('Data fetch and format');
}

View File

@@ -49,7 +49,6 @@ try
}
$oPage = new ajax_page("");
$oPage->no_cache();
$oPage->SetContentType('text/html');
$sListParams = utils::ReadParam('list_params', '{}', false, 'raw_data');

View File

@@ -727,10 +727,16 @@ class ApplicationInstaller
SetupPage::log_info("There are $iOrphanCount useless records in {$sDBPrefix}priv_change (".sprintf('%.2f', ((100.0*$iOrphanCount)/$iTotalCount))."%)");
if ($iOrphanCount > 0)
{
SetupPage::log_info("Removing the orphan records...");
$sCleanup = "DELETE FROM `{$sDBPrefix}priv_change` USING `{$sDBPrefix}priv_change` LEFT JOIN `{$sDBPrefix}priv_changeop` ON `{$sDBPrefix}priv_change`.id = `{$sDBPrefix}priv_changeop`.changeid WHERE `{$sDBPrefix}priv_changeop`.id IS NULL;";
CMDBSource::Query($sCleanup);
SetupPage::log_info("Cleanup completed successfully.");
//N°3793
if ($iOrphanCount > 100000)
{
SetupPage::log_warning("There are too much useless records ($iOrphanCount) in {$sDBPrefix}priv_change. Cleanup cannot be done during setup.");
} else {
SetupPage::log_info("Removing the orphan records...");
$sCleanup = "DELETE FROM `{$sDBPrefix}priv_change` USING `{$sDBPrefix}priv_change` LEFT JOIN `{$sDBPrefix}priv_changeop` ON `{$sDBPrefix}priv_change`.id = `{$sDBPrefix}priv_changeop`.changeid WHERE `{$sDBPrefix}priv_changeop`.id IS NULL;";
CMDBSource::Query($sCleanup);
SetupPage::log_info("Cleanup completed successfully.");
}
}
else
{

View File

@@ -33,10 +33,14 @@ LoginWebPage::DoLogin(true); // Check user rights and prompt if needed (must be
$sOperation = Utils::ReadParam('operation', 'step1');
$oP = new SetupPage('iTop email test utility');
// Although this page doesn't expose sensitive info, with it we can send multiple emails
// So we're adding this http header to reduce CSRF exposure...
$oP->add_xframe_options('DENY');
/**
* Helper to check server setting required to send an email
*/
*/
function CheckEmailSetting($oP)
{
$bRet = true;
@@ -255,11 +259,11 @@ try
break;
case 'step2':
$oP->no_cache();
$sTo = Utils::ReadParam('to', '', false, 'raw_data');
$sFrom = Utils::ReadParam('from', '', false, 'raw_data');
DisplayStep2($oP, $sFrom, $sTo);
break;
$oP->no_cache();
$sTo = Utils::ReadParam('to', '', false, 'raw_data');
$sFrom = Utils::ReadParam('from', '', false, 'raw_data');
DisplayStep2($oP, $sFrom, $sTo);
break;
default:
$oP->error("Error: unsupported operation '$sOperation'");

View File

@@ -61,24 +61,29 @@ class iTopExtension
* @var bool
*/
public $bVisible;
/**
* @var string[]
*/
public $aModules;
/**
* @var string[]
*/
public $aModuleVersion;
/**
* @var string[]
*/
public $aModuleInfo;
/**
* @var string
*/
public $sSourceDir;
/**
*
*
* @var string[]
*/
public $aMissingDependencies;
@@ -96,6 +101,7 @@ class iTopExtension
$this->sInstalledVersion = '';
$this->aModules = array();
$this->aModuleVersion = array();
$this->aModuleInfo = array();
$this->sSourceDir = '';
$this->bVisible = true;
$this->aMissingDependencies = array();
@@ -310,11 +316,11 @@ class iTopExtensionsMap
$sModuleVersion = '0.0.1';
}
if (($sParentExtensionId !== null) && (array_key_exists($sParentExtensionId, $this->aExtensions)) && ($this->aExtensions[$sParentExtensionId] instanceof iTopExtension))
{
if (($sParentExtensionId !== null) && (array_key_exists($sParentExtensionId, $this->aExtensions)) && ($this->aExtensions[$sParentExtensionId] instanceof iTopExtension)) {
// Already inside an extension, let's add this module the list of modules belonging to this extension
$this->aExtensions[$sParentExtensionId]->aModules[] = $sModuleName;
$this->aExtensions[$sParentExtensionId]->aModuleVersion[$sModuleName] = $sModuleVersion;
$this->aExtensions[$sParentExtensionId]->aModuleInfo[$sModuleName] = $aModuleInfo[2];
}
else
{
@@ -329,8 +335,6 @@ class iTopExtensionsMap
}
// Let's create a "fake" extension from this module (containing just this module) for backwards compatibility
$sExtensionId = $sModuleId;
$oExtension = new iTopExtension();
$oExtension->sCode = $sModuleName;
$oExtension->sLabel = $aModuleInfo[2]['label'];
@@ -341,9 +345,10 @@ class iTopExtensionsMap
$oExtension->sMoreInfoUrl = $aModuleInfo[2]['doc.more_information'];
$oExtension->aModules = array($sModuleName);
$oExtension->aModuleVersion[$sModuleName] = $sModuleVersion;
$oExtension->aModuleInfo[$sModuleName] = $aModuleInfo[2];
$oExtension->sSourceDir = $sSearchDir;
$oExtension->bVisible = $bVisible;
$this->AddExtension($oExtension);
$this->AddExtension($oExtension);
}
closedir($hDir);

View File

@@ -54,5 +54,7 @@ if (!function_exists('json_decode'))
}
/////////////////////////////////////////////////////////////////////
//N°3671 setup context: force $bForceTrustProxy to be persisted in next calls
utils::GetAbsoluteUrlAppRoot(true);
$oWizard = new WizardController('WizStepWelcome');
$oWizard->Run();

View File

@@ -551,14 +551,25 @@ class SetupUtils
SetupPage::log('Info - PHP functions disabled: '.implode(', ', $aDisabled));
if (in_array('exec', $aDisabled))
{
$aResult[] = new CheckResult(CheckResult::ERROR, "The PHP exec() function has been disabled on this server");
return new CheckResult(CheckResult::ERROR, "The PHP exec() function has been disabled on this server");
}
// availability of dot / dot.exe
if (empty($sGraphvizPath))
{
$sGraphvizPath = 'dot';
} else {
clearstatcache();
if (!is_file($sGraphvizPath) || !is_executable($sGraphvizPath)) {
//N°3412 avoid shell injection
return new CheckResult(CheckResult::WARNING, "$sGraphvizPath could not be executed: Please make sure it is installed and in the path");
}
if (!utils::IsWindowsEnvironment()){
$sGraphvizPath = escapeshellcmd($sGraphvizPath);
}
}
$sCommand = "\"$sGraphvizPath\" -V 2>&1";
$aOutput = array();
@@ -568,10 +579,6 @@ class SetupUtils
{
$oResult = new CheckResult(CheckResult::INFO, "dot is present: ".$aOutput[0]);
}
elseif ($iRetCode == 1)
{
$oResult = new CheckResult(CheckResult::WARNING, "dot could not be found: ".implode(' ', $aOutput)." - Please make sure it is installed and in the path.");
}
else
{
$oResult = new CheckResult(CheckResult::WARNING, "dot could not be executed (retcode=$iRetCode): Please make sure it is installed and in the path");

View File

@@ -716,7 +716,7 @@ class WizStepLicense extends WizardStep
$aLicenses = SetupUtils::GetLicenses();
$oPage->add_style(
<<<EOF
fieldset {
fieldset ul{
max-height: 18em;
overflow: auto;
}
@@ -978,7 +978,7 @@ class WizStepMiscParams extends WizardStep
public function Display(WebPage $oPage)
{
$sDefaultLanguage = $this->oWizard->GetParameter('default_language', $this->oWizard->GetParameter('admin_language'));
$sApplicationURL = $this->oWizard->GetParameter('application_url', utils::GetDefaultUrlAppRoot());
$sApplicationURL = $this->oWizard->GetParameter('application_url', utils::GetDefaultUrlAppRoot(true));
$sDefaultGraphvizPath = (strtolower(substr(PHP_OS, 0, 3)) === 'win') ? 'C:\\Program Files\\Graphviz\\bin\\dot.exe' : '/usr/bin/dot';
$sGraphvizPath = $this->oWizard->GetParameter('graphviz_path', $sDefaultGraphvizPath);
$sSampleData = $this->oWizard->GetParameter('sample_data', 'yes');
@@ -996,14 +996,14 @@ class WizStepMiscParams extends WizardStep
$oPage->add('<fieldset>');
$oPage->add('<legend>Application URL</legend>');
$oPage->add('<table>');
$oPage->add('<tr><td>URL: </td><td><input id="application_url" name="application_url" type="text" size="35" maxlength="1024" value="'.htmlentities($sApplicationURL, ENT_QUOTES, 'UTF-8').'"><span id="v_application_url"/></td><tr>');
$oPage->add('<tr><td colspan="2">Change the value above if the end-users will be accessing the application by another path due to a specific configuration of the web server.</td><tr>');
$oPage->add('<tr><td>URL: </td><td><input id="application_url" name="application_url" type="text" size="35" maxlength="1024" value="'.htmlentities($sApplicationURL, ENT_QUOTES, 'UTF-8').'" style="width: 100%;box-sizing: border-box;"><span id="v_application_url"/></td><tr>');
$oPage->add('<tr><td colspan="2"><div class="message message-warning">Change the value above if the end-users will be accessing the application by another path due to a specific configuration of the web server.</div></td><tr>');
$oPage->add('</table>');
$oPage->add('</fieldset>');
$oPage->add('<fieldset>');
$oPage->add('<legend>Path to Graphviz\' dot application</legend>');
$oPage->add('<table style="width: 100%;">');
$oPage->add('<tr><td>Path: </td><td><input id="graphviz_path" name="graphviz_path" type="text" size="35" maxlength="1024" value="'.htmlentities($sGraphvizPath, ENT_QUOTES, 'UTF-8').'"><span id="v_graphviz_path"/></td><tr>');
$oPage->add('<tr><td>Path: </td><td><input id="graphviz_path" name="graphviz_path" type="text" size="35" maxlength="1024" value="'.htmlentities($sGraphvizPath, ENT_QUOTES, 'UTF-8').'" style="width: 100%;box-sizing: border-box;"><span id="v_graphviz_path"/></td><tr>');
$oPage->add('<tr><td colspan="2"><a href="http://www.graphviz.org" target="_blank">Graphviz</a> is required to display the impact analysis graph (i.e. impacts / depends on).</td><tr>');
$oPage->add('<tr><td colspan="2"><span id="graphviz_status"></span></td><tr>');
$oPage->add('</table>');
@@ -1125,21 +1125,21 @@ class WizStepUpgradeMiscParams extends WizardStep
public function Display(WebPage $oPage)
{
$sApplicationURL = $this->oWizard->GetParameter('application_url', utils::GetDefaultUrlAppRoot());
$sApplicationURL = $this->oWizard->GetParameter('application_url', utils::GetAbsoluteUrlAppRoot(true)); //Preserve existing configuration.
$sDefaultGraphvizPath = (strtolower(substr(PHP_OS, 0, 3)) === 'win') ? 'C:\\Program Files\\Graphviz\\bin\\dot.exe' : '/usr/bin/dot';
$sGraphvizPath = $this->oWizard->GetParameter('graphviz_path', $sDefaultGraphvizPath);
$oPage->add('<h2>Additional parameters</h2>');
$oPage->add('<fieldset>');
$oPage->add('<legend>Application URL</legend>');
$oPage->add('<table>');
$oPage->add('<tr><td>URL: </td><td><input id="application_url" name="application_url" type="text" size="35" maxlength="1024" value="'.htmlentities($sApplicationURL, ENT_QUOTES, 'UTF-8').'"><span id="v_application_url"/></td><tr>');
$oPage->add('<tr><td colspan="2">Change the value above if the end-users will be accessing the application by another path due to a specific configuration of the web server.</td><tr>');
$oPage->add('<tr><td>URL: </td><td><input id="application_url" name="application_url" type="text" size="35" maxlength="1024" value="'.htmlentities($sApplicationURL, ENT_QUOTES, 'UTF-8').'" style="width: 100%;box-sizing: border-box;"><span id="v_application_url"/></td><tr>');
$oPage->add('<tr><td colspan="2"><div class="message message-warning">Change the value above if the end-users will be accessing the application by another path due to a specific configuration of the web server.</div></td><tr>');
$oPage->add('</table>');
$oPage->add('</fieldset>');
$oPage->add('<fieldset>');
$oPage->add('<legend>Path to Graphviz\' dot application</legend>');
$oPage->add('<table style="width: 100%;">');
$oPage->add('<tr><td>Path: </td><td><input id="graphviz_path" name="graphviz_path" type="text" size="35" maxlength="1024" value="'.htmlentities($sGraphvizPath, ENT_QUOTES, 'UTF-8').'"><span id="v_graphviz_path"/></td><tr>');
$oPage->add('<tr><td>Path: </td><td><input id="graphviz_path" name="graphviz_path" type="text" size="35" maxlength="1024" value="'.htmlentities($sGraphvizPath, ENT_QUOTES, 'UTF-8').'" style="width: 100%;box-sizing: border-box;"><span id="v_graphviz_path"/></td><tr>');
$oPage->add('<tr><td colspan="2"><a href="http://www.graphviz.org" target="_blank">Graphviz</a> is required to display the impact analysis graph (i.e. impacts / depends on).</td><tr>');
$oPage->add('<tr><td colspan="2"><span id="graphviz_status"></span></td><tr>');
$oPage->add('</table>');
@@ -1376,14 +1376,14 @@ class WizStepModulesChoice extends WizardStep
if (substr($sBannerPath, 0, 1) == '/')
{
// absolute path, means relative to APPROOT
$sBannerUrl = utils::GetDefaultUrlAppRoot().$sBannerPath;
$sBannerUrl = utils::GetDefaultUrlAppRoot(true).$sBannerPath;
}
else
{
// relative path: i.e. relative to the directory containing the XML file
$sFullPath = dirname($this->GetSourceFilePath()).'/'.$sBannerPath;
$sRealPath = realpath($sFullPath);
$sBannerUrl = utils::GetDefaultUrlAppRoot().str_replace(realpath(APPROOT), '', $sRealPath);
$sBannerUrl = utils::GetDefaultUrlAppRoot(true).str_replace(realpath(APPROOT), '', $sRealPath);
}
$oPage->add('<td><img src="'.$sBannerUrl.'"/><td>');
}
@@ -1666,10 +1666,12 @@ EOF
/**
* Converts the list of selected "choices" into a list of "modules": take into account the selected and the mandatory modules
* @param hash $aInfo Info about the "choice" array('options' => array(...), 'alternatives' => array(...))
* @param hash $aSelectedChoices List of selected choices array('name' => 'selected_value_id')
* @param hash $aModules Return parameter: List of selected modules array('module_id' => true)
*
* @param array $aInfo Info about the "choice" array('options' => array(...), 'alternatives' => array(...))
* @param array $aSelectedChoices List of selected choices array('name' => 'selected_value_id')
* @param array $aModules Return parameter: List of selected modules array('module_id' => true)
* @param string $sParentId Used for recursion
*
* @return string A text representation of what will be installed
*/
protected function GetSelectedModules($aInfo, $aSelectedChoices, &$aModules, $sParentId = '', $sDisplayChoices = '', &$aSelectedExtensions = null)
@@ -1690,35 +1692,55 @@ EOF
}
}
$aOptions = isset($aInfo['options']) ? $aInfo['options'] : array();
foreach($aOptions as $index => $aChoice)
{
foreach($aOptions as $index => $aChoice) {
$sChoiceId = $sParentId.self::$SEP.$index;
if ( (isset($aChoice['mandatory']) && $aChoice['mandatory']) ||
(isset($aSelectedChoices[$sChoiceId]) && ($aSelectedChoices[$sChoiceId] == $sChoiceId)) )
{
$aModuleInfo = [];
// Get the extension corresponding to the choice
foreach ($this->oExtensionsMap->GetAllExtensions() as $sExtensionVersion => $oExtension) {
if (utils::StartsWith($sExtensionVersion, $aChoice['extension_code'].'/')) {
$aModuleInfo = $oExtension->aModuleInfo;
break;
}
}
if ((isset($aChoice['mandatory']) && $aChoice['mandatory']) ||
(isset($aSelectedChoices[$sChoiceId]) && ($aSelectedChoices[$sChoiceId] == $sChoiceId))) {
$sDisplayChoices .= '<li>'.$aChoice['title'].'</li>';
if (isset($aChoice['modules']))
{
foreach($aChoice['modules'] as $sModuleId)
{
$aModules[$sModuleId] = true; // store the Id of the selected module
if (isset($aChoice['modules'])) {
foreach ($aChoice['modules'] as $sModuleId) {
$bSelected = true;
if (isset($aModuleInfo[$sModuleId])) {
// Test if module has 'auto_select'
$aInfo = $aModuleInfo[$sModuleId];
if (isset($aInfo['auto_select'])) {
// Check the module selection
try {
$bSelected = false;
SetupInfo::SetSelectedModules($aModules);
eval('$bSelected = ('.$aInfo['auto_select'].');');
}
catch (Exception $e) {
$bSelected = false;
}
}
}
if ($bSelected) {
$aModules[$sModuleId] = true; // store the Id of the selected module
SetupInfo::SetSelectedModules($aModules);
}
}
}
$sChoiceType = isset($aChoice['type']) ? $aChoice['type'] : 'wizard_option';
if ($aSelectedExtensions !== null)
{
if ($aSelectedExtensions !== null) {
$aSelectedExtensions[] = $aChoice['extension_code'];
}
// Recurse only for selected choices
if (isset($aChoice['sub_options']))
{
if (isset($aChoice['sub_options'])) {
$sDisplayChoices .= '<ul>';
$sDisplayChoices = $this->GetSelectedModules($aChoice['sub_options'], $aSelectedChoices, $aModules, $sChoiceId, $sDisplayChoices, $aSelectedExtensions);
$sDisplayChoices .= '</ul>';
}
$sDisplayChoices .= '</li>';
}
$index++;
}
$aAlternatives = isset($aInfo['alternatives']) ? $aInfo['alternatives'] : array();
@@ -1754,7 +1776,6 @@ EOF
}
$sDisplayChoices .= '</li>';
}
$index++;
}
if ($sParentId == '')
{
@@ -2494,7 +2515,7 @@ class WizStepDone extends WizardStep
$aManualSteps = array();
$aAvailableModules = SetupUtils::AnalyzeInstallation($this->oWizard);
$sRootUrl = utils::GetAbsoluteUrlAppRoot();
$sRootUrl = utils::GetAbsoluteUrlAppRoot(true);
$aSelectedModules = json_decode($this->oWizard->GetParameter('selected_modules'), true);
foreach($aSelectedModules as $sModuleId)
{
@@ -2526,7 +2547,7 @@ class WizStepDone extends WizardStep
{
// To mitigate security risks: pass only the filename without the extension, the download will add the extension itself
$oPage->p('Your backup is ready');
$oPage->p('<a style="background:transparent;" href="'.utils::GetAbsoluteUrlAppRoot().'setup/ajax.dataloader.php?operation=async_action&step_class=WizStepDone&params[backup]='.urlencode($sBackupDestination).'&authent='.$this->oWizard->GetParameter('authent','').'" target="_blank"><img src="../images/tar.png" style="border:0;vertical-align:middle;">&nbsp;Download '.basename($sBackupDestination).'</a>');
$oPage->p('<a style="background:transparent;" href="'.utils::GetAbsoluteUrlAppRoot(true).'setup/ajax.dataloader.php?operation=async_action&step_class=WizStepDone&params[backup]='.urlencode($sBackupDestination).'&authent='.$this->oWizard->GetParameter('authent','').'" target="_blank"><img src="../images/tar.png" style="border:0;vertical-align:middle;">&nbsp;Download '.basename($sBackupDestination).'</a>');
}
else
{

View File

@@ -435,18 +435,16 @@ abstract class Controller
$sFileMimeType = utils::GetFileMimeType($sFilePath);
header('Content-Type: '.$sFileMimeType);
if ($bFileTransfer)
{
if ($bFileTransfer) {
header('Content-Description: File Transfer');
header('Content-Disposition: inline; filename="'.$sDownloadArchiveName);
}
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Expires: 0');
foreach ($aHeaders as $sKey => $sValue)
{
foreach ($aHeaders as $sKey => $sValue) {
header($sKey.': '.$sValue);
}
@@ -558,7 +556,7 @@ abstract class Controller
{
case 'html':
$this->m_oPage = new iTopWebPage($this->GetOperationTitle());
$this->m_oPage->add_header('X-Frame-Options: deny');
$this->m_oPage->add_xframe_options();
break;
case 'ajax':

View File

@@ -160,9 +160,41 @@ abstract class FormManager
/**
* @param array|null $aArgs
*
* @return mixed
* @return array
*
* @since 2.7.4 3.0.0 N°3430
*/
abstract public function OnSubmit($aArgs = null);
public function OnSubmit($aArgs = null)
{
$aData = array(
'valid' => true,
'messages' => array(
'success' => array(),
'warnings' => array(), // Not used as of today, just to show that the structure is ready for change like this.
'error' => array(),
),
);
$this->CheckTransaction($aData);
return $aData;
}
/**
* @param array $aData
*
* @since 2.7.4 3.0.0 N°3430
*/
public function CheckTransaction(&$aData)
{
$isTransactionValid = \utils::IsTransactionValid($this->oForm->GetTransactionId(), false); //The transaction token is kept in order to preserve BC with ajax forms (the second call would fail if the token is deleted). (The GC will take care of cleaning the token for us later on)
if (!$isTransactionValid) {
$aData['messages']['error'] += [
'_main' => [\Dict::S('UI:Error:InvalidToken')] //This message is generic, if you override this method you should use a more precise message. @see \Combodo\iTop\Portal\Form\ObjectFormManager::CheckTransaction
];
$aData['valid'] = false;
}
}
/**
* @param array|null $aArgs

View File

@@ -12,7 +12,7 @@
{% if bFailedLogin %}
<p class="hilite">{{ sMessage }}</p>
{% else %}
<p>{{ sMessage }}</p>
<p>{{ sMessage|raw }}</p>
{% endif %}
</div>
{% endblock login_title %}

View File

@@ -73,7 +73,7 @@ class ItopDataTestCase extends ItopTestCase
protected function setUp()
{
parent::setUp();
//require_once(APPROOT.'/application/startup.inc.php');
require_once(APPROOT.'/application/startup.inc.php');
require_once(APPROOT.'application/utils.inc.php');
@@ -408,8 +408,12 @@ class ItopDataTestCase extends ItopTestCase
* @return \DBObject
* @throws Exception
*/
protected function CreateUser($sLogin, $iProfileId)
protected function CreateUser($sLogin, $iProfileId, $sPassword=null)
{
if (empty($sPassword)){
$sPassword = $sLogin;
}
$oUserProfile = new URP_UserProfile();
$oUserProfile->Set('profileid', $iProfileId);
$oUserProfile->Set('reason', 'UNIT Tests');
@@ -417,7 +421,7 @@ class ItopDataTestCase extends ItopTestCase
$oUser = $this->createObject('UserLocal', array(
'contactid' => 2,
'login' => $sLogin,
'password' => $sLogin,
'password' => $sPassword,
'language' => 'EN US',
'profile_list' => $oSet,
));
@@ -426,6 +430,29 @@ class ItopDataTestCase extends ItopTestCase
return $oUser;
}
/**
* @param \DBObject $oUser
* @param int $iProfileId
*
* @return \DBObject
* @throws Exception
*/
protected function AddProfileToUser($oUser, $iProfileId)
{
$oUserProfile = new URP_UserProfile();
$oUserProfile->Set('profileid', $iProfileId);
$oUserProfile->Set('reason', 'UNIT Tests');
/** @var DBObjectSet $oSet */
$oSet = $oUser->Get('profile_list');
$oSet->AddObject($oUserProfile);
$oUser = $this->updateObject('UserLocal', $oUser->GetKey(), array(
'profile_list' => $oSet,
));
$this->debug("Updated {$oUser->GetName()} ({$oUser->GetKey()})");
return $oUser;
}
/**
* Create a Hypervisor in database

View File

@@ -93,7 +93,31 @@ class ItopTestCase extends TestCase
{
$sId = str_replace('"', '', $this->getName());
$sId = str_replace(' ', '_', $sId);
return $sId;
}
public function InvokeNonPublicStaticMethod($sObjectClass, $sMethodName, $aArgs)
{
return $this->InvokeNonPublicMethod($sObjectClass, $sMethodName, null, $aArgs);
}
/**
* @param string $sObjectClass for example DBObject::class
* @param string $sMethodName
* @param object $oObject
* @param array $aArgs
*
* @return mixed method result
*
* @throws \ReflectionException
*/
public function InvokeNonPublicMethod($sObjectClass, $sMethodName, $oObject, $aArgs)
{
$class = new \ReflectionClass($sObjectClass);
$method = $class->getMethod($sMethodName);
$method->setAccessible(true);
return $method->invokeArgs($oObject, $aArgs);
}
}

Some files were not shown because too many files have changed in this diff Show More