mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 23:44:11 +01:00
Compare commits
168 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec1dcc8df6 | ||
|
|
47ed863da9 | ||
|
|
88290f9e91 | ||
|
|
cfdbc8ae62 | ||
|
|
aaa8f6d311 | ||
|
|
da217a1cb3 | ||
|
|
d90b1a3d82 | ||
|
|
3694108f42 | ||
|
|
8cf75f826f | ||
|
|
ad9726b64c | ||
|
|
e32e275f40 | ||
|
|
195056492e | ||
|
|
af338de17f | ||
|
|
a6aa183e26 | ||
|
|
b5074c4cee | ||
|
|
8b9589744b | ||
|
|
8259a79cd2 | ||
|
|
a23ea9a01f | ||
|
|
949b213f9d | ||
|
|
a940adc4ba | ||
|
|
5d994edd62 | ||
|
|
b1ca1f2630 | ||
|
|
58e315d7f6 | ||
|
|
1059befa39 | ||
|
|
0f5130611d | ||
|
|
a1271da74a | ||
|
|
0d40235791 | ||
|
|
dd63f2b817 | ||
|
|
8f84c3b84b | ||
|
|
00c58bb245 | ||
|
|
3a876d5c75 | ||
|
|
147916062b | ||
|
|
0de6f98add | ||
|
|
a076792e77 | ||
|
|
2d2a6857de | ||
|
|
373641e01d | ||
|
|
d11eceac62 | ||
|
|
3965806fa0 | ||
|
|
2625d2da80 | ||
|
|
02d32a556d | ||
|
|
71fcc6f026 | ||
|
|
7168860a0b | ||
|
|
684c88e0b8 | ||
|
|
84741c19f0 | ||
|
|
86f649affc | ||
|
|
4f5c987d8b | ||
|
|
e7b5953feb | ||
|
|
e441e5e78a | ||
|
|
6be9a87c15 | ||
|
|
43daa2ef08 | ||
|
|
caa2a05bf4 | ||
|
|
fc39d8aca9 | ||
|
|
cf12578289 | ||
|
|
44952d1ea0 | ||
|
|
7f15eed9a8 | ||
|
|
c2f5cafaf3 | ||
|
|
81822efa0f | ||
|
|
923a025f1c | ||
|
|
a4b6f4e37c | ||
|
|
f0c73451a2 | ||
|
|
db6e813cba | ||
|
|
d74e3e6b42 | ||
|
|
b740cb2afd | ||
|
|
6ad3c40c42 | ||
|
|
f49c8ce188 | ||
|
|
acf828b72e | ||
|
|
bac92716f3 | ||
|
|
07257cc2d2 | ||
|
|
87ba67225a | ||
|
|
2ad3b3c27e | ||
|
|
92a640e41a | ||
|
|
842df7646b | ||
|
|
01b38d2ed6 | ||
|
|
9af4846372 | ||
|
|
91fc2d2e2b | ||
|
|
2432ff77a3 | ||
|
|
d229e08f02 | ||
|
|
b5adb2e82b | ||
|
|
386c90c601 | ||
|
|
5d0c61178b | ||
|
|
3bcae734e5 | ||
|
|
842e8f9e01 | ||
|
|
52cd4f7c5e | ||
|
|
995619af9b | ||
|
|
c842162fe2 | ||
|
|
83f99642e0 | ||
|
|
ae6a264d6d | ||
|
|
a06bf6ea7c | ||
|
|
bb8d4a92cb | ||
|
|
1429792690 | ||
|
|
1f26b59d90 | ||
|
|
7b093a6bba | ||
|
|
d4607ee815 | ||
|
|
5c0e92d51a | ||
|
|
cd4b3fdaab | ||
|
|
0030d5c2b8 | ||
|
|
95a0efedcf | ||
|
|
13a1d32f56 | ||
|
|
35155e4b7a | ||
|
|
77710f1613 | ||
|
|
2763b99142 | ||
|
|
db13c105ad | ||
|
|
2276539f24 | ||
|
|
9b7cd20d47 | ||
|
|
e1d644c33b | ||
|
|
c601082a5e | ||
|
|
5836be7131 | ||
|
|
6f40bb4c35 | ||
|
|
241bd1cdeb | ||
|
|
71c5f47cd8 | ||
|
|
74246a8278 | ||
|
|
c450c9426c | ||
|
|
46f9fe743c | ||
|
|
c31df5fff3 | ||
|
|
6e0af1a3b7 | ||
|
|
e9e18513be | ||
|
|
9d2fc883b8 | ||
|
|
913ea0cef2 | ||
|
|
82ba7f25b0 | ||
|
|
bb877a244b | ||
|
|
a12959d60e | ||
|
|
83434b5506 | ||
|
|
dcd4abe72b | ||
|
|
571520815a | ||
|
|
e9cff0920b | ||
|
|
905ee19519 | ||
|
|
0b95220d1b | ||
|
|
e1b2a767f5 | ||
|
|
3058b2eb00 | ||
|
|
38bc2d9d58 | ||
|
|
c8e8778d7b | ||
|
|
656fa3208a | ||
|
|
f647ce61c2 | ||
|
|
6b76e5a853 | ||
|
|
dbb6e43751 | ||
|
|
f07f0ba1c7 | ||
|
|
a5894c1a4c | ||
|
|
e06996a2e4 | ||
|
|
2f0e7c6d29 | ||
|
|
7115a6ae7d | ||
|
|
765560d1f5 | ||
|
|
bc024d9ed0 | ||
|
|
37a4a3eb47 | ||
|
|
54e9bd5c8e | ||
|
|
066a6d8b36 | ||
|
|
4123c6213d | ||
|
|
8265b9b034 | ||
|
|
c4756e8cec | ||
|
|
37351d6b3e | ||
|
|
57a085eec1 | ||
|
|
0019595923 | ||
|
|
4d61c14f80 | ||
|
|
cf1b613923 | ||
|
|
1304e2eb2d | ||
|
|
3cf16627c1 | ||
|
|
4aaa237bf9 | ||
|
|
cece15d10c | ||
|
|
aa15e009cb | ||
|
|
b9ca2ac13d | ||
|
|
80e1e0e61a | ||
|
|
ecebe4ecd5 | ||
|
|
8bfcb14d0c | ||
|
|
1cf1473d6b | ||
|
|
aa43425df3 | ||
|
|
35d77ff642 | ||
|
|
539fa43503 | ||
|
|
eb537f45f4 | ||
|
|
a2a4cd4e7a |
@@ -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,17 +78,19 @@ 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
|
||||
ij_xml_block_comment_at_first_column = true
|
||||
ij_xml_keep_blank_lines = 2
|
||||
ij_xml_keep_indents_on_empty_lines = false
|
||||
ij_xml_keep_line_breaks = false
|
||||
ij_xml_keep_line_breaks = true
|
||||
ij_xml_keep_line_breaks_in_text = true
|
||||
ij_xml_keep_whitespaces = false
|
||||
ij_xml_keep_whitespaces_around_cdata = preserve
|
||||
@@ -93,21 +99,24 @@ 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
|
||||
ij_shell_redirect_followed_by_space = false
|
||||
ij_shell_switch_cases_indented = false
|
||||
ij_shell_use_unix_line_separator = true
|
||||
|
||||
[{*.cjs,*.js}]
|
||||
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 +150,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
|
||||
@@ -271,17 +280,17 @@ ij_javascript_while_brace_force = always
|
||||
ij_javascript_while_on_new_line = false
|
||||
ij_javascript_wrap_comments = false
|
||||
|
||||
[{*.ctp,*.hphp,*.inc,*.module,*.php,*.php4,*.php5,*.phtml}]
|
||||
[{*.ctp, *.hphp, *.inc, *.module, *.php, *.php4, *.php5, *.phtml}]
|
||||
indent_style = tab
|
||||
ij_continuation_indent_size = 4
|
||||
ij_smart_tabs = true
|
||||
ij_wrap_on_typing = false
|
||||
ij_php_align_assignments = false
|
||||
ij_php_align_assignments = true
|
||||
ij_php_align_class_constants = false
|
||||
ij_php_align_group_field_declarations = false
|
||||
ij_php_align_inline_comments = false
|
||||
ij_php_align_key_value_pairs = false
|
||||
ij_php_align_multiline_array_initializer_expression = false
|
||||
ij_php_align_key_value_pairs = true
|
||||
ij_php_align_multiline_array_initializer_expression = true
|
||||
ij_php_align_multiline_binary_operation = false
|
||||
ij_php_align_multiline_chained_methods = false
|
||||
ij_php_align_multiline_extends_list = false
|
||||
@@ -297,6 +306,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 +395,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 +417,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 +431,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 +499,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,6 +514,7 @@ ij_json_wrap_long_lines = false
|
||||
[{*.htm,*.html,*.sht,*.shtm,*.shtml}]
|
||||
indent_style = tab
|
||||
ij_smart_tabs = true
|
||||
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
|
||||
@@ -527,10 +542,30 @@ ij_html_space_inside_empty_tag = false
|
||||
ij_html_text_wrap = normal
|
||||
ij_html_uniform_ident = false
|
||||
|
||||
[{*.markdown,*.md}]
|
||||
ij_visual_guides = none
|
||||
ij_markdown_force_one_space_after_blockquote_symbol = true
|
||||
ij_markdown_force_one_space_after_header_symbol = true
|
||||
ij_markdown_force_one_space_after_list_bullet = true
|
||||
ij_markdown_force_one_space_between_words = true
|
||||
ij_markdown_keep_indents_on_empty_lines = false
|
||||
ij_markdown_max_lines_around_block_elements = 1
|
||||
ij_markdown_max_lines_around_header = 1
|
||||
ij_markdown_max_lines_between_paragraphs = 1
|
||||
ij_markdown_min_lines_around_block_elements = 1
|
||||
ij_markdown_min_lines_around_header = 1
|
||||
ij_markdown_min_lines_between_paragraphs = 1
|
||||
|
||||
[{*.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
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
* script used to sort license file (usefull for autogeneration)
|
||||
* Example: php
|
||||
* script used to sort license file (useful for autogeneration)
|
||||
*
|
||||
* Requirements :
|
||||
* * bash (on Windows, use Git Bash)
|
||||
* * composer (if you use the phar version, mind to create a `Composer` alias !)
|
||||
* * JQ command
|
||||
* to install on Windows :
|
||||
* `curl -L -o /usr/bin/jq.exe https://github.com/stedolan/jq/releases/latest/download/jq-win64.exe`
|
||||
* this is a Windows port : https://stedolan.github.io/jq/
|
||||
*
|
||||
* Licenses sources :
|
||||
* * `composer licenses --format json` (see https://getcomposer.org/doc/03-cli.md#licenses)
|
||||
* * keep every existing nodes with `/licenses/license[11]/product/@scope` not in ['lib', 'datamodels']
|
||||
* ⚠ If licenses were added manually, they might be removed by this tool ! Be very careful to check for the result before pushing !
|
||||
*
|
||||
* To launch, check requirements and run `php updateLicenses.php`
|
||||
* The target license file path is in `$xmlFilePath`
|
||||
*/
|
||||
|
||||
$iTopFolder = __DIR__ . "/../../" ;
|
||||
@@ -51,39 +66,83 @@ function get_license_nodes($file_path)
|
||||
$xp = new DOMXPath($dom);
|
||||
|
||||
$licenseList = $xp->query('/licenses/license');
|
||||
$licenses = iterator_to_array($licenseList);
|
||||
$licenses = iterator_to_array($licenseList);
|
||||
|
||||
usort($licenses, 'sort_by_product');
|
||||
|
||||
return $licenses;
|
||||
}
|
||||
|
||||
/** @noinspection SuspiciousAssignmentsInspection */
|
||||
function fix_product_name(DOMNode &$oProductNode)
|
||||
{
|
||||
$sProductNameOrig = $oProductNode->nodeValue;
|
||||
|
||||
// sample : `C:\Dev\wamp64\www\itop-27\.make\license/../..//lib/symfony/polyfill-ctype`
|
||||
$sProductNameFixed = remove_dir_from_string($sProductNameOrig, 'lib/');
|
||||
|
||||
// sample : `C:\Dev\wamp64\www\itop-27\.make\license/../..//datamodels/2.x/authent-cas/vendor/apereo/phpcas`
|
||||
$sProductNameFixed = remove_dir_from_string($sProductNameFixed, 'vendor/');
|
||||
|
||||
$oProductNode->nodeValue = $sProductNameFixed;
|
||||
}
|
||||
|
||||
function remove_dir_from_string($sString, $sNeedle)
|
||||
{
|
||||
if (strpos($sString, $sNeedle) === false) {
|
||||
return $sString;
|
||||
}
|
||||
|
||||
$sStringTmp = strstr($sString, $sNeedle);
|
||||
$sStringFixed = str_replace($sNeedle, '', $sStringTmp);
|
||||
|
||||
// DEBUG trace O:)
|
||||
// echo "$sNeedle = $sString => $sStringFixed\n";
|
||||
|
||||
return $sStringFixed;
|
||||
}
|
||||
|
||||
$old_licenses = get_license_nodes($xmlFilePath);
|
||||
|
||||
//generate file with updated licenses
|
||||
$generated_license_file_path = __DIR__."/provfile.xml";
|
||||
exec("bash " . __DIR__ . "/gen-community-license.sh $iTopFolder > ". $generated_license_file_path);
|
||||
echo "- Generating licences...";
|
||||
exec("bash ".__DIR__."/gen-community-license.sh $iTopFolder > ".$generated_license_file_path);
|
||||
echo "OK!\n";
|
||||
|
||||
echo "- Get licenses nodes...";
|
||||
$new_licenses = get_license_nodes($generated_license_file_path);
|
||||
exec("rm -f ". $generated_license_file_path);
|
||||
unlink($generated_license_file_path);
|
||||
|
||||
foreach ($old_licenses as $b) {
|
||||
$aProductNode = get_product_node($b);
|
||||
|
||||
if (get_scope($aProductNode) !== "lib" && get_scope($aProductNode) !== "datamodels" )
|
||||
{
|
||||
if (get_scope($aProductNode) !== "lib" && get_scope($aProductNode) !== "datamodels") {
|
||||
$new_licenses[] = $b;
|
||||
}
|
||||
}
|
||||
|
||||
usort($new_licenses, 'sort_by_product');
|
||||
echo "OK!\n";
|
||||
|
||||
echo "- Overwritting Combodo license file...";
|
||||
$new_dom = new DOMDocument("1.0");
|
||||
$new_dom->formatOutput = true;
|
||||
$root = $new_dom->createElement("licenses");
|
||||
$new_dom->appendChild($root);
|
||||
|
||||
foreach ($new_licenses as $b) {
|
||||
$node = $new_dom->importNode($b,true);
|
||||
$root->appendChild($new_dom->importNode($b,true));
|
||||
$node = $new_dom->importNode($b, true);
|
||||
|
||||
// N°3870 fix when running script in Windows
|
||||
// fix should be in gen-community-license.sh but it is easier to do it here !
|
||||
if (strncasecmp(PHP_OS, 'WIN', 3) === 0) {
|
||||
$oProductNodeOrig = get_product_node($node);
|
||||
fix_product_name($oProductNodeOrig);
|
||||
}
|
||||
|
||||
$root->appendChild($node);
|
||||
}
|
||||
|
||||
$new_dom->save($xmlFilePath);
|
||||
$new_dom->save($xmlFilePath);
|
||||
echo "OK!\n";
|
||||
77
.make/release/changelog.php
Normal file
77
.make/release/changelog.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/**
|
||||
* Usage :
|
||||
* `php changelog.php 2.7.4`
|
||||
*
|
||||
* As argument is passed the git ref (tag name or sha1) we want to use as reference
|
||||
*
|
||||
* Outputs :
|
||||
*
|
||||
* 1. List of bugs as CSV :
|
||||
* bug ref;link
|
||||
* Example :
|
||||
* <code>
|
||||
* Bug_ref;Bug_URL;sha1
|
||||
* 1234;https://support.combodo.com/pages/UI.php?operation=details&class=Bug&id=1234;949b213f9|b1ca1f263|a1271da74
|
||||
* </code>
|
||||
*
|
||||
* 2. List of commits sha1/message without bug ref
|
||||
* Example :
|
||||
* <code>
|
||||
* sha1;subject
|
||||
* a6aa183e2;:bookmark: Prepare 2.7.5
|
||||
* </code>
|
||||
*/
|
||||
|
||||
|
||||
if (count($argv) === 1) {
|
||||
echo '⚠ You must pass the base tag/sha1 as parameter';
|
||||
exit(1);
|
||||
}
|
||||
$sBaseReference = $argv[1];
|
||||
|
||||
|
||||
//--- Get log
|
||||
$sGitLogCommand = 'git log --decorate --pretty="%h;%s" --date-order --no-merges '.$sBaseReference.'..HEAD';
|
||||
$sGitLogRaw = shell_exec($sGitLogCommand);
|
||||
|
||||
|
||||
//--- Analyze log
|
||||
$aGitLogLines = preg_split('/\n/', trim($sGitLogRaw));;
|
||||
$aLogLinesWithBugRef = [];
|
||||
$aLogLineNoBug = [];
|
||||
foreach ($aGitLogLines as $sLogLine) {
|
||||
$sBugRef = preg_match('/[nN]°(\d{3,4})/', $sLogLine, $aLineBugRef);
|
||||
if (($sBugRef === false) || empty($aLineBugRef)) {
|
||||
$aLogLineNoBug[] = $sLogLine;
|
||||
continue;
|
||||
}
|
||||
|
||||
$iBugId = $aLineBugRef[1];
|
||||
$sSha = substr($sLogLine, 0, 9);
|
||||
|
||||
if (array_key_exists($iBugId, $aLogLinesWithBugRef)) {
|
||||
$aBugShaRefs = $aLogLinesWithBugRef[$iBugId];
|
||||
$aBugShaRefs[] = $sSha;
|
||||
$aLogLinesWithBugRef[$iBugId] = $aBugShaRefs;
|
||||
} else {
|
||||
$aLogLinesWithBugRef[$iBugId] = [$sSha];
|
||||
}
|
||||
}
|
||||
$aBugsList = array_keys($aLogLinesWithBugRef);
|
||||
sort($aBugsList, SORT_NUMERIC);
|
||||
|
||||
|
||||
//-- Output results
|
||||
echo "# Bugs included\n";
|
||||
echo "Bug_ref;Bug_URL;sha1\n";
|
||||
foreach ($aBugsList as $sBugRef) {
|
||||
$sShaRefs = implode('|', $aLogLinesWithBugRef[$sBugRef]);
|
||||
echo "{$sBugRef};https://support.combodo.com/pages/UI.php?operation=details&class=Bug&id={$sBugRef};$sShaRefs\n";
|
||||
}
|
||||
echo "\n";
|
||||
echo "# Logs line without bug referenced\n";
|
||||
echo "sha1;subject\n";
|
||||
foreach ($aLogLineNoBug as $sLogLine) {
|
||||
echo "$sLogLine\n";
|
||||
}
|
||||
@@ -123,8 +123,9 @@ We would like to give a special thank you to the people from the community who c
|
||||
- Lassiter, Dennis
|
||||
- Lazcano, Federico
|
||||
- Lucas, Jonathan
|
||||
- Malik, Remie
|
||||
- Rosenke, Stephan
|
||||
- Malik, Remie
|
||||
- Mindêllo de Andrade, Lucas (a.k.a @rokam)
|
||||
- Rosenke, Stephan
|
||||
- Seki, Shoji
|
||||
- Shilov, Vladimir
|
||||
- Tulio, Marco
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -72,15 +72,14 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public abstract function ListSupportedLoginModes();
|
||||
abstract public function ListSupportedLoginModes();
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function LoginAction($sLoginState, &$iErrorCode)
|
||||
{
|
||||
switch ($sLoginState)
|
||||
{
|
||||
switch ($sLoginState) {
|
||||
case LoginWebPage::LOGIN_STATE_START:
|
||||
return $this->OnStart($iErrorCode);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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']))
|
||||
{
|
||||
|
||||
@@ -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");
|
||||
@@ -357,14 +354,15 @@ JS
|
||||
);
|
||||
|
||||
// Highlight code content created with CKEditor
|
||||
// Note: We check for the <code> tag inside the <pre> tag to only target code from CKEditor, otherwise we might highlight some others things. See N°3810
|
||||
$this->add_ready_script(
|
||||
<<<JS
|
||||
// Highlight code content for HTML AttributeText
|
||||
$("[data-attribute-type='AttributeText'] .HTML pre").each(function(i, block) {
|
||||
$("[data-attribute-type='AttributeText'] .HTML pre > code").parent().each(function(i, block) {
|
||||
hljs.highlightBlock(block);
|
||||
});
|
||||
// Highlight code content for CaseLogs
|
||||
$("[data-attribute-type='AttributeCaseLog'] .caselog_entry_html pre").each(function(i, block) {
|
||||
$("[data-attribute-type='AttributeCaseLog'] .caselog_entry_html pre > code").parent().each(function(i, block) {
|
||||
hljs.highlightBlock(block);
|
||||
});
|
||||
JS
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -45,7 +45,7 @@ register_shutdown_function(function()
|
||||
$sMessage = substr($sMessage, 0, $iStackTracePos);
|
||||
}
|
||||
}
|
||||
IssueLog::error($sMessage);
|
||||
IssueLog::error($sMessage, null, $err);
|
||||
if (strpos($err['message'], 'Allowed memory size of') !== false)
|
||||
{
|
||||
$sLimit = ini_get('memory_limit');
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -563,48 +563,93 @@ class utils
|
||||
|
||||
public static function ReadFromFile($sFileName)
|
||||
{
|
||||
if (!file_exists($sFileName)) return false;
|
||||
if (!file_exists($sFileName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return file_get_contents($sFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to convert a value expressed in a 'user friendly format'
|
||||
* as in php.ini, e.g. 256k, 2M, 1G etc. Into a number of bytes
|
||||
* @param mixed $value The value as read from php.ini
|
||||
* @return number
|
||||
* @param mixed $value The value as read from php.ini (eg 256k, 2M, 1G etc.)
|
||||
*
|
||||
* @return int conversion to number of bytes
|
||||
*
|
||||
* @since 2.7.5 3.0.0 convert to int numeric values
|
||||
*
|
||||
* @link https://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes Shorthand bytes value reference in PHP.net FAQ
|
||||
*/
|
||||
public static function ConvertToBytes( $value )
|
||||
public static function ConvertToBytes($value)
|
||||
{
|
||||
$iReturn = $value;
|
||||
if ( !is_numeric( $value ) )
|
||||
{
|
||||
$iLength = strlen( $value );
|
||||
$iReturn = substr( $value, 0, $iLength - 1 );
|
||||
$sUnit = strtoupper( substr( $value, $iLength - 1 ) );
|
||||
switch ( $sUnit )
|
||||
{
|
||||
case 'G':
|
||||
$iReturn *= 1024;
|
||||
case 'M':
|
||||
$iReturn *= 1024;
|
||||
case 'K':
|
||||
$iReturn *= 1024;
|
||||
}
|
||||
}
|
||||
return $iReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the memory limit is at least what is required
|
||||
*
|
||||
* @param int $memoryLimit set limit in bytes
|
||||
* @param int $requiredLimit required limit in bytes
|
||||
* @return bool
|
||||
*/
|
||||
public static function IsMemoryLimitOk($memoryLimit, $requiredLimit)
|
||||
{
|
||||
return ($memoryLimit >= $requiredLimit) || ($memoryLimit == -1);
|
||||
}
|
||||
if (!is_numeric($value)) {
|
||||
$iLength = strlen($value);
|
||||
$iReturn = substr($value, 0, $iLength - 1);
|
||||
$sUnit = strtoupper(substr($value, $iLength - 1));
|
||||
switch ($sUnit) {
|
||||
case 'G':
|
||||
$iReturn *= 1024;
|
||||
case 'M':
|
||||
$iReturn *= 1024;
|
||||
case 'K':
|
||||
$iReturn *= 1024;
|
||||
}
|
||||
} else {
|
||||
$iReturn = (int)$value;
|
||||
}
|
||||
|
||||
return $iReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the memory limit is at least what is required
|
||||
*
|
||||
* @param int $iMemoryLimit set limit in bytes, use {@link utils::ConvertToBytes()} to convert current php.ini value
|
||||
* @param int $iRequiredLimit required limit in bytes
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function IsMemoryLimitOk($iMemoryLimit, $iRequiredLimit)
|
||||
{
|
||||
if ($iMemoryLimit === -1) {
|
||||
// -1 means : no limit (see https://www.php.net/manual/fr/ini.core.php#ini.memory-limit)
|
||||
return true;
|
||||
}
|
||||
|
||||
return ($iMemoryLimit >= $iRequiredLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set memory_limit to required value
|
||||
*
|
||||
* @param string $sRequiredLimit required limit, for example '512M'
|
||||
*
|
||||
* @return bool|null null if nothing was done, true if modifying memory_limit was successful, false otherwise
|
||||
*
|
||||
* @uses utils::ConvertToBytes()
|
||||
* @uses \ini_get('memory_limit')
|
||||
* @uses \ini_set()
|
||||
* @uses utils::ConvertToBytes()
|
||||
*
|
||||
* @since 2.7.5 N°3806
|
||||
*/
|
||||
public static function SetMinMemoryLimit($sRequiredLimit)
|
||||
{
|
||||
$iRequiredLimit = static::ConvertToBytes($sRequiredLimit);
|
||||
$sMemoryLimit = trim(ini_get('memory_limit'));
|
||||
if (empty($sMemoryLimit)) {
|
||||
// On some PHP installations, memory_limit does not exist as a PHP setting!
|
||||
// (encountered on a 5.2.0 under Windows)
|
||||
// In that case, ini_set will not work
|
||||
return false;
|
||||
}
|
||||
$iMemoryLimit = static::ConvertToBytes($sMemoryLimit);
|
||||
|
||||
if (static::IsMemoryLimitOk($iMemoryLimit, $iRequiredLimit)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ini_set('memory_limit', $iRequiredLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a value into a more friendly format (KB, MB, GB, TB) instead a juste a Bytes amount.
|
||||
@@ -733,7 +778,7 @@ class utils
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
*
|
||||
* @since 2.7.0 N°2478 always call {@link MetaModel::GetConfig} first, cache is only set when loading from disk
|
||||
* @since 2.7.0 N°2478 this method will now always call {@link MetaModel::GetConfig} first, and cache in this class is only set when loading from disk
|
||||
*/
|
||||
public static function GetConfig()
|
||||
{
|
||||
@@ -782,22 +827,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 +886,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 +1006,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 +1050,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;
|
||||
}
|
||||
|
||||
@@ -2221,8 +2377,11 @@ class utils
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sPath for example '/var/www/html/itop/data/backups/manual/itop_27-2019-10-03_15_35.tar.gz'
|
||||
* @param string $sBasePath for example '/var/www/html/itop/data/'
|
||||
* @param string $sPath for example `/var/www/html/itop/data/backups/manual/itop_27-2019-10-03_15_35.tar.gz`
|
||||
* **Warning**, if path is a symlink, it will be resolved !
|
||||
* So `C:\Dev\wamp64\www\itop-dev/env-production/itop-hub-connector/land.php`
|
||||
* Will become `C:\Dev\wamp64\www\itop-dev\datamodels\2.x\itop-hub-connector\land.php`
|
||||
* @param string $sBasePath for example `/var/www/html/itop/data/`
|
||||
*
|
||||
* @return bool|string false if path :
|
||||
* * invalid
|
||||
@@ -2230,7 +2389,10 @@ class utils
|
||||
* * not contained in base path
|
||||
* Otherwise return the real path (see realpath())
|
||||
*
|
||||
* @uses \realpath()
|
||||
* @uses static::StartsWith
|
||||
* @since 2.6.5 2.7.0 N°2538
|
||||
* @since 2.7.5 details in PHPDoc about symlink resolution
|
||||
*/
|
||||
final public static function RealPath($sPath, $sBasePath)
|
||||
{
|
||||
@@ -2337,4 +2499,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');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"ext-soap": "*",
|
||||
"combodo/tcpdf": "6.3.5",
|
||||
"nikic/php-parser": "^3.1",
|
||||
"pear/archive_tar": "1.4.10",
|
||||
"pear/archive_tar": "1.4.13",
|
||||
"pelago/emogrifier": "2.1.0",
|
||||
"scssphp/scssphp": "1.0.6",
|
||||
"swiftmailer/swiftmailer": "5.4.12",
|
||||
|
||||
29
composer.lock
generated
29
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "27af144ea2acf2c138f587052a4ceddc",
|
||||
"content-hash": "8c7f3127435b1afb67965369c50d0898",
|
||||
"packages": [
|
||||
{
|
||||
"name": "combodo/tcpdf",
|
||||
@@ -168,16 +168,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pear/archive_tar",
|
||||
"version": "1.4.10",
|
||||
"version": "1.4.13",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pear/Archive_Tar.git",
|
||||
"reference": "bbb4f10f71a1da2715ec6d9a683f4f23c507a49b"
|
||||
"reference": "2b87b41178cc6d4ad3cba678a46a1cae49786011"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pear/Archive_Tar/zipball/bbb4f10f71a1da2715ec6d9a683f4f23c507a49b",
|
||||
"reference": "bbb4f10f71a1da2715ec6d9a683f4f23c507a49b",
|
||||
"url": "https://api.github.com/repos/pear/Archive_Tar/zipball/2b87b41178cc6d4ad3cba678a46a1cae49786011",
|
||||
"reference": "2b87b41178cc6d4ad3cba678a46a1cae49786011",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -230,7 +230,21 @@
|
||||
"archive",
|
||||
"tar"
|
||||
],
|
||||
"time": "2020-09-15T14:13:23+00:00"
|
||||
"support": {
|
||||
"issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Archive_Tar",
|
||||
"source": "https://github.com/pear/Archive_Tar"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/mrook",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/michielrook",
|
||||
"type": "patreon"
|
||||
}
|
||||
],
|
||||
"time": "2021-02-16T10:50:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pear/console_getopt",
|
||||
@@ -2590,5 +2604,6 @@
|
||||
"platform-dev": [],
|
||||
"platform-overrides": {
|
||||
"php": "5.6.0"
|
||||
}
|
||||
},
|
||||
"plugin-api-version": "2.0.0"
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ class MySQLException extends CoreException
|
||||
*/
|
||||
public function __construct($sIssue, $aContext, $oException = null, $oMysqli = null)
|
||||
{
|
||||
|
||||
if ($oException != null)
|
||||
{
|
||||
$aContext['mysql_errno'] = $oException->getCode();
|
||||
@@ -58,6 +59,11 @@ class MySQLException extends CoreException
|
||||
$aContext['mysql_error'] = CMDBSource::GetError();
|
||||
}
|
||||
parent::__construct($sIssue, $aContext);
|
||||
//if is connection error, don't log the default message with password in
|
||||
if (mysqli_connect_errno()) {
|
||||
error_log($this->message);
|
||||
error_reporting(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +154,17 @@ class CMDBSource
|
||||
|
||||
/** @var mysqli $m_oMysqli */
|
||||
protected static $m_oMysqli;
|
||||
/**
|
||||
* The mysqli object is really hard to mock ! This attribute is used only in certain methods, so that we can mock only a very little subset of the mysqli object.
|
||||
* We are setting it in {@see Init}, by default it is a copy of {@see $m_oMysqli}
|
||||
* The mock can be injected using the setter {@see SetMySQLiForQuery}
|
||||
*
|
||||
* @var mysqli $oMySQLiForQuery
|
||||
* @see GetMySQLiForQuery
|
||||
* @see SetMySQLiForQuery
|
||||
* @since 2.7.5 N°3513 new var to allow mock in tests ({@see \Combodo\iTop\Test\UnitTest\Core\TransactionsTest})
|
||||
*/
|
||||
protected static $oMySQLiForQuery;
|
||||
|
||||
/**
|
||||
* @var int number of level for nested transactions : 0 if no transaction was ever opened, +1 for each 'START TRANSACTION' sent
|
||||
@@ -214,6 +231,7 @@ class CMDBSource
|
||||
self::$m_sDBTlsCA = empty($sTlsCA) ? null : $sTlsCA;
|
||||
|
||||
self::$m_oMysqli = self::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, true);
|
||||
self::SetMySQLiForQuery(self::$m_oMysqli);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -227,12 +245,12 @@ class CMDBSource
|
||||
*
|
||||
* @return \mysqli
|
||||
* @throws \MySQLException
|
||||
*
|
||||
* @uses IsOpenedDbConnectionUsingTls when asking for a TLS connection, to check if it was really opened using TLS
|
||||
*/
|
||||
public static function GetMysqliInstance(
|
||||
$sDbHost, $sUser, $sPwd, $sSource = '', $bTlsEnabled = false, $sTlsCa = null, $bCheckTlsAfterConnection = false
|
||||
) {
|
||||
$oMysqli = null;
|
||||
|
||||
$sServer = null;
|
||||
$iPort = null;
|
||||
self::InitServerAndPort($sDbHost, $sServer, $iPort);
|
||||
@@ -262,7 +280,7 @@ class CMDBSource
|
||||
}
|
||||
catch(mysqli_sql_exception $e)
|
||||
{
|
||||
throw new MySQLException('Could not connect to the DB server', array('host' => $sServer, 'user' => $sUser), $e);
|
||||
throw new MySQLException('Could not connect to the DB server', array('host' => $sServer, 'user' => $sUser),$e);
|
||||
}
|
||||
|
||||
if ($bTlsEnabled
|
||||
@@ -331,41 +349,41 @@ class CMDBSource
|
||||
* parameters were used.<br>
|
||||
* This method can be called to ensure that the DB connection really uses TLS.
|
||||
*
|
||||
* <p>We're using this object connection : {@link self::$m_oMysqli}
|
||||
* <p>We're using our own mysqli instance to do the check as this check is done when creating the mysqli instance : the consumer
|
||||
* might want a dedicated object, and if so we don't want to overwrite the one saved in CMDBSource !<br>
|
||||
* This is the case for example with {@see \iTopMutex} !
|
||||
*
|
||||
* @param \mysqli $oMysqli
|
||||
*
|
||||
* @return boolean true if the connection was really established using TLS
|
||||
* @return boolean true if the connection was really established using TLS, false otherwise
|
||||
* @throws \MySQLException
|
||||
*
|
||||
* @used-by GetMysqliInstance
|
||||
* @uses IsMySqlVarNonEmpty
|
||||
* @uses 'ssl_version' MySQL var
|
||||
* @uses 'ssl_cipher' MySQL var
|
||||
*/
|
||||
private static function IsOpenedDbConnectionUsingTls($oMysqli)
|
||||
{
|
||||
if (self::$m_oMysqli == null)
|
||||
{
|
||||
self::$m_oMysqli = $oMysqli;
|
||||
}
|
||||
|
||||
$bNonEmptySslVersionVar = self::IsMySqlVarNonEmpty('ssl_version');
|
||||
$bNonEmptySslCipherVar = self::IsMySqlVarNonEmpty('ssl_cipher');
|
||||
$bNonEmptySslVersionVar = self::IsMySqlVarNonEmpty('ssl_version', $oMysqli);
|
||||
$bNonEmptySslCipherVar = self::IsMySqlVarNonEmpty('ssl_cipher', $oMysqli);
|
||||
|
||||
return ($bNonEmptySslVersionVar && $bNonEmptySslCipherVar);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sVarName
|
||||
* @param mysqli $oMysqli connection to use for the query
|
||||
*
|
||||
* @return bool
|
||||
* @throws \MySQLException
|
||||
*
|
||||
* @uses SHOW STATUS queries
|
||||
* @uses 'SHOW SESSION STATUS' queries
|
||||
*/
|
||||
private static function IsMySqlVarNonEmpty($sVarName)
|
||||
private static function IsMySqlVarNonEmpty($sVarName, $oMysqli)
|
||||
{
|
||||
try
|
||||
{
|
||||
$sResult = self::QueryToScalar("SHOW SESSION STATUS LIKE '$sVarName'", 1);
|
||||
$sResult = self::QueryToScalar("SHOW SESSION STATUS LIKE '$sVarName'", 1, $oMysqli);
|
||||
}
|
||||
catch (MySQLQueryHasNoResultException $e)
|
||||
{
|
||||
@@ -530,6 +548,24 @@ class CMDBSource
|
||||
return self::$m_oMysqli;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
private static function GetMySQLiForQuery()
|
||||
{
|
||||
return self::$oMySQLiForQuery;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used for test purpose (mysqli mock)
|
||||
* @param $oMySQLi
|
||||
*/
|
||||
private static function SetMySQLiForQuery($oMySQLi)
|
||||
{
|
||||
self::$oMySQLiForQuery = $oMySQLi;
|
||||
}
|
||||
|
||||
public static function GetErrNo()
|
||||
{
|
||||
if (self::$m_oMysqli->errno != 0)
|
||||
@@ -665,10 +701,15 @@ class CMDBSource
|
||||
*/
|
||||
private static function DBQuery($sSql)
|
||||
{
|
||||
$sShortSQL = substr(preg_replace("/\s+/", " ", substr($sSql, 0, 180)), 0, 150);
|
||||
if (substr_compare($sShortSQL, "SELECT", 0, strlen("SELECT")) !== 0) {
|
||||
IssueLog::Trace("$sShortSQL", 'cmdbsource');
|
||||
}
|
||||
|
||||
$oKPI = new ExecutionKPI();
|
||||
try
|
||||
{
|
||||
$oResult = self::$m_oMysqli->query($sSql);
|
||||
$oResult = self::GetMySQLiForQuery()->query($sSql);
|
||||
}
|
||||
catch (mysqli_sql_exception $e)
|
||||
{
|
||||
@@ -680,7 +721,7 @@ class CMDBSource
|
||||
{
|
||||
$aContext = array('query' => $sSql);
|
||||
|
||||
$iMySqlErrorNo = self::$m_oMysqli->errno;
|
||||
$iMySqlErrorNo = self::GetMySQLiForQuery()->errno;
|
||||
$aMySqlHasGoneAwayErrorCodes = MySQLHasGoneAwayException::getErrorCodes();
|
||||
if (in_array($iMySqlErrorNo, $aMySqlHasGoneAwayErrorCodes))
|
||||
{
|
||||
@@ -702,7 +743,7 @@ class CMDBSource
|
||||
private static function LogDeadLock(Exception $e)
|
||||
{
|
||||
// checks MySQL error code
|
||||
$iMySqlErrorNo = self::$m_oMysqli->errno;
|
||||
$iMySqlErrorNo = self::GetMySQLiForQuery()->errno;
|
||||
if (!in_array($iMySqlErrorNo, array(self::MYSQL_ERRNO_WAIT_TIMEOUT, self::MYSQL_ERRNO_DEADLOCK)))
|
||||
{
|
||||
return;
|
||||
@@ -710,7 +751,7 @@ class CMDBSource
|
||||
|
||||
// Get error info
|
||||
$sUser = UserRights::GetUser();
|
||||
$oError = self::$m_oMysqli->query('SHOW ENGINE INNODB STATUS');
|
||||
$oError = self::GetMySQLiForQuery()->query('SHOW ENGINE INNODB STATUS');
|
||||
if ($oError !== false)
|
||||
{
|
||||
$aData = $oError->fetch_all(MYSQLI_ASSOC);
|
||||
@@ -732,7 +773,7 @@ class CMDBSource
|
||||
);
|
||||
DeadLockLog::Info($sMessage, $iMySqlErrorNo, $aLogContext);
|
||||
|
||||
IssueLog::Error($sMessage, 'DeadLock', $e->getMessage());
|
||||
IssueLog::Error($sMessage, LogChannels::DEADLOCK, $e->getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -748,10 +789,15 @@ class CMDBSource
|
||||
*/
|
||||
private static function StartTransaction()
|
||||
{
|
||||
$aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3);
|
||||
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()';
|
||||
$bHasExistingTransactions = self::IsInsideTransaction();
|
||||
if (!$bHasExistingTransactions)
|
||||
{
|
||||
IssueLog::Trace("START TRANSACTION $sCaller", 'cmdbsource');
|
||||
self::DBQuery('START TRANSACTION');
|
||||
} else {
|
||||
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") START TRANSACTION $sCaller", 'cmdbsource');
|
||||
}
|
||||
|
||||
self::AddTransactionLevel();
|
||||
@@ -769,9 +815,12 @@ class CMDBSource
|
||||
*/
|
||||
private static function Commit()
|
||||
{
|
||||
$aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3);
|
||||
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()';
|
||||
if (!self::IsInsideTransaction())
|
||||
{
|
||||
// should not happen !
|
||||
IssueLog::Error("No Transaction COMMIT $sCaller", 'cmdbsource');
|
||||
throw new MySQLNoTransactionException('Trying to commit transaction whereas none have been started !', null);
|
||||
}
|
||||
|
||||
@@ -779,8 +828,10 @@ class CMDBSource
|
||||
|
||||
if (self::IsInsideTransaction())
|
||||
{
|
||||
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") COMMIT $sCaller", 'cmdbsource');
|
||||
return;
|
||||
}
|
||||
IssueLog::Trace("COMMIT $sCaller", 'cmdbsource');
|
||||
self::DBQuery('COMMIT');
|
||||
}
|
||||
|
||||
@@ -799,17 +850,22 @@ class CMDBSource
|
||||
*/
|
||||
private static function Rollback()
|
||||
{
|
||||
$aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3);
|
||||
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()';
|
||||
if (!self::IsInsideTransaction())
|
||||
{
|
||||
// should not happen !
|
||||
IssueLog::Error("No Transaction ROLLBACK $sCaller", 'cmdbsource');
|
||||
throw new MySQLNoTransactionException('Trying to commit transaction whereas none have been started !', null);
|
||||
}
|
||||
self::RemoveLastTransactionLevel();
|
||||
if (self::IsInsideTransaction())
|
||||
{
|
||||
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") ROLLBACK $sCaller", 'cmdbsource');
|
||||
return;
|
||||
}
|
||||
|
||||
IssueLog::Trace("ROLLBACK $sCaller", 'cmdbsource');
|
||||
self::DBQuery('ROLLBACK');
|
||||
}
|
||||
|
||||
@@ -859,6 +915,17 @@ class CMDBSource
|
||||
self::$m_iTransactionLevel = 0;
|
||||
}
|
||||
|
||||
public static function IsDeadlockException(Exception $e)
|
||||
{
|
||||
while ($e instanceof Exception) {
|
||||
if (($e instanceof MySQLException) && ($e->getCode() == 1213)) {
|
||||
return true;
|
||||
}
|
||||
$e = $e->getPrevious();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @deprecated 2.7.0 N°1627 use ItopCounter instead
|
||||
@@ -912,17 +979,21 @@ class CMDBSource
|
||||
/**
|
||||
* @param string $sSql
|
||||
* @param int $iCol beginning at 0
|
||||
* @param mysqli $oMysqli if not null will query using this connection, otherwise will use {@see GetMySQLiForQuery}
|
||||
*
|
||||
* @return string corresponding cell content on the first line
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLQueryHasNoResultException
|
||||
* @since 2.7.5-2 2.7.6 3.0.0 N°4215 new optional mysqli param
|
||||
*/
|
||||
public static function QueryToScalar($sSql, $iCol = 0)
|
||||
public static function QueryToScalar($sSql, $iCol = 0, $oMysqli = null)
|
||||
{
|
||||
$oMysqliToQuery = (is_null($oMysqli)) ? self::GetMySQLiForQuery() : $oMysqli;
|
||||
|
||||
$oKPI = new ExecutionKPI();
|
||||
try
|
||||
{
|
||||
$oResult = self::$m_oMysqli->query($sSql);
|
||||
$oResult = $oMysqliToQuery->query($sSql);
|
||||
}
|
||||
catch(mysqli_sql_exception $e)
|
||||
{
|
||||
@@ -962,7 +1033,7 @@ class CMDBSource
|
||||
$oKPI = new ExecutionKPI();
|
||||
try
|
||||
{
|
||||
$oResult = self::$m_oMysqli->query($sSql);
|
||||
$oResult = self::GetMySQLiForQuery()->query($sSql);
|
||||
}
|
||||
catch(mysqli_sql_exception $e)
|
||||
{
|
||||
@@ -1044,7 +1115,7 @@ class CMDBSource
|
||||
{
|
||||
try
|
||||
{
|
||||
$oResult = self::$m_oMysqli->query($sSql);
|
||||
$oResult = self::GetMySQLiForQuery()->query($sSql);
|
||||
}
|
||||
catch(mysqli_sql_exception $e)
|
||||
{
|
||||
@@ -1181,17 +1252,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 +1317,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
|
||||
@@ -1480,7 +1602,7 @@ class CMDBSource
|
||||
$sSql = "SELECT * FROM `$sTable`";
|
||||
try
|
||||
{
|
||||
$oResult = self::$m_oMysqli->query($sSql);
|
||||
$oResult = self::GetMySQLiForQuery()->query($sSql);
|
||||
}
|
||||
catch(mysqli_sql_exception $e)
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
|
||||
@@ -149,14 +149,14 @@ class CoreCannotSaveObjectException extends CoreException
|
||||
*
|
||||
* @param array $aContextData containing at least those keys : issues, id, class
|
||||
*/
|
||||
public function __construct($aContextData)
|
||||
public function __construct($aContextData, $oPrevious = null)
|
||||
{
|
||||
$this->aIssues = $aContextData['issues'];
|
||||
$this->iObjectId = $aContextData['id'];
|
||||
$this->sObjectClass = $aContextData['class'];
|
||||
|
||||
$sIssues = implode(', ', $this->aIssues);
|
||||
parent::__construct($sIssues, $aContextData);
|
||||
parent::__construct($sIssues, $aContextData, '', $oPrevious);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2739,50 +2739,72 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
}
|
||||
|
||||
$iTransactionRetry = 1;
|
||||
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||
try
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('START TRANSACTION');
|
||||
}
|
||||
|
||||
// First query built upon on the root class, because the ID must be created first
|
||||
$this->m_iKey = $this->DBInsertSingleTable($sRootClass);
|
||||
|
||||
// Then do the leaf class, if different from the root class
|
||||
if ($sClass != $sRootClass)
|
||||
{
|
||||
$this->DBInsertSingleTable($sClass);
|
||||
}
|
||||
|
||||
// Then do the other classes
|
||||
foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass)
|
||||
{
|
||||
if ($sParentClass == $sRootClass)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$this->DBInsertSingleTable($sParentClass);
|
||||
}
|
||||
|
||||
$this->OnObjectKeyReady();
|
||||
|
||||
$this->DBWriteLinks();
|
||||
$this->WriteExternalAttributes();
|
||||
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('COMMIT');
|
||||
}
|
||||
// TODO Deep clone this object before the transaction (to use it in case of rollback)
|
||||
// $iTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
|
||||
$iTransactionRetryCount = 1;
|
||||
$iTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms');
|
||||
$iTransactionRetry = $iTransactionRetryCount;
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
while ($iTransactionRetry > 0) {
|
||||
try {
|
||||
$iTransactionRetry--;
|
||||
if ($bIsTransactionEnabled) {
|
||||
CMDBSource::Query('START TRANSACTION');
|
||||
}
|
||||
|
||||
// First query built upon on the root class, because the ID must be created first
|
||||
$this->m_iKey = $this->DBInsertSingleTable($sRootClass);
|
||||
|
||||
// Then do the leaf class, if different from the root class
|
||||
if ($sClass != $sRootClass) {
|
||||
$this->DBInsertSingleTable($sClass);
|
||||
}
|
||||
|
||||
// Then do the other classes
|
||||
foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass) {
|
||||
if ($sParentClass == $sRootClass) {
|
||||
continue;
|
||||
}
|
||||
$this->DBInsertSingleTable($sParentClass);
|
||||
}
|
||||
|
||||
$this->OnObjectKeyReady();
|
||||
|
||||
$this->DBWriteLinks();
|
||||
$this->WriteExternalAttributes();
|
||||
|
||||
if ($bIsTransactionEnabled) {
|
||||
CMDBSource::Query('COMMIT');
|
||||
}
|
||||
break;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
if (!CMDBSource::IsInsideTransaction() && CMDBSource::IsDeadlockException($e))
|
||||
{
|
||||
// Deadlock found when trying to get lock; try restarting transaction (only in main transaction)
|
||||
if ($iTransactionRetry > 0)
|
||||
{
|
||||
// wait and retry
|
||||
IssueLog::Error("Insert TRANSACTION Retrying...");
|
||||
usleep(random_int(1, 5) * 1000 * $iTransactionRetryDelay * ($iTransactionRetryCount - $iTransactionRetry));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
IssueLog::Error("Insert Deadlock TRANSACTION prevention failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->m_bIsInDB = true;
|
||||
@@ -3123,13 +3145,11 @@ abstract class DBObject implements iDisplay
|
||||
array(), $aParams);
|
||||
while ($oTrigger = $oSet->Fetch())
|
||||
{
|
||||
/** @var \Trigger $oTrigger */
|
||||
try
|
||||
{
|
||||
/** @var \TriggerOnObjectUpdate $oTrigger */
|
||||
try {
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
catch (Exception $e) {
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
@@ -3158,9 +3178,11 @@ abstract class DBObject implements iDisplay
|
||||
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
$iIsTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
|
||||
// TODO Deep clone this object before the transaction (to use it in case of rollback)
|
||||
// $iTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
|
||||
$iTransactionRetryCount = 1;
|
||||
$iIsTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms');
|
||||
$iTransactionRetry = $iIsTransactionRetryCount;
|
||||
$iTransactionRetry = $iTransactionRetryCount;
|
||||
}
|
||||
while ($iTransactionRetry > 0)
|
||||
{
|
||||
@@ -3228,13 +3250,6 @@ abstract class DBObject implements iDisplay
|
||||
$this->DBWriteLinks();
|
||||
$this->WriteExternalAttributes();
|
||||
|
||||
// following lines are resetting changes (so after this {@see DBObject::ListChanges()} won't return changes anymore)
|
||||
// new values are already in the object (call {@see DBObject::Get()} to get them)
|
||||
// call {@see DBObject::ListPreviousValuesForUpdatedAttributes()} to get changed fields and previous values
|
||||
$this->m_bDirty = false;
|
||||
$this->m_aTouchedAtt = array();
|
||||
$this->m_aModifiedAtt = array();
|
||||
|
||||
if (count($aChanges) != 0)
|
||||
{
|
||||
$this->RecordAttChanges($aChanges, $aOriginalValues);
|
||||
@@ -3248,18 +3263,18 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
catch (MySQLException $e)
|
||||
{
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
if ($e->getCode() == 1213)
|
||||
if (!CMDBSource::IsInsideTransaction() && CMDBSource::IsDeadlockException($e))
|
||||
{
|
||||
// Deadlock found when trying to get lock; try restarting transaction
|
||||
IssueLog::Error($e->getMessage());
|
||||
// Deadlock found when trying to get lock; try restarting transaction (only in main transaction)
|
||||
if ($iTransactionRetry > 0)
|
||||
{
|
||||
// wait and retry
|
||||
IssueLog::Error("Update TRANSACTION Retrying...");
|
||||
usleep(random_int(1, 5) * 1000 * $iIsTransactionRetryDelay * ($iIsTransactionRetryCount - $iTransactionRetry));
|
||||
usleep(random_int(1, 5) * 1000 * $iIsTransactionRetryDelay * ($iTransactionRetryCount - $iTransactionRetry));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
@@ -3273,10 +3288,11 @@ abstract class DBObject implements iDisplay
|
||||
'id' => $this->GetKey(),
|
||||
'class' => get_class($this),
|
||||
'issues' => $aErrors
|
||||
));
|
||||
), $e);
|
||||
}
|
||||
catch (CoreCannotSaveObjectException $e)
|
||||
{
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
@@ -3285,6 +3301,7 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
@@ -3298,6 +3315,13 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
}
|
||||
|
||||
// following lines are resetting changes (so after this {@see DBObject::ListChanges()} won't return changes anymore)
|
||||
// new values are already in the object (call {@see DBObject::Get()} to get them)
|
||||
// call {@see DBObject::ListPreviousValuesForUpdatedAttributes()} to get changed fields and previous values
|
||||
$this->m_bDirty = false;
|
||||
$this->m_aTouchedAtt = array();
|
||||
$this->m_aModifiedAtt = array();
|
||||
|
||||
try
|
||||
{
|
||||
$this->AfterUpdate();
|
||||
@@ -3496,9 +3520,11 @@ abstract class DBObject implements iDisplay
|
||||
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
$iIsTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
|
||||
$iIsTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms');
|
||||
$iTransactionRetry = $iIsTransactionRetryCount;
|
||||
// TODO Deep clone this object before the transaction (to use it in case of rollback)
|
||||
// $iTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
|
||||
$iTransactionRetryCount = 1;
|
||||
$iTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms');
|
||||
$iTransactionRetry = $iTransactionRetryCount;
|
||||
}
|
||||
while ($iTransactionRetry > 0)
|
||||
{
|
||||
@@ -3521,18 +3547,18 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
catch (MySQLException $e)
|
||||
{
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
if ($e->getCode() == 1213)
|
||||
if (!CMDBSource::IsInsideTransaction() && CMDBSource::IsDeadlockException($e))
|
||||
{
|
||||
// Deadlock found when trying to get lock; try restarting transaction
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($iTransactionRetry > 0)
|
||||
{
|
||||
// wait and retry
|
||||
IssueLog::Error("Delete TRANSACTION Retrying...");
|
||||
usleep(random_int(1, 5) * 1000 * $iIsTransactionRetryDelay * ($iIsTransactionRetryCount - $iTransactionRetry));
|
||||
usleep(random_int(1, 5) * 1000 * $iTransactionRetryDelay * ($iTransactionRetryCount - $iTransactionRetry));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
@@ -3709,16 +3735,22 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
/**
|
||||
* Apply a stimulus (workflow)
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param string $sStimulusCode
|
||||
* @param bool $bDoNotWrite
|
||||
*
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param string $sStimulusCode
|
||||
* @param bool $bDoNotWrite if true we won't save the object !
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
*
|
||||
* @throws CoreException
|
||||
* @throws CoreUnexpectedValue
|
||||
*
|
||||
* @uses \AttributeStopWatch::Start
|
||||
* @uses \AttributeStopWatch::Stop
|
||||
* @uses \DBObject::DBWrite
|
||||
* @uses \TriggerOnStateLeave::DoActivate
|
||||
* @uses \TriggerOnStateEnter::DoActivate
|
||||
*/
|
||||
public function ApplyStimulus($sStimulusCode, $bDoNotWrite = false)
|
||||
{
|
||||
@@ -3842,8 +3874,7 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
}
|
||||
|
||||
if (!$bDoNotWrite)
|
||||
{
|
||||
if (!$bDoNotWrite) {
|
||||
$this->DBWrite();
|
||||
}
|
||||
|
||||
@@ -3851,30 +3882,26 @@ abstract class DBObject implements iDisplay
|
||||
$aParams = array(
|
||||
'class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL),
|
||||
'previous_state' => $sPreviousState,
|
||||
'new_state' => $sNewState);
|
||||
'new_state' => $sNewState,
|
||||
);
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateLeave AS t WHERE t.target_class IN (:class_list) AND t.state=:previous_state"), array(), $aParams);
|
||||
while ($oTrigger = $oSet->Fetch())
|
||||
{
|
||||
/** @var \Trigger $oTrigger */
|
||||
try
|
||||
{
|
||||
while ($oTrigger = $oSet->Fetch()) {
|
||||
/** @var \TriggerOnStateLeave $oTrigger */
|
||||
try {
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
catch (Exception $e) {
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateEnter AS t WHERE t.target_class IN (:class_list) AND t.state=:new_state"), array(), $aParams);
|
||||
while ($oTrigger = $oSet->Fetch())
|
||||
{
|
||||
/** @var \Trigger $oTrigger */
|
||||
try{
|
||||
while ($oTrigger = $oSet->Fetch()) {
|
||||
/** @var \TriggerOnStateEnter $oTrigger */
|
||||
try {
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
catch (Exception $e) {
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1004,10 +1004,8 @@ abstract class DBSearch
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a SQL query from the current search
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* Generate a SQL query from the current search
|
||||
*
|
||||
* @param array $aOrderBy Array of '[<classalias>.]attcode' => bAscending
|
||||
* @param array $aArgs
|
||||
* @param null $aAttToLoad
|
||||
@@ -1015,12 +1013,16 @@ abstract class DBSearch
|
||||
* @param int $iLimitCount
|
||||
* @param int $iLimitStart
|
||||
* @param bool $bGetCount
|
||||
* @param bool $bBeautifulSQL
|
||||
*
|
||||
* @return string
|
||||
* @throws CoreException
|
||||
* @throws Exception
|
||||
* @throws MissingQueryArgument
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
* @throws \MissingQueryArgument
|
||||
* @internal
|
||||
*
|
||||
*/
|
||||
public function MakeSelectQuery($aOrderBy = array(), $aArgs = array(), $aAttToLoad = null, $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false)
|
||||
public function MakeSelectQuery($aOrderBy = array(), $aArgs = array(), $aAttToLoad = null, $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false, $bBeautifulSQL = true)
|
||||
{
|
||||
// Check the order by specification, and prefix with the class alias
|
||||
// and make sure that the ordering columns are going to be selected
|
||||
@@ -1085,8 +1087,7 @@ abstract class DBSearch
|
||||
}
|
||||
try
|
||||
{
|
||||
// $bBeautifulSQL = self::$m_bTraceQueries || self::$m_bDebugQuery || self::$m_bIndentQueries;
|
||||
$sRes = $oSQLQuery->RenderSelect($aOrderSpec, $aScalarArgs, $iLimitCount, $iLimitStart, $bGetCount, true);
|
||||
$sRes = $oSQLQuery->RenderSelect($aOrderSpec, $aScalarArgs, $iLimitCount, $iLimitStart, $bGetCount, $bBeautifulSQL);
|
||||
if ($sClassAlias == '_itop_')
|
||||
{
|
||||
IssueLog::Info('SQL Query (_itop_): '.$sRes);
|
||||
@@ -1186,6 +1187,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());
|
||||
|
||||
|
||||
@@ -176,31 +176,29 @@ class InlineImage extends DBObject
|
||||
$sOQL = 'SELECT InlineImage WHERE temp_id = :temp_id';
|
||||
$oSearch = DBObjectSearch::FromOQL($sOQL);
|
||||
$oSet = new DBObjectSet($oSearch, array(), array('temp_id' => $sTempId));
|
||||
$aInlineImagesId = array();
|
||||
while($oInlineImage = $oSet->Fetch())
|
||||
{
|
||||
$aInlineImagesId[] = $oInlineImage->GetKey();
|
||||
$aInlineImagesId = array();
|
||||
while ($oInlineImage = $oSet->Fetch()) {
|
||||
$aInlineImagesId[] = $oInlineImage->GetKey();
|
||||
$oInlineImage->SetItem($oObject);
|
||||
$oInlineImage->Set('temp_id', '');
|
||||
$oInlineImage->DBUpdate();
|
||||
}
|
||||
IssueLog::Trace('FinalizeInlineImages (see $aInlineImagesId for the id list)', 'InlineImage', array(
|
||||
'$sObjectClass' => get_class($oObject),
|
||||
'$sTransactionId' => $iTransactionId,
|
||||
'$sTempId' => $sTempId,
|
||||
'$aInlineImagesId' => $aInlineImagesId,
|
||||
'$sUser' => UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
));
|
||||
IssueLog::Trace('FinalizeInlineImages (see $aInlineImagesId for the id list)', LogChannels::INLINE_IMAGE, array(
|
||||
'$sObjectClass' => get_class($oObject),
|
||||
'$sTransactionId' => $iTransactionId,
|
||||
'$sTempId' => $sTempId,
|
||||
'$aInlineImagesId' => $aInlineImagesId,
|
||||
'$sUser' => UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
IssueLog::Trace('FinalizeInlineImages "error" $iTransactionId is null', 'InlineImage', array(
|
||||
'$sObjectClass' => get_class($oObject),
|
||||
'$sTransactionId' => $iTransactionId,
|
||||
'$sUser' => UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
));
|
||||
else {
|
||||
IssueLog::Trace('FinalizeInlineImages "error" $iTransactionId is null', LogChannels::INLINE_IMAGE, array(
|
||||
'$sObjectClass' => get_class($oObject),
|
||||
'$sTransactionId' => $iTransactionId,
|
||||
'$sUser' => UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,12 +218,12 @@ class InlineImage extends DBObject
|
||||
$aInlineImagesId[] = $oInlineImage->GetKey();
|
||||
$oInlineImage->DBDelete();
|
||||
}
|
||||
IssueLog::Trace('OnFormCancel', 'InlineImage', array(
|
||||
'$sTempId' => $sTempId,
|
||||
'$aInlineImagesId' => $aInlineImagesId,
|
||||
'$sUser' => UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
));
|
||||
IssueLog::Trace('OnFormCancel', LogChannels::INLINE_IMAGE, array(
|
||||
'$sTempId' => $sTempId,
|
||||
'$aInlineImagesId' => $aInlineImagesId,
|
||||
'$sUser' => UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -565,17 +563,17 @@ JS
|
||||
|
||||
protected function AfterInsert()
|
||||
{
|
||||
IssueLog::Trace(__METHOD__, 'InlineImage', array(
|
||||
'id' => $this->GetKey(),
|
||||
'expire' => $this->Get('expire'),
|
||||
'temp_id' => $this->Get('temp_id'),
|
||||
'item_class' => $this->Get('item_class'),
|
||||
'item_id' => $this->Get('item_id'),
|
||||
'item_org_id' => $this->Get('item_org_id'),
|
||||
'secret' => $this->Get('secret'),
|
||||
'user' => $sUser = UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
|
||||
IssueLog::Trace(__METHOD__, LogChannels::INLINE_IMAGE, array(
|
||||
'id' => $this->GetKey(),
|
||||
'expire' => $this->Get('expire'),
|
||||
'temp_id' => $this->Get('temp_id'),
|
||||
'item_class' => $this->Get('item_class'),
|
||||
'item_id' => $this->Get('item_id'),
|
||||
'item_org_id' => $this->Get('item_org_id'),
|
||||
'secret' => $this->Get('secret'),
|
||||
'user' => $sUser = UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
|
||||
));
|
||||
|
||||
parent::AfterInsert();
|
||||
@@ -583,17 +581,17 @@ JS
|
||||
|
||||
protected function AfterUpdate()
|
||||
{
|
||||
IssueLog::Trace(__METHOD__, 'InlineImage', array(
|
||||
'id' => $this->GetKey(),
|
||||
'expire' => $this->Get('expire'),
|
||||
'temp_id' => $this->Get('temp_id'),
|
||||
'item_class' => $this->Get('item_class'),
|
||||
'item_id' => $this->Get('item_id'),
|
||||
'item_org_id' => $this->Get('item_org_id'),
|
||||
'secret' => $this->Get('secret'),
|
||||
'user' => $sUser = UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
|
||||
IssueLog::Trace(__METHOD__, LogChannels::INLINE_IMAGE, array(
|
||||
'id' => $this->GetKey(),
|
||||
'expire' => $this->Get('expire'),
|
||||
'temp_id' => $this->Get('temp_id'),
|
||||
'item_class' => $this->Get('item_class'),
|
||||
'item_id' => $this->Get('item_id'),
|
||||
'item_org_id' => $this->Get('item_org_id'),
|
||||
'secret' => $this->Get('secret'),
|
||||
'user' => $sUser = UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
|
||||
));
|
||||
|
||||
parent::AfterUpdate();
|
||||
@@ -601,17 +599,17 @@ JS
|
||||
|
||||
protected function AfterDelete()
|
||||
{
|
||||
IssueLog::Trace(__METHOD__, 'InlineImage', array(
|
||||
'id' => $this->GetKey(),
|
||||
'expire' => $this->Get('expire'),
|
||||
'temp_id' => $this->Get('temp_id'),
|
||||
'item_class' => $this->Get('item_class'),
|
||||
'item_id' => $this->Get('item_id'),
|
||||
'item_org_id' => $this->Get('item_org_id'),
|
||||
'secret' => $this->Get('secret'),
|
||||
'user' => $sUser = UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
|
||||
IssueLog::Trace(__METHOD__, LogChannels::INLINE_IMAGE, array(
|
||||
'id' => $this->GetKey(),
|
||||
'expire' => $this->Get('expire'),
|
||||
'temp_id' => $this->Get('temp_id'),
|
||||
'item_class' => $this->Get('item_class'),
|
||||
'item_id' => $this->Get('item_id'),
|
||||
'item_org_id' => $this->Get('item_org_id'),
|
||||
'secret' => $this->Get('secret'),
|
||||
'user' => $sUser = UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
|
||||
));
|
||||
|
||||
parent::AfterDelete();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,10 +398,10 @@ class MonthlyRotatingLogFileNameBuilder extends RotatingLogFileNameBuilder
|
||||
*/
|
||||
protected function GetFileSuffix($oDate)
|
||||
{
|
||||
$sWeekYear = $oDate->format('o');
|
||||
$sWeekNumber = $oDate->format('m');
|
||||
$sMonthYear = $oDate->format('o');
|
||||
$sMonthNumber = $oDate->format('m');
|
||||
|
||||
return $sWeekYear.'-month'.$sWeekNumber;
|
||||
return $sMonthYear.'-month'.$sMonthNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -502,6 +502,7 @@ class FileLog
|
||||
protected function Write($sText, $sLevel = '', $sChannel = '', $aContext = array())
|
||||
{
|
||||
$sTextPrefix = empty($sLevel) ? '' : (str_pad($sLevel, 7).' | ');
|
||||
$sTextPrefix .= str_pad(UserRights::GetUserId(), 5)." | ";
|
||||
$sTextSuffix = empty($sChannel) ? '' : " | $sChannel";
|
||||
$sText = "{$sTextPrefix}{$sText}{$sTextSuffix}";
|
||||
$sLogFilePath = $this->oFileNameBuilder->GetLogFilePath();
|
||||
@@ -516,12 +517,9 @@ class FileLog
|
||||
{
|
||||
flock($hLogFile, LOCK_EX);
|
||||
$sDate = date('Y-m-d H:i:s');
|
||||
if (empty($aContext))
|
||||
{
|
||||
if (empty($aContext)) {
|
||||
fwrite($hLogFile, "$sDate | $sText\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sContext = var_export($aContext, true);
|
||||
fwrite($hLogFile, "$sDate | $sText\n$sContext\n");
|
||||
}
|
||||
@@ -532,6 +530,21 @@ class FileLog
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simple enum like class to factorize channels values as constants
|
||||
* Channels are used especially as parameters in {@see \LogAPI} methods
|
||||
*
|
||||
* @since 2.7.5 3.0.0 N°4012
|
||||
*/
|
||||
class LogChannels
|
||||
{
|
||||
const DEADLOCK = 'DeadLock';
|
||||
const INLINE_IMAGE = 'InlineImage';
|
||||
const PORTAL = 'portal';
|
||||
}
|
||||
|
||||
|
||||
abstract class LogAPI
|
||||
{
|
||||
const CHANNEL_DEFAULT = '';
|
||||
@@ -543,11 +556,11 @@ abstract class LogAPI
|
||||
const LEVEL_DEBUG = 'Debug';
|
||||
const LEVEL_TRACE = 'Trace';
|
||||
/**
|
||||
* @var string default log level, can be overrided
|
||||
* @see GetMinLogLevel
|
||||
* @var string default log level, can be overrided
|
||||
* @since 2.7.1 N°2977
|
||||
*/
|
||||
const LEVEL_DEFAULT = self::LEVEL_OK;
|
||||
const LEVEL_DEFAULT = self::LEVEL_OK;
|
||||
|
||||
protected static $aLevelsPriority = array(
|
||||
self::LEVEL_ERROR => 400,
|
||||
@@ -604,36 +617,29 @@ abstract class LogAPI
|
||||
|
||||
public static function Log($sLevel, $sMessage, $sChannel = null, $aContext = array())
|
||||
{
|
||||
if (! static::$m_oFileLog)
|
||||
{
|
||||
if (!static::$m_oFileLog) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! isset(self::$aLevelsPriority[$sLevel]))
|
||||
{
|
||||
if (!isset(self::$aLevelsPriority[$sLevel])) {
|
||||
IssueLog::Error("invalid log level '{$sLevel}'");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_null($sChannel))
|
||||
{
|
||||
if (is_null($sChannel)) {
|
||||
$sChannel = static::CHANNEL_DEFAULT;
|
||||
}
|
||||
|
||||
$sMinLogLevel = self::GetMinLogLevel($sChannel);
|
||||
|
||||
if ($sMinLogLevel === false || $sMinLogLevel === 'false')
|
||||
{
|
||||
if ($sMinLogLevel === false || $sMinLogLevel === 'false') {
|
||||
return;
|
||||
}
|
||||
if (is_string($sMinLogLevel))
|
||||
{
|
||||
if (! isset(self::$aLevelsPriority[$sMinLogLevel]))
|
||||
{
|
||||
if (is_string($sMinLogLevel)) {
|
||||
if (!isset(self::$aLevelsPriority[$sMinLogLevel])) {
|
||||
throw new Exception("invalid configuration for log_level '{$sMinLogLevel}' is not within the list: ".implode(',', array_keys(self::$aLevelsPriority)));
|
||||
}
|
||||
elseif (self::$aLevelsPriority[$sLevel] < self::$aLevelsPriority[$sMinLogLevel])
|
||||
{
|
||||
} elseif (self::$aLevelsPriority[$sLevel] < self::$aLevelsPriority[$sMinLogLevel]) {
|
||||
//priority too low regarding the conf, do not log this
|
||||
return;
|
||||
}
|
||||
@@ -740,7 +746,7 @@ class DeadLockLog extends LogAPI
|
||||
return self::CHANNEL_WAIT_TIMEOUT;
|
||||
break;
|
||||
case 1213:
|
||||
return self::CHANNEL_DEADLOCK_FOUND;
|
||||
return self::CHANNEL_DEADLOCK_FOUND;
|
||||
break;
|
||||
default:
|
||||
return self::CHANNEL_DEFAULT;
|
||||
@@ -749,17 +755,21 @@ class DeadLockLog extends LogAPI
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $iMySQLErrNo will be converted to channel using {@link GetChannelFromMysqlErrorNo}
|
||||
* @param string $sLevel
|
||||
* @param string $sMessage
|
||||
* @param null $iMysqlErroNo
|
||||
* @param int $iMysqlErrorNumber will be converted to channel using {@link GetChannelFromMysqlErrorNo}
|
||||
* @param array $aContext
|
||||
*
|
||||
* @throws \Exception
|
||||
* @noinspection PhpParameterNameChangedDuringInheritanceInspection
|
||||
*
|
||||
* @since 2.7.1 method creation
|
||||
* @since 2.7.5 3.0.0 rename param names and fix phpdoc (thanks Hipska !)
|
||||
*/
|
||||
public static function Log($iMySQLErrNo, $sMessage, $iMysqlErroNo = null, $aContext = array())
|
||||
public static function Log($sLevel, $sMessage, $iMysqlErrorNumber = null, $aContext = array())
|
||||
{
|
||||
$sChannel = self::GetChannelFromMysqlErrorNo($iMysqlErroNo);
|
||||
parent::Log($iMySQLErrNo, $sMessage, $sChannel, $aContext);
|
||||
$sChannel = self::GetChannelFromMysqlErrorNo($iMysqlErrorNumber);
|
||||
parent::Log($sLevel, $sMessage, $sChannel, $aContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -5373,7 +5366,7 @@ abstract class MetaModel
|
||||
$aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable` ADD $sKeyFieldDefinition";
|
||||
if (!$bTableToCreate)
|
||||
{
|
||||
$aAlterTableItems[$sTable][$sKeyField] = "ADD $sKeyFieldDefinition";
|
||||
$aAlterTableItems[$sTable]['field'][$sKeyField] = "ADD $sKeyFieldDefinition";
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -5386,7 +5379,7 @@ abstract class MetaModel
|
||||
$aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable`, DROP PRIMARY KEY, ADD PRIMARY key(`$sKeyField`)";
|
||||
if (!$bTableToCreate)
|
||||
{
|
||||
$aAlterTableItems[$sTable][$sKeyField] = "CHANGE `$sKeyField` $sKeyFieldDefinition";
|
||||
$aAlterTableItems[$sTable]['field'][$sKeyField] = "CHANGE `$sKeyField` $sKeyFieldDefinition";
|
||||
}
|
||||
}
|
||||
if (self::IsAutoIncrementKey($sClass) && !CMDBSource::IsAutoIncrement($sTable, $sKeyField))
|
||||
@@ -5395,7 +5388,7 @@ abstract class MetaModel
|
||||
$aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable` CHANGE `$sKeyField` $sKeyFieldDefinition";
|
||||
if (!$bTableToCreate)
|
||||
{
|
||||
$aAlterTableItems[$sTable][$sKeyField] = "CHANGE `$sKeyField` $sKeyFieldDefinition";
|
||||
$aAlterTableItems[$sTable]['field'][$sKeyField] = "CHANGE `$sKeyField` $sKeyFieldDefinition";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5458,7 +5451,7 @@ abstract class MetaModel
|
||||
}
|
||||
else
|
||||
{
|
||||
$aAlterTableItems[$sTable][$sField] = "ADD $sFieldDefinition";
|
||||
$aAlterTableItems[$sTable]['field'][$sField] = "ADD $sFieldDefinition";
|
||||
$aAdditionalRequests = self::GetAdditionalRequestAfterAlter($sClass, $sTable, $sField);
|
||||
if (!empty($aAdditionalRequests))
|
||||
{
|
||||
@@ -5502,7 +5495,7 @@ abstract class MetaModel
|
||||
}
|
||||
else
|
||||
{
|
||||
$aAlterTableItems[$sTable][] = "ADD $sIndexType `$sIndexName` ($sColumns)";
|
||||
$aAlterTableItems[$sTable]['index'][] = "ADD $sIndexType `$sIndexName` ($sColumns)";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5543,7 +5536,7 @@ abstract class MetaModel
|
||||
if (CMDBSource::HasIndex($sTable, $sField))
|
||||
{
|
||||
$aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` DROP INDEX `$sIndexName`";
|
||||
$aAlterTableItems[$sTable][] = "DROP INDEX `$sIndexName`";
|
||||
$aAlterTableItems[$sTable]['index'][] = "DROP INDEX `$sIndexName`";
|
||||
}
|
||||
$sSugFixAfterChange = "ALTER TABLE `$sTable` ADD $sIndexType `$sIndexName` ($sColumns)";
|
||||
$sAlterTableItemsAfterChange = "ADD $sIndexType `$sIndexName` ($sColumns)";
|
||||
@@ -5552,17 +5545,12 @@ 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";
|
||||
$aAlterTableItems[$sTable]['field'][$sField] = "CHANGE `$sField` $sFieldDefinition";
|
||||
}
|
||||
|
||||
// Create indexes (external keys only... so far)
|
||||
@@ -5577,7 +5565,7 @@ abstract class MetaModel
|
||||
}
|
||||
else
|
||||
{
|
||||
$aAlterTableItems[$sTable][] = $sAlterTableItemsAfterChange;
|
||||
$aAlterTableItems[$sTable]['index'][] = $sAlterTableItemsAfterChange;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5637,9 +5625,12 @@ abstract class MetaModel
|
||||
{
|
||||
$aAlterTableItems[$sTable] = array();
|
||||
}
|
||||
array_unshift($aAlterTableItems[$sTable], "DROP INDEX `$sIndexId`");
|
||||
if (isset($aAlterTableItems[$sTable]['index']))
|
||||
{
|
||||
array_unshift($aAlterTableItems[$sTable]['index'], "DROP INDEX `$sIndexId`");
|
||||
}
|
||||
}
|
||||
$aAlterTableItems[$sTable][] = "ADD INDEX `$sIndexId` ($sColumns)";
|
||||
$aAlterTableItems[$sTable]['index'][] = "ADD INDEX `$sIndexId` ($sColumns)";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5657,7 +5648,7 @@ abstract class MetaModel
|
||||
// without specifying the value of this unknown column
|
||||
$sFieldDefinition = "`$sField` ".CMDBSource::GetFieldType($sTable, $sField).' NULL';
|
||||
$aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` CHANGE `$sField` $sFieldDefinition";
|
||||
$aAlterTableItems[$sTable][$sField] = "CHANGE `$sField` $sFieldDefinition";
|
||||
$aAlterTableItems[$sTable]['field'][$sField] = "CHANGE `$sField` $sFieldDefinition";
|
||||
}
|
||||
$aSugFix[$sClass][$sAttCode][] = "-- Recommended action: ALTER TABLE `$sTable` DROP `$sField`";
|
||||
}
|
||||
@@ -5676,7 +5667,10 @@ abstract class MetaModel
|
||||
{
|
||||
$aAlterTableItems[$sTable] = array();
|
||||
}
|
||||
array_unshift($aAlterTableItems[$sTable], "DROP INDEX `$sIndexId`");
|
||||
if (isset($aAlterTableItems[$sTable]['index']))
|
||||
{
|
||||
array_unshift($aAlterTableItems[$sTable]['index'], "DROP INDEX `$sIndexId`");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5708,8 +5702,16 @@ abstract class MetaModel
|
||||
}
|
||||
foreach ($aAlterTableItems as $sTable => $aChangeList)
|
||||
{
|
||||
$sChangeList = implode(', ', $aChangeList);
|
||||
$aCondensedQueries[] = "ALTER TABLE `$sTable` $sChangeList";
|
||||
if (isset($aAlterTableItems[$sTable]['field']))
|
||||
{
|
||||
$sChangeList = implode(', ', $aChangeList['field']);
|
||||
$aCondensedQueries[] = "ALTER TABLE `$sTable` $sChangeList";
|
||||
}
|
||||
if (isset($aAlterTableItems[$sTable]['index']))
|
||||
{
|
||||
$sChangeList = implode(', ', $aChangeList['index']);
|
||||
$aCondensedQueries[] = "ALTER TABLE `$sTable` $sChangeList";
|
||||
}
|
||||
// Add request right after the ALTER TABLE
|
||||
if (isset($aPostTableAlteration[$sTable]))
|
||||
{
|
||||
|
||||
@@ -242,6 +242,8 @@ class iTopMutex
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws \MySQLException
|
||||
*
|
||||
* @since 2.7.5 3.0.0 N°3968 specify `wait_timeout` for the mutex dedicated connection
|
||||
*/
|
||||
public function InitMySQLSession()
|
||||
{
|
||||
@@ -254,10 +256,36 @@ class iTopMutex
|
||||
|
||||
$this->hDBLink = CMDBSource::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, false);
|
||||
|
||||
if (!$this->hDBLink)
|
||||
{
|
||||
if (!$this->hDBLink) {
|
||||
throw new Exception("Could not connect to the DB server (host=$sServer, user=$sUser): ".mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno().')');
|
||||
}
|
||||
|
||||
// Make sure that the server variable `wait_timeout` is at least 86400 seconds for this connection,
|
||||
// since the lock will be released if/when the connection times out.
|
||||
// Source https://dev.mysql.com/doc/refman/5.7/en/locking-functions.html :
|
||||
// > A lock obtained with GET_LOCK() is released explicitly by executing RELEASE_LOCK() or implicitly when your session terminates
|
||||
//
|
||||
// BEWARE: If you want to check the value of this variable, when run from an interactive console `SHOW VARIABLES LIKE 'wait_timeout'`
|
||||
// will actually returns the value of the variable `interactive_timeout` which may be quite different.
|
||||
$sSql = "SHOW VARIABLES LIKE 'wait_timeout'";
|
||||
$result = mysqli_query($this->hDBLink, $sSql);
|
||||
if (!$result) {
|
||||
throw new Exception("Failed to issue MySQL query '".$sSql."': ".mysqli_error($this->hDBLink).' (mysql errno: '.mysqli_errno($this->hDBLink).')');
|
||||
}
|
||||
if ($aRow = mysqli_fetch_array($result, MYSQLI_BOTH)) {
|
||||
$iTimeout = (int)$aRow[1];
|
||||
} else {
|
||||
mysqli_free_result($result);
|
||||
throw new Exception("No result for query '".$sSql."'");
|
||||
}
|
||||
mysqli_free_result($result);
|
||||
|
||||
if ($iTimeout < 86400) {
|
||||
$result = mysqli_query($this->hDBLink, 'SET SESSION wait_timeout=86400');
|
||||
if ($result === false) {
|
||||
throw new Exception("Failed to issue MySQL query '".$sSql."': ".mysqli_error($this->hDBLink).' (mysql errno: '.mysqli_errno($this->hDBLink).')');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -597,7 +597,7 @@ static public $yy_action = array(
|
||||
** defined, then do no error processing.
|
||||
*/
|
||||
const YYNOCODE = 119;
|
||||
const YYSTACKDEPTH = 100;
|
||||
const YYSTACKDEPTH = 1000;
|
||||
const YYNSTATE = 175;
|
||||
const YYNRULE = 125;
|
||||
const YYERRORSYMBOL = 76;
|
||||
@@ -1175,6 +1175,10 @@ static public $yy_action = array(
|
||||
}
|
||||
/* Here code is inserted which will execute if the parser
|
||||
** stack ever overflows */
|
||||
#line 30 "..\oql-parser.y"
|
||||
|
||||
throw new OQLParserStackOverFlowException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol);
|
||||
#line 1186 "..\oql-parser.php"
|
||||
return;
|
||||
}
|
||||
$yytos = new OQLParser_yyStackEntry;
|
||||
@@ -1474,116 +1478,116 @@ static public $yy_action = array(
|
||||
** function yy_r0($yymsp){ ... } // User supplied code
|
||||
** #line <lineno> <thisfile>
|
||||
*/
|
||||
#line 29 "..\oql-parser.y"
|
||||
#line 37 "..\oql-parser.y"
|
||||
function yy_r0(){ $this->my_result = $this->yystack[$this->yyidx + 0]->minor; }
|
||||
#line 1483 "..\oql-parser.php"
|
||||
#line 33 "..\oql-parser.y"
|
||||
#line 1488 "..\oql-parser.php"
|
||||
#line 41 "..\oql-parser.y"
|
||||
function yy_r3(){
|
||||
$this->_retvalue = new OqlUnionQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor);
|
||||
}
|
||||
#line 1488 "..\oql-parser.php"
|
||||
#line 40 "..\oql-parser.y"
|
||||
#line 1493 "..\oql-parser.php"
|
||||
#line 48 "..\oql-parser.y"
|
||||
function yy_r5(){
|
||||
$this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, array($this->yystack[$this->yyidx + -2]->minor));
|
||||
}
|
||||
#line 1493 "..\oql-parser.php"
|
||||
#line 43 "..\oql-parser.y"
|
||||
#line 1498 "..\oql-parser.php"
|
||||
#line 51 "..\oql-parser.y"
|
||||
function yy_r6(){
|
||||
$this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, array($this->yystack[$this->yyidx + -2]->minor));
|
||||
}
|
||||
#line 1498 "..\oql-parser.php"
|
||||
#line 47 "..\oql-parser.y"
|
||||
#line 1503 "..\oql-parser.php"
|
||||
#line 55 "..\oql-parser.y"
|
||||
function yy_r7(){
|
||||
$this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + -4]->minor);
|
||||
}
|
||||
#line 1503 "..\oql-parser.php"
|
||||
#line 50 "..\oql-parser.y"
|
||||
#line 1508 "..\oql-parser.php"
|
||||
#line 58 "..\oql-parser.y"
|
||||
function yy_r8(){
|
||||
$this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + -6]->minor);
|
||||
}
|
||||
#line 1508 "..\oql-parser.php"
|
||||
#line 55 "..\oql-parser.y"
|
||||
#line 1513 "..\oql-parser.php"
|
||||
#line 63 "..\oql-parser.y"
|
||||
function yy_r9(){
|
||||
$this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor);
|
||||
}
|
||||
#line 1513 "..\oql-parser.php"
|
||||
#line 58 "..\oql-parser.y"
|
||||
#line 1518 "..\oql-parser.php"
|
||||
#line 66 "..\oql-parser.y"
|
||||
function yy_r10(){
|
||||
array_push($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor);
|
||||
$this->_retvalue = $this->yystack[$this->yyidx + -2]->minor;
|
||||
}
|
||||
#line 1519 "..\oql-parser.php"
|
||||
#line 63 "..\oql-parser.y"
|
||||
#line 1524 "..\oql-parser.php"
|
||||
#line 71 "..\oql-parser.y"
|
||||
function yy_r11(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; }
|
||||
#line 1522 "..\oql-parser.php"
|
||||
#line 64 "..\oql-parser.y"
|
||||
#line 1527 "..\oql-parser.php"
|
||||
#line 72 "..\oql-parser.y"
|
||||
function yy_r12(){ $this->_retvalue = null; }
|
||||
#line 1525 "..\oql-parser.php"
|
||||
#line 66 "..\oql-parser.y"
|
||||
#line 1530 "..\oql-parser.php"
|
||||
#line 74 "..\oql-parser.y"
|
||||
function yy_r13(){
|
||||
// insert the join statement on top of the existing list
|
||||
array_unshift($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor);
|
||||
// and return the updated array
|
||||
$this->_retvalue = $this->yystack[$this->yyidx + 0]->minor;
|
||||
}
|
||||
#line 1533 "..\oql-parser.php"
|
||||
#line 72 "..\oql-parser.y"
|
||||
#line 1538 "..\oql-parser.php"
|
||||
#line 80 "..\oql-parser.y"
|
||||
function yy_r14(){
|
||||
$this->_retvalue = Array($this->yystack[$this->yyidx + 0]->minor);
|
||||
}
|
||||
#line 1538 "..\oql-parser.php"
|
||||
#line 78 "..\oql-parser.y"
|
||||
#line 1543 "..\oql-parser.php"
|
||||
#line 86 "..\oql-parser.y"
|
||||
function yy_r16(){
|
||||
// create an array with one single item
|
||||
$this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor);
|
||||
}
|
||||
#line 1544 "..\oql-parser.php"
|
||||
#line 83 "..\oql-parser.y"
|
||||
#line 1549 "..\oql-parser.php"
|
||||
#line 91 "..\oql-parser.y"
|
||||
function yy_r17(){
|
||||
// create an array with one single item
|
||||
$this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor);
|
||||
}
|
||||
#line 1550 "..\oql-parser.php"
|
||||
#line 88 "..\oql-parser.y"
|
||||
function yy_r18(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, '=', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1553 "..\oql-parser.php"
|
||||
#line 89 "..\oql-parser.y"
|
||||
function yy_r19(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'BELOW', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1556 "..\oql-parser.php"
|
||||
#line 90 "..\oql-parser.y"
|
||||
function yy_r20(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'BELOW_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1559 "..\oql-parser.php"
|
||||
#line 91 "..\oql-parser.y"
|
||||
function yy_r21(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_BELOW', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1562 "..\oql-parser.php"
|
||||
#line 92 "..\oql-parser.y"
|
||||
function yy_r22(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_BELOW_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1565 "..\oql-parser.php"
|
||||
#line 93 "..\oql-parser.y"
|
||||
function yy_r23(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'ABOVE', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1568 "..\oql-parser.php"
|
||||
#line 94 "..\oql-parser.y"
|
||||
function yy_r24(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'ABOVE_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1571 "..\oql-parser.php"
|
||||
#line 95 "..\oql-parser.y"
|
||||
function yy_r25(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_ABOVE', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1574 "..\oql-parser.php"
|
||||
#line 1555 "..\oql-parser.php"
|
||||
#line 96 "..\oql-parser.y"
|
||||
function yy_r26(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_ABOVE_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1577 "..\oql-parser.php"
|
||||
function yy_r18(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, '=', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1558 "..\oql-parser.php"
|
||||
#line 97 "..\oql-parser.y"
|
||||
function yy_r19(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'BELOW', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1561 "..\oql-parser.php"
|
||||
#line 98 "..\oql-parser.y"
|
||||
function yy_r27(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; }
|
||||
#line 1580 "..\oql-parser.php"
|
||||
function yy_r20(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'BELOW_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1564 "..\oql-parser.php"
|
||||
#line 99 "..\oql-parser.y"
|
||||
function yy_r21(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_BELOW', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1567 "..\oql-parser.php"
|
||||
#line 100 "..\oql-parser.y"
|
||||
function yy_r22(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_BELOW_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1570 "..\oql-parser.php"
|
||||
#line 101 "..\oql-parser.y"
|
||||
function yy_r23(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'ABOVE', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1573 "..\oql-parser.php"
|
||||
#line 102 "..\oql-parser.y"
|
||||
function yy_r24(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'ABOVE_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1576 "..\oql-parser.php"
|
||||
#line 103 "..\oql-parser.y"
|
||||
function yy_r31(){ $this->_retvalue = new FunctionOqlExpression($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + -1]->minor); }
|
||||
#line 1583 "..\oql-parser.php"
|
||||
function yy_r25(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_ABOVE', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1579 "..\oql-parser.php"
|
||||
#line 104 "..\oql-parser.y"
|
||||
function yy_r32(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor; }
|
||||
#line 1586 "..\oql-parser.php"
|
||||
#line 105 "..\oql-parser.y"
|
||||
function yy_r33(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1589 "..\oql-parser.php"
|
||||
function yy_r26(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_ABOVE_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1582 "..\oql-parser.php"
|
||||
#line 106 "..\oql-parser.y"
|
||||
function yy_r27(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; }
|
||||
#line 1585 "..\oql-parser.php"
|
||||
#line 111 "..\oql-parser.y"
|
||||
function yy_r31(){ $this->_retvalue = new FunctionOqlExpression($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + -1]->minor); }
|
||||
#line 1588 "..\oql-parser.php"
|
||||
#line 112 "..\oql-parser.y"
|
||||
function yy_r32(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor; }
|
||||
#line 1591 "..\oql-parser.php"
|
||||
#line 113 "..\oql-parser.y"
|
||||
function yy_r33(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1594 "..\oql-parser.php"
|
||||
#line 119 "..\oql-parser.y"
|
||||
function yy_r37(){
|
||||
if ($this->yystack[$this->yyidx + -1]->minor == 'MATCHES')
|
||||
{
|
||||
@@ -1594,44 +1598,44 @@ static public $yy_action = array(
|
||||
$this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor);
|
||||
}
|
||||
}
|
||||
#line 1601 "..\oql-parser.php"
|
||||
#line 128 "..\oql-parser.y"
|
||||
#line 1606 "..\oql-parser.php"
|
||||
#line 136 "..\oql-parser.y"
|
||||
function yy_r42(){
|
||||
$this->_retvalue = new ListOqlExpression($this->yystack[$this->yyidx + -1]->minor);
|
||||
}
|
||||
#line 1606 "..\oql-parser.php"
|
||||
#line 131 "..\oql-parser.y"
|
||||
#line 1611 "..\oql-parser.php"
|
||||
#line 139 "..\oql-parser.y"
|
||||
function yy_r43(){
|
||||
$this->_retvalue = new NestedQueryOqlExpression($this->yystack[$this->yyidx + -1]->minor);
|
||||
}
|
||||
#line 1611 "..\oql-parser.php"
|
||||
#line 146 "..\oql-parser.y"
|
||||
#line 1616 "..\oql-parser.php"
|
||||
#line 154 "..\oql-parser.y"
|
||||
function yy_r47(){
|
||||
$this->_retvalue = array();
|
||||
}
|
||||
#line 1616 "..\oql-parser.php"
|
||||
#line 157 "..\oql-parser.y"
|
||||
#line 1621 "..\oql-parser.php"
|
||||
#line 165 "..\oql-parser.y"
|
||||
function yy_r51(){ $this->_retvalue = new IntervalOqlExpression($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1619 "..\oql-parser.php"
|
||||
#line 170 "..\oql-parser.y"
|
||||
#line 1624 "..\oql-parser.php"
|
||||
#line 178 "..\oql-parser.y"
|
||||
function yy_r61(){ $this->_retvalue = new ScalarOqlExpression($this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1622 "..\oql-parser.php"
|
||||
#line 172 "..\oql-parser.y"
|
||||
#line 1627 "..\oql-parser.php"
|
||||
#line 180 "..\oql-parser.y"
|
||||
function yy_r63(){ $this->_retvalue = new ScalarOqlExpression(null); }
|
||||
#line 1625 "..\oql-parser.php"
|
||||
#line 174 "..\oql-parser.y"
|
||||
#line 1630 "..\oql-parser.php"
|
||||
#line 182 "..\oql-parser.y"
|
||||
function yy_r64(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1628 "..\oql-parser.php"
|
||||
#line 175 "..\oql-parser.y"
|
||||
#line 1633 "..\oql-parser.php"
|
||||
#line 183 "..\oql-parser.y"
|
||||
function yy_r65(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -2]->minor); }
|
||||
#line 1631 "..\oql-parser.php"
|
||||
#line 176 "..\oql-parser.y"
|
||||
#line 1636 "..\oql-parser.php"
|
||||
#line 184 "..\oql-parser.y"
|
||||
function yy_r66(){ $this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; }
|
||||
#line 1634 "..\oql-parser.php"
|
||||
#line 179 "..\oql-parser.y"
|
||||
#line 1639 "..\oql-parser.php"
|
||||
#line 187 "..\oql-parser.y"
|
||||
function yy_r67(){ $this->_retvalue = new VariableOqlExpression(substr($this->yystack[$this->yyidx + 0]->minor, 1)); }
|
||||
#line 1637 "..\oql-parser.php"
|
||||
#line 181 "..\oql-parser.y"
|
||||
#line 1642 "..\oql-parser.php"
|
||||
#line 189 "..\oql-parser.y"
|
||||
function yy_r68(){
|
||||
if ($this->yystack[$this->yyidx + 0]->minor[0] == '`')
|
||||
{
|
||||
@@ -1643,22 +1647,22 @@ static public $yy_action = array(
|
||||
}
|
||||
$this->_retvalue = new OqlName($name, $this->m_iColPrev);
|
||||
}
|
||||
#line 1650 "..\oql-parser.php"
|
||||
#line 192 "..\oql-parser.y"
|
||||
#line 1655 "..\oql-parser.php"
|
||||
#line 200 "..\oql-parser.y"
|
||||
function yy_r69(){$this->_retvalue=(int)$this->yystack[$this->yyidx + 0]->minor; }
|
||||
#line 1653 "..\oql-parser.php"
|
||||
#line 193 "..\oql-parser.y"
|
||||
#line 1658 "..\oql-parser.php"
|
||||
#line 201 "..\oql-parser.y"
|
||||
function yy_r70(){$this->_retvalue=(int)-$this->yystack[$this->yyidx + 0]->minor; }
|
||||
#line 1656 "..\oql-parser.php"
|
||||
#line 194 "..\oql-parser.y"
|
||||
#line 1661 "..\oql-parser.php"
|
||||
#line 202 "..\oql-parser.y"
|
||||
function yy_r71(){$this->_retvalue=new OqlHexValue($this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1659 "..\oql-parser.php"
|
||||
#line 195 "..\oql-parser.y"
|
||||
#line 1664 "..\oql-parser.php"
|
||||
#line 203 "..\oql-parser.y"
|
||||
function yy_r72(){$this->_retvalue=stripslashes(substr($this->yystack[$this->yyidx + 0]->minor, 1, strlen($this->yystack[$this->yyidx + 0]->minor) - 2)); }
|
||||
#line 1662 "..\oql-parser.php"
|
||||
#line 198 "..\oql-parser.y"
|
||||
#line 1667 "..\oql-parser.php"
|
||||
#line 206 "..\oql-parser.y"
|
||||
function yy_r73(){$this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; }
|
||||
#line 1665 "..\oql-parser.php"
|
||||
#line 1670 "..\oql-parser.php"
|
||||
|
||||
/**
|
||||
* placeholder for the left hand side in a reduce operation.
|
||||
@@ -1759,6 +1763,10 @@ static public $yy_action = array(
|
||||
}
|
||||
/* Here code is inserted which will be executed whenever the
|
||||
** parser fails */
|
||||
#line 33 "..\oql-parser.y"
|
||||
|
||||
throw new OQLParserParseFailureException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol);
|
||||
#line 1775 "..\oql-parser.php"
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1772,8 +1780,8 @@ static public $yy_action = array(
|
||||
{
|
||||
#line 25 "..\oql-parser.y"
|
||||
|
||||
throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN);
|
||||
#line 1781 "..\oql-parser.php"
|
||||
throw new OQLParserSyntaxErrorException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN);
|
||||
#line 1791 "..\oql-parser.php"
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1940,19 +1948,47 @@ throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCo
|
||||
} while ($yymajor != self::YYNOCODE && $this->yyidx >= 0);
|
||||
}
|
||||
}
|
||||
#line 263 "..\oql-parser.y"
|
||||
#line 271 "..\oql-parser.y"
|
||||
|
||||
|
||||
class OQLParserException extends OQLException
|
||||
{
|
||||
public function __construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue)
|
||||
{
|
||||
parent::__construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue);
|
||||
}
|
||||
}
|
||||
|
||||
class OQLParserSyntaxErrorException extends OQLParserException
|
||||
{
|
||||
public function __construct($sInput, $iLine, $iCol, $sTokenName, $sTokenValue)
|
||||
{
|
||||
$sIssue = "Unexpected token $sTokenName";
|
||||
|
||||
|
||||
parent::__construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue);
|
||||
}
|
||||
}
|
||||
|
||||
class OQLParserStackOverFlowException extends OQLParserException
|
||||
{
|
||||
public function __construct($sInput, $iLine, $iCol)
|
||||
{
|
||||
$sIssue = "Stack overflow";
|
||||
|
||||
parent::__construct($sIssue, $sInput, $iLine, $iCol, '');
|
||||
}
|
||||
}
|
||||
|
||||
class OQLParserParseFailureException extends OQLParserException
|
||||
{
|
||||
public function __construct($sInput, $iLine, $iCol)
|
||||
{
|
||||
$sIssue = "Unexpected token $sTokenName";
|
||||
|
||||
parent::__construct($sIssue, $sInput, $iLine, $iCol, '');
|
||||
}
|
||||
}
|
||||
|
||||
class OQLParser extends OQLParserRaw
|
||||
{
|
||||
// dirty, but working for us (no other mean to get the final result :-(
|
||||
@@ -2005,4 +2041,4 @@ class OQLParser extends OQLParserRaw
|
||||
}
|
||||
}
|
||||
|
||||
#line 2014 "..\oql-parser.php"
|
||||
#line 2052 "..\oql-parser.php"
|
||||
|
||||
@@ -23,7 +23,15 @@ later : solve the 2 remaining shift-reduce conflicts (JOIN)
|
||||
%name OQLParser_
|
||||
%declare_class {class OQLParserRaw}
|
||||
%syntax_error {
|
||||
throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN);
|
||||
throw new OQLParserSyntaxErrorException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN);
|
||||
}
|
||||
/* Bug N°4052 Parser stack size too small for huge OQL requests */
|
||||
%stack_size 1000
|
||||
%stack_overflow {
|
||||
throw new OQLParserStackOverFlowException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol);
|
||||
}
|
||||
%parse_failure {
|
||||
throw new OQLParserParseFailureException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol);
|
||||
}
|
||||
|
||||
result ::= union(X). { $this->my_result = X; }
|
||||
@@ -263,15 +271,43 @@ func_name(A) ::= F_INET_NTOA(X). { A=X; }
|
||||
%code {
|
||||
|
||||
class OQLParserException extends OQLException
|
||||
{
|
||||
public function __construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue)
|
||||
{
|
||||
parent::__construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue);
|
||||
}
|
||||
}
|
||||
|
||||
class OQLParserSyntaxErrorException extends OQLParserException
|
||||
{
|
||||
public function __construct($sInput, $iLine, $iCol, $sTokenName, $sTokenValue)
|
||||
{
|
||||
$sIssue = "Unexpected token $sTokenName";
|
||||
|
||||
|
||||
parent::__construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue);
|
||||
}
|
||||
}
|
||||
|
||||
class OQLParserStackOverFlowException extends OQLParserException
|
||||
{
|
||||
public function __construct($sInput, $iLine, $iCol)
|
||||
{
|
||||
$sIssue = "Stack overflow";
|
||||
|
||||
parent::__construct($sIssue, $sInput, $iLine, $iCol, '');
|
||||
}
|
||||
}
|
||||
|
||||
class OQLParserParseFailureException extends OQLParserException
|
||||
{
|
||||
public function __construct($sInput, $iLine, $iCol)
|
||||
{
|
||||
$sIssue = "Unexpected token $sTokenName";
|
||||
|
||||
parent::__construct($sIssue, $sInput, $iLine, $iCol, '');
|
||||
}
|
||||
}
|
||||
|
||||
class OQLParser extends OQLParserRaw
|
||||
{
|
||||
// dirty, but working for us (no other mean to get the final result :-(
|
||||
|
||||
@@ -1 +1 @@
|
||||
2020-09-29
|
||||
2021-06-03
|
||||
@@ -331,4 +331,12 @@ class OQLJoin
|
||||
return $this->sRightField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function GetLeftField()
|
||||
{
|
||||
return $this->sLeftField;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 (($oExtKeyAttDef instanceof AttributeExternalKey) && ($sJoinedClass == $oExtKeyAttDef->GetTargetClass())) {
|
||||
// The join is not used, remove from tree
|
||||
$oCurrentClassNode->RemoveJoin($sLeftKey, $index);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.5";
|
||||
$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;
|
||||
|
||||
|
||||
@@ -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.5',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -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.5',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -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.5',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -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.5',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -222,13 +222,11 @@ class DatabaseAnalyzer
|
||||
{
|
||||
$oFixSearch->AddCondition($sAttCode, $sValue, '=');
|
||||
}
|
||||
$aFixit[] = $oFixSearch->MakeSelectQuery().';';
|
||||
$aFixit[] = $oFixSearch->MakeSelectQuery([], [], null, null, 0, 0, false, false).';';
|
||||
$aFixit[] = "";
|
||||
}
|
||||
$aErrorsAndFixes[$sClass][$sErrorDesc]['fixit'] = $aFixit;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private function GetUniquenessRuleMessage($sUniquenessRuleId)
|
||||
@@ -326,13 +324,8 @@ class DatabaseAnalyzer
|
||||
{
|
||||
$sField = MetaModel::DBGetClassField($sClass);
|
||||
$sRootField = MetaModel::DBGetClassField($sRootClass);
|
||||
$sSelWrongRecs = <<<SQL
|
||||
SELECT `$sTable`.`$sKeyField` AS id
|
||||
FROM `$sTable`
|
||||
JOIN `$sRootTable` ON `$sRootTable`.`$sRootKey` = `$sTable`.`$sKeyField`
|
||||
WHERE `$sTable`.`$sField` != `$sRootTable`.`$sRootField`
|
||||
SQL;
|
||||
// Copy the finalclass of the root table
|
||||
$sSelWrongRecs = "SELECT `$sTable`.`$sKeyField` AS id FROM `$sTable` JOIN `$sRootTable` ON `$sRootTable`.`$sRootKey` = `$sTable`.`$sKeyField` WHERE `$sTable`.`$sField` != `$sRootTable`.`$sRootField`";
|
||||
// Copy the final class of the root table
|
||||
$sFixItRequest = "UPDATE `$sTable`,`$sRootTable` SET `$sTable`.`$sField` = `$sRootTable`.`$sRootField` WHERE `$sTable`.`$sKeyField` = `$sRootTable`.`$sRootKey`";
|
||||
$this->ExecQuery($sSelWrongRecs, $sFixItRequest, Dict::Format('DBAnalyzer-Integrity-FinalClass', $sField, $sTable, $sRootTable), $sClass, $aErrorsAndFixes);
|
||||
}
|
||||
@@ -360,65 +353,79 @@ SQL;
|
||||
|
||||
// Note: a class/table may have an external key on itself
|
||||
$sSelect = "SELECT DISTINCT `$sTable`.`$sKeyField` AS id, `$sTable`.`$sExtKeyField` AS value";
|
||||
$sFilter = "FROM `$sTable` LEFT JOIN `$sRemoteTable` AS `{$sRemoteTable}_1` ON `$sTable`.`$sExtKeyField` = `{$sRemoteTable}_1`.`$sRemoteKey`";
|
||||
$sFrom = "FROM `$sTable`";
|
||||
$sJoin = "LEFT JOIN `$sRemoteTable` AS `{$sRemoteTable}_1` ON `$sTable`.`$sExtKeyField` = `{$sRemoteTable}_1`.`$sRemoteKey`";
|
||||
|
||||
$sFilter = $sFilter." WHERE `{$sRemoteTable}_1`.`$sRemoteKey` IS NULL";
|
||||
$sFilter = "WHERE `{$sRemoteTable}_1`.`$sRemoteKey` IS NULL";
|
||||
// Exclude the records pointing to 0/null from the errors (separate test below)
|
||||
$sFilter .= " AND `$sTable`.`$sExtKeyField` IS NOT NULL";
|
||||
$sFilter .= " AND `$sTable`.`$sExtKeyField` != 0";
|
||||
|
||||
$sSelWrongRecs = "$sSelect $sFilter";
|
||||
$sSelWrongRecs = "$sSelect $sFrom $sJoin $sFilter";
|
||||
|
||||
$sErrorDesc = Dict::Format('DBAnalyzer-Integrity-InvalidExtKey', $sAttCode, $sTable, $sExtKeyField);
|
||||
$this->ExecQuery($sSelWrongRecs, '', $sErrorDesc, $sClass, $aErrorsAndFixes);
|
||||
$aFixIt = [];
|
||||
// Fix it request needs the values of the enum to generate the requests
|
||||
if (isset($aErrorsAndFixes[$sClass][$sErrorDesc]['values']))
|
||||
{
|
||||
$aFixIt = array();
|
||||
$aFixIt[] = "-- Remove inconsistant entries:";
|
||||
$sIds = implode(', ', array_keys($aErrorsAndFixes[$sClass][$sErrorDesc]['values']));
|
||||
$aFixIt[] = "DELETE `$sTable` FROM `$sTable` WHERE `$sTable`.`$sExtKeyField` IN ($sIds)";
|
||||
$aFixIt[] = "";
|
||||
$aFixIt[] = "-- Or fix inconsistant values: Replace XXX with the appropriate value";
|
||||
foreach (array_keys($aErrorsAndFixes[$sClass][$sErrorDesc]['values']) as $sKey)
|
||||
{
|
||||
$aFixIt[] = "UPDATE `$sTable` SET `$sTable`.`$sExtKeyField` = XXX WHERE `$sTable`.`$sExtKeyField` = '$sKey'";
|
||||
}
|
||||
$aErrorsAndFixes[$sClass][$sErrorDesc]['fixit'] = $aFixIt;
|
||||
}
|
||||
|
||||
if (!$oAttDef->IsNullAllowed())
|
||||
{
|
||||
$sSelect = "SELECT DISTINCT `$sTable`.`$sKeyField` AS id";
|
||||
$sDelete = "DELETE `$sTable`";
|
||||
$sFilter = "FROM `$sTable` WHERE `$sTable`.`$sExtKeyField` IS NULL OR `$sTable`.`$sExtKeyField` = 0";
|
||||
$sSelWrongRecs = "$sSelect $sFilter";
|
||||
$sFixItRequest = "$sDelete $sFilter";
|
||||
$sErrorDesc = Dict::Format('DBAnalyzer-Integrity-MissingExtKey', $sAttCode, $sTable, $sExtKeyField);
|
||||
$this->ExecQuery($sSelWrongRecs, $sFixItRequest, $sErrorDesc, $sClass, $aErrorsAndFixes);
|
||||
if (isset($aErrorsAndFixes[$sClass][$sErrorDesc]['count']) && ($aErrorsAndFixes[$sClass][$sErrorDesc]['count'] > 0))
|
||||
{
|
||||
$aFixIt = $aErrorsAndFixes[$sClass][$sErrorDesc]['fixit'];
|
||||
$aFixIt[] = "-- Alternate fix";
|
||||
$aFixIt[] = "-- Replace XXX with the appropriate value";
|
||||
$aFixIt[] = "UPDATE `$sTable` SET `$sTable`.`$sExtKeyField` = XXX WHERE `$sTable`.`$sExtKeyField` IS NULL OR `$sTable`.`$sExtKeyField` = 0";
|
||||
$aAdditionalFixIt = $this->GetSpecificExternalKeysFixItForNull($sTable, $sExtKeyField);
|
||||
if ($oAttDef->IsNullAllowed()) {
|
||||
$aFixIt[] = "-- Fix inconsistant values: remove the external key";
|
||||
foreach (array_keys($aErrorsAndFixes[$sClass][$sErrorDesc]['values']) as $sKey) {
|
||||
$aFixIt[] = "UPDATE `$sTable` SET `$sTable`.`$sExtKeyField` = 0 WHERE `$sTable`.`$sExtKeyField` = '$sKey'";
|
||||
}
|
||||
} else {
|
||||
$aAdditionalFixIt = $this->GetSpecificExternalKeysFixItForNull($sTable, $sExtKeyField, $sFilter, $sJoin);
|
||||
foreach ($aAdditionalFixIt as $sFixIt)
|
||||
{
|
||||
$aFixIt[] = $sFixIt;
|
||||
}
|
||||
|
||||
$aFixIt[] = "-- Alternate fix: remove inconsistant entries:";
|
||||
$sIds = implode(', ', array_keys($aErrorsAndFixes[$sClass][$sErrorDesc]['values']));
|
||||
$aFixIt[] = "DELETE `$sTable` FROM `$sTable` WHERE `$sTable`.`$sExtKeyField` IN ($sIds)";
|
||||
|
||||
$aFixIt[] = "-- Alternate fix: update inconsistant values: Replace XXX with the appropriate value";
|
||||
foreach (array_keys($aErrorsAndFixes[$sClass][$sErrorDesc]['values']) as $sKey) {
|
||||
$aFixIt[] = "UPDATE `$sTable` SET `$sTable`.`$sExtKeyField` = XXX WHERE `$sTable`.`$sExtKeyField` = '$sKey'";
|
||||
}
|
||||
}
|
||||
$aErrorsAndFixes[$sClass][$sErrorDesc]['fixit'] = $aFixIt;
|
||||
}
|
||||
|
||||
if (!$oAttDef->IsNullAllowed()) {
|
||||
$sSelect = "SELECT DISTINCT `$sTable`.`$sKeyField` AS id";
|
||||
$sDelete = "DELETE `$sTable`";
|
||||
$sFrom = "FROM `$sTable`";
|
||||
$sFilter = "WHERE `$sTable`.`$sExtKeyField` IS NULL OR `$sTable`.`$sExtKeyField` = 0";
|
||||
$sSelWrongRecs = "$sSelect $sFrom $sFilter";
|
||||
$sFixItRequest = "$sDelete $sFrom $sFilter";
|
||||
$sErrorDesc = Dict::Format('DBAnalyzer-Integrity-MissingExtKey', $sAttCode, $sTable, $sExtKeyField);
|
||||
$this->ExecQuery($sSelWrongRecs, '', $sErrorDesc, $sClass, $aErrorsAndFixes);
|
||||
$aFixIt = [];
|
||||
if (isset($aErrorsAndFixes[$sClass][$sErrorDesc]['count']) && ($aErrorsAndFixes[$sClass][$sErrorDesc]['count'] > 0))
|
||||
{
|
||||
$aAdditionalFixIt = $this->GetSpecificExternalKeysFixItForNull($sTable, $sExtKeyField, $sFilter);
|
||||
foreach ($aAdditionalFixIt as $sFixIt)
|
||||
{
|
||||
$aFixIt[] = $sFixIt;
|
||||
}
|
||||
$aFixIt[] = "-- Alternate fix: remove inconsistant entries:";
|
||||
$aFixIt[] = $sFixItRequest;
|
||||
$aFixIt[] = "-- Alternate fix: replace XXX with the appropriate value";
|
||||
$aFixIt[] = "UPDATE `$sTable` SET `$sTable`.`$sExtKeyField` = XXX WHERE `$sTable`.`$sExtKeyField` IS NULL OR `$sTable`.`$sExtKeyField` = 0";
|
||||
$aErrorsAndFixes[$sClass][$sErrorDesc]['fixit'] = $aFixIt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function GetSpecificExternalKeysFixItForNull($sTable, $sExtKeyField)
|
||||
private function GetSpecificExternalKeysFixItForNull($sTable, $sExtKeyField, $sFilter, $sJoin = '')
|
||||
{
|
||||
$aFixIt = array();
|
||||
if ($sTable == 'ticket' && $sExtKeyField == 'org_id')
|
||||
{
|
||||
$aFixIt[] = "-- Alternate fix: set the ticket org to the caller org";
|
||||
$aFixIt[] = "UPDATE ticket AS t JOIN contact AS c ON t.caller_id=c.id SET t.org_id=c.org_id WHERE t.org_id IS NULL OR t.org_id = 0";
|
||||
$aFixIt[] = "UPDATE ticket JOIN contact AS c ON ticket.caller_id=c.id $sJoin SET ticket.org_id=c.org_id $sFilter";
|
||||
}
|
||||
return $aFixIt;
|
||||
}
|
||||
@@ -441,7 +448,8 @@ SQL;
|
||||
$aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode);
|
||||
if (!is_null($aAllowedValues) && count($aAllowedValues) > 0)
|
||||
{
|
||||
$sExpectedValues = implode(",", CMDBSource::Quote(array_keys($aAllowedValues), true));
|
||||
$aAllowedValues = array_keys($aAllowedValues);
|
||||
$sExpectedValues = implode(",", CMDBSource::Quote($aAllowedValues, true));
|
||||
|
||||
$aCols = $oAttDef->GetSQLExpressions(); // Workaround a PHP bug: sometimes issuing a Notice if invoking current(somefunc())
|
||||
$sMyAttributeField = current($aCols); // get the first column for the moment
|
||||
@@ -458,15 +466,18 @@ SQL;
|
||||
if (isset($aErrorsAndFixes[$sClass][$sErrorDesc]['fixit']))
|
||||
{
|
||||
$aFixIt = $aErrorsAndFixes[$sClass][$sErrorDesc]['fixit'];
|
||||
$aFixIt[] = "-- Alternative: Replace 'XXX' with the appropriate value";
|
||||
$aFixIt[] = "-- Alternative: Replace enums with the appropriate value";
|
||||
}
|
||||
else
|
||||
{
|
||||
$aFixIt = array("-- Replace 'XXX' with the appropriate value");
|
||||
$aFixIt = ["-- Replace enums with the appropriate value"];
|
||||
}
|
||||
foreach (array_keys($aErrorsAndFixes[$sClass][$sErrorDesc]['values']) as $sKey)
|
||||
{
|
||||
$aFixIt[] = "UPDATE `$sTable` SET `$sTable`.`$sMyAttributeField` = 'XXX' WHERE `$sTable`.`$sMyAttributeField` = '$sKey'";
|
||||
foreach ($aAllowedValues as $sAllowedValue) {
|
||||
$aFixIt[] = "-- Replace $sKey by $sAllowedValue";
|
||||
$aFixIt[] = "UPDATE `$sTable` SET `$sTable`.`$sMyAttributeField` = '$sAllowedValue' WHERE `$sTable`.`$sMyAttributeField` = '$sKey'";
|
||||
}
|
||||
}
|
||||
$aErrorsAndFixes[$sClass][$sErrorDesc]['fixit'] = $aFixIt;
|
||||
}
|
||||
|
||||
@@ -31,15 +31,16 @@ const MAX_RESULTS = 10;
|
||||
* @param ApplicationContext $oAppContext
|
||||
*
|
||||
* @return \iTopWebPage
|
||||
* @throws CoreException
|
||||
* @throws DictExceptionMissingString
|
||||
* @throws MySQLException
|
||||
* @throws \CoreException
|
||||
* @throws \DictExceptionMissingString
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
function DisplayDBInconsistencies(iTopWebPage &$oP, ApplicationContext &$oAppContext)
|
||||
{
|
||||
$iShowId = intval(utils::ReadParam('show_id', '0'));
|
||||
$sErrorLabelSelection = utils::ReadParam('error_selection', '');
|
||||
$sClassSelection = utils::ReadParam('class_selection', '');
|
||||
$bVerbose = utils::ReadParam('verbose', 0);
|
||||
if (!empty($sClassSelection))
|
||||
{
|
||||
$aClassSelection = explode(",", $sClassSelection);
|
||||
@@ -109,11 +110,16 @@ function DisplayDBInconsistencies(iTopWebPage &$oP, ApplicationContext &$oAppCon
|
||||
|
||||
if ($iShowId == 3)
|
||||
{
|
||||
DisplayInconsistenciesReport($aResults);
|
||||
DisplayInconsistenciesReport($aResults, $bVerbose);
|
||||
}
|
||||
|
||||
$oP->p(Dict::S('DBTools:ErrorsFound'));
|
||||
|
||||
if ($iShowId > 0) {
|
||||
$oP->p(Dict::S('DBTools:Disclaimer'));
|
||||
$oP->p(Dict::S('DBTools:Indication'));
|
||||
}
|
||||
|
||||
$oP->add('<table class="listResults"><tr><th>'.Dict::S('DBTools:Class').'</th><th>'.Dict::S('DBTools:Count').'</th><th>'.Dict::S('DBTools:Error').'</th></tr>');
|
||||
$bTable = true;
|
||||
foreach($aResults as $sClass => $aErrorList)
|
||||
@@ -148,7 +154,7 @@ function DisplayDBInconsistencies(iTopWebPage &$oP, ApplicationContext &$oAppCon
|
||||
$oP->p(Dict::S('DBTools:SQLquery'));
|
||||
$sQuery = $aError['query'];
|
||||
$oP->add('<div style="padding: 15px; background: #f1f1f1;">');
|
||||
$oP->add('<code>'.$sQuery.'</code>');
|
||||
$oP->add('<pre>'.$sQuery.'</pre>');
|
||||
$oP->add('</div>');
|
||||
|
||||
if (isset($aError['fixit']))
|
||||
@@ -163,27 +169,26 @@ function DisplayDBInconsistencies(iTopWebPage &$oP, ApplicationContext &$oAppCon
|
||||
$oP->add('<br></div>');
|
||||
}
|
||||
|
||||
$oP->p(Dict::S('DBTools:SQLresult'));
|
||||
$sQueryResult = '';
|
||||
$iCount = count($aError['res']);
|
||||
$iMaxCount = MAX_RESULTS;
|
||||
foreach($aError['res'] as $aRes)
|
||||
{
|
||||
$iMaxCount--;
|
||||
if ($iMaxCount < 0)
|
||||
{
|
||||
$sQueryResult .= 'Displayed '.MAX_RESULTS."/$iCount results.<br>";
|
||||
break;
|
||||
if ($bVerbose) {
|
||||
$oP->p(Dict::S('DBTools:SQLresult'));
|
||||
$sQueryResult = '';
|
||||
$iCount = count($aError['res']);
|
||||
$iMaxCount = MAX_RESULTS;
|
||||
foreach ($aError['res'] as $aRes) {
|
||||
$iMaxCount--;
|
||||
if ($iMaxCount < 0) {
|
||||
$sQueryResult .= 'Displayed '.MAX_RESULTS."/$iCount results.<br>";
|
||||
break;
|
||||
}
|
||||
foreach ($aRes as $sKey => $sValue) {
|
||||
$sQueryResult .= "'$sKey'='$sValue' ";
|
||||
}
|
||||
$sQueryResult .= '<br>';
|
||||
}
|
||||
foreach($aRes as $sKey => $sValue)
|
||||
{
|
||||
$sQueryResult .= "'$sKey'='$sValue' ";
|
||||
}
|
||||
$sQueryResult .= '<br>';
|
||||
$oP->add('<div style="padding: 15px; background: #f1f1f1;">');
|
||||
$oP->add('<pre>'.$sQueryResult.'</pre>');
|
||||
$oP->add('</div>');
|
||||
}
|
||||
$oP->add('<div style="padding: 15px; background: #f1f1f1;">');
|
||||
$oP->add('<code>'.$sQueryResult.'</code>');
|
||||
$oP->add('</div>');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,14 +199,15 @@ function DisplayDBInconsistencies(iTopWebPage &$oP, ApplicationContext &$oAppCon
|
||||
|
||||
/**
|
||||
* @param $aResults
|
||||
* @param bool $bVerbose
|
||||
*
|
||||
* @return mixed
|
||||
* @throws CoreException
|
||||
* @throws DictExceptionMissingString
|
||||
* @throws \CoreException
|
||||
* @throws \DictExceptionMissingString
|
||||
*/
|
||||
function DisplayInconsistenciesReport($aResults)
|
||||
function DisplayInconsistenciesReport($aResults, $bVerbose = false)
|
||||
{
|
||||
$sReportFile = DBAnalyzerUtils::GenerateReport($aResults);
|
||||
$sReportFile = DBAnalyzerUtils::GenerateReport($aResults, $bVerbose);
|
||||
|
||||
$sZipReport = "{$sReportFile}.zip";
|
||||
$oArchive = new ZipArchive();
|
||||
@@ -212,9 +218,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);
|
||||
|
||||
@@ -28,6 +28,8 @@ Dict::Add('EN US', 'English', 'English', array(
|
||||
'DBTools:Class' => 'Class',
|
||||
'DBTools:Title' => 'Database Maintenance Tools',
|
||||
'DBTools:ErrorsFound' => 'Errors Found',
|
||||
'DBTools:Indication' => 'Important: after fixing errors in the database you\'ll have to run the analysis again as new inconsistencies will be generated',
|
||||
'DBTools:Disclaimer' => 'DISCLAIMER: BACKUP YOUR DATABASE BEFORE RUNNING THE FIXES',
|
||||
'DBTools:Error' => 'Error',
|
||||
'DBTools:Count' => 'Count',
|
||||
'DBTools:SQLquery' => 'SQL query',
|
||||
|
||||
@@ -23,6 +23,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
|
||||
'DBTools:Class' => 'Classe',
|
||||
'DBTools:Title' => 'Outils maintenance base de données',
|
||||
'DBTools:ErrorsFound' => 'Erreurs trouvées',
|
||||
'DBTools:Indication' => 'Important : après correction il est nécessaire de relancer l\'analyse car d\'autres inconsistances peuvent être générées par les modifications',
|
||||
'DBTools:Disclaimer' => 'ATTENTION : EFFECTUEZ UNE SAUVEGARDE DE LA BASE AVANT D\'APPLIQUER LES CORRECTIONS',
|
||||
'DBTools:Error' => 'Erreur',
|
||||
'DBTools:Count' => 'Nombre',
|
||||
'DBTools:SQLquery' => 'Requête SQL',
|
||||
@@ -41,7 +43,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
|
||||
|
||||
'DBTools:Inconsistencies' => 'Incohérences de base de données',
|
||||
|
||||
'DBAnalyzer-Integrity-OrphanRecord' => 'Enregistrement orphelin dans `%1$s`, il devrait avoir son équivalent dans la tableit `%2$s`',
|
||||
'DBAnalyzer-Integrity-OrphanRecord' => 'Enregistrement orphelin dans `%1$s`, il devrait avoir son équivalent dans la table `%2$s`',
|
||||
'DBAnalyzer-Integrity-InvalidExtKey' => 'Clef externe invalide %1$s (colonne: `%2$s.%3$s`)',
|
||||
'DBAnalyzer-Integrity-MissingExtKey' => 'Clef externe manquante %1$s (colonne: `%2$s.%3$s`)',
|
||||
'DBAnalyzer-Integrity-InvalidValue' => 'Valeur invalide pour %1$s (colonne: `%2$s.%3$s`)',
|
||||
|
||||
@@ -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.5',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
namespace Combodo\iTop\DBTools\Service;
|
||||
|
||||
use CoreException;
|
||||
use Dict;
|
||||
use DictExceptionMissingString;
|
||||
use MetaModel;
|
||||
|
||||
@@ -20,29 +21,31 @@ class DBAnalyzerUtils
|
||||
* @throws CoreException
|
||||
* @throws DictExceptionMissingString
|
||||
*/
|
||||
public static function GenerateReport($aResults)
|
||||
public static function GenerateReport($aResults, $bVerbose = false)
|
||||
{
|
||||
$sDBToolsFolder = str_replace("\\", '/', APPROOT.'log/');
|
||||
$sReportFile = 'dbtools-report';
|
||||
|
||||
$fReport = fopen($sDBToolsFolder.$sReportFile.'.log', 'w');
|
||||
fwrite($fReport, 'Database Maintenance tools: '.date('Y-m-d H:i:s')."\r\n");
|
||||
fwrite($fReport, '-- Database Maintenance tools: '.date('Y-m-d H:i:s')."\r\n");
|
||||
fwrite($fReport, "-- ".Dict::S('DBTools:Disclaimer')."\r\n");
|
||||
fwrite($fReport, "-- ".Dict::S('DBTools:Indication')."\r\n");
|
||||
foreach ($aResults as $sClass => $aErrorList)
|
||||
{
|
||||
fwrite($fReport, '');
|
||||
foreach ($aErrorList as $sErrorLabel => $aError)
|
||||
{
|
||||
fwrite($fReport, "\r\n----------\r\n");
|
||||
fwrite($fReport, 'Class: '.MetaModel::GetName($sClass).' ('.$sClass.")\r\n");
|
||||
fwrite($fReport, "\r\n-- \r\n");
|
||||
fwrite($fReport, '-- Class: '.MetaModel::GetName($sClass).' ('.$sClass.")\r\n");
|
||||
$iCount = $aError['count'];
|
||||
fwrite($fReport, 'Count: '.$iCount."\r\n");
|
||||
fwrite($fReport, 'Error: '.$sErrorLabel."\r\n");
|
||||
fwrite($fReport, '-- Count: '.$iCount."\r\n");
|
||||
fwrite($fReport, '-- Error: '.$sErrorLabel."\r\n");
|
||||
$sQuery = $aError['query'];
|
||||
fwrite($fReport, 'Query: '.$sQuery."\r\n");
|
||||
fwrite($fReport, '-- Query: '.$sQuery."\r\n");
|
||||
|
||||
if (isset($aError['fixit']))
|
||||
{
|
||||
fwrite($fReport, "\r\nFix it (indication):\r\n\r\n");
|
||||
fwrite($fReport, "\r\n-- Fix it (indication):\r\n\r\n");
|
||||
$aFixitQueries = $aError['fixit'];
|
||||
foreach ($aFixitQueries as $sFixitQuery)
|
||||
{
|
||||
@@ -51,31 +54,26 @@ class DBAnalyzerUtils
|
||||
fwrite($fReport, "\r\n");
|
||||
}
|
||||
|
||||
$sQueryResult = '';
|
||||
$aIdList = array();
|
||||
foreach ($aError['res'] as $aRes)
|
||||
{
|
||||
foreach ($aRes as $sKey => $sValue)
|
||||
{
|
||||
$sQueryResult .= "'$sKey'='$sValue' ";
|
||||
if ($sKey == 'id')
|
||||
{
|
||||
$aIdList[] = $sValue;
|
||||
if ($bVerbose) {
|
||||
$sQueryResult = '';
|
||||
$aIdList = [];
|
||||
foreach ($aError['res'] as $aRes) {
|
||||
$sQueryResult .= " - ";
|
||||
foreach ($aRes as $sKey => $sValue) {
|
||||
$sQueryResult .= "'$sKey'='$sValue' ";
|
||||
if ($sKey == 'id') {
|
||||
$aIdList[] = $sValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
$sQueryResult .= "\r\n";
|
||||
|
||||
fwrite($fReport, "-- Result: ".$sQueryResult);
|
||||
$sIdList = '('.implode(',', $aIdList).')';
|
||||
fwrite($fReport, "\r\n-- Ids: ".$sIdList."\r\n");
|
||||
}
|
||||
fwrite($fReport, "Result: \r\n".$sQueryResult);
|
||||
$sIdList = '('.implode(',', $aIdList).')';
|
||||
fwrite($fReport, 'Ids: '.$sIdList."\r\n");
|
||||
}
|
||||
}
|
||||
fclose($fReport);
|
||||
|
||||
|
||||
$sReportFile = $sDBToolsFolder.$sReportFile;
|
||||
|
||||
return $sReportFile;
|
||||
return $sDBToolsFolder.$sReportFile;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,26 +64,26 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
|
||||
// Lost attachments
|
||||
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
'DBTools:LostAttachments' => 'Lost attachments~~',
|
||||
'DBTools:LostAttachments:Disclaimer' => 'Here you can search your database for lost or misplaced attachments. This is NOT a data recovery tool, is does not retrieve deleted data.~~',
|
||||
'DBTools:LostAttachments' => '附件丢失',
|
||||
'DBTools:LostAttachments:Disclaimer' => '您可以在数据库里搜索附件是否有丢失或误挪动. 这不是数据恢复工具, 无法恢复已删除的数据.',
|
||||
|
||||
'DBTools:LostAttachments:Button:Analyze' => '分析',
|
||||
'DBTools:LostAttachments:Button:Restore' => '还原',
|
||||
'DBTools:LostAttachments:Button:Restore:Confirm' => '该操作无法回退, 请确认是否继续还原.',
|
||||
'DBTools:LostAttachments:Button:Busy' => '请稍后...',
|
||||
|
||||
'DBTools:LostAttachments:Step:Analyze' => 'First, search for lost/misplaced attachments by analyzing the database.~~',
|
||||
'DBTools:LostAttachments:Step:Analyze' => '首先, 通过分析数据库来搜索丢失或误挪动的附件.',
|
||||
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults' => '分析结果:',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:None' => 'Great! Every thing seems to be at the right place.~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Some' => 'Some attachments (%1$d) seem to be misplaced. Take a look at the following list and check the ones you would like to move.~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:None' => '非常好! 所有附件都是正常的.',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Some' => '某些附件 (%1$d) 看起来放错了位置. 请检查下面的列表并选择要挪动的文件.',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:Filename' => '文件名',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:CurrentLocation' => '当前位置',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:TargetLocation' => '移动到...',
|
||||
|
||||
'DBTools:LostAttachments:Step:RestoreResults' => '还原结果:',
|
||||
'DBTools:LostAttachments:Step:RestoreResults:Results' => '%1$d/%2$d attachments were restored.~~',
|
||||
'DBTools:LostAttachments:Step:RestoreResults:Results' => '%1$d/%2$d 的附件已还原.',
|
||||
|
||||
'DBTools:LostAttachments:StoredAsInlineImage' => 'Stored as inline image~~',
|
||||
'DBTools:LostAttachments:History' => 'Attachment "%1$s" restored with DB tools~~'
|
||||
'DBTools:LostAttachments:History' => 'Attachment "附件 %1$s" 已被数据库工具恢复'
|
||||
));
|
||||
|
||||
@@ -61,7 +61,6 @@ try
|
||||
LoginWebPage::DoLoginEx(null /* any portal */, false);
|
||||
|
||||
$oPage = new ajax_page("");
|
||||
$oPage->no_cache();
|
||||
|
||||
$sOperation = utils::ReadParam('operation', '');
|
||||
|
||||
|
||||
@@ -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.5',
|
||||
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*/);
|
||||
|
||||
@@ -30,10 +30,10 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
'Attachment:Max_Ko' => '(最大文件尺寸: %1$s KB)',
|
||||
'Attachments:NoAttachment' => '没有附件. ',
|
||||
'Attachments:PreviewNotAvailable' => '该附件类型不支持预览.',
|
||||
'Attachments:Error:FileTooLarge' => '上传的文件过大. %1$s~~',
|
||||
'Attachments:Error:UploadedFileEmpty' => 'The received file is empty and cannot be attached.
|
||||
Either you have pushed an empty file,
|
||||
or ask your iTop administrator if the iTop server disk is full.~~',
|
||||
'Attachments:Error:FileTooLarge' => '上传的文件过大. %1$s',
|
||||
'Attachments:Error:UploadedFileEmpty' => '收到的文件为空,无法添加.
|
||||
可能是因为您上传了一个空文件,,
|
||||
或者咨询iTop 管理员确认iTop 服务器磁盘空间是否已满.',
|
||||
'Attachments:Render:Icons' => '显示为图标',
|
||||
'Attachments:Render:Table' => '显示为列表',
|
||||
));
|
||||
@@ -47,11 +47,11 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
'Class:Attachment+' => '',
|
||||
'Class:Attachment/Attribute:expire' => '过期',
|
||||
'Class:Attachment/Attribute:expire+' => '~~',
|
||||
'Class:Attachment/Attribute:temp_id' => '临时 id',
|
||||
'Class:Attachment/Attribute:temp_id' => '临时id',
|
||||
'Class:Attachment/Attribute:temp_id+' => '~~',
|
||||
'Class:Attachment/Attribute:item_class' => 'Item class~~',
|
||||
'Class:Attachment/Attribute:item_class+' => '~~',
|
||||
'Class:Attachment/Attribute:item_id' => 'Item~~',
|
||||
'Class:Attachment/Attribute:item_id' => '项目',
|
||||
'Class:Attachment/Attribute:item_id+' => '~~',
|
||||
'Class:Attachment/Attribute:item_org_id' => 'Item organization~~',
|
||||
'Class:Attachment/Attribute:item_org_id+' => '',
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
|
||||
@@ -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.5',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -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.5',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -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.5',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -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+' => '',
|
||||
|
||||
@@ -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.5',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -86,8 +86,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
'Class:Change/Attribute:category/Value:hardware+' => '硬件',
|
||||
'Class:Change/Attribute:category/Value:network' => '网络',
|
||||
'Class:Change/Attribute:category/Value:network+' => '网络',
|
||||
'Class:Change/Attribute:category/Value:other' => '其他',
|
||||
'Class:Change/Attribute:category/Value:other+' => '其他',
|
||||
'Class:Change/Attribute:category/Value:other' => '其它',
|
||||
'Class:Change/Attribute:category/Value:other+' => '其它',
|
||||
'Class:Change/Attribute:category/Value:software' => '软件',
|
||||
'Class:Change/Attribute:category/Value:software+' => '软件',
|
||||
'Class:Change/Attribute:category/Value:system' => '系统',
|
||||
|
||||
@@ -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.5',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -365,8 +365,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
'Class:PhysicalDevice/Attribute:status/Value:obsolete+' => '废弃',
|
||||
'Class:PhysicalDevice/Attribute:status/Value:production' => '生产',
|
||||
'Class:PhysicalDevice/Attribute:status/Value:production+' => '生产',
|
||||
'Class:PhysicalDevice/Attribute:status/Value:stock' => '闲置',
|
||||
'Class:PhysicalDevice/Attribute:status/Value:stock+' => '闲置',
|
||||
'Class:PhysicalDevice/Attribute:status/Value:stock' => '库存',
|
||||
'Class:PhysicalDevice/Attribute:status/Value:stock+' => '库存',
|
||||
'Class:PhysicalDevice/Attribute:brand_id' => '品牌',
|
||||
'Class:PhysicalDevice/Attribute:brand_id+' => '',
|
||||
'Class:PhysicalDevice/Attribute:brand_name' => '品牌名称',
|
||||
@@ -868,8 +868,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
'Class:VirtualDevice/Attribute:status/Value:obsolete+' => '废弃',
|
||||
'Class:VirtualDevice/Attribute:status/Value:production' => '生产',
|
||||
'Class:VirtualDevice/Attribute:status/Value:production+' => '生产',
|
||||
'Class:VirtualDevice/Attribute:status/Value:stock' => '闲置',
|
||||
'Class:VirtualDevice/Attribute:status/Value:stock+' => '闲置',
|
||||
'Class:VirtualDevice/Attribute:status/Value:stock' => '库存',
|
||||
'Class:VirtualDevice/Attribute:status/Value:stock+' => '库存',
|
||||
'Class:VirtualDevice/Attribute:logicalvolumes_list' => '逻辑卷',
|
||||
'Class:VirtualDevice/Attribute:logicalvolumes_list+' => '该设备使用的所有逻辑卷',
|
||||
));
|
||||
|
||||
@@ -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.5',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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.5',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<fieldset id="form-update-outer">
|
||||
<legend>{{ 'iTopUpdate:UI:SelectUpdateFile'|dict_s }}</legend>
|
||||
|
||||
<div class="details">
|
||||
|
||||
@@ -22,6 +22,9 @@ $.ajax({
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#check-update").prop("disabled", true);
|
||||
$("#file").prop("disabled", true);
|
||||
$('#form-update-outer').slideUp(600);
|
||||
oRequirements.addClass("message_error");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,33 +24,33 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
'iTopUpdate:UI:PageTitle' => '应用升级',
|
||||
'itop-core-update:UI:SelectUpdateFile' => '应用升级',
|
||||
'itop-core-update:UI:ConfirmUpdate' => ' 升级',
|
||||
'itop-core-update:UI:UpdateCoreFiles' => 'Application Upgrade~~',
|
||||
'iTopUpdate:UI:MaintenanceModeActive' => 'The application is currently under maintenance, no user can access the application. You have to run a setup or restore the application archive to return in normal mode.~~',
|
||||
'itop-core-update:UI:UpdateDone' => 'Application Upgrade~~',
|
||||
'itop-core-update:UI:UpdateCoreFiles' => '应用升级',
|
||||
'iTopUpdate:UI:MaintenanceModeActive' => '应用正处于维护状态, 用户无法登录系统. 您必须运行安装向导或从备份中还原才能恢复到正常状态.',
|
||||
'itop-core-update:UI:UpdateDone' => '应用升级',
|
||||
|
||||
'itop-core-update/Operation:SelectUpdateFile/Title' => 'Application Upgrade~~',
|
||||
'itop-core-update/Operation:ConfirmUpdate/Title' => 'Confirm Application Upgrade~~',
|
||||
'itop-core-update/Operation:UpdateCoreFiles/Title' => 'Application Upgrading~~',
|
||||
'itop-core-update/Operation:UpdateDone/Title' => 'Application Upgrade Done~~',
|
||||
'itop-core-update/Operation:SelectUpdateFile/Title' => '应用升级',
|
||||
'itop-core-update/Operation:ConfirmUpdate/Title' => '确认升级',
|
||||
'itop-core-update/Operation:UpdateCoreFiles/Title' => '应用升级进行中',
|
||||
'itop-core-update/Operation:UpdateDone/Title' => '升级完毕',
|
||||
|
||||
'iTopUpdate:UI:SelectUpdateFile' => '请选择要上传的升级文件',
|
||||
'iTopUpdate:UI:CheckUpdate' => '校验升级文件',
|
||||
'iTopUpdate:UI:ConfirmInstallFile' => '即将安装 %1$s',
|
||||
'iTopUpdate:UI:DoUpdate' => '升级',
|
||||
'iTopUpdate:UI:CurrentVersion' => '当前已安装的版本',
|
||||
'iTopUpdate:UI:NewVersion' => 'Newly installed version~~',
|
||||
'iTopUpdate:UI:NewVersion' => '新安装的版本',
|
||||
'iTopUpdate:UI:Back' => '返回',
|
||||
'iTopUpdate:UI:Cancel' => '取消',
|
||||
'iTopUpdate:UI:Continue' => '继续',
|
||||
'iTopUpdate:UI:RunSetup' => 'Run Setup~~',
|
||||
'iTopUpdate:UI:RunSetup' => '运行安装向导',
|
||||
'iTopUpdate:UI:WithDBBackup' => '数据库备份',
|
||||
'iTopUpdate:UI:WithFilesBackup' => '应用文件备份',
|
||||
'iTopUpdate:UI:WithoutBackup' => 'No backup is planned~~',
|
||||
'iTopUpdate:UI:WithoutBackup' => '暂时没有备份计划',
|
||||
'iTopUpdate:UI:Backup' => '升级之前执行备份',
|
||||
'iTopUpdate:UI:DoFilesArchive' => '打包应用文件',
|
||||
'iTopUpdate:UI:UploadArchive' => 'Select a package to upload~~',
|
||||
'iTopUpdate:UI:ServerFile' => 'Path of a package already on the server~~',
|
||||
'iTopUpdate:UI:WarningReadOnlyDuringUpdate' => 'During the upgrade, the application will be read-only.~~',
|
||||
'iTopUpdate:UI:UploadArchive' => '请选择要上传的包',
|
||||
'iTopUpdate:UI:ServerFile' => '软件包已在服务器上',
|
||||
'iTopUpdate:UI:WarningReadOnlyDuringUpdate' => '在升级期间, 应用会变成只读状态.',
|
||||
|
||||
'iTopUpdate:UI:Status' => '状态',
|
||||
'iTopUpdate:UI:Action' => '升级',
|
||||
@@ -74,7 +74,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
'iTopUpdate:UI:CanCoreUpdate:Failed' => '文件系统检查失败',
|
||||
'iTopUpdate:UI:CanCoreUpdate:Yes' => '应用无法升级',
|
||||
'iTopUpdate:UI:CanCoreUpdate:No' => '应用无法升级: %1$s',
|
||||
'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s~~',
|
||||
'iTopUpdate:UI:CanCoreUpdate:Warning' => '警告: 应用升级可能会失败: %1$s',
|
||||
|
||||
// Setup Messages
|
||||
'iTopUpdate:UI:SetupMessage:Ready' => '准备开始',
|
||||
@@ -82,9 +82,9 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
'iTopUpdate:UI:SetupMessage:Backup' => '数据库备份',
|
||||
'iTopUpdate:UI:SetupMessage:FilesArchive' => '打包应用文件',
|
||||
'iTopUpdate:UI:SetupMessage:CopyFiles' => '复制新文件',
|
||||
'iTopUpdate:UI:SetupMessage:CheckCompile' => 'Check application upgrade~~',
|
||||
'iTopUpdate:UI:SetupMessage:CheckCompile' => '检查更新',
|
||||
'iTopUpdate:UI:SetupMessage:Compile' => '升级应用程序和数据库',
|
||||
'iTopUpdate:UI:SetupMessage:UpdateDatabase' => 'Upgrade database~~',
|
||||
'iTopUpdate:UI:SetupMessage:UpdateDatabase' => '升级数据库',
|
||||
'iTopUpdate:UI:SetupMessage:ExitMaintenance' => '正在退出维护模式',
|
||||
'iTopUpdate:UI:SetupMessage:UpdateDone' => '升级完成',
|
||||
|
||||
@@ -109,7 +109,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
'Menu:iTopUpdate+' => '应用升级',
|
||||
|
||||
// Missing itop entries
|
||||
'Class:ModuleInstallation/Attribute:installed' => '安装在',
|
||||
'Class:ModuleInstallation/Attribute:installed' => '安装时间',
|
||||
'Class:ModuleInstallation/Attribute:name' => '名称',
|
||||
'Class:ModuleInstallation/Attribute:version' => '版本',
|
||||
'Class:ModuleInstallation/Attribute:comment' => '备注',
|
||||
|
||||
@@ -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.5',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -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.5',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -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.5',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -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.5',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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.5',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -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.5',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -144,9 +144,9 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
|
||||
'Class:Incident/Attribute:cumulatedpending' => 'Pendências acumuladas',
|
||||
'Class:Incident/Attribute:cumulatedpending+' => '',
|
||||
'Class:Incident/Attribute:tto' => 'TTO',
|
||||
'Class:Incident/Attribute:tto+' => '',
|
||||
'Class:Incident/Attribute:tto+' => 'Tempo de atribuição',
|
||||
'Class:Incident/Attribute:ttr' => 'TTR',
|
||||
'Class:Incident/Attribute:ttr+' => '',
|
||||
'Class:Incident/Attribute:ttr+' => 'Tempo de resolução',
|
||||
'Class:Incident/Attribute:tto_escalation_deadline' => 'Prazo determinado TTO',
|
||||
'Class:Incident/Attribute:tto_escalation_deadline+' => '',
|
||||
'Class:Incident/Attribute:sla_tto_passed' => 'SLA TTO passou',
|
||||
|
||||
@@ -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.5',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -63,6 +63,8 @@ Dict::Add('EN US', 'English', 'English', array(
|
||||
'Portal:File:DisplayInfo+' => '%1$s (%2$s) <a href="%3$s" class="file_open_link" target="_blank">Open</a> / <a href="%4$s" class="file_download_link">Download</a>',
|
||||
'Portal:Calendar-FirstDayOfWeek' => 'en-us', //work with moment.js locales
|
||||
'Portal:Form:Close:Warning' => 'Do you want to leave this form ? Data entered may be lost',
|
||||
'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting again this form.',
|
||||
'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting again this form.',
|
||||
));
|
||||
|
||||
// UserProfile brick
|
||||
|
||||
@@ -62,6 +62,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
|
||||
'Portal:File:DisplayInfo+' => '%1$s (%2$s) <a href="%3$s" class="file_open_link" target="_blank">Ouvrir</a> / <a href="%4$s" class="file_download_link">Télécharger</a>',
|
||||
'Portal:Calendar-FirstDayOfWeek' => 'fr', //work with moment.js locales
|
||||
'Portal:Form:Close:Warning' => 'Voulez-vous quitter ce formulaire ? Les données saisies seront perdues',
|
||||
'Portal:Error:ObjectCannotBeCreated' => 'Erreur: L\'objet n\'a pas été créé. Vérifiez les objets liés et les attachements avant de soumettre à nouveau le formulaire.',
|
||||
'Portal:Error:ObjectCannotBeUpdated' => 'Erreur: L\'objet n\'a pas été modifié. Vérifiez les objets liés et les attachements avant de soumettre à nouveau le formulaire.',
|
||||
));
|
||||
|
||||
// UserProfile brick
|
||||
|
||||
@@ -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.5', array(
|
||||
// Identification
|
||||
'label' => 'Portal Development Library',
|
||||
'category' => 'Portal',
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -30,6 +30,8 @@ use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use DBSearch;
|
||||
use FieldExpression;
|
||||
use IssueLog;
|
||||
use LogChannels;
|
||||
use MetaModel;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@@ -65,6 +67,8 @@ class BrowseBrickController extends BrickController
|
||||
*/
|
||||
public function DisplayAction(Request $oRequest, $sBrickId, $sBrowseMode = null, $sDataLoading = null)
|
||||
{
|
||||
$sPortalId = $this->getParameter('combodo.portal.instance.id');
|
||||
|
||||
/** @var \Combodo\iTop\Portal\Helper\BrowseBrickHelper $oBrowseBrickHelper */
|
||||
$oBrowseBrickHelper = $this->get('browse_brick');
|
||||
/** @var \Combodo\iTop\Portal\Helper\RequestManipulatorHelper $oRequestManipulator */
|
||||
@@ -266,8 +270,7 @@ class BrowseBrickController extends BrickController
|
||||
// Note : This could be way more simpler if we had a SetInternalParam($sParam, $value) verb
|
||||
$aQueryParams = $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->GetInternalParams();
|
||||
// Note : $iSearchloopMax was initialized on the previous loop
|
||||
for ($j = 0; $j <= $iSearchLoopMax; $j++)
|
||||
{
|
||||
for ($j = 0; $j <= $iSearchLoopMax; $j++) {
|
||||
$aQueryParams['search_value_'.$j] = '%'.$aSearchValues[$j].'%';
|
||||
}
|
||||
$aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->SetInternalParams($aQueryParams);
|
||||
@@ -277,12 +280,11 @@ class BrowseBrickController extends BrickController
|
||||
$oQuery = $aLevelsProperties[$aLevelsPropertiesKeys[0]]['search'];
|
||||
|
||||
// Testing appropriate data loading mode if we are in auto
|
||||
if ($sDataLoading === AbstractBrick::ENUM_DATA_LOADING_AUTO)
|
||||
{
|
||||
if ($sDataLoading === AbstractBrick::ENUM_DATA_LOADING_AUTO) {
|
||||
// - Check how many records there is.
|
||||
// - Update $sDataLoading with its new value regarding the number of record and the threshold
|
||||
$oCountSet = new DBObjectSet($oQuery);
|
||||
$fThreshold = (float)MetaModel::GetModuleSetting($this->getParameter('combodo.portal.instance.id'),
|
||||
$fThreshold = (float)MetaModel::GetModuleSetting($sPortalId,
|
||||
'lazy_loading_threshold');
|
||||
$sDataLoading = ($oCountSet->Count() > $fThreshold) ? AbstractBrick::ENUM_DATA_LOADING_LAZY : AbstractBrick::ENUM_DATA_LOADING_FULL;
|
||||
unset($oCountSet);
|
||||
@@ -440,17 +442,21 @@ class BrowseBrickController extends BrickController
|
||||
}
|
||||
}
|
||||
|
||||
IssueLog::Debug('Portal BrowseBrick query', LogChannels::PORTAL, array(
|
||||
'sPortalId' => $sPortalId,
|
||||
'sBrickId' => $sBrickId,
|
||||
'oql' => $oSet->GetFilter()->ToOQL(),
|
||||
));
|
||||
|
||||
|
||||
// Preparing response
|
||||
if ($oRequest->isXmlHttpRequest())
|
||||
{
|
||||
if ($oRequest->isXmlHttpRequest()) {
|
||||
$aData = $aData + array(
|
||||
'data' => $aItems,
|
||||
'levelsProperties' => $aLevelsProperties,
|
||||
);
|
||||
$oResponse = new JsonResponse($aData);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$aData = $aData + array(
|
||||
'oBrick' => $oBrick,
|
||||
'sBrickId' => $sBrickId,
|
||||
|
||||
@@ -41,13 +41,14 @@ use Dict;
|
||||
use Exception;
|
||||
use FieldExpression;
|
||||
use iPopupMenuExtension;
|
||||
use IssueLog;
|
||||
use JSButtonItem;
|
||||
use LogChannels;
|
||||
use MetaModel;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use UnaryExpression;
|
||||
use URLButtonItem;
|
||||
use VariableExpression;
|
||||
|
||||
/**
|
||||
* Class ManageBrickController
|
||||
@@ -89,10 +90,10 @@ class ManageBrickController extends BrickController
|
||||
/** @var \Combodo\iTop\Portal\Brick\ManageBrick $oBrick */
|
||||
$oBrick = $oBrickCollection->GetBrickById($sBrickId);
|
||||
|
||||
if (is_null($sDisplayMode))
|
||||
{
|
||||
if (is_null($sDisplayMode)) {
|
||||
$sDisplayMode = $oBrick->GetDefaultDisplayMode();
|
||||
}
|
||||
|
||||
$aData = $this->GetData($oRequest, $sBrickId, $sGroupingTab, $oBrick::AreDetailsNeededForDisplayMode($sDisplayMode));
|
||||
|
||||
$aExportFields = $oBrick->GetExportFields();
|
||||
@@ -102,8 +103,7 @@ class ManageBrickController extends BrickController
|
||||
'iDefaultListLength' => $oBrick->GetDefaultListLength(),
|
||||
);
|
||||
// Preparing response
|
||||
if ($oRequest->isXmlHttpRequest())
|
||||
{
|
||||
if ($oRequest->isXmlHttpRequest()) {
|
||||
$oResponse = new JsonResponse($aData);
|
||||
}
|
||||
else
|
||||
@@ -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(
|
||||
@@ -812,31 +813,32 @@ class ManageBrickController extends BrickController
|
||||
'iItemsCount' => $oSet->Count(),
|
||||
'aColumnsDefinition' => $aColumnsDefinition,
|
||||
);
|
||||
|
||||
IssueLog::Debug('Portal ManageBrick query', LogChannels::PORTAL, array(
|
||||
'sPortalId' => $sPortalId,
|
||||
'sBrickId' => $sBrickId,
|
||||
'sGroupingTab' => $sGroupingTab,
|
||||
'oql' => $oSet->GetFilter()->ToOQL(),
|
||||
'aGroupingTabs' => $aGroupingTabs,
|
||||
));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$aGroupingAreasData = array();
|
||||
$sGroupingArea = null;
|
||||
}
|
||||
|
||||
// Preparing response
|
||||
if ($oRequest->isXmlHttpRequest())
|
||||
{
|
||||
if ($oRequest->isXmlHttpRequest()) {
|
||||
$aData = $aData + array(
|
||||
'data' => $aGroupingAreasData[$sGroupingArea]['aItems'],
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$aDisplayValues = array();
|
||||
$aUrls = array();
|
||||
$aColumns = array();
|
||||
$aNames = array();
|
||||
if ($bHasScope)
|
||||
{
|
||||
foreach ($aGroupingTabsValues as $aValues)
|
||||
{
|
||||
if ($bHasScope) {
|
||||
foreach ($aGroupingTabsValues as $aValues) {
|
||||
$aDisplayValues[] = array(
|
||||
'value' => $aValues['count'],
|
||||
'label' => $aValues['label'],
|
||||
|
||||
@@ -639,7 +639,7 @@ class ObjectController extends BrickController
|
||||
$oSet = new DBObjectSet($oSearch, array(), array('this' => $oHostObject, 'ac_query' => '%'.$sQuery.'%'));
|
||||
$oSet->OptimizeColumnLoad(array($oSearch->GetClassAlias() => array('friendlyname')));
|
||||
// Note : This limit is also used in the field renderer by typeahead to determine how many suggestions to display
|
||||
$oSet->SetLimit(MetaModel::GetConfig()->Get('max_display_limit'));
|
||||
$oSet->SetLimit(MetaModel::GetConfig()->GetMaxDisplayLimit());
|
||||
|
||||
// - Retrieving objects
|
||||
while ($oItem = $oSet->Fetch())
|
||||
|
||||
@@ -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.
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,14 +25,12 @@ use AttributeDateTime;
|
||||
use AttributeTagSet;
|
||||
use CMDBChangeOpAttachmentAdded;
|
||||
use CMDBChangeOpAttachmentRemoved;
|
||||
use CMDBSource;
|
||||
use Combodo\iTop\Form\Field\Field;
|
||||
use Combodo\iTop\Form\Field\FileUploadField;
|
||||
use Combodo\iTop\Form\Field\LabelField;
|
||||
use Combodo\iTop\Form\Form;
|
||||
use Combodo\iTop\Form\FormManager;
|
||||
use Combodo\iTop\Portal\Helper\ApplicationHelper;
|
||||
use CoreCannotSaveObjectException;
|
||||
use DBObject;
|
||||
use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
@@ -80,6 +78,12 @@ class ObjectFormManager extends FormManager
|
||||
protected $aFormProperties;
|
||||
/** @var array $aCallbackUrls */
|
||||
protected $aCallbackUrls = array();
|
||||
/**
|
||||
* List of hidden fields, used for form update (eg. remove them from the form regarding they dependencies)
|
||||
* @var array $aHiddenFieldsId
|
||||
* @since 2.7.5
|
||||
*/
|
||||
protected $aHiddenFieldsId = array();
|
||||
|
||||
/**
|
||||
* Creates an instance of \Combodo\iTop\Portal\Form\ObjectFormManager from JSON data that must contain at least :
|
||||
@@ -943,6 +947,8 @@ class ObjectFormManager extends FormManager
|
||||
if($oField->GetHidden() === false)
|
||||
{
|
||||
$oForm->AddField($oField);
|
||||
} else {
|
||||
$this->aHiddenFieldsId[]=$oField->GetId();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1048,6 +1054,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,122 +1096,106 @@ 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;
|
||||
}
|
||||
|
||||
$sObjectClass = get_class($this->oObject);
|
||||
|
||||
try {
|
||||
// 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
|
||||
$bIsNew = $this->oObject->IsNew();
|
||||
$bWasModified = $this->oObject->IsModified();
|
||||
$bActivateTriggers = (!$bIsNew && $bWasModified);
|
||||
try
|
||||
{
|
||||
$this->oObject->DBWrite();
|
||||
}
|
||||
catch (Exception $e) {
|
||||
if ($bIsNew) {
|
||||
throw new Exception(Dict::S('Portal:Error:ObjectCannotBeCreated'));
|
||||
}
|
||||
throw new Exception(Dict::S('Portal:Error:ObjectCannotBeUpdated'));
|
||||
}
|
||||
// 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']);
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
$aData['valid'] = false;
|
||||
$aData['messages']['error'] += array('_main' => array($e->getMessage()));
|
||||
IssueLog::Error(__METHOD__.' at line '.__LINE__.' : '.$e->getMessage());
|
||||
}
|
||||
|
||||
return $aData;
|
||||
@@ -1470,4 +1480,22 @@ class ObjectFormManager extends FormManager
|
||||
}
|
||||
return $oChangeOp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @since 2.7.5
|
||||
*/
|
||||
public function GetHiddenFieldsId()
|
||||
{
|
||||
return $this->aHiddenFieldsId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aHiddenFieldsId
|
||||
* @since 2.7.5
|
||||
*/
|
||||
public function SetHiddenFieldsId($aHiddenFieldsId)
|
||||
{
|
||||
$this->aHiddenFieldsId = $aHiddenFieldsId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -280,8 +280,8 @@ class ObjectFormHandlerHelper
|
||||
->SetFormProperties($aFormProperties);
|
||||
|
||||
$oFormManager->Build();
|
||||
|
||||
// Check the number of editable fields
|
||||
$aFormData['hidden_fields'] = $oFormManager->GetHiddenFieldsId();
|
||||
// Check the number of editable fields
|
||||
$aFormData['editable_fields_count'] = $oFormManager->GetForm()->GetEditableFieldCount();
|
||||
}
|
||||
else
|
||||
@@ -388,6 +388,7 @@ class ObjectFormHandlerHelper
|
||||
$aFormData['object_state'] = $oFormManager->GetObject()->GetState();
|
||||
$aFormData['fieldset'] = $aFieldSetData;
|
||||
$aFormData['display_mode'] = (isset($aFormProperties['properties'])) ? $aFormProperties['properties']['display_mode'] : ApplicationHelper::FORM_DEFAULT_DISPLAY_MODE;
|
||||
$aFormData['hidden_fields'] = $oFormManager->GetHiddenFieldsId();
|
||||
// - Set a text to be copied on title if the object is not in creation
|
||||
if($sMode !== static::ENUM_MODE_CREATE && !empty($sObjectId))
|
||||
{
|
||||
|
||||
@@ -397,15 +397,16 @@
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// N°3995: Loader is shown immediately, otherwise when we have a huge amount of items, we can have a bottleneck on the buildMosaic() function, blocking the display of the loader
|
||||
showMosaicLoader();
|
||||
$(document).ready(function(){
|
||||
// Auto collapse item actions popup
|
||||
$('body').click(function(){
|
||||
$('#brick_content_mosaic .item-action-wrapper.collapse.in').collapse('hide');
|
||||
});
|
||||
|
||||
// Build the tree (collapsed)
|
||||
showMosaicLoader();
|
||||
// Build the mosaic (collapsed)
|
||||
buildMosaic(oRawDatas);
|
||||
hideMosaicLoader();
|
||||
registerFilterListeners();
|
||||
|
||||
@@ -321,6 +321,8 @@
|
||||
}
|
||||
};
|
||||
|
||||
// N°3995: Loader is shown immediately, otherwise when we have a huge amount of items, we can have a bottleneck on the buildTree() function, blocking the display of the loader
|
||||
showTreeLoader();
|
||||
$(document).ready(function(){
|
||||
// Init expand/collapse all buttons
|
||||
$('#btn-collapse-all').on('click', function (oEvent) {
|
||||
@@ -405,7 +407,6 @@
|
||||
});
|
||||
|
||||
// Build the tree (collapsed)
|
||||
showTreeLoader();
|
||||
buildTree(oRawDatas);
|
||||
hideTreeLoader();
|
||||
registerFilterListeners();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -480,6 +480,10 @@
|
||||
{
|
||||
ShowErrorDialog('{{ 'UI:ObjectDoesNotExist'|dict_s|escape('js') }}', '{{ 'Error:HTTP:404'|dict_s|escape('js') }}');
|
||||
}
|
||||
else if(oXHR.status === 0 && oXHR.readyState === 0)
|
||||
{
|
||||
//DO NOTHING the ajax call has been aborted
|
||||
}
|
||||
else if(oXHR.responseText !== undefined && IsJSONString(oXHR.responseText) === true)
|
||||
{
|
||||
var oData = JSON.parse(oXHR.responseText);
|
||||
|
||||
@@ -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.5', array(
|
||||
// Identification
|
||||
'label' => 'Enhanced Customer Portal',
|
||||
'category' => 'Portal',
|
||||
|
||||
@@ -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.5',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user