diff --git a/.doc/README.md b/.doc/README.md index e6c294bc1..fb56f5f16 100644 --- a/.doc/README.md +++ b/.doc/README.md @@ -77,24 +77,20 @@ Then, **for a method** of an eligible class: :notebook: as spaces are used to mark code, the templates (`.doc/phpdoc-templates/combodo-wiki/*`) have very few indentation, thus they are awful to read (sorry). - - - ## Installation + ``` cd .doc composer require phpdocumentor/phpdocumentor:~2 --dev ``` ## Generation -`./bin/build-doc-object-manipulation` and `./bin/build-doc-extensions` contains examples of doc. generation, beware: they have to be called from the .doc directory: -```shell -cd /path/to/itop/.doc -./bin/build-doc-object-manipulation -``` - -the resulting documentation is written into `data/phpdocumentor/output` +1. Switch to this directory : `cd /path/to/itop/.doc` +2. `composer install` +3. `./bin/build-doc-object-manipulation` +3. `./bin/build-doc-extensions` +4. Get the generated files from `/path/to/itop/data/phpdocumentor/output` ## Dokuwiki requirements * the template uses the [wrap plugin](https://www.dokuwiki.org/plugin:wrap). diff --git a/.doc/bin/build-doc-extensions b/.doc/bin/build-doc-extensions index 05480345e..b9f78ffd7 100755 --- a/.doc/bin/build-doc-extensions +++ b/.doc/bin/build-doc-extensions @@ -1,6 +1,6 @@ #!/bin/sh -x -rm -rf /tmp/phpdoc-twig-cache/ && rm -rf data/phpdocumentor/output/extensions/ && rm -rf data/phpdocumentor/temp/extensions/ && .doc/vendor/bin/phpdoc -c .doc/phpdoc-extensions.dist.xml -vvv +rm -rf /tmp/phpdoc-twig-cache/ && rm -rf data/phpdocumentor/output/extensions/ && rm -rf data/phpdocumentor/temp/extensions/ && ./vendor/bin/phpdoc -c ./phpdoc-extensions.dist.xml -vvv # now wee need to lowercase every generated file because dokuwiki can't handle uppercase -cd data/phpdocumentor/output/extensions/ && for i in $( ls | grep [A-Z] ); do mv -i $i `echo $i | tr 'A-Z' 'a-z'`; done \ No newline at end of file +cd ../data/phpdocumentor/output/extensions/ && for i in $(ls | grep [A-Z]); do mv -i $i $(echo $i | tr 'A-Z' 'a-z'); done diff --git a/.doc/contributing-guide/contributing-stickers-side-by-side.png b/.doc/contributing-guide/contributing-stickers-side-by-side.png new file mode 100644 index 000000000..8bc8cc208 Binary files /dev/null and b/.doc/contributing-guide/contributing-stickers-side-by-side.png differ diff --git a/.editorconfig b/.editorconfig index 317d36083..b1daa6d05 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,26 +1,30 @@ +root = true + [*] charset = utf-8 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_formatter_tags_enabled = true ij_smart_tabs = false -ij_visual_guides = 80, 120, 140 +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 -ij_css_brace_placement = 0 +ij_css_brace_placement = end_of_line +ij_css_enforce_quotes_on_format = false ij_css_hex_color_long_format = false ij_css_hex_color_lower_case = false ij_css_hex_color_short_format = false @@ -31,59 +35,18 @@ ij_css_keep_single_line_blocks = false ij_css_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow ij_css_space_after_colon = true ij_css_space_before_opening_brace = true -ij_css_value_alignment = 0 - -[*.csv] -max_line_length = 2147483647 -ij_wrap_on_typing = false -ij_csv_wrap_long_lines = false - -[*.feature] -indent_size = 2 -ij_gherkin_keep_indents_on_empty_lines = false - -[*.less] -indent_size = 2 -ij_less_align_closing_brace_with_properties = false -ij_less_blank_lines_around_nested_selector = 1 -ij_less_blank_lines_between_blocks = 1 -ij_less_brace_placement = 0 -ij_less_hex_color_long_format = false -ij_less_hex_color_lower_case = false -ij_less_hex_color_short_format = false -ij_less_hex_color_upper_case = false -ij_less_keep_blank_lines_in_code = 2 -ij_less_keep_indents_on_empty_lines = false -ij_less_keep_single_line_blocks = false -ij_less_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow -ij_less_space_after_colon = true -ij_less_space_before_opening_brace = true -ij_less_value_alignment = 0 - -[*.sass] -indent_size = 2 -ij_sass_align_closing_brace_with_properties = false -ij_sass_blank_lines_around_nested_selector = 1 -ij_sass_blank_lines_between_blocks = 1 -ij_sass_brace_placement = 0 -ij_sass_hex_color_long_format = false -ij_sass_hex_color_lower_case = false -ij_sass_hex_color_short_format = false -ij_sass_hex_color_upper_case = false -ij_sass_keep_blank_lines_in_code = 2 -ij_sass_keep_indents_on_empty_lines = false -ij_sass_keep_single_line_blocks = false -ij_sass_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow -ij_sass_space_after_colon = true -ij_sass_space_before_opening_brace = true -ij_sass_value_alignment = 0 +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 ij_scss_brace_placement = 0 +ij_scss_enforce_quotes_on_format = false ij_scss_hex_color_long_format = false ij_scss_hex_color_lower_case = false ij_scss_hex_color_short_format = false @@ -94,17 +57,20 @@ ij_scss_keep_single_line_blocks = false ij_scss_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow ij_scss_space_after_colon = true ij_scss_space_before_opening_brace = true +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 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 @@ -112,10 +78,45 @@ 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}] +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 = true +ij_xml_keep_line_breaks_in_text = true +ij_xml_keep_whitespaces = false +ij_xml_keep_whitespaces_around_cdata = preserve +ij_xml_keep_whitespaces_inside_cdata = true +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 = 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 @@ -134,13 +135,13 @@ ij_javascript_array_initializer_wrap = off ij_javascript_assignment_wrap = off ij_javascript_binary_operation_sign_on_next_line = false ij_javascript_binary_operation_wrap = off -ij_javascript_blacklist_imports = rxjs/Rx,node_modules/**/* +ij_javascript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** ij_javascript_blank_lines_after_imports = 1 ij_javascript_blank_lines_around_class = 1 ij_javascript_blank_lines_around_field = 0 ij_javascript_blank_lines_around_function = 1 ij_javascript_blank_lines_around_method = 1 -ij_javascript_block_brace_style = next_line +ij_javascript_block_brace_style = end_of_line ij_javascript_call_parameters_new_line_after_left_paren = false ij_javascript_call_parameters_right_paren_on_new_line = false ij_javascript_call_parameters_wrap = off @@ -148,15 +149,15 @@ ij_javascript_catch_on_new_line = false 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 = never -ij_javascript_else_on_new_line = true +ij_javascript_do_while_brace_force = always +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 ij_javascript_field_prefix = _ ij_javascript_file_name_style = relaxed ij_javascript_finally_on_new_line = false -ij_javascript_for_brace_force = never +ij_javascript_for_brace_force = always ij_javascript_for_statement_new_line_after_left_paren = false ij_javascript_for_statement_right_paren_on_new_line = false ij_javascript_for_statement_wrap = off @@ -192,6 +193,9 @@ ij_javascript_parentheses_expression_new_line_after_left_paren = false ij_javascript_parentheses_expression_right_paren_on_new_line = false ij_javascript_place_assignment_sign_on_next_line = false ij_javascript_prefer_as_type_cast = false +ij_javascript_prefer_explicit_types_function_expression_returns = false +ij_javascript_prefer_explicit_types_function_returns = false +ij_javascript_prefer_explicit_types_vars_fields = false ij_javascript_prefer_parameters_wrap = false ij_javascript_reformat_c_style_comments = false ij_javascript_space_after_colon = true @@ -272,21 +276,22 @@ ij_javascript_use_path_mapping = always ij_javascript_use_public_modifier = false ij_javascript_use_semicolon_after_statement = true ij_javascript_var_declaration_wrap = normal -ij_javascript_while_brace_force = never +ij_javascript_while_brace_force = always ij_javascript_while_on_new_line = false ij_javascript_wrap_comments = false -[{*.module,*.hphp,*.phtml,*.php5,*.php4,*.php,*.ctp,*.inc}] +[{*.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_class_constants = false +ij_php_align_class_constants = true 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_match_arm_bodies = false +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 @@ -294,14 +299,17 @@ ij_php_align_multiline_for = true ij_php_align_multiline_parameters = false ij_php_align_multiline_parameters_in_calls = false ij_php_align_multiline_ternary_operation = false +ij_php_align_named_arguments = false ij_php_align_phpdoc_comments = false ij_php_align_phpdoc_param_names = false +ij_php_anonymous_brace_style = end_of_line ij_php_api_weight = 1 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_author_weight = 7 +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 ij_php_blank_lines_after_class_header = 0 @@ -318,7 +326,8 @@ ij_php_blank_lines_before_imports = 1 ij_php_blank_lines_before_method_body = 0 ij_php_blank_lines_before_package = 1 ij_php_blank_lines_before_return_statement = 1 -ij_php_block_brace_style = next_line +ij_php_blank_lines_between_imports = 0 +ij_php_block_brace_style = end_of_line ij_php_call_parameters_new_line_after_left_paren = false ij_php_call_parameters_right_paren_on_new_line = false ij_php_call_parameters_wrap = normal @@ -328,11 +337,11 @@ ij_php_class_brace_style = next_line ij_php_comma_after_last_array_element = true ij_php_concat_spaces = false ij_php_copyright_weight = 28 -ij_php_deprecated_weight = 28 +ij_php_deprecated_weight = 2 ij_php_do_while_brace_force = always ij_php_else_if_style = as_is -ij_php_else_on_new_line = true -ij_php_example_weight = 3 +ij_php_else_on_new_line = false +ij_php_example_weight = 4 ij_php_extends_keyword_wrap = off ij_php_extends_list_wrap = off ij_php_fields_default_visibility = private @@ -343,6 +352,8 @@ ij_php_for_statement_new_line_after_left_paren = false ij_php_for_statement_right_paren_on_new_line = false ij_php_for_statement_wrap = off ij_php_force_short_declaration_array_style = false +ij_php_getters_setters_naming_style = camel_case +ij_php_getters_setters_order_style = getters_first ij_php_global_weight = 28 ij_php_group_use_wrap = on_every_item ij_php_if_brace_force = always @@ -362,7 +373,8 @@ ij_php_keep_control_statement_in_one_line = true ij_php_keep_first_column_comment = true ij_php_keep_indents_on_empty_lines = false ij_php_keep_line_breaks = true -ij_php_keep_rparen_and_lbrace_on_one_line = true +ij_php_keep_rparen_and_lbrace_on_one_line = false +ij_php_keep_simple_classes_in_one_line = false ij_php_keep_simple_methods_in_one_line = false ij_php_lambda_brace_style = end_of_line ij_php_license_weight = 28 @@ -370,6 +382,7 @@ ij_php_line_comment_add_space = false ij_php_line_comment_at_first_column = true ij_php_link_weight = 28 ij_php_lower_case_boolean_const = true +ij_php_lower_case_keywords = true ij_php_lower_case_null_const = true ij_php_method_brace_style = next_line ij_php_method_call_chain_wrap = off @@ -380,9 +393,11 @@ ij_php_method_weight = 28 ij_php_modifier_list_wrap = false ij_php_multiline_chained_calls_semicolon_on_new_line = false ij_php_namespace_brace_style = 1 +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 = 4 +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 @@ -399,11 +414,13 @@ ij_php_property_read_weight = 28 ij_php_property_weight = 28 ij_php_property_write_weight = 28 ij_php_return_type_on_new_line = false -ij_php_return_weight = 5 -ij_php_see_weight = 2 +ij_php_return_weight = 6 +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_enum_backed_type = 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 @@ -417,6 +434,8 @@ 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_enum_backed_type = false +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 @@ -433,6 +452,7 @@ ij_php_space_before_method_call_parentheses = false ij_php_space_before_method_left_brace = true ij_php_space_before_method_parentheses = false ij_php_space_before_quest = true +ij_php_space_before_short_closure_left_parenthesis = false ij_php_space_before_switch_left_brace = true ij_php_space_before_switch_parentheses = true ij_php_space_before_try_left_brace = true @@ -450,6 +470,7 @@ ij_php_spaces_around_equality_operators = true ij_php_spaces_around_logical_operators = true ij_php_spaces_around_multiplicative_operators = true ij_php_spaces_around_null_coalesce_operator = true +ij_php_spaces_around_pipe_in_union_type = false ij_php_spaces_around_relational_operators = true ij_php_spaces_around_shift_operators = true ij_php_spaces_around_unary_operator = false @@ -465,11 +486,11 @@ ij_php_spaces_within_parentheses = false ij_php_spaces_within_short_echo_tags = true ij_php_spaces_within_switch_parentheses = false ij_php_spaces_within_while_parentheses = false -ij_php_special_else_if_treatment = false +ij_php_special_else_if_treatment = true ij_php_subpackage_weight = 28 ij_php_ternary_operation_signs_on_next_line = false ij_php_ternary_operation_wrap = off -ij_php_throws_weight = 6 +ij_php_throws_weight = 7 ij_php_todo_weight = 28 ij_php_unknown_tag_weight = 28 ij_php_upper_case_boolean_const = false @@ -481,9 +502,24 @@ ij_php_version_weight = 28 ij_php_while_brace_force = always ij_php_while_on_new_line = false -[{*.sht,*.htm,*.html,*.shtm,*.shtml}] +[{*.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 +ij_json_space_after_colon = true +ij_json_space_after_comma = true +ij_json_space_before_colon = true +ij_json_space_before_comma = false +ij_json_spaces_within_braces = false +ij_json_spaces_within_brackets = false +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 @@ -503,209 +539,37 @@ ij_html_keep_whitespaces_inside = span,pre,textarea ij_html_line_comment_at_first_column = true ij_html_new_line_after_last_attribute = never ij_html_new_line_before_first_attribute = never -ij_html_quote_style = double +ij_html_quote_style = none ij_html_remove_new_line_before_tags = br ij_html_space_after_tag_name = false ij_html_space_around_equality_in_attribute = false ij_html_space_inside_empty_tag = false ij_html_text_wrap = normal -[{*.ts,*.ats}] -ij_continuation_indent_size = 4 -ij_typescript_align_imports = false -ij_typescript_align_multiline_array_initializer_expression = false -ij_typescript_align_multiline_binary_operation = false -ij_typescript_align_multiline_chained_methods = false -ij_typescript_align_multiline_extends_list = false -ij_typescript_align_multiline_for = true -ij_typescript_align_multiline_parameters = true -ij_typescript_align_multiline_parameters_in_calls = false -ij_typescript_align_multiline_ternary_operation = false -ij_typescript_align_object_properties = 0 -ij_typescript_align_union_types = false -ij_typescript_align_var_statements = 0 -ij_typescript_array_initializer_new_line_after_left_brace = false -ij_typescript_array_initializer_right_brace_on_new_line = false -ij_typescript_array_initializer_wrap = off -ij_typescript_assignment_wrap = off -ij_typescript_binary_operation_sign_on_next_line = false -ij_typescript_binary_operation_wrap = off -ij_typescript_blacklist_imports = rxjs/Rx,node_modules/**/* -ij_typescript_blank_lines_after_imports = 1 -ij_typescript_blank_lines_around_class = 1 -ij_typescript_blank_lines_around_field = 0 -ij_typescript_blank_lines_around_field_in_interface = 0 -ij_typescript_blank_lines_around_function = 1 -ij_typescript_blank_lines_around_method = 1 -ij_typescript_blank_lines_around_method_in_interface = 1 -ij_typescript_block_brace_style = end_of_line -ij_typescript_call_parameters_new_line_after_left_paren = false -ij_typescript_call_parameters_right_paren_on_new_line = false -ij_typescript_call_parameters_wrap = off -ij_typescript_catch_on_new_line = false -ij_typescript_chained_call_dot_on_new_line = true -ij_typescript_class_brace_style = end_of_line -ij_typescript_comma_on_new_line = false -ij_typescript_do_while_brace_force = never -ij_typescript_else_on_new_line = false -ij_typescript_enforce_trailing_comma = keep -ij_typescript_extends_keyword_wrap = off -ij_typescript_extends_list_wrap = off -ij_typescript_field_prefix = _ -ij_typescript_file_name_style = relaxed -ij_typescript_finally_on_new_line = false -ij_typescript_for_brace_force = never -ij_typescript_for_statement_new_line_after_left_paren = false -ij_typescript_for_statement_right_paren_on_new_line = false -ij_typescript_for_statement_wrap = off -ij_typescript_force_quote_style = false -ij_typescript_force_semicolon_style = false -ij_typescript_function_expression_brace_style = end_of_line -ij_typescript_if_brace_force = never -ij_typescript_import_merge_members = global -ij_typescript_import_prefer_absolute_path = global -ij_typescript_import_sort_members = true -ij_typescript_import_sort_module_name = false -ij_typescript_import_use_node_resolution = true -ij_typescript_imports_wrap = on_every_item -ij_typescript_indent_case_from_switch = true -ij_typescript_indent_chained_calls = true -ij_typescript_indent_package_children = 0 -ij_typescript_jsdoc_include_types = false -ij_typescript_jsx_attribute_value = braces -ij_typescript_keep_blank_lines_in_code = 2 -ij_typescript_keep_first_column_comment = true -ij_typescript_keep_indents_on_empty_lines = false -ij_typescript_keep_line_breaks = true -ij_typescript_keep_simple_blocks_in_one_line = false -ij_typescript_keep_simple_methods_in_one_line = false -ij_typescript_line_comment_add_space = true -ij_typescript_line_comment_at_first_column = false -ij_typescript_method_brace_style = end_of_line -ij_typescript_method_call_chain_wrap = off -ij_typescript_method_parameters_new_line_after_left_paren = false -ij_typescript_method_parameters_right_paren_on_new_line = false -ij_typescript_method_parameters_wrap = off -ij_typescript_object_literal_wrap = on_every_item -ij_typescript_parentheses_expression_new_line_after_left_paren = false -ij_typescript_parentheses_expression_right_paren_on_new_line = false -ij_typescript_place_assignment_sign_on_next_line = false -ij_typescript_prefer_as_type_cast = false -ij_typescript_prefer_parameters_wrap = false -ij_typescript_reformat_c_style_comments = false -ij_typescript_space_after_colon = true -ij_typescript_space_after_comma = true -ij_typescript_space_after_dots_in_rest_parameter = false -ij_typescript_space_after_generator_mult = true -ij_typescript_space_after_property_colon = true -ij_typescript_space_after_quest = true -ij_typescript_space_after_type_colon = true -ij_typescript_space_after_unary_not = false -ij_typescript_space_before_async_arrow_lparen = true -ij_typescript_space_before_catch_keyword = true -ij_typescript_space_before_catch_left_brace = true -ij_typescript_space_before_catch_parentheses = true -ij_typescript_space_before_class_lbrace = true -ij_typescript_space_before_class_left_brace = true -ij_typescript_space_before_colon = true -ij_typescript_space_before_comma = false -ij_typescript_space_before_do_left_brace = true -ij_typescript_space_before_else_keyword = true -ij_typescript_space_before_else_left_brace = true -ij_typescript_space_before_finally_keyword = true -ij_typescript_space_before_finally_left_brace = true -ij_typescript_space_before_for_left_brace = true -ij_typescript_space_before_for_parentheses = true -ij_typescript_space_before_for_semicolon = false -ij_typescript_space_before_function_left_parenth = true -ij_typescript_space_before_generator_mult = false -ij_typescript_space_before_if_left_brace = true -ij_typescript_space_before_if_parentheses = true -ij_typescript_space_before_method_call_parentheses = false -ij_typescript_space_before_method_left_brace = true -ij_typescript_space_before_method_parentheses = false -ij_typescript_space_before_property_colon = false -ij_typescript_space_before_quest = true -ij_typescript_space_before_switch_left_brace = true -ij_typescript_space_before_switch_parentheses = true -ij_typescript_space_before_try_left_brace = true -ij_typescript_space_before_type_colon = false -ij_typescript_space_before_unary_not = false -ij_typescript_space_before_while_keyword = true -ij_typescript_space_before_while_left_brace = true -ij_typescript_space_before_while_parentheses = true -ij_typescript_spaces_around_additive_operators = true -ij_typescript_spaces_around_arrow_function_operator = true -ij_typescript_spaces_around_assignment_operators = true -ij_typescript_spaces_around_bitwise_operators = true -ij_typescript_spaces_around_equality_operators = true -ij_typescript_spaces_around_logical_operators = true -ij_typescript_spaces_around_multiplicative_operators = true -ij_typescript_spaces_around_relational_operators = true -ij_typescript_spaces_around_shift_operators = true -ij_typescript_spaces_around_unary_operator = false -ij_typescript_spaces_within_array_initializer_brackets = false -ij_typescript_spaces_within_brackets = false -ij_typescript_spaces_within_catch_parentheses = false -ij_typescript_spaces_within_for_parentheses = false -ij_typescript_spaces_within_if_parentheses = false -ij_typescript_spaces_within_imports = false -ij_typescript_spaces_within_interpolation_expressions = false -ij_typescript_spaces_within_method_call_parentheses = false -ij_typescript_spaces_within_method_parentheses = false -ij_typescript_spaces_within_object_literal_braces = false -ij_typescript_spaces_within_object_type_braces = true -ij_typescript_spaces_within_parentheses = false -ij_typescript_spaces_within_switch_parentheses = false -ij_typescript_spaces_within_type_assertion = false -ij_typescript_spaces_within_union_types = true -ij_typescript_spaces_within_while_parentheses = false -ij_typescript_special_else_if_treatment = true -ij_typescript_ternary_operation_signs_on_next_line = false -ij_typescript_ternary_operation_wrap = off -ij_typescript_union_types_wrap = on_every_item -ij_typescript_use_chained_calls_group_indents = false -ij_typescript_use_double_quotes = true -ij_typescript_use_explicit_js_extension = global -ij_typescript_use_path_mapping = always -ij_typescript_use_public_modifier = false -ij_typescript_use_semicolon_after_statement = true -ij_typescript_var_declaration_wrap = normal -ij_typescript_while_brace_force = never -ij_typescript_while_on_new_line = false -ij_typescript_wrap_comments = 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 -[{*.yml,*.yaml}] +[{*.yaml,*.yml}] indent_size = 2 -ij_continuation_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 - -[{*.zsh,*.bash,*.sh}] -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 - -[{.stylelintrc,.eslintrc,.babelrc,jest.config,*.bowerrc,*.jsb3,*.jsb2,*.json}] -indent_size = 2 -ij_json_keep_blank_lines_in_code = 0 -ij_json_keep_indents_on_empty_lines = false -ij_json_keep_line_breaks = true -ij_json_space_after_colon = true -ij_json_space_after_comma = true -ij_json_space_before_colon = true -ij_json_space_before_comma = false -ij_json_spaces_within_braces = false -ij_json_spaces_within_brackets = false -ij_json_wrap_long_lines = false - -[{phpunit.xml.dist,*.jhm,*.rng,*.wsdl,*.fxml,*.xslt,*.jrxml,*.ant,*.xul,*.xsl,*.xsd,*.tld,*.jnlp,*.xml}] -indent_size = 2 -indent_style = tab -tab_width = 2 -ij_smart_tabs = true -ij_xml_block_comment_at_first_column = true -ij_xml_keep_indents_on_empty_lines = false -ij_xml_line_comment_at_first_column = 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 diff --git a/.gitflow b/.gitflow deleted file mode 100644 index 44adde783..000000000 --- a/.gitflow +++ /dev/null @@ -1,9 +0,0 @@ -[gitflow "branch"] -master = master -develop = develop -[gitflow "prefix"] -feature = feature/ -release = release/ -hotfix = hotfix/ -versiontag = -support = support/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index bc4aef17f..462770aad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,15 @@ +################################### Temporary ignore rules during 2.8 UI/UX dev - START +/css/backoffice/main.css + +# Sass converter +/**/.sass-cache/ +################################### Temporary ignore rules during 2.8 UI/UX dev - END + + + + + # no slash at the end to handle also symlinks /toolkit /env-* @@ -6,12 +17,6 @@ # maintenance mode (N°2240) /.maintenance -# listing prevention in conf directory -/conf/** -!/conf/.htaccess -!/conf/index.php -!/conf/web.config - # composer reserver directory, from sources, populate/update using "composer install" vendor/* test/vendor/* @@ -19,6 +24,7 @@ test/vendor/* # all conf but listing prevention /conf/** !/conf/.htaccess +!/conf/index.php !/conf/web.config # all datas but listing prevention @@ -26,6 +32,8 @@ test/vendor/* !/data/.htaccess !/data/index.php !/data/web.config +!/data/exclude.txt +!/data/.compilation-symlinks # iTop extensions /extensions/** @@ -40,11 +48,6 @@ test/vendor/* # Jetbrains /.idea/** -!/.idea/encodings.xml -!/.idea/codeStyles -!/.idea/codeStyles/* -!/.idea/inspectionProfiles -!/.idea/inspectionProfiles/* # doc. generation /.doc/vendor @@ -139,4 +142,3 @@ local.properties .cache-main .scala_dependencies .worksheet - diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 6af43b87d..000000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index d68e1e434..000000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index c2bae49d7..000000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Combodo.xml b/.idea/inspectionProfiles/Combodo.xml deleted file mode 100644 index 763f7a53d..000000000 --- a/.idea/inspectionProfiles/Combodo.xml +++ /dev/null @@ -1,171 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index b9013fdbd..000000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 7f3af8c75..000000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.jenkins/bin/init/append_files.sh b/.jenkins/bin/init/append_files.sh deleted file mode 100755 index 56226a792..000000000 --- a/.jenkins/bin/init/append_files.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -set -x - -# create target dirs -mkdir -p var -mkdir -p toolkit - -# cleanup target dirs -rm -rf toolkit/* - -# fill target dirs -curl https://www.combodo.com/documentation/iTopDataModelToolkit-2.3.zip > toolkit.zip -unzip toolkit.zip -rm toolkit.zip -cp -r .jenkins/configuration/default-environment/unattended_install/* toolkit diff --git a/.jenkins/bin/init/composer_install.sh b/.jenkins/bin/init/composer_install.sh deleted file mode 100755 index 8d74fe861..000000000 --- a/.jenkins/bin/init/composer_install.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -set -x - -# on the root dir -# composer install -a # => Not needed anymore (libs were added to git with N°2435) - - -# under the test dir -cd test -composer install diff --git a/.jenkins/bin/init/debug.sh b/.jenkins/bin/init/debug.sh deleted file mode 100755 index 583a0aed6..000000000 --- a/.jenkins/bin/init/debug.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash - -set -x - - - -whoami -pwd -ls - -echo "$BRANCH_NAME:${BRANCH_NAME}" - -echo "printenv :" -printenv - diff --git a/.jenkins/bin/tests/phpunit.sh b/.jenkins/bin/tests/phpunit.sh deleted file mode 100755 index 17fa52827..000000000 --- a/.jenkins/bin/tests/phpunit.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash -set -x - -cd test - -export DEBUG_UNIT_TEST=0 -RUN_NONREG_TESTS=0 - -#USAGE ${debugMode} ${runNonRegOQLTests} "${coverture}" "${testFile}" - -if [ $# -ge 1 -a "x$1" == "xtrue" ] -then - export DEBUG_UNIT_TEST=1 -else - export DEBUG_UNIT_TEST=0 -fi - -set -x -OPTION="" -if [ $# -ge 3 -a "x$3" == "xtrue" ] -then - ##coverture - OPTION="-dxdebug.coverage_enable=1 --coverage-clover ../var/test/coverage.xml" -fi - -TESTFILE="$4" -if [ "x$TESTFILE" != "x" ] -then - # shellcheck disable=SC2001 - TESTFILE=$(echo "$TESTFILE" | sed 's|test/||1') - php vendor/bin/phpunit --log-junit ../var/test/phpunit-log.junit.xml $OPTION $TESTFILE --teamcity - exit 0 -fi - -if [ $# -ge 2 -a "x$2" == "xtrue" ] -then - php vendor/bin/phpunit --log-junit ../var/test/phpunit-log.junit.xml $OPTION --teamcity -else - #echo php vendor/bin/phpunit --log-junit ../var/test/phpunit-log.junit.xml --teamcity - php vendor/bin/phpunit --log-junit ../var/test/phpunit-log.junit.xml $OPTION --exclude-group OQL --teamcity -fi diff --git a/.jenkins/bin/unattended_install/default_env.sh b/.jenkins/bin/unattended_install/default_env.sh deleted file mode 100755 index e02b87fd6..000000000 --- a/.jenkins/bin/unattended_install/default_env.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -x - -chmod 666 conf/production/config-itop.php - -cd toolkit -php unattended_install.php --response_file=default-params.xml --clean=true diff --git a/.jenkins/configuration/default-environment/unattended_install/default-params.xml b/.jenkins/configuration/default-environment/unattended_install/default-params.xml deleted file mode 100644 index e6139784e..000000000 --- a/.jenkins/configuration/default-environment/unattended_install/default-params.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - - upgrade - - - - datamodels/2.x/ - 2.5.0 - default-config-itop.php - extensions - production - - - - jenkins_itop - IKnowYouSeeMeInJenkinsConf - itop_ci - - - - - http://127.0.0.1/itop/svn/trunk/ - /usr/bin/dot - - admin - admin - EN US - - EN US - - authent-external - authent-local - itop-backup - itop-config - itop-profiles-itil - itop-sla-computation - itop-tickets - itop-welcome-itil - itop-config-mgmt - itop-attachments - itop-datacenter-mgmt - itop-endusers-devices - itop-storage-mgmt - itop-virtualization-mgmt - itop-bridge-virtualization-storage - itop-service-mgmt - itop-request-mgmt - itop-portal - itop-portal-base - itop-change-mgmt - itop-knownerror-mgmt - - - itop-config-mgmt-core - itop-config-mgmt-datacenter - itop-config-mgmt-end-user - itop-config-mgmt-storage - itop-config-mgmt-virtualization - itop-service-mgmt-enterprise - itop-ticket-mgmt-simple-ticket - itop-ticket-mgmt-simple-ticket-enhanced-portal - itop-change-mgmt-simple - itop-kown-error-mgmt - - 1 - - - 1 - - - diff --git a/.jenkins/configuration/default-environment/unattended_install/unattended_install.php b/.jenkins/configuration/default-environment/unattended_install/unattended_install.php deleted file mode 100644 index 804d206db..000000000 --- a/.jenkins/configuration/default-environment/unattended_install/unattended_install.php +++ /dev/null @@ -1,208 +0,0 @@ -Get('mode'); - -if ($sMode == 'install') -{ - echo "Installation mode detected.\n"; - $bClean = utils::ReadParam('clean', false, true /* CLI allowed */); - if ($bClean) - { - echo "Cleanup mode detected.\n"; - $sTargetEnvironment = $oParams->Get('target_env', ''); - if ($sTargetEnvironment == '') - { - $sTargetEnvironment = 'production'; - } - $sTargetDir = APPROOT.'env-'.$sTargetEnvironment; - - // Configuration file - $sConfigFile = APPCONF.$sTargetEnvironment.'/'.ITOP_CONFIG_FILE; - if (file_exists($sConfigFile)) - { - echo "Trying to delete the configuration file: '$sConfigFile'.\n"; - @chmod($sConfigFile, 0770); // RWX for owner and group, nothing for others - unlink($sConfigFile); - } - else - { - echo "No config file to delete ($sConfigFile does not exist).\n"; - } - - // env-xxx directory - if (file_exists($sTargetDir)) - { - if (is_dir($sTargetDir)) - { - echo "Emptying the target directory '$sTargetDir'.\n"; - SetupUtils::tidydir($sTargetDir); - } - else - { - die("ERROR the target dir '$sTargetDir' exists, but is NOT a directory !!!\nExiting.\n"); - } - } - else - { - echo "No target directory to delete ($sTargetDir does not exist).\n"; - } - - // Database - $aDBSettings = $oParams->Get('database', array()); - $sDBServer = $aDBSettings['server']; - $sDBUser = $aDBSettings['user']; - $sDBPwd = $aDBSettings['pwd']; - $sDBName = $aDBSettings['name']; - $sDBPrefix = $aDBSettings['prefix']; - - if ($sDBPrefix != '') - { - die("Cleanup not implemented for a partial database (prefix= '$sDBPrefix')\nExiting."); - } - - $oMysqli = new mysqli($sDBServer, $sDBUser, $sDBPwd); - if ($oMysqli->connect_errno) - { - die("Cannot connect to the MySQL server (".$oMysqli->connect_errno . ") ".$oMysqli->connect_error."\nExiting"); - } - else - { - if ($oMysqli->select_db($sDBName)) - { - echo "Deleting database '$sDBName'\n"; - $oMysqli->query("DROP DATABASE `$sDBName`"); - } - else - { - echo "The database '$sDBName' does not seem to exist. Nothing to cleanup.\n"; - } - } - } -} - -$bHasErrors = false; -$aChecks = SetupUtils::CheckBackupPrerequisites(APPROOT.'data'); // mmm should be the backup destination dir - -$aSelectedModules = $oParams->Get('selected_modules'); -$sSourceDir = $oParams->Get('source_dir', 'datamodels/latest'); -$sExtensionDir = $oParams->Get('extensions_dir', 'extensions'); -$aChecks = array_merge($aChecks, SetupUtils::CheckSelectedModules($sSourceDir, $sExtensionDir, $aSelectedModules)); - - -foreach($aChecks as $oCheckResult) -{ - switch($oCheckResult->iSeverity) - { - case CheckResult::ERROR: - $bHasErrors = true; - $sHeader = "Error"; - break; - - case CheckResult::WARNING: - $sHeader = "Warning"; - break; - - case CheckResult::INFO: - default: - $sHeader = "Info"; - break; - } - echo $sHeader.": ".$oCheckResult->sLabel; - if (strlen($oCheckResult->sDescription)) - { - echo ' - '.$oCheckResult->sDescription; - } - echo "\n"; -} - -if ($bHasErrors) -{ - echo "Encountered stopper issues. Aborting...\n"; - die; -} - -$bFoundIssues = false; - -$bInstall = utils::ReadParam('install', true, true /* CLI allowed */); -if ($bInstall) -{ - echo "Starting the unattended installation...\n"; - $oWizard = new ApplicationInstaller($oParams); - $bRes = $oWizard->ExecuteAllSteps(); - if (!$bRes) - { - echo "\nencountered installation issues!"; - $bFoundIssues = true; - } -} -else -{ - echo "No installation requested.\n"; -} -if (!$bFoundIssues && $bCheckConsistency) -{ - echo "Checking data model consistency.\n"; - ob_start(); - $sCheckRes = ''; - try - { - MetaModel::CheckDefinitions(false); - $sCheckRes = ob_get_clean(); - } - catch(Exception $e) - { - $sCheckRes = ob_get_clean()."\nException: ".$e->getMessage(); - } - if (strlen($sCheckRes) > 0) - { - echo $sCheckRes; - echo "\nfound consistency issues!"; - $bFoundIssues = true; - } -} - -if (!$bFoundIssues) -{ - // last line: used to check the install - // the only way to track issues in case of Fatal error or even parsing error! - echo "\ninstalled!"; - exit; -} diff --git a/.make/build/afterBuild.php b/.make/build/afterBuild.php new file mode 100644 index 000000000..cecdaf9e9 --- /dev/null +++ b/.make/build/afterBuild.php @@ -0,0 +1,90 @@ + array("pipe", "r"), // stdin + 1 => array("pipe", "w"), // stdout + 2 => array("pipe", "w"), // stderr + ); + $process = proc_open($cmd, $descriptorspec, $pipes, __DIR__ . '/..', null); + + $stdout = stream_get_contents($pipes[1]); + fclose($pipes[1]); + + $stderr = stream_get_contents($pipes[2]); + fclose($pipes[2]); + + $iCode = proc_close($process); + $bSuccess = (0 === $iCode); + + $iElapsed = time() - $iBeginTime; + if (!$bSuccess) { + fwrite(STDERR, sprintf( + "\nCOMMAND FAILED! (%s) \n - status:%s \n - stderr:%s \n - stdout: %s\n - elapsed:%ss\n\n", + $cmd, + $iCode, + rtrim($stderr), + rtrim($stdout), + $iElapsed + )); + } + else + { + echo "| elapsed:${iElapsed}s \n"; + } + + if (!empty($stderr)) + { + fwrite(STDERR, "$stderr\n"); + } + if (!empty($stdout)) + { + echo "stdout :$stdout\n\n"; + } + + return $bSuccess; +} diff --git a/.make/build/commands/setupCssCompiler.php b/.make/build/commands/setupCssCompiler.php new file mode 100644 index 000000000..728911b18 --- /dev/null +++ b/.make/build/commands/setupCssCompiler.php @@ -0,0 +1,51 @@ + + * + */ + +use Combodo\iTop\Composer\iTopComposer; + +$iTopFolder = __DIR__."/../../../"; + +require_once("$iTopFolder/approot.inc.php"); +require_once(APPROOT."/application/utils.inc.php"); + +if (php_sapi_name() !== 'cli') +{ + throw new \Exception('This script can only run from CLI'); +} + +$sCssFile = APPROOT.'/css/setup.css'; +if (file_exists($sCssFile)) +{ + fwrite(STDERR, "$sCssFile already exists (it should not), removing it."); + if (!unlink($sCssFile)) + { + fwrite(STDERR, "Failed to remove $sCssFile, exiting."); + exit(1); + } +} +$sCssRelPath = utils::GetCSSFromSASS('css/setup.scss'); + +if (!file_exists($sCssFile)) +{ + fwrite(STDERR, "Failed to compile $sCssFile, exiting."); + exit(1); +} \ No newline at end of file diff --git a/.make/composer/listOutdated.php b/.make/composer/listOutdated.php index f67a2fcf2..4c47bfa81 100644 --- a/.make/composer/listOutdated.php +++ b/.make/composer/listOutdated.php @@ -1,6 +1,6 @@ OK to commit !\n"; +exit(0); + + + +function GetFilesWithExtension($sExtension, $aFiles) { + return array_filter( + $aFiles, + function($item) use ($sExtension) { + return (endsWith($item, '.'.$sExtension)); + } + ); +} + +function endsWith( $haystack, $needle ) { + $length = strlen( $needle ); + if( !$length ) { + return true; + } + return substr( $haystack, -$length ) === $needle; +} + +function exitWithMessage($sMessage, $iCode) { + echo $sMessage; + exit($iCode); +} \ No newline at end of file diff --git a/.make/license/updateLicenses.php b/.make/license/updateLicenses.php index b2ef0b631..7531aa003 100644 --- a/.make/license/updateLicenses.php +++ b/.make/license/updateLicenses.php @@ -1,7 +1,22 @@ 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); \ No newline at end of file +$new_dom->save($xmlFilePath); +echo "OK!\n"; \ No newline at end of file diff --git a/.make/release/changelog.php b/.make/release/changelog.php new file mode 100644 index 000000000..bd76f9e7b --- /dev/null +++ b/.make/release/changelog.php @@ -0,0 +1,77 @@ + + * Bug_ref;Bug_URL;sha1 + * 1234;https://support.combodo.com/pages/UI.php?operation=details&class=Bug&id=1234;949b213f9|b1ca1f263|a1271da74 + * + * + * 2. List of commits sha1/message without bug ref + * Example : + * + * sha1;subject + * a6aa183e2;:bookmark: Prepare 2.7.5 + * + */ + + +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"; +} \ No newline at end of file diff --git a/.make/release/update-versions.php b/.make/release/update-versions.php index 0f1dcd24e..488131d4b 100644 --- a/.make/release/update-versions.php +++ b/.make/release/update-versions.php @@ -25,7 +25,6 @@ require_once (__DIR__.DIRECTORY_SEPARATOR.'update.classes.inc.php'); /** @var \FileVersionUpdater[] $aFilesUpdaters */ $aFilesUpdaters = array( new iTopVersionFileUpdater(), - new CssVariablesFileUpdater(), new DatamodelsModulesFiles(), ); diff --git a/.make/release/update.classes.inc.php b/.make/release/update.classes.inc.php index 6e20ee0d7..d344455c0 100644 --- a/.make/release/update.classes.inc.php +++ b/.make/release/update.classes.inc.php @@ -89,26 +89,6 @@ class iTopVersionFileUpdater extends AbstractSingleFileVersionUpdater } } -class CssVariablesFileUpdater extends AbstractSingleFileVersionUpdater -{ - public function __construct() - { - parent::__construct('css/css-variables.scss'); - } - - /** - * @inheritDoc - */ - public function UpdateFileContent($sVersionLabel, $sFileContent, $sFileFullPath) - { - return preg_replace( - '/(\$version: "v)[^"]*(";)/', - '${1}'.$sVersionLabel.'${2}', - $sFileContent - ); - } -} - abstract class AbstractGlobFileVersionUpdater extends FileVersionUpdater { protected $sGlobPattern; diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 646fbafdb..2d03f2bdb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,7 +42,7 @@ If you want to use another license, you may [create an extension][wiki new ext]. When we first start with Git, we were using the [GitFlow](https://nvie.com/posts/a-successful-git-branching-model/) branch model. As there was some confusions about branches to use for current developed release and previous maintained release, and also because we were using just a very few of the GitFlow commands, we decided to add just a little modification to this branch model : since april 2020 - we don't have anymore a `master` branch. + we don't have a `master` branch anymore. Here are the branches we use and their meaning : @@ -52,23 +52,23 @@ Here are the branches we use and their meaning : For example, if no version is currently prepared for shipping we could have: -- `develop` containing future 2.8.0 version +- `develop` containing future 3.0.0 version - `support/2.7`: 2.7.x maintenance version - `support/2.6`: 2.6.x maintenance version - `support/2.5`: 2.5.x maintenance version -In this example, when 2.8.0-beta is shipped that will become: +In this example, when 3.0.0-beta is shipped that will become: -- `develop`: future 2.9.0 version -- `release/2.8.0`: 2.8.0-beta +- `develop`: future 3.1.0 version +- `release/3.0.0`: 3.0.0-beta - `support/2.7`: 2.7.x maintenance version - `support/2.6`: 2.6.x maintenance version - `support/2.5`: 2.5.x maintenance version -And when 2.8.0 final will be out: +And when 3.0.0 final will be out: -- `develop`: future 2.9.0 version -- `support/2.8`: 2.8.x maintenance version (will host developments for 2.8.1) +- `develop`: future 3.1.0 version +- `support/3.0`: 3.0.x maintenance version (will host developments for 3.0.1) - `support/2.7`: 2.7.x maintenance version - `support/2.6`: 2.6.x maintenance version - `support/2.5`: 2.5.x maintenance version @@ -125,8 +125,7 @@ Our tests are located in the `test/` directory, containing a PHPUnit config file * ⬆️ `:arrow_up:` when upgrading dependencies * ⬇️ `:arrow_down:` when downgrading dependencies * ♻️ `:recycle:` code refactoring - * 💄 `:lipstick:` Updating the UI and style files. - + * 💄 `:lipstick:` Updating the UI and style files. ## 👥 Pull request @@ -137,3 +136,25 @@ When your code is working, please: * create a pull request. Detailed procedure to work on fork and create PR is available [in GitHub help pages](https://help.github.com/articles/creating-a-pull-request-from-a-fork/). + +You might check the ["Allow edits from maintainers" PR checkbox][allow_edits_checkbox] to ease review. + +[allow_edits_checkbox]: https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork#enabling-repository-maintainer-permissions-on-existing-pull-requests + +### 🙏 We are thankful + +We are thankful for all your contributions to the iTop universe! As a thank you gift, we will send stickers to every iTop (& extensions) contributors! + +Stickers' design might change from one year to another. For the first year we wanted to try a "craft beer label" look, see examples below: + +* Bug hunter: Fix a bug +* Translator: Add/update translations +* White hat: Find and/or fix a vulnerability +* Contributor: Contribute by finding a bug, making an extension or any other way +* Partner: For Combodo's official partners +* Graduated: Follow a Combodo's iTop training +* Ambassador: Outstanding community contributors +* Beta tester: Test and give feedback on beta releases +* Extension developer: Develop and publish an extension + +![](.doc/contributing-guide/contributing-stickers-side-by-side.png) diff --git a/Jenkinsfile b/Jenkinsfile index 32c49f9b7..56f298717 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,73 +1,11 @@ -pipeline { - agent any - parameters { - booleanParam(name: 'debugMode', defaultValue: 'false', description: 'Debug mode?') - string(name: 'testFile', defaultValue: '', description: 'Provide test file to execute. Example: test/core/LogAPITest.php') - booleanParam(name: 'coverture', defaultValue: 'false', description: 'Test coverture?') - booleanParam(name: 'runNonRegOQLTests', defaultValue: 'false', description: 'Do You want to run legacy OQL regression tests?') - } - stages { +def infra - stage('init') { - parallel { - stage('debug') { - steps { - sh './.jenkins/bin/init/debug.sh' - } - } - stage('append files to project') { - steps { - sh './.jenkins/bin/init/append_files.sh' - } - } - stage('composer install') { - steps { - sh './.jenkins/bin/init/composer_install.sh' - } - } - } - } +node(){ + checkout scm - stage('unattended_install') { - parallel { - stage('unattended_install default env') { - steps { - sh './.jenkins/bin/unattended_install/default_env.sh' - } - } - } - } - - stage('test') { - parallel { - stage('phpunit') { - steps { - sh './.jenkins/bin/tests/phpunit.sh ${debugMode} ${runNonRegOQLTests} "${coverture}" "${testFile}"' - } - } - } - } - - } - - post { - always { - archiveArtifacts allowEmptyArchive:true, excludes: '.gitkeep', artifacts: 'var/test/*.xml' - junit 'var/test/phpunit-log.junit.xml' - } - failure { - slackSend(channel: "#jenkins-itop", color: '#FF0000', message: "Ho no! Build failed! (${currentBuild.result}), Job '${env.JOB_NAME_UNESCAPED} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})") - } - fixed { - slackSend(channel: "#jenkins-itop", color: '#FFa500', message: "Yes! Build repaired! (${currentBuild.result}), Job '${env.JOB_NAME_UNESCAPED} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})") - } - } - - environment { - DEBUG_UNIT_TEST = '0' - JOB_NAME_UNESCAPED = env.JOB_NAME.replaceAll("%2F", "/") - } - options { - timeout(time: 20, unit: 'MINUTES') - } + infra = load '/var/lib/jenkins/workspace/itop-test-infra_master/src/Infra.groovy' } + + +infra.call() + diff --git a/README.md b/README.md index cfb243b02..b291bbc3d 100644 --- a/README.md +++ b/README.md @@ -72,50 +72,57 @@ iTop development is sponsored, led and supported by [Combodo][0]. ## Contributors -We would like to give a special thank you to the people from the community who contributed to this project, including: +We would like to give a special thank you 🤗 to the people from the community who contributed to this project, including: ### Names - - Alves, David - - Beck, Pedro - - Bilger, Jean-François - - Bostoen, Jeffrey - - Cardoso, Anderson - - Cassaro, Bruno - - Casteleyn, Thomas - - Castro, Randall Badilla - - Colantoni, Maria Laura - - Couronné, Guy - - Dvořák, Lukáš - - Goethals, Stefan - - Gumble, David - - Hippler, Lars - - Khamit, Shamil - - Kincel, Martin - - Konečný, Kamil - - Kunin, Vladimir - - Lassiter, Dennis - - Lazcano, Federico - - Lucas, Jonathan - - Malik, Remie - - Rosenke, Stephan - - Seki, Shoji - - Shilov, Vladimir - - Tulio, Marco - - Turrubiates, Miguel + +- Alves, David +- Beck, Pedro +- Bilger, Jean-François +- Bostoen, Jeffrey (a.k.a @jbostoen) +- Cardoso, Anderson +- Cassaro, Bruno +- Casteleyn, Thomas (a.k.a @Hipska) +- Castro, Randall Badilla +- Colantoni, Maria Laura +- Couronné, Guy +- Dvořák, Lukáš +- Goethals, Stefan +- Gumble, David +- Kaltefleiter, Lars (a.k.a @larhip) +- Khamit, Shamil +- Kincel, Martin +- Konečný, Kamil +- Kunin, Vladimir +- Lassiter, Dennis +- Lazcano, Federico +- Lucas, Jonathan +- Malik, Remie +- Mindêllo de Andrade, Lucas (a.k.a @rokam) +- Raenker, Martin +- Rosenke, Stephan +- Seki, Shoji +- Shilov, Vladimir +- Stukalov, Ilya (a.k.a @ilya-stukalov) +- Tulio, Marco +- Turrubiates, Miguel ### Aliases - - chifu1234 - - cprobst - - Karkoff1212 - - larhip - - Laura - - Purple Grape - - Schlobinux - - theBigOne - - ulmerspatz + +- chifu1234 +- cprobst +- DudekArtur +- Karkoff1212 +- Laura +- Purple Grape +- Schlobinux +- theBigOne +- ulmerspatz ### Companies - - Hardis - - ITOMIG - - Pimkie + +- [Hardis](https://www.hardis-group.com/) +- [ITOMIG](https://www.itomig.de/) +- [Pimkie](https://www.pimkie.com/) +- [Super-Visions](https://www.super-visions.com/) diff --git a/SECURITY.md b/SECURITY.md index 360d282f7..0c0cd411f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -9,7 +9,7 @@ responsible disclosure and will make every effort to acknowledge your contributi ### iTop vulnerabilities Please send a procedure to reproduce iTop vulnerabilities to [itop-security@combodo.com](mailto:itop-security@combodo.com). -You can send us a standard "given / then / when" report, including iTop version, impacts, and maybe installed modules or data if they are +You can send us a standard "given / when / then" report, including iTop version, impacts, and maybe installed modules or data if they are needed to reproduce. ### Dependencies vulnerabilities diff --git a/addons/userrights/userrightsmatrix.class.inc.php b/addons/userrights/userrightsmatrix.class.inc.php index 4141cfffb..d80af3ed0 100644 --- a/addons/userrights/userrightsmatrix.class.inc.php +++ b/addons/userrights/userrightsmatrix.class.inc.php @@ -1,5 +1,5 @@ "addon/userrights,grant_by_profile", + "category" => "addon/userrights,grant_by_profile,filter", "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", @@ -55,7 +42,6 @@ class URP_Profiles extends UserRightsBaseClassGUI "db_table" => "priv_urp_profiles", "db_key_field" => "id", "db_finalclass_field" => "", - "display_template" => "", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); @@ -170,11 +156,9 @@ class URP_Profiles extends UserRightsBaseClassGUI function DisplayBareRelations(WebPage $oPage, $bEditMode = false) { parent::DisplayBareRelations($oPage, $bEditMode); - if (!$bEditMode) - { - $oPage->SetCurrentTab('UI:UserManagement:GrantMatrix'); - $this->DoShowGrantSumary($oPage); - } + + $oPage->SetCurrentTab('UI:UserManagement:GrantMatrix'); + $this->DoShowGrantSumary($oPage); } public static function GetReadOnlyAttributes() @@ -235,15 +219,14 @@ class URP_UserProfile extends UserRightsBaseClassGUI { $aParams = array ( - "category" => "addon/userrights,grant_by_profile", + "category" => "addon/userrights,grant_by_profile,filter", "key_type" => "autoincrement", - "name_attcode" => "userid", + "name_attcode" => array("userlogin", "profile"), "state_attcode" => "", "reconc_keys" => array(), "db_table" => "priv_urp_userprofile", "db_key_field" => "id", "db_finalclass_field" => "", - "display_template" => "", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); @@ -263,24 +246,56 @@ class URP_UserProfile extends UserRightsBaseClassGUI MetaModel::Init_SetZListItems('advanced_search', array('userid', 'profileid')); // Criteria of the advanced search form } - public function GetName() - { - return Dict::Format('UI:UserManagement:LinkBetween_User_And_Profile', $this->Get('userlogin'), $this->Get('profile')); - } - public function CheckToDelete(&$oDeletionPlan) { - if (MetaModel::GetConfig()->Get('demo_mode')) - { + if (MetaModel::GetConfig()->Get('demo_mode')) { // Users deletion is NOT allowed in demo mode $oDeletionPlan->AddToDelete($this, null); $oDeletionPlan->SetDeletionIssues($this, array('deletion not allowed in demo mode.'), true); $oDeletionPlan->ComputeResults(); + return false; } + try { + $this->CheckIfProfileIsAllowed(UR_ACTION_DELETE); + } + catch (SecurityException $e) { + // Users deletion is NOT allowed + $oDeletionPlan->AddToDelete($this, null); + $oDeletionPlan->SetDeletionIssues($this, [$e->getMessage()], true); + $oDeletionPlan->ComputeResults(); + + return false; + } + return parent::CheckToDelete($oDeletionPlan); } + public function DoCheckToDelete(&$oDeletionPlan) + { + if (MetaModel::GetConfig()->Get('demo_mode')) { + // Users deletion is NOT allowed in demo mode + $oDeletionPlan->AddToDelete($this, null); + $oDeletionPlan->SetDeletionIssues($this, array('deletion not allowed in demo mode.'), true); + $oDeletionPlan->ComputeResults(); + + return false; + } + try { + $this->CheckIfProfileIsAllowed(UR_ACTION_DELETE); + } + catch (SecurityException $e) { + // Users deletion is NOT allowed + $oDeletionPlan->AddToDelete($this, null); + $oDeletionPlan->SetDeletionIssues($this, [$e->getMessage()], true); + $oDeletionPlan->ComputeResults(); + + return false; + } + + return parent::DoCheckToDelete($oDeletionPlan); + } + protected function OnInsert() { $this->CheckIfProfileIsAllowed(UR_ACTION_CREATE); @@ -293,7 +308,6 @@ class URP_UserProfile extends UserRightsBaseClassGUI protected function OnDelete() { - $this->CheckIfProfileIsAllowed(UR_ACTION_DELETE); } /** @@ -343,13 +357,12 @@ class URP_UserOrg extends UserRightsBaseClassGUI ( "category" => "addon/userrights,grant_by_profile", "key_type" => "autoincrement", - "name_attcode" => "userid", + "name_attcode" => array("userlogin", "allowed_org_name"), "state_attcode" => "", "reconc_keys" => array(), "db_table" => "priv_urp_userorg", "db_key_field" => "id", "db_finalclass_field" => "", - "display_template" => "", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); @@ -369,12 +382,6 @@ class URP_UserOrg extends UserRightsBaseClassGUI MetaModel::Init_SetZListItems('advanced_search', array('userid', 'allowed_org_id')); // Criteria of the advanced search form } - public function GetName() - { - return Dict::Format('UI:UserManagement:LinkBetween_User_And_Org', $this->Get('userlogin'), $this->Get('allowed_org_name')); - } - - protected function OnInsert() { $this->CheckIfOrgIsAllowed(); @@ -434,7 +441,7 @@ class UserRightsProfile extends UserRightsAddOnAPI // Support drastic data model changes: no organization class (or not writable)! if (MetaModel::IsValidClass('Organization') && !MetaModel::IsAbstract('Organization')) { - $oOrg = new Organization(); + $oOrg = MetaModel::NewObject('Organization'); $oOrg->Set('name', 'My Company/Department'); $oOrg->Set('code', 'SOMECODE'); $iOrgId = $oOrg->DBInsertNoReload(); @@ -442,17 +449,13 @@ class UserRightsProfile extends UserRightsAddOnAPI // Support drastic data model changes: no Person class (or not writable)! if (MetaModel::IsValidClass('Person') && !MetaModel::IsAbstract('Person')) { - $oContact = new Person(); + $oContact = MetaModel::NewObject('Person'); $oContact->Set('name', 'My last name'); $oContact->Set('first_name', 'My first name'); if (MetaModel::IsValidAttCode('Person', 'org_id')) { $oContact->Set('org_id', $iOrgId); } - if (MetaModel::IsValidAttCode('Person', 'phone')) - { - $oContact->Set('phone', '+00 000 000 000'); - } $oContact->Set('email', 'my.email@foo.org'); $iContactId = $oContact->DBInsertNoReload(); } @@ -561,7 +564,7 @@ class UserRightsProfile extends UserRightsAddOnAPI /** * @param $oUser User - * @return array + * @return bool */ public function IsAdministrator($oUser) { @@ -571,16 +574,22 @@ class UserRightsProfile extends UserRightsAddOnAPI /** * @param $oUser User - * @return array + * @return bool */ public function IsPortalUser($oUser) { // UserRights caches the list for us return UserRights::HasProfile(PORTAL_PROFILE_NAME, $oUser); } + /** * @param $oUser User - * @return bool + * + * @return array + * @throws \ArchivedObjectException + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \MySQLException */ public function ListProfiles($oUser) { @@ -601,30 +610,115 @@ class UserRightsProfile extends UserRightsAddOnAPI { $this->LoadCache(); - $aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, UR_ACTION_READ); - if ($aObjectPermissions['permission'] == UR_ALLOWED_NO) + // Let us pass an administrator for bypassing the grant matrix check in order to test this method without the need to set up a complex profile + // In the nominal case Administrators never end up here (since they completely bypass GetSelectFilter) + if (!static::IsAdministrator($oUser) && (MetaModel::HasCategory($sClass, 'silo') || MetaModel::HasCategory($sClass, 'bizmodel'))) { - return false; + // N°4354 - Categories 'silo' and 'bizmodel' do check the grant matrix. Whereas 'filter' always allows to read (but the result can be filtered) + $aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, UR_ACTION_READ); + if ($aObjectPermissions['permission'] == UR_ALLOWED_NO) + { + return false; + } } - // Determine how to position the objects of this class - // + $oFilter = true; + $aConditions = array(); + + // Determine if this class is part of a silo and build the filter for it $sAttCode = self::GetOwnerOrganizationAttCode($sClass); - if (is_null($sAttCode)) + if (!is_null($sAttCode)) { - // No filtering for this object - return true; + $aUserOrgs = $this->GetUserOrgs($oUser, $sClass); + if (count($aUserOrgs) > 0) + { + $oFilter = $this->MakeSelectFilter($sClass, $aUserOrgs, $aSettings, $sAttCode); + } + // else: No org means 'any org' } - // Position the user - // - $aUserOrgs = $this->GetUserOrgs($oUser, $sClass); - if (count($aUserOrgs) == 0) + // else: No silo for this class + + // Specific conditions to hide, for non-administrators, the Administrator Users, the Administrator Profile and related links + // Note: when logged as an administrator, GetSelectFilter is completely bypassed. + if ($this->AdministratorsAreHidden()) { - // No org means 'any org' - return true; + if ($sClass == 'URP_Profiles') + { + $oExpression = new FieldExpression('id', $sClass); + $oScalarExpr = new ScalarExpression(1); + + $aConditions[] = new BinaryExpression($oExpression, '!=', $oScalarExpr); + } + else if (($sClass == 'URP_UserProfile') || ($sClass == 'User') || (is_subclass_of($sClass, 'User'))) + { + $aAdministrators = $this->GetAdministrators(); + if (count($aAdministrators) > 0) + { + $sAttCode = ($sClass == 'URP_UserProfile') ? 'userid' : 'id'; + $oExpression = new FieldExpression($sAttCode, $sClass); + $oListExpr = ListExpression::FromScalars($aAdministrators); + $aConditions[] = new BinaryExpression($oExpression, 'NOT IN', $oListExpr); + } + } } - return $this->MakeSelectFilter($sClass, $aUserOrgs, $aSettings, $sAttCode); + // Handling of the added conditions + if (count($aConditions) > 0) + { + if($oFilter === true) + { + // No 'silo' filter, let's build a clean one + $oFilter = new DBObjectSearch($sClass); + } + + // Add the conditions to the filter + foreach($aConditions as $oCondition) + { + $oFilter->AddConditionExpression($oCondition); + } + } + + return $oFilter; + } + + /** + * Retrieve (and memoize) the list of administrator accounts. + * Note that there should always be at least one administrator account + * @return number[] + */ + private function GetAdministrators() + { + static $aAdministrators = null; + + if ($aAdministrators === null) + { + // Find all administrators + $aAdministrators = array(); + $oAdministratorsFilter = new DBObjectSearch('User'); + $oLnkFilter = new DBObjectSearch('URP_UserProfile'); + $oExpression = new FieldExpression('profileid', 'URP_UserProfile'); + $oScalarExpr = new ScalarExpression(1); + $oCondition = new BinaryExpression($oExpression, '=', $oScalarExpr); + $oLnkFilter->AddConditionExpression($oCondition); + $oAdministratorsFilter->AddCondition_ReferencedBy($oLnkFilter, 'userid'); + $oAdministratorsFilter->AllowAllData(true); // Mandatory to prevent infinite recursion !! + $oSet = new DBObjectSet($oAdministratorsFilter); + $oSet->OptimizeColumnLoad(array('User' => array('login'))); + while($oUser = $oSet->Fetch()) + { + $aAdministrators[] = $oUser->GetKey(); + } + } + return $aAdministrators; + } + + /** + * Whether or not to hide the 'Administrator' profile and the administrator accounts + * @return boolean + */ + private function AdministratorsAreHidden() + { + return ((bool)MetaModel::GetConfig()->Get('security.hide_administrators')); } diff --git a/addons/userrights/userrightsprofile.db.class.inc.php b/addons/userrights/userrightsprofile.db.class.inc.php index 2115f1b8d..ac5b2277b 100644 --- a/addons/userrights/userrightsprofile.db.class.inc.php +++ b/addons/userrights/userrightsprofile.db.class.inc.php @@ -1,6 +1,6 @@ "priv_urp_profiles", "db_key_field" => "id", "db_finalclass_field" => "", - "display_template" => "", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); @@ -312,11 +311,9 @@ class URP_Profiles extends UserRightsBaseClassGUI function DisplayBareRelations(WebPage $oPage, $bEditMode = false) { parent::DisplayBareRelations($oPage, $bEditMode); - if (!$bEditMode) - { - $oPage->SetCurrentTab('UI:UserManagement:GrantMatrix'); - $this->DoShowGrantSumary($oPage); - } + + $oPage->SetCurrentTab('UI:UserManagement:GrantMatrix'); + $this->DoShowGrantSumary($oPage); } } @@ -330,13 +327,12 @@ class URP_UserProfile extends UserRightsBaseClassGUI ( "category" => "addon/userrights", "key_type" => "autoincrement", - "name_attcode" => "userid", + "name_attcode" => array("userlogin", "profile"), "state_attcode" => "", "reconc_keys" => array(), "db_table" => "priv_urp_userprofile", "db_key_field" => "id", "db_finalclass_field" => "", - "display_template" => "", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); @@ -355,11 +351,6 @@ class URP_UserProfile extends UserRightsBaseClassGUI MetaModel::Init_SetZListItems('standard_search', array('userid', 'profileid')); // Criteria of the std search form MetaModel::Init_SetZListItems('advanced_search', array('userid', 'profileid')); // Criteria of the advanced search form } - - public function GetName() - { - return Dict::Format('UI:UserManagement:LinkBetween_User_And_Profile', $this->Get('userlogin'), $this->Get('profile')); - } } class URP_UserOrg extends UserRightsBaseClassGUI @@ -370,13 +361,12 @@ class URP_UserOrg extends UserRightsBaseClassGUI ( "category" => "addon/userrights", "key_type" => "autoincrement", - "name_attcode" => "userid", + "name_attcode" => array("userlogin", "allowed_org_name"), "state_attcode" => "", "reconc_keys" => array(), "db_table" => "priv_urp_userorg", "db_key_field" => "id", "db_finalclass_field" => "", - "display_template" => "", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); @@ -395,11 +385,6 @@ class URP_UserOrg extends UserRightsBaseClassGUI MetaModel::Init_SetZListItems('standard_search', array('userid', 'allowed_org_id')); // Criteria of the std search form MetaModel::Init_SetZListItems('advanced_search', array('userid', 'allowed_org_id')); // Criteria of the advanced search form } - - public function GetName() - { - return Dict::Format('UI:UserManagement:LinkBetween_User_And_Org', $this->Get('userlogin'), $this->Get('allowed_org_name')); - } } @@ -417,7 +402,6 @@ class URP_ActionGrant extends UserRightsBaseClass "db_table" => "priv_urp_grant_actions", "db_key_field" => "id", "db_finalclass_field" => "", - "display_template" => "", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); @@ -454,7 +438,6 @@ class URP_StimulusGrant extends UserRightsBaseClass "db_table" => "priv_urp_grant_stimulus", "db_key_field" => "id", "db_finalclass_field" => "", - "display_template" => "", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); @@ -491,7 +474,6 @@ class URP_AttributeGrant extends UserRightsBaseClass "db_table" => "priv_urp_grant_attributes", "db_key_field" => "id", "db_finalclass_field" => "", - "display_template" => "", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); @@ -535,7 +517,7 @@ class UserRightsProfile extends UserRightsAddOnAPI // Support drastic data model changes: no organization class (or not writable)! if (MetaModel::IsValidClass('Organization') && !MetaModel::IsAbstract('Organization')) { - $oOrg = new Organization(); + $oOrg = MetaModel::NewObject('Organization'); $oOrg->Set('name', 'My Company/Department'); $oOrg->Set('code', 'SOMECODE'); $oOrg::SetCurrentChange($oChange); @@ -544,17 +526,13 @@ class UserRightsProfile extends UserRightsAddOnAPI // Support drastic data model changes: no Person class (or not writable)! if (MetaModel::IsValidClass('Person') && !MetaModel::IsAbstract('Person')) { - $oContact = new Person(); + $oContact = MetaModel::NewObject('Person'); $oContact->Set('name', 'My last name'); $oContact->Set('first_name', 'My first name'); if (MetaModel::IsValidAttCode('Person', 'org_id')) { $oContact->Set('org_id', $iOrgId); } - if (MetaModel::IsValidAttCode('Person', 'phone')) - { - $oContact->Set('phone', '+00 000 000 000'); - } $oContact->Set('email', 'my.email@foo.org'); $oContact::SetCurrentChange($oChange); $iContactId = $oContact->DBInsertNoReload(); @@ -711,7 +689,7 @@ class UserRightsProfile extends UserRightsAddOnAPI public function LoadCache() { - if (!is_null($this->m_aProfiles)) return; + if (!is_null($this->m_aProfiles)) return false; // Could be loaded in a shared memory (?) $oKPI = new ExecutionKPI(); diff --git a/addons/userrights/userrightsprojection.class.inc.php b/addons/userrights/userrightsprojection.class.inc.php index 081149398..61fb07fe6 100644 --- a/addons/userrights/userrightsprojection.class.inc.php +++ b/addons/userrights/userrightsprojection.class.inc.php @@ -1,6 +1,6 @@ "priv_urp_profiles", "db_key_field" => "id", "db_finalclass_field" => "", - "display_template" => "", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); @@ -144,11 +143,9 @@ class URP_Profiles extends UserRightsBaseClass function DisplayBareRelations(WebPage $oPage, $bEditMode = false) { parent::DisplayBareRelations($oPage, $bEditMode); - if (!$bEditMode) - { - $oPage->SetCurrentTab('UI:UserManagement:GrantMatrix'); - $this->DoShowGrantSumary($oPage); - } + + $oPage->SetCurrentTab('UI:UserManagement:GrantMatrix'); + $this->DoShowGrantSumary($oPage); } } @@ -167,7 +164,6 @@ class URP_Dimensions extends UserRightsBaseClass "db_table" => "priv_urp_dimensions", "db_key_field" => "id", "db_finalclass_field" => "", - "display_template" => "", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); @@ -274,13 +270,12 @@ class URP_UserProfile extends UserRightsBaseClass ( "category" => "addon/userrights", "key_type" => "autoincrement", - "name_attcode" => "userid", + "name_attcode" => array("userlogin", "profile"), "state_attcode" => "", "reconc_keys" => array(), "db_table" => "priv_urp_userprofile", "db_key_field" => "id", "db_finalclass_field" => "", - "display_template" => "", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); @@ -299,11 +294,6 @@ class URP_UserProfile extends UserRightsBaseClass MetaModel::Init_SetZListItems('standard_search', array('userid', 'profileid')); // Criteria of the std search form MetaModel::Init_SetZListItems('advanced_search', array('userid', 'profileid')); // Criteria of the advanced search form } - - public function GetName() - { - return Dict::Format('UI:UserManagement:LinkBetween_User_And_Profile', $this->Get('userlogin'), $this->Get('profile')); - } } @@ -321,7 +311,6 @@ class URP_ProfileProjection extends UserRightsBaseClass "db_table" => "priv_urp_profileprojection", "db_key_field" => "id", "db_finalclass_field" => "", - "display_template" => "", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); @@ -402,7 +391,6 @@ class URP_ClassProjection extends UserRightsBaseClass "db_table" => "priv_urp_classprojection", "db_key_field" => "id", "db_finalclass_field" => "", - "display_template" => "", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); @@ -474,7 +462,6 @@ class URP_ActionGrant extends UserRightsBaseClass "db_table" => "priv_urp_grant_actions", "db_key_field" => "id", "db_finalclass_field" => "", - "display_template" => "", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); @@ -511,7 +498,6 @@ class URP_StimulusGrant extends UserRightsBaseClass "db_table" => "priv_urp_grant_stimulus", "db_key_field" => "id", "db_finalclass_field" => "", - "display_template" => "", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); @@ -548,7 +534,6 @@ class URP_AttributeGrant extends UserRightsBaseClass "db_table" => "priv_urp_grant_attributes", "db_key_field" => "id", "db_finalclass_field" => "", - "display_template" => "", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); diff --git a/application/DBSearchHelper.php b/application/DBSearchHelper.php new file mode 100644 index 000000000..1266d1976 --- /dev/null +++ b/application/DBSearchHelper.php @@ -0,0 +1,60 @@ +GetClass(); + foreach ($oAppContext->GetNames() as $key) { + // Find the value of the object corresponding to each 'context' parameter + $aCallSpec = [$sClass, 'MapContextParam']; + $sAttCode = ''; + if (is_callable($aCallSpec)) { + $sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter + } + + if (MetaModel::IsValidAttCode($sClass, $sAttCode)) { + // Add Hierarchical condition if hierarchical key + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if (isset($oAttDef) && ($oAttDef->IsExternalKey())) { + $iDefaultValue = intval($oAppContext->GetCurrentValue($key)); + if ($iDefaultValue != 0) { + try { + /** @var AttributeExternalKey $oAttDef */ + $sTargetClass = $oAttDef->GetTargetClass(); + $sHierarchicalKeyCode = MetaModel::IsHierarchicalClass($sTargetClass); + if ($sHierarchicalKeyCode !== false) { + $oFilter = new DBObjectSearch($sTargetClass); + $oFilter->AddCondition('id', $iDefaultValue); + $oHKFilter = new DBObjectSearch($sTargetClass); + $oHKFilter->AddCondition_PointingTo($oFilter, $sHierarchicalKeyCode, TREE_OPERATOR_BELOW); + $oSearch->AddCondition_PointingTo($oHKFilter, $sAttCode); + } + } + catch (Exception $e) { + // If filtering fails just ignore it + } + } + } + } + } + } +} \ No newline at end of file diff --git a/application/Html2Text.php b/application/Html2Text.php index 9cbe27ce1..6b3c898a6 100644 --- a/application/Html2Text.php +++ b/application/Html2Text.php @@ -30,8 +30,11 @@ function mb_str_replace($search, $replace, $subject, &$count = 0) { $replacements = array_pad($replacements, count($searches), ''); foreach ($searches as $key => $search) { $parts = mb_split(preg_quote($search), $subject); - $count += count($parts) - 1; - $subject = implode($replacements[$key], $parts); + if (is_array($parts)) + { + $count += count($parts) - 1; + $subject = implode($replacements[$key], $parts); + } } } else { // Call mb_str_replace for each subject in array, recursively diff --git a/application/ajaxwebpage.class.inc.php b/application/ajaxwebpage.class.inc.php index 82ddaf0d1..7f06e4fde 100644 --- a/application/ajaxwebpage.class.inc.php +++ b/application/ajaxwebpage.class.inc.php @@ -1,414 +1,20 @@ m_sReadyScript = ""; - //$this->add_header("Content-type: text/html; charset=utf-8"); - $this->add_header("Cache-control: no-cache"); - $this->m_oTabs = new TabManager(); - $this->sContentType = 'text/html'; - $this->sContentDisposition = 'inline'; - $this->m_sMenu = ""; - - utils::InitArchiveMode(); - } - - /** - * @inheritDoc - * @throws \Exception - */ - public function AddTabContainer($sTabContainer, $sPrefix = '') - { - $this->add($this->m_oTabs->AddTabContainer($sTabContainer, $sPrefix)); - } - - /** - * @inheritDoc - * @throws \Exception - */ - public function AddToTab($sTabContainer, $sTabCode, $sHtml) - { - $this->add($this->m_oTabs->AddToTab($sTabContainer, $sTabCode, $sHtml)); - } - - /** - * @inheritDoc - */ - public function SetCurrentTabContainer($sTabContainer = '') - { - return $this->m_oTabs->SetCurrentTabContainer($sTabContainer); - } - - /** - * @inheritDoc - */ - public function SetCurrentTab($sTabCode = '', $sTabTitle = null) - { - return $this->m_oTabs->SetCurrentTab($sTabCode, $sTabTitle); - } - - /** - * @inheritDoc - * @throws \Exception - */ - public function AddAjaxTab($sTabCode, $sUrl, $bCache = true, $sTabTitle = null) - { - $this->add($this->m_oTabs->AddAjaxTab($sTabCode, $sUrl, $bCache, $sTabTitle)); - } - - /** - * @inheritDoc - */ - public function GetCurrentTab() - { - return $this->m_oTabs->GetCurrentTab(); - } - - /** - * @inheritDoc - */ - public function RemoveTab($sTabCode, $sTabContainer = null) - { - $this->m_oTabs->RemoveTab($sTabCode, $sTabContainer); - } - - /** - * @inheritDoc - */ - public function FindTab($sPattern, $sTabContainer = null) - { - return $this->m_oTabs->FindTab($sPattern, $sTabContainer); - } - - /** - * Make the given tab the active one, as if it were clicked - * DOES NOT WORK: apparently in the *old* version of jquery - * that we are using this is not supported... TO DO upgrade - * the whole jquery bundle... - */ - public function SelectTab($sTabContainer, $sTabCode) - { - $this->add_ready_script($this->m_oTabs->SelectTab($sTabContainer, $sTabCode)); - } - - /** - * @param string $sHtml - */ - public function AddToMenu($sHtml) - { - $this->m_sMenu .= $sHtml; - } - - /** - * @inheritDoc - */ - public function output() - { - if (!empty($this->sContentType)) - { - $this->add_header('Content-type: '.$this->sContentType); - } - if (!empty($this->sContentDisposition)) - { - $this->add_header('Content-Disposition: '.$this->sContentDisposition.'; filename="'.$this->sContentFileName.'"'); - } - foreach($this->a_headers as $s_header) - { - header($s_header); - } - if ($this->m_oTabs->TabsContainerCount() > 0) - { - $this->add_ready_script( -<< tag in the page - // is taken into account and causes "local" tabs to be considered as Ajax - // unless their URL is equal to the URL of the page... - if ($('base').length > 0) - { - $('div[id^=tabbedContent] > ul > li > a').each(function() { - var sHash = location.hash; - var sCleanLocation = location.href.toString().replace(sHash, '').replace(/#$/, ''); - $(this).attr("href", sCleanLocation+$(this).attr("href")); - }); - } - if ($.bbq) - { - // This selector will be reused when selecting actual tab widget A elements. - var tab_a_selector = 'ul.ui-tabs-nav a'; - - // Enable tabs on all tab widgets. The `event` property must be overridden so - // that the tabs aren't changed on click, and any custom event name can be - // specified. Note that if you define a callback for the 'select' event, it - // will be executed for the selected tab whenever the hash changes. - tabs.tabs({ event: 'change' }); - - // Define our own click handler for the tabs, overriding the default. - tabs.find( tab_a_selector ).click(function() - { - var state = {}; - - // Get the id of this tab widget. - var id = $(this).closest( 'div[id^=tabbedContent]' ).attr( 'id' ); - - // Get the index of this tab. - var idx = $(this).parent().prevAll().length; - - // Set the state! - state[ id ] = idx; - $.bbq.pushState( state ); - }); - } - else - { - tabs.tabs(); - } -EOF -); - } - // Render the tabs in the page (if any) - $this->s_content = $this->m_oTabs->RenderIntoContent($this->s_content, $this); - - // Additional UI widgets to be activated inside the ajax fragment - // Important: Testing the content type is not enough because some ajax handlers have not correctly positionned the flag (e.g json response corrupted by the script) - if (($this->sContentType == 'text/html') && (preg_match('/class="date-pick"/', $this->s_content) || preg_match('/class="datetime-pick"/', $this->s_content)) ) - { - $this->add_ready_script( -<<outputCollapsibleSectionInit(); - - $oKPI = new ExecutionKPI(); - $s_captured_output = $this->ob_get_clean_safe(); - if (($this->sContentType == 'text/html') && ($this->sContentDisposition == 'inline')) - { - // inline content != attachment && html => filter all scripts for malicious XSS scripts - echo self::FilterXSS($this->s_content); - } - else - { - echo $this->s_content; - } - if (!empty($this->m_sMenu)) - { - $uid = time(); - echo "
\n"; - echo "
\n"; - echo "\n"; - echo self::FilterXSS($this->m_sMenu); - echo "\n"; - echo "
\n"; - echo "
\n"; - - echo "\n"; - } - - //echo $this->s_deferred_content; - if (count($this->a_scripts) > 0) - { - echo "\n"; - } - if (count($this->a_linked_scripts) > 0) - { - echo "\n"; - } - if (!empty($this->s_deferred_content)) - { - echo "\n"; - } - if (!empty($this->m_sReadyScript)) - { - echo "\n"; - } - if(count($this->a_linked_stylesheets) > 0) - { - echo "\n"; - } - - if (trim($s_captured_output) != "") - { - echo self::FilterXSS($s_captured_output); - } - - $oKPI->ComputeAndReport('Echoing'); - - if (class_exists('DBSearch')) - { - DBSearch::RecordQueryTrace(); - } - if (class_exists('ExecutionKPI')) - { - ExecutionKPI::ReportStats(); - } - } - - /** - * Adds a paragraph with a smaller font into the page - * NOT implemented (i.e does nothing) - * @param string $sText Content of the (small) paragraph - * @return void - */ - public function small_p($sText) - { - } - - /** - * @inheritDoc - * @throws \Exception - */ - public function add($sHtml) - { - if (($this->m_oTabs->GetCurrentTabContainer() != '') && ($this->m_oTabs->GetCurrentTab() != '')) - { - $this->m_oTabs->AddToTab($this->m_oTabs->GetCurrentTabContainer(), $this->m_oTabs->GetCurrentTab(), $sHtml); - } - else - { - parent::add($sHtml); - } - } - - /** - * @inheritDoc - */ - public function start_capture() - { - $sCurrentTabContainer = $this->m_oTabs->GetCurrentTabContainer(); - $sCurrentTab = $this->m_oTabs->GetCurrentTab(); - - if (!empty($sCurrentTabContainer) && !empty($sCurrentTab)) - { - $iOffset = $this->m_oTabs->GetCurrentTabLength(); - return array('tc' => $sCurrentTabContainer, 'tab' => $sCurrentTab, 'offset' => $iOffset); - } - else - { - return parent::start_capture(); - } - } - - /** - * @inheritDoc - */ - public function end_capture($offset) - { - if (is_array($offset)) - { - if ($this->m_oTabs->TabExists($offset['tc'], $offset['tab'])) - { - $sCaptured = $this->m_oTabs->TruncateTab($offset['tc'], $offset['tab'], $offset['offset']); - } - else - { - $sCaptured = ''; - } - } - else - { - $sCaptured = parent::end_capture($offset); - } - return $sCaptured; - } - - /** - * @inheritDoc - */ - public function add_at_the_end($s_html, $sId = '') - { - if ($sId != '') - { - $this->add_script("$('#{$sId}').remove();"); // Remove any previous instance of the same Id - } - $this->s_deferred_content .= $s_html; - } - - /** - * @inheritDoc - */ - public function add_ready_script($sScript) - { - $this->m_sReadyScript .= $sScript."\n"; - } - - /** - * @inheritDoc - */ - public function GetUniqueId() - { - assert(false); - return 0; - } - - /** - * @inheritDoc - */ - public static function FilterXSS($sHTML) - { - return str_ireplace(array(''), array(''), $sHTML); - } } diff --git a/application/application.inc.php b/application/application.inc.php index a41b19712..b74b99820 100644 --- a/application/application.inc.php +++ b/application/application.inc.php @@ -1,27 +1,11 @@ - +/* + * @copyright Copyright (C) 2010-2021 Combodo SARL + * @license http://opensource.org/licenses/AGPL-3.0 + */ /** * Includes all the classes to have the application up and running - * - * @copyright Copyright (C) 2010-2012 Combodo SARL - * @license http://opensource.org/licenses/AGPL-3.0 */ require_once(APPROOT.'/application/applicationcontext.class.inc.php'); @@ -31,10 +15,4 @@ require_once(APPROOT.'/application/audit.category.class.inc.php'); require_once(APPROOT.'/application/audit.rule.class.inc.php'); require_once(APPROOT.'/application/query.class.inc.php'); require_once(APPROOT.'/setup/moduleinstallation.class.inc.php'); -//require_once(APPROOT.'/application/menunode.class.inc.php'); require_once(APPROOT.'/application/utils.inc.php'); - -class ApplicationException extends CoreException -{ -} -?> diff --git a/application/applicationcontext.class.inc.php b/application/applicationcontext.class.inc.php index 9ae18666b..1361f92d3 100644 --- a/application/applicationcontext.class.inc.php +++ b/application/applicationcontext.class.inc.php @@ -1,5 +1,5 @@ aValues); + } + /** * Returns the context as sequence of input tags to be inserted inside a
tag + * * @return string The context as a sequence of tags */ public function GetForForm() { $sContext = ""; - foreach($this->aValues as $sName => $sValue) - { + foreach ($this->aValues as $sName => $sValue) { $sContext .= "\n"; } return $sContext; } + /** + * Returns the context an array of input blocks + * + * @return array The context as a sequence of tags + * @since 3.0.0 + */ + public function GetForUIForm() + { + $aContextInputBlocks = []; + foreach ($this->aValues as $sName => $sValue) { + $aContextInputBlocks[] = InputUIBlockFactory::MakeForHidden("c[$sName]", htmlentities($sValue, ENT_QUOTES, 'UTF-8')); + } + return $aContextInputBlocks; + } + + /** + * Returns the context as sequence of input tags to be inserted inside a tag + * + */ + public function GetForFormBlock(): UIBlock + { + $oContext = new UIContentBlock(); + foreach ($this->aValues as $sName => $sValue) { + $oContext->AddSubBlock(InputUIBlockFactory::MakeForHidden('c[$sName]', utils::HtmlEntities($sValue))); + } + return $oContext; + } /** * Returns the context as a hash array 'parameter_name' => value + * * @return array The context information */ public function GetAsHash() @@ -294,7 +336,7 @@ class ApplicationContext $sPrevious = self::GetUrlMakerClass(); self::$m_sUrlMakerClass = $sClass; - $_SESSION['UrlMakerClass'] = $sClass; + Session::Set('UrlMakerClass', $sClass); return $sPrevious; } @@ -307,9 +349,9 @@ class ApplicationContext { if (is_null(self::$m_sUrlMakerClass)) { - if (isset($_SESSION['UrlMakerClass'])) + if (Session::IsSet('UrlMakerClass')) { - self::$m_sUrlMakerClass = $_SESSION['UrlMakerClass']; + self::$m_sUrlMakerClass = Session::Get('UrlMakerClass'); } else { @@ -362,9 +404,9 @@ class ApplicationContext */ protected static function LoadPluginProperties() { - if (isset($_SESSION['PluginProperties'])) + if (Session::IsSet('PluginProperties')) { - self::$m_aPluginProperties = $_SESSION['PluginProperties']; + self::$m_aPluginProperties = Session::Get('PluginProperties'); } else { @@ -384,7 +426,7 @@ class ApplicationContext if (is_null(self::$m_aPluginProperties)) self::LoadPluginProperties(); self::$m_aPluginProperties[$sPluginClass][$sProperty] = $value; - $_SESSION['PluginProperties'][$sPluginClass][$sProperty] = $value; + Session::Set(['PluginProperties', $sPluginClass, $sProperty], $value); } /** diff --git a/application/applicationextension.inc.php b/application/applicationextension.inc.php index 81a6d9c66..7bc8d7184 100644 --- a/application/applicationextension.inc.php +++ b/application/applicationextension.inc.php @@ -1,7 +1,7 @@ OnStart($iErrorCode); @@ -431,7 +431,7 @@ interface iApplicationUIExtension * * @param DBObjectSet $oSet A set of persistent objects (DBObject) * - * @return string[string] + * @return array */ public function EnumAllowedActions(DBObjectSet $oSet); } @@ -778,6 +778,10 @@ abstract class ApplicationPopupMenuItem /** @ignore */ protected $sLabel; /** @ignore */ + protected $sTooltip; + /** @ignore */ + protected $sIconClass; + /** @ignore */ protected $aCssClasses; /** @@ -792,6 +796,8 @@ abstract class ApplicationPopupMenuItem { $this->sUID = $sUID; $this->sLabel = $sLabel; + $this->sTooltip = ''; + $this->sIconClass = ''; $this->aCssClasses = array(); } @@ -846,6 +852,47 @@ abstract class ApplicationPopupMenuItem $this->aCssClasses[] = $sCssClass; } + + /** + * @param $sTooltip + * + * @since 3.0.0 + */ + public function SetTooltip($sTooltip) + { + $this->sTooltip = $sTooltip; + } + + /** + * @return string + * + * @since 3.0.0 + */ + public function GetTooltip() + { + return $this->sTooltip; + } + + /** + * @param $sIconClass + * + * @since 3.0.0 + */ + public function SetIconClass($sIconClass) + { + $this->sIconClass = $sIconClass; + } + + /** + * @return string + * + * @since 3.0.0 + */ + public function GetIconClass() + { + return $this->sIconClass; + } + /** * Returns the components to create a popup menu item in HTML * @@ -864,6 +911,8 @@ abstract class ApplicationPopupMenuItem /** * Class for adding an item into a popup menu that browses to the given URL * + * Note: This works only in the backoffice, {@see \URLButtonItem} for the end-user portal + * * @api * @package Extensibility * @since 2.0 @@ -871,7 +920,7 @@ abstract class ApplicationPopupMenuItem class URLPopupMenuItem extends ApplicationPopupMenuItem { /** @ignore */ - protected $sURL; + protected $sUrl; /** @ignore */ protected $sTarget; @@ -880,26 +929,46 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem * * @param string $sUID The unique identifier of this menu in iTop... make sure you pass something unique enough * @param string $sLabel The display label of the menu (must be localized) - * @param string $sURL If the menu is an hyperlink, provide the absolute hyperlink here + * @param string $sUrl If the menu is an hyperlink, provide the absolute hyperlink here * @param string $sTarget In case the menu is an hyperlink and a specific target is needed (_blank for example), pass it here */ - public function __construct($sUID, $sLabel, $sURL, $sTarget = '_top') + public function __construct($sUID, $sLabel, $sUrl, $sTarget = '_top') { parent::__construct($sUID, $sLabel); - $this->sURL = $sURL; + $this->sUrl = $sUrl; $this->sTarget = $sTarget; } /** @ignore */ public function GetMenuItem() { - return array('label' => $this->GetLabel(), 'url' => $this->sURL, 'target' => $this->sTarget, 'css_classes' => $this->aCssClasses); + return array('label' => $this->GetLabel(), + 'url' => $this->GetUrl(), + 'target' => $this-> GetTarget(), + 'css_classes' => $this->aCssClasses, + 'icon_class' => $this->sIconClass, + 'tooltip' => $this->sTooltip + ); + } + + /** @ignore */ + public function GetUrl() + { + return $this->sUrl; + } + + /** @ignore */ + public function GetTarget() + { + return $this->sTarget; } } /** * Class for adding an item into a popup menu that triggers some Javascript code * + * Note: This works only in the backoffice, {@see \JSButtonItem} for the end-user portal + * * @api * @package Extensibility * @since 2.0 @@ -907,7 +976,9 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem class JSPopupMenuItem extends ApplicationPopupMenuItem { /** @ignore */ - protected $sJSCode; + protected $sJsCode; + /** @ignore */ + protected $sUrl; /** @ignore */ protected $aIncludeJSFiles; @@ -925,7 +996,8 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem public function __construct($sUID, $sLabel, $sJSCode, $aIncludeJSFiles = array()) { parent::__construct($sUID, $sLabel); - $this->sJSCode = $sJSCode; + $this->sJsCode = $sJSCode; + $this->sUrl = '#'; $this->aIncludeJSFiles = $aIncludeJSFiles; } @@ -935,9 +1007,11 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem // Note: the semicolumn is a must here! return array( 'label' => $this->GetLabel(), - 'onclick' => $this->sJSCode.'; return false;', - 'url' => '#', - 'css_classes' => $this->aCssClasses, + 'onclick' => $this->GetJsCode().'; return false;', + 'url' => $this->GetUrl(), + 'css_classes' => $this->GetCssClasses(), + 'icon_class' => $this->sIconClass, + 'tooltip' => $this->sTooltip ); } @@ -946,6 +1020,18 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem { return $this->aIncludeJSFiles; } + + /** @ignore */ + public function GetJsCode() + { + return $this->sJsCode; + } + + /** @ignore */ + public function GetUrl() + { + return $this->sUrl; + } } /** @@ -1017,11 +1103,14 @@ class JSButtonItem extends JSPopupMenuItem * @api * @package Extensibility * @since 2.0 + * @deprecated 3.0.0 If you need to include: + * * JS/CSS files/snippets, use {@see \iBackofficeLinkedScriptsExtension}, {@see \iBackofficeLinkedStylesheetsExtension}, etc instead + * * HTML (and optionally JS/CSS), use {@see \iPageUIBlockExtension} to manipulate {@see \Combodo\iTop\Application\UI\Base\UIBlock} instead */ interface iPageUIExtension { /** - * Add content to the North pane + * Add content to the header of the page * * @param iTopWebPage $oPage The page to insert stuff into. * @@ -1030,7 +1119,7 @@ interface iPageUIExtension public function GetNorthPaneHtml(iTopWebPage $oPage); /** - * Add content to the South pane + * Add content to the footer of the page * * @param iTopWebPage $oPage The page to insert stuff into. * @@ -1048,12 +1137,56 @@ interface iPageUIExtension public function GetBannerHtml(iTopWebPage $oPage); } +/** + * Implement this interface to add content to any iTopWebPage + * + * There are 3 places where content can be added: + * + * * The north pane: (normaly empty/hidden) at the top of the page, spanning the whole + * width of the page + * * The south pane: (normaly empty/hidden) at the bottom of the page, spanning the whole + * width of the page + * * The admin banner (two tones gray background) at the left of the global search. + * Limited space, use it for short messages + * + * Each of the methods of this interface is supposed to return the HTML to be inserted at + * the specified place and can use the passed iTopWebPage object to add javascript or CSS definitions + * + * @api + * @package Extensibility + * @since 3.0.0 + */ +interface iPageUIBlockExtension +{ + /** + * Add content to the "admin banner" + * + * @return iUIBlock|null The Block to add into the page + */ + public function GetBannerBlock(); + + /** + * Add content to the header of the page + * + * @return iUIBlock|null The Block to add into the page + */ + public function GetHeaderBlock(); + + /** + * Add content to the footer of the page + * + * @return iUIBlock|null The Block to add into the page + */ + public function GetFooterBlock(); +} + /** * Extend this class instead of iPageUIExtension if you don't need to overload all methods * * @api * @package Extensibility * @since 2.7.0 + * @deprecated since 3.0.0 use AbstractPageUIBlockExtension instead */ abstract class AbstractPageUIExtension implements iPageUIExtension { @@ -1062,6 +1195,8 @@ abstract class AbstractPageUIExtension implements iPageUIExtension */ public function GetNorthPaneHtml(iTopWebPage $oPage) { + DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use iPageUIBlockExtension instead'); + return ''; } @@ -1070,6 +1205,8 @@ abstract class AbstractPageUIExtension implements iPageUIExtension */ public function GetSouthPaneHtml(iTopWebPage $oPage) { + DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use iPageUIBlockExtension instead'); + return ''; } @@ -1078,11 +1215,192 @@ abstract class AbstractPageUIExtension implements iPageUIExtension */ public function GetBannerHtml(iTopWebPage $oPage) { + DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use iPageUIBlockExtension instead'); + return ''; } } +/** + * Extend this class instead of iPageUIExtension if you don't need to overload all methods + * + * @api + * @package Extensibility + * @since 3.0.0 + */ +abstract class AbstractPageUIBlockExtension implements iPageUIBlockExtension +{ + /** + * @inheritDoc + */ + public function GetBannerBlock() + { + return null; + } + + /** + * @inheritDoc + */ + public function GetHeaderBlock() + { + return null; + } + + /** + * @inheritDoc + */ + public function GetFooterBlock() + { + return null; + } +} + +/** + * Implement this interface to add script (JS) files to the backoffice pages + * + * @see \iTopWebPage::$a_linked_scripts + * @api + * @since 3.0.0 + */ +interface iBackofficeLinkedScriptsExtension +{ + /** + * @see \iTopWebPage::$a_linked_scripts Each script will be included using this property + * @return array An array of absolute URLs to the files to include + */ + public function GetLinkedScriptsAbsUrls(): array; +} + +/** + * Implement this interface to add inline script (JS) to the backoffice pages' head. + * Will be executed first, BEFORE the DOM interpretation. + * + * @see \iTopWebPage::$a_early_scripts + * @api + * @since 3.0.0 + */ +interface iBackofficeEarlyScriptExtension +{ + /** + * @see \iTopWebPage::$a_early_scripts + * @return string + */ + public function GetEarlyScript(): string; +} + +/** + * Implement this interface to add inline script (JS) to the backoffice pages that will be executed immediately, without waiting for the DOM to be ready. + * + * @see \iTopWebPage::$a_scripts + * @api + * @since 3.0.0 + */ +interface iBackofficeScriptExtension +{ + /** + * @see \iTopWebPage::$a_scripts + * @return string + */ + public function GetScript(): string; +} + +/** + * Implement this interface to add inline script (JS) to the backoffice pages that will be executed right when the DOM is ready. + * + * @see \iTopWebPage::$a_init_scripts + * @api + * @since 3.0.0 + */ +interface iBackofficeInitScriptExtension +{ + /** + * @see \iTopWebPage::$a_init_scripts + * @return string + */ + public function GetInitScript(): string; +} + +/** + * Implement this interface to add inline script (JS) to the backoffice pages that will be executed slightly AFTER the DOM is ready (just after the init. scripts). + * + * @see \iTopWebPage::$a_ready_scripts + * @api + * @since 3.0.0 + */ +interface iBackofficeReadyScriptExtension +{ + /** + * @see \iTopWebPage::$a_ready_scripts + * @return string + */ + public function GetReadyScript(): string; +} + +/** + * Implement this interface to add stylesheets (CSS) to the backoffice pages + * + * @see \iTopWebPage::$a_linked_stylesheets + * @api + * @since 3.0.0 + */ +interface iBackofficeLinkedStylesheetsExtension +{ + /** + * @see \iTopWebPage::$a_linked_stylesheets + * @return array An array of absolute URLs to the files to include + */ + public function GetLinkedStylesheetsAbsUrls(): array; +} + +/** + * Implement this interface to add inline style (CSS) to the backoffice pages' head. + * + * @see \iTopWebPage::$a_styles + * @api + * @since 3.0.0 + */ +interface iBackofficeStyleExtension +{ + /** + * @see \iTopWebPage::$a_styles + * @return string + */ + public function GetStyle(): string; +} + +/** + * Implement this interface to add Dict entries + * + * @see \iTopWebPage::$a_dict_entries + * @api + * @since 3.0.0 + */ +interface iBackofficeDictEntriesExtension +{ + /** + * @see \iTopWebPage::a_dict_entries + * @return array + */ + public function GetDictEntries(): array; +} + +/** + * Implement this interface to add Dict entries prefixes + * + * @see \iTopWebPage::$a_dict_entries_prefixes + * @api + * @since 3.0.0 + */ +interface iBackofficeDictEntriesPrefixesExtension +{ + /** + * @see \iTopWebPage::a_dict_entries_prefixes + * @return array + */ + public function GetDictEntriesPrefixes(): array; +} + /** * Implement this interface to add content to any enhanced portal page * diff --git a/application/audit.category.class.inc.php b/application/audit.category.class.inc.php index 14d80f0ec..06f7df30f 100644 --- a/application/audit.category.class.inc.php +++ b/application/audit.category.class.inc.php @@ -1,5 +1,5 @@ "priv_auditrule", "db_key_field" => "id", "db_finalclass_field" => "", - "display_template" => "", ); MetaModel::Init_Params($aParams); MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); diff --git a/application/capturewebpage.class.inc.php b/application/capturewebpage.class.inc.php index 287345167..d86e46a33 100644 --- a/application/capturewebpage.class.inc.php +++ b/application/capturewebpage.class.inc.php @@ -1,84 +1,8 @@ - /** - * Adapter class: when an API requires WebPage and you want to produce something else - * - * @copyright Copyright (C) 2016 Combodo SARL + * @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/CaptureWebPage.php, now loadable using autoloader * @license http://opensource.org/licenses/AGPL-3.0 + * @copyright Copyright (C) 2010-2021 Combodo SARL */ -require_once(APPROOT."/application/webpage.class.inc.php"); - -class CaptureWebPage extends WebPage -{ - protected $aReadyScripts; - - function __construct() - { - parent::__construct('capture web page'); - $this->aReadyScripts = array(); - } - - public function GetHtml() - { - $trash = $this->ob_get_clean_safe(); - return $this->s_content; - } - - public function GetJS() - { - $sRet = implode("\n", $this->a_scripts); - if (!empty($this->s_deferred_content)) - { - $sRet .= "\n\$('body').append('".addslashes(str_replace("\n", '', $this->s_deferred_content))."');"; - } - return $sRet; - } - - public function GetReadyJS() - { - return "\$(document).ready(function() {\n".implode("\n", $this->aReadyScripts)."\n});"; - } - - public function GetCSS() - { - return $this->a_styles; - } - - public function GetJSFiles() - { - return $this->a_linked_scripts; - } - - public function GetCSSFiles() - { - return $this->a_linked_stylesheets; - } - - public function output() - { - throw new Exception(__method__.' should not be called'); - } - - public function add_ready_script($sScript) - { - $this->aReadyScripts[] = $sScript; - } -} - +DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/application/WebPage/CaptureWebPage.php, now loadable using autoloader'); \ No newline at end of file diff --git a/application/clipage.class.inc.php b/application/clipage.class.inc.php index db348322f..db12ad181 100644 --- a/application/clipage.class.inc.php +++ b/application/clipage.class.inc.php @@ -1,99 +1,8 @@ - - /** - * CLI page - * The page adds the content-type text/XML and the encoding into the headers - * - * @copyright Copyright (C) 2010-2015 Combodo SARL + * @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/CLIPage.php, now loadable using autoloader * @license http://opensource.org/licenses/AGPL-3.0 + * @copyright Copyright (C) 2010-2021 Combodo SARL */ -require_once(APPROOT."/application/webpage.class.inc.php"); - -class CLIPage implements Page -{ - /** @var string */ - public $s_title; - - function __construct($s_title) - { - $this->s_title = $s_title; - } - - public function output() - { - if (class_exists('DBSearch')) - { - DBSearch::RecordQueryTrace(); - } - if (class_exists('ExecutionKPI')) - { - ExecutionKPI::ReportStats(); - } - } - - public function add($sText) - { - echo $sText; - } - - public function p($sText) - { - echo $sText."\n"; - } - - public function pre($sText) - { - echo $sText."\n"; - } - - public function add_comment($sText) - { - echo "#".$sText."\n"; - } - - public function table($aConfig, $aData, $aParams = array()) - { - $aCells = array(); - foreach($aConfig as $sName=>$aDef) - { - if (strlen($aDef['description']) > 0) - { - $aCells[] = $aDef['label'].' ('.$aDef['description'].')'; - } - else - { - $aCells[] = $aDef['label']; - } - } - echo implode(';', $aCells)."\n"; - - foreach($aData as $aRow) - { - $aCells = array(); - foreach($aConfig as $sName=>$aAttribs) - { - $sValue = $aRow["$sName"]; - $aCells[] = $sValue; - } - echo implode(';', $aCells)."\n"; - } - } -} +DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/application/WebPage/CLIPage.php, now loadable using autoloader'); \ No newline at end of file diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 9978ece10..2b7b2dc84 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -1,22 +1,47 @@ sDisplayMode = static::DEFAULT_DISPLAY_MODE; $this->bAllowWrite = false; $this->bAllowDelete = false; } + /** + * Return the allowed display modes + * + * @see static::ENUM_DISPLAY_MODE_XXX + * + * @return string[] + * @since 3.0.0 + */ + public static function EnumDisplayModes(): array + { + return [ + static::ENUM_DISPLAY_MODE_VIEW, + static::ENUM_DISPLAY_MODE_EDIT, + static::ENUM_DISPLAY_MODE_CREATE, + static::ENUM_DISPLAY_MODE_STIMULUS, + static::ENUM_DISPLAY_MODE_PRINT, + static::ENUM_DISPLAY_MODE_BULK_EDIT, + ]; + } + + /** + * @see static::$sDisplayMode + * @return string + * @since 3.0.0 + */ + public function GetDisplayMode(): string + { + return $this->sDisplayMode; + } + + /** + * @param string $sMode + * + * @see static::$sDisplayMode + * @return $this + * @since 3.0.0 + */ + public function SetDisplayMode(string $sMode) + { + $this->sDisplayMode = $sMode; + + return $this; + } + /** * returns what will be the next ID for the forms */ @@ -106,7 +253,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay /** * @param \WebPage $oPage - * @param \DBObject $oObj + * @param \cmdbAbstractObject $oObj * @param array $aParams * * @throws \Exception @@ -126,8 +273,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay $sParams .= $sName.'='.urlencode($value).'&'; // Always add a trailing & } $sUrl = utils::GetAbsoluteUrlAppRoot().'pages/'.$oObj->GetUIPage().'?'.$sParams.'class='.get_class($oObj).'&id='.$oObj->getKey().'&'.$oAppContext->GetForLink().'&a=1'; - $oPage->add_script( - <<add_early_script(<<Reload(); + $oObj->SetDisplayMode(static::ENUM_DISPLAY_MODE_VIEW); $oObj->DisplayDetails($oPage, false); } @@ -168,116 +315,99 @@ EOF * last plugin to set the message for a given message id) In practice, standard messages are recorded at the end * but they will not overwrite existing messages * + * @see SetSessionMessageFromInstance() to call from within an instance + * * @param string $sClass The class of the object (must be the final class) * @param int $iKey The identifier of the object * @param string $sMessageId Your id or one of the well-known ids: 'create', 'update' and 'apply_stimulus' * @param string $sMessage The HTML message (must be correctly escaped) - * @param string $sSeverity Any of the following: ok, info, error. + * @param string $sSeverity Any of the \WebPage::ENUM_SESSION_MESSAGE_SEVERITY_XXX constants * @param float $fRank Ordering of the message: smallest displayed first (can be negative) * @param bool $bMustNotExist Do not alter any existing message (considering the id) * - * @see SetSessionMessageFromInstance() to call from within an instance + * @return void */ - public static function SetSessionMessage( - $sClass, $iKey, $sMessageId, $sMessage, $sSeverity, $fRank, $bMustNotExist = false - ) { + public static function SetSessionMessage($sClass, $iKey, $sMessageId, $sMessage, $sSeverity, $fRank, $bMustNotExist = false) + { $sMessageKey = $sClass.'::'.$iKey; - if (!isset($_SESSION['obj_messages'][$sMessageKey])) - { - $_SESSION['obj_messages'][$sMessageKey] = array(); + if (!Session::IsSet(['obj_messages', $sMessageKey])) { + Session::Set(['obj_messages', $sMessageKey], []); } - if (!$bMustNotExist || !array_key_exists($sMessageId, $_SESSION['obj_messages'][$sMessageKey])) - { - $_SESSION['obj_messages'][$sMessageKey][$sMessageId] = array( + if (!$bMustNotExist || !Session::IsSet(['obj_messages', $sMessageKey, $sMessageId])) { + Session::Set(['obj_messages', $sMessageKey, $sMessageId], [ 'rank' => $fRank, 'severity' => $sSeverity, 'message' => $sMessage, - ); + ]); } } /** - * @param \WebPage $oPage - * @param bool $bEditMode + * Important: For compatibility reasons, this function still allows to manipulate the $oPage. In that case, markup will be put above the real header of the panel. + * To insert something IN the panel, we now need to add UIBlocks in either the "subtitle" or "toolbar" sections of the array that will be returned. * + * @param \WebPage $oPage + * @param bool $bEditMode Note that this parameter is no longer used in this method. Use {@see static::$sDisplayMode} instead + * + * @return array UIBlocks to be inserted in the "subtitle" and the "toolbar" sections of the ObjectDetails block. eg. ['subtitle' => [, ], 'toolbar' => []] + * + * @throws \ApplicationException * @throws \ArchivedObjectException * @throws \CoreException * @throws \CoreUnexpectedValue - * @throws \DictExceptionMissingString * @throws \MySQLException * @throws \OQLException - * @throws \Exception + * + * @since 3.0.0 $bEditMode is deprecated and no longer used */ public function DisplayBareHeader(WebPage $oPage, $bEditMode = false) { - // Standard Header with name, actions menu and history block - // + $aHeaderBlocks = [ + 'subtitle' => [], + 'toolbar' => [], + ]; - if (!$oPage->IsPrintableVersion()) - { + // Standard Header with name, actions menu and history block + + if (!$oPage->IsPrintableVersion()) { // Is there a message for this object ?? - $aMessages = array(); - $aRanks = array(); - if (MetaModel::GetConfig()->Get('concurrent_lock_enabled')) - { + $aMessages = []; + $aRanks = []; + if (MetaModel::GetConfig()->Get('concurrent_lock_enabled')) { $aLockInfo = iTopOwnershipLock::IsLocked(get_class($this), $this->GetKey()); - if ($aLockInfo['locked']) - { + if ($aLockInfo['locked']) { $aRanks[] = 0; $sName = $aLockInfo['owner']->GetName(); - if ($aLockInfo['owner']->Get('contactid') != 0) - { + if ($aLockInfo['owner']->Get('contactid') != 0) { $sName .= ' ('.$aLockInfo['owner']->Get('contactid_friendlyname').')'; } - $aResult['message'] = Dict::Format('UI:CurrentObjectIsLockedBy_User', $sName); - $aMessages[] = "
".Dict::Format('UI:CurrentObjectIsLockedBy_User', - $sName)."
"; + $aMessages[] = AlertUIBlockFactory::MakeForDanger('', Dict::Format('UI:CurrentObjectIsLockedBy_User', $sName)); } } $sMessageKey = get_class($this).'::'.$this->GetKey(); - if (array_key_exists('obj_messages', $_SESSION) && array_key_exists($sMessageKey, - $_SESSION['obj_messages'])) - { - foreach($_SESSION['obj_messages'][$sMessageKey] as $sMessageId => $aMessageData) - { - $sMsgClass = 'message_'.$aMessageData['severity']; - if(!in_array("
".$aMessageData['message']."
",$aMessages)) - { - $aMessages[] = "
".$aMessageData['message']."
"; - $aRanks[] = $aMessageData['rank']; - } - } - unset($_SESSION['obj_messages'][$sMessageKey]); - } - array_multisort($aRanks, $aMessages); - foreach($aMessages as $sMessage) - { - $oPage->add($sMessage); - } + $oPage->AddSessionMessages($sMessageKey, $aRanks, $aMessages); } - if (!$oPage->IsPrintableVersion()) - { + if (!$oPage->IsPrintableVersion() && ($this->GetDisplayMode() === static::ENUM_DISPLAY_MODE_VIEW)) { // action menu $oSingletonFilter = new DBObjectSearch(get_class($this)); $oSingletonFilter->AddCondition('id', $this->GetKey(), '='); $oBlock = new MenuBlock($oSingletonFilter, 'details', false); - $oBlock->Display($oPage, -1); + $oActionMenuBlock = $oBlock->GetRenderContent($oPage); + $aHeaderBlocks['toolbar'][$oActionMenuBlock->GetId()] = $oActionMenuBlock; } + $aTags = array(); + // Master data sources - $aIcons = array(); - if (!$oPage->IsPrintableVersion()) - { + if (!$oPage->IsPrintableVersion()) { $oCreatorTask = null; $bCanBeDeletedByTask = false; $bCanBeDeletedByUser = true; $aMasterSources = array(); $aSyncData = $this->GetSynchroData(); - if (count($aSyncData) > 0) - { - foreach($aSyncData as $iSourceId => $aSourceData) - { + if (count($aSyncData) > 0) { + foreach ($aSyncData as $iSourceId => $aSourceData) { $oDataSource = $aSourceData['source']; $oReplica = reset($aSourceData['replica']); // Take the first one! @@ -323,24 +453,24 @@ EOF $sTaskUrl = $aMasterSources[$oCreatorTask->GetKey()]['url']; if (!$bCanBeDeletedByUser) { - $sTip = "

".Dict::Format('Core:Synchro:TheObjectCannotBeDeletedByUser_Source', - $sTaskUrl)."

"; + $sTip = "
".Dict::Format('Core:Synchro:TheObjectCannotBeDeletedByUser_Source', + $sTaskUrl)."
"; } else { - $sTip = "

".Dict::Format('Core:Synchro:TheObjectWasCreatedBy_Source', $sTaskUrl)."

"; + $sTip = "
".Dict::Format('Core:Synchro:TheObjectWasCreatedBy_Source', $sTaskUrl)."
"; } if ($bCanBeDeletedByTask) { - $sTip .= "

".Dict::Format('Core:Synchro:TheObjectCanBeDeletedBy_Source', $sTaskUrl)."

"; + $sTip .= "
".Dict::Format('Core:Synchro:TheObjectCanBeDeletedBy_Source', $sTaskUrl)."
"; } } else { - $sTip = "

".Dict::S('Core:Synchro:ThisObjectIsSynchronized')."

"; + $sTip = "
".Dict::S('Core:Synchro:ThisObjectIsSynchronized')."
"; } - $sTip .= "

".Dict::S('Core:Synchro:ListOfDataSources')."

"; + $sTip .= "
".Dict::S('Core:Synchro:ListOfDataSources')."
"; foreach($aMasterSources as $aStruct) { // Formatting last synchro date @@ -350,70 +480,53 @@ EOF $oDataSource = $aStruct['datasource']; $sLink = $aStruct['url']; - $sTip .= "

".$oDataSource->GetIcon(true, - 'style="vertical-align:middle"')." $sLink
"; - $sTip .= Dict::S('Core:Synchro:LastSynchro').'
'.$sLastSynchro."

"; + $sTip .= "
".$oDataSource->GetIcon(true, '')."$sLink
"; + $sTip .= Dict::S('Core:Synchro:LastSynchro').'
'.$sLastSynchro."
"; } - $sLabel = htmlentities(Dict::S('Tag:Synchronized'), ENT_QUOTES, 'UTF-8'); + $sLabel = Dict::S('Tag:Synchronized'); $sSynchroTagId = 'synchro_icon-'.$this->GetKey(); - $aIcons[] = "
  $sLabel
"; - $sTip = addslashes($sTip); - $oPage->add_ready_script("$('#$sSynchroTagId').qtip( { content: '$sTip', show: 'mouseover', hide: { fixed: true }, style: { name: 'dark', tip: 'topLeft' }, position: { corner: { target: 'bottomMiddle', tooltip: 'topLeft' }} } );"); + $aTags[$sSynchroTagId] = ['title' => $sTip, 'css_classes' => 'ibo-object-details--tag--synchronized', 'decoration_classes' => 'fas fa-lock', 'label' => $sLabel]; } } - if ($this->IsArchived()) - { - $sLabel = htmlentities(Dict::S('Tag:Archived'), ENT_QUOTES, 'UTF-8'); - $sTitle = htmlentities(Dict::S('Tag:Archived+'), ENT_QUOTES, 'UTF-8'); - $aIcons[] = "
  $sLabel
"; - } - elseif ($this->IsObsolete()) - { - $sLabel = htmlentities(Dict::S('Tag:Obsolete'), ENT_QUOTES, 'UTF-8'); - $sTitle = htmlentities(Dict::S('Tag:Obsolete+'), ENT_QUOTES, 'UTF-8'); - $aIcons[] = "
  $sLabel
"; + if ($this->IsArchived()) { + $sLabel = Dict::S('Tag:Archived'); + $sTitle = Dict::S('Tag:Archived+'); + $aTags['archived'] = ['title' => $sTitle, 'css_classes' => 'ibo-object-details--tag--archived', 'decoration_classes' => 'fas fa-archive', 'label' => $sLabel]; + } elseif ($this->IsObsolete()) { + $sLabel = Dict::S('Tag:Obsolete'); + $sTitle = Dict::S('Tag:Obsolete+'); + $aTags['obsolete'] = ['title' => $sTitle, 'css_classes' => 'ibo-object-details--tag--obsolete', 'decoration_classes' => 'fas fa-eye-slash', 'label' => $sLabel]; } - $sObjectIcon = $this->GetIcon(); - $sClassName = MetaModel::GetName(get_class($this)); - $sObjectName = $this->GetName(); - if (count($aIcons) > 0) - { - $sTags = '
'.implode(' ', $aIcons).'
'; - } - else - { - $sTags = ''; + foreach ($aTags as $sIconId => $aIconData) { + $sTagTooltipContent = utils::EscapeHtml($aIconData['title']); + $aHeaderBlocks['subtitle'][static::HEADER_BLOCKS_SUBTITLE_TAG_PREFIX.$sIconId] = new Html(<<{$aIconData['label']} +HTML + ); } - $oPage->add( - << -
-
$sObjectIcon
-
-

$sClassName: $sObjectName

- $sTags -
-
- -EOF - ); + return $aHeaderBlocks; } /** * Display history tab of an object * - * @param \WebPage $oPage + * @deprecated 3.0.0 will be removed in 3.1, see N°3824 + * * @param bool $bEditMode * @param int $iLimitCount * @param int $iLimitStart * + * @param \WebPage $oPage + * * @throws \CoreException + * */ public function DisplayBareHistory(WebPage $oPage, $bEditMode = false, $iLimitCount = 0, $iLimitStart = 0) { + DeprecatedCallsLog::NotifyDeprecatedPhpMethod(); // history block (with as a tab) $oHistoryFilter = new DBObjectSearch('CMDBChangeOp'); $oHistoryFilter->AddCondition('objkey', $this->GetKey(), '='); @@ -427,12 +540,14 @@ EOF * Display properties tab of an object * * @param \WebPage $oPage - * @param bool $bEditMode + * @param bool $bEditMode Note that this parameter is no longer used in this method. Use {@see static::$sDisplayMode} instead * @param string $sPrefix * @param array $aExtraParams * * @return array * @throws \CoreException + * + * @since 3.0.0 $bEditMode is deprecated and no longer used */ public function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '', $aExtraParams = array()) { @@ -442,24 +557,12 @@ EOF if (!isset($aExtraParams['disable_plugins']) || !$aExtraParams['disable_plugins']) { /** @var iApplicationUIExtension $oExtensionInstance */ - foreach(MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) + foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) { $oExtensionInstance->OnDisplayProperties($this, $oPage, $bEditMode); } } - // Special case to display the case log, if any... - // WARNING: if you modify the loop below, also check the corresponding code in UpdateObject and DisplayModifyForm - foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef) - { - if ($oAttDef instanceof AttributeCaseLog) - { - $sComment = (isset($aExtraParams['fieldsComments'][$sAttCode])) ? $aExtraParams['fieldsComments'][$sAttCode] : ''; - $this->DisplayCaseLog($oPage, $sAttCode, $sComment, $sPrefix, $bEditMode); - $aFieldsMap[$sAttCode] = $this->m_iFormId.'_'.$sAttCode; - } - } - return $aFieldsMap; } @@ -482,24 +585,31 @@ EOF */ public function DisplayDashboard($oPage, $sAttCode) { + // Retrieve parameters + /** @var bool $bIsContainerInEdition True if the container of the dashboard is currently in edition; meaning that the dashboard could not be up-to-date with data changed in the container (eg. when editing an object and adding linkedset items) */ + $bIsContainerInEdition = (utils::ReadParam('host_container_in_edition', 'false') === 'true'); + $sClass = get_class($this); $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - if (!$oAttDef instanceof AttributeDashboard) - { + // Consistency checks + if (!$oAttDef instanceof AttributeDashboard) { throw new CoreException(Dict::S('UI:Error:InvalidDashboard')); } // Load the dashboard $oDashboard = $oAttDef->GetDashboard(); - if (is_null($oDashboard)) - { + if (is_null($oDashboard)) { throw new CoreException(Dict::S('UI:Error:InvalidDashboard')); } $bCanEdit = UserRights::IsAdministrator() || $oAttDef->IsUserEditable(); $sDivId = $oDashboard->GetId(); - $oPage->add('
'); + + if ($bIsContainerInEdition) { + $oPage->AddUiBlock(AlertUIBlockFactory::MakeForInformation(Dict::S('UI:Dashboard:NotUpToDateUntilContainerSaved'))); + } + $oPage->add('
'); $aExtraParams = array( 'query_params' => $this->ToArgsForQuery(), 'dashboard_div_id' => $sDivId, @@ -510,7 +620,7 @@ EOF /** * @param \WebPage $oPage - * @param bool $bEditMode + * @param bool $bEditMode Note that this parameter is no longer used in this method. Use {@see static::$sDisplayMode} instead * * @throws \CoreException * @throws \CoreUnexpectedValue @@ -520,6 +630,8 @@ EOF * @throws \MySQLHasGoneAwayException * @throws \OQLException * @throws \Exception + * + * @since 3.0.0 $bEditMode is deprecated and no longer used */ public function DisplayBareRelations(WebPage $oPage, $bEditMode = false) { @@ -534,16 +646,19 @@ EOF $aList = array_keys(MetaModel::ListAttributeDefs(get_class($this))); } $sClass = get_class($this); - foreach($aList as $sAttCode) - { + foreach($aList as $sAttCode) { $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); - if ($oAttDef instanceof AttributeDashboard) - { - if ($bEditMode) - { - continue; + if ($oAttDef instanceof AttributeDashboard) { + if (!$this->IsNew()) { + $sHostContainerInEditionUrlParam = ($bEditMode) ? '&host_container_in_edition=true' : ''; + $oPage->AddAjaxTab($oAttDef->GetLabel(), + utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=dashboard&class='.get_class($this).'&id='.$this->GetKey().'&attcode='.$oAttDef->GetCode().$sHostContainerInEditionUrlParam, + true, + 'Class:'.$sClass.'/Attribute:'.$sAttCode, + AjaxTab::ENUM_TAB_PLACEHOLDER_DASHBOARD); + // Add graphs dependencies + WebResourcesHelper::EnableC3JSToWebPage($oPage); } - $oPage->AddAjaxTab($oAttDef->GetLabel(), utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=dashboard&class='.get_class($this).'&id='.$this->GetKey().'&attcode='.$oAttDef->GetCode(), true, 'Class:'.$sClass.'/Attribute:'.$sAttCode); continue; } @@ -560,12 +675,6 @@ EOF $oLinkSet = $oOrmLinkSet->ToDBObjectSet(utils::ShowObsoleteData()); $iCount = $oLinkSet->Count(); - $sCount = ''; - if ($iCount != 0) - { - $sCount = " ($iCount)"; - } - $oPage->SetCurrentTab('Class:'.$sClass.'/Attribute:'.$sAttCode, $oAttDef->GetLabel().$sCount); if ($this->IsNew()) { $iFlags = $this->GetInitialStateAttributeFlags($sAttCode); @@ -611,6 +720,9 @@ EOF continue; } + $sCount = ($iCount != 0) ? " ($iCount)" : ""; + $oPage->SetCurrentTab('Class:'.$sClass.'/Attribute:'.$sAttCode, $oAttDef->GetLabel().$sCount); + $aArgs = array('this' => $this); $bReadOnly = ($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE)); if ($bEditMode && (!$bReadOnly)) @@ -626,7 +738,10 @@ EOF { $sTargetClass = $sLinkedClass; } - $oPage->p(MetaModel::GetClassIcon($sTargetClass)." ".$oAttDef->GetDescription().''); + + $oClassIcon = new MedallionIcon(MetaModel::GetClassIcon($sTargetClass, false)); + $oClassIcon->SetDescription($oAttDef->GetDescription())->AddCSSClass('ibo-block-list--medallion'); + $oPage->AddUiBlock($oClassIcon); $sDisplayValue = ''; // not used $sHTMLValue = "".self::GetFormElementForField($oPage, $sClass, $sAttCode, @@ -661,11 +776,32 @@ EOF 'table_id' => $sClass.'_'.$sAttCode, ); } - else - { + else { // n:n links $oLinkingAttDef = MetaModel::GetAttributeDef($sLinkedClass, $oAttDef->GetExtKeyToRemote()); + $sLinkingAttCode = $oLinkingAttDef->GetCode(); $sTargetClass = $oLinkingAttDef->GetTargetClass(); + + // N°2334 fields to display for n:n relations + $aLnkAttDefsToDisplay = MetaModel::GetZListAttDefsFilteredForIndirectLinkClass($sClass, $sAttCode); + $aRemoteAttDefsToDisplay = MetaModel::GetZListAttDefsFilteredForIndirectRemoteClass($sTargetClass); + $aLnkAttCodesToDisplay = array_map(function ($oLnkAttDef) { + return ormLinkSet::LINK_ALIAS.'.'.$oLnkAttDef->GetCode(); + }, + $aLnkAttDefsToDisplay + ); + if (!in_array(ormLinkSet::LINK_ALIAS.'.'.$sLinkingAttCode, $aLnkAttCodesToDisplay)) { + // we need to display a link to the remote class instance ! + $aLnkAttCodesToDisplay[] = ormLinkSet::LINK_ALIAS.'.'.$sLinkingAttCode; + } + $aRemoteAttCodesToDisplay = array_map(function ($oRemoteAttDef) { + return ormLinkSet::REMOTE_ALIAS.'.'.$oRemoteAttDef->GetCode(); + }, + $aRemoteAttDefsToDisplay + ); + $aAttCodesToDisplay = array_merge($aLnkAttCodesToDisplay, $aRemoteAttCodesToDisplay); + $sAttCodesToDisplay = implode(',', $aAttCodesToDisplay); + $aParams = array( 'link_attr' => $oAttDef->GetExtKeyToMe(), 'object_id' => $this->GetKey(), @@ -673,72 +809,62 @@ EOF 'view_link' => false, 'menu' => false, //'menu_actions_target' => '_blank', - 'display_limit' => true, // By default limit the list to speed up the initial load & display + // By default limit the list to speed up the initial load & display + 'display_limit' => true, 'table_id' => $sClass.'_'.$sAttCode, + // N°2334 specify fields to display for n:n relations + 'zlist' => false, + 'extra_fields' => $sAttCodesToDisplay, ); } - $oPage->p(MetaModel::GetClassIcon($sTargetClass)." ".$oAttDef->GetDescription()); + $oClassIcon = new MedallionIcon(MetaModel::GetClassIcon($sTargetClass, false)); + $oClassIcon->SetDescription($oAttDef->GetDescription())->AddCSSClass('ibo-block-list--medallion'); + $oPage->AddUiBlock($oClassIcon); $oBlock = new DisplayBlock($oLinkSet->GetFilter(), 'list', false); $oBlock->Display($oPage, 'rel_'.$sAttCode, $aParams); } - if (array_key_exists($sAttCode, $aRedundancySettings)) - { - foreach($aRedundancySettings[$sAttCode] as $oRedundancyAttDef) - { + if (array_key_exists($sAttCode, $aRedundancySettings)) { + foreach ($aRedundancySettings[$sAttCode] as $oRedundancyAttDef) { $sRedundancyAttCode = $oRedundancyAttDef->GetCode(); $sValue = $this->Get($sRedundancyAttCode); $iRedundancyFlags = $this->GetFormAttributeFlags($sRedundancyAttCode); $bRedundancyReadOnly = ($iRedundancyFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE)); - $oPage->add('
'); - $oPage->add(''.$oRedundancyAttDef->GetLabel().''); - if ($bEditMode && (!$bRedundancyReadOnly)) - { + $oFieldSet = FieldSetUIBlockFactory::MakeStandard($oRedundancyAttDef->GetLabel()); + $oFieldSet->AddCSSClass('mt-5'); + $oPage->AddSubBlock($oFieldSet); + + if ($bEditMode && (!$bRedundancyReadOnly)) { $sInputId = $this->m_iFormId.'_'.$sRedundancyAttCode; - $oPage->add("".self::GetFormElementForField($oPage, $sClass, + $oFieldSet->AddSubBlock(new Html("".self::GetFormElementForField($oPage, $sClass, $sRedundancyAttCode, $oRedundancyAttDef, $sValue, '', $sInputId, '', $iFlags, - $aArgs).''); + $aArgs).'')); + } else { + $oFieldSet->AddSubBlock(new Html($oRedundancyAttDef->GetDisplayForm($sValue, $oPage, false, $this->m_iFormId))); } - else - { - $oPage->add($oRedundancyAttDef->GetDisplayForm($sValue, $oPage, false, $this->m_iFormId)); - } - $oPage->add('
'); } } } - $oPage->SetCurrentTab(''); /** @var \iApplicationUIExtension $oExtensionInstance */ - foreach(MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) - { + foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) { $oExtensionInstance->OnDisplayRelations($this, $oPage, $bEditMode); } - // Display Notifications after the other tabs since this tab disappears in edition - if (!$bEditMode) - { + $oPage->SetCurrentTab(''); + + if (!$this->IsNew()) { // Look for any trigger that considers this object as "In Scope" // If any trigger has been found then display a tab with notifications - // - $oTriggerSet = new CMDBObjectSet(new DBObjectSearch('Trigger')); - $aTriggers = array(); - while ($oTrigger = $oTriggerSet->Fetch()) - { - if ($oTrigger->IsInScope($this)) - { - $aTriggers[] = $oTrigger->GetKey(); - } - } - if (count($aTriggers) > 0) - { + // + $aTriggers = $this->GetRelatedTriggersIDs(); + if (count($aTriggers) > 0) { $iId = $this->GetKey(); $aParams = array('triggers' => $aTriggers, 'id' => $iId); $aNotifSearches = array(); $iNotifsCount = 0; - $aNotificationClasses = MetaModel::EnumChildClasses('EventNotification', ENUM_CHILD_CLASSES_EXCLUDETOP); - foreach($aNotificationClasses as $sNotifClass) - { + $aNotificationClasses = MetaModel::EnumChildClasses('EventNotification'); + foreach ($aNotificationClasses as $sNotifClass) { $aNotifSearches[$sNotifClass] = DBObjectSearch::FromOQL("SELECT $sNotifClass AS Ev JOIN Trigger AS T ON Ev.trigger_id = T.id WHERE T.id IN (:triggers) AND Ev.object_id = :id"); $aNotifSearches[$sNotifClass]->SetInternalParams($aParams); $oNotifSet = new DBObjectSet($aNotifSearches[$sNotifClass], array()); @@ -748,9 +874,11 @@ EOF $sCount = ($iNotifsCount > 0) ? ' ('.$iNotifsCount.')' : ''; $oPage->SetCurrentTab('UI:NotificationsTab', Dict::S('UI:NotificationsTab').$sCount); - foreach($aNotificationClasses as $sNotifClass) - { - $oPage->p(MetaModel::GetClassIcon($sNotifClass, true).' '.MetaModel::GetName($sNotifClass)); + foreach ($aNotificationClasses as $sNotifClass) { + $oClassIcon = new MedallionIcon(MetaModel::GetClassIcon($sNotifClass, false)); + $oClassIcon->SetDescription(MetaModel::GetName($sNotifClass))->AddCSSClass('ibo-block-list--medallion'); + $oPage->AddUiBlock($oClassIcon); + $oBlock = new DisplayBlock($aNotifSearches[$sNotifClass], 'list', false); $oBlock->Display($oPage, 'notifications_'.$sNotifClass, array('menu' => false)); } @@ -758,9 +886,35 @@ EOF } } + /** + * @return string[] IDs of the triggers that consider this object as "In Scope" + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \MySQLException + * @since 3.0.0 + */ + public function GetRelatedTriggersIDs(): array + { + $aTriggers = []; + // Request only "leaf" classes to avoid reloads + $aTriggerClasses = MetaModel::EnumChildClasses('Trigger'); + foreach ($aTriggerClasses as $sTriggerClass) { + if (MetaModel::IsLeafClass($sTriggerClass)) { + $oTriggerSet = new CMDBObjectSet(new DBObjectSearch($sTriggerClass)); + while ($oTrigger = $oTriggerSet->Fetch()) { + if ($oTrigger->IsInScope($this)) { + $aTriggers[] = $oTrigger->GetKey(); + } + } + } + } + + return $aTriggers; + } + /** * @param \WebPage $oPage - * @param bool $bEditMode + * @param bool $bEditMode Note that this parameter is no longer used in this method. Use {@see static::$sDisplayMode} instead * @param string $sPrefix * @param array $aExtraParams * @@ -772,6 +926,8 @@ EOF * @throws \MySQLException * @throws \OQLException * @throws \Exception + * + * @since 3.0.0 $bEditMode is deprecated and no longer used */ public function GetBareProperties(WebPage $oPage, $bEditMode, $sPrefix, $aExtraParams = array()) { @@ -779,115 +935,72 @@ EOF $sClass = get_class($this); $aDetailsList = MetaModel::GetZListItems($sClass, 'details'); $aDetailsStruct = self::ProcessZlist($aDetailsList, array('UI:PropertiesTab' => array()), 'UI:PropertiesTab', 'col1', ''); - // Compute the list of properties to display, first the attributes in the 'details' list, then - // all the remaining attributes that are not external fields - $sEditMode = ($bEditMode) ? 'edit' : 'view'; - $aDetails = array(); - $iInputId = 0; $aFieldsMap = array(); $aFieldsComments = (isset($aExtraParams['fieldsComments'])) ? $aExtraParams['fieldsComments'] : array(); $aExtraFlags = (isset($aExtraParams['fieldsFlags'])) ? $aExtraParams['fieldsFlags'] : array(); - foreach($aDetailsStruct as $sTab => $aCols) - { - $aDetails[$sTab] = array(); - $aTableStyles[] = 'vertical-align:top'; - $aTableClasses = array(); - $aColStyles[] = 'vertical-align:top'; - $aColClasses = array(); - + $bHasFieldsWithRichTextEditor = false; + foreach ($aDetailsStruct as $sTab => $aCols) { ksort($aCols); - $iColCount = count($aCols); - if ($iColCount > 1) - { - $aTableClasses[] = 'n-cols-details'; - $aTableClasses[] = $iColCount.'-cols-details'; - - $aColStyles[] = 'width:'.floor(100 / $iColCount).'%'; - } - else - { - $aTableClasses[] = 'one-col-details'; - } - $oPage->SetCurrentTab($sTab); - $oPage->add(''); - foreach($aCols as $sColIndex => $aFieldsets) - { - $oPage->add(''); } - $oPage->add('
'); - $sPreviousLabel = ''; - $aDetails[$sTab][$sColIndex] = array(); - foreach($aFieldsets as $sFieldsetName => $aFields) - { - if (!empty($sFieldsetName) && ($sFieldsetName[0] != '_')) - { - $sLabel = $sFieldsetName; + $oMultiColumn = new MultiColumn(); + $oPage->AddUiBlock($oMultiColumn); + + foreach ($aCols as $sColIndex => $aFieldsets) { + $oColumn = new Column(); + $oMultiColumn->AddColumn($oColumn); + + foreach ($aFieldsets as $sFieldsetName => $aFields) { + if ($sFieldsetName[0] != '_') { + $oFieldSet = new FieldSet(Dict::S($sFieldsetName)); + $oColumn->AddSubBlock($oFieldSet); } - else - { - $sLabel = ''; - } - if ($sLabel != $sPreviousLabel) - { - if (!empty($sPreviousLabel)) - { - $oPage->add('
'); - $oPage->add(''.Dict::S($sPreviousLabel).''); - } - $oPage->Details($aDetails[$sTab][$sColIndex]); - if (!empty($sPreviousLabel)) - { - $oPage->add('
'); - } - $aDetails[$sTab][$sColIndex] = array(); - $sPreviousLabel = $sLabel; - } - foreach($aFields as $sAttCode) - { + $aDetails = []; + foreach ($aFields as $sAttCode) { $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + + // Skip case logs as they will be handled by the activity panel + if ($oAttDef instanceof AttributeCaseLog) { + continue; + } + + if (($oAttDef instanceof AttributeText) && ($oAttDef->GetFormat() === 'html')) { + $bHasFieldsWithRichTextEditor = true; + } + $sAttDefClass = get_class($oAttDef); $sAttLabel = MetaModel::GetLabel($sClass, $sAttCode); - if ($bEditMode) - { + if ($bEditMode) { $sComments = isset($aFieldsComments[$sAttCode]) ? $aFieldsComments[$sAttCode] : ''; $sInfos = ''; $iFlags = $this->GetFormAttributeFlags($sAttCode); - if (array_key_exists($sAttCode, $aExtraFlags)) - { + if (array_key_exists($sAttCode, $aExtraFlags)) { // the caller may override some flags if needed $iFlags = $iFlags | $aExtraFlags[$sAttCode]; } - if ((!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0) && !($oAttDef instanceof AttributeDashboard)) - { + if ((!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0) && !($oAttDef instanceof AttributeDashboard)) { $sInputId = $this->m_iFormId.'_'.$sAttCode; - if ($oAttDef->IsWritable()) - { - if ($sStateAttCode == $sAttCode) - { + if ($oAttDef->IsWritable()) { + $sInputType = ''; + if (($sStateAttCode === $sAttCode) && (MetaModel::HasLifecycle($sClass))) { // State attribute is always read-only from the UI - $sHTMLValue = $this->GetStateLabel(); + $sHTMLValue = $this->GetAsHTML($sAttCode); $val = array( 'label' => '', 'value' => $sHTMLValue, + 'input_id' => $sInputId, 'comments' => $sComments, 'infos' => $sInfos, ); - } - else - { - if ($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE)) - { + } else { + if ($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE)) { // Check if the attribute is not read-only because of a synchro... - if ($iFlags & OPT_ATT_SLAVE) - { + if ($iFlags & OPT_ATT_SLAVE) { $aReasons = array(); $this->GetSynchroReplicaFlags($sAttCode, $aReasons); - $sSynchroIcon = " "; $sTip = ''; - foreach($aReasons as $aRow) - { + foreach ($aReasons as $aRow) { $sDescription = htmlentities($aRow['description'], ENT_QUOTES, 'UTF-8'); $sDescription = str_replace(array("\r\n", "\n"), "
", @@ -896,27 +1009,35 @@ EOF $sTip .= "
Synchronized with {$aRow['name']}
"; $sTip .= "
$sDescription
"; } - $sTip = addslashes($sTip); - $oPage->add_ready_script("$('#synchro_$sInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );"); + $sTip = utils::HtmlEntities($sTip); + $sSynchroIcon = '
'; $sComments = $sSynchroIcon; } // Attribute is read-only $sHTMLValue = "".$this->GetAsHTML($sAttCode).''; - } - else - { + } else { $sValue = $this->Get($sAttCode); $sDisplayValue = $this->GetEditValue($sAttCode); $aArgs = array('this' => $this, 'formPrefix' => $sPrefix); - $sHTMLValue = "".self::GetFormElementForField($oPage, $sClass, $sAttCode, - $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, - $aArgs).''; + $sHTMLValue = "".self::GetFormElementForField( + $oPage, $sClass, $sAttCode, $oAttDef, $sValue, + $sDisplayValue, $sInputId, '', $iFlags, $aArgs, + true, $sInputType + ).''; } $aFieldsMap[$sAttCode] = $sInputId; + + // Attribute description + $sDescription = $oAttDef->GetDescription(); + $sDescriptionForHTMLTag = utils::HtmlEntities($sDescription); + $sDescriptionHTMLTag = (empty($sDescriptionForHTMLTag) || $sDescription === $oAttDef->GetLabel()) ? '' : 'class="ibo-has-description" data-tooltip-content="'.$sDescriptionForHTMLTag.'" data-tooltip-max-width="600px"'; + $val = array( - 'label' => ''.$oAttDef->GetLabel().'', + 'label' => ''.$oAttDef->GetLabel().'', 'value' => $sHTMLValue, + 'input_id' => $sInputId, + 'input_type' => $sInputType, 'comments' => $sComments, 'infos' => $sInfos, ); @@ -924,8 +1045,13 @@ EOF } else { + // Attribute description + $sDescription = $oAttDef->GetDescription(); + $sDescriptionForHTMLTag = utils::HtmlEntities($sDescription); + $sDescriptionHTMLTag = (empty($sDescriptionForHTMLTag) || $sDescription === $oAttDef->GetLabel()) ? '' : 'class="ibo-has-description" data-tooltip-content="'.$sDescriptionForHTMLTag.' "data-tooltip-max-width="600px"'; + $val = array( - 'label' => ''.$oAttDef->GetLabel().'', + 'label' => ''.$oAttDef->GetLabel().'', 'value' => "".$this->GetAsHTML($sAttCode)."", 'comments' => $sComments, 'infos' => $sInfos, @@ -954,14 +1080,15 @@ EOF $val['attflags'] = ($bEditMode) ? $this->GetFormAttributeFlags($sAttCode) : OPT_ATT_READONLY; // - How the field should be rendered - $val['layout'] = (in_array($oAttDef->GetEditClass(), static::GetAttEditClassesToRenderAsLargeField())) ? 'large' : 'small'; + $val['layout'] = + (in_array($oAttDef->GetEditClass(), static::GetAttEditClassesToRenderAsLargeField())) + ? Field::ENUM_FIELD_LAYOUT_LARGE + : Field::ENUM_FIELD_LAYOUT_SMALL; // - For simple fields, we get the raw (stored) value as well $bExcludeRawValue = false; - foreach (static::GetAttDefClassesToExcludeFromMarkupMetadataRawValue() as $sAttDefClassToExclude) - { - if (is_a($sAttDefClass, $sAttDefClassToExclude, true)) - { + foreach (static::GetAttDefClassesToExcludeFromMarkupMetadataRawValue() as $sAttDefClassToExclude) { + if (is_a($sAttDefClass, $sAttDefClassToExclude, true)) { $bExcludeRawValue = true; break; } @@ -969,24 +1096,21 @@ EOF $val['value_raw'] = ($bExcludeRawValue === false) ? $this->Get($sAttCode) : ''; // The field is visible, add it to the current column - $aDetails[$sTab][$sColIndex][] = $val; - $iInputId++; + $oField = FieldUIBlockFactory::MakeFromParams($val); + if ($sFieldsetName[0] != '_') { + $oFieldSet->AddSubBlock($oField); + } else { + $oColumn->AddSubBlock($oField); + } } } } - if (!empty($sPreviousLabel)) - { - $oPage->add('
'); - $oPage->add(''.Dict::S($sFieldsetName).''); - } - $oPage->Details($aDetails[$sTab][$sColIndex]); - if (!empty($sPreviousLabel)) - { - $oPage->add('
'); - } - $oPage->add('
'); + } + + // Fields with CKEditor need to have the highlight.js lib loaded even if they are in read-only, as it is needed to format code snippets + if ($bHasFieldsWithRichTextEditor) { + WebResourcesHelper::EnableCKEditorToWebPage($oPage); } return $aFieldsMap; @@ -995,8 +1119,10 @@ EOF /** * @param \WebPage $oPage - * @param bool $bEditMode + * @param bool $bEditMode Note that this parameter is no longer used in this method, {@see static::$sDisplayMode} is used instead, but we cannot remove it as it part of the base interface (iDisplay)... * + * @throws \ApplicationException + * @throws \ArchivedObjectException * @throws \CoreException * @throws \CoreUnexpectedValue * @throws \DictExceptionMissingString @@ -1004,52 +1130,69 @@ EOF * @throws \MySQLException * @throws \MySQLHasGoneAwayException * @throws \OQLException - * @throws \Exception + * + * @since 3.0.0 $bEditMode is deprecated and no longer used */ public function DisplayDetails(WebPage $oPage, $bEditMode = false) { + // N°3786: As this can now be call recursively from the self::ReloadAndDisplay(), we need to make sure we don't fall into an infinite loop + static $bBlockReentrance = false; + $sClass = get_class($this); $iKey = $this->GetKey(); - $sMode = static::ENUM_OBJECT_MODE_VIEW; - $sTemplate = Utils::ReadFromFile(MetaModel::GetDisplayTemplate($sClass)); - if (!empty($sTemplate)) - { - $oTemplate = new DisplayTemplate($sTemplate); - // Note: to preserve backward compatibility with home-made templates, the placeholder '$pkey$' has been preserved - // but the preferred method is to use '$id$' - $oTemplate->Render($oPage, array( - 'class_name' => MetaModel::GetName($sClass), - 'class' => $sClass, - 'pkey' => $iKey, - 'id' => $iKey, - 'name' => $this->GetName(), - )); + if ($this->GetDisplayMode() === static::ENUM_DISPLAY_MODE_VIEW) { + // The concurrent access lock makes sense only for already existing objects + $LockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled'); + if ($LockEnabled) { + $aLockInfo = iTopOwnershipLock::IsLocked($sClass, $iKey); + if ($aLockInfo['locked'] === true && $aLockInfo['owner']->GetKey() == UserRights::GetUserId() && $bBlockReentrance === false) { + // If the object is locked by the current user, it's worth trying again, since + // the lock may be released by 'onunload' which is called AFTER loading the current page. + //$bTryAgain = $oOwner->GetKey() == UserRights::GetUserId(); + $bBlockReentrance = true; + self::ReloadAndDisplay($oPage, $this, array('operation' => 'details')); + + return; + } + } } - else - { - // Object's details - // template not found display the object using the *old style* - $oPage->add(<< -
-HTML - ); - $this->DisplayBareHeader($oPage, $bEditMode); - /** @var \iTopWebPage $oPage */ - $oPage->AddTabContainer(OBJECT_PROPERTIES_TAB); - $oPage->SetCurrentTabContainer(OBJECT_PROPERTIES_TAB); - $oPage->SetCurrentTab('UI:PropertiesTab'); - $this->DisplayBareProperties($oPage, $bEditMode); - $this->DisplayBareRelations($oPage, $bEditMode); - //$oPage->SetCurrentTab('UI:HistoryTab'); - //$this->DisplayBareHistory($oPage, $bEditMode); - $oPage->AddAjaxTab('UI:HistoryTab', - utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=history&class='.$sClass.'&id='.$iKey); - $oPage->add(<< -HTML - ); + + // Object's details + $oObjectDetails = ObjectFactory::MakeDetails($this, $this->GetDisplayMode()); + if ($oPage->IsPrintableVersion()) { + $oObjectDetails->SetIsHeaderVisibleOnScroll(false); + } + + // Note: DisplayBareHeader is called before adding $oObjectDetails to the page, so it can inject HTML before it through $oPage. + /** @var \iTopWebPage $oPage */ + $aHeadersBlocks = $this->DisplayBareHeader($oPage, $bEditMode); + if (false === empty($aHeadersBlocks['subtitle'])) { + $oObjectDetails->AddSubTitleBlocks($aHeadersBlocks['subtitle']); + } + if (false === empty($aHeadersBlocks['toolbar'])) { + $oObjectDetails->AddToolbarBlocks($aHeadersBlocks['toolbar']); + } + + $oPage->AddUiBlock($oObjectDetails); + + $oPage->AddTabContainer(OBJECT_PROPERTIES_TAB, '', $oObjectDetails); + $oPage->SetCurrentTabContainer(OBJECT_PROPERTIES_TAB); + $oPage->SetCurrentTab('UI:PropertiesTab'); + $this->DisplayBareProperties($oPage, $bEditMode); + $this->DisplayBareRelations($oPage, $bEditMode); + // TODO 3.0.0: What to do with this? + //$this->DisplayBareHistory($oPage, $bEditMode); + + // Note: Adding the JS snippet which enables the image upload should have been done directly by the ActivityPanel which would have kept the independance principle + // of the UIBlock. For now we keep it this way in order to move on and trace this known limitation in N°3736. + /** @var ActivityPanel $oActivityPanel */ + $oActivityPanel = $oPage->GetContentLayout()->GetSubBlock(ActivityPanel::BLOCK_CODE); + // Note: Testing if block exists is necessary as during the 'release_lock_and_details' operation we don't have an activity panel + if (!is_null($oActivityPanel) && $oActivityPanel->HasTransactionId()) { + $iTransactionId = $oActivityPanel->GetTransactionId(); + $sTempId = utils::GetUploadTempId($iTransactionId); + $oPage->add_ready_script(InlineImage::EnableCKEditorImageUpload($this, $sTempId)); } } @@ -1086,7 +1229,7 @@ HTML */ public static function DisplaySet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { - $oPage->add(self::GetDisplaySet($oPage, $oSet, $aExtraParams)); + $oPage->AddUiBlock(self::GetDisplaySetBlock($oPage, $oSet, $aExtraParams)); } /** @@ -1102,193 +1245,225 @@ HTML */ public static function GetDisplaySetForPrinting(WebPage $oPage, DBObjectSet $oSet, $aExtraParams = array()) { - $sTableId = isset($aExtraParams['table_id']) ? $aExtraParams['table_id'] : null; - - $bViewLink = true; - $sSelectMode = 'none'; - $iListId = $sTableId; - $sClassAlias = $oSet->GetClassAlias(); - $sClassName = $oSet->GetClass(); - $sZListName = 'list'; - $aClassAliases = array($sClassAlias => $sClassName); - $aList = cmdbAbstractObject::FlattenZList(MetaModel::GetZListItems($sClassName, $sZListName)); - - $oDataTable = new PrintableDataTable($iListId, $oSet, $aClassAliases, $sTableId); - $oSettings = DataTableSettings::GetDataModelSettings($aClassAliases, $bViewLink, array($sClassAlias => $aList)); - $oSettings->iDefaultPageSize = 0; - $oSettings->aSortOrder = MetaModel::GetOrderByDefault($sClassName); - - return $oDataTable->Display($oPage, $oSettings, false /* $bDisplayMenu */, $sSelectMode, $bViewLink, - $aExtraParams); + $sTableId = isset($aExtraParams['table_id']) ? $aExtraParams['table_id'] : utils::GetUniqueId();; + $aExtraParams['view_link'] = true; + $aExtraParams['select_mode'] = 'none'; + return DataTableUIBlockFactory::MakeForObject($oPage, $sTableId, $oSet, $aExtraParams); } /** * Get the HTML fragment corresponding to the display of a table representing a set of objects * - * @see DisplayBlock to get a similar table but with the JS for pagination & sorting - * - * @param CMDBObjectSet The set of objects to display - * @param array $aExtraParams Some extra configuration parameters to tweak the behavior of the display - * * @param WebPage $oPage The page object is used for out-of-band information (mostly scripts) output + * @param \DBObjectSet $oSet The set of objects to display + * @param array $aExtraParams key used : + *
    + *
  • view_link : if true then for extkey will display links with friendly name and make column sortable, default true + *
  • menu : if true prints DisplayBlock menu, default true + *
  • display_aliases : list of query aliases that will be printed, defaults to [] (displays all) + *
  • zlist : name of the zlist to use, false to disable zlist lookup, default to 'list' + *
  • extra_fields : list of . to add to the result, separator ',', defaults to empty string + *
* * @return String The HTML fragment representing the table of objects. Warning : no JS added to handled * pagination or table sorting ! * - * @throws \CoreException*@throws \Exception - * @throws \ApplicationException + * @see DisplayBlock to get a similar table but with the JS for pagination & sorting + * + * @deprecated 3.0.0 use GetDisplaySetBlock */ - public static function GetDisplaySet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) + public static function GetDisplaySet(WebPage $oPage, DBObjectSet $oSet, $aExtraParams = array()) { - if ($oPage->IsPrintableVersion() || $oPage->is_pdf()) - { + DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use GetDisplaySetBlock'); + $oPage->AddUiBlock(static::GetDisplaySetBlock($oPage, $oSet, $aExtraParams)); + + return ""; + } + + /** + * @param \WebPage $oPage + * @param \DBObjectSet $oSet + * @param array $aExtraParams + * + * @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock|string + * @throws \ApplicationException + * @throws \ArchivedObjectException + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \DictExceptionMissingString + * @throws \MissingQueryArgument + * @throws \MySQLException + * @throws \MySQLHasGoneAwayException + * @throws \OQLException + * @throws \ReflectionException + * + * @since 3.0.0 + */ + public static function GetDisplaySetBlock(WebPage $oPage, DBObjectSet $oSet, $aExtraParams = array()) + { + if ($oPage->IsPrintableVersion() || $oPage->is_pdf()) { return self::GetDisplaySetForPrinting($oPage, $oSet, $aExtraParams); } - - if (empty($aExtraParams['currentId'])) - { - $iListId = $oPage->GetUniqueId(); // Works only if not in an Ajax page !! - } - else - { + if (empty($aExtraParams['currentId'])) { + $iListId = utils::GetUniqueId(); // Works only if not in an Ajax page !! + } else { $iListId = $aExtraParams['currentId']; } - // Initialize and check the parameters - $bViewLink = isset($aExtraParams['view_link']) ? $aExtraParams['view_link'] : true; - $sLinkageAttribute = isset($aExtraParams['link_attr']) ? $aExtraParams['link_attr'] : ''; - $iLinkedObjectId = isset($aExtraParams['object_id']) ? $aExtraParams['object_id'] : 0; - $sTargetAttr = isset($aExtraParams['target_attr']) ? $aExtraParams['target_attr'] : ''; - if (!empty($sLinkageAttribute)) - { - if ($iLinkedObjectId == 0) - { - // if 'links' mode is requested the id of the object to link to must be specified - throw new ApplicationException(Dict::S('UI:Error:MandatoryTemplateParameter_object_id')); - } - if ($sTargetAttr == '') - { - // if 'links' mode is requested the d of the object to link to must be specified - throw new ApplicationException(Dict::S('UI:Error:MandatoryTemplateParameter_target_attr')); + return DataTableUIBlockFactory::MakeForResult($oPage, $iListId, $oSet, $aExtraParams); + } + + public static function GetDataTableFromDBObjectSet(DBObjectSet $oSet, $aParams = array()) + { + $aFields = null; + if (isset($aParams['fields']) && (strlen($aParams['fields']) > 0)) { + $aFields = explode(',', $aParams['fields']); + } + + $bFieldsAdvanced = false; + if (isset($aParams['fields_advanced'])) { + $bFieldsAdvanced = (bool)$aParams['fields_advanced']; + } + + $bLocalize = true; + if (isset($aParams['localize_values'])) { + $bLocalize = (bool)$aParams['localize_values']; + } + + $aList = array(); + + $aClasses = $oSet->GetFilter()->GetSelectedClasses(); + $aAuthorizedClasses = array(); + foreach ($aClasses as $sAlias => $sClassName) { + if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO) { + $aAuthorizedClasses[$sAlias] = $sClassName; } } - $bDisplayMenu = isset($aExtraParams['menu']) ? $aExtraParams['menu'] == true : true; - $bSelectMode = isset($aExtraParams['selection_mode']) ? $aExtraParams['selection_mode'] == true : false; - $bSingleSelectMode = isset($aExtraParams['selection_type']) ? ($aExtraParams['selection_type'] == 'single') : false; + $aHeader = array(); + foreach ($aAuthorizedClasses as $sAlias => $sClassName) { + $aList[$sAlias] = array(); - $aExtraFieldsRaw = isset($aExtraParams['extra_fields']) ? explode(',', - trim($aExtraParams['extra_fields'])) : array(); - $aExtraFields = array(); - foreach($aExtraFieldsRaw as $sFieldName) - { - // Ignore attributes not of the main queried class - if (preg_match('/^(.*)\.(.*)$/', $sFieldName, $aMatches)) - { - $sClassAlias = $aMatches[1]; - $sAttCode = $aMatches[2]; - if ($sClassAlias == $oSet->GetFilter()->GetClassAlias()) - { - $aExtraFields[] = $sAttCode; + foreach (MetaModel::GetZListItems($sClassName, 'list') as $sAttCode) { + $oAttDef = Metamodel::GetAttributeDef($sClassName, $sAttCode); + if (is_null($aFields) || (count($aFields) == 0)) { + // Standard list of attributes (no link sets) + if ($oAttDef->IsScalar() && ($oAttDef->IsWritable() || $oAttDef->IsExternalField())) { + $sAttCodeEx = $oAttDef->IsExternalField() ? $oAttDef->GetKeyAttCode().'->'.$oAttDef->GetExtAttCode() : $sAttCode; + + $aList[$sAlias][$sAttCodeEx] = $oAttDef; + + if ($bFieldsAdvanced && $oAttDef->IsExternalKey(EXTKEY_RELATIVE)) { + $sRemoteClass = $oAttDef->GetTargetClass(); + foreach (MetaModel::GetReconcKeys($sRemoteClass) as $sRemoteAttCode) { + $aList[$sAlias][$sAttCode.'->'.$sRemoteAttCode] = MetaModel::GetAttributeDef($sRemoteClass, + $sRemoteAttCode); + } + } + } + } else { + // User defined list of attributes + if (in_array($sAttCode, $aFields) || in_array($sAlias.'.'.$sAttCode, $aFields)) { + $aList[$sAlias][$sAttCode] = $oAttDef; + } } } - else - { - $aExtraFields[] = $sFieldName; - } - } - $sClassName = $oSet->GetFilter()->GetClass(); - $sZListName = isset($aExtraParams['zlist']) ? ($aExtraParams['zlist']) : 'list'; - if ($sZListName !== false) - { - $aList = self::FlattenZList(MetaModel::GetZListItems($sClassName, $sZListName)); - $aList = array_merge($aList, $aExtraFields); - } - else - { - $aList = $aExtraFields; - } - - // Filter the list to removed linked set since we are not able to display them here - foreach($aList as $index => $sAttCode) - { - $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode); - if ($oAttDef instanceof AttributeLinkedSet) - { - // Removed from the display list - unset($aList[$index]); - } - } - - - if (!empty($sLinkageAttribute)) - { - // The set to display is in fact a set of links between the object specified in the $sLinkageAttribute - // and other objects... - // The display will then group all the attributes related to the link itself: - // | Link_attr1 | link_attr2 | ... || Object_attr1 | Object_attr2 | Object_attr3 | .. | Object_attr_n | - $aDisplayList = array(); - $aAttDefs = MetaModel::ListAttributeDefs($sClassName); - assert(isset($aAttDefs[$sLinkageAttribute])); - $oAttDef = $aAttDefs[$sLinkageAttribute]; - assert($oAttDef->IsExternalKey()); - // First display all the attributes specific to the link record - foreach($aList as $sLinkAttCode) - { - $oLinkAttDef = $aAttDefs[$sLinkAttCode]; - if ((!$oLinkAttDef->IsExternalKey()) && (!$oLinkAttDef->IsExternalField())) - { - $aDisplayList[] = $sLinkAttCode; + // Replace external key by the corresponding friendly name (if not already in the list) + foreach ($aList[$sAlias] as $sAttCode => $oAttDef) { + if ($oAttDef->IsExternalKey()) { + unset($aList[$sAlias][$sAttCode]); + $sFriendlyNameAttCode = $sAttCode.'_friendlyname'; + if (!array_key_exists($sFriendlyNameAttCode, + $aList[$sAlias]) && MetaModel::IsValidAttCode($sClassName, $sFriendlyNameAttCode)) { + $oFriendlyNameAtt = MetaModel::GetAttributeDef($sClassName, $sFriendlyNameAttCode); + $aList[$sAlias][$sFriendlyNameAttCode] = $oFriendlyNameAtt; + } } } - // Then display all the attributes neither specific to the link record nor to the 'linkage' object (because the latter are constant) - foreach($aList as $sLinkAttCode) - { - $oLinkAttDef = $aAttDefs[$sLinkAttCode]; - if (($oLinkAttDef->IsExternalKey() && ($sLinkAttCode != $sLinkageAttribute)) - || ($oLinkAttDef->IsExternalField() && ($oLinkAttDef->GetKeyAttCode() != $sLinkageAttribute))) - { - $aDisplayList[] = $sLinkAttCode; + + foreach ($aList[$sAlias] as $sAttCodeEx => $oAttDef) { + $sColLabel = $bLocalize ? MetaModel::GetLabel($sClassName, $sAttCodeEx) : $sAttCodeEx; + + $oFinalAttDef = $oAttDef->GetFinalAttDef(); + if (get_class($oFinalAttDef) == 'AttributeDateTime') { + $aHeader[$oAttDef->GetCode().'/D'] = ['label' => $sColLabel.' ('.Dict::S('UI:SplitDateTime-Date').')']; + $aHeader[$oAttDef->GetCode().'/T'] = ['label' => $sColLabel.' ('.Dict::S('UI:SplitDateTime-Time').')']; + } else { + $aHeader[$oAttDef->GetCode()] = ['label' => $sColLabel]; } } - // First display all the attributes specific to the link - // Then display all the attributes linked to the other end of the relationship - $aList = $aDisplayList; } - $sSelectMode = 'none'; - if ($bSelectMode) - { - $sSelectMode = $bSingleSelectMode ? 'single' : 'multiple'; + + $oSet->Seek(0); + $aRows = []; + while ($aObjects = $oSet->FetchAssoc()) { + $aRow = []; + foreach ($aAuthorizedClasses as $sAlias => $sClassName) { + $oObj = $aObjects[$sAlias]; + foreach ($aList[$sAlias] as $sAttCodeEx => $oAttDef) { + if (is_null($oObj)) { + $aRow[$oAttDef->GetCode()] = ''; + } else { + $oFinalAttDef = $oAttDef->GetFinalAttDef(); + if (get_class($oFinalAttDef) == 'AttributeDateTime') { + $sDate = $oObj->Get($sAttCodeEx); + if ($sDate === null) { + $aRow[$oAttDef->GetCode().'/D'] = ''; + $aRow[$oAttDef->GetCode().'/T'] = ''; + } else { + $iDate = AttributeDateTime::GetAsUnixSeconds($sDate); + $aRow[$oAttDef->GetCode().'/D'] = date('Y-m-d', $iDate); // Format kept as-is for 100% backward compatibility of the exports + $aRow[$oAttDef->GetCode().'/T'] = date('H:i:s', $iDate); // Format kept as-is for 100% backward compatibility of the exports + } + } else { + if ($oAttDef instanceof AttributeCaseLog) { + $rawValue = $oObj->Get($sAttCodeEx); + $outputValue = str_replace("\n", "
", htmlentities($rawValue->__toString(), ENT_QUOTES, 'UTF-8')); + // Trick for Excel: treat the content as text even if it begins with an equal sign + $aRow[$oAttDef->GetCode()] = $outputValue; + } else { + $rawValue = $oObj->Get($sAttCodeEx); + // Due to custom formatting rules, empty friendlynames may be rendered as non-empty strings + // let's fix this and make sure we render an empty string if the key == 0 + if ($oAttDef instanceof AttributeExternalField && $oAttDef->IsFriendlyName()) { + $sKeyAttCode = $oAttDef->GetKeyAttCode(); + if ($oObj->Get($sKeyAttCode) == 0) { + $rawValue = ''; + } + } + if ($bLocalize) { + $outputValue = htmlentities($oFinalAttDef->GetEditValue($rawValue), ENT_QUOTES, 'UTF-8'); + } else { + $outputValue = htmlentities($rawValue, ENT_QUOTES, 'UTF-8'); + } + $aRow[$oAttDef->GetCode()] = $outputValue; + } + } + } + } + } + $aRows[] = $aRow; } + $oTable = new StaticTable(); + $oTable->SetColumns($aHeader); + $oTable->SetData($aRows); - $sClassAlias = $oSet->GetClassAlias(); - $bDisplayLimit = isset($aExtraParams['display_limit']) ? $aExtraParams['display_limit'] : true; - - $sTableId = isset($aExtraParams['table_id']) ? $aExtraParams['table_id'] : null; - $aClassAliases = array($sClassAlias => $sClassName); - $oDataTable = new DataTable($iListId, $oSet, $aClassAliases, $sTableId); - $oSettings = DataTableSettings::GetDataModelSettings($aClassAliases, $bViewLink, array($sClassAlias => $aList)); - - if ($bDisplayLimit) - { - $iDefaultPageSize = appUserPreferences::GetPref('default_page_size', - MetaModel::GetConfig()->GetMinDisplayLimit()); - $oSettings->iDefaultPageSize = $iDefaultPageSize; - } - else - { - $oSettings->iDefaultPageSize = 0; - } - $oSettings->aSortOrder = MetaModel::GetOrderByDefault($sClassName); - - return $oDataTable->Display($oPage, $oSettings, $bDisplayMenu, $sSelectMode, $bViewLink, $aExtraParams); + return $oTable; + //DataTableUIBlockFactory::MakeForStaticData('', $aHeader, $aRows); } /** * @param \WebPage $oPage * @param \CMDBObjectSet $oSet - * @param array $aExtraParams + * @param array $aExtraParams key used : + *
    + *
  • view_link : if true then for extkey will display links with friendly name and make column sortable, default true + *
  • menu : if true prints DisplayBlock menu, default true + *
  • display_aliases : list of query aliases that will be printed, defaults to [] (displays all) + *
  • zlist : name of the zlist to use, false to disable zlist lookup, default to 'list' + *
  • extra_fields : list of . to add to the result, separator ',', defaults to empty string + *
* * @return string * @throws \CoreException @@ -1296,15 +1471,14 @@ HTML * @throws \MissingQueryArgument * @throws \MySQLException * @throws \MySQLHasGoneAwayException + * @deprecated since 3.0.0 */ public static function GetDisplayExtendedSet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { - if (empty($aExtraParams['currentId'])) - { - $iListId = $oPage->GetUniqueId(); // Works only if not in an Ajax page !! - } - else - { + DeprecatedCallsLog::NotifyDeprecatedPhpMethod(); + if (empty($aExtraParams['currentId'])) { + $iListId = utils::GetUniqueId(); // Works only if not in an Ajax page !! + } else { $iListId = $aExtraParams['currentId']; } $aList = array(); @@ -1367,7 +1541,7 @@ HTML } // Filter the list to removed linked set since we are not able to display them here - foreach($aList[$sAlias] as $index => $sAttCode) + foreach ($aList[$sAlias] as $index => $sAttCode) { $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode); if ($oAttDef instanceof AttributeLinkedSet) @@ -1376,6 +1550,11 @@ HTML unset($aList[$sAlias][$index]); } } + + if (empty($aList[$sAlias])) + { + unset($aList[$sAlias], $aAuthorizedClasses[$sAlias]); + } } $sSelectMode = 'none'; @@ -1870,11 +2049,12 @@ HTML */ public static function GetSearchForm(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { - $oSearchForm = new \Combodo\iTop\Application\Search\SearchForm(); + $oSearchForm = new SearchForm(); return $oSearchForm->GetSearchForm($oPage, $oSet, $aExtraParams); } + /** * @param \WebPage $oPage * @param string $sClass @@ -1887,144 +2067,232 @@ HTML * @param int $iFlags * @param array $aArgs * @param bool $bPreserveCurrentValue Preserve the current value even if not allowed + * @param string $sInputType type of rendering used, see ENUM_INPUT_TYPE_* const * * @return string + * * @throws \ArchivedObjectException + * @throws \ConfigException * @throws \CoreException + * @throws \CoreUnexpectedValue * @throws \DictExceptionMissingString + * @throws \MySQLException + * @throws \OQLException + * @throws \ReflectionException + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\RuntimeError + * @throws \Twig\Error\SyntaxError */ - public static function GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value = '', $sDisplayValue = '', $iId = '', $sNameSuffix = '', $iFlags = 0, $aArgs = array(), $bPreserveCurrentValue = true) + public static function GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value = '', $sDisplayValue = '', $iId = '', $sNameSuffix = '', $iFlags = 0, $aArgs = array(), $bPreserveCurrentValue = true, &$sInputType = '') { $sFormPrefix = isset($aArgs['formPrefix']) ? $aArgs['formPrefix'] : ''; $sFieldPrefix = isset($aArgs['prefix']) ? $sFormPrefix.$aArgs['prefix'] : $sFormPrefix; - if ($sDisplayValue == '') - { + if ($sDisplayValue == '') { $sDisplayValue = $value; } - if (isset($aArgs[$sAttCode]) && empty($value)) - { + if (isset($aArgs[$sAttCode]) && empty($value)) { // default value passed by the context (either the app context of the operation) $value = $aArgs[$sAttCode]; } - if (!empty($iId)) - { + if (!empty($iId)) { $iInputId = $iId; - } - else - { - $iInputId = $oPage->GetUniqueId(); + } else { + $iInputId = utils::GetUniqueId(); } $sHTMLValue = ''; - if (!$oAttDef->IsExternalField()) - { + if (!$oAttDef->IsExternalField()) { $bMandatory = 'false'; - if ((!$oAttDef->IsNullAllowed()) || ($iFlags & OPT_ATT_MANDATORY)) - { + if ((!$oAttDef->IsNullAllowed()) || ($iFlags & OPT_ATT_MANDATORY)) { $bMandatory = 'true'; } - $sValidationSpan = ""; + $sValidationSpan = ""; $sReloadSpan = ""; - $sHelpText = htmlentities($oAttDef->GetHelpOnEdition(), ENT_QUOTES, 'UTF-8'); + $sHelpText = utils::EscapeHtml($oAttDef->GetHelpOnEdition()); // mandatory field control vars $aEventsList = array(); // contains any native event (like change), plus 'validate' for the form submission $sNullValue = $oAttDef->GetNullValue(); // used for the ValidateField() call in js/forms-json-utils.js $sFieldToValidateId = $iId; // can be different than the displayed field (for example in TagSet) + // List of attributes that depend on the current one + // Might be modified depending on the current field + $sWizardHelperJsVarName = "oWizardHelper{$sFormPrefix}"; + $aDependencies = MetaModel::GetDependentAttributes($sClass, $sAttCode); + switch ($oAttDef->GetEditClass()) { case 'Date': + $sInputType = self::ENUM_INPUT_TYPE_SINGLE_INPUT; $aEventsList[] = 'validate'; $aEventsList[] = 'keyup'; $aEventsList[] = 'change'; - $sPlaceholderValue = 'placeholder="'.htmlentities(AttributeDate::GetFormat()->ToPlaceholder(), - ENT_QUOTES, 'UTF-8').'"'; - $sHTMLValue = "
{$sValidationSpan}{$sReloadSpan}"; + $sPlaceholderValue = 'placeholder="'.utils::EscapeHtml(AttributeDate::GetFormat()->ToPlaceholder()).'"'; + $sDisplayValueForHtml = utils::EscapeHtml($sDisplayValue); + $sHTMLValue = << + +
{$sValidationSpan}{$sReloadSpan} +HTML; break; case 'DateTime': + $sInputType = self::ENUM_INPUT_TYPE_SINGLE_INPUT; $aEventsList[] = 'validate'; $aEventsList[] = 'keyup'; $aEventsList[] = 'change'; - $sPlaceholderValue = 'placeholder="'.htmlentities(AttributeDateTime::GetFormat()->ToPlaceholder(), - ENT_QUOTES, 'UTF-8').'"'; - $sHTMLValue = "
{$sValidationSpan}{$sReloadSpan}"; + $sPlaceholderValue = 'placeholder="'.utils::EscapeHtml(AttributeDateTime::GetFormat()->ToPlaceholder()).'"'; + $sDisplayValueForHtml = utils::EscapeHtml($sDisplayValue); + $sHTMLValue = << + +
{$sValidationSpan}{$sReloadSpan} +HTML; break; case 'Duration': + $sInputType = self::ENUM_INPUT_TYPE_MULTIPLE_INPUTS; $aEventsList[] = 'validate'; $aEventsList[] = 'change'; - $oPage->add_ready_script("$('#{$iId}_d').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); - $oPage->add_ready_script("$('#{$iId}_h').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); - $oPage->add_ready_script("$('#{$iId}_m').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); - $oPage->add_ready_script("$('#{$iId}_s').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); + $oPage->add_ready_script("$('#{$iId}_d').on('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); + $oPage->add_ready_script("$('#{$iId}_h').on('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); + $oPage->add_ready_script("$('#{$iId}_m').on('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); + $oPage->add_ready_script("$('#{$iId}_s').on('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); $aVal = AttributeDuration::SplitDuration($value); - $sDays = ""; - $sHours = ""; - $sMinutes = ""; - $sSeconds = ""; - $sHidden = ""; - $sHTMLValue = Dict::Format('UI:DurationForm_Days_Hours_Minutes_Seconds', $sDays, $sHours, $sMinutes, - $sSeconds).$sHidden." ".$sValidationSpan.$sReloadSpan; - $oPage->add_ready_script("$('#{$iId}').bind('update', function(evt, sFormId) { return ToggleDurationField('$iId'); });"); + $sDays = ""; + $sHours = ""; + $sMinutes = ""; + $sSeconds = ""; + $sHidden = ""; + $sHTMLValue = Dict::Format('UI:DurationForm_Days_Hours_Minutes_Seconds', $sDays, $sHours, $sMinutes, $sSeconds).$sHidden." ".$sValidationSpan.$sReloadSpan; + $oPage->add_ready_script("$('#{$iId}').on('update', function(evt, sFormId) { return ToggleDurationField('$iId'); });"); break; case 'Password': + $sInputType = self::ENUM_INPUT_TYPE_PASSWORD; $aEventsList[] = 'validate'; $aEventsList[] = 'keyup'; $aEventsList[] = 'change'; - $sHTMLValue = "
{$sValidationSpan}{$sReloadSpan}"; break; case 'OQLExpression': case 'Text': + $sInputType = self::ENUM_INPUT_TYPE_TEXTAREA; $aEventsList[] = 'validate'; $aEventsList[] = 'keyup'; $aEventsList[] = 'change'; + $sEditValue = $oAttDef->GetEditValue($value); + $sEditValueForHtml = utils::EscapeHtml($sEditValue); + $sFullscreenLabelForHtml = utils::EscapeHtml(Dict::S('UI:ToggleFullScreen')); $aStyles = array(); $sStyle = ''; $sWidth = $oAttDef->GetWidth(); - if (!empty($sWidth)) - { + if (!empty($sWidth)) { $aStyles[] = 'width:'.$sWidth; } $sHeight = $oAttDef->GetHeight(); - if (!empty($sHeight)) - { + if (!empty($sHeight)) { $aStyles[] = 'height:'.$sHeight; } - if (count($aStyles) > 0) - { + if (count($aStyles) > 0) { $sStyle = 'style="'.implode('; ', $aStyles).'"'; } - if ($oAttDef->GetEditClass() == 'OQLExpression') - { + $aTextareaCssClasses = []; + + if ($oAttDef->GetEditClass() == 'OQLExpression') { + $aTextareaCssClasses[] = 'ibo-query-oql'; + $aTextareaCssClasses[] = 'ibo-is-code'; + // N°3227 button to open predefined queries dialog + $sPredefinedBtnId = 'predef_btn_'.$sFieldPrefix.$sAttCode.$sNameSuffix; + $sSearchQueryLbl = Dict::S('UI:Edit:SearchQuery'); + $oPredefQueryButton = ButtonUIBlockFactory::MakeIconAction( + 'fas fa-search', + $sSearchQueryLbl, + null, + null, + false, + $sPredefinedBtnId + ); + $oPredefQueryButton->AddCSSClass('ibo-action-button') + ->SetOnClickJsCode( + <<RenderHtml(); + $oPage->add_ready_script($oPredefQueryRenderer->RenderJsInline($oPredefQueryButton::ENUM_JS_TYPE_ON_INIT)); + + $oPage->add_ready_script(<<

Use the search form above to search for objects to be added.

"; + +if ($('#ac_dlg_{$iId}').length == 0) +{ + $('body').append('
'); + $('#ac_dlg_{$iId}').dialog({ + width: $(window).width()*0.8, + height: $(window).height()*0.8, + autoOpen: false, + modal: true, + title: '$sSearchQueryLbl', + resizeStop: oACWidget_{$iId}.UpdateSizes, + close: oACWidget_{$iId}.OnClose + }); +} +JS + ); + + // test query link $sTestResId = 'query_res_'.$sFieldPrefix.$sAttCode.$sNameSuffix; //$oPage->GetUniqueId(); $sBaseUrl = utils::GetAbsoluteUrlAppRoot().'pages/run_query.php?expression='; - $sInitialUrl = $sBaseUrl.urlencode($sEditValue); - $sAdditionalStuff = "".Dict::S('UI:Edit:TestQuery').""; - $oPage->add_ready_script("$('#$iId').bind('change keyup', function(evt, sFormId) { $('#$sTestResId').attr('href', '$sBaseUrl'+encodeURIComponent($(this).val())); } );"); + $sTestQueryLbl = Dict::S('UI:Edit:TestQuery'); + $oTestQueryButton = ButtonUIBlockFactory::MakeIconAction( + 'fas fa-play', + $sTestQueryLbl, + null, + null, + false, + $sTestResId + ); + $oTestQueryButton->AddCSSClass('ibo-action-button') + ->SetOnClickJsCode( + <<RenderHtml(); + $oPage->add_ready_script($oTestQueryRenderer->RenderJsInline($oTestQueryButton::ENUM_JS_TYPE_ON_INIT)); + } else { + $sAdditionalStuff = ''; } - else - { - $sAdditionalStuff = ""; - } - // Ok, the text area is drawn here - $sHTMLValue = "
$sAdditionalStuff
{$sValidationSpan}{$sReloadSpan}"; + // Ok, the text area is drawn here + $sTextareCssClassesAsString = implode(' ', $aTextareaCssClasses); + $sHTMLValue = << +
+ +
+ + +{$sValidationSpan}{$sReloadSpan} +HTML; $oPage->add_ready_script( <<GetWidth(); - if (!empty($sWidth)) - { + if (!empty($sWidth)) { $aStyles[] = 'width:'.$sWidth; } $sHeight = $oAttDef->GetHeight(); - if (!empty($sHeight)) - { + if (!empty($sHeight)) { $aStyles[] = 'height:'.$sHeight; } - if (count($aStyles) > 0) - { + if (count($aStyles) > 0) { $sStyle = 'style="'.implode('; ', $aStyles).'"'; } - $sHeader = '
'; // will be hidden in CSS (via :empty) if it remains empty + $sHeader = '
'; // will be hidden in CSS (via :empty) if it remains empty $sEditValue = is_object($value) ? $value->GetModifiedEntry('html') : ''; - $sPreviousLog = is_object($value) ? $value->GetAsHTML($oPage, true /* bEditMode */, - array('AttributeText', 'RenderWikiHtml')) : ''; + $sPreviousLog = is_object($value) ? $value->GetAsHTML($oPage, true /* bEditMode */, array('AttributeText', 'RenderWikiHtml')) : ''; $iEntriesCount = is_object($value) ? count($value->GetIndex()) : 0; $sHidden = ""; // To know how many entries the case log already contains - $sHTMLValue = "
$sHeader$sPreviousLog
{$sValidationSpan}{$sReloadSpan}$sHidden"; + $sHTMLValue = "$sHeader
"; + $sHTMLValue .= ""; + $sHTMLValue .= "$sPreviousLog
{$sValidationSpan}{$sReloadSpan}$sHidden"; // Note: This should be refactored for all types of attribute (see at the end of this function) but as we are doing this for a maintenance release, we are scheduling it for the next main release in to order to avoid regressions as much as possible. $sNullValue = $oAttDef->GetNullValue(); - if (!is_numeric($sNullValue)) - { + if (!is_numeric($sNullValue)) { $sNullValue = "'$sNullValue'"; // Add quotes to turn this into a JS string if it's not a number } $sOriginalValue = ($iFlags & OPT_ATT_MUSTCHANGE) ? json_encode($value->GetModifiedEntry('html')) : 'undefined'; - $oPage->add_ready_script("$('#$iId').bind('keyup change validate', function(evt, sFormId) { return ValidateCaseLogField('$iId', $bMandatory, sFormId, $sNullValue, $sOriginalValue) } );"); // Custom validation function + $oPage->add_ready_script("$('#$iId').on('keyup change validate', function(evt, sFormId) { return ValidateCaseLogField('$iId', $bMandatory, sFormId, $sNullValue, $sOriginalValue) } );"); // Custom validation function // Replace the text area with CKEditor // To change the default settings of the editor, // a) edit the file /js/ckeditor/config.js // b) or override some of the configuration settings, using the second parameter of ckeditor() - $aConfig = array(); - $sLanguage = strtolower(trim(UserRights::GetUserLanguage())); - $aConfig['language'] = $sLanguage; - $aConfig['contentsLanguage'] = $sLanguage; - $aConfig['extraPlugins'] = 'disabler,codesnippet'; + $aConfig = utils::GetCkeditorPref(); $aConfig['placeholder'] = Dict::S('UI:CaseLogTypeYourTextHere'); + + // - Final config $sConfigJS = json_encode($aConfig); + WebResourcesHelper::EnableCKEditorToWebPage($oPage); $oPage->add_ready_script("$('#$iId').ckeditor(function() { /* callback code */ }, $sConfigJS);"); // Transform $iId into a CKEdit $oPage->add_ready_script( <<GetEditValue($value); $oWidget = new UIHTMLEditorWidget($iId, $oAttDef, $sNameSuffix, $sFieldPrefix, $sHelpText, $sValidationSpan.$sReloadSpan, $sEditValue, $bMandatory); @@ -2126,13 +2391,11 @@ EOF break; case 'LinkedSet': - if ($oAttDef->IsIndirect()) - { + $sInputType = self::ENUM_INPUT_TYPE_LINKEDSET; + if ($oAttDef->IsIndirect()) { $oWidget = new UILinksWidget($sClass, $sAttCode, $iId, $sNameSuffix, $oAttDef->DuplicatesAllowed()); - } - else - { + } else { $oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iId, $sNameSuffix); } $aEventsList[] = 'validate'; @@ -2142,52 +2405,55 @@ EOF break; case 'Document': + $sInputType = self::ENUM_INPUT_TYPE_DOCUMENT; $aEventsList[] = 'validate'; $aEventsList[] = 'change'; $oDocument = $value; // Value is an ormDocument object + $sFileName = ''; - if (is_object($oDocument)) - { + if (is_object($oDocument)) { $sFileName = $oDocument->GetFileName(); } - $iMaxFileSize = utils::ConvertToBytes(ini_get('upload_max_filesize')); - $sHTMLValue = "
\n"; - $sHTMLValue .= "\n"; - $sHTMLValue .= "\n"; + $sFileNameForHtml = utils::EscapeHtml($sFileName); + $bHasFile = !empty($sFileName); - $sHTMLValue .= "\n"; - $sHTMLValue .= "".htmlentities($sFileName, ENT_QUOTES, - 'UTF-8')."  "; - $sHTMLValue .= "
"; - $sHTMLValue .= "
"; - $sHTMLValue .= "
"; - $sHTMLValue .= "
\n"; - $sHTMLValue .= "\n"; - $sHTMLValue .= "\n"; - $sHTMLValue .= "{$sValidationSpan}{$sReloadSpan}\n"; - if ($sFileName == '') - { - $oPage->add_ready_script("$('#remove_attr_{$iId}').hide();"); + $iMaxFileSize = utils::ConvertToBytes(ini_get('upload_max_filesize')); + $sRemoveBtnLabelForHtml = utils::EscapeHtml(Dict::S('UI:Button:RemoveDocument')); + $sExtraCSSClassesForRemoveButton = $bHasFile ? '' : 'ibo-is-hidden'; + + $sHTMLValue = << + + + + {$sFileNameForHtml}   + + + +{$sValidationSpan}{$sReloadSpan} +HTML; + + if ($sFileName == '') { + $oPage->add_ready_script("$('#remove_attr_{$iId}').addClass('ibo-is-hidden');"); } break; case 'Image': + $sInputType = self::ENUM_INPUT_TYPE_IMAGE; $aEventsList[] = 'validate'; $aEventsList[] = 'change'; $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/edit_image.js'); $oDocument = $value; // Value is an ormDocument objectm $sDefaultUrl = $oAttDef->Get('default_image'); - if (is_object($oDocument) && !$oDocument->IsEmpty()) - { + if (is_object($oDocument) && !$oDocument->IsEmpty()) { $sUrl = 'data:'.$oDocument->GetMimeType().';base64,'.base64_encode($oDocument->GetData()); - } - else - { + } else { $sUrl = null; } - $sHTMLValue = "
\n"; + $sHTMLValue = "
\n"; $sHTMLValue .= "{$sValidationSpan}{$sReloadSpan}\n"; $aEditImage = array( @@ -2198,9 +2464,9 @@ EOF 'current_image_url' => $sUrl, 'default_image_url' => $sDefaultUrl, 'labels' => array( - 'reset_button' => htmlentities(Dict::S('UI:Button:ResetImage'), ENT_QUOTES, 'UTF-8'), - 'remove_button' => htmlentities(Dict::S('UI:Button:RemoveImage'), ENT_QUOTES, 'UTF-8'), - 'upload_button' => $sHelpText, + 'reset_button' => utils::EscapeHtml(Dict::S('UI:Button:ResetImage')), + 'remove_button' => utils::EscapeHtml(Dict::S('UI:Button:RemoveImage')), + 'upload_button' => !empty($sHelpText) ? $sHelpText : utils::EscapeHtml(Dict::S('UI:Button:UploadImage')), ), ); $sEditImageOptions = json_encode($aEditImage); @@ -2217,6 +2483,7 @@ EOF break; case 'One Way Password': + $sInputType = self::ENUM_INPUT_TYPE_PASSWORD; $aEventsList[] = 'validate'; $oWidget = new UIPasswordWidget($sAttCode, $iId, $sNameSuffix); $sHTMLValue = $oWidget->Display($oPage, $aArgs); @@ -2224,15 +2491,13 @@ EOF break; case 'ExtKey': + /** @var \AttributeExternalKey $oAttDef */ $aEventsList[] = 'validate'; $aEventsList[] = 'change'; - if ($bPreserveCurrentValue) - { + if ($bPreserveCurrentValue) { $oAllowedValues = MetaModel::GetAllowedValuesAsObjectSet($sClass, $sAttCode, $aArgs, '', $value); - } - else - { + } else { $oAllowedValues = MetaModel::GetAllowedValuesAsObjectSet($sClass, $sAttCode, $aArgs); } $sFieldName = $sFieldPrefix.$sAttCode.$sNameSuffix; @@ -2240,41 +2505,40 @@ EOF $aExtKeyParams['iFieldSize'] = $oAttDef->GetMaxSize(); $aExtKeyParams['iMinChars'] = $oAttDef->GetMinAutoCompleteChars(); $sHTMLValue = UIExtKeyWidget::DisplayFromAttCode($oPage, $sAttCode, $sClass, $oAttDef->GetLabel(), - $oAllowedValues, $value, $iId, $bMandatory, $sFieldName, $sFormPrefix, $aExtKeyParams); + $oAllowedValues, $value, $iId, $bMandatory, $sFieldName, $sFormPrefix, $aExtKeyParams, false, $sInputType); $sHTMLValue .= "\n"; + + $bHasExtKeyUpdatingRemoteClassFields = ( + array_key_exists('replaceDependenciesByRemoteClassFields', $aArgs) + && ($aArgs['replaceDependenciesByRemoteClassFields']) + ); + if ($bHasExtKeyUpdatingRemoteClassFields) { + // On this field update we need to update all the corresponding remote class fields + // Used when extkey widget is in a linkedset indirect + $sWizardHelperJsVarName = $aArgs['wizHelperRemote']; + $aDependencies = $aArgs['remoteCodes']; + } + break; case 'RedundancySetting': - $sHTMLValue = ''; - $sHTMLValue .= ''; - $sHTMLValue .= ''; - $sHTMLValue .= ''; - $sHTMLValue .= ''; - $sHTMLValue .= '
'; $sHTMLValue .= '
'; $sHTMLValue .= $oAttDef->GetDisplayForm($value, $oPage, true); $sHTMLValue .= '
'; - $sHTMLValue .= '
'.$sValidationSpan.$sReloadSpan.'
'; - $oPage->add_ready_script("$('#$iId :input').bind('keyup change validate', function(evt, sFormId) { return ValidateRedundancySettings('$iId',sFormId); } );"); // Custom validation function + $sHTMLValue .= '
'.$sValidationSpan.$sReloadSpan.'
'; + $oPage->add_ready_script("$('#$iId :input').on('keyup change validate', function(evt, sFormId) { return ValidateRedundancySettings('$iId',sFormId); } );"); // Custom validation function break; case 'CustomFields': - $sHTMLValue = ''; - $sHTMLValue .= ''; - $sHTMLValue .= ''; - $sHTMLValue .= ''; // No validation span for this one: it does handle its own validation! - $sHTMLValue .= ''; - $sHTMLValue .= '
'; $sHTMLValue .= '
'; $sHTMLValue .= '
'; - $sHTMLValue .= '
'; - $sHTMLValue .= '
'; - $sHTMLValue .= '
'.$sReloadSpan.'
'; + $sHTMLValue .= ''; + $sHTMLValue .= '
'.$sReloadSpan.'
'; // No validation span for this one: it does handle its own validation! $sHTMLValue .= "\n"; $oForm = $value->GetForm($sFormPrefix); - $oRenderer = new \Combodo\iTop\Renderer\Console\ConsoleFormRenderer($oForm); - $aRenderRes = $oRenderer->Render(); + $oPredefQueryRenderer = new ConsoleFormRenderer($oForm); + $aRenderRes = $oPredefQueryRenderer->Render(); $aFieldSetOptions = array( 'field_identifier_attr' => 'data-field-id', @@ -2295,52 +2559,53 @@ EOF $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/form_field.js'); $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/subform_field.js'); $oPage->add_ready_script( -<<